Learn OLE DB Development with Visual C++ 6.0
Nathan Wallace
Wordware Publishing, Inc.
Library of Congress Cataloging-in-Publication Data Wallace, Nathan Learn OLE DB Development with Visual C++ 6.0 / by Nathan Wallace. p. cm. Includes index. ISBN 1-55622-634-9 (pbk.) 1. Microsoft Visual C++. 2. Database management. 3. OLE (Computer file) I. Title. QA76.73.C153W314 1999 005.7—dc21 99-34012 CIP
© 2000, Wordware Publishing, Inc. All Rights Reserved 2320 Los Rios Boulevard Plano, Texas 75074 No part of this book may be reproduced in any form or by any means without permission in writing from Wordware Publishing, Inc. Printed in the United States of America
ISBN 1-55622-634-9 10 9 8 7 6 5 4 3 2 1 0001
Product names mentioned are used for identification purposes only and may be trademarks of their respective companies. All inquiries for volume purchases of this book should be addressed to Wordware Publishing, Inc., at the above address. Telephone inquiries may be made by calling: (972) 423-0090
Dedication To Beth Kohler. Thanks for all the help and patience.
Acknowledgments Like so many complex undertakings, this book would not have existed without support from many people beside the guy who tickled the keyboard. I’d like to take a moment to thank Beth Kohler, who tried to make vague sense of my frantic scribblings. Any leftover typos or illogic is entirely mine. A big thank you to Russell Stultz, owner of Wordware Publishing, for his care and thoughtfulness in a business all too often filled with the reverse. And a final thanks to my wife, Laura, for her kindness, support, and patience during the creative process. Above all, I need to thank the Totoro, because without him, I wouldn’t be here at all!
Contents Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiii Chapter One
A COM Primer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 What is COM? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 How Did COM Happen? . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 Why Do We Need COM? . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 How COM Works. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 The COM Server. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 The COM ClassFactory . . . . . . . . . . . . . . . . . . . . . . . . . . 8 The COM Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 The COM Reference Count . . . . . . . . . . . . . . . . . . . . . . . . 9 The COM System in Windows . . . . . . . . . . . . . . . . . . . . . . 10 The COM Client . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 COM is Static, Automation is Dynamic. . . . . . . . . . . . . . . . . . . 10 Automation Opens COM to the World . . . . . . . . . . . . . . . . . . . 12 From Automation to OCX to ActiveX . . . . . . . . . . . . . . . . . . . . 13 ActiveX Controls are Automation Servers with a User Interface . . . . . . 16 Stock Properties and Property Pages Standardize ActiveX Control Behavior . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 Connection Points Allow Events to be Sent from ActiveX Controls to Containers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 Persistence Allows ActiveX Controls to Save Their State Over Time. . . . 21 Where We Go From Here . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Chapter Two
An ATL Primer . . . . . . . . . . . . . . . . . . . ATL in Visual C++ . . . . . . . . . . . . . . . . . . Getting ATL for Older Visual C++ Versions . . . . ATL’s Online Documentation . . . . . . . . . . . . Creating ATL Projects with the ATL AppWizard . . . The New Dialog . . . . . . . . . . . . . . . . . . The ATL AppWizard . . . . . . . . . . . . . . . . The Confirmation Dialog. . . . . . . . . . . . . . Adding COM Interfaces with the ATL Object Wizard The Object Wizard Objects. . . . . . . . . . . . . The Object Wizard Controls . . . . . . . . . . . . The Object Wizard Miscellaneous Elements . . . . The Object Wizard Data Access Elements . . . . . The Object Wizard Names Tab . . . . . . . . . . . The Object Wizard Attributes Tab . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
23 23 24 24 25 25 26 26 27 27 27 28 28 28 29
vi
n Contents Specialized Object Wizard Tabs . . . . . . . . . . . . . . . . . Creating Functions and Properties with the ATL Interface Wizards The ClassView Shortcut Menu . . . . . . . . . . . . . . . . . . The Add Method to Interface Dialog . . . . . . . . . . . . . . . The Add Property to Interface Dialog . . . . . . . . . . . . . . The Edit Attributes Dialog . . . . . . . . . . . . . . . . . . . . Some Advanced Topics for ATL Projects . . . . . . . . . . . . . . The Proxy Generator . . . . . . . . . . . . . . . . . . . . . . . Advanced Servers . . . . . . . . . . . . . . . . . . . . . . . . ATL Code (Templates and Macros, Oh My!) . . . . . . . . . . . . Templates Create Custom Classes from Standard C++ Code . . Macros Expand into Customized Code. . . . . . . . . . . . . . What’s New with ATL Version 3.0? . . . . . . . . . . . . . . . . . Version 3.0 Changes to the AppWizard . . . . . . . . . . . . . Version 3.0 Changes to the ATL Object Wizard . . . . . . . . . Version 3.0 Changes to the ClassView Context Menus . . . . . . The Add Windows Message Handler Option . . . . . . . . The Implement Connection Point Option . . . . . . . . . The Implement Interface Option . . . . . . . . . . . . . . Where We Go From Here . . . . . . . . . . . . . . . . . . . . . .
Chapter Three
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
29 30 30 30 31 32 32 32 33 33 33 33 34 34 35 36 36 38 39 40
An MFC Primer. . . . . . . . . . . . . . . . . . . . . . . . . . . MFC in Visual C++ . . . . . . . . . . . . . . . . . . . . . . . . . MFC’s Online Documentation . . . . . . . . . . . . . . . . . . . Creating MFC Projects with the MFC ActiveX ControlWizard . . . . The New Dialog . . . . . . . . . . . . . . . . . . . . . . . . . . The MFC ActiveX ControlWizard . . . . . . . . . . . . . . . . . The Confirmation Dialog. . . . . . . . . . . . . . . . . . . . . . Working MFC Magic with the ClassWizard. . . . . . . . . . . . . . The ClassWizard Message Maps Tab . . . . . . . . . . . . . . . . The ClassWizard Member Variables Tab . . . . . . . . . . . . . . The ClassWizard Automation Tab . . . . . . . . . . . . . . . . . The ClassWizard ActiveX Events Tab. . . . . . . . . . . . . . . . The ClassWizard Class Info Tab . . . . . . . . . . . . . . . . . . Augmenting ActiveX Control Features with ClassWizard Dialogs . . The ClassWizard New Class Dialog . . . . . . . . . . . . . . . . The ClassWizard Add Method Dialog . . . . . . . . . . . . . . . The ClassWizard Add Property Dialog . . . . . . . . . . . . . . . The ClassWizard Add Event Dialog . . . . . . . . . . . . . . . . Some Advanced Topics for MFC ActiveX Control Projects . . . . . . Using Windows Events with ClassWizard . . . . . . . . . . . . . Editing Property Pages . . . . . . . . . . . . . . . . . . . . . . . Connecting Property Page Controls to ActiveX Control Properties with ClassWizard . . . . . . . . . . . . . . . . . . . . . . . . MFC Code (Classes and Macros, Oh My!) . . . . . . . . . . . . . . MFC Classes Encapsulate Much Windows Functionality in C++ Wrappers . . . . . . . . . . . . . . . . . . . . . . . . . . . . Macros Expand into Customized Code. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
41 41 41 42 42 43 44 44 45 45 46 46 47 47 47 48 48 49 49 49 50
. . . 51 . . . 52 . . . 52 . . . 52
Contents n
vii
Where We Go From Here . . . . . . . . . . . . . . . . . . . . . . . . . . 52 Chapter Four
An OLE DB Primer. . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 IAccessor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 IAccessor::AddRefAccessor. . . . . . . . . . . . . . . . . . . . . . . . 54 IAccessor::CreateAccessor . . . . . . . . . . . . . . . . . . . . . . . . 55 IAccessor::GetBindings. . . . . . . . . . . . . . . . . . . . . . . . . . 63 IAccessor::ReleaseAccessor . . . . . . . . . . . . . . . . . . . . . . . 65 IColumnsInfo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 IColumnsInfo::GetColumnInfo . . . . . . . . . . . . . . . . . . . . . . 67 IColumnsInfo::MapColumnIDs. . . . . . . . . . . . . . . . . . . . . . 74 ISourcesRowset . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 ISourcesRowset::GetSourcesRowset . . . . . . . . . . . . . . . . . . . 75 IDBCreateSession. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 IDBCreateSession::CreateSession . . . . . . . . . . . . . . . . . . . . 79 IDBInfo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80 IDBInfo::GetKeywords . . . . . . . . . . . . . . . . . . . . . . . . . . 80 IDBInfo::GetLiteralInfo . . . . . . . . . . . . . . . . . . . . . . . . . 84 IGetDataSource . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90 IGetDataSource::GetDataSource . . . . . . . . . . . . . . . . . . . . . 90 IOpenRowset . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 IOpenRowset::OpenRowset . . . . . . . . . . . . . . . . . . . . . . . 91 ISessionProperties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 ISessionProperties::GetProperties . . . . . . . . . . . . . . . . . . . . 95 ISessionProperties::SetProperties . . . . . . . . . . . . . . . . . . . . 97 IDBCreateCommand . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 IDBCreateCommand::CreateCommand . . . . . . . . . . . . . . . . . 99 ITableDefinition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100 ITableDefinition::AddColumn . . . . . . . . . . . . . . . . . . . . . 100 ITableDefinition::CreateTable . . . . . . . . . . . . . . . . . . . . . 102 ITableDefinition::DropColumn . . . . . . . . . . . . . . . . . . . . . 107 ITableDefinition::DropTable . . . . . . . . . . . . . . . . . . . . . . 108 ICommand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 ICommand::Cancel . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 ICommand::Execute . . . . . . . . . . . . . . . . . . . . . . . . . . 110 ICommand::GetDBSession . . . . . . . . . . . . . . . . . . . . . . . 116 ICommandProperties . . . . . . . . . . . . . . . . . . . . . . . . . . . 117 ICommandProperties::GetProperties . . . . . . . . . . . . . . . . . . 118 ICommandProperties::SetProperties . . . . . . . . . . . . . . . . . . 121 ICommandText . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123 ICommandText::GetCommandText . . . . . . . . . . . . . . . . . . . 124 ICommandText::SetCommandText . . . . . . . . . . . . . . . . . . . 125 IColumnsRowset . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126 IColumnsRowset::GetAvailableColumns . . . . . . . . . . . . . . . . 127 IColumnsRowset::GetColumnsRowset . . . . . . . . . . . . . . . . . 128 Required Metadata Columns . . . . . . . . . . . . . . . . . . . . . . 132 Optional Metadata Columns . . . . . . . . . . . . . . . . . . . . . . 134 ICommandPrepare . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
viii
n Contents ICommandPrepare::Prepare . . . . . . . . . . . . ICommandPrepare::Unprepare . . . . . . . . . . . ICommandWithParameters . . . . . . . . . . . . . . ICommandWithParameters::GetParameterInfo . . . ICommandWithParameters::MapParameterNames . ICommandWithParameters::SetParameterInfo . . . IRowset . . . . . . . . . . . . . . . . . . . . . . . . IRowset::AddRefRows . . . . . . . . . . . . . . . IRowset::GetData . . . . . . . . . . . . . . . . . . IRowset::GetNextRows . . . . . . . . . . . . . . . IRowset::ReleaseRows . . . . . . . . . . . . . . . IRowset::RestartPosition . . . . . . . . . . . . . . IRowsetInfo . . . . . . . . . . . . . . . . . . . . . . IRowsetInfo::GetProperties . . . . . . . . . . . . . IRowsetInfo::GetReferencedRowset . . . . . . . . IRowsetInfo::GetSpecification . . . . . . . . . . . IRowsetChange . . . . . . . . . . . . . . . . . . . . IRowsetChange::DeleteRows . . . . . . . . . . . . IRowsetChange::InsertRow . . . . . . . . . . . . . IRowsetChange::SetData . . . . . . . . . . . . . . IRowsetUpdate . . . . . . . . . . . . . . . . . . . . IRowsetUpdate::GetOriginalData. . . . . . . . . . IRowsetUpdate::GetPendingRows . . . . . . . . . IRowsetUpdate::GetRowStatus . . . . . . . . . . . IRowsetUpdate::Undo . . . . . . . . . . . . . . . IRowsetUpdate::Update . . . . . . . . . . . . . . ITransaction . . . . . . . . . . . . . . . . . . . . . . ITransaction::Abort . . . . . . . . . . . . . . . . . ITransaction::Commit . . . . . . . . . . . . . . . ITransaction::GetTransactionInfo . . . . . . . . . . ITransactionOptions. . . . . . . . . . . . . . . . . . ITransactionOptions::GetOptions . . . . . . . . . . ITransactionOptions::SetOptions . . . . . . . . . . ITransactionObject . . . . . . . . . . . . . . . . . . ITransactionObject::GetTransactionObject . . . . . ITransactionJoin. . . . . . . . . . . . . . . . . . . . ITransactionJoin::GetOptionsObject . . . . . . . . ITransactionJoin::JoinTransaction . . . . . . . . . IRowsetIndex . . . . . . . . . . . . . . . . . . . . . IRowsetIndex::GetIndexInfo . . . . . . . . . . . . IRowsetIndex::Seek. . . . . . . . . . . . . . . . . IRowsetIndex::SetRange . . . . . . . . . . . . . . IErrorRecords . . . . . . . . . . . . . . . . . . . . . IErrorRecords::AddErrorRecord . . . . . . . . . . IErrorRecords::GetBasicErrorInfo. . . . . . . . . . IErrorRecords::GetCustomErrorObject . . . . . . . IErrorRecords::GetErrorInfo . . . . . . . . . . . . IErrorRecords::GetErrorParameters. . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
138 140 140 141 144 145 150 153 155 157 162 165 167 168 171 172 173 174 177 182 187 188 191 193 195 199 203 204 205 207 209 209 209 210 211 211 212 212 214 215 216 219 222 222 224 225 225 226
Contents n
Chapter Five
ix
IErrorRecords::GetRecordCount . . IErrorLookup . . . . . . . . . . . . . IErrorLookup::GetErrorDescription. IErrorLookup::GetHelpInfo . . . . . IErrorLookup::ReleaseErrors . . . . ISQLErrorInfo . . . . . . . . . . . . . ISQLErrorInfo::GetSQLInfo . . . . . Where We Go From Here . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
227 228 229 230 231 232 232 233
An ADO Primer . . . . . . . . . . ADO Collections . . . . . . . . . . . The Errors Collection . . . . . . . The Fields Collection . . . . . . . The Parameters Collection . . . . The Properties Collection . . . . . The ADO Connection Object . . . The ADO Command Object . . . . The ADO Recordset Object . . . . The ADO Field Object . . . . . . The ADO Error Object . . . . . . The ADO Property Object. . . . . The ADO Parameter Object . . . . ADO Events . . . . . . . . . . . . . BeginTransComplete Event . . . . CommitTransComplete Event. . . ConnectComplete Event . . . . . Disconnect Event . . . . . . . . . EndOfRecordset Event . . . . . . ExecuteComplete Event . . . . . FetchComplete Event . . . . . . . FetchProgress Event . . . . . . . FieldChangeComplete Event . . . InfoMessage Event . . . . . . . . MoveComplete Event . . . . . . . OnError Event . . . . . . . . . . OnReadyStateChange Event . . . RecordChangeComplete Event . . RecordsetChangeComplete Event. RollbackTransComplete Event . . WillChangeField Event . . . . . . WillChangeRecord Event . . . . . WillChangeRecordset Event . . . WillConnect Event . . . . . . . . WillExecute Event . . . . . . . . WillMove Event . . . . . . . . . . Where We Go From Here . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
235 235 236 237 239 241 242 259 264 293 299 301 304 308 308 309 309 310 310 311 312 313 313 314 314 315 316 316 317 317 318 319 319 320 321 322 323
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
x
n Contents
Chapter Six
Creating and Using Simple OLE DB Providers and Consumers with ATL and MFC . . . . . . . . . . . . . . . . . . . . . . . . . . . 325 OLE DB Template Changes are Required in ATL . . . . . . . . . . . . . 325 Creating an OLE DB Provider COM DLL with ATL . . . . . . . . . . . . 326 What the Wizard Wrought . . . . . . . . . . . . . . . . . . . . . . . 329 Gentlemen, Start Your Keyboards! . . . . . . . . . . . . . . . . . . . 341 Returning a Custom Rowset . . . . . . . . . . . . . . . . . . . . . . 342 Defining a Custom User Record . . . . . . . . . . . . . . . . . . . . 345 Redefining the Rowset Implementation Template to Support Bookmarks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 347 Implementing Bookmarks in the Custom Rowset Implementation . . . 350 Implementing Bookmarks in the Custom IRowsetLocate Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . 355 Implementing Variable COLUMNIFO in the Custom Rowset Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . 360 Disabling Schema Support . . . . . . . . . . . . . . . . . . . . . . . 364 Building the OLE DB Provider DLL . . . . . . . . . . . . . . . . . . . 368 Creating an OLE DB Consumer Application with MFC . . . . . . . . . . 368 Building the MFC OLE DB Consumer Application . . . . . . . . . . . 383 Creating the Text File Database and Testing the OLE DB Provider with the MFC Consumer Application . . . . . . . . . . . . . . . . . . . . 384 Where We Go From Here . . . . . . . . . . . . . . . . . . . . . . . . . 385
Chapter Seven
Creating OLE DB Service Providers with ATL. . . . . . . . . . . Service Providers Have the Best of Both Worlds in OLE DB . . . . . . . Creating an OLE DB Service Provider COM DLL Consumer with ATL . . Gentlemen, Start Your Keyboards! . . . . . . . . . . . . . . . . . . . Building the OLE DB Service Provider Consumer DLL . . . . . . . . . Creating an OLE DB Service Provider COM DLL Provider with ATL . . . Gentlemen, Start Your Keyboards! . . . . . . . . . . . . . . . . . . . Building the OLE DB Provider DLL . . . . . . . . . . . . . . . . . . . Creating an OLE DB Service Provider Consumer Application with MFC . Building the MFC OLE DB Consumer Application . . . . . . . . . . . Testing the OLE DB Service Provider with the MFC Consumer Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Where We Go From Here . . . . . . . . . . . . . . . . . . . . . . . . .
387 387 388 393 400 400 401 403 404 417
Creating and Using Simple OLE DB Consumers in ATL. OLE DB Consumer Templates in Action. . . . . . . . . . . . . . Creating an OLE DB Consumer COM Server with ATL . . . . . . Creating an ODBC-Compliant Database with Access . . . . . . Creating an ODBC Connection with Control Panel . . . . . . . Creating an OLE DB Provider COM DLL with ATL . . . . . . . . What the Wizard Wrought . . . . . . . . . . . . . . . . . . . Customizing the OLE DB Consumer . . . . . . . . . . . . . . Disabling Rowset Return on Database Open . . . . . . . . . . Adding OLE DB Consumer and Microsoft Agent Support . . . Adding the COM Server Code to Use the OLE DB Consumer . .
419 419 420 420 423 426 433 436 437 438 440
Chapter Eight
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
417 418
Contents n
xi
Building the OLE DB Consumer DLL . . . . . . . . . . . . . . . Creating a Client Application for an OLE DB Consumer with MFC. Building the MFC OLE DB Consumer Application . . . . . . . . Testing the OLE DB Consumer with the MFC Application . . . . Where We Go from Here . . . . . . . . . . . . . . . . . . . . . .
. . . . .
. . . . .
. . . . .
449 449 469 469 470
Chapter Nine
Creating and Using Complex OLE DB Consumers in OLE DB Consumer Templates in MFC. . . . . . . . . . . . . Creating an OLE DB Consumer MDI Application with MFC . Building the MFC OLE DB Consumer Application . . . . . Testing the MFC OLE DB Consumer Application . . . . . . Where We Go From Here . . . . . . . . . . . . . . . . . . .
. . . . . .
. . . . . .
. . . . . .
471 471 471 498 498 499
Chapter Ten
Putting OLE DB on the Internet . . . . . . . . . . . . . . . . . Using the LPK_TOOL Application to Create HTML Run-time License LPK Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Downloading the ActiveX SDK . . . . . . . . . . . . . . . . . . . . Code Signing using Authenticode™—Obtaining an SPC . . . . . . . Code Signing using Authenticode™—Digitally Signing Code . . . . Obtaining and Using ActiveX Control Pad from Microsoft . . . . . . Automatic COM
Tags/Visual Editing . . . . . . . . . Creation of HTML Layout Controls . . . . . . . . . . . . . . . . . Visual Editing of HTML Layout Controls . . . . . . . . . . . . . . ActiveX Script Wizard for HTML and ALX . . . . . . . . . . . . . Obtaining and Using File Transfer Protocol (FTP) Software to Place COM Elements on the Internet . . . . . . . . . . . . . . . . . . . Using Internet Explorer to Access COM Over the Internet and World Wide Web. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Mastering COM Over the Internet . . . . . . . . . . . . . . . . . . Where We Go From Here . . . . . . . . . . . . . . . . . . . . . . .
MFC . . . . . . . . . . . . . . .
. . 501 . . . . . . . . .
. . . . . . . . .
501 504 505 510 513 514 515 517 519
. . 521 . . 526 . . 529 . . 529
Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 531
A note to our readers: Wordware Publishing, Inc. is offering a complete series of ActiveX/COM-oriented books for Visual C++ programmers. Much of the information within these books overlaps to ensure that each book stands alone. However, each focuses on a specific ActiveX/COM-related technology. Therefore, each book provides an essential set of resources for programmers who must either write stand-alone COM programs or include one or more COM modules as part of other applications. Learn ActiveX Development Using Visual C++ 6.0
Learn OLE DB Development with Visual C++ 6.0
Learn ActiveX Template Library Development with Visual C++ 6.0
Learn Microsoft Transaction Server Development Using Visual C++ 6.0
Introduction Welcome to Learn OLE DB Development with Visual C++ 6.0. This book is the result of many hours of labor at the computer, and over the various online and printed documentation for OLE DB. I became intrigued with OLE DB when it was first released, since for the first time it permitted any type of data to be accessed using a standard syntax. When support for OLE DB was added to Visual C++ 6.0, I proposed doing a book on it, and the industry agreed one was needed. I hope you enjoy the book, and find it as exciting as I do!
Who Can Use This Book? Anyone can use this book, provided they are at least familiar with the syntax of Visual C++ as a language, and know something about SQL programming (the book does not cover this aspect of OLE DB at all due to space limitations). You can be a rank beginner with COM, ATL, and/or MFC and still make full use of this book; the chapters are carefully presented in an order of -increasing complexity to allow you to come up to speed as you learn about ATL, MFC, OLE DB, and COM together. If you are knowledgeable in one or more areas but not in others, then you can skip around as you develop increasingly powerful projects in a hands-on fashion.
Hands-On Explanations When I was learning how to use computers, I absolutely hated computer books that were all generalities and no real instructions. This book completely avoids that pitfall by using detailed, step-by-step guides to creating all the projects and support systems needed by OLE DB. You can identify where the hands-on stuff starts with the icon at the left. If you’re looking for precise details of how to create an OLE DB component or develop an OLE DB application, flip through the chapter until you see this symbol, fire up your computer, and dive in!
xiv
n Introduction The other thing I’ve disliked about computer books (even some that I’ve written) is that they contain lots and lots of code, and then an explanatory paragraph or two which has little connection with the code. You find a line you don’t understand in the listing, and search for hours trying to see where it is explained or why it seems unusual. This book remedies this problem, with the line-by-line approach. You can identify where this information starts with the icon at the left. These sections first contain a numbered code listing, and then a breakdown of every major line or group of lines that isn’t automatically generated. The explanations tell you what the code does and why it’s important (although they don’t do a blow-by-blow coverage because that’s the province of the source code comments). For OLE DB programming, this approach needs some modification. While the step-by-step and line-by-line systems work just fine, I also use a modified system called Before and After and the Wizard’s Wand. The Before section gives a listing of the wizard-generated code, and then provides directions in a step-by-step fashion as to which elements to remove, add, or modify using line numbers. The After section gives the finished code along with a line-by-line explanation of what the code (both changed and unchanged) does. The Wizard’s Wand section gives a listing of wizard-generated code and details what it does in the line-by-line format.
What’s In This Book? Chapter 1—A COM Primer Before you can start using C++ to write OLE DB consumers and providers, you need a good working understanding of what the Component Object Model is, how it works, and why it matters. This chapter will give you all that information, and in the process bring you up to speed on both the history and underlying technologies behind COM, Automation, and ActiveX.
Chapter 2—An ATL Primer ATL stands for “ActiveX Template Library,” a name that encompasses a lot of territory. First, it invokes ActiveX, which in this context means all of COM except COM+. The template portion of ATL’s name indicates that this system uses the C++ template mechanism heavily, along with a lot of very sophisticated macros. The library part of ATL indicates that, unlike some other template-based systems, ATL provides extensive source code rather than binary OBJ files. This source code is modified to produce the final code, resulting in executables that are both quite small for what they do and very
Introduction n
xv
fast, with minimum dependencies on external DLL files. There is a lot to ATL, and it often confuses beginners to either COM or itself. There is enough material in the ATL system for two or three full-sized books; this particular book is going to focus on the basics. This chapter will cover how ATL fits into Visual C++ and what its various wizard dialogs do. A brief look is taken at the templates and macros ATL uses, but because these are more advanced topics, they won’t be covered in great detail. (Individual chapters will cover the specific macros and template classes they use.) When you’ve finished reading this chapter, you’ll be ready to start creating powerful ATL-based COM servers and ActiveX controls for use with OLE DB applications!
Chapter 3—An MFC Primer MFC stands for the Microsoft Foundation Classes. MFC is a set of C++ objects that encapsulate access to a number of Windows programming aspects, including the GDI (graphical device interface), file operations, window creation, message handling, and various specialized aspects of the OS. One of these specialized aspects is ActiveX; support for it is added via existing support for COM (the Component Object Model, discussed in Chapter 1). MFC’s main claim to fame is its provision of C++ objects rather than raw API calls for Windows programming, and this holds true for ActiveX as well. There is a lot to MFC, and it often confuses beginners to either COM or itself. There is enough material in the MFC system for two or three full-sized books; this particular book is going to focus on the basics. This chapter will cover how MFC fits into Visual C++ and what its various wizard dialogs do. A brief look is taken at the classes MFC uses, but because these are more advanced topics, they won’t be covered in great detail. (Individual chapters will cover the specific classes they use.) When you’ve finished reading this chapter, you’ll be ready to start creating MFC-based COM servers for OLE DB!
Chapter 4—An OLE DB Primer OLE DB programming consists of using a set of powerful COM interfaces. These interfaces (along with a couple of API calls) are what developers use to make their database components work with OLE DB seamlessly. This chapter will give you complete information on these API calls and interfaces, and in the process explain what OLE DB is and how it works. There is one important point to remember, however: OLE DB has absolutely no database functionality of its own! Instead, it relies on the powerful Microsoft technology known as ADO (ActiveX Data Objects) for actual programmatic connection to its clients.
Chapter 5—An ADO Primer Microsoft has two high-level database technologies: OLE DB (which isn’t an acronym) and MTS (which is, and stands for Microsoft Transaction Server). Both of these powerful database systems rely on a single technology to connect with end users: ActiveX Data Objects (ADO). ADO is a set of COM
xvi
n Introduction objects (i.e., interfaces) designed to connect with database systems via the OLE DB technology, but users don’t need to know anything about OLE DB to use ADO. Instead, they simply create appropriate ADO objects and make method calls on them to connect with databases and obtain and set information. For OLE DB developers, ADO is the vehicle their users need to connect with the OLE DB providers and consumers they create. For MTS programmers, ADO is the system they use to interact with databases under the umbrella of MTS. (ADO itself has several advanced capabilities, including RDS (Remote Data Service) and MD (Multi-Dimensional) database interactions, which we won’t go into here.) Either way, database programmers using Microsoft technology need a detailed reference about ADO. This chapter provides that reference, with a complete writeup on all the collections, objects, methods, and events of the ADO system. Dive in, and your database development will never be the same!
Chapter 6—Creating and Using Simple OLE DB Providers and Consumers with ATL and MFC At this point, you understand how to use COM, ATL, MFC, OLE DB, and ADO. All these acronyms add up to the most powerful database technology the PC world has ever seen. Now it is time to do some actual programming and put all this information into practice. This chapter will provide you with a start in using the OLE DB templates for ATL and MFC, showing you how to create a simple (but really very powerful) OLE DB provider in ATL, and then a matching consumer in MFC.
Chapter 7—Creating OLE DB Service Providers with ATL Now that you’ve implemented your first OLE DB simple provider, you’re ready to move to a higher level in OLE DB: service providers. These OLE DB elements are both providers and consumers (although not always using OLE DB templates) that play a major role in three-tier applications for OLE DB. This chapter will show you how to implement a service provider for Access databases as dual COM servers, one of which as a consumer acquires a database record, then sends this information as COM properties to another COM server which provides it as an OLE DB provider record. An MFC application that accesses the OLE DB provider is also shown.
Chapter 8—Creating and Using Simple OLE DB Consumers in ATL In Chapter 6, you learned how to create a simple OLE DB provider with ATL, and how to develop a simple MFC front-end consumer for it. In Chapter 7, you developed a powerful three-tier ATL provider and MFC consumer supported by an MFC client. In this chapter, you’ll learn how to take advantage of the template functionality of an ATL OLE DB consumer to create a simple but very powerful COM server that can function in a web page to connect to
Introduction n
xvii
an Access database and drive the Microsoft Agent ActiveX control. When you’ve finished this chapter, you’ll be ready to move on to a powerful MDI (Multiple Document Interface) OLE DB consumer written in MFC.
Chapter 9—Creating and Using Complex OLE DB Consumers in MFC In Chapter 6, you learned how to create a simple OLE DB provider with ATL, and how to develop a simple MFC front-end consumer for it. In Chapter 7, you developed a powerful three-tier ATL provider and MFC consumer supported by an MFC client. In Chapter 8, you learned how to take advantage of the template functionality of an ATL OLE DB consumer to create a simple but very powerful COM server that can function in an application to connect to an Access database and drive the Microsoft Agent ActiveX control. This chapter shows you how to create a powerful MDI (Multiple Document Interface) OLE DB consumer written in MFC.
Chapter 10—OLE DB on the Internet At this point, you’re fully up to speed on all that Visual C++ 6.0 has to offer in terms of OLE DB; you can create and use OLE DB components and applications with the best of them. You understand the issues involved in creating OLE DB Automation servers and ActiveX controls and clients in both ATL and MFC, and you’re an ace with the underlying concepts behind OLE DB. So what’s left? The Internet, that’s what! More and more database developers are tasked by their clients to put DBMS (and thus OLE DB) functionality onto web pages, either on a local intranet or the big, bad Internet/World Wide Web itself. Doing this requires an understanding of arcane issues totally unrelated to MTS or COM, and so this chapter will fill that essential gap. This chapter will add mastery of the LPK_TOOL application for creating HTML license keys, understanding of the ActiveX Control Pad and the ActiveX SDK’s code signing tools, and familiarity with the two major tools of Internet and World Wide Web functionality, FTP and Internet Explorer, to your OLE DB skills inventory.
Source Code Duplication One of the thornier issues for a computer programming book author is: source code or no source code, and if so, how much source code? I’ve read and written books that try many different approaches to this dilemma. This book goes to one extreme: I include full source code for every project, even if it duplicates a previous project substantially. I do this so that a reader who is only interested in one type of project does not have to go hunting for the code he or she wants. For those reading the book through, this is a bit redundant, but hopefully not too much so. I feel the utility for the average “deadline reader” (like me!) is worth it.
xviii
n Introduction
Project Source and the CD The CD distributed with this book contains complete project source code for all the material in the book, organized into directories named after the chapters. In addition, there are several third-party applications and utilities. Since each hands-on section assumes you start from scratch, you don’t need any of these files to do the projects, but they’re there if you want them. To install the files on your computer, simply open a DOS window, change drives to your CD-ROM drive, and type in “XCOPY /S *.* [your target drive and directory]”. The XCOPY command using the /S switch copies all the files and directories into the new directory for you, and sets their read-only attributes to false (always a pain with CD files!). It’s also a good idea to look at the README.TXT file on the CD for any last-minute typos, bug fixes, or updates to the book. Despite our best efforts, there are always a few loose ends!
Get Updates on the World Wide Web! The author maintains a Web presence, and this book is one of the ones kept current there. The URL is: http://www.ciupkc.com/books/lodbdcpp/ default.htm. Along with the README.TXT file on the CD, this is the place to go for updates on bug and typo fixes, new and more sophisticated versions of the book’s projects, and a host of information on ActiveX, Visual C++, and WWW programming. Please drop by!
OLE DB Forever! This book is not intended to be the most definitive resource on OLE DB out there; what it does do, and does well, is bring you up to speed on the techniques needed to create a robust, fully functional set of OLE DB components and applications in the C++ development framework of your choice, and then demonstrates the truly amazing power of OLE DB in a real-world setting. So go ahead, join the OLE DB revolution! We’re way past the sky; it’s Alpha Centauri or bust!
Chapter One
A COM Primer Before you can start using C++ to write OLE DB consumers and providers, you need a good working understanding of what the Component Object Model is, how it works, and why it matters. This chapter gives you all that information, and in the process brings you up to speed on both the history and underlying technologies behind COM, Automation, and ActiveX. (If you already know all you want about COM, Automation, and ActiveX, feel free to move on to Chapters 2 or 3, depending on whether you want to program in ATL or MFC.)
What is COM? COM is an acronym that stands for the Component Object Model. It was developed at Microsoft to allow applications to share data and program code while running on an end user’s computer. Initially a limited system for allowing programs like Word and Excel to display each other’s files, it now has become a centerpiece technology of the Windows operating system. Internet Explorer 5, Internet Information Server, Microsoft Transaction Server, Active Server Pages, and Universal Data Access are only some of the current releases from Microsoft that depend wholly or partially on COM. The Component Object Model is a specification for writing applications that can be used by other applications. The specification provides details on how compilers must output their executable files for the applications to communicate, and on how the operating system must support this communication. One of the benefits of this approach is that it permits COM to be languageand even platform-independent, so that a COM application written in Visual Basic can easily work with one written in Java or C++. Since COM stands for Component Object Model, you would expect objects to be involved with it somehow. COM provides for six key features of objectoriented programming (OOP):
1
2
n Chapter One—A COM Primer n
Identity This means that each COM component must be identified as different from all other COM components. This is done by their GUID attributes.
n
State This means that COM components must keep track of changes made to their status in some way. COM components have at least the information on their usage count, and many keep internal data as well.
n
Behavior This means that COM components must implement methods to provide standard and custom functionality of some sort. They do this via their interfaces.
n
Encapsulation This means that the internal data and functionality used by a COM component is hidden from its users, available as a “black box.” Since COM only supports functions and then only their names and parameters, it nicely supports encapsulation. All the internal functions and data of COM components are completely inaccessible to users.
n
Inheritance This means that one COM component can be derived from another, using the ancestor component’s functions while adding its own. COM supports inheritance but in a special way.
n
Polymorphism This means that any two COM components that implement the same interaction can be used interchangeably. COM fully supports this capability.
The component part of COM’s name is the major driving force behind the technology. Although initially conceived as a way for Microsoft applications to display and edit each other’s data, COM has since become the leading mechanism for creating reusable applications. This is in some part due to the advent of the Internet, which provided a new and unexpected market for component software. Also, Bill Gates, the CEO of Microsoft, has long been a proponent of “component software,” which he viewed as applications created by end users out of many smaller “applets,” or components. COM was written to some degree to further this strategy.
How Did COM Happen? COM did not come about as part of a single development process. Instead, it began in Windows 3, with the Object Linking and Embedding (OLE) system. OLE 1.0, as it is now known, allowed one application, such as Word and Excel, to display another application’s data without the user having to explicitly open up the second application. Although it was exciting in its time, OLE 1.0 had two serious limitations: The embedded data could not be edited in the containing document, and there was no standardized system for storing the embedded information. These limitations of OLE 1.0 caused Microsoft to produce OLE 2, which was considered a name (pronounced “oh lay two”) rather than an acronym. OLE 2 came out with Windows 3.1. OLE 2 was the first true COM technology,
Chapter One—A COM Primer n
3
since the COM specification was not available when OLE 1.0 was released; OLE 1.0 used a different system. OLE 2 produced a new and unique form of data called a compound document, which included information from any number of OLE-supporting applications in one file, and supported editing, updating, and printing all within any one of the contributing applications. Although OLE 2 was a quantum jump ahead of OLE 1.0, it still had its own limitations, the most noticeable of which was the requirement to open a new window whenever editing of an embedded data element was required. This led to the development of the next release of OLE, called OLE Automation. This technology allowed editing inside the application where the data was embedded (called in-place editing), but also added two other major improvements over OLE 2 to the implementation of COM: It provided access to COM services from non-C++ application development environments like Visual Basic, and it provided for the creation of COM-based components that existed outside the world of compound documents. Automation was supported in Windows 3.11. This last enhancement was to have the most lasting impact on COM, because to this point Mr. Gates’ dream of component software was still not realized with the OLE implementation of COM. Instead, it had become a reality through a most unexpected mechanism: the VBX control. VBX controls were add-ins for the Visual Basic development environment, created in C++ initially and later in VB itself. They allowed VB programmers to create useful software elements like custom list boxes or printer utilities, and distribute the binary file only for reuse by other programmers, either for free or after payment for a license. But VBX controls were not implemented in COM, and while they were soon available for C++, problems remained in this crossover system. Also, Borland released its breakthrough Delphi product, which also featured proprietary reusable binary components called VCL controls. The COM team was tasked with making a reusable control that worked on COM principles rather than proprietary ones. They succeeded, and the OCX control joined the COM family. It was quickly supported in C++, Visual Basic, and Delphi, and seemed to be the end of the line for COM, since it had finally achieved its goal of reusable component software at the binary level. Then along came Java and the Internet. Just as no one expects the Spanish Inquisition (as they used to say on Monty Python), no one expected Java or the Internet, in particular the World Wide Web, to explode onto the computer stage with the force of a hydrogen bomb. Microsoft had long assumed that its dominance of the PC desktop was beyond challenge, but Java and the web browser, coupled with the Internet, represented a whole new way of approaching personal computing, one controlled by Sun and Netscape rather than Microsoft. Microsoft scrambled to meet the Java/WWW challenge, and Bill Gates developed the Internet Initiative. This led to the retooling of COM into ActiveX, with compound documents becoming ActiveX documents, OCX controls becoming ActiveX controls, and so forth. ActiveX COM elements
4
n Chapter One—A COM Primer were enhanced for Internet use, with added security features, smaller code sizes, and data elements designed for asynchronous downloading. Soon Java was corralled into the COM camp, with support for ActiveX added in Microsoft’s Visual J++ environment for Java programming. An enhanced version of Automation was added to Microsoft’s web browser, Internet Explorer, and called ActiveX scripting. Despite legal and government squabbles, ActiveX soon became a dominant technology for the Internet and World Wide Web, and continues to gain in strength both there and on the emerging corporate and public intranet/extranet systems. OLE 1.0, OLE 2, OLE Automation, OCX controls, ActiveX, and COM+ are all implementations of COM that are part of the Windows operating system. They allow developers to actually create and use COM components in various ways without having to write all the COM support code themselves. COM functionality is provided via various API functions that developers include in their programs along with appropriate references, just like the core Windows API. Although Windows was COM’s original home, it has begun to branch out recently. Microsoft has just released an implementation of COM for the Solaris variant of the UNIX and Apple Macintosh operating systems. COM has now become a central element of Microsoft’s product line. Once relegated to the application data-sharing niche, then to the VBX-replacement slot, and finally to the Java-competitor position, COM has been made the core technology for a host of new releases: n
Internet Explorer 5 This cutting-edge web browser includes full support for ActiveX controls and in fact uses one for its display interface.
n
Windows 98 Along with its integration of IE 5 into the operating system, Windows 98 also supports the Active Desktop, a COM-based system for placing web-enabled applications on the Windows desktop.
n
Internet Information Server Microsoft’s entry in the web server wars, IIS includes many powerful COM-based systems, including Active Server Pages, ISAPI Extensions, and ISAPI Filters.
n
Microsoft Transaction Server A database-oriented system, MTS uses COM to allow the combination of transactions from many different database systems and vendors, as well as non-database actions, into single activities that succeed or fail and can be “rolled back” (returned to a valid previous state in the database) without losing information if they fail.
n
OLE DB Harking back to the OLE 2 roots of COM, OLE DB allows non-database applications to interact with database-oriented technologies (like MTS) using the same techniques as ODBC databases.
Chapter One—A COM Primer n
5
Why Do We Need COM? As you look at all the technologies that are part of COM now, you may wonder why there has been all the bother. After all, programmers have been exchanging code for years, long before Microsoft was around; why the big fuss? The answer lies in four separate aspects of PC software creation: how computer code is turned into working programs, how developers actually write programs, how memory is used in Windows, and how Windows DLL files work. The core of a computer program is a set of text files called its source code. Since you’re reading this book, you are very likely a programmer yourself and have written many such files. You know how much of an investment each project takes in terms of your time, your expertise, and your company’s resources. Source code is literally money in the bank, a sizable investment. The concept of giving this away to other companies, perhaps even competing companies, is hardly a good idea. But prior to COM, giving away source code was precisely how programmers had to exchange their code! A prime example is the C and C++ libraries used for many years prior to COM. These are binary files (LIB) rather than source code (C or CPP), but in most cases they were accompanied by their C and CPP source code simply because without it the libraries were not very useful. Quite often, they had to be rewritten for the needs of each project or the style of individual developers. The reason for this lies in the incompatibility of binary computer code at the compiler level. Each compiler produces a set of machine language instructions (binary numbers) that are designed to be fed by the operating system into the CPU of the computer in structured ways to produce program behavior. While there are standards for programming languages like C++, there are currently no standards for the binary output of compilers. Thus, without the source code to draw upon, one development environment has no way to effectively interpret and use the binary output of another programming system, even one that uses the same language. Even more difficulty lies in the area of reusing binary files at the application level. Once source code has been turned into a binary executable file, it has no natural capability to import and use binary files produced by another application as part of its own running machine language code. There is no “roadmap” as to what the various number codes mean inside a machine language file. A number may represent a data item, a processor directive, or an internal value used in some calculation; the outside program has no way to know this. An example of this problem is the need for filters for word processing documents. Each modern word processor application stores its information in a binary format, including not only the text characters typed in by the writer,
6
n Chapter One—A COM Primer but also formatting data, fonts, graphics, annotations, and so on. Each program has a different format for storing this information; the result is that a WordPerfect WP file cannot be used automatically by Microsoft Word, and vice versa. This problem was solved by the adoption of open formats. Each word processing application company published its internal data format freely; this allowed competing and complementary products to create filters that used the data format specification as a roadmap to read in and display and manipulate the other application’s binary data. Once the roadmap was available, writing the filtering code was easy; this allowed binary compatibility between the different applications’ data files. This led to the conclusion that what programmers need is a version of their own open formats and roadmap-based filters. Once there is a standard way to create binary computer files and a well-known roadmap to interpret them, writing filtering programs to use any sort of binary information is fairly easy, whether the data is a word processing document or code for use inside another application. Interestingly enough, the developers of the Windows 3.0 operating system at Microsoft ran into this problem from a different angle; they needed to share low-level system functions like graphics and file management between parts of the OS being developed by different teams. Their solution, also developed for efficiency reasons, was the DLL, or dynamic-link library. Initially only available to the operating system, DLLs would become the mechanism used to implement COM itself. A dynamic-link library is an executable file, like an EXE, that is never allowed to run on its own. Instead, it is loaded into memory by the Windows operating system when requested by an actual application. The requesting application then uses code from the DLL just as if the DLL’s code had been included in its own binary file; this is the long-desired binary compatibility for applications! As long as reusable software components are not easily available,a company must spend money time and again to “reinvent the wheel,” redoing simple user interface or sorting programming just because a previous project was not available as source code, or the code did not fit the programming style of the current development team. The NIH syndrome (Not Invented Here) has been a major stumbling block to the adoption of software components; it manifests itself as the attitude of developers that any code they did not personally write is inferior at best. Analysts have suggested that this tendency by programmers to distrust the work of others continues to be a major obstacle to the adoption of any standard for reusable code. Reusable code at the binary level gets around this syndrome by isolating programmers from the internal behavior of components, making the components into cost-effective “black boxes.”
Chapter One—A COM Primer n
7
Once again, the DLL file concept provides this sort of functionality. DLLs provide a list of “exported functions,” which developers can call with a simple API mechanism, but give no hint as to their internal implementation. DLL files serve as perfect “black boxes” for reusable code. The only difficulty, initially, was that they were creatable only at Microsoft and usable only inside Windows itself. That eventually changed about the same time as COM itself was initially released. Without some form of component reuse at the operating system level, each application must contain all of its executable code, even the code that does simple calculation or graphics tasks, such as drawing a line. This can result in “code bloat,” huge executable files occupying limited hard drive and RAM space. The DLL system also solved this problem. The Windows operating system actually inserts a DLL’s code into the same memory space as the application that calls it. A DLL makes a group of functions available (called exported functions) to Windows; whenever the binary code of the application using the DLL wants to use one of these exported functions, it first uses the LoadLibrary API call to cause the DLL code to be placed in memory, obtains the function address via another API call, and then invokes the function, just as if it had written the code itself. In Windows 3.x, DLL code actually resided in physical RAM only once; if more than one application was using the same DLL at the same time, the operating system did some fancy pointer arithmetic to keep them both happy. Windows 95 no longer uses this system except for some operating system DLLs, mainly for performance reasons and because RAM is so much more plentiful and cheaper than a few years ago. But what makes DLLs useful for ending space bloat is the fact that once the application is through with them, it can unload them and free up the RAM while continuing to execute. Although DLL files solved the space bloat problem, the reinventing-the-wheel problem, and the binary incompatibility problem, they had difficulties of their own. Their locations were “hard coded,” or else they had to reside in the WINDOWS\SYSTEM directory. They also had no way to let an application know which version of the DLL was currently on a computer, which could result in inconsistent behavior when an older version of an application tried to use the new version’s functions. An even worse problem with DLL files lay in how they were reused by multiple applications from different vendors. Both vendors might buy a DLL from a third party and distribute it, but the second vendor’s application would use a newer version of the DLL in question. When it was installed, the other application that used the previous DLL version would break, because it either expected functions to exist that were no longer supported, or because the supported functions worked in different ways. This “broken application” problem was what led, more than any of the other concerns, to the creation of COM. COM took the DLL mechanism and
8
n Chapter One—A COM Primer upgraded its behavior to a consistent standard that still met the other three needs of reusable component software (binary compatibility, effective reusability, and efficient executable size) and also supported versioning and enforcement of backwards compatibility that prevented new COM components from breaking applications that used older ones.
How COM Works COM, in its basic incarnation, consists of six elements working in a somewhat intricate but relatively straightforward way to produce reusable component software. These elements are the server application, the ClassFactory implementation, the interface specification, the reference count mechanism, the Windows COM system, and the client application.
The COM Server The server application is where you, the COM component developer, put your work. It is an executable file, in either DLL or EXE format, that contains all the code to create and maintain the COM elements you provide to COM clients. When a COM client asks the COM system for one of the interfaces your server supports, the COM system will load your server into memory and run it. A server must provide some very specific functions and behaviors to allow the COM system to use it successfully.
The COM ClassFactory A ClassFactory is an implementation of one of the requirements for a COM server: the need to return an interface pointer when the COM system requests it. The ClassFactory can be a C++ class, but it does not have to be; the only requirement is that a ClassFactory’s behavior be implemented at the specific entry points COM expects. The client for a COM server never sees any aspect of the ClassFactory; only the COM system uses it. A ClassFactory also has some specific requirements in its behavior.
The COM Interface The COM interface is the only part of the functionality of a COM server that is visible to a COM client application. For this reason, considerable confusion has arisen as to just what interfaces really are. Simply put, an interface is a specification of what functions a given server provides for a COM component with a specific name. This name is not a text string like most programs use, but rather a special number called (in general) a GUID. A COM server is expected to provide implementation code for all the interfaces it exposes to clients; how it does the implementation is not specified. When asked for an interface pointer, a server provides a pointer to the client via the server’s ClassFactory. The client will dereference this pointer and make function calls
Chapter One—A COM Primer n
9
on its implementation. What actually happens is that these function calls are processed into the VTable, which is simply a list of function pointers. These function pointers, when called, execute the implementation code in the server’s application. This is where the confusion often arises. While from the client’s standpoint an interface is an object, in fact its pointer is a link to a set of function pointers and nothing more. The server does not actually provide a pointer to its internal implementation object (if any) when asked for an interface pointer, but only the pointer to a set of functions. The interface specifies which functions and in what order, but other than that has no information about how those functions are implemented. So in fact an interface has no physical existence; instead, what exists are the implementations of whatever functions it has agreed to provide, and a special pointer that gives clients access to these functions. All COM interfaces derive from IUnknown, the standard interface that the system rests upon. COM interfaces based on IUnknown are called “custom” interfaces, because their functions are non-standard, implemented differently in each new interface.
The COM Reference Count COM servers are loaded into memory by the COM system. For this reason, clients cannot manage the memory used by the servers. To help the COM system decide when a server is no longer needed in memory, the reference counting mechanism is another required implementation for all COM servers. The reference counting system adds two methods to every COM interface: AddRef and Release. The AddRef method is used to increment an internal counter that keeps track of how many times an interface has been requested by the client. The call to AddRef must be made by the implementation of the ClassFactory in a server; what type of internal counting the method uses is up to the individual developer. When a client is finished with an interface pointer, it is required to call the Release method on that pointer; this method is expected to decrement the same internal counter used by AddRef. This way, the COM server always knows how many clients are using it at any given time. At appropriate times, the COM system checks with each server to see whether it thinks it should no longer be in memory. The server is required to be able to accurately know how many interfaces are still in use by clients; if this value is 0, it must inform the COM system it is no longer needed and can be unloaded, but otherwise it must tell the COM system that it needs to stay in memory.
10
n Chapter One—A COM Primer
The COM System in Windows Since clients for COM servers are never allowed to know about either the server’s DLL or EXE file, or the ClassFactory implementation, clearly some other application must be able to deal with these essential aspects of using COM. Windows provides a set of API calls, similar to those used for Windows operating system API functions, that initialize the COM internal system for a client and allow it to initially request an interface pointer for a given GUID “name.” When the client application is done with using COM services, another API call shuts down COM for that application.
The COM Client The COM client application is where all the other five pieces of the COM puzzle fit together. COM clients initialize the COM system, make the API call to obtain a specific interface pointer for a specific server, and then use the functions supported by that interface as they wish. When they are done with a given interface, they call its Release method to tell the server they are done with it. When they are finished using COM services, they shut down their access to the COM system. What makes COM work, and work so well, is that neither the server nor the client need to know anything about each other! As long as both follow the rules for COM behavior, they can mesh together as if one single developer wrote both applications.
COM is Static, Automation is Dynamic Remember that COM was developed to allow Microsoft programs to communicate with each other, and only for that reason. It therefore was not oriented towards client/server interactions, but more towards peer interactions between applications. A concept had been growing for some time, however, that the big, feature-laden application was a dinosaur that was about to be done in by a new breed of program called component software. Component software was conceived as a combination of many smaller elements, sometimes referred to as applets, which would be connected by end users in a simple scripting language like Perl. The scripted portion of the component software would create the applet objects as needed, and tell them what to do in response to user interactions. This type of embedding came to be called Automating. Microsoft began to seriously consider supporting component software, and developed a system using a variant of its Visual Basic language. This system was based on COM, which then was implemented as OLE 2. It was called OLE Automation, but is now usually just called Automation. It has become, to many users, the dominant version of COM in use today.
Chapter One—A COM Primer n
11
Basic COM is entirely fixed at compile time in its function invocations. The only way to call a function on a server’s interface pointer in the client is to give the client a header file or two that gives precise type information about that server’s interfaces. If the server has since changed, and supports more interfaces than are present in the header files the client is using, the client will never know about those interfaces and so can never ask for them or use their functions. This type of interface function calling is called static invocation. Automation, on the other hand, is not fixed at compile time in what functions it can call on a given interface pointer. Instead, it can ask the Automation server for a list of its available interfaces and functions, check them against the source code of its script, and connect the script’s function calls with the actual interface functions entirely at run time. This type of interface function calling is known as dynamic invocation. The information as to which functions a given Automation server supports and their parameters and data types are stored for each Automation server in a file called a Type Library. A Type Library can be thought of as a compiled version of a C .h header file, optimized for use by the Automation system. Compilers that create Automation servers also create their Type Libraries; clients that support Automation servers have the capacity to locate and obtain Type Library information, usually as part of their development framework. IDispatch is a COM interface, derived from IUnknown just like custom interfaces, that supports Automation via four functions: GetTypeInfoCount, GetTypeInfo, GetIDsOfNames, and Invoke. Each of these functions must be invoked using original static invocation in the client’s executable file. What makes Automation work is that the GetIDsOfNames and Invoke functions can and do call any Automation-enabled function of their IDispatchderived custom interface, by obtaining a number called a DISPID from GetIDsOfNames against a textual version of the function’s name, and passing that number along with any appropriate parameters to Invoke. To allow the client to figure out which of the custom functions is being called, a complementary component called a script parser must be provided. This component takes the text file, called a script, and turns it into symbols that the client can understand and translate via GetIDsOfNames and Invoke into real Automation interface function calls in its executable code. The client itself is responsible for obtaining data from the script, storing it internally, and reporting it when asked, but these are not COM issues. Since the functions of an Automation server’s IDispatch interface are dynamic rather than static, the IDispatch interface became known as a Dispinterface rather than an interface to highlight this distinction. A later development merged interface and Dispinterface capabilities into one object; these are called dual interfaces. To make Automation servers more programmer-friendly, the COM team added two new terms to the COM vocabulary: properties (data stored inside
12
n Chapter One—A COM Primer the instance of a given IDispatch-derived Automation server) and methods (COM functions given a new name). Although properties seemed to work like C++ class member variables to the developers of Visual Basic, Java, or VBScript, in fact they were a trick supplied by Automation based on getting and setting functions. (This made them harder to use for C++ programmers, and this is one reason Automation is not as popular among the C++ community.)
Automation Opens COM to the World The scripting language used for OLE Automation was called VBA, or Visual Basic for Applications. Major applications like Word and Excel were given Automation capabilities, and still have them. But the market for component software never materialized, perhaps because of the massive increase in memory and hard drive capacities coupled with drastically reduced costs as the PC market exploded. OLE Automation probably would have been only a niche version of COM, had not two additional factors intervened: Visual Basic and the Internet. The latter aspect led to the merging of Automation with the Windows Desktop, and to the next evolutionary step of COM: COM+. Visual Basic was Microsoft’s entry into the RAD market (Rapid Application Development) where application user interfaces and even program functionality can be quickly laid out and coded, in exchange for somewhat poor run-time performance. Once the prototype was created in VB, it would be recoded in C++ for speed and executable size. But Visual Basic became a larger success than Microsoft probably anticipated, to the point that its sales revenue became a factor in the company’s strategy. One major thing had been left out of Visual Basic at that point: access to OLE and COM. The VB development team was assigned the task of supplying this access for the next version of Visual Basic. Because at that time Visual Basic was not compiled into executable machine code, but instead tokenized and then interpreted by the VB run-time module, using the C++ static invocation system for VB was not possible. On the other hand, by treating the tokens in the parsed VB source code like a VBA script, OLE Automation could very easily provide COM functionality to Visual Basic programs. This provided a substantial new market for OLE Automation, and it became known simply as Automation since many of its uses had little to do with the compound documents which were the focus of OLE. Another player entered the PC world at about this time: the Internet. Taken from its comfortable academic niche and thrust into the mainstream of the world’s lifestyle via the user-friendly World Wide Web and Mosaic and Netscape browser applications, the Internet upset everyone’s calculations as to the future of PC computing development. Adding to the furor was Sun’s powerful and revolutionary Java language, which was platform-independent
Chapter One—A COM Primer n
13
and could send its code over the Internet safely to run on client machines worldwide from a single source. Microsoft retaliated with two powerful new products: Visual J++, its version of a Java compiler that provided increasing support for Windows at the cost of losing platform-independence, and Internet Explorer, a web browser to compete with Netscape Navigator. Visual J++ features the ability for its Java programs to both import and use Automation-enabled COM servers, and to even create COM servers that support Automation. Like Visual Basic, it too must rely on the Automation dynamic invocation system to connect its Java code with the COM system. But the most important fallout of the Internet for COM was the decision to emulate the JavaScript capability of Netscape Navigator in Microsoft’s web browser, Internet Explorer. IE 3 offered relatively complete support for JavaScript, but also had its own scripting language, VBScript, based of course on Visual Basic for Applications. VBScript was part of HTML, and ran on the client machine that had downloaded a web page with included <SCRIPT> tags. The scripts in VBScript were designed along classic Automation lines, controlling the IE browser and creating and using applets, in this case Java applets and special COM Automation servers called ActiveX controls. But it is vital to remember that no matter how sophisticated and user-friendly Automation technology becomes, it is still based on four static-invocation functions of the COM IDispatch interface.
From Automation to OCX to ActiveX When Automation was released, many developers saw it as the culmination of COM. It provided access to COM for Visual Basic and allowed creating COM servers not tied to compound documents, but added considerable support for compound documents as well. Many felt things couldn’t get better than Automation. But trouble was brewing on the horizon, as the increasing popularity of the Visual Basic development environment led to a new type of software component, the VBX control. VBX controls had two important features that Automation servers did not: a user interface and the ability to communicate with their clients (called containers). The unprecedented success of VBX controls led Microsoft to task the COM team with creating an Automation equivalent. The result was the OCX control specification, which supported all the functionality of VBX controls with COM technology with the added bonus of being 32-bit. Then, before OCX controls really got their feet on the ground, the arrival of the Internet and Java caused them to be recast as ActiveX controls, with both added and removed functionality. Automation was a powerful COM technology, with many useful features. But it had limitations as well. Perhaps the most serious feature that Automation lacked was the ability for a non-compound document server to display a user
14
n Chapter One—A COM Primer interface. While compound document servers could create visual images, manipulate them, and even interact with their containing application via in-place editing, Automation servers could do none of these things. Also, even with in-place editing, there was no effective mechanism for a server to contact its client or container with information asynchronously (like the Windows messaging system). These limitations tended to keep COM and Automation from becoming mainstream development tools. Programmers moved instead towards Visual C++ and its MFC system for application development, and to Visual Basic for rapid prototyping and hobbyist computer programming. In these environments, emphasis soon shifted towards components that had a sophisticated graphical user interface along with underlying program functionality. In Visual C++, these components were provided by Microsoft and became known as common controls. In Visual Basic, however, these elements were called VBX controls, and they paved the way for a whole new form of component software. A VBX control is a type of DLL application written with special support to allow it to hook into the Visual Basic system. VBX controls supported two important features: a graphical user interface and the ability to communicate with their containing application (which became known as a container). This allowed VBX controls to behave like C++ common controls, but with one big difference: They could be created by outside developers, using an SDK released by Microsoft. It seems likely that Microsoft had no concept when it released the SDK for VBX control creation, initially for C++ and then for VB itself, just what genie it was releasing from the bottle. Within a year, the VBX control market exploded into a multimillion dollar industry, and took sales of the Visual Basic product along with it. Soon, bulletin boards around the world were filled with sophisticated freeware and shareware applications written in Visual Basic using the thousands of powerful third-party VBX controls now available. The C++ development community agitated for the ability to use VBX controls, and they got it, although not as cleanly as some would have liked. There were several inherent limitations of VBX controls which made them unsuitable for use in other environments: n
VBX controls are inherently 16-bit. This meant that a massive porting job would be needed to move them to the 32-bit environment of Windows 95 and Windows NT, which Microsoft viewed as the future of Windows.
n
VBX controls don’t use the Windows messaging system. This meant that to use them outside of Visual Basic, an interpreter had to be added to convert their VB-specific communication system to the standard Windows arrangement.
Chapter One—A COM Primer n
15
n
VBX controls had only eight available functions. This meant that to define a custom method for a VBX control, you had to define the clumsy Action property, which was hard to use in Visual Basic, let alone any other language.
n
The VBX controls event mechanism, because of its tie to the Visual Basic system, was inflexible; adding new events was difficult, if not impossible.
All these problems led Microsoft to decide that a general system for controls was needed, and COM and Automation were the obvious technologies to implement it. The result was the OCX control, released in 1994 as a specification and SDK. OCX controls were specialized Automation DLL servers (you cannot create an EXE OCX control) with the extension OCX rather than DLL on their filenames. OCX controls could only be used by applications that were written as OCX control containers. Like Automation servers, OCX controls exposed properties and methods, which could be called by the container as desired. Unlike Automation servers, however, OCX controls had a graphical user interface displayed in the control container, and they had the ability to asynchronously send messages to the container in response either to user actions or to internal state changes. Soon OCX controls were everywhere, from Visual C++ to Delphi to Java, and were viewed by the developer community as another COM triumph. But there were storm clouds on the horizon, named the Internet and Java. Their winds of change would blow OCX controls away and replace them with something both similar and very different: the ActiveX control. When AOL dumped millions of its users on the academic network called the Internet in late 1994, no one really understood what it would mean for PC software development. But when combined with the brand-new graphical information display technology called HTML and the World Wide Web, the Internet became the wildfire technology of the last half of the 1990s. The Netscape web browser became synonymous with the WWW, and it was not a Microsoft product. Microsoft’s browser, Internet Explorer, lagged behind in capability and was virtually ignored by the developer community. Then in 1995, Sun Microsystems released the Java programming language. It provided for the first time a secure way to send programs over the Internet and WWW, producing Java applets, which were very similar to OCX controls but worked either by themselves or as part of an HTML web page. In combination with the JavaScript programming language, they behaved very much like Automation servers in web pages or standalone applications. Java, unlike COM, was platform-independent; it ran on Windows, on UNIX workstations, and on the Macintosh. Java was touted as the language of the future, and together with Netscape, it became a serious candidate to challenge the dominance of Microsoft Windows on the PC desktop.
16
n Chapter One—A COM Primer Microsoft was taken by surprise by the WWW and Java, but it soon came back fighting. Its response to Netscape was Internet Explorer version 3, which featured both a JavaScript clone called JScript and its own programming language called VBScript. To counter the Java initiative, Microsoft produced a version of COM called ActiveX. ActiveX encompassed most existing COM and Automation technologies; OCX controls became known as ActiveX controls, for example. Compound documents were called ActiveX documents, and COM servers and Automation servers were renamed ActiveX servers or ActiveX code components. Although initially resisted, soon the innate power of ActiveX moved it to the forefront of both web development and component software programming. As ActiveX matured, the name was shifted to a smaller set of technologies, principally those involving the Internet and WWW. The COM name reappeared, now oriented towards a host of new technologies like Transaction Server, OLE DB, and the Windows DNA system. Internet Explorer moved to version 4 and into the operating system itself in Windows 98. ActiveX controls, however, have remained unchanged since their release in 1996.
ActiveX Controls are Automation Servers with a User Interface By now you may be a bit tired of hearing that ActiveX controls have a user interface. After all, what’s so special about that? What makes the ability to create a user interface so special is that ActiveX controls are not compound documents. A compound document, the previous user interface system supported for COM servers, is a very limited display. It is literally a “window into another application,” and as such does not need to know about things like resizing, background colors, font settings, or display optimization. Controls, on the other hand, are designed to blend in with a consistent user interface around them. They need to understand how to resize when needed, and often need to support display optimization to enhance application performance. The ActiveX control at its basic level maintains its own display window, complete with a device context. While this window is a child window of the container, it nonetheless has its own message queue and refreshes itself when needed. This has some good and bad points. The good points are that ActiveX controls are responsible for drawing themselves, taking this job away from the container, and that they can receive messages using the normal Windows messaging system if needed. The bad points are that ActiveX controls must be rectangular, and cannot be transparent or easily overlaid with non-windowed elements of their container’s display. To get around these limitations, an ActiveX control can be made windowless.
Chapter One—A COM Primer n
17
Windowless ActiveX controls are a relatively recent innovation; some ActiveX control containers may not support them. Those that do, provide the windowless control with its display window and also take care of sending and receiving windows messages for it. What the container gets in return for this effort is a control that can be non-rectangular and even transparent if desired. Also, the control will often redisplay faster due to less overhead internally. ActiveX controls must always draw themselves, and thus are fully responsible for what the user sees on their window. This requires their container to provide them with all the messages that might require refreshing their display. This can range from a simple Paint message to resize requests or more advanced data acquisition calls (needed to store the image of the control in the container as a metafile). Why this matters is that some drawing commands don’t work on a device context for a metafile. The ActiveX control must be able to distinguish between normal drawing requests and those that are needed to create a metafile, and alter its drawing code appropriately. The other major effect of having a user interface for ActiveX controls is that they must be able to change size when their container changes its size. The ActiveX system supports three types of sizing for ActiveX controls: n
Autosizing This type of sizing makes the control entirely responsible for its own display extents. The container will tell the control its own extents and let the control figure out how big it wants to be.
n
Content Sizing This type of sizing makes the container responsible for setting the size of the control initially, but allows the control to decide on its final size.
n
Integral Sizing This type of sizing makes the container responsible for the size of the control. The control must resize itself to the desired values.
Aside from these sizing aspects, ActiveX controls must also decide which mapping mode they will use in their drawing operations. The default is MM_TEXT, but this can be changed if the control wishes.
Stock Properties and Property Pages Standardize ActiveX Control Behavior Automation servers are quite free-form; aside from the needed methods of IDispatch, they are free to support whatever properties and methods they like. Since their clients presumably have access to their Type Library, they will know what custom elements are supported and deal with them appropriately. But ActiveX controls are designed to be used in a general environment, sometimes with little prior knowledge about their behavior. To help compensate for this, the ActiveX system provides stock and ambient properties and
18
n Chapter One—A COM Primer methods to help ActiveX controls blend in with their containers and function consistently for end users. Custom properties and methods of ActiveX controls are just that: custom. To save on naming overhead, if a property or method isn’t qualified as stock or ambient, it is custom by default. They are implemented by the control and are different for each given control, based on its unique functionality. List-oriented controls will expose a property that is an iterator over their internal list of items. Graphical controls will provide an image property to set their displayed graphic. The custom methods of a control are likewise unique; the list control will provide methods to add and delete items from its list, while the graphical control will expose methods to resize its image and change its orientation. From the container standpoint, the custom methods and properties of an ActiveX control are available just like Automation-based properties and methods. End users will access the custom properties and methods with scripts or program code, which will be sent to the control via the usual Automation system (either Dispinterface or dual interface, depending on how the control is implemented). From a display standpoint, custom methods and properties have no effect (although they may change what is displayed, they are not central to how the display of the control is rendered). The Windows system (where most ActiveX controls live and work) has consistency as one of its major features. Once a user has learned how to manipulate scroll bars, buttons, list boxes, radio buttons, check boxes, and so on in one application, he can reasonably expect the same user interface behaviors from other Windows applications. As long as the Windows applications are all using common controls or API-based user interface elements, this will be the case. But ActiveX controls (and their precursors, VBX controls) have their own custom user interfaces in many cases. The behavior of these elements can differ widely despite a similar appearance, resulting in considerable user confusion. To help work around this, ActiveX controls are provided by the ActiveX system with a list of so-called stock properties and methods. These stock features are designed to allow both containers and users to have a consistent and understandable set of appearances and behaviors from ActiveX controls. ActiveX controls live on in the display of their container. This means that they have two choices in their own appearance: They can be a complete iconoclast and set their colors, fonts, and other styles solely to please themselves, or they can set their display values to match those of the container except when it would interfere with their own functionality. In order for an ActiveX control to set its own internal display values to match those of its container, however, it must have some way of obtaining the container’s values. This system is called ambient properties.
Chapter One—A COM Primer n
19
Ambient properties are actually properties of the ActiveX control container. They are exposed to the control itself via a special COM interface the control must implement. An ActiveX control container will obtain this interface from the control when the control is first loaded, and call its functions with the initial values of the ambient properties it supports. Also, whenever one of these ambient properties changes, the ActiveX control will be informed of the new value by another call to the interface’s notification function. Another thorny aspect of the various properties of ActiveX controls is making them available to end users for setting at run time. To help with this task, ActiveX provides the Property Page system. This system is similar to the Property Sheet dialogs introduced in Windows 95; these are tabbed dialogs that group similar properties together and provide consistent, Windows UI-based methods for manipulating them.
Connection Points Allow Events to be Sent from ActiveX Controls to Containers The need for two-way communication between programs has been around since Windows programming began. In Windows, the system to provide this functionality is the Callback function. For COM, however, the interface is the clear choice to implement communication between servers and clients; these interfaces are known as outgoing and incoming interfaces. The problems lie in keeping track of obtaining and notifying many incoming and outgoing interfaces between clients and servers. To handle this difficulty, ActiveX provides the Connection Point system. The Windows system by its very design needs some way for applications to communicate with Windows itself. Probably the most well-known example is the so-called Window procedure, required for every window created by an application. This function is called by Windows to deal with dispatched messages. Another common callback function is the one used to enumerate window objects (like those on the Desktop). This system works because Windows is the operating system, and thus exempt from the problems of interprocess communication. Also, the functions involved are strictly standardized so that Windows can support them. To provide a generalized interprocess method of inter-application communication, a COM interface is the vehicle of choice. Since ActiveX controls are COM servers via Automation and OCX, it makes sense for them to use interfaces for their communication with their containers and vice versa. The communication system that ActiveX implements is provided by sink interfaces (incoming) and source interfaces (outgoing). Sink interfaces are called that because they sink, or consume, notifications from a client to a server. In technical terms, all server interfaces (COM and
20
n Chapter One—A COM Primer Automation) are sink interfaces since they are called by the client or container. They are incoming because they leave the client and come in to the server. Source interfaces are called that because they source, or provide, notifications from a server to a client. They are outgoing because they go out to the client from the server, reversing the normal interaction. These interfaces are what is unique to the ActiveX system, since COM and Automation servers and clients do not have them. It is the source interface that tells a client or container that something of importance has happened in the server; this notification is called an event. In order for a server to use an outgoing interface, the client must implement it and provide a registration of this implementation so the COM system can find it. This makes a client into a server, and vice versa, to support source interfaces. For this reason, the implementation of a source interface in the client then becomes a sink interface (because it is consuming event notifications from the server). The problem that arises with the sink and source interface system lies in the concepts of one-to-many, many-to-one, and many-to-many connection topologies: n
One to Many This topology results from a single implementation of an outgoing interface being used by two or more servers in a single client. The client must be able to determine which server has sent a notification to the single outgoing interface.
n
Many to One This topology results from a single server supporting two or more outgoing interfaces which are implemented on a single client. The client must keep track of all of the incoming notifications for all the implemented source interfaces.
n
Many to Many This topology is a combination of the above two; it occurs when two or more servers are providing notification to the same client on the same outgoing interface, but to two different instances of that interface in the client. The server and the client both must keep track of which instance has been sent and which notification has been received.
To prevent a confusing multiplicity of sink and source implementations, ActiveX provides the IConnectionPoint and IConnectionPointContainer interfaces; these are collectively known as Connection Points. These two statically invoked COM interfaces allow clients and servers to inform each other of what sink and source interfaces they support, and hook up the implemented ones to allow effective two-way communication.
Chapter One—A COM Primer n
21
Persistence Allows ActiveX Controls to Save Their State Over Time Compound documents save their information in special DocObject files, which are also called structured storages and are supported by OLE interfaces and functions. Automation did not need this complexity, since Automation servers rarely needed to save their state; those that did used files or other custom implementations. ActiveX controls, however, need the same generalized information persistence as compound documents, since they are designed to be edited in one environment and displayed in another. This capability is called persistence. The ActiveX system implemented three interfaces to encapsulate persistence: IPersistStorage, IPersistStream, and IPersistPropertyBag. Each of these interfaces has a unique place in the ActiveX data storage arrangement. All persistence is the responsibility of the container; the control only is required to support the interfaces that provide the data. An ActiveX storage is very similar to a folder on a hard drive (also called a directory). It holds no data in and of itself, but serves to contain other data (folders and files) in a single logical and isolated unit. The IPersistStorageXXX interfaces provide storage support for ActiveX controls, allowing them to create and manipulate persistence storages as needed, adding other storages and streams to them. An ActiveX stream is equivalent to a file on a hard drive. It contains data in a format appropriate to whatever application created it, but cannot contain other files or folders. The IPersistStreamXXX interfaces provide stream support for ActiveX controls, allowing them to create, read, and write persistence data streams as needed. Both of the previous persistence implementations are binary, and meant for use in applications that can deal with binary data (like Microsoft Word). Other applications, like Visual Basic or Internet Explorer, need their persistence data stored as text with named associations. To implement this type of persistence, ActiveX provides the IPersistPropertyBagXXX interfaces. These interfaces permit ActiveX controls to read and write their data as HTML tags or named fields in FRM or other text files as needed.
Where We Go From Here This chapter has been a whirlwind tour of some of the most powerful and complex technology in the modern computing environment. This book, due to space limitations, cannot cover all the wonders of COM; instead it focuses on teaching you how to create OLE DB components in Microsoft Visual C++ 6.0, ATL 3.0, and ATL 2.1, using the Microsoft Foundation Classes libraries. If you need a more detailed coverage of COM itself, both theoretically and
22
n Chapter One—A COM Primer structurally, check out the author’s web site at http://www.ciupkc.com/ books/. There are references to several good books on COM and COMrelated subjects there. Meanwhile, the next chapter takes us into the wonderful world of ATL and of the Visual C++ IDE that hosts it. We’ll learn how to create projects in Visual C++ 6.0, and how to use the various ATL wizards and dialogs to automate a great deal of the COM programming we need to do. So fire up Visual C++ 6.0, turn the page, and step into the new millenium of COM development. (If you don’t use ATL but MFC instead, move on to Chapter 3, which does the same job for Microsoft Foundation Classes.)
Chapter Two
An ATL Primer ATL stands for “ActiveX Template Library,” a name that encompasses a lot of territory. First, it invokes ActiveX, which in this context means all of COM except COM+. The template portion of ATL’s name indicates that this system uses the C++ template mechanism heavily, along with a lot of very sophisticated macros. The library part of ATL indicates that, unlike some other template-based systems, ATL provides extensive source code rather than binary OBJ files. This source code is modified to produce the final code, resulting in executables that are both quite small for what they do and very fast, with minimum dependencies on external DLL files. There is a lot to ATL, and it often confuses beginners to either COM or itself. There is enough material in the ATL system for two or three full-sized books; this particular book is going to focus on the basics. This chapter covers how ATL fits into Visual C++, and what its various wizard dialogs do. A brief look is taken at the templates and macros ATL uses, but because these are more advanced topics, they won’t be covered in great detail. (Individual chapters will cover the specific macros and template classes they use.) When you’ve finished reading this chapter, you’ll be ready to start creating powerful ATL-based OLE DB consumers and Providers as Automation servers and ActiveX controls! As of Visual C++ 6.0, ATL enters version 3.0. Most of the features of 3.0 are the same as those of 2.1, and so the following sections are written from the perspective of an ATL 2.1 user. A section at the end of the chapter covers the new features of ATL version 3.0.
ATL in Visual C++ ATL does not work by itself; it is designed for, and only works with, Microsoft Visual C++ 4.2, 5.0, or 6.0. It enhances the Integrated Development Environment of Visual C++ with wizard dialogs and automatic code generators. It also provides extensive online help and a detailed tutorial and sample project.
23
24
n Chapter Two—An ATL Primer
Getting ATL for Older Visual C++ Versions If you have purchased Microsoft C++ 5.0 or 6.0, you automatically have ATL; it is part of the application. If you have version 4, then you may not have the ATL system, since it was developed after that application’s release. Also, even if you have ATL 1.0 for Visual C++ 4.2, you should obtain version 2.1 to use this book. Fortunately, Microsoft has released version 2 of ATL as a free download for owners of Visual C++ 4.2b or higher. To get the self-installing executable for ATL 2.1, activate a web browser and go to the URL http://www.microsoft .com/visualc/prodinfo/archives/download/default.htm. Figure 2-1 shows this web site, which provides considerable information about ATL as well as the download link. Once you download the file, run it either by double-clicking on it in Explorer or using Run from the Start menu. It will extract itself and allow you to choose various simple installation options. Once it is finished, reboot your computer and you will find ATL installed and ready for use in Visual C++.
Figure 2-1 Downloading ATL from the Microsoft web site
ATL’s Online Documentation As mentioned above, ATL is a complex system. It has many useful features that can make COM programming easier and faster, but at a price in time and effort to familiarize yourself with it. Since you’ve purchased (or are considering purchasing) this book, you are obviously interested in ATL. One very good way to learn about it is to work through all the online documentation installed with it (either by default in Visual C++ 5.0 or 6.0, or when you install version 2.0 after downloading it).
Chapter Two—An ATL Primer n
25
To access the online documents for ATL, use the Help menu option and select Search. In the edit control, enter ATL and press the Search button. A number of results will appear; scroll down until you see the “” option, as illustrated in Figure 2-2. As shown in the figure, clicking on it will bring up a group of topics; the best place to start is “ATL Article Overview”; select it and press the Display button. From there all the ATL documents are hyperlinked into Figure 2-2 one detailed unit, which you can The ATL online browse and print at your leisure. documentation in Visual C++
Creating ATL Projects with the ATL AppWizard An ATL project starts with the ATL AppWizard. It is found in the ubiquitous New dialog of Visual C++, and provides a simple wizard dialog that hides a lot of power behind its plain face. With this wizard and its attendant confirmation dialog, you unleash all the power of ATL.
The New Dialog To start an ATL project, select File|New from the Visual C++ menu. The New dialog appears; select the Projects tab to produce the display shown in Figure 2-3. By clicking on the ATL COM AppWizard entry as illustrated in the figure, you enable entry of the project name (which should be different from the planned name(s) of any interfaces to avoid conflicts) and selection of its directory on the hard drive of the development comFigure 2-3 puter. When you have The Visual entered this informaC++ New tion, pressing OK will dialog creating start the actual ATL a new ATL AppWizard. AppWizard project
26
n Chapter Two—An ATL Primer
The ATL AppWizard
Figure 2-4 The ATL AppWizard dialog
Once you press OK in the New dialog, the ATL AppWizard dialog appears, very similar in appearance to Figure 2-4. It contains only one page, with a small number of choices. As illustrated by the figure, you can select a DLL, an EXE, or an NT Service basic project type. You also have two check boxes to enable support for Microsoft Foundation Classes (MFC) and to enable the inclusion of proxy/stub functions directly in the server DLL (if a DLL project is selected) rather than requiring them in a separate DLL as is the standard approach. Once you have made the desired choices, press the Finish button to bring up a confirmation dialog.
The Confirmation Dialog Pressing OK in the ATL AppWizard brings up a Confirmation dialog, as shown in Figure 2-5. This dialog lists the project name, the name of the COM server that will be generated and its file type (EXE or DLL), which file contains the DLL intialization code if there is one, what the IDL filename is, and what filename contains proxy/stub makefile code. Pressing OK actually generates the new ATL project with the selected options and displayed filenames. Figure 2-5 The ATL AppWizard confirmation dialog
Chapter Two—An ATL Primer n
27
Adding COM Interfaces with the ATL Object Wizard Once you have created your ATL project via the ATL AppWizard, all you really have is a shell that does nothing. The AppWizard does not add any COM functionality! Instead, you must do that yourself. Fortunately, ATL provides (as of version 2.0) the ATL Object Wizard dialog system to help with this task. You access it via the Insert|New ATL Object menu option, which brings up the Object Wizard dialog box. There are four types of interfaces you can create using the version 2.0 ATL Object Wizard, each of which is discussed below. Once you’ve decided which type of interface object to use, select it in the dialog box and press Next to move to the tabbed dialog of the ATL Object Wizard.
The Object Wizard Objects The top-level choice in the left-hand list box brings up a group of six objects, as illustrated in Figure 2-6. These six objects are all basic COM interfaces, ranging from the Simple Object which is a pure IUnknown interface, to Figure 2-6 the Microsoft Transaction Basic and speServer Component, which is a cialized COM powerful COM interface capainterface ble of working in the MTS objects in the ATL Object database management system. Wizard
The Object Wizard Controls The Controls entry in the left-hand list box brings up the three ActiveX control objects shown in Figure 2-7. The Full control is just that, a complete ActiveX control suitable for use with Visual C++, Visual Basic, or Delphi. The Internet Explorer ActiveX control object is a “lightweight” version of an ActiveX control, which reduces its size for Internet download but does not seriously degrade its functionality. The Property Page object implements the Figure 2-7 ActiveX control property page ActiveX consystem. This book will cover all trol and Propthree of these controls in detail erty Page Objects in the for our OLE DB ActiveX ATL Object controls. Wizard
28
n Chapter Two—An ATL Primer
The Object Wizard Miscellaneous Elements
Figure 2-8 The ATL Dialog object in the ATL Object Wizard
If you select the Miscellaneous entry in the left-hand list box, you’ll see a display very much like that of Figure 2-8. It contains one entry in version 2.0, a Dialog object. This object allows ATL projects to display standard Windows dialog boxes, which can be edited in Visual C++’s dialog editor just like MFC dialogs. We won’t use this object in this book, but you might want to in one of your own projects.
The Object Wizard Data Access Elements
Figure 2-9 The OLE DB Provider object in the ATL Object Wizard
The Data Access entry in the left-hand list box brings up the display shown in Figure 2-9. It has one entry in version 2.0, the OLE DB Provider object, and two entries in version 3.0, with the addition of the OLE DB Consumer object (see “Version 3.0 Changes to the ATL Object Wizard”). These are advanced interfaces that allow any type of data to be accessed using ActiveX Data Objects in a manner identical with a relational database. This book will cover these objects in great detail.
The Object Wizard Names Tab
Figure 2-10 Setting the names for an ATL interface object with the ATL Object Wizard
Once you’ve selected the type of interface object to create for your ATL project and pressed the Next button, you’ll see a display similar to that of Figure 2-10. This is the Names tab of the ATL Object Wizard tabbed dialog box. All you need to do is enter a unique name in the Short Name edit control. The
Chapter Two—An ATL Primer n
29
wizard then automatically fills in all the other edit controls for you. While you can alter these values, it is best not to, to avoid name conflicts later. Once you’ve done this, select the Attributes tab to set basic properties for your COM interface.
The Object Wizard Attributes Tab Pressing the Attributes tab of the ATL Object Wizard tabbed dialog box shows you a display very much like that of Figure 2-11. There are four basic attributes you can select for a COM interface from this tab of the dialog: the Threading Model (a complex topic involving using two or more copies of the same COM interface at the same time in the same process), whether the interface is a custom one (basic COM) or a dual one (usable by Automation clients), whether or not the interface can be aggregated (used “blind” by another COM interface, a topic not covered in this book), and which of three specialized features the interface will support: ISupportErrorInfo (Rich Error Information for Automation), Connection Points (interfaces for ActiveX Figure 2-11 Setting basic events), and the Free attributes for Threaded Marshaler (a an ATL intervery advanced topic for face object speeding up threading for with the ATL COM servers that is not Object covered in this book). Wizard
Specialized Object Wizard Tabs The previous two tabs are always present for ATL interface objects. Other objects, like ActiveX controls, have additional tabs. These will be covered in the appropriate chapters where they are used. When you have set the desired names and options for the ATL interface object, press OK to add it to the current ATL project.
30
n Chapter Two—An ATL Primer
Creating Functions and Properties with the ATL Interface Wizards Even after you’ve added an interface to your ATL project, you have a lot of work cut out for you. The interface you added contains only basic methods and properties, which in many cases do little or nothing. You must add your own custom methods and properties to the interface. This is intricate work, and again ATL comes to our rescue in version 2.0 with two wizard dialogs. These dialogs are rather hidden, only available from the shortcut (right-click) menu for a COM interface in the ClassView pane of the Project Viewer window of Visual C++.
The ClassView Shortcut Menu Figure 2-12 shows the shortcut (right-click) menu for a COM interface in the ClassView pane of the Project Viewer window of Visual C++. It has a number of useful options, but the ones we are interested in are Add Property and Add Method. Selecting Add Method brings up the Add Method Wizard for the ATL COM interface; selecting Add Property brings up the Add Property Wizard for the ATL Automation Dispinterface.
Figure 2-12 The shortcut menu for an ATL interface in the ClassView pane of Visual C++
The Add Method to Interface Dialog Selecting Add Method from the ATL COM interface shortcut menu brings up the wizard dialog shown in Figure 2-13. As illustrated by the figure, the return type of the method is fixed as HRESULT. You enter a unique name for the method and set any desired parameters it will receive, using IDL syntax. The read-only edit control at the bottom of the dialog will, as you press various buttons and make various entries, update itself with the IDL source code your actions have generated. To add the method to the selected interface, simply press OK. To add specialized IDL attributes for the method function, press the Attributes button (covered in a section below) before you press OK.
Chapter Two—An ATL Primer n
31
Figure 2-13 The Add Method Wizard dialog in ATL
The Add Property to Interface Dialog Selecting Add Property from the ATL COM interface shortcut menu brings up the wizard dialog shown in Figure 2-14. As illustrated by the figure, the return type of the method is fixed as HRESULT. You enter a unique name for the property and set any desired parameters it will receive, using IDL syntax. More importantly, you can select the Property Type value from the combo box; this list contains all the Automation data types ATL supports. While the function itself won’t return this data type in C++, other languages like VBScript or Delphi Pascal will treat the property as if it does return that data type. The Get and Put Function check boxes allow you to determine whether the property is read/write, read-only, or write-only. The read-only edit control at the bottom of the dialog will, as you press various buttons and make various entries, update itself with the IDL source code your actions have generated. To add the method to the selected interface, simply press OK. To add specialized IDL attributes for the method function, press the Attributes button (covered Figure 2-14 below) before you press OK. The Add Property Wizard dialog in ATL
32
n Chapter Two—An ATL Primer
The Edit Attributes Dialog In either wizard dialog, you can press the Attributes button to bring up a dialog very similar to that shown in Figure 2-15. This dialog is used to add specialized information to the IDL file source code generated by the wizard for the property or method. The left-hand combo box allows you to select from the available attributes; once you have selected one, you can enter a desired initial value in a right-hand edit control or combo box. The read-only edit control will show you what your choices have done to the IDL source code for the Figure 2-15 property or method. Press OK to The Add Propadd your changes to the generated erty Edit IDL source code or Cancel to abort Attributes them. You can also edit to some Wizard dialog degree the generated attributes in ATL already shown.
Some Advanced Topics for ATL Projects ATL is oriented towards COM development, and since more and more of Microsoft’s technologies use COM, ATL has acquired more and more capabilities. Among these are proxy generation of specialized classes for Smart Pointers and Connection Points, advanced server projects like MTS, ASP, Visual C++ add-ins, and Component Registrars, and OLE DB providers.
The Proxy Generator Although ATL code is generated with many automatic elements, in some cases the basic ATL system cannot create needed code. To help with this, the ATL Proxy Generator component was developed. The Proxy Generator outputs code that transforms one type of interface into another so that it can be more easily used in ATL. As of this writing, the Proxy Generator can handle two types of proxies: Smart Pointers (special wrapper classes that avoid most of the grunt work of using COM interfaces in C++) and Connection Points (specialized interfaces used for events in ActiveX controls). It is located on the Insert menu, under Components and Controls. You will use this component several times during the course of this book; since it is very specialized, it is covered when it is used rather than here.
Chapter Two—An ATL Primer n
33
Advanced Servers The OLE DB provider and consumer interface objects are the focus of this book’s non-ActiveX code; they contain the same functionality as a basic COM Automation server, but also include full support for OLE DB interfaces of the appropriate type (explained in Chapter 4 and beyond).
ATL Code (Templates and Macros, Oh My!) ATL’s code is not straightforward. As we’ll see in examining the various files output by the ATL wizards, there are many unusual software constructs in them. The two most common, and most powerful, are templates and macros.
Templates Create Custom Classes from Standard C++ Code Templates are what ATL is all about. Simply put, a template is a special software syntax used in C++ to allow generation of code from a basic pattern or template. This template has generic symbols for its variables, which therefore end up being untyped. When a template is given real variables with real types, the template “fills in” these actual data types and variables and performs the indicated actions on them. What makes ATL work is that this system also works for class variables. You can write a template that generates a custom class from a template, replacing key variables and functions in the template class with the imported class’s functionality, while maintaining all the standard code of the template class unchanged. You’ll see a number of concrete examples in later chapters as to how this works in COM server projects.
Macros Expand into Customized Code Macros came before templates; they do much the same thing, but in a simpler way. A macro is expanded during the preprocessor step of compilation, and adds its code to that of the basic source file, replacing parameters of the macro with real variables from the macro’s invocation. Unlike templates, however, macros are typed and so cannot be generic. You’ll see a number of key macros in the coming chapters; where each is used, a detailed explanation and source listing is provided to help you understand how the ATL code works.
34
n Chapter Two—An ATL Primer
What’s New with ATL Version 3.0? With the release of Visual C++ 6.0, a new version of ATL has been released as well: 3.0. It has a number of important changes, some of which are only used at the advanced level, a number of which affect intermediate developers, and a few of which impact all users. The most fundamental changes are the addition of two new shortcut menu entries: an event handler for Windows messages and a Connection Point interface (rather than having to use the Proxy Generator). Also, a number of new ATL objects are provided in the Object Wizard, and there is a change to the basic ATL AppWizard itself. For intermediate users, there are some changes to the functionality of a number of ATL classes, as well as the addition of some new classes. At the advanced level, there are new macros, changes to how templates work, and support for ATL in MFC projects.
Version 3.0 Changes to the AppWizard Figure 2-16 shows the ATL COM AppWizard dialog for version 3.0. You’ll notice it looks very much like the one for version 2.1 except for the new check box at the bottom labeled Support MTS. This check box enables automatic support in the server for the Microsoft Transaction Server system when it is checked. This is somewhat subtle: It does not make the server an MTS-aware component; you have to do that yourself by adding an MTS Object from the Object Wizard. It does, however, put in the proper header files for compilation of MTS objects, interfaces, and API calls rather than requiring you to find and include them by hand. It also causes the MTSEX.DLL dynamic-link library to be Figure 2-16 delay-loaded when the The ATL COM server is loaded, for AppWizard efficiency. dialog for Version 3.0
Chapter Two—An ATL Primer n
35
Version 3.0 Changes to the ATL Object Wizard The changes to the ATL Object Wizard are more substantial, because a number of new and different ATL objects have been made available. Each page of the wizard is covered below, along with a brief synopsis of its new objects.
Figure 2-17 The ATL Object Wizard dialog for version 3.0 showing the first six objects
Figure 2-18 The ATL Object Wizard dialog for version 3.0 showing the MMC SnapIn Object
Figure 2-19 The ATL Object Wizard dialog for version 3.0 showing the first six control objects
Figure 2-20 The ATL Object Wizard dialog for version 3.0 showing the Lite HTML Control object
Figure 2-17 shows the ATL Object Wizard dialog’s Objects page, with the top six objects visible. They are the same as the ones for ATL 2.1, but have somewhat more elegant icons. Figure 2-18 shows the seventh object in the Objects page, the MMC SnapIn object. This ATL object is new for version 3.0, and is used to create a component that can connect with the Windows NT Microsoft Management Console application. The object provides support for all the common interfaces needed to interact with the MMC UI; the developer provides the code to actually manage some aspect of NT behavior. This book does not cover MMC SnapIn components. Figure 2-19 shows the ATL Object Wizard dialog’s Controls page, with the top six objects visible. There are significant changes from the 2.1 version, as follows: n
The Internet Explorer Control is now the Lite Control. It otherwise has the same capabilities.
n
The Full Control and Property Page objects have new icons but otherwise work the same.
Four new controls have been added (the fourth is shown in Figure 2-20). They are: n
Composite Control This object is a dialog-based template that allows visual creation of control user interfaces from existing Windows common controls. It is a Full control otherwise.
36
n Chapter Two—An ATL Primer n
Lite Composite Control This object is identical in behavior to the Composite control, but only supports the interfaces of a Lite (Internet Explorer) control for faster downloading and performance.
n
HTML Control This object is a dialog-based template hosting the WebBrowser control and connecting its DHTML text with COM interfaces in C++ to allow using DHTML as a user interface. This book does not cover DHTML components.
n
Lite HTML Control This object is identical in behavior to the HTML control, but only supports the interfaces of a Lite (Internet Explorer) control for faster downloading and performance. This book does not cover DHTML components.
The Miscellaneous page has no changes; it still only contains the Dialog object for giving an ATL server a user interface. Figure 2-21 shows the Object Wizard dialog’s Data Access page, with both the Consumer and Provider objects shown. The Provider object was supported in version 2.1; version 3.0 adds a ConFigure 2-21 sumer object as well. This book will The ATL Object Wizard cover these OLE DB components in dialog for vergreat detail. sion 3.0 showing the Consumer and Provider OLE DB objects
Version 3.0 Changes to the ClassView Context Menus There are three exciting new context (“shortcut”) menu options available for many ATL classes; each is discussed below and then covered in more detail when it is used in later chapters.
The Add Windows Message Handler Option Figure 2-22 shows the ATL ClassView context menu displaying the Add Windows Message Handler option. Selecting it brings up the dialog box shown in Figure 2-23. Warning
This option is only available for classes that “wrap” a window, such as Dialog objects, and Composite, Lite Composite, HTML, and Lite HTML controls. Other objects will not have this option since they do not normally receive Windows messages.
Chapter Two—An ATL Primer n
37
Figure 2-22 The ATL ClassView context menu showing the Add Windows Message Handler option
Figure 2-23 The ATL New Windows Message Handler Wizard dialog
The New Windows Message Handler Wizard allows you to select from all appropriate Windows messages the ATL class can receive, displayed in the left-hand list box. You move a message to the right-hand list box by pressing either the Add Handler button or the Add and Edit button. (The latter choice will bring up code where the event handler is added so you can enter custom statements to respond to the message.) You can edit an existing handler by pressing the Edit Existing button. To accept the new event handler, press OK. To exit without saving the changes made in the wizard, press Cancel (unless you’ve pressed Add and Edit; this constitutes pressing OK first and cannot be undone).
38
n Chapter Two—An ATL Primer
The Implement Connection Point Option Figure 2-24 shows the ATL ClassView context menu displaying the Implement Connection Point option. Selecting it brings up the dialog box shown in Figure 2-25.
Figure 2-24 The ATL ClassView context menu showing the Implement Connection Point option
Figure 2-25 The ATL Connection Point Wizard dialog
Warning
This option is only available after you have compiled your ATL project at least once, or at the minimum compiled its IDL file to produce a Type Library. Otherwise, an error dialog will appear when you try to select this option.
The Implement Connection Point Wizard replaces the Proxy Generator, which previously had to be used to compile a header file implementing an inline function dealing with adding support for a Connection Point to an ATL object.
Chapter Two—An ATL Primer n
39
The Implement Interface Option Figure 2-26 shows the ATL ClassView context menu displaying the Implement Interface option. Selecting it brings up the Implement Interface Wizard dialog box; press the Add TypeLib button to bring up the dialog shown in Figure 2-27.
Figure 2-26 The ATL ClassView context menu showing the Implement Interface option Figure 2-27 The ATL Browse Type Libraries dialog
Shown in the dialog are all the Type Libraries registered on the current development computer. Selecting one will make its interfaces available for implementation in the current class; you can select as many as you need. Pressing OK brings up the dialog box shown in Figure 2-28.
Figure 2-28 The ATL Implement Interface Wizard dialog
The figure shows a sample list of the interfaces supported by a chosen Type Library’s server. Selecting them will cause “stub” code to be placed for them in the current object, allowing a developer to automatically aggregate or contain the interface as desired.
40
n Chapter Two—An ATL Primer
Where We Go From Here Now that you’re familiar with the basic mechanics of ATL in Visual C++, you are ready to add your understanding of COM to this knowledge and start creating OLE DB consumers and providers as ActiveX controls and Automation servers. Chapter 4 begins the process with basic OLE DB programming information. (Chapter 3 goes over MFC; you can skip it if you don’t use that environment.) Put on your Component Object Model hardhat and fire up the ATL rivet gun: OLE DB awaits!
Chapter Three
An MFC Primer MFC stands for the Microsoft Foundation Classes. MFC is a set of C++ objects that encapsulate access to a number of Windows programming aspects, including the GDI (graphical device interface), file operations, window creation, message handling, and various specialized aspects of the OS. One of these specialized aspects is ActiveX; support for it is added via existing support for COM (the Component Object Model, discussed in Chapter 1). MFC’s main claim to fame is its provision of C++ objects rather than raw API calls for Windows programming, and this holds true for ActiveX as well. There is a lot to MFC, and it often confuses beginners to either COM or itself. There is enough material in the MFC system for two or three full-sized books; this particular book is going to focus on the basics. This chapter covers how MFC fits into Visual C++ and what its various wizard dialogs do. A brief look is taken at the classes MFC uses, but because these are more advanced topics, they won’t be covered in great detail. (Individual chapters will cover the specific classes they use.) When you’ve finished reading this chapter, you’ll be ready to start creating MFC-based COM servers for OLE DB.
MFC in Visual C++ MFC does not work by itself; it is designed for, and only works with, Microsoft Visual C++. It enhances the Integrated Development Environment of Visual C++ with wizard dialogs and automatic code generators. It also provides extensive online help and many detailed sample projects.
MFC’s Online Documentation As mentioned above, MFC is a complex system. It has many useful features that can make ActiveX programming easier and faster, but at a price in time and effort to familiarize yourself with it. Since you’ve purchased (or are considering purchasing) this book, you are obviously interested in MFC and ActiveX. One very good way to learn about it is to work through all the online documentation installed with it.
41
42
n Chapter Three—An MFC Primer To access the online documents for MFC, use the Help menu option and select Search. In the edit control, enter MFC and press the Search button. A number of results will appear; scroll down until you see the feature you are interested in. Clicking on it will bring up a group of topics; the best place to start is “MFC Article Overview”; select it and press the Display button. From there, all the MFC documents are hyperlinked into one detailed unit, which you can browse and print at your leisure.
Creating MFC Projects with the MFC ActiveX ControlWizard An MFC project starts with the MFC ActiveX ControlWizard. It is found in the ubiquitous New dialog of Visual C++, and provides a simple wizard dialog that hides a lot of power behind its plain face. With this wizard and its attendant confirmation dialog, you unleash all the power of MFC for ActiveX programming.
The New Dialog To start an MFC project, select File|New from the Visual C++ menu. The New dialog appears; select the Projects tab to produce the display shown in Figure 3-1. By clicking on the MFC ActiveX ControlWizard entry as illustrated in the figure, you enable entry of the project name (which should be different from the planned name(s) of any other controls to avoid conflicts) and selection of its directory on the hard drive of the development computer. When you have entered this information, pressing OK will start the actual MFC ActiveX ControlWizard.
Figure 3-1 The Visual C++ New dialog creating a new MFC ActiveX ControlWizard project
Chapter Three—An MFC Primer n
43
The MFC ActiveX ControlWizard Once you press OK in the New dialog, the MFC ActiveX ControlWizard dialog appears, very similar in appearance to Figure 3-2. It contains two pages, with a number of choices. As illustrated by the figure, you can enter how many controls you want to create in the project (default is 1), whether or not to use a run-time license (a means of protecting openly distributed controls from being reused after they are Figure 3-2 The MFC downloaded for display), ActiveX whether or not to generate ControlWizard source file comments (default dialog first is yes, a good idea), and page whether or not to create a basic WinHelp helpfile project (default is no). Once you have made the desired choices, press the Next button to bring up the second page of the wizard. Figure 3-3 The MFC ActiveX ControlWizard dialog second page
Figure 3-4 The MFC ActiveX ControlWizard Edit Names dialog
Pressing Next brings up the second page of the wizard, as shown in Figure 3-3. As illustrated in the figure, you select the active control for its settings from a drop-down list. For each control you can edit its filenames (shown in Figure 3-4) or set a number of specialized options (shown in Figure 3-5). You can also select a base Windows common control to use as the basis for the currently selected ActiveX control, and set a number of advanced options. Once you have made the desired choices, press the Finish button to bring up the confirmation dialog.
44
n Chapter Three—An MFC Primer
Figure 3-5 The MFC ActiveX ControlWizard Advanced ActiveX Features dialog
The Confirmation Dialog Pressing OK in the MFC ActiveX ControlWizard brings up a confirmation dialog, as shown in Figure 3-6. This dialog lists the project name, the name of the ActiveX control that will be generated, and its default Property Page, as well as the filenames for these objects. Pressing OK actually generates the new MFC project with the selected options and displayed filenames.
Figure 3-6 The MFC ActiveX ControlWizard confirmation dialog
Working MFC Magic with the ClassWizard Once you have created your MFC project via the MFC ActiveX ControlWizard, all you really have is a shell that does nothing. The ControlWizard does not add any ActiveX functionality! Instead, you must do that yourself. Fortunately, MFC provides the enormously powerful ClassWizard dialog system to help with this task. You access it via the View|ClassWizard menu option as shown in Figure 3-7, which brings up the ClassWizard dialog box. There are five tabs for the ClassWizard, each of which is discussed below.
Figure 3-7 The MFC ClassWizard menu option
Chapter Three—An MFC Primer n
45
The ClassWizard Message Maps Tab The leftmost tab covers MFC Message Maps, as illustrated in Figure 3-8. These are specialized macros and functions that connect Windows messages with the currently selected ActiveX control’s MFC class.
Figure 3-8 The MFC ClassWizard Message Maps tab
The ClassWizard Member Variables Tab The second tab of the ClassWizard from the left is the Member Variables tab, as shown in Figure 3-9. Member variables are used in several ways in ActiveX MFC programming: to store information for control use, to connect with exposed ActiveX control properties, and to move information in and out of Windows controls on ActiveX control Property Pages.
Figure 3-9 The MFC ClassWizard Member Variables tab
46
n Chapter Three—An MFC Primer
The ClassWizard Automation Tab
Figure 3-10 The MFC ClassWizard Automation tab
When you select the Automation tab of the MFC ClassWizard, you’ll see a display very much like that of Figure 3-10. It contains two drop-down lists, one of all the projects in the current workspace on the left and one of all the classes in the current project on the right. If the currently selected class in the right-hand combo box is Automation-enabled (by default all ActiveX control classes are), the various buttons to the right are enabled as well. They are used to add custom and stock methods and properties to the ActiveX control.
The ClassWizard ActiveX Events Tab
Figure 3-11 The MFC ClassWizard ActiveX Events tab
The ActiveX Events tab of the MFC ClassWizard brings up the display shown in Figure 3-11. It has the same basic layout as the Automation tab covered above, with its project and class selection combo boxes. Its left-hand list box gives all the currently enabled ActiveX events (which are different from Windows events) for the current Automation-enabled class. To add a new event, use the Add Event button to the right.
Chapter Three—An MFC Primer n
47
The ClassWizard Class Info Tab
Figure 3-12 The MFC ClassWizard Class Info tab
If you select the rightmost tab of the ClassWizard, you’ll see a display similar to that of Figure 3-12. This is the Class Info tab of the MFC ClassWizard tabbed dialog box. Its layout is similar to the two previous tabs, with its project and class selection combo boxes. For each selected class, the tab displays the header file, source file, base class, and resource file information. It also allows selection of a Windows message filter class, and of “foreign” classes and variables for use in the class currently selected.
Augmenting ActiveX Control Features with ClassWizard Dialogs There are a number of important dialogs accessed through ClassWizard’s various tabs; each one is shown below with a quick tour of its major features.
The ClassWizard New Class Dialog Figure 3-13 shows the New Class dialog for the MFC ClassWizard. It is accessed from the Automation tab of the wizard via the Add Class button’s New Class menu option. While you won’t use this dialog in normal ActiveX programming with Figure 3-13 The MFC ClassWizard New Class dialog in Visual C++ 6.0
48
n Chapter Three—An MFC Primer MFC, it is a vital way to add Automation/ActiveX features to a basic MFC project. To use the New Class dialog, simply enter a new class name in the upper edit control, then select a base class from the middle combo box (CCmdTarget is a good one). Finally, decide whether to use basic Automation or TypeID support (shown in the figure). TypeID support is best for most Automation projects.
The ClassWizard Add Method Dialog
Figure 3-14 The MFC ClassWizard Add Method dialog in Visual C++ 6.0
Selecting the Add Method button from the ClassWizard Automation tab brings up the Add Method dialog shown in Figure 3-14. As illustrated in the figure, there are stock methods available from the upper combo box, or you can enter a new name for a custom method. An internal name will be created, which you can edit if desired. The return type is void for stock methods, but you can set it to other values if desired (the MFC system will actually move these in and out as parameters). Finally, you can add any desired parameters to the method via the two combo boxes at the bottom of the dialog.
The ClassWizard Add Property Dialog
Figure 3-15 The MFC ClassWizard Add Property dialog in Visual C++ 6.0
Selecting the Add Property button from the ClassWizard Automation tab brings up the Add Property dialog shown in Figure 3-15. As illustrated by the figure, this is a much more involved dialog box. First, you must enter an external name for the property; this is what clients of your control or other Automation class will work with. Then you must select a data type for the property from the available list in the combo box. The MFC system will add automatic support for Get and
Chapter Three—An MFC Primer n
49
Put methods (explained in Chapter 2) if you select that radio button in the Implementation section; if you select Member Variable, these function edit controls are disabled. This dialog also allows you to select from a number of stock properties; in this case the Implementation radio button is automatically Stock and you cannot change it. Finally, you can add parameters of any supported data type to the property; this is because ActiveX/Automation properties are actually functions.
The ClassWizard Add Event Dialog Selecting the Add Event button from the ClassWizard ActiveX Events tab brings up the Add Event dialog shown in Figure 3-16. As illustrated by the figure, you can either select a stock event (two are supported) or enter a new external name for a custom one. An internal name based on the external name is automatically generated; you can edit it if you like. If you select a Custom implementation, you can also add parameters of any supported data type to the event; Figure 3-16 The MFC this is because ActiveX events are ClassWizard really just method functions Add Event diaexchanged between clients and log in Visual controls. C++ 6.0
Some Advanced Topics for MFC ActiveX Control Projects There are three other topics MFC ActiveX developers need to understand beyond those covered already in this chapter: connecting Windows events to ActiveX controls, editing Property Page dialogs, and connecting Property Page dialog controls to ActiveX control properties. Each is discussed below.
Using Windows Events with ClassWizard Figure 3-17 shows the ClassWizard Message Maps tab. It lists all the Windows messages a given MFC class (in the upper left-hand list box) can receive (in the upper right-hand list box). Selecting one of these messages will enable the Add Function button on the right- hand side. Clicking on it will bring up a dialog allowing editing of the default name given to the event handler; once this dialog has been accepted, the new event and its handler function are shown in the lower list box display. You can then write custom code in the
50
n Chapter Three—An MFC Primer actual handler itself (the Edit Code button is a handy way to find the exact function in the class file).
Figure 3-17 The MFC ClassWizard Message Maps tab in Visual C++ 6.0
Editing Property Pages
Figure 3-18 The Dialog Editor in Visual C++ 6.0
MFC automatically creates a Property Page dialog resource for all ActiveX control projects. It is editable in the Dialog Editor of Visual C++, as shown in Figure 3-18. You use the standard dialog box editing techniques to put on Windows common controls, position them, and change their captions and other properties using the Edit Properties dialog box also shown in the figure. (Note its little “push pin” button; if you click on it, this button “flips up” and the dialog stays on top even when you edit the main dialog resource. Otherwise, the Edit Properties dialog box is hidden once you return to editing the main display.)
Chapter Three—An MFC Primer n
51
Connecting Property Page Controls to ActiveX Control Properties with ClassWizard Once you have a Property Page dialog all ready to go, MFC will happily display it for you, but it lets you decide which controls connect to what ActiveX control properties. Unlike the ATL system, MFC provides help in this connection process via the ClassWizard. Its Member Variables tab shown in Figure 3-19 illustrates how this works. As shown in the figure, once you have a control on your Property Page dialog that can be connected to an ActiveX control property, its identifier is listed in the lower list box of the dialog page. To connect it with an ActiveX control property, simply select the desired control Figure 3-19 identifier and The MFC click on the ClassWizard Add Variable Member Vaributton to the ables tab in right. Visual C++ 6.0
Once you do this, the Add Member Variable dialog appears, as shown in Figure 3-20. The upper edit control allows you to enter the desired name; note that it already contains the appropriate prefix for the Hungarian notation used by MFC. Once you enter the desired name, select either a Value category or a Reference one; the former passes the data from the control as a value, while the latter puts it in directly as a pointer. Next, select the variable type, Figure 3-20 which can be any of those AutoThe MFC mation supports plus some MFC ClassWizard specialties like CString. Then Add Member select the optional property name Variable dialog in Visual from those available, including C++ 6.0 both custom and stock properties.
52
n Chapter Three—An MFC Primer
MFC Code (Classes and Macros, Oh My!) MFC’s code is not straightforward. As we’ll see in examining the various files output by the MFC wizards, there are many unusual software constructs in them. The two most common, and most powerful, are classes and macros.
MFC Classes Encapsulate Much Windows Functionality in C++ Wrappers MFC is a collection of C++ classes that encapsulate much of the functionality of Windows programming in the C++ metaphor (the actual Windows API is C-based, and so are just functions rather than classes). This was done initially to speed developers’ understanding of the powerful but very complex and often unintuitive Windows programming process. The MFC system is a compromise; it does use C++ classes and syntax, but doesn’t do much more. Other vendors such as Inprise (aka Borland) have other wrapper classes that are much more sophisticated, but developers pay a price in that they do things much the way the framework wants them to, rather than their own way. MFC’s compromise allows much developer flexibility while providing considerable C++ capability.
Macros Expand into Customized Code A macro is a defined identifier that represents a block of source code. A macro is expanded during the preprocessor step of compilation, and adds its code to that of the basic source file, replacing parameters of the macro with real variables from the macro’s invocation. Unlike C++ templates, however, macros are typed and so cannot be generic. You’ll see a number of key macros in the coming chapters; it is often useful to select them in the IDE and examine their online help entry.
Where We Go From Here Now that you’re familiar with the basic mechanics of MFC in Visual C++, you are ready to add your understanding of COM to this knowledge and start creating OLE DB consumer and provider ActiveX controls and Automation servers. Chapter 4 begins with a detailed overview of OLE DB programming and the COM interfaces it requires. Put on your Component Object Model hardhat and fire up the MFC assembly line: OLE DB awaits!
Chapter Four
An OLE DB Primer OLE DB is based on a set of COM interfaces that together provide the functionality of consumers (which provide access to databases via OLE DB providers) and providers (which give information from various data sources to their consumers). The following sections cover all the major interfaces of OLE DB in detail.
IAccessor The IAccessor interface provides methods for OLE DB accessor management. All rowsets and commands must implement the IAccessor interface. OLE DB accessors can be used for rowset data, parameter data, or both. Row and parameter OLE DB accessors can be created on a command. The OLE DB consumer must verify that row OLE DB accessors created on the command are still valid following a change to the command. OLE DB accessors created on a command are not persisted with that command. OLE DB accessors created on a command are inherited by the rowsets it creates. To the OLE DB consumer, it appears as if each OLE DB accessor has been copied from the command to the rowset: The bindings, flags, and handle of each OLE DB accessor are the same on both the rowset and the command. OLE DB accessors created on a rowset are only available to that rowset. They are not available to the command that created the rowset or other rowsets created by the command. When an OLE DB accessor is created on a rowset or command, it has a reference count of 1. If a rowset inherits an OLE DB accessor from a command, the OLE DB accessor has a reference count of 1 on the rowset, regardless of the reference count of the same OLE DB accessor on the command. Calls to the AddRefAccessor method increment the reference count of the OLE DB accessor, and calls to the ReleaseAccessor method decrement the reference count of the OLE DB accessor. When the reference count of an OLE DB accessor reaches zero, the OLE DB accessor and all resources used by that OLE DB accessor are released. If the OLE DB accessor is created on a
53
54
n Chapter Four—An OLE DB Primer command and inherited by the rowset, releasing the OLE DB accessor on the command does not affect the “copy” of the OLE DB accessor on the rowset and vice versa. When the reference count of a rowset reaches zero, all OLE DB accessors created on that rowset or inherited from the command that created it are released completely. If any OLE DB accessors were inherited from the command, the “copies” of these parent OLE DB accessors on the command are not affected. When the reference count of a command reaches zero, all OLE DB accessors created on the command are released completely. If any rowsets inherited OLE DB accessors from the command, the “copies” of these OLE DB accessors on the rowsets are not affected. To create an OLE DB accessor, an OLE DB consumer calls the CreateAccessor method. The OLE DB consumer may create and release OLE DB accessors at any time while the rowset or command remains in existence. When one thread of an OLE DB consumer shares an OLE DB accessor with another thread, it calls the AddRefAccessor method to increment the reference count of that OLE DB accessor. When the OLE DB consumer is done with a rowset, it calls the ReleaseAccessor method to release any OLE DB accessors on the rowset, including OLE DB accessors inherited from the command. When the OLE DB consumer is done with a command, it calls the ReleaseAccessor method to release any OLE DB accessors created on the command. In both cases, the OLE DB consumer must call the ReleaseAccessor method once for each reference count on the OLE DB accessor. Method AddRefAccessor CreateAccessor GetBindings ReleaseAccessor
Description This method adds a reference count to an existing OLE DB accessor. This method creates an OLE DB accessor from a set of bindings. This method returns the bindings in an OLE DB accessor. This method releases an OLE DB accessor.
IAccessor::AddRefAccessor This method adds a reference count to an existing OLE DB accessor. The OLE DB consumer must increment the reference count on an OLE DB accessor by calling the AddRefAccessor method before passing it to another thread. It has the following syntax: HRESULT AddRefAccessor ( HACCESSOR hAccessor, ULONG * pcRefCount);
Chapter Four—An OLE DB Primer n
55
It has the following parameters: hAccessor [in] This parameter is the handle of the OLE DB accessor for which to increment the reference count. pcRefCount [out] This parameter is a pointer to memory in which to return the reference count of the OLE DB accessor handle. If *pcRefCount is a null pointer, no reference count is returned. It has the following return codes: S_OK This return code indicates that the method succeeded. E_FAIL This return code indicates that a provider-specific error occurred. E_UNEXPECTED This return code indicates that ITransaction::Commit or ITransaction::Abort was called and the object is in a zombie state. This error can be returned only when the method is called on a rowset. DB_E_BADACCESSORHANDLE This return code indicates that hAccessor was invalid.
IAccessor::CreateAccessor The CreateAccessor method creates an OLE DB accessor from a set of bindings. The CreateAccessor method always checks all error conditions that do not require it to validate the OLE DB accessor against the metadata. As a general rule, this means it checks the error conditions for all of the return codes except those that return DB_E_ERRORSOCCURRED. The CreateAccessor method may validate the OLE DB accessor against the metadata for row OLE DB accessors created on the rowset; it never validates the OLE DB accessor against the metadata for row or parameter OLE DB accessors created on the command. When the CreateAccessor method validates the OLE DB accessor against the metadata, it validates each binding in the OLE DB accessor, setting the appropriate DBBINDSTATUS values as it goes. If the CreateAccessor method fails in any way, it does not create the OLE DB accessor and sets *phAccessor to a null handle. If the CreateAccessor method does not validate the OLE DB accessor against the metadata, then the validation is said to be delayed. The CreateAccessor method simply creates the OLE DB accessor and validation is done by the first method that uses the OLE DB accessor. If the OLE DB accessor is found to be invalid, it remains in existence and can be used again. If the OLE DB accessor validation is delayed, the OLE DB provider determines whether the method validating the OLE DB accessor validates it against the
56
n Chapter Four—An OLE DB Primer metadata before or during data transfer. If the method validates the OLE DB accessor before transferring any data, it can return any of the return codes listed below. If the method validates the OLE DB accessor while transferring the data, it sets the status value of any column or parameter for which the OLE DB accessor is invalid (within the context of the method) to DBSTATUS_E_BADACCESSOR and returns DB_S_ERRORSOCCURRED or DB_E_ERRORSOCCURRED. Whether the method continues processing other columns or parameters depends on both the method and the provider. The following return codes are returned by methods that perform delayed OLE DB accessor validation before transferring any data. The DBBINDSTATUS value to which each corresponds is also listed. Return Code E_NOINTERFACE DB_E_BADBINDINFO DB_E_BADORDINAL DB_E_BADSTORAGEFLAGS
DBBINDSTATUS Value DBBINDSTATUS_NOINTERFACE DBBINDSTATUS_BADBINDINFO DBBINDSTATUS_BADORDINAL DBBINDSTATUS_BADSTORAGEFLAGS
It has the following syntax: HRESULT CreateAccessor ( DBACCESSORFLAGS dwAccessorFlags, ULONG cBindings, const DBBINDING rgBindings[], ULONG cbRowSize, HACCESSOR* phAccessor, DBBINDSTATUS rgStatus[]);
It has the following parameters: dwAccessorFlags [in] This parameter is a bitmask that describes the properties of the OLE DB accessor and how it is to be used. These flags have the following meanings: Value DBACCESSOR_INVALID DBACCESSOR_PASSBYREF
Description This flag is used by GetBindings to indicate that the method failed. This flag indicates that the OLE DB accessor is a reference OLE DB accessor. The value passed in the OLE DB consumer buffer is a pointer to the passer’s internal buffer. This pointer need not point to the start of the internal buffer, as long as the relative offsets of all elements of the buffer align
Chapter Four—An OLE DB Primer n Value DBACCESSOR_PASSBYREF (cont.)
57
Description with the offsets specified in the OLE DB accessor. The passee must know the internal structure of the passer’s buffer in order to read information from it. The passee must not free the buffer at the pointer, nor may it write to this buffer. For row OLE DB accessors, this buffer is the rowset’s copy of the row. The OLE DB consumer reads information directly from this copy of the row at a later point in time, so the OLE DB provider must guarantee that the pointer remains valid. For parameter OLE DB accessors, this buffer is the OLE DB consumer’s buffer. The OLE DB provider reads data from this buffer only when ICommand:: Execute is called, so the pointer is not required to remain valid after Execute returns. Support for this flag is optional. An OLE DB consumer determines whether an OLE DB provider supports this bit by calling IDBProperties::GetProperties for the DBPROP_BYREFACCESSORS property. When this flag is used, the dwMemOwner in the DBBINDING structure is ignored. If the OLE DB accessor is used for row data, the OLE DB accessor refers to the OLE DB provider’s memory; the OLE DB consumer must not write to or free this memory. If the OLE DB accessor is used for input parameters, the OLE DB provider copies the row of data without assuming ownership.
DBACCESSOR_ ROWDATA
DBACCESSOR_ PARAMETERDATA
DBACCESSOR_ OPTIMIZED
It is an error to specify an output or input/output parameter in a reference OLE DB accessor. This flag indicates that the OLE DB accessor is a row OLE DB accessor and describes bindings to columns in the rowset. An OLE DB accessor may be a row OLE DB accessor, a parameter OLE DB accessor, or both. This flag indicates that the OLE DB accessor is a parameter OLE DB accessor and describes bindings to parameters in the command text. In a parameter OLE DB accessor, it is an error to bind an input or an input/output parameter more than once. This flag indicates that the row OLE DB accessor is to be optimized. This hint may affect how a provider structures its internal buffers. A particular column can be bound by only one optimized OLE DB accessor. The column can also be bound by other, nonoptimized OLE DB accessors, but the types specified in the nonoptimized OLE DB accessors must be convertible from the type in the optimized OLE DB accessor. All optimized
58
n Chapter Four—An OLE DB Primer Value DBACCESSOR_ OPTIMIZED (cont.)
Description OLE DB accessors must be created before the first row is fetched with IRowset::GetNextRows, IRowsetLocate::GetRowsAt, IRowsetLocate:: GetRowsByBookmark, or IRowsetScroll:: GetRowsAtRatio. This flag is ignored for parameter OLE DB accessors.
cBindings [in] This parameter indicates the number of bindings in the OLE DB accessor. If cBindings is zero, the CreateAccessor method creates a null OLE DB accessor. Null OLE DB accessors are used only by IRowsetChange:: InsertRow to create a new row in which each column is set to its default value, NULL, or a status of DBSTATUS_E_UNAVAILABLE. OLE DB providers that support InsertRow must support the creation of null OLE DB accessors. rgBindings [in] This parameter indicates an array of DBBINDING structures. cbRowSize [in] This parameter indicates the numer of bytes allocated for a single set of parameters or criteria values in the OLE DB consumer’s buffer. cbRowSize is used by ICommand::Execute to process multiple sets of parameters, and by IViewFilter::GetFilter and IViewFilter::SetFilter to get and set multiple OR conditions in criteria. In both cases, a single OLE DB accessor may describe multiple sets of values. cbRowSize is generally the size of the structure that contains a single set of parameter or criteria values and is used as the offset to the start of the next set of values within the array of structures. For example, if cParamSets is greater than 1 in the DBPARAMS structure passed to the Execute method, the OLE DB provider assumes that the pData element of this structure points to an array of structures containing parameter values, each cbRowSize bytes in size. Similarly, if cRows is greater than 1 in the SetFilter method, the OLE DB provider assumes that the pCriteriaData argument points to an array of structures containing criteria values, each cbRowSize bytes in size. cbRowSize must be large enough to contain the structure defined by the bindings in rgBindings. The OLE DB provider is not required to verify this, although it may. cbRowSize is not used when fetching rowset data. phAccessor [out] This parameter indicates a pointer to memory in which to return the handle of the created OLE DB accessor. If the CreateAccessor method fails, it must attempt to set *phAccessor to a null handle. rgStatus [out] This parameter indicates an array of cBindings DBBINDSTATUS values in which the CreateAccessor method returns the status of each binding; that
Chapter Four—An OLE DB Primer n
59
is, whether it was successfully validated or not. If rgStatus is a null pointer, no bind status values are returned. The OLE DB consumer allocates and owns the memory for this array. The bind status values are returned for the following reasons: Value DBBINDSTATUS_OK
DBBINDSTATUS_ BADORDINAL
Description This flag shows that no errors were found in the binding. Because OLE DB accessor validation can be deferred, a status of DBBINDSTATUS_OK does not necessarily mean that the binding was successfully validated. This flag indicates that a parameter ordinal was zero in a parameter OLE DB accessor. If the OLE DB accessor is validated against the metadata when the CreateAccessor method is called, DBBINDSTATUS_BADORDINAL can be returned for the following reasons: n In a row OLE DB accessor, a column ordinal in a
binding was outside the range of available columns on the rowset. n In a parameter OLE DB accessor, a parameter
ordinal was greater than the number of parameters in the command text. These reasons cause a status value of DBSTATUS_E_ BADACCESSOR to be returned if the OLE DB accessor is validated when used.
DBBINDSTATUS_ UNSUPPORTED CONVERSION
Some OLE DB providers may support binding more parameters than the number of parameters in the command text, and such OLE DB providers do not return DBBINDSTATUS_BADORDINAL in this case. This flag shows that if the OLE DB accessor is validated against the metadata when the CreateAccessor method is called, DBBINDSTATUS_ UNSUPPORTEDCONVERSION can be returned for the following reason: n The specified conversion was not supported by the
OLE DB provider. n The OLE DB consumer attempted to convert a
column to a storage object (ISequentialStream, IStorage, IStream, ILockBytes) and the particular conversion is not supported by the provider. These reasons cause a status value of DBSTATUS_E_ BADACCESSOR to be returned if the OLE DB accessor is validated when used.
60
n Chapter Four—An OLE DB Primer Value DBBINDSTATUS_ BADBINDINFO
Description This flag indicates that dwPart in a binding was not one of the following: DBPART_VALUE DBPART_LENGTH DBPART_STATUS eParamIO in a binding in a parameter OLE DB accessor was not one of the following: DBPARAMIO_INPUT DBPARAMIO_OUTPUT DBPARAMIO_INPUT | DBPARAMIO_OUTPUT A row OLE DB accessor was optimized and a column ordinal in a binding was already used in another optimized OLE DB accessor. In a parameter OLE DB accessor, two or more bindings contained the same ordinal for an input or input/output parameter. wType in a binding was DBTYPE_EMPTY or DBTYPE_ NULL. wType in a binding was one of the following: DBTYPE_BYREF | DBTYPE _EMPTY DBTYPE_BYREF | DBTYPE_NULL or DBTYPE_BYREF | DBTYPE_RESERVED wType in a binding was used with more than one of the following mutually exclusive type indicators: DBTYPE_ BYREF, DBTYPE_ARRAY, or DBTYPE_VECTOR. wType in a binding was DBTYPE_IUNKNOWN, and pObject in the same binding was a null pointer. Provider-owned memory was specified for a nonpointer type in a nonreference row OLE DB accessor. OLE DB provider-owned memory was specified for a column and the OLE DB provider does not support binding to OLE DB provider-owned memory for this column. OLE DB provider-owned memory was specified for a column for which IColumnsInfo::GetColumnInfo returned DBCOLUMNFLAGS_ISLONG. In a nonreference parameter OLE DB accessor, a binding specified OLE DB provider-owned memory. An output or input/output parameter was specified in a parameter reference OLE DB accessor. dwFlags in a binding was set to DBBINDFLAG_HTML, and wType for the same binding was not a string value.
Chapter Four—An OLE DB Primer n Value DBBINDSTATUS_ BADBINDINFO (cont.)
61
Description If the OLE DB accessor is validated against the metadata when the CreateAccessor method is called, DBBINDSTATUS_BADBINDINFO can be returned for the following reasons: n A row OLE DB accessor was not optimized, a column number in a binding specified a column that was already used in an optimized OLE DB accessor, and the OLE DB provider did not support a conversion from the type specified in the optimized OLE DB accessor for the column to the type specified in wType. n In a parameter OLE DB accessor, eParamIO in a
binding specified the incorrect I/O type for the parameter. Some OLE DB providers cannot determine parameter I/O types and never return DBBINDSTATUS_BADBINDINFO in this case. n In a reference OLE DB accessor, the value specified
for dwPart, obValue, cbMaxLen, or wType in a binding did not match the format of the corresponding element in the rowset’s copy of the row. n In a nonreference OLE DB accessor, a binding
specified provider-owned memory, wType was X | DBTYPE_BYREF, and the data type of the corresponding element of the rowset’s copy of the row was not X or X | DBTYPE_BYREF. n In a nonreference OLE DB accessor, a binding
specified OLE DB provider-owned memory, wType was DBTYPE_BSTR, and the data type of the corresponding element of the rowset’s copy of the row was not DBTYPE_BSTR. n The OLE DB accessor was used for passing key
column values in IRowsetIndex::Seek or IRowsetIndex::SetRange, and the order in which the key columns were bound did not match the order in which they were returned in IColumnsInfo:: GetColumnInfo. n The OLE DB accessor was used for passing key
column values in the Seek method or the SetRange method, and a less significant key column was bound without binding all more significant key columns. n The OLE DB accessor was used for passing key
column values in the Seek method or the SetRange method, and a non-key column was bound before the last bound key column.
62
n Chapter Four—An OLE DB Primer Value DBBINDSTATUS_ BADBINDINFO (cont.) DBBINDSTATUS_ BADSTORAGE FLAGS
Description These reasons cause a status value of DBSTATUS_ E_BADACCESSOR to be returned if the OLE DB accessor is validated when used. This flag indicates that dwFlags, in the DBOBJECT structure pointed to by a binding, specified invalid storage flags. If the OLE DB accessor is validated against the metadata when the CreateAccessor method is called, DBBINDSTATUS_BADSTORAGEFLAGS can be returned for the following reason: n dwFlags, in the DBOBJECT structure pointed to by
a binding, specified a valid storage flag that was not supported by the object.
DBBINDSTATUS_ NOINTERFACE
These reasons cause a status value of DBSTATUS_E_ BADACCESSOR to be returned if the OLE DB accessor is validated when used. This flag shows that if the OLE DB accessor is validated against the metadata when the CreateAccessor method is called, DBBINDSTATUS_NOINTERFACE can be returned for the following reasons: n The OLE DB provider did not support the storage
interface specified in iid in the DBOBJECT structure pointed to by a binding in the OLE DB accessor. n The OLE object in a column or parameter did not
support the interface specified in iid in the DBOBJECT structure pointed to by the corresponding binding in the OLE DB accessor. n The OLE DB provider supports only one open
storage object at a time—that is, DBPROP_MULTIPLESTORAGEOBJECTS is VARIANT_FALSE—and wType in more than one binding was DBTYPE_IUNKNOWN. These reasons cause a status value of DBSTATUS_E_ BADACCESSOR to be returned if the OLE DB accessor is validated when used.
It has the following return codes: S_OK This return code indicates that the method succeeded. If rgStatus is not a null pointer, each element is set to DBBINDSTATUS_OK. E_FAIL This return code means that an OLE DB provider-specific error occurred.
Chapter Four—An OLE DB Primer n
63
E_INVALIDARG This return code shows that *phAccessor was a null pointer, or cBindings was not zero and rgBindings was a null pointer. E_UNEXPECTED This return code indicates that ITransaction::Commit or ITransaction:: Abort was called and the object is in a zombie state. This error can be returned only when the method is called on a rowset. DB_E_BADACCESSORFLAGS This return code indicates that dwAccessorFlags was invalid; the DBACCESSOR_PARAMETERDATA bit was set in dwAccessorFlags, and the OLE DB provider does not support parameters; neither the DBACCESSOR_PARAMETERDATA bit nor the DBACCESSOR_ROWDATA bit was set in dwAccessorFlags; or a method that fetches rows (IRowset:: GetNextRows, IRowsetLocate::GetRowsAt, IRowsetLocate::GetRowsByBookmark, or IRowsetScroll::GetRowsAtRatio) had already been called and the DBACCESSOR_OPTIMIZED bit in dwAccessorFlags was set. This return code can also inndicate that the DBACCESSOR_PARAMETERDATA bit was set and the CreateAccessor method was called on a rowset. DB_E_BYREFACCESSORNOTSUPPORTED This return code shows that dwAccessorFlags was DBACCESSOR_ PASSBYREF and the value of the DBPROP_BYREFACCESSORS property is VARIANT_FALSE. DB_E_ERRORSOCCURRED This return code means OLE DB accessor validation failed. To determine which bindings failed, the OLE DB consumer checks the values returned in rgStatus, at least one of which is not DBBINDSTATUS_OK. DB_E_NOTREENTRANT This return code indicates that the OLE DB provider called a method from IRowsetNotify in the OLE DB consumer and the method has not yet returned. DB_E_NULLACCESSORNOTSUPPORTED This return code shows that cBindings was zero and the rowset does not support IRowsetChange::InsertRow, or the CreateAccessor method was called on a command.
IAccessor::GetBindings This method returns the bindings in an OLE DB accessor. It makes no logical change to the state of the object. If the OLE DB accessor is null, then the method sets *pcBindings to zero and *prgBindings to a null pointer. It has the following syntax: HRESULT GetBindings ( HACCESSOR hAccessor,
64
n Chapter Four—An OLE DB Primer DBACCESSORFLAGS *pdwAccessorFlags, ULONG * pcBindings, DBBINDING ** prgBindings);
It has the following parameters: hAccessor [in] This parameter shows the handle of the OLE DB accessor for which to return the bindings. pdwAccessorFlags [out] This parameter indicates a pointer to memory in which to return a bitmask that describes the properties of the OLE DB accessor and how it is intended to be used. If this method fails, *pdwAccessorFlags is set to DBACCESSOR_INVALID. pcBindings [out] This parameter provides a pointer to memory in which to return the number of bindings in the OLE DB accessor. If this method fails, *pcBindings is set to zero. prgBindings [out] This parameter indicates a pointer to memory in which to return an array of DBBINDING structures. One DBBINDING structure is returned for each binding in the OLE DB accessor. OLE DB provider allocates memory for these structures and any structures pointed to by elements of these structures; for example, if pObject in a binding structure is not a null pointer, the OLE DB provider allocates a DBOBJECT structure for return to the OLE DB consumer. The OLE DB provider returns the address to the memory for these structures; the OLE DB consumer releases the memory for these structures with IMalloc::Free when it no longer needs the bindings. If *pcBindings is zero on output or the method fails, the OLE DB provider does not allocate any memory and ensures that *prgBindings is a null pointer on output. It has the following return codes: S_OK This return code shows that the method succeeded. E_FAIL This return code indicates that an OLE DB provider-specific error occurred. E_INVALIDARG This return code shows that *pdwAccessorFlags, *pcBindings, or *prgBindings were null pointers. E_OUTOFMEMORY This return code shows that the OLE DB provider was unable to allocate sufficient memory in which to return the binding structures.
Chapter Four—An OLE DB Primer n
65
E_UNEXPECTED This return code indicates that ITransaction::Commit or ITransaction:: Abort was called and the object is in a zombie state. This error can be returned only when the method is called on a rowset. DB_E_BADACCESSORHANDLE This return code shows that hAccessor was invalid. DB_E_NOTREENTRANT This return code indicates that the OLE DB provider called a method from IRowsetNotify in the OLE DB consumer and the method has not yet returned.
IAccessor::ReleaseAccessor This method releases an OLE DB accessor. ReleaseAccessor decrements the reference count of the OLE DB accessor. If the reference count reaches zero, it releases the OLE DB accessor and all resources used by the OLE DB accessor. After an OLE DB accessor is released, methods called with the handle to that OLE DB accessor return DB_E_BADACCESSORHANDLE. On rowsets, OLE DB accessors are read-only and can be shared among threads in a free-threaded style without synchronization. The OLE DB consumer must call the ReleaseAccessor method to decrement the reference count on an OLE DB accessor that has been passed to a thread and is no longer needed by that thread. This method can be called while the rowset is in a zombie state to enable the OLE DB consumer to clean up after a transaction has been committed or aborted. It has the following syntax: HRESULT ReleaseAccessor ( HACCESSOR hAccessor, ULONG * pcRefCount);
It has the following parameters: hAccessor [in] This parameter indicates the handle of the OLE DB accessor to release. pcRefCount [out] This parameter gives a pointer to memory in which to return the remaining reference count of the OLE DB accessor handle. If *pcRefCount is a null pointer, no reference count is returned.
66
n Chapter Four—An OLE DB Primer It has the following return codes: S_OK This return code indicates that the method succeeded. E_FAIL This return code shows that an OLE DB provider-specific error occurred. DB_E_BADACCESSORHANDLE This return code indicates that hAccessor was invalid. DB_E_NOTREENTRANT This return code shows that the OLE DB provider called a method from IRowsetNotify in the OLE DB consumer and the method has not yet returned.
IColumnsInfo IColumnsInfo is the simpler of two interfaces that can be used to expose information about columns of an OLE DB rowset or prepared OLE DB command. It provides a limited set of information in an array. All OLE DB rowsets and OLE DB commands implement IColumnsInfo. Service components can synthesize IColumnsRowset from this so that OLE DB consumers and OLE DB providers have independent choice of whether they want to code for simple limited information, or to work with flexible and openended column descriptions. IColumnsInfo is required on both commands and rowsets. The GetColumnInfo method returns the most commonly used metadata: column IDs, data types, updatability, and so on. The GetColumnInfo method returns the metadata in an array of structures, which can be created and accessed quickly. The metadata returned, however, is limited. OLE DB consumers that require more complete metadata can obtain it by calling IColumnsRowset:: GetColumnsRowset. Note
For OLE DB commands that expose ICommandPrepare, the methods on this interface can be called only after the OLE DB command is prepared or the OLE DB rowset is instantiated. If an OLE DB command text is set but not prepared, any calls to methods on IColumnsInfo return DB_E_NOTPREPARED. For OLE DB commands that do not expose ICommandPrepare, the methods on this interface can be called only after the OLE DB command text has been set.
Chapter Four—An OLE DB Primer n Method GetColumnInfo MapColumnIDs
67
Description This method returns the column metadata needed by most OLE DB consumers. This method returns an array of ordinals of the columns in an OLE DB rowset that are identified by the specified column IDs.
IColumnsInfo::GetColumnInfo This method returns the column metadata needed by most OLE DB consumers. The function makes no logical change to the state of the object. It has the following syntax: HRESULT GetColumnInfo ( ULONG * pcColumns, DBCOLUMNINFO ** prgInfo, OLECHAR ** ppStringsBuffer);
It has the following parameters: pcColumns [out] This parameter provides a pointer to memory in which to return the number of columns in the OLE DB rowset; this number includes the bookmark column, if there is one. If the GetColumnInfo method is called on an OLE DB command that does not return rows, *pcColumns is set to zero. If this method terminates due to an error, *pcColumns is set to zero. prgInfo [out] This parameter indicates a pointer to memory in which to return an array of DBCOLUMNINFO structures. One structure is returned for each column in the OLE DB rowset. The OLE DB provider allocates memory for the structures and returns the address to this memory; the OLE DB consumer releases this memory with IMalloc::Free when it no longer needs the column information. If *pcColumns is 0 on output or terminates due to an error, the OLE DB provider does not allocate any memory and ensures that *prgInfo is a null pointer on output. ppStringsBuffer [out] This parameter shows a pointer to memory in which to return a pointer to storage for all string values (names used either within columnid or for *pwszName) within a single allocation block. If no returned columns have either form of string name, or if this method terminates due to error, this parameter returns a null pointer. If there are any string names, then this will be a buffer containing all the values of those names. The OLE DB consumer should free the buffer with IMalloc::Free when finished working with the names. If *pcColumns is zero on output, the OLE DB provider does not allocate any memory and ensures that *ppStringsBuffer is a null pointer on output. Each of
68
n Chapter Four—An OLE DB Primer the individual string values stored in this buffer is terminated by a nulltermination character. Therefore, the buffer may contain one or more strings, each with its own null-termination character, and may contain embedded null-termination characters. It has the following return codes: S_OK This return code shows that the method succeeded. E_FAIL This return code indicates that an OLE DB provider-specific error occurred. E_INVALIDARG This return code shows *pcColumns, *prgInfo, or *ppStringsBuffer as null pointers. E_OUTOFMEMORY This return code indicates that the OLE DB provider was unable to allocate sufficient memory in which to return the column information structures. E_UNEXPECTED This return code shows that ITransaction::Commit or ITransaction::Abort was called and the object is in a zombie state. This error can be returned only when the method is called on an OLE DB rowset. DB_E_ERRORSINCOMMAND This return code means that the OLE DB command text contained one or more errors. OLE DB providers should use OLE DB error objects to return details about the errors. DB_E_NOCOMMAND This return code shows that no command text was set. This error can be returned only when this method is called from the OLE DB Command object. DB_E_NOTPREPARED This return code indicates that the OLE DB command exposed ICommandPrepare and the OLE DB command text was set, but the OLE DB command was not prepared. This error can be returned only when this method is called from the OLE DB Command object. DB_E_NOTREENTRANT This return code shows that the OLE DB provider called a method from IRowsetNotify in the OLE DB consumer and the method has not yet returned. DB_SEC_E_PERMISSIONDENIED This return code means that the OLE DB consumer did not have sufficient permission to retrieve the column information.
Chapter Four—An OLE DB Primer n
69
The GetColumnInfo method returns a fixed set of column metadata in an array of DBCOLUMNINFO structures, one per column. The returned metadata is that most commonly used by OLE DB consumers, such as the data type and column ID. The order of the structures is the order in which the columns appear in the OLE DB rowset (column ordinal order). This is the same order as they appear in IColumnsRowset, and is predictable from the ordering of requested columns in the command text. Bookmark columns may be returned on any OLE DB rowset, regardless of its source (for example, ICommand::Execute, IOpenRowset::OpenRowset, IColumnsRowset::GetColumnsRowset, or IDBSchemaRowset::GetRowset) or whether bookmarks were requested. When bookmarks are returned, they will always appear as the first element of *prgInfo with iOrdinal equal to zero. When processing the columns returned by the GetColumnInfo method, generic OLE DB consumers should be prepared to receive and handle bookmark columns appropriately (that is, not display them to the user if not appropriate for the application). The GetColumnInfo method provides a quick alternative to the GetColumnsRowset method. While the GetColumnsRowset method returns all available column metadata, it does so in an OLE DB rowset. To get the metadata, the OLE DB consumer must therefore create the column metadata rowset, create one or more OLE DB accessors, fetch each row in the OLE DB rowset, and get the data from the OLE DB rowset. Calling the GetColumnInfo method on an OLE DB command before the OLE DB command is executed may be an expensive operation.
DBCOLUMNINFO Structures GetColumnInfo returns column metadata in DBCOLUMNINFO structures. These structures have the following syntax: typedef struct tagDBCOLUMNINFO { LPOLESTR pwszName; ITypeInfo * pTypeInfo; ULONG iOrdinal; DBCOLUMNFLAGS dwFlags; ULONG ulColumnSize; DBTYPE wType; BYTE bPrecision; BYTE bScale; DBID columnid; } DBCOLUMNINFO;
70
n Chapter Four—An OLE DB Primer The elements of the DBCOLUMNINFO structure are used as follows: Element pwszName
pTypeInfo iOrdinal
dwFlags
ulColumnSize
Description This flag provides a pointer to the name of the column; this might not be unique. If this cannot be determined, a null pointer is returned. The name can be different from the string part of the column ID if the column has been renamed by the OLE DB command text. This name always reflects the most recent renaming of the column in the current view or OLE DB command text. This flag is reserved for future use. OLE DB providers should return a null pointer in *pTypeInfo. This flag is the ordinal of the column. This is zero for the bookmark column of the row, if any. Other columns are numbered starting from one. This flag indicates a bitmask that describes column characteristics. The DBCOLUMNFLAGS enumerated type specifies the bits in the bitmask. This flag provides the maximum possible length of a value in the column. For columns that use a fixed-length data type, this is the size of the data type. For columns that use a variable-length data type, this is one of the following: n The maximum length of the column in characters, for
DBTYPE_STR and DBTYPE_WSTR, or bytes, for DBTYPE_BYTES, if one is defined. For example, a CHAR(5) column in a SQL table has a maximum length of 5. n The maximum length of the data type in characters, for
DBTYPE_STR and DBTYPE_WSTR, or bytes, for DBTYPE_BYTES, if the column does not have a defined length. For data types that do not have a length, this is set to ~0 (bitwise, the value is not 0; that is, all bits are set to 1). n ~0 (bitwise, the value is not 0; that is, all bits are set to
wType
bPrecision
1) if neither the column nor the data type has a defined maximum length. This flag provides the indicator of the column’s data type. If the data type of the column varies from row to row, this must be DBTYPE_VARIANT. This flag shows that if wType is a numeric data type, this is the maximum precision of the column. The precision of columns with a data type of DBTYPE_DECIMAL or DBTYPE_NUMERIC depends on the definition of the column.
Chapter Four—An OLE DB Primer n Element bPrecision (cont.)
bScale
columnid
71
Description For DBTYPE_DBTIMESTAMP data types, this is the length of the string representation (assuming the maximum allowed precision of the fractional seconds component). If the column’s data type is not numeric or datetime, this is ~0 (bitwise, the value is not 0; that is, all bits are set to 1). This flag indicates that if wType is DBTYPE_DECIMAL or DBTYPE_NUMERIC, this is the number of digits to the right of the decimal point. Otherwise, this is ~0 (bitwise, the value is not 0; that is, all bits are set to 1). For DBTYPE_DBTIMESTAMP data types, this is the length of the string representation of the fractional seconds component. This flag shows the column ID of the column. The column ID of a base table should be invariant under views.
DBCOLUMNFLAGS Enumerated Type The dwFlags element of the DBCOLUMNINFO structure is a bitmask that describes column characteristics. The DBCOLUMNFLAGS enumerated type specifies the bits in the bitmask; the meanings of these values are as follows: Value DBCOLUMNFLAGS_ CACHEDEFERRED
DBCOLUMNFLAGS_ ISBOOKMARK DBCOLUMNFLAGS_ ISCHAPTER DBCOLUMNFLAGS_ ISFIXEDLENGTH
Description This flag is set if, when a deferred column is first read, its value is cached by the OLE DB provider. Later reads of the column are done from this cache. The contents of the cache can be overwritten by IRowsetChange:: SetData or IRowsetRefresh::RefreshVisibleData. The cached value is released when the row handle is released. Otherwise, it is not set. This flag can be set through the DBPROP_CACHEDEFERRED property in the OLE DB Rowset property group. This flag is set if the column contains a bookmark. Otherwise, it is not set. This flag is set if the column contains a chapter value. Otherwise, it is not set. This flag is set if all data in the column has the same length. Otherwise, it is not set. The flag is always set for fixed-length data types except DBTYPE_BSTR. The flag is set for variable-length data types (DBTYPE_STR, DBTYPE_WSTR, DBTYPE_ BSTR, and DBTYPE_BYTES) if all the values are the same length. The flag is set for DBTYPE_VECTOR or DBTYPE_ARRAY columns if all elements in each
72
n Chapter Four—An OLE DB Primer Value DBCOLUMNFLAGS_ ISFIXEDLENGTH (cont.)
DBCOLUMNFLAGS_ ISLONG
DBCOLUMNFLAGS_ ISNULLABLE
DBCOLUMNFLAGS_ ISROWID DBCOLUMNFLAGS_ ISROWVER
Description vector or array are of the same length and each vector or array has the same number of elements. Otherwise, the flag is not set. The flag is not affected by DBTYPE_BYREF. This flag is set if the column contains a BLOB that contains very long data. The definition of very long data is OLE DB provider-specific. The setting of this flag corresponds to the value of the IS_LONG column in the PROVIDER_TYPES schema rowset for the data type. When this flag is set, the OLE DB provider supports reading the value through a storage interface. Although such BLOBs can be retrieved in a single piece with the IRowset::GetData method, there may be OLE DB provider-specific problems in doing so. For example, the BLOB might be truncated due to machine limits on memory or the GetData method might fail if called more than once for the BLOB. Furthermore, when this flag is set, the OLE DB provider might not be able to accurately return the maximum length of the BLOB data in ulColumnSize in the DBCOLUMNINFO structure. When this flag is not set, the BLOB can be accessed directly through the GetData method. It is OLE DB provider-specific whether columns without this flag can be read through a storage interface. This flag is set if the column can be set to NULL or if the OLE DB provider cannot determine whether or not the column can be set to NULL. Otherwise, it is not set. This reflects only declarative rules. DBCOLUMNFLAGS_ISNULLABLE is generally used by OLE DB consumers attempting to set data to determine whether or not a particular column might be able to hold a null value. This flag is set if the column contains a persistent row identifier that cannot be written to and has no meaningful value except to identify the row. This flag is set if the column contains a timestamp or other versioning mechanism that cannot be written to directly and that is automatically updated to a new, increasing value when the row is updated and committed. The data type of a version column is OLE DB provider-specific. How a version column is used— for example, how two version values are compared—is also OLE DB provider-specific.
Chapter Four—An OLE DB Primer n Value DBCOLUMNFLAGS_ MAYBENULL
73
Description This flag is set if NULL can be gotten from the column, or if the OLE DB provider cannot guarantee that NULLs cannot be gotten from the column. Otherwise, it is not set. DBCOLUMNFLAGS_MAYBENULL is generally used by OLE DB consumers reading data to determine whether or not they might encounter a null value. When DBCOLUMNINFO is returned in the COLUMNS rowset, DBCOLUMNFLAGS_ MAYBENULL should not be set if a simple select over the single column of that table would return no null values.
DBCOLUMNFLAGS_ MAYDEFER
DBCOLUMNFLAGS_MAYBENULL can be set even if DBCOLUMNFLAGS_ISNULLABLE is not set. For example, in a left outer join, if there is no row on the right side that matches a row on the left side, the columns from the right side in the joined row contain NULLs, even if the underlying columns are nonnullable; that is, if DBCOLUMNFLAGS_ISNULLABLE is not set. This flag is set if the column is deferred. Otherwise, it is not set.
DBCOLUMNFLAGS_ WRITE
A deferred column is one for which the data is not fetched from the data source until the OLE DB consumer attempts to get it. That is, the data is not fetched when the row is fetched, but the IRowset:: GetData method is called. This flag can be set through the DBPROP_DEFERRED property in the OLE DB Rowset property group. This flag is set if the IRowsetChange::SetData method can be called for the column. Otherwise, it is not set. This flag can be set through the DBPROP_ MAYWRITECOLUMN property in the OLE DB Rowset property group.
DBCOLUMNFLAGS_ WRITEUNKNOWN
OLE DB providers never set both DBCOLUMNFLAGS_WRITE and DBCOLUMNFLAGS_ WRITEUNKNOWN; they are mutually exclusive. Absence of these flags means the column is read-only. This flag is set if it is not known whether the IRowsetChange::SetData method can be called for the column. OLE DB providers never set both DBCOLUMNFLAGS_WRITE and DBCOLUMNFLAGS_ WRITEUNKNOWN; they are mutually exclusive. Absence of these flags means the column is read-only.
74
n Chapter Four—An OLE DB Primer
IColumnsInfo::MapColumnIDs This method returns an array of ordinals of the columns in an OLE DB rowset that are identified by the specified column IDs. This method makes no logical change to the state of the object. It has the following syntax: HRESULT MapColumnIDs ( ULONG cColumnIDs, const DBID rgColumnIDs[], ULONG rgColumns[]);
It has the following parameters: cColumnIDs [in] This parameter gives the number of column IDs to map. If cColumnIDs is zero, MapColumnIDs does nothing and returns S_OK. rgColumnIDs [in] This parameter shows an array of IDs of the columns for which to determine the column ordinals. If rgColumnIDs contains a duplicate column ID, a column ordinal is returned once for each occurrence of the column ID. If the column ID is invalid, the corresponding element of rgColumns is set to DB_INVALIDCOLUMN. rgColumns [out] This parameter indicates an array of cColumnIDs ordinals of the columns identified by the elements of rgColumnIDs. The OLE DB consumer allocates but is not required to initialize memory for this array and passes the address of this memory to the OLE DB provider. The OLE DB provider returns the column IDs in the array. It has the following return codes: S_OK This return code indicates the method succeeded. All elements of rgColumns are set to values other than DB_INVALIDCOLUMN. DB_S_ERRORSOCCURRED This return code shows that an element of rgColumnIDs was invalid. If the column ID is invalid, the corresponding element of rgColumns is set to DB_INVALIDCOLUMN. E_FAIL This return code shows that a provider-specific error occurred. E_INVALIDARG This return code indicates that cColumnIDs was not zero and rgColumnIDs was a null pointer, or rgColumns was a null pointer. E_UNEXPECTED This return code means that ITransaction::Commit or ITransaction::Abort
Chapter Four—An OLE DB Primer n
75
was called and the object is in a zombie state. This error can be returned only when the method is called on an OLE DB rowset. DB_E_ERRORSOCCURRED This return code indicates that all elements of rgColumnIDs were invalid. All elements of rgColumns are set to DB_INVALIDCOLUMN. DB_E_NOCOMMAND This return code means that no command text was set. This error can be returned only when this method is called from the Command object. DB_E_NOTPREPARED This return code indicates that the OLE DB command exposed ICommandPrepare and the OLE DB command text was set, but the OLE DB command was not prepared. This error can be returned only when this method is called from the OLE DB Command object. DB_E_NOTREENTRANT This return code shows that the OLE DB provider called a method from IRowsetNotify in the OLE DB consumer and the method has not yet returned. MapColumnIDs returns the ordinals of the columns in the OLE DB rowset that are identified by the elements of rgColumnIDs. These column ordinals do not change during the life of the OLE DB rowset. Column ordinals may change between different instantiations of an OLE DB rowset if the OLE DB command text does not define an order, such as a SELECT * FROM MyTable. Two column IDs that are the same—except that one contains a GUID and the other contains a pointer to a GUID—are equivalent if both use the same GUID. Both are mapped to the same column ordinal.
ISourcesRowset ISourcesRowset returns an OLE DB rowset of data sources and enumerators visible from the current enumerator. Method GetSourcesRowset
Description This method returns an OLE DB rowset of the data sources and enumerators visible from the current enumerator.
ISourcesRowset::GetSourcesRowset This method returns an OLE DB rowset of the data sources and enumerators visible from the current enumerator. GetSourcesRowset returns the following rowset. The rowset is read-only. The columns are returned in the order shown.
76
n Chapter Four—An OLE DB Primer Column Name SOURCES_NAME
Type Indicator DBTYPE_WSTR
SOURCES_PARSENAME
DBTYPE_WSTR
SOURCES_DESCRIPTION
DBTYPE_WSTR
SOURCES_TYPE
DBTYPE_UI2
Description This column contains the name of the data source or enumerator. This column contains the string to pass to IParseDisplayName to obtain a moniker for the data source or enumerator. This column contains the description of the data source or enumerator. This column contains information as to whether the row describes a data source or an enumerator: n DBSOURCETYPE_DATASOURCE n DBSOURCETYPE_ENUMERATOR
SOURCES_ISPARENT
DBTYPE_BOOL
If a single piece of code is capable of being used both as a data source and as an enumerator, it is listed in the rowset twice, once in each role. This column shows if the row describes an enumerator. SOURCES_ ISPARENT is VARIANT_TRUE if the enumerator is the parent enumerator; that is, the enumerator whose enumeration contains the enumerator on which ISourcesRowset:: GetSourcesRowset was just called. This allows the OLE DB consumer to go backward through the enumeration. Whether an enumerator is able to enumerate its parent is OLE DB provider-specific. Otherwise, SOURCES_ISPARENT is VARIANT_ FALSE. If the row describes a data source, SOURCES_ISPARENT is ignored by the OLE DB consumer.
It has the following syntax: HRESULT GetSourcesRowset( IUnknown pUnkOuter, REFIID riid, ULONG cPropertySets, DBPROPSET rgPropertySets[], IUnknown ** ppSourcesRowset);
Chapter Four—An OLE DB Primer n
77
It has the following parameters: pUnkOuter This parameter provides a pointer to the controlling IUnknown interface if the source’s OLE DB rowset is being created as part of an aggregate. If the OLE DB rowset is not part of an aggregate, this must be set to a null pointer. riid This parameter shows the IID of the interface on which to return a pointer. This interface is conceptually added to the list of required interfaces on the resulting OLE DB rowset, and the method fails (E_NOINTERFACE) if that interface cannot be supported on the resulting OLE DB rowset. cPropertySets [in] This parameter gives the number of DBPROPSET structures in rgPropertySets. If this is zero, the OLE DB provider ignores rgPropertySets. rgPropertySets [in/out] This parameter shows an array of DBPROPSET structures containing properties and values to be set. The properties specified in these structures must belong to the OLE DB Rowset property group. If the same property is specified more than once in rgPropertySets, then it is OLE DB provider-specific which value is used. If cPropertySets is zero, this argument is ignored. ppSourcesRowset This parameter gives a pointer to memory in which to return the requested interface pointer on the OLE DB rowset. If an error occurs, the returned pointer is null. It has the following return codes: S_OK This return code indicates that the method succeeded. In all DBPROP structures passed to the method, dwStatus is set to DBPROPSTATUS_OK. DB_S_ERRORSOCCURRED This return code indicates that the OLE DB rowset was opened but one or more properties—for which the dwOptions element of the DBPROP structure was DBPROPOPTIONS_OPTIONAL—were not set. The OLE DB consumer checks dwStatus in the DBPROP structures to determine which properties were not set. The method can fail to set properties for a number of reasons, including: n
The property was not supported by the OLE DB provider.
n
The property was not in the OLE DB Rowset property group.
n
The property set was not supported by the OLE DB provider.
n
It was not possible to set the property.
78
n Chapter Four—An OLE DB Primer n
The colid in the DBPROP structure was invalid.
n
The data type in vValue in the DBPROP structure was not the data type of the property or was not VT_EMPTY.
n
The value in vValue in the DBPROP structure was invalid.
n
The property’s value conflicted with an existing property.
n
A property was specified to be applied to all columns, but could not be applied to one or more columns.
E_FAIL This return code shows that an OLE DB provider-specific error occurred. E_INVALIDARG This return code indicates that ppSourcesRowset was a null pointer; cPropertySets was not zero and rgPropertySets was a null pointer; or in an element of rgPropertySets, cProperties was not zero and rgProperties was a null pointer. E_NOINTERFACE This return code shows that the OLE DB rowset did not support the interface specified in riid. E_OUTOFMEMORY This return code indicates that the OLE DB provider did not have enough memory to create the OLE DB Rowset object. E_UNEXPECTED This return code shows that the Enumerator object was in an uninitialized state. DB_E_ABORTLIMITREACHED This return code states that the method failed because a resource limit has been reached. For example, a query used to implement the method timed out. No OLE DB rowset is returned. DB_E_ERRORSOCCURRED This return code indicates that the OLE DB rowset was opened but one or more properties were not set. These properties had the dwOptions element of the DBPROP structure set to DBPROPOPTIONS_OPTIONAL. The OLE DB consumer checks dwStatus in the DBPROP structures to determine which properties were not set. None of the satisfiable properties are remembered. The method can fail to set properties for any of the reasons specified in DB_S_ERRORSOCCURRED, except the reason that states it was not possible to set the property. DB_E_NOAGGREGATION This return code indicates that pUnkOuter was not a null pointer and the OLE DB rowset did not support aggregation or that pUnkOuter was non-null and riid was not IID_Unknown.
Chapter Four—An OLE DB Primer n
79
IDBCreateSession OLE DB consumers call IDBCreateSession::CreateSession on a data source object to obtain a new session. Method CreateSession
Description This method creates a new session from the data source object and returns the requested interface on the newly created session.
IDBCreateSession::CreateSession This method creates a new session from the data source object and returns the requested interface on the newly created session. It has the following syntax: HRESULT CreateSession ( IUnknown * pUnkOuter, REFIID riid, IUnknown ** ppDBSession);
It has the following parameters: pUnkOuter [in] This parameter shows a pointer to the controlling IUnknown interface if the new session is being created as part of an aggregate. It is a null pointer if the session is not part of an aggregate. riid [in] This parameter indicates the IID of the interface. ppDBSession [out] This parameter gives a pointer to memory in which to return the interface pointer. It has the following return codes: S_OK This return code shows that the method succeeded. E_FAIL This return code means that an OLE DB provider-specific error occurred. E_INVALIDARG This return code states that *ppDBSession was a null pointer. E_NOINTERFACE This return code indicates that the session did not support the interface specified in riid.
80
n Chapter Four—An OLE DB Primer E_OUTOFMEMORY This return code shows that the OLE DB provider did not have enough memory to create the session. E_UNEXPECTED This return code indicates that the data source object was in an uninitialized state. DB_E_NOAGGREGATION This return code shows that *pUnkOuter was not a null pointer and the session being created does not support aggregation or that *pUnkOuter was non-null and riid was not IID_Unknown. DB_E_OBJECTCREATIONLIMITREACHED This return code states that the maximum number of sessions supported by the OLE DB provider has already been created. The OLE DB consumer must release one or more currently held sessions before obtaining a new session object. This error is only returned by OLE DB providers that have a fixed maximum number of sessions as returned by DBPROP_ACTIVESESSIONS. It is not returned due to other resource constraints, such as available memory (for which the provider returns E_OUTOFMEMORY). DB_E_OBJECTOPEN This return code shows that the OLE DB provider would have to open a new connection to support the operation and DBPROP_MULTIPLECONNECTIONS is set to VARIANT_FALSE.
IDBInfo IDBInfo returns information about the keywords and literals a provider supports. It is an optional interface on the data source objects. Method GetKeywords GetLiteralInfo
Description This method returns a list of OLE DB provider-specific keywords. This method returns information about literals used in text OLE DB commands and in ITableDefinition and IIndexDefinition.
IDBInfo::GetKeywords This method returns a list of OLE DB provider-specific keywords. The following list contains the keywords in OLE DB: ABSOLUTE ACTION ADD ALL
INTO IS ISOLATION JOIN
Chapter Four—An OLE DB Primer n ALLOCATE ALTER AND ANY ARE AS ASC ASSERTION AT AUTHORIZATION AVG BEGIN BETWEEN BIT BIT_LENGTH BOTH BY CASCADE CASCADED CASE CAST CATALOG CHAR CHAR_LENGTH CHARACTER CHARACTER_LENGTH CHECK CLOSE COALESCE COLLATE COLLATION COLUMN COMMIT CONNECT CONNECTION CONSTRAINT CONSTRAINTS CONTINUE CONVERT CORRESPONDING COUNT
KEY LANGUAGE LAST LEADING LEFT LEVEL LIKE LOCAL LOWER MATCH MAX MIN MINUTE MODULE MONTH NAMES NATIONAL NATURAL NCHAR NEXT NO NOT NULL NULLIF NUMERIC OCTET_LENGTH OF ON ONLY OPEN OPTION OR ORDER OUTER OUTPUT OVERLAPS PARTIAL POSITION PRECISION PREPARE PRESERVE
81
82
n Chapter Four—An OLE DB Primer CREATE CROSS CURRENT CURRENT_DATE CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR DATE DAY DEALLOCATE DEC DECIMAL DECLARE DEFAULT DEFERRABLE DEFERRED DELETE DESC DESCRIBE DESCRIPTOR DIAGNOSTICS DISCONNECT DISTINCT DISTINCTROW DOMAIN DOUBLE DROP ELSE END END-EXEC ESCAPE EXCEPT EXCEPTION EXEC EXECUTE EXISTS EXTERNAL EXTRACT FALSE FETCH
PRIMARY PRIOR PRIVILEGES PROCEDURE PUBLIC READ REAL REFERENCES RELATIVE RESTRICT REVOKE RIGHT ROLLBACK ROWS SCHEMA SCROLL SECOND SECTION SELECT SESSION SESSION_USER SET SIZE SMALLINT SOME SQL SQLCODE SQLERROR SQLSTATE SUBSTRING SUM SYSTEM_USER TABLE TEMPORARY THEN TIME TIMESTAMP TIMEZONE_HOUR TIMEZONE_MINUTE TO TRAILING
Chapter Four—An OLE DB Primer n FIRST FLOAT FOR FOREIGN FOUND FROM FULL GET GLOBAL GO GOTO GRANT GROUP HAVING HOUR IDENTITY IMMEDIATE IN INDICATOR INITIALLY INNER INPUT INSENSITIVE INSERT INT INTEGER INTERSECT INTERVAL
83
TRANSACTION TRANSLATE TRANSLATION TRIGGER TRIM TRUE UNION UNIQUE UNKNOWN UPDATE UPPER USAGE USER USING VALUE VALUES VARCHAR VARYING VIEW WHEN WHENEVER WHERE WITH WORK WRITE YEAR ZONE
It has the following syntax: HRESULT GetKeywords( LPOLESTR * ppwszKeywords);
It has the following parameters: ppwszKeywords [out] This parameter gives a pointer to memory in which to return the address of a string. The string contains a comma-separated list of all keywords unique to the OLE DB provider. The OLE DB provider allocates memory for the string and returns the address to this memory; the OLE DB consumer releases this memory with IMalloc::Free when it no longer needs the string.
84
n Chapter Four—An OLE DB Primer It has the following return codes: S_OK This return code indicates the method succeeded. E_FAIL This return code shows that an OLE DB provider-specific error occurred. E_INVALIDARG This return code indicates that *ppwszKeywords was a null pointer. E_OUTOFMEMORY This return code indicates that the OLE DB provider was unable to allocate sufficient memory in which to return the keywords. E_UNEXPECTED This return code shows that the data source object was in an uninitialized state.
IDBInfo::GetLiteralInfo This function returns information about literals used in text commands, ITableDefinition, IIndexDefinition, and IOpenRowset, or any interface that takes DBIDs as arguments. In the context of the GetLiteralInfo method, a literal is one of several things: n
A special character or characters used by text commands, such as the character used to quote identifiers. The GetLiteralInfo method returns the character or characters.
n
A literal data value, such as a character literal in a SQL statement. For such literal data values, the GetLiteralInfo method returns the maximum length of the literal in characters, a list of the characters that cannot be used in the literal, and a list of the characters that cannot be used as the first character of the literal.
n
The name of a database object such as a column or table. For such names, the GetLiteralInfo method returns the maximum length of the name in characters, a list of the characters that cannot be used in the name, and a list of the characters that cannot be used as the first character of the name.
Information about literals is returned in the DBLITERALINFO structure: typedef struct tagDBLITERALINFO { LPOLESTR pwszLiteralValue; LPOLESTR pwszInvalidChars; LPOLESTR pwszInvalidStartingChars; DBLITERAL lt; BOOL fSupported; ULONG cchMaxLen; } DBLITERALINFO;
Chapter Four—An OLE DB Primer n
85
The elements of this structure are used as follows: Element
Description
pwszLiteralValue
This element gives a pointer to a string in the *ppCharBuffer buffer containing the actual literal value. For example, if lt is DBLITERAL_LIKE_PERCENT, and the percent character (%) is used to match zero or more characters in a LIKE clause, this would be “%”. This is used for DBLITERAL_CATALOG_ SEPARATOR, DBLITERAL_ESCAPE_PERCENT_PREFIX, DBLITERAL_ESCAPE_UNDERSCORE_PREFIX, DBLITERAL_LIKE_PERCENT, DBLITERAL_LIKE_ UNDERSCORE, DBLITERAL_QUOTE_PREFIX, and DBLITERAL_SCHEMA_SEPARATOR. For all other DBLITERAL values, pwszLiteralValue is not used and is set to a null pointer.
pwszInvalidChars
This element provides a pointer to a string in the *ppCharBuffer buffer containing the characters that are not valid in the literal. For example, if table names can contain anything other than a numeric character, this would be “0123456789" when lt is DBLITERAL_TABLE_NAME. If the literal can contain any valid character, this is a null pointer. This is not used for DBLITERAL_BINARY_LITERAL, DBLITERAL_CATALOG_ SEPARATOR, DBLITERAL_ESCAPE_PERCENT_PREFIX, DBLITERAL_ESCAPE_UNDERSCORE_PREFIX, DBLITERAL_LIKE_PERCENT, DBLITERAL_LIKE_ UNDERSCORE, DBLITERAL_QUOTE_PREFIX, and DBLITERAL_SCHEMA_SEPARATOR; pwszInvalidChars is set to a null pointer for these DBLITERAL values.
pwszInvalidStartingChars
This element provides a pointer to a string in the *ppCharBuffer buffer containing the characters that are not valid as the first character of the literal. If the literal can start with any valid character, this is a null pointer. For example, if table names can begin with anything other than a numeric character, this would be “0123456789" when lt is DBLITERAL_TABLE_NAME. This is not used for DBLITERAL_BINARY_LITERAL, DBLITERAL_CATALOG_ SEPARATOR, DBLITERAL_ESCAPE_PERCENT_PREFIX, DBLITERAL_ESCAPE_UNDERSCORE_PREFIX, DBLITERAL_LIKE_PERCENT, DBLITERAL_LIKE_ UNDERSCORE, DBLITERAL_QUOTE_PREFIX, and DBLITERAL_SCHEMA_SEPARATOR; pwszInvalidStartingChars is set to a null pointer for these DBLITERAL values.
lt
This element indicates the literal described in the structure.
86
n Chapter Four—An OLE DB Primer Element
Description
fSupported
This element isTRUE if the OLE DB provider supports the literal specified by It. If cLiterals is 0, this is always TRUE, because the GetLiteralInfo method only returns information about literals it supports in this case. It is FALSE if the OLE DB provider does not support the literal, or the value of the corresponding element of the rgLiterals array was not a valid value in the DBLITERAL enumerated type.
cchMaxLen
This element provides the maximum number of characters in the literal. If there is no maximum or the maximum is unknown, cchMaxLen is set to ~0 (bitwise, the value is not 0; that is, all bits are set to 1). For DBLITERAL_CATALOG_ SEPARATOR, DBLITERAL_ESCAPE_PERCENT_PREFIX, DBLITERAL_ESCAPE_UNDERSCORE_PREFIX, DBLITERAL_LIKE_PERCENT, DBLITERAL_LIKE_ UNDERSCORE, DBLITERAL_QUOTE_PREFIX, and DBLITERAL_SCHEMA_SEPARATOR, this is the actual number of characters in the literal.
The following values of DBLITERAL are supported: Value DBLITERAL_INVALID DBLITERAL_ BINARY_LITERAL DBLITERAL_ CATALOG_NAME DBLITERAL_ CATALOG_SEPARATOR DBLITERAL_ CHAR_LITERAL DBLITERAL_ COLUMN_ALIAS DBLITERAL_ COLUMN_NAME DBLITERAL_ CORRELATION_NAME DBLITERAL_ CURSOR_NAME DBLITERAL_ESCAPE_ PERCENT_PREFIX
Description This is an invalid value. This value shows a binary literal in a text command. This value gives a catalog name in a text command. This value shows the character that separates the catalog name from the rest of the identifier in a text command. This value indicates a character literal in a text command. This value shows a column alias in a text command. This value gives a column name used in a text command or in a data-definition interface. This value shows a correlation name (table alias) in a text command. This value provides a cursor name in a text command. This value indicates that the character used in a LIKE clause to escape the character returned for the DBLITERAL_LIKE_PERCENT literal. For example, if a percent sign (%) is used to match zero or more
Chapter Four—An OLE DB Primer n Value DBLITERAL_ESCAPE_ PERCENT_PREFIX (cont.)
DBLITERAL_ESCAPE_ PERCENT_SUFFIX
DBLITERAL_ESCAPE_ UNDERSCORE_PREFIX
DBLITERAL_ESCAPE_ UNDERSCORE_SUFFIX
DBLITERAL_ INDEX_NAME DBLITERAL_ LIKE_PERCENT
87
Description characters and this is a backslash (\), the characters “abc\%%” match all character values that start with “abc%”. Some SQL dialects support a clause (the ESCAPE clause) that can be used to override this value. This value shows the escape character, if any, used to suffix the character returned for the DBLITERAL_LIKE_ PERCENT literal. For example, if a percent sign (%) is used to match zero or more characters, and percent signs are escaped by enclosing in open and close square brackets, DBLITERAL_ESCAPE_PERCENT_PREFIX is “[”, DBLITERAL_ESCAPE_PERCENT_SUFFIX is “]”, and the characters “abc[%]%” match all character values that start with “abc%”. OLE DB providers that do not use a suffix character to escape the DBLITERAL_ ESCAPE_PERCENT character do not return this literal value, and set the lt member of the DBLITERAL structure to DBLITERAL_INVALID if requested. This value gives the character used in a LIKE clause to escape the character returned for the DBLITERAL_ LIKE_UNDERSCORE literal. For example, if an underscore (_) is used to match exactly one character and this is a backslash (\), the characters “abc\_ _” match all character values that are five characters long and start with “abc_”. Some SQL dialects support a clause (the ESCAPE clause) that can be used to override this value. This value provides the escape character, if any, used to suffix the character returned for the DBLITERAL_LIKE_ UNDERSCORE literal. For example, if an underscore (_) is used to match exactly one character, and underscores are escaped by enclosing in open and close square brackets, DBLITERAL_ESCAPE_UNDERSCORE_ PREFIX is “[”, DBLITERAL_ESCAPE_UNDERSCORE_ SUFFIX is “]”, and the characters “abc[_]_” match all character values that are five characters long and start with “abc_”. OLE DB providers that do not use a suffix character to escape the DBLITERAL_ESCAPE_UNDERSCORE character do not return this literal value, and set the lt member of the DBLITERAL structure to DBLITERAL_INVALID if requested. This value shows an index name used in a text command or in a data-definition interface. This value indicates the character used in a LIKE clause to match zero or more characters. For example, if this is a percent sign (%), the characters “abc%” match all character values that start with “abc”.
88
n Chapter Four—An OLE DB Primer Value DBLITERAL_ LIKE_UNDERSCORE
Description This value shows the character used in a LIKE clause to match exactly one character. For example, if this is an underscore (_), the characters “abc_” match all character values that are four characters long and start with “abc”.
DBLITERAL_ PROCEDURE_NAME DBLITERAL_ SCHEMA_NAME DBLITERAL_ SCHEMA_SEPARATOR
This value gives a procedure name in a text command.
DBLITERAL_ TABLE_NAME DBLITERAL_ TEXT_COMMAND DBLITERAL_ USER_NAME DBLITERAL_ VIEW_NAME DBLITERAL_ QUOTE_PREFIX DBLITERAL_ QUOTE_SUFFIX
This value indicates a schema name in a text command. This value shows the character that separates the schema name from the rest of the identifier in a text command. This value gives a table name used in a text command or in a data-definition interface. This value provides a text command, such as a SQL statement. This value specifies a user name in a text command. This value shows a view name in a text command. This value gives the character used in a text command as the opening quote for quoting identifiers that contain special characters. This value indicates the character used in a text command as the closing quote for quoting identifiers that contain special characters. 1.x providers that use the same character as the prefix and suffix may not return this literal value, and set the lt member of the DBLITERAL structure to DBLITERAL_INVALID if requested.
If an OLE DB provider returns non-null CATALOG or SCHEMA column values in schema rowsets, then it must support the IDBInfo method to describe how a fully qualified name is assembled. An OLE DB provider that does not support the IDBInfo method must return null values for CATALOG and SCHEMA columns in all schema rowsets. It has the following syntax: HRESULT GetLiteralInfo( ULONG cLiterals, const DBLITERAL rgLiterals[], ULONG * pcLiteralInfo, DBLITERALINFO ** prgLiteralInfo, OLECHAR ** ppCharBuffer);
Chapter Four—An OLE DB Primer n
89
It has the following parameters: cLiterals [in] This parameter gives the number of literals being asked about. If this is zero, the OLE DB provider ignores rgLiterals and returns information about all of the literals it supports. rgLiterals [in] This parameter shows an array of cLiterals literals about which to return information. If the OLE DB consumer specifies an invalid DBLITERAL value in this array, GetLiteralInfo returns False in fSupported in the corresponding element of the *prgLiteralInfo array. If cLiterals is zero, this parameter is ignored. pcLiteralInfo [out] This parameter provides a pointer to memory in which to return the number of literals for which information was returned. If cLiterals is zero, this is the total number of literals supported by the OLE DB provider. If an error other than DB_E_ERRORSOCCURRED occurs, *pcLiteralInfo is set to zero. prgLiteralInfo [out] This parameter shows a pointer to memory in which to return a pointer to an array of DBLITERALINFO structures. One structure is returned for each literal. The OLE DB provider allocates memory for the structures and returns the address to this memory; the OLE DB consumer releases this memory with IMalloc::Free when it no longer needs the structures. If *pcLiteralInfo is zero on output or an error other than DB_E_ERRORSOCCURRED occurs, the OLE DB provider does not allocate any memory and ensures that *prgLiteralInfo is a null pointer on output. ppCharBuffer [out] This parameter provides a pointer to memory in which to return a pointer for all string values (pwszLiteralValue, pwszInvalidChars, and pwszInvalidStartingChars) within a single allocation block. The OLE DB provider allocates this memory and the OLE DB consumer releases it with IMalloc::Free when it no longer needs it. If *pcLiteralInfo is zero on output or an error occurs, the OLE DB provider does not allocate any memory and ensures that *ppCharBuffer is a null pointer on output. Each of the individual string values stored in this buffer is terminated by a null-termination character. Therefore, the buffer may contain one or more strings, each with its own null-termination character, and may contain embedded null-termination characters. It has the following return codes: S_OK This return code means the method succeeded. In each structure returned in *prgLiteralInfo, the fSupported element is set to True.
90
n Chapter Four—An OLE DB Primer DB_S_ERRORSOCCURRED This return code shows that rgLiterals contained at least one unsupported or invalid literal. In the structures returned in *prgLiteralInfo for unsupported or invalid literals, the fSupported element is set to False. E_FAIL This return code specifies that a provider-specific error occurred. E_INVALIDARG This return code shows that cLiterals was not equal to zero and rgLiterals was a null pointer, or that pcLiteralInfo, prgLiteralInfo, or ppCharBuffer was a null pointer. E_OUTOFMEMORY This return code indicates that the OLE DB provider was unable to allocate sufficient memory in which to return the DBLITERALINFO structures or the strings containing the valid and starting characters. E_UNEXPECTED This return code shows that the data source object was in an uninitialized state. DB_E_ERRORSOCCURRED This return code means that all literals were either invalid or unsupported. The OLE DB provider allocates memory for *prgLiteralInfo and sets the value of the fSupported element in all of the structures to False. The OLE DB consumer frees this memory when it no longer needs the information.
IGetDataSource This is a mandatory interface on the session for obtaining an interface pointer to the data source object. Method GetDataSource
Description Returns an interface pointer on the data source object that created this session.
IGetDataSource::GetDataSource This method returns an interface pointer on the data source object that created the session. It has the following syntax: HRESULT GetDataSource ( REFIID riid, IUnknown ** ppDataSource);
Chapter Four—An OLE DB Primer n
91
It has the following parameters: riid [in] This parameter gives the IID of the interface on which to return a pointer. ppDataSource [out] This parameter provides a pointer to memory in which to return the interface pointer. If GetDataSource fails, it must attempt to set *ppDataSource to a null pointer. It has the following return codes: S_OK This return code means that the method succeeded. E_FAIL This return code indicates that an OLE DB provider-specific error occurred. E_INVALIDARG This return code shows *ppDataSource was a null pointer. E_NOINTERFACE This return code means that the object that created this session did not support the interface specified in riid.
IOpenRowset IOpenRowset is a required interface on the session. It can be supported by providers that do not support creating rowsets through commands. The IOpenRowset model enables consumers to open and work directly with individual tables or indexes in a data source by using IOpenRowset:: OpenRowset, which generates a rowset of all rows in the table or index. Method OpenRowset
Description This method opens and returns a rowset that includes all rows from a single base table or index.
IOpenRowset::OpenRowset This function opens and returns a rowset that includes all rows from a single base table or index. If the table or index has no rows, the rowset is still created. The resulting rowset is fully functional and can be used, for example, to insert new rows or determine column metadata. pTableID and pIndexID are used in the following combinations: n
If pTableID is not a null pointer and pIndexID is a null pointer, the table identified by *pTableID is opened.
92
n Chapter Four—An OLE DB Primer n
If pTableID is a null pointer and pIndexID is not a null pointer, *pIndexID must uniquely and fully identify an index; this index is opened. If *pIndexID does not uniquely and fully identify an index, DB_E_NOINDEX is returned.
n
If neither pTableID nor pIndexID is a null pointer, *pIndexID must identify an index for the table identified by *pTableID; this index is opened. If *pIndexID does not identify an index for the table identified by *pTableID, DB_E_NOINDEX is returned.
n
If both pTableID and pIndexID are null pointers, E_INVALIDARG is returned.
OLE DB consumers must supply fully qualified names as pTableID on OLE DB providers that support catalog or schema names.The threading model of the returned rowset is determined by the property DBPROP_ROWTHREAD_MODEL. It has the following syntax: HRESULT OpenRowset( IUnknown * pUnkOuter, DBID * pTableID, DBID * pIndexID, REFIID riid, ULONG cPropertySets, DBPROPSET rgPropertySets[], IUnknown ** ppRowset);
It has the following parameters: pUnkOuter [in] This parameter shows the controlling IUnknown if the rowset is to be aggregated; otherwise it is a null pointer. pTableID [in] This parameter gives the DBID of the table to open. pIndexID [in] This parameter indicates the DBID of the index to open. riid [in] This parameter provides the IID of the interface to return in *ppRowset. This interface is conceptually added to the list of required interfaces on the resulting rowset, and the method fails (E_NOINTERFACE) if that interface cannot be supported on the resulting rowset. This must be an interface that the rowset supports, even when *ppRowset is set to a null pointer and no rowset is created. cPropertySets [in] This parameter gives the number of DBPROPSET structures in rgPropertySets. If this is zero, the OLE DB provider ignores rgPropertySets.
Chapter Four—An OLE DB Primer n
93
rgPropertySets [in/out] This parameter shows an array of DBPROPSET structures containing properties and values to be set. The properties specified in these structures must belong to the Rowset property group. If the same property is specified more than once in rgPropertySets, then it is OLE DB provider-specific which value is used. If cPropertySets is zero, this argument is ignored. ppRowset [in/out] This parameter provides a pointer to memory in which to return the interface pointer to the created rowset. If *ppRowset is a null pointer, no rowset is created; properties are verified and if a required property cannot be set, DB_E_ERRORSOCCURRED is returned. If the OpenRowset method fails, *ppRowset is set to a null pointer. It has the following return codes: S_OK This return code means the method succeeded and the rowset is opened. In all DBPROP structures passed to the method, dwStatus is set to DBPROPSTATUS_OK. DB_S_ASYNCHRONOUS This return code shows that the method has initiated asynchronous creation of the rowset. The OLE DB consumer can call IDBAsynchStatus to poll for status or IConnectionPointContainer to obtain the IID_IDBAsynchNotify Connection Point. Attempting to call any other interfaces may fail, and the full set of interfaces may not be available on the object until asynchronous initialization of the rowset has completed. DB_S_ERRORSOCCURRED This return code indicates the rowset was opened but one or more properties—for which the dwOptions element of the DBPROP structure was DBPROPOPTIONS_OPTIONAL—were not set. The OLE DB consumer checks dwStatus in the DBPROP structure to determine which properties were not set. The method can fail to set properties for a number of reasons, including: n
colid in the DBPROP structure was invalid.
n
The data type in vValue in the DBPROP structure was not the data type of the property or was not VT_EMPTY.
n
The value in vValue in the DBPROP structure was invalid.
n
The property’s value conflicted with an existing property.
n
A property was specified to be applied to all columns, but could not be applied to one or more columns.
n
The property was not supported by the OLE DB provider.
n
It was not possible to set the property.
94
n Chapter Four—An OLE DB Primer DB_S_STOPLIMITREACHED This return code shows that execution has been stopped because a resource limit has been reached. The results obtained so far have been returned. This return code takes precedence over DB_S_ERRORSOCCURRED; that is, if the conditions described here and those described in DB_S_ERRORSOCCURRED both occur, the OLE DB provider returns this code. When the OLE DB consumer receives this return code, it should also check for the conditions described in DB_S_ERRORSOCCURRED. E_FAIL This return code indicates an OLE DB provider-specific error occurred. E_INVALIDARG This return code shows that *pTableID and *pIndexID were both null pointers; that cPropertySets was not zero and prgPropertySets was a null pointer; or that in an element of rgPropertySets, cProperties was not zero and rgProperties was a null pointer. E_NOINTERFACE This return code means the rowset did not support the interface specified in riid or riid was IID_NULL. DB_E_ERRORSOCCURRED This return code indicates no rowset was returned because one or more properties—for which the dwOptions element of the DBPROP structure was DBPROPOPTIONS_REQUIRED or an invalid value—were not set. The OLE DB consumer checks dwStatus in the DBPROP structures to determine which properties were not set. None of the satisfiable properties are remembered. The method can fail to set properties for any of the reasons specified in DB_S_ERRORSOCCURRED except the reason that states that it was not possible to set the property. DB_E_ABORTLIMITREACHED This return code shows the method failed because a resource limit has been reached. For example, a query used to implement the method timed out. No rowset is returned. DB_E_NOAGGREGATION This return code indicates pUnkOuter was not a null pointer and the rowset being created does not support aggregation. pUnkOuter was non-null and riid was not IID_Unknown. DB_E_NOINDEX This return code means the specified index does not exist in the current data source or did not apply to the specified table. The provider does not support opening indexes through the IOpenRowset method. DB_E_NOTABLE This return code provides that the specified table does not exist in the current data source.
Chapter Four—An OLE DB Primer n
95
DB_E_PARAMNOTOPTIONAL This return code shows that the table specified by *pTableID is a procedure that requires one or more parameters. DB_SEC_E_PERMISSIONDENIED This return code indicates that the OLE DB consumer did not have sufficient permission to open the rowset. For example, a rowset included a column for which the OLE DB consumer does not have read permission. DB_E_OBJECTOPEN This return code shows the OLE DB provider would have to open a new connection to support the operation and DBPROP_MULTIPLECONNECTIONS is set to VARIANT_FALSE.
ISessionProperties ISessionProperties returns information about the properties a session supports and the current settings of those properties. It is a mandatory interface on sessions. Method GetProperties Set Properties
Description This method returns the list of properties in the Session property group that are currently set on the session. This method sets properties in the Session property group.
ISessionProperties::GetProperties This method returns the list of properties in the Session property group that are currently set on the session. It has the following syntax: HRESULT GetProperties ( ULONG cPropertyIDSets, const DBPROPIDSET rgPropertyIDSets[], ULONG * pcPropertySets, DBPROPSET ** prgPropertySets);
It has the following parameters: cPropertyIDSets [in] This parameter shows the number of DBPROPIDSET structures in rgPropertyIDSets. If cPropertyIDSets is zero, the OLE DB provider ignores rgPropertyIDSets and returns the values of all properties in the Session property group for which values have been set or defaults exist. It does not return the values of properties in the Session property group for which values have not been set and no defaults exist. If cPropertyIDSets is not zero, the OLE DB provider returns the values of the requested properties. If a property is not supported, the returned value of dwStatus in the returned DBPROP structure for that property is
96
n Chapter Four—An OLE DB Primer DBPROPSTATUS_NOTSUPPORTED and the value of dwOptions is undefined. rgPropertyIDSets [in] This parameter gives an array of cPropertyIDSets DBPROPIDSET structures. The properties specified in these structures must belong to the Session property group. The OLE DB provider returns the values of information about the properties specified in these structures. If cPropertyIDSets is 0, then this parameter is ignored. pcProperty Sets [out] This parameter provides a pointer to memory in which to return the number of DBPROPSET structures returned in *prgPropertySets. If cPropertyIDSets is zero, *pcPropertySets is the total number of property sets for which the OLE DB provider supports at least one property in the Session property group. If cPropertyIDSets is greater than zero, *pcPropertySets is set to cPropertyIDSets. If an error other than DB_E_ERRORSOCCURRED occurs, *pcPropertySets is set to zero. prgPropertySets [out] This parameter shows a pointer to memory in which to return an array of DBPROPSET structures. If cPropertyIDSets is zero, then one structure is returned for each property set that contains at least one property belonging to the Session property group. If cPropertyIDSets is not zero, then one structure is returned for each property set specified in rgPropertyIDSets. If cPropertyIDSets is not zero, the DBPROPSET structures in *prgPropertySets are returned in the same order as the DBPROPIDSET structures in rgPropertyIDSets; that is, for corresponding elements of each array, the guidPropertySet elements are the same. If cPropertyIDs, in an element of rgPropertyIDSets, is not zero, the DBPROP structures in the corresponding element of *prgPropertySets are returned in the same order as the DBPROPID values in rgPropertyIDs; that is, for corresponding elements of each array, the property IDs are the same. The OLE DB provider allocates memory for the structures and returns the address to this memory; the OLE DB consumer releases this memory with the IMalloc::Free method when it no longer needs the structures. Before calling the IMalloc::Free method for *prgPropertySets, the OLE DB consumer should call IMalloc::Free for the rgProperties element within each element of *prgPropertySets. If *pcPropertySets is zero on output or if an error other than DB_E_ERRORSOCCURRED occurs, the OLE DB provider does not allocate any memory and ensures that *prgPropertySets is a null pointer on output. It has the following return codes: S_OK This return code indicates that the method succeeded. In all DBPROP
Chapter Four—An OLE DB Primer n
97
structures returned by the method, dwStatus is set to DBPROPSTATUS_OK. DB_S_ERRORSOCCURRED This return code shows that no value was returned for one or more properties. The OLE DB consumer checks dwStatus in the DBPROP structure to determine the properties for which values were not returned. The GetProperties method can fail to return properties for a number of reasons, including: n
The property was not supported by the OLE DB provider.
n
The property was not in the Session property group.
n
The property set was not supported by the OLE DB provider. If cPropertyIDs in the DBPROPIDSET structure for the property set was zero, the OLE DB provider cannot set dwStatus in the DBPROP structure because it does not know the IDs of any properties in the property set. Instead, it sets cProperties to zero in the DBPROPSET structure returned for the property set.
E_FAIL This return code means that an OLE DB provider-specific error occurred. E_INVALIDARG This return code shows that cPropertyIDSets was not equal to zero and rgPropertyIDSets was a null pointer; *pcPropertySets or *prgPropertySets was a null pointer; or in an element of rgPropertyIDSets, cPropertyIDs was not zero and rgPropertyIDs was a null pointer. E_OUTOFMEMORY This return code indicates the OLE DB provider was unable to allocate sufficient memory in which to return the DBPROPSET or DBPROP structures. DB_E_ERRORSOCCURRED This return code means values were not returned for any properties. The OLE DB provider allocates memory for *prgPropertySets and the OLE DB consumer checks dwStatus in the DBPROP structures to determine why properties were not returned. The OLE DB consumer frees this memory when it no longer needs the information.
ISessionProperties::SetProperties This method sets properties in the Session property group. It has the following syntax: HRESULT SetProperties ( ULONG cPropertySets, DBPROPSET rgPropertySets[]);
98
n Chapter Four—An OLE DB Primer It has the following parameters: cPropertySets [in] This parameter shows the number of DBPROPSET structures in rgPropertySets. If this is zero, OLE DB provider ignores rgPropertySets and the method does not do anything. rgPropertySets [in/out] This parameter gives an array of DBPROPSET structures containing properties and values to be set. The properties specified in these structures must belong to the Session property group. If the same property is specified more than once in rgPropertySets, then which value is used is OLE DB provider-specific. If cPropertySets is zero, this parameter is ignored. It has the following return codes: S_OK This return code shows that the method succeeded. In all DBPROP structures passed to the method, dwStatus is set to DBPROPSTATUS_OK. DB_S_ERRORSOCCURRED This return code indicates that one or more properties were not set. Properties not in error remain set. The OLE DB consumer checks dwStatus in the DBPROP structures to determine which properties were not set. The SetProperties method can fail to set properties for a number of reasons, including: n
The property was not supported by the OLE DB provider.
n
The property was not in the Session property group.
n
The property set was not supported by the OLE DB provider.
n
The property is read-only and was not set to its default value.
n
It was not possible to set the property.
n
colid in the DBPROP structure was not DB_NULLID.
n
The value of dwOptions in the DBPROP structure was invalid.
n
The data type in vValue in the DBPROP structure was not the data type of the property or was not VT_EMPTY.
n
The value in vValue in the DBPROP structure was invalid.
n
The property’s value conflicted with an existing property.
E_FAIL This return code means that an OLE DB provider-specific error occurred. E_INVALIDARG This return code shows that cPropertySets was not equal to zero and rgPropertySets was a null pointer, or that in an element of rgPropertySets, cProperties was not zero and rgProperties was a null pointer. DB_E_ERRORSOCCURRED This return code indicates all property values were invalid and no
Chapter Four—An OLE DB Primer n
99
properties were set. The OLE DB consumer checks dwStatus in the DBPROP structures to determine why properties were not set.
IDBCreateCommand Consumers call IDBCreateCommand::CreateCommand on a session to obtain a new command. Method CreateCommand
Description This method creates a new command.
IDBCreateCommand::CreateCommand This method creates a new command. If the session is transacted, the command and any actions performed as a result of executing that command are within the scope of the transaction. It has the following syntax: HRESULT CreateCommand( IUnknown * pUnkOuter, REFIID riid, IUnknown ** ppCommand);
It has the following parameters: pUnkOuter [in] This parameter shows a pointer to the controlling IUnknown interface if the new command is being created as part of an aggregate. It is a null pointer if the command is not part of an aggregate. riid [in] This parameter gives the IID of the interface requested on the command. ppCommand [out] This parameter provides a pointer to memory in which to return the interface pointer on the newly created command. It has the following return codes: S_OK This return code means the method succeeded. E_FAIL This return code indicates that an OLE DB provider-specific error occurred. E_INVALIDARG This return code shows *ppCommand was a null pointer. E_NOINTERFACE This return code states that the command did not support the interface specified in riid.
100
n Chapter Four—An OLE DB Primer E_OUTOFMEMORY This return code indicates that the OLE DB provider did not have enough memory to create the command. DB_E_NOAGGREGATION This return code shows *pUnkOuter was not a null pointer and the command being created does not support aggregation, or that *pUnkOuter was non-null and riid was not IID_Unknown. DB_E_OBJECTOPEN This return code means that the OLE DB provider would have to open a new connection to support the operation and DBPROP_MULTIPLECONNECTIONS is set to VARIANT_FALSE.
ITableDefinition The ITableDefinition interface exposes simple methods to create, drop, and alter tables on the data source. ITableDefinition is optional for OLE DB providers that do not otherwise support table creation; it is mandatory for OLE DB providers that support table creation through commands. Method AddColumn CreateTable DropColumn DropTable
Description This method adds a new column to a base table. This method creates a new base table in the data source. This method drops a column from a base table. This method drops a base table in the data source.
ITableDefinition::AddColumn This method adds a new column to a base table. If the AddColumn method returns any errors, the column is not created. The placement of the new column, whether inserted or added after the last column, is OLE DB provider-specific. It has the following syntax: HRESULT AddColumn( DBID * pTableID, DBCOLUMNDESC * pColumnDesc, DBID ** ppColumnID);
Chapter Four—An OLE DB Primer n
101
It has the following parameters: pTableID [in] This parameter gives a pointer to the DBID of the table to which the column is to be added. pColumnDesc [in/out] This parameter provides a pointer to the DBCOLUMNDESC structure that describes the new column. ppColumnID [out] This parameter shows a pointer to memory in which to return the returned DBID of newly created column. If this is a null pointer, no DBID is returned. If *ppColumnID is non-null, the OLE DB provider allocates memory for the DBID and overwrites *ppColumnID with a pointer to this new DBID without regard for its current value. It has the following return codes: S_OK This return code shows that the method succeeded. DB_S_ERRORSOCCURRED This return code means that the column was added, but one or more properties—for which the dwOptions element of the DBPROP structure was DBPROPOPTIONS_OPTIONAL—were not set. The OLE DB consumer checks dwStatus in the DBPROP structure to determine which properties were not set. The method can fail to set properties for a number of reasons, including: n
The property was not supported by the OLE DB provider.
n
The property was not in the Column, Rowset, or Table property group.
n
The property set was not supported by the OLE DB provider.
n
It was not possible to set the property.
n
colid in the DBPROP structure was not DB_NULLID.
n
The data type in vValue in the DBPROP structure was not the data type of the property or was not VT_EMPTY.
n
The value in vValue in the DBPROP structure was invalid.
n
The property’s value conflicted with an existing property.
E_FAIL This return code indicates that an OLE DB provider-specific error occurred. E_INVALIDARG This return code shows *pTableID or *pColumnDesc was a null pointer. DB_E_BADCOLUMNID This return code means that dbcid in *pColumnDesc was an invalid column ID.
102
n Chapter Four—An OLE DB Primer DB_E_BADPRECISION This return code indicates that the precision in *pColumnDesc was invalid. DB_E_BADSCALE This return code shows the scale in *pColumnDesc was invalid. DB_E_BADTABLEID This return code provides that *pTableID was an invalid table ID. DB_E_BADTYPE This return code shows that one or more of the wType, pwszTypeName, and pTypeInfo elements in an rgColumnDescs element was invalid. DB_E_DUPLICATECOLUMNID This return code states that dbcid in *pColumnDesc was the same as an existing column ID. DB_E_ERRORSOCCURRED This return code indicates that the column was not added because one or more properties—for which the dwOptions element of the DBPROP structure was DBPROPOPTIONS_REQUIRED or an invalid value—could not be set. The OLE DB consumer checks dwStatus in the DBPROP structures to determine which properties were not set. The method can fail to set properties for any of the reasons specified in DB_S_ERRORSOCCURRED. DB_E_NOTABLE This return code shows that the specified table does not exist in the current data source. DB_E_TABLEINUSE This return code means the specified table was in use. DB_SEC_E_PERMISSIONDENIED This return code shows that the OLE DB consumer did not have sufficient permission to add a column.
ITableDefinition::CreateTable This function creates a new base table in the data source. If *ppRowset is not a null pointer, an empty rowset is opened on the newly created table. If CreateTable returns any errors, the table is not created, and *ppTableID is set to Null on output. It has the following syntax: HRESULT CreateTable( IUnknown * pUnkOuter, DBID * pTableID, ULONG cColumnDescs, DBCOLUMNDESC rgColumnDescs[], REFIID riid, ULONG cPropertySets, DBPROPSET rgPropertySets[],
Chapter Four—An OLE DB Primer n DBID ** IUnknown **
103
ppTableID, ppRowset);
It has the following parameters: pUnkOuter [in] This parameter indicates the controlling unknown if the rowset is to be aggregated; otherwise it is a null pointer. pTableID [in] This parameter gives a pointer to the ID of the table to create. If this is a null pointer, the OLE DB provider must assign a unique ID to the table. cColumnDescs [in] This parameter shows the number of DBCOLUMNDESC structures in the rgColumnDescs array. This can be zero if the OLE DB provider allows the creation of tables with no columns. rgColumnDescs [in/out] This parameter gives an array of DBCOLUMNDESC structures that describe the columns of the table. It has the following structure: typedef struct tagDBCOLUMNDESC { LPOLESTR pwszTypeName; ITypeInfo * pTypeInfo; DBPROPSET * rgPropertySets; CLSID * pclsid; ULONG cPropertySets; ULONG ulColumnSize; DBID dbcid; DBTYPE wType; BYTE bPrecision; BYTE bScale; } DBCOLUMNDESC;
The elements of this structure are used as follows. The OLE DB consumer generally decides the values to use in the non-properties elements of this structure based on values from the PROVIDER_TYPES schema rowset. Element pwszTypeName
Description This element gives the OLE DB provider-specific name of the data type of the column. This name corresponds to a value in the TYPE_NAME column in the PROVIDER_TYPES schema rowset. In most cases, there is no reason for an OLE DB consumer to specify a value for pwszTypeName that is different from the values listed in the PROVIDER_TYPES schema rowset.
104
n Chapter Four—An OLE DB Primer Element pTypeInfo
rgPropertySets
pclsid
cPropertySets
ulColumnSize
dbcid wType
bPrecision
Description This element indicates that if *pTypeInfo is not a null pointer, then the data type of the column is an abstract data type (ADT) and values in this column are actually instances of the type described by the Type Library. wType may be either DBTYPE_BYTES with a length of at least 4, or it may be DBTYPE_IUNKNOWN. The instance values are required to be COM objects derived from the IUnknown method. This element shows an array of DBPROPSET structures containing properties and values to be set. The properties specified in these structures must belong to the Column property group. All properties specified in *rgPropertySets for this element of rgColumnDescs apply to the column specified by dbcid; the colid element in the DBPROP structure for specified properties is ignored. If the same property is specified more than once in *rgPropertySets, then it is OLE DB providerspecific which value is used. If cPropertySets is zero, this argument is ignored. If the column contains OLE objects, this element specifies a pointer to the class ID of those objects. If more than one class of objects can reside in the column, *pclsid is set to IID_NULL. This element provides the number of DBPROPSET structures in *rgPropertySets. If this is zero, the OLE DB provider ignores *rgPropertySets. This element shows that if wType is DBTYPE_STR or DBTYPE_WSTR, this is the maximum length in characters for values in this column. If wType is DBTYPE_BYTES, this is the maximum length in bytes for values in this column. For all other values of wType, this is ignored. OLE DB providers that do not require a length argument in the specification of the OLE DB provider type (pwszTypeName) ignore this value. This element shows the column ID of the column. This element provides the type indicator for the data type of the column. This name corresponds to a value in the DATA_TYPE column in the PROVIDER_TYPES schema rowset. In most cases, there is no reason for an OLE DB consumer to specify a value for wType that is different from the values listed in the PROVIDER_ TYPES schema rowset. This element gives the maximum precision of data values in the column when wType is the indicator for a numeric type; it is ignored for all other data types. This
Chapter Four—An OLE DB Primer n Element bPrecision (cont.)
bScale
105
Description must be within the limits specified for the type in the COLUMN_SIZE column in the PROVIDER_TYPES schema rowset. This element specifies the scale of data values in the column when wType is DBTYPE_NUMERIC or DBTYPE_DECIMAL; it is ignored for all other data types. This must be within the limits specified for the type in the MINIMUM_SCALE and MAXIMUM_SCALE columns in the PROVIDER_TYPES schema rowset.
riid [in] The IID of the interface to be returned for the resulting rowset; this is ignored if *ppRowset is a null pointer. cPropertySets [in] This parameter gives the number of DBPROPSET structures in rgPropertySets. If this is zero, the OLE DB provider ignores rgPropertySets. rgPropertySets [in/out] This parameter shows an array of DBPROPSET structures containing properties and values to be set. The properties specified in these structures must belong to the Rowset property group (for properties that apply to the rowset returned in *ppRowset) or the Table property group (for properties that apply to the table). If the same property is specified more than once in rgPropertySets, then it is OLE DB provider-specific which value is used. If cPropertySets is zero, this argument is ignored. ppTableID [out] This parameter provides a pointer to memory in which to return the DBID of the newly created table. If *ppTableID is a null pointer, no DBID is returned. ppRowset [out] This parameter gives a pointer to memory in which to return the requested interface pointer on an empty rowset opened on the newly created table. If *ppRowset is a null pointer, no rowset is created. It has the following return codes: S_OK This return code indicates that the method succeeded and the table is created and opened. In all DBPROP structures passed to the method, dwStatus is set to DBPROPSTATUS_OK. DB_S_ERRORSOCCURRED This indicates the table was created and the rowset was opened, but one or more properties—for which the dwOptions element of the DBPROP structure was DBPROPOPTIONS_OPTIONAL—were not set. The OLE DB consumer checks dwStatus in the DBPROP structures to determine which
106
n Chapter Four—An OLE DB Primer properties were not set. The method can fail to set properties for a number of reasons, including: n
The property was not supported by the OLE DB provider.
n
The property was not in the Column, Rowset, or Table property group.
n
The property set was not supported by the OLE DB provider.
n
It was not possible to set the property.
n
colid in the DBPROP structure was not DB_NULLID.
n
The data type in vValue in the DBPROP structure was not the data type of the property or was not VT_EMPTY.
n
The value in vValue in the DBPROP structure was invalid.
n
The property’s value conflicted with an existing property.
E_FAIL This return code means that an OLE DB provider-specific error occurred. E_INVALIDARG This return code shows that *pTableID and *ppTableID were both null pointers; or cColumnDesc was not zero; rgColumnDescs was a null pointer; cPropertySets was not zero and rgPropertySets was a null pointer; or cColumnDesc was zero and the OLE DB provider does not support creating tables with no columns. It can also mean that in an element of rgPropertySets, cProperties was not zero and rgProperties was a null pointer, or in an element of rgColumnDescs, cPropertySets was not zero and rgPropertySets was a null pointer. DB_E_BADCOLUMNID This return code shows that dbcid in an element of rgColumnDescs was an invalid column ID. DB_E_BADTABLEID This return code indicates that *pTableID was an invalid table ID. DB_E_BADPRECISION This return code means that the precision in an element of rgColumnDescs was invalid. DB_E_BADSCALE This return code shows that the scale in an element of rgColumnDescs was invalid. DB_E_BADTYPE This return code specifies that one or more of the wType, pwszTypeName, or pTypeInfo elements in an element of rgColumnDescs was invalid. DB_E_DUPLICATECOLUMNID This return code provides that dbcid was the same in two or more elements of rgColumnDescs.
Chapter Four—An OLE DB Primer n
107
DB_E_DUPLICATETABLEID This return code indicates the specified table already exists in the current data source. DB_E_ERRORSOCCURRED This return code shows that the table was not created and no rowset was returned because one or more properties—for which the dwOptions element of the DBPROP structure was DBPROPOPTIONS_REQUIRED or an invalid value—could not be set. The OLE DB consumer checks dwStatus in the DBPROP structures to determine which properties were not set. The method can fail to set properties for any of the reasons specified in DB_S_ERRORSOCCURRED. DB_E_NOAGGREGATION This return code states that *pUnkOuter was not a null pointer and the rowset being created does not support aggregation. *pUnkOuter was non-null and riid was not IID_Unknown. DB_SEC_E_PERMISSIONDENIED This return code means the OLE DB consumer did not have sufficient permission to create the table. DB_E_OBJECTOPEN This return code shows that the OLE DB provider would have to open a new connection to support the operation, and DBPROP_MULTIPLECONNECTIONS is set to VARIANT_FALSE.
ITableDefinition::DropColumn This method drops a column from the base table. If the DropColumn method returns any errors, the column is not dropped. It has the following syntax: HRESULT DropColumn( DBID* pTableID, DBID* pColumnID);
It has the following parameters: pTableID [in] This parameter gives a pointer to the DBID of the table from which to drop the column. pColumnID [in] This parameter provides a pointer to the DBID of the column to drop. It has the following return codes: S_OK This return code means that the method succeeded and the column was dropped from the base table.
108
n Chapter Four—An OLE DB Primer E_FAIL This return code indicates an OLE DB provider-specific error occurred. E_INVALIDARG This return code shows that *pTableID or *pColumnID was a null pointer. DB_E_BADCOLUMNID This return code states that the column specified in *pColumnID does not exist in the specified table. DB_E_BADTABLEID This return code shows *pTableID was an invalid table ID. DB_E_NOTABLE This return code indicates the specified table does not exist in the current data source. DB_E_TABLEINUSE This return code means the specified table was in use. DB_SEC_E_PERMISSIONDENIED This return code states the OLE DB consumer did not have sufficient permission to drop the column. DB_E_BADTABLEID This return code specifies that *pTableID was an invalid table ID.
ITableDefinition::DropTable This method drops a base table in the data source. It has the following syntax: HRESULT DropTable ( DBID * pTableID);
It has the following parameters: pTableID [in] This parameter provides a pointer to the DBID of the base table to drop. It has the following return codes: S_OK This return code means that the method succeeded and the table is dropped. E_FAIL This return code shows that an OLE DB provider-specific error occurred. E_INVALIDARG This return code indicates that *pTableID was a null pointer. DB_E_NOTABLE This return code means the specified table does not exist in the current data source.
Chapter Four—An OLE DB Primer n
109
DB_E_TABLEINUSE This return code shows that the specified table was in use and could not be dropped. DB_SEC_E_PERMISSIONDENIED This return code states the OLE DB consumer did not have sufficient permission to drop the table. DB_E_BADTABLEID This return code indicates *pTableID was an invalid table ID.
ICommand ICommand contains methods to execute commands. A command can be executed many times and the parameter values can vary. This interface is mandatory on commands. A command object contains a single text command, which is specified through ICommandText. Method Cancel Execute GetDBSession
Description This method cancels the current command execution. This method executes the command. This method returns an interface pointer to the session that created the command.
ICommand::Cancel This method cancels the current command execution. Cancel may be called on a separate thread after Execute has been called and before the method has returned. If the executing command can be canceled, the method returns S_OK, and Execute returns DB_E_CANCELED. If the executing command cannot be canceled, Cancel returns DB_E_CANTCANCEL and Execute continues. If the Execute method has not been called or has already returned, the method returns S_OK. If the DBPROPVAL_ASYNCH_INITIALIZE bit in the DBPROP_ROWSET_ ASYNCH property has been set prior to execution of the command, the Execute method may return with DB_S_ASYNCHRONOUS before the command has fully executed. Calling ICommand::Cancel after the Execute method has returned has no effect on any asynchronously executing commands. Once the Execute method has returned DB_S_ASYNCHRONOUS, the command can only be canceled by calling IDBAsynchStatus::Abort on the returned object. It has the following syntax: HRESULT Cancel();
110
n Chapter Four—An OLE DB Primer It has no parameters. It has the following return codes: S_OK This return code means the method succeeded, or the command was not executing. E_FAIL This return code shows that an OLE DB provider-specific error occurred. DB_E_CANTCANCEL This return code indicates the executing command cannot be canceled.
ICommand::Execute This method executes the command. If the command returns rows, such as a SQL SELECT statement, the result of this method is a rowset over the result rows. If no rows match the command, the rowset is still created. The resulting rowset is fully functional and can be used, for example, to insert new rows or determine column metadata. If the command returns multiple results (rowsets or row counts), the OLE DB consumer requests a multiple-results object by setting riid to IID_IMultipleResults. The Execute method creates the multiple-results object and returns an IMultipleResults interface pointer to it in *ppRowset. The OLE DB consumer repeatedly calls IMultipleResults::GetResult to retrieve the results in order. If any or all parameters fail and the OLE DB provider does not support errors within an array of parameters (that is, the command fails if any or all of the parameters fail), the OLE DB provider returns DB_E_ERRORSOCCURRED and returns any error information for the failed parameters in their status bindings. If any or all parameters fail and the OLE DB provider supports errors within an array of parameters, the OLE DB provider returns DB_S_ERRORSOCCURRED, sets *pcRowsAffected to the number of successful parameters, and returns any error information for the failed parameters in their status bindings. If Execute is called multiple times for a single command, with or without changes to the command text, the outcome may reflect changes in the underlying stored data, depending on the isolation level specified for the surrounding transaction. Execute can be called when a rowset is already open on the command only if the only change between the calls is a change in the value of existing parameters (calls to ICommandWithParameters::SetParameterInfo will fail). Methods that modify the command (ICommandPrepare::Prepare, ICommandPrepare:: Unprepare, ICommandProperties::SetProperties, and ICommandText:: SetCommandText) while a rowset is open will fail and return DB_E_OBJECT-
Chapter Four—An OLE DB Primer n
111
OPEN. Each call to Execute creates a new rowset, which must be explicitly released by IRowset::Release. Execute does not affect the prepared state of a command. The OLE DB consumer determines whether the command supports parameters by calling QueryInterface for ICommandWithParameters. If this interface is exposed, the command supports parameters; if it is not exposed, the command does not support parameters. If the command does not support parameters, Execute ignores *pParams. However, if the command text includes parameters, Execute returns DB_E_ERRORSINCOMMAND. If an input parameter value is not specified, Execute returns DB_E_PARAMNOTOPTIONAL. If the OLE DB provider cannot describe parameters and the OLE DB consumer has not called SetParameterInfo for all parameters, the behavior of Execute is undefined. For example, Execute might guess at the parameter information or it might fail completely. If Execute returns DB_S_ERRORSOCCURRED or DB_E_ERRORSOCCURRED, the OLE DB consumer can immediately call ICommandProperties:: GetProperties with the DBPROPSET_PROPERTIESINERROR property set to return the properties that could not be set. Execute does not alter the value of any properties. That is, ICommandProperties::GetProperties returns the same value for a property regardless of whether it is called before or after Execute and whether Execute succeeded or failed. However, if a property value is not required, IRowsetInfo:: GetProperties can return a different value for that property than ICommandProperties::GetProperties. If several threads concurrently request execution of a given command, the corresponding executions are serialized, and each thread will block until its corresponding execution concludes. Execute can fail even if ICommandPrepare::Prepare has succeeded; this may be the case if, for example, the underlying schema has changed between the Prepare and Execute calls and the command text had therefore become illegal. It has the following syntax: HRESULT Execute ( IUnknown * pUnkOuter, REFIID riid, DBPARAMS * pParams, LONG * pcRowsAffected, IUnknown ** ppRowset);
112
n Chapter Four—An OLE DB Primer It has the following parameters: pUnkOuter [in] This parameter provides a pointer to the controlling IUnknown interface if the rowset is being created as part of an aggregate; otherwise, it is null. riid [in] This parameter gives the requested IID for the rowset returned in *ppRowset. This interface is conceptually added to the list of required interfaces on the resulting rowset, and the method fails (E_NOINTERFACE) if that interface cannot be supported on the resulting rowset. If this is IID_NULL, then *ppRowset is ignored and no rowset is returned, even if the command would otherwise generate a rowset. Specifying IID_NULL is useful in the case of text commands that do not generate rowsets, such as data definition commands, as a hint to the provider that no rowset properties need to be verified. If riid is IID_IMultipleResults, the OLE DB provider creates a multiple results object and returns a pointer to it in *ppRowset; it does this even if the command generates a single result. If the OLE DB provider supports multiple results and the command generates multiple results but riid is not IID_IMultipleResults, the OLE DB provider returns the first result and discards any remaining results. If riid is IID_IMultipleResults and the OLE DB provider does not support multiple results, Execute returns E_NOINTERFACE. pParams [in/out] This parameter gives a pointer to a DBPARAMS structure that specifies the values for one or more parameters. In text commands that use parameters, if no value is specified for a parameter through *pParams, an error occurs. struct DBPARAMS { void * pData; ULONG cParamSets; HACCESSOR hAccessor; };
The elements of this structure are used as follows: Element pData
Description This element shows a pointer to a buffer from which the OLE DB provider retrieves input parameter data and to which the OLE DB provider returns output parameter data, according to the bindings specified by hAccessor. This pointer must be a valid pointer to a contiguous block of OLE DB consumer-owned memory for the input and output parameter values. When
Chapter Four—An OLE DB Primer n Element pData (cont.)
cParamSets
hAccessor
113
Description output parameter data is available to the OLE DB consumer depends on the DBPROP_OUTPUTPARAMETERAVAILABILITY property. This element indicates the number of sets of parameters in *pData. If cParamSets is greater than 1, then the bindings described by hAccessor define the offsets within *pData for each set of parameters, and cbRowSize (as specified in IAccessor::CreateAccessor) defines a single fixed offset between each of those values and the corresponding values for the next set of parameters. Sets of multiple parameters (cParamSets is greater than one) can be specified only if DBPROP_MULTIPLEPARAMSETS is VARIANT_TRUE and the command does not return any rowsets. This element shows the handle of the OLE DB accessor to use. If hAccessor is the handle of a null accessor (cBindings in CreateAccessor was 0), then Execute does not retrieve or return any parameter values.
If the command text does not include parameters, the OLE DB provider ignores this argument. pcRowsAffected [out] This parameter gives a pointer to memory in which to return the count of rows affected by a command that updates, deletes, or inserts rows. If cParamSets is greater than one, *pcRowsAffected is the total number of rows affected by all of the sets of parameters specified in the execution. If the number of affected rows is not available, *pcRowsAffected is set to DB_COUNTUNAVAILABLE on output. If riid is IID_IMultipleResults, the value returned in *pcRowsAffected is either DB_COUNTUNAVAILABLE or the total number of rows affected by the entire command; to retrieve individual row counts, the OLE DB consumer calls IMultipleResults:: GetResult. If the command does not update, delete, or insert rows, *pcRowsAffected is undefined on output. If *pcRowsAffected is a null pointer, no count of rows is returned. *pcRowsAffected is undefined if ICommand::Execute returns DB_S_ ASYNCHRONOUS. For asynchronously executed commands, the OLE DB consumer should call IDBAsynchStatus::GetStatus to obtain the number of rows affected in pulProgress. ppRowset [in/out] This parameter shows a pointer to the memory in which to return the rowset’s pointer. If *ppRowset is a null pointer, no rowset is created. It has the following return codes: S_OK This return code means that the method succeeded. In all DBPROP structures returned by the method, dwStatus is set to DBPROPSTATUS_OK, the
114
n Chapter Four—An OLE DB Primer status of all input parameters bound by the OLE DB accessor is set to DBSTATUS_S_OK or DBSTATUS_S_ISNULL, and the status of all output parameters bound by the OLE DB accessor is set to DBSTATUS_S_OK, DBSTATUS_S_ISNULL, or DBSTATUS_S_TRUNCATED or is unknown because the parameter value has not been returned yet. DB_S_ASYNCHRONOUS This return code shows the method has initiated asynchronous creation of the rowset. The OLE DB consumer can call IDBAsynchStatus to poll for status or IConnectionPointContainer to obtain the IID_IDBAsynchNotify Connection Point. Attempting to call any other interfaces may fail, and the full set of interfaces may not be available on the object until asynchronous initialization of the rowset has completed. DB_S_ERRORSOCCURRED This return code can be returned for either of the following reasons: n
The command was executed but an error occurred while returning one or more output parameter values. To determine which output parameters were not returned, the OLE DB consumer checks the status values.
n
The rowset was opened but one or more properties—for which the dwOptions element of the DBPROP structure was DBPROPOPTIONS_OPTIONAL—were not set. The OLE DB consumer calls IRowsetInfo::GetProperties to determine which properties were set.
DB_S_STOPLIMITREACHED This return code means execution has been stopped because a resource limit has been reached. The results obtained so far have been returned. Execution cannot be resumed. It can also mean that multiple sets of parameters were specified and one or more but not all of the parameters have been processed prior to the command being canceled by ICommand::Cancel or IDBAsynchStatus:: Abort. This return code takes precedence over DB_S_ERRORSOCCURRED. That is, if the conditions described here and if those described in DB_S_ ERRORSOCCURRED both occur, the OLE DB provider returns this code. When the OLE DB consumer receives this return code, it should also check for the conditions described in DB_S_ERRORSOCCURRED. E_FAIL This return code shows that a provider-specific error occurred. E_INVALIDARG This return code indicates *pParams was not ignored, and that cParamSets in the DBPARAMS structure pointed to by *pParams was greater than one, *ppRowset was not a null pointer, and the OLE DB provider does not support multiple results.
Chapter Four—An OLE DB Primer n
115
It can also mean that *pParams was not ignored and, in the DBPARAMS structure pointed to by *pParams, *pData was a null pointer. It additionally can mean that *pParams was not ignored, was not a null pointer, and in the DBPARAMS structure pointed to by *pParams, cParamSets was zero. Finally, it can mean riid was not IID_NULL and *ppRowset was a null pointer. E_NOINTERFACE This return code means that the rowset did not support the interface specified in riid, or that riid was IID_IMultipleResults and the OLE DB provider did not support multiple results objects. DB_E_ABORTLIMITREACHED This return code specifies that execution has been aborted because a resource limit has been reached. For example, a query timed out. No results have been returned. DB_E_BADACCESSORHANDLE This return code shows that *pParams was not ignored and hAccessor in the DBPARAMS structure pointed to by *pParams was invalid. DB_E_BADACCESSORTYPE This return code means that hAccessor in the DBPARAMS structure pointed to by *pParams was not the handle of a parameter accessor. DB_E_CANCELED This return code provides that the command was canceled by a call to Cancel on another thread. No records were affected. DB_E_CANTCONVERTVALUE This return code indicates that a literal value in the command text could not be converted to the type of the associated column for reasons other than data overflow. DB_E_DATAOVERFLOW This return code states that a literal value in the command text overflowed the type specified by the associated column. DB_E_ERRORSINCOMMAND This return code shows the command text contained one or more errors. OLE DB providers should use OLE DB error objects to return details about the errors. DB_E_ERRORSOCCURRED This return code indicates the method failed due to one or more invalid input parameter values. To determine which input parameter values were invalid, the OLE DB consumer checks the status values. It can also indicate the command was not executed and no rowset was returned because one or more properties—for which the dwOptions element of the DBPROP structure was DBPROPOPTIONS_REQUIRED—were not set.
116
n Chapter Four—An OLE DB Primer DB_E_INTEGRITYVIOLATION This return code indicates that a literal value in the command text violated the integrity constraints for the column. DB_E_NOAGGREGATION This return code shows *pUnkOuter was not a null pointer and the rowset being created does not support aggregation, or that *pUnkOuter was non-null and riid was not IID_Unknown. DB_E_NOCOMMAND This return code means no command text was currently set on the command object. DB_E_NOTABLE This return code indicates that the specific table or view does not exist in the current data source. DB_E_PARAMNOTOPTIONAL This return code states that a value was not supplied for a required parameter, or the command text used parameters and *pParams was a null pointer. DB_SEC_E_PERMISSIONDENIED This return code shows that the OLE DB consumer did not have sufficient permission to execute the command. For example, a rowset-returning command specified a column for which the OLE DB consumer does not have read permission, or an update command specified a column for which the OLE DB consumer does not have write permission. DB_E_OBJECTOPEN This return code means that the OLE DB provider would have to open a new connection to support the operation and DBPROP_MULTIPLECONNECTIONS is set to VARIANT_FALSE. If this method performs deferred accessor validation and that validation takes place before any data is transferred, it can also return any of the following HRESULTs for the reasons listed in the corresponding DBBINDSTATUS values in IAccessor::CreateAccessor: E_NOINTERFACE DB_E_BADBINDINFO DB_E_BADORDINAL DB_E_BADSTORAGEFLAGS DB_E_UNSUPPORTEDCONVERSION
ICommand::GetDBSession This method returns an interface pointer to the session that created the command. It has the following syntax:
Chapter Four—An OLE DB Primer n
117
HRESULT GetDBSession ( REFIID riid, IUnknown ** ppSession);
It has the following parameters: riid [in] This parameter indicates the IID of the interface on which to return a pointer. ppSession [out] This parameter shows a pointer to memory in which to return the interface pointer. If the OLE DB provider does not have an object that created the command, it sets *ppSession to a null pointer. If GetDBSession fails, it must attempt to set *ppSession to a null pointer. It has the following return codes: S_OK This return code means the method succeeded. S_FALSE This return code shows that the OLE DB provider did not have an object that created the command. Therefore, it set *ppSession to a null pointer. E_FAIL This return code indicates that an OLE DB provider-specific error occurred. E_INVALIDARG This return code states that *ppSession was a null pointer. E_NOINTERFACE This return code shows that the session did not support the interface specified in riid.
ICommandProperties ICommandProperties specifies to the command the properties from the Rowset property group that must be supported by the rowsets returned by ICommand::Execute. A special case of these properties, and the ones most commonly requested, are the interfaces the rowset must support. In addition to interfaces, the OLE DB consumer can request properties that modify the behavior of the rowset or interfaces. All rowsets must support IRowset, IAccessor, IColumnsInfo, IRowsetInfo, and IConvertType. OLE DB providers may choose to return rowsets supporting other interfaces if doing so is possible and the support for the returned interfaces does not affect OLE DB consumer code that is not expecting them. The riid parameter of ICommand::Execute should be one of the interfaces returned by IRowsetInfo::GetProperties.
118
n Chapter Four—An OLE DB Primer This interface is mandatory on commands. Method GetProperties SetProperties
Description This method returns the list of properties in the Rowset property group that are currently requested for the rowset. This method sets properties in the Rowset property group.
ICommandProperties::GetProperties This method returns the list of properties in the Rowset property group that are currently requested for the rowset. GetProperties returns the current values of properties that have been set by the OLE DB consumer with ICommandProperties::SetProperties. For all values not set by the OLE DB consumer, GetProperties returns the initial property value. Even though IDBProperties::GetPropertyInfo lists a property as being supported by the OLE DB provider, GetProperties will not return a value for it if it does not apply to the current circumstances. For example, the OLE DB provider’s ability to support the property might be affected by the current transaction or the current command text. The property values returned by ICommandProperties::GetProperties are not affected by executing the command. However, IRowsetInfo::GetProperties might return a different value for a property than does ICommandProperties:: GetProperties. For example, if an OLE DB consumer requests ordered bookmarks if they are possible, it calls SetProperties to set the value of DBPROP_ ORDEREDBOOKMARKS to VARIANT_TRUE and specifies a dwOptions value of DBPROPOPTIONS_OPTIONAL. If the OLE DB provider cannot determine at this point in the definition of the command whether this is possible, ICommandProperties::GetProperties returns a value of VARIANT_TRUE and a dwOptions of DBPROPOPTIONS_OPTIONAL for this property. If the OLE DB provider determines during optimization or execution that ordered bookmarks are not possible, IRowsetInfo::GetProperties returns a value of VARIANT_FALSE and a dwOptions of zero. If ICommand::Execute returns DB_E_ERRORSOCCURRED, the OLE DB consumer can immediately call GetProperties with the DBPROPSET_ PROPERTIESINERROR property set to return all the properties that were in error. It has the following syntax: HRESULT GetProperties ( const ULONG cPropertyIDSets, const DBPROPIDSET rgPropertyIDSets[], ULONG * pcPropertySets, DBPROPSET ** prgPropertySets);
Chapter Four—An OLE DB Primer n
119
It has the following parameters: cPropertyIDSets [in] This parameter shows the number of DBPROPIDSET structures in rgPropertyIDSets. If cPropertySets is zero, the OLE DB provider ignores rgPropertyIDSets and returns the values of all properties in the Rowset property group for which values have been set or defaults exist. It does not return the values of properties in the Rowset property group for which values have not been set and no defaults exist, nor does it return the values of properties for which no value has been set or default exists, and for which a value will be set automatically because a value for another property in the Rowset property group has been set. If cPropertyIDSets is not zero, the OLE DB provider returns the values of the requested properties. If a property is not supported, the returned value of dwStatus in the returned DBPROP structure for that property is DBPROPSTATUS_NOTSUPPORTED and the value of dwOptions is undefined. rgPropertyIDSets [in] This parameter gives an array of cPropertyIDSets DBPROPIDSET structures. The properties specified in these structures must belong to the Rowset property group. The OLE DB provider returns the values of the properties specified in these structures. If cPropertyIDSets is zero, then this parameter is ignored. pcPropertySets [out] This parameter indicates a pointer to memory in which to return the number of DBPROPSET structures returned in *prgPropertySets. If cPropertyIDSets is zero, *pcPropertySets is the total number of property sets for which the OLE DB provider supports at least one property in the Rowset property group. If cPropertyIDSets is greater than zero, *pcPropertySets is set to cPropertyIDSets. If an error other than DB_E_ERRORSOCCURRED occurs, *pcPropertySets is set to zero. prgPropertySets [out] This parameter provides a pointer to memory in which to return an array of DBPROPSET structures. If cPropertyIDSets is zero, then one structure is returned for each property set that contains at least one property belonging to the Rowset property group. If cPropertyIDSets is not zero, then one structure is returned for each property set specified in rgPropertyIDSets. If cPropertyIDSets is not zero, the DBPROPSET structures in *prgPropertySets are returned in the same order as the DBPROPIDSET structures in rgPropertyIDSets; that is, for corresponding elements of each array, the guidPropertySet elements are the same. If cPropertyIDs, in an element of rgPropertyIDSets, is not zero, the DBPROP structures in the corresponding element of *prgPropertySets are returned in the same order
120
n Chapter Four—An OLE DB Primer as the DBPROPID values in rgPropertyIDs. Thus, in the case where no column properties are specified in rgPropertyIDSets, corresponding elements of the input rgPropertyIDs and the returned rgProperties have the same property ID. However, if a column property is requested in rgPropertyIDSets, multiple properties may be returned, one for each column, in rgProperties. In this case, corresponding elements of rgPropertyIDs and rgProperties will not have the same property ID, and rgProperties will contain more elements than rgPropertyIDs. The OLE DB provider allocates memory for the structures and returns the address to this memory; the OLE DB consumer releases this memory with IMalloc::Free when it no longer needs the structures. Before calling IMalloc::Free for *prgPropertySets, the OLE DB consumer should call IMalloc::Free for the rgProperties element within each element of *prgPropertySets. If *pcPropertySets is zero on output or an error other than DB_E_ERRORSOCCURRED occurs, the OLE DB provider does not allocate any memory and ensures that *prgPropertySets is a null pointer on output. It has the following return codes: S_OK This return code means the method succeeded. In all DBPROP structures returned by the method, dwStatus is set to DBPROPSTATUS_OK. DB_S_ERRORSOCCURRED This return code shows that no value was returned for one or more properties. The OLE DB consumer checks dwStatus in the DBPROP structure to determine the properties for which values were not returned. The GetProperties method can fail to return properties for a number of reasons, including: n
The property was not supported by the OLE DB provider.
n
The property was not in the Rowset property group.
n
The property set was not supported by the OLE DB provider. If cPropertyIDs in the DBPROPIDSET structure for the property set was zero, the OLE DB provider cannot set dwStatus in the DBPROP structure because it does not know the IDs of any properties in the property set. Instead, it sets cProperties to zero in the DBPROPSET structure returned for the property set.
E_FAIL This return code states that an OLE DB provider-specific error occurred. E_INVALIDARG This return code shows that cPropertyIDSets was not equal to zero and rgPropertyIDSets was a null pointer, or *pcPropertySets or *prgPropertySets was a null pointer. It can also mean in an element of rgPropertyIDSets, cPropertyIDs was not zero and rgPropertyIDs was a null
Chapter Four—An OLE DB Primer n
121
pointer, or guidPropertySet was DBPROPSET_PROPERTIESINERROR and cPropertyIDs was not zero, or rgPropertyIDs was not a null pointer. Finally, it can indicate cPropertyIDSets was greater than one, and in an element of rgPropertyIDSets, guidPropertySet was DBPROPSET_ PROPERTIESINERROR. E_OUTOFMEMORY This return code indicates that the OLE DB provider was unable to allocate sufficient memory in which to return the DBPROPSET or DBPROP structures. DB_E_ERRORSOCCURRED This return code specifies that no values were returned for any properties. The OLE DB provider allocates memory for *prgPropertySets and the OLE DB consumer checks dwStatus in the DBPROP structures to determine why properties were not returned. The OLE DB consumer frees this memory when it no longer needs the information.
ICommandProperties::SetProperties This method sets properties in the Rowset property group. OLE DB consumers should first set properties for interfaces and then set other properties that modify those interfaces. The combination of the values of an interface property and a noninterface property might result in another interface being requested. However, the value of a noninterface property, by itself, never results in another interface being requested. Setting property values is a cumulative operation. That is, each call to SetProperties attempts to change the values of the specified properties. If a new value is illegal or conflicts with the value of another property, the value of the property is not changed and SetProperties returns DBPROPSTATUS_BADVALUE or DBPROPSTATUS_CONFLICTING in the dwStatus element of the DBPROP structure for the property. Properties are processed from the beginning of the array to the end of the array. As they are processed, properties that conflict with previously set properties, including those set previously on the same call, are marked with DBPROP STATUS_CONFLICTING and processing continues through the array of properties. SetProperties cannot always determine whether a property can be set to its requested value. For example, the OLE DB provider often cannot determine whether the rowset is updatable until it creates the rowset. In this case, SetProperties appears to successfully set the property value and the OLE DB provider delays the determination until ICommand::Execute is called. If the value of dwOptions for a property is DBPROPOPTIONS_OPTIONAL, the OLE DB provider attempts to determine whether it can set the property to the requested value. If it can make this determination, it sets the property and returns DBPROPSTATUS_OK or does not set the property and returns DBPROPSTATUS_NOTSET. If it cannot make this determination, it can delay
122
n Chapter Four—An OLE DB Primer setting the property until the command is executed; in this case, the value of dwOptions returned for the property by GetProperties is DBPROPOPTIONS_OPTIONAL. OLE DB consumers should not attempt to unset mandatory rowset interfaces such as IRowset, IAccessor, IColumnsInfo, and IRowsetInfo. These interfaces are always supported, and SetProperties returns DBPROPSTATUS_ NOTSETTABLE for them. Even if IDBProperties::GetPropertyInfo lists a property as being supported by the provider, SetProperties can return DBPROPSTATUS_NOTSUPPORTED when the OLE DB consumer attempts to set the property value if the property does not apply to the current circumstances. For example, the OLE DB provider’s ability to support the property might be affected by the current transaction or the current command text. If an error occurs when setting a particular property, SetProperties flags the error in dwStatus in the DBPROP structure and continues processing. Although OLE DB providers allow changing properties at any time before command execution, OLE DB consumers are encouraged to set all properties prior to preparing the command to avoid forcing the OLE DB provider to reprepare the command at execution. It has the following syntax: HRESULT SetProperties ( ULONG cPropertySets, DBPROPSET rgPropertySets[]);
It has the following parameters: cPropertySets [in] This parameter shows the number of DBPROPSET structures in rgPropertySets. If this is zero, the OLE DB provider ignores rgPropertySets and the method does not do anything. rgPropertySets [in/out] This parameter provides an array of DBPROPSET structures containing properties and values to be set. The properties specified in these structures must belong to the Rowset property group. If the same property is specified more than once in rgPropertySets, then which value is used is OLE DB provider-specific. If cPropertySets is zero, this parameter is ignored. It has the following return codes: S_OK This return code means the method succeeded. In all DBPROP structures passed to the method, dwStatus is set to DBPROPSTATUS_OK.
Chapter Four—An OLE DB Primer n
123
DB_S_ERRORSOCCURRED This return code shows one or more properties were not set. Properties not in error remain set. The OLE DB consumer checks dwStatus in the DBPROP structures to determine which properties were not set. SetProperties can fail to set properties for a number of reasons, including: n
The property was not supported by the OLE DB provider.
n
The property was not in the Rowset property group.
n
The property set was not supported by the OLE DB provider.
n
The property is read-only and was not set to its default value.
n
It was not possible to set the property
n
The value of dwOptions in a DBPROP structure was invalid.
n
colid in the DBPROP structure was not DB_NULLID and the property cannot be set on a column.
n
The data type in vValue in the DBPROP structure was not the data type of the property or was not VT_EMPTY.
n
The value in vValue in the DBPROP structure was invalid.
n
The property’s value conflicted with an existing property.
E_FAIL This return code means an OLE DB provider-specific error occurred. E_INVALIDARG This return code specifies cPropertySets was not equal to zero and rgPropertySets was a null pointer, or in an element of rgPropertySets, cProperties was not zero and rgProperties was a null pointer. DB_E_ERRORSOCCURRED This return code states that all property values were invalid and no properties were set. The OLE DB consumer checks dwStatus in the DBPROP structures to determine why properties were not set. The method can fail to set properties for any of the reasons specified in DB_S_ERRORSOCCURRED, except the reason that states that it was not possible to set the property. DB_E_OBJECTOPEN This return code means properties cannot be set while there is an open rowset.
ICommandText This interface is mandatory on commands. A command object can have only one command text. When the command text is specified through SetCommandText, it replaces the existing command text.
124
n Chapter Four—An OLE DB Primer Method GetCommandText SetCommandText
Description Returns the command text set by the last call to SetCommandText. Sets the command text, replacing the existing command text.
ICommandText::GetCommandText This method returns the command text set by the last call to SetCommandText. The GUID returned in the GetCommandText method is the dialect that will be used by the OLE DB provider to interpret the statement. This is generally the dialect specified in SetCommandText. However, if DBGUID_DEFAULT is specified in SetCommandText, the OLE DB provider should return the particular dialect that it will use to interpret the statement. If the OLE DB provider chooses between multiple dialects at execution time for a command that has been set with DBGUID_DEFAULT, or if the OLE DB provider is returning some OLE DB provider-specific syntax that may be different from the command set in SetCommandText, it returns DBGUID_DEFAULT. It has the following syntax: HRESULT GetCommandText ( GUID * pguidDialect, LPOLESTR * ppwszCommand);
It has the following parameters: pguidDialect [in/out] This parameter gives a pointer to memory containing a GUID that specifies the syntax and general rules for parsing the command text—that is, the dialect that will be used by the OLE DB provider to interpret the statement. This is generally the dialect specified in SetCommandText. However, if DBGUID_DEFAULT is specified in SetCommandText, the OLE DB provider returns the particular dialect that it will use to interpret the statement. If the OLE DB provider determines between multiple dialects at execution time for a command that has been set with DBGUID_ DEFAULT, or if the OLE DB provider is returning some OLE DB providerspecific syntax that may be different from the command set in SetCommandText, it returns DBGUID_DEFAULT. OLE DB providers can define GUIDs for their own dialects. If GetCommandText returns an error, *pguidDialect is set to DB_ NULLGUID. ppwszCommand [out] This parameter gives a pointer to memory in which to return the command text. The Command object allocates memory for the command text
Chapter Four—An OLE DB Primer n
125
and returns the address to this memory. The OLE DB consumer releases this memory with IMalloc::Free when it no longer needs the text. If GetCommandText returns an error, *ppwszCommand is set to a null pointer. It has the following return codes: S_OK This return code means the method succeeded. DB_S_DIALECTIGNORED This return code shows that the method succeeded, but the input value of *pguidDialect was ignored. The text is returned in the dialect specified in SetCommandText or the dialect that makes the most sense to the OLE DB provider. The value returned in *pguidDialect represents that dialect. E_FAIL This return code indicates an OLE DB provider-specific error occurred. E_INVALIDARG This return code specifies *ppwszCommand is a null pointer. E_OUTOFMEMORY This return code states that the OLE DB provider was unable to allocate sufficient memory in which to return the command text. DB_E_NOCOMMAND This return code means no command text was currently set on the Command object.
ICommandText::SetCommandText This method sets the command text, replacing the existing command text. A Command object contains a single text command, usually a SQL statement. The new command text is copied into the Command object; thus, the OLE DB consumer can delete the original text without affecting the Command object. All meaningful error checking, such as syntax checking and parsing, is deferred until ICommandPrepare::Prepare or ICommand::Execute is called. SetCommandText only verifies that the command text can be copied into the Command object’s space. If the text of a prepared or unprepared command is overwritten with new command text, by calling SetCommandText, the Command object is left in an unprepared state. SetCommandText does not alter the value of any properties. That is, ICommandProperties::GetProperties returns the same value for a property regardless of whether it is called before or after SetCommandText and whether SetCommandText succeeded or failed. Furthermore, setting the command text does not reset parameter information set through SetParameterInfo.
126
n Chapter Four—An OLE DB Primer It has the following syntax: HRESULT SetCommandText ( REFGUID rguidDialect, LPCOLESTR pwszCommand);
Parameters rguidDialect [in] This parameter shows a GUID that specifies the syntax and general rules for the OLE DB provider to use in parsing the command text. pwszCommand [in] This parameter gives a pointer to the text of the command. If *pwszCommand is an empty string (“”) or pwszCommand is a null pointer, the current command text is cleared and the command is put in an unprepared state. Any properties that have been set on the command are unaffected; that is, they retain their current values. Methods that require a command, such as ICommand::Execute, ICommandPrepare:: Prepare, or IColumnsInfo::GetColumnsInfo, will return DB_E_NOCOMMAND until a new command text is set. It has the following return codes: S_OK This return code means the method succeeded. E_FAIL This return code shows that an OLE DB provider-specific error occurred. DB_E_DIALECTNOTSUPPORTED This return code indicates that the provider did not support the dialect specified in rguidDialect. DB_E_OBJECTOPEN This return code specifies that a rowset was open on the command.
IColumnsRowset This interface supplies complete information about columns in a rowset. The methods in it can be called from a rowset or a command. IColumnsRowset is optional on both commands and rowsets. Advanced OLE DB providers implement this interface. OLE DB consumers use the methods in IColumnsRowset for detailed and flexible information about the columns of a rowset. A service component can be used to synthesize IColumnsRowset from the simpler IColumnsInfo (with defaults and Null values to round out the information).
Chapter Four—An OLE DB Primer n
127
Note
For commands that expose ICommandPrepare, the methods on this interface can be called only after the command is prepared or the rowset is instantiated. If a command text is set but not prepared, any calls to methods on IColumnsInfo return DB_E_NOTPREPARED. For commands that do not expose ICommandPrepare, the methods on this interface can be called only after the command text has been set.
Method GetAvailableColumns
GetColumnsRowset
Description This method returns a list of optional metadata columns that can be supplied in a column’s rowset. This method returns a rowset containing metadata about each column in the current rowset. The rowset is known as the column metadata rowset and is read-only.
IColumnsRowset::GetAvailableColumns This method returns a list of optional metadata columns that can be supplied in a column metadata rowset. The method makes no logical change to the state of the object. Calling GetAvailableColumns on a command before the command is executed may be an expensive operation. It has the following syntax: HRESULT GetAvailableColumns ( ULONG * pcOptColumns, DBID ** prgOptColumns);
It has the following parameters: pcOptColumns [out] This parameter shows a pointer to memory in which to return the count of the elements in *prgOptColumns. If an error occurs, *pcOptColumns is set to zero. prgOptColumns [out] This parameter gives a pointer to memory in which to return an array of the optional columns this OLE DB provider can supply. In addition to the optional columns listed in GetColumnsRowset, the OLE DB provider can return OLE DB provider-specific columns. The rowset or command allocates memory for the structures and returns the address to this memory; the OLE DB consumer releases this memory with IMalloc::Free when it no longer needs the list of columns. If *pcOptColumns is zero on output or an
128
n Chapter Four—An OLE DB Primer error occurs, the OLE DB provider does not allocate any memory and ensures that *prgOptColumns is a null pointer on output. It has the following return codes: S_OK This return code means the method succeeded. E_FAIL This return code shows that an OLE DB provider-specific error occurred. E_INVALIDARG This return code states that *pcOptColumns or *prgOptColumns was a null pointer. E_OUTOFMEMORY This return code indicates that the OLE DB provider was unable to allocate sufficient memory in which to return the column IDs. E_UNEXPECTED This return code shows that ITransaction::Commit or ITransaction::Abort was called and the object is in a zombie state. This error can be returned only when the method is called on a rowset. DB_E_NOCOMMAND This return code provides that no command text was set. This error can be returned only when this method is called from the Command object. DB_E_NOTPREPARED This return code means that the command exposed ICommandPrepare and the command text was set, but the command was not prepared. This error can be returned only when this method is called from the Command object. DB_E_NOTREENTRANT This return code indicates that the OLE DB provider called a method from IRowsetNotify in the OLE DB consumer and the method has not yet returned. DB_SEC_E_PERMISSIONDENIED This return code states that the OLE DB consumer did not have sufficient permission to retrieve the available optional metadata columns.
IColumnsRowset::GetColumnsRowset This method returns a rowset containing metadata about each column in the current rowset. This rowset is known as the column metadata rowset and is read-only. The method makes no logical change to the state of the object. The GetColumnsRowset method creates a rowset containing metadata about a rowset. Unlike the IColumnsInfo::GetColumnInfo method, it provides all of the metadata, but it is more complex to implement and use.
Chapter Four—An OLE DB Primer n
129
The rows in the column metadata rowset describe the columns in the underlying rowset. The column metadata rowset contains one row for each column in the rowset. This includes the columns of the base table and any pseudocolumns generated by the OLE DB provider or data source, such as bookmarks and row IDs. The order of the rows is the order in which the columns appear in the rowset (column ordinal order). This is the same order as they appear in IColumnsInfo. The order is usually predictable from the ordering of requested columns in the command text; if the command text does not specify an order, such as SELECT * FROM MyTable, then the order is determined by the OLE DB provider, such as when the command is prepared. Each column in the column metadata rowset describes a single attribute, such as the name or data type, of a column in the original rowset. The order of the required columns is the same as the order in which they are listed below. The order of the optional columns is arbitrary, although they must be after the required columns. That is, the optional columns in the column metadata rowset can occur in any order after the required columns. The column metadata rowset always includes the required columns. It contains only those optional columns that are requested. GetColumnsRowset can be called for rowsets created by GetColumnsRowset. Calling GetColumnsRowset on a command before the command is executed may be an expensive operation. It has the following syntax: HRESULT GetColumnsRowset ( IUnknown * pUnkOuter, ULONG cOptColumns, const DBID rgOptColumns[], REFIID riid, ULONG cPropertySets, DBPROPSET rgPropertySets[], IUnknown ** ppColRowset);
It has the following parameters: pUnkOuter [in] This parameter provides a pointer to the controlling IUnknown interface if the column metadata rowset is being created as part of an aggregate. It is a null pointer if the rowset is not part of an aggregate. cOptColumns [in] This parameter gives the number of elements in rgOptColumns. If cOptColumns is zero, then rgOptColumns is ignored, and the OLE DB provider returns all available columns in the columns rowset.
130
n Chapter Four—An OLE DB Primer rgOptColumns [in] This parameter shows an array that specifies the optional columns to return. In addition to the optional columns listed later in this section, the OLE DB consumer can request OLE DB provider-specific columns. riid [in] This parameter indicates the IID of the requested rowset interface. This interface is conceptually added to the list of required interfaces on the resulting rowset, and the method fails (E_NOINTERFACE) if that interface cannot be supported on the resulting rowset. cPropertySets [in] This parameter specifies the number of DBPROPSET structures in rgPropertySets. If this is zero, the OLE DB provider ignores rgPropertySets. rgPropertySets [in/out] This parameter provides an array of DBPROPSET structures containing properties and values to be set. The properties specified in these structures must belong to the Rowset property group. If the same property is specified more than once in rgPropertySets, then it is OLE DB providerspecific which value is used. If cPropertySets is zero, this argument is ignored. ppColRowset [out] This parameter gives a pointer to memory in which to return the requested interface pointer on the column metadata rowset. If an error occurs, the returned pointer is null. If the GetColumnsRowset method is called on a command that does not return rows, then the column metadata rowset will be empty. It has the following return codes: S_OK This return code means that the method succeeded. In all DBPROP structures passed to the method, dwStatus is set to DBPROPSTATUS_OK. DB_S_ASYNCHRONOUS This return code shows that the method has initiated asynchronous creation of the rowset. The OLE DB consumer can call IDBAsynchStatus to poll for status or IConnectionPointContainer to obtain the IID_IDB Asynch-Notify Connection Point. Attempting to call any other interfaces may fail and the full set of interfaces may not be available on the object until asynchronous initialization of the rowset has completed. DB_S_ERRORSOCCURRED This return code indicates that the rowset was opened but one or more properties—for which the dwOptions element of the DBPROP structure was DBPROPOPTIONS_OPTIONAL—were not set. The OLE DB consumer checks dwStatus in the DBPROP structures to determine which properties were not set. The method can fail to set properties for a number of reasons, including:
Chapter Four—An OLE DB Primer n
131
n
The property was not supported by the OLE DB provider.
n
The property was not in the Rowset property group.
n
The property set was not supported by the OLE DB provider.
n
It was not possible to set the property.
n
colid in the DBPROP structure was invalid.
n
The data type in vValue in the DBPROP structure was not the data type of the property or was not VT_EMPTY.
n
The value in vValue in the DBPROP structure was invalid.
n
The property’s value conflicted with an existing property.
n
A property was specified to be applied to all columns, but could not be applied to one or more columns.
E_FAIL This return code indicates that an OLE DB provider-specific error occurred. E_INVALIDARG This return code means *ppColRowset was a null pointer; *cPropertySets was greater than zero and rgPropertySets was a null pointer; or cOptColumns was greater than zero and rgOptColumns was a null pointer. It can also mean in an element of rgPropertySets, cProperties was not zero and rgProperties was a null pointer. E_NOINTERFACE This return code states that the column metadata rowset did not support the interface specified in riid. E_UNEXPECTED This return code shows that ITransaction::Commit or ITransaction::Abort was called and the object is in a zombie state. This error can be returned only when the method is called on a rowset. DB_E_ABORTLIMITREACHED This return code specifies the method failed because a resource limit has been reached. For example, a query used to implement the method timed out. No rowset is returned. DB_E_BADCOLUMNID This return code indicates an element of rgOptColumns was an invalid DBID. DB_E_ERRORSOCCURRED This return code states that no rowset was returned because one or more properties—for which the dwOptions element of the DBPROP structure was DBPROPOPTIONS_REQUIRED or an invalid value—were not set. The OLE DB consumer checks dwStatus in the DBPROP structures to determine which properties were not set. None of the satisfiable properties are remembered. The method can fail to set properties for any of the
132
n Chapter Four—An OLE DB Primer reasons specified in DB_S_ERRORSOCCURRED, except the reason that states that it was not possible to set the property. DB_E_NOAGGREGATION This return code shows that *pUnkOuter was not a null pointer and the column’s rowset does not support aggregation, or that *pUnkOuter was non-null and riid was not IID_Unknown. DB_E_NOCOMMAND This return code indicates that no command text was set. This error can be returned only when this method is called from the Command object. DB_E_NOTPREPARED This return code states that the command exposed ICommandPrepare and the command text was set, but the command was not prepared. This error can be returned only when this method is called from the Command object. DB_E_NOTREENTRANT This return code shows that the OLE DB provider called a method from IRowsetNotify in the OLE DB consumer and the method has not yet returned. DB_SEC_E_PERMISSIONDENIED This return code means that the OLE DB consumer did not have sufficient permission to create the column metadata rowset.
Required Metadata Columns The column metadata rowset always contains the following columns; these columns return the same information as GetColumnInfo. Column ID DBCOLUMN_IDNAME
DBCOLUMN_GUID DBCOLUMN_PROPID
Type Indicator DBTYPE_WSTR
DBTYPE_GUID DBTYPE_UI4
Description This column contains the column name. This column, together with the DBCOLUMN_GUID and DBCOLUMN_ PROPID columns, form the ID of the column. One or more of these columns will be NULL depending on which elements of the DBID structure the OLE DB provider uses. The column ID of a base table should be invariant under views. This column contains the column GUID. This column contains the column property ID.DBCOLUMN_NAMEDBTYPE_WSTR This column contains the name of the column; it might not be unique. If this cannot be determined, a NULL is returned.
Chapter Four—An OLE DB Primer n Column ID DBCOLUMN_PROPID (cont.)
Type Indicator
DBCOLUMN_NUMBER
DBTYPE_UI4
DBCOLUMN_TYPE
DBTYPE_UI2
DBCOLUMN_ TYPEINFO
DBTYPE_ IUNKNOWN
DBCOLUMN_ COLUMNSIZE
DBTYPE_UI4
133
Description The name can be different from the value returned in DBCOLUMN_IDNAME if the column has been renamed by the command text. This name always reflects the most recent renaming of the column in the current view or command text. If the GetColumnsRowset method is called for a column metadata rowset (the rowset returned by GetColumnsRowset), the name of each column is the name of the column ID constant. For example, the name of the DBCOLUMN_SCALE column is “DBCOLUMN_SCALE”. This column contains the ordinal of the column. This is zero for the bookmark column of the row, if any. Other columns are numbered starting with one. This column cannot contain a NULL value. This column contains the indicator of the column’s data type. If the data type of the column varies from row to row, this must be DBTYPE_VARIANT. The column cannot contain a NULL value. This column is reserved for future use. OLE DB providers should return a null pointer in pTypeInfo. This column contains the maximum possible length of a value in the column. For columns that use a fixed-length data type, this is the size of the data type. For columns that use a variable-length data type, this is one of the following: n The maximum length of the column in
characters, for DBTYPE_STR and DBTYPE_WSTR, or bytes, for DBTYPE_BYTES, if one is defined. For example, a CHAR(5) column in a SQL table has a maximum length of 5. n The maximum length of the data type
in characters, for DBTYPE_STR and DBTYPE_WSTR, or bytes, for DBTYPE_BYTES, if the column does not have a defined length. n ~0 (bitwise, the value is not 0; that is, all bits are set to 1) if neither the column nor the data type has a defined maximum length.
134
n Chapter Four—An OLE DB Primer Column ID DBCOLUMN_ COLUMNSIZE (cont.)
Type Indicator
DBCOLUMN_ PRECISION
DBTYPE_UI2
DBCOLUMN_SCALE
DBTYPE_I2
DBCOLUMN_FLAGS
DBTYPE_UI4
Description n For data types that do not have a length, this is set to ~0 (bitwise, the value is not 0; that is, all bits are set to 1). This column shows that if DBCOLUMN_ TYPE is a numeric data type, this is the maximum precision of the column. The precision of columns with a data type of DBTYPE_DECIMAL or DBTYPE_ NUMERIC depends on the definition of the column. If DBCOLUMN_TYPE is not a numeric data type, this is NULL. This column shows that if DBCOLUMN_ TYPE is DBTYPE_DECIMAL or DBTYPE_ NUMERIC, this is the number of digits to the right of the decimal point. Otherwise, this is NULL. This column contains a bitmask that describes column characteristics. The DBCOLUMNFLAGS enumerated type specifies the bits in the bitmask.. This column cannot contain a NULL value.
Optional Metadata Columns The following columns are optional; if the column metadata rowset does not contain one of them, the OLE DB consumer can safely use the default value. The default value is the value the OLE DB consumer should assume if the OLE DB provider does not support that information. It is also the value the column metadata rowset returns when the OLE DB provider does have support, but does not specify that information for a particular column. The OLE DB provider can also have optional, provider-specific columns. Column ID DBCOLUMN_ BASECATALOGNAME
Type Indicator DBTYPE_WSTR
DBCOLUMN_ BASECOLUMNNAME
DBTYPE_WSTR
Description This column contains the name of the catalog in the data source that contains the column. It is NULL if the base catalog name cannot be determined. The default is NULL. This column contains the name of the column in the data source. This might be different from the column name returned in the DBCOLUMN_NAME column if an alias was used. It is NULL if the base column name cannot be determined. The default is NULL.
Chapter Four—An OLE DB Primer n Column ID DBCOLUMN_ BASESCHEMANAME
Type Indicator DBTYPE_WSTR
DBCOLUMN_ BASETABLENAME
DBTYPE_WSTR
DBCOLUMN_CLSID
DBTYPE_GUID
DBCOLUMN_ COLLATINGSEQUENCE
DBTYPE_I4
DBCOLUMN_ COMPUTEMODE
DBTYPE_I4
135
Description This column contains the name of the schema in the data source that contains the column. It is NULL if the base schema name cannot be determined. The default of this column is NULL. This column contains the name of the table in the data source that contains the column. It is NULL if the base table name cannot be determined. The default of this column is NULL. This column shows that if all objects in the column have the same class ID, this is that class ID. If the column may contain objects with different class IDs, or if the column is not of DBTYPE_IUNKNOWN, this is set to NULL. The default is NULL. This column contains the locale ID (LCID) that defines the collating sequence for the column. The default of this column is the code page installed on the local machine. This column shows whether a column is computed. It can be one of the following: n DBCOMPUTEMODE_COMPUTED:
The column is computed, such as Salary/12. n DBCOMPUTEMODE_DYNAMIC:
The column is computed and IRowset::GetData returns the value of the column based on the current values of its component columns, which might have been changed with IRowsetChange::SetData or IRowsetChange::InsertRow. n DBCOMPUTEDMODE_NOTCOMP
DBCOLUMN_ DATETIMEPRECISION
DBTYPE_UI4
UTED: The column is not computed. This is the default. This column contains the datetime precision—number of digits in the fractional seconds portion—if the column is a datetime or interval type. The default of this column is derived from the value in column DATETIME_ PRECISION in the COLUMNS schema rowset.
136
n Chapter Four—An OLE DB Primer Column ID DBCOLUMN_ DEFAULTVALUE
Type Indicator DBTYPE_ VARIANT
DBCOLUMN_ DOMAINCATALOG
DBTYPE_WSTR
DBCOLUMN_ DOMAINSCHEMA
DBTYPE_WSTR
DBCOLUMN_ DOMAINNAME
DBTYPE_WSTR
DBCOLUMN_ HASDEFAULT
DBTYPE_BOOL
DBCOLUMN_ ISAUTOINCREMENT
DBTYPE_BOOL
DBCOLUMN_ ISCASESENSITIVE
DBTYPE_BOOL
Description This column contains the column default value if declared statically. Dynamic initialization is handled by notifications. It is NULL if the default value cannot be determined. The default is NULL. This column contains the name of the catalog containing the column’s domain. It is NULL if the domain catalog name cannot be determined. The default is NULL. This column shows the name of the schema containing the column’s domain. It is NULL if the domain schema name cannot be determined. The default is NULL. This column contains the name of the domain of which the column is a member. It is NULL if the domain name cannot be determined. The default is NULL. If this column contains VARIANT_TRUE, then the column has a default value. If VARIANT_FALSE, the column does not have a default value. If NULL, the OLE DB provider could not determine if the column has a default value or if a default value does not make sense for the column. For example, it is a computed, derived, or nonupdatable column.The default is VARIANT_FALSE. If this column contains VARIANT_TRUE, the column assigns values to new rows in fixed increments. If VARIANT_FALSE, the column does not assign values to new rows in fixed increments. The default is VARIANT_FALSE. This column shows VARIANT_TRUE if the order of the column is case sensitive and if searches on the column are case sensitive. Otherwise, it is VARIANT_ FALSE. The default is VARIANT_TRUE.
Chapter Four—An OLE DB Primer n Column ID DBCOLUMN_ ISSEARCHABLE
Type Indicator DBTYPE_UI4
DBCOLUMN_ ISUNIQUE
DBTYPE_BOOL
DBCOLUMN_ MAYSORT
DBTYPE_BOOL
DBCOLUMN_ OCTETLENGTH
DBTYPE_UI4
DBCOLUMN_ KEYCOLUMN
DBTYPE_BOOL
DBCOLUMN_ BASETABLEVERSION
DBTYPE_UI8
137
Description This column contains an integer indicating the searchability of a column. The default of this column is derived from the value of the SEARCHABLE column in the PROVIDER_TYPES schema rowset. If this column contains VARIANT_TRUE, then no two rows in the base table—the table returned in DBCOLUMN_BASETABLENAME—can have the same value in this column. DBCOLUMN_ISUNIQUE is guaranteed to be VARIANT_TRUE if the column constitutes a key by itself, or if there is a constraint of type UNIQUE that applies only to this column. If VARIANT_FALSE, the column can contain duplicate values in the base table. The default is VARIANT_FALSE. If this column contains VARIANT_TRUE, then the column can be sorted. If VARIANT_FALSE, the column cannot be sorted. This column contains the maximum length in octets (bytes) of the column, if the column is a character or binary type. A value of zero means the column has no maximum length. It is NULL for all other types of columns. The default is derived from the value of the CHARACTER_ OCTET_LENGTH column in the COLUMNS schema rowset. If this column contains VARIANT_TRUE, the column is one of a set of columns required in order to uniquely identify the row. If VARIANT_FALSE, the column is not required to uniquely identify the row. If the DBPROP_UNIQUEROW property is set to VARIANT_TRUE, then the set of columns with DBCOLUMN_ KEYCOLUMN set to VARIANT_TRUE uniquely identifies a row in the rowset. This column contains the version number of the table in the data source that contains the column. This number is assumed to change every time the table definition is modified. The way in which this number is generated is provider specific.
138
n Chapter Four—An OLE DB Primer
ICommandPrepare This optional interface encapsulates command optimization, a separation of compile time and run time, as found in traditional relational database systems. The result of this optimization is a command execution plan. If the provider supports command preparation by supporting this interface, commands must be in a prepared state prior to calling these methods: IColumnsInfo::GetColumnInfo IColumnsInfo::MapColumnIDs IColumnsRowset::GetAvailableColumns IColumnsRowset::GetColumnsRowset The interface has these methods: Method Prepare Unprepare
Description This method validates and optimizes the current command. This method discards the current command execution plan.
ICommandPrepare::Prepare This method validates and optimizes the current command. Although they are not required to do so, OLE DB consumers should set any properties before calling Prepare, because these properties might be relevant to preparing the command. If Prepare is called redundantly, the OLE DB provider determines whether command optimization is reinvoked; the OLE DB provider returns S_OK. If Prepare returns DB_S_ERRORSOCCURRED or DB_E_ERRORSOCCURRED, the OLE DB consumer can immediately call the ICommandProperties:: GetProperties method with the DBPPROPSET_PROPERTIESINERROR property set to return the properties that could not be set. It has the following syntax: HRESULT Prepare ( ULONG cExpectedRuns);
It has the following parameter: cExpectedRuns [in] In using this parameter, the OLE DB consumer can indicate how often the command execution plan, which is produced by Prepare, will be used; that is, how often the command is likely to be executed without renewed optimization. This guides the optimizer in determining tradeoffs between search effort and run-time processing effort. A value of zero indicates that
Chapter Four—An OLE DB Primer n
139
the OLE DB consumer is unable to provide an estimate, and leaves it to the optimizer to choose a default value. It has the following return codes: S_OK This return code means the method succeeded. DB_S_ERRORSOCCURRED This return code shows that the command was prepared but one or more properties—for which the dwOptions element of the DBPROP structure was DBPROPOPTIONS_OPTIONAL—were not set. The OLE DB consumer calls ICommandProperties::GetProperties to determine which properties were set. E_FAIL This return code indicates that an OLE DB provider-specific error occurred. E_OUTOFMEMORY This return code specifies that the provider ran out of memory while preparing the command. DB_E_ABORTLIMITREACHED This return code shows preparation has been aborted because a resource limit has been reached. For example, the preparation timed out. DB_E_ERRORSINCOMMAND This return code states that the command text contained one or more errors. OLE DB providers should use OLE DB error objects to return details about the errors. DB_E_ERRORSOCCURRED This return code indicates that the command was not prepared because one or more properties—for which the dwOptions element of the DBPROP structure was DBPROPOPTIONS_REQUIRED—were not set. The OLE DB consumer calls the ICommandProperties::GetProperties method to determine which properties were not set. DB_E_NOCOMMAND This return code shows that no command text was currently set on the Command object. DB_E_OBJECTOPEN This return code means that a rowset was open on the command. DB_SEC_E_PERMISSIONDENIED This return code states that the OLE DB consumer did not have sufficient permission to prepare the command.
140
n Chapter Four—An OLE DB Primer
ICommandPrepare::Unprepare This method discards the current command execution plan. The method has no effect if the command was not prepared; the OLE DB provider returns S_OK. It has the following syntax: HRESULT Unprepare();
There are no parameters. It has the following return codes: S_OK This return code means the method succeeded. E_FAIL This return code shows that an OLE DB provider-specific error occurred. DB_E_OBJECTOPEN This return code indicates a rowset was open on the command.
ICommandWithParameters OLE DB providers that support parameters must support ICommandWithParameters. Any provider that returns DBPROPVAL_SQL_ANSI92_ INTERMEDIATE or DBPROPVAL_SQL_ANSI92_FULL for the DBPROP_ SQLSUPPORT property can support parameters. This optional interface encapsulates parameters. Parameters are scalar values, or a vector of scalar values, typically expressed in predicates but possibly supported by many OLE DB providers in any scalar expression. For scalar parameters of prepared commands, there is a presumption that different parameter values do not require different plans. In other words, a single preparation and its resulting plan are satisfactory for all possible values of scalar parameters. Parameter values are set when a command is executed. Methods are included here to offer a means for setting and obtaining a list of parameters and their types. Method GetParameterInfo MapParameterNames SetParameterInfo
Description This method gets a list of the command’s parameters, their names, and their types. This method returns an array of parameter ordinals when given named parameters. This method specifies the native data type of each parameter.
Chapter Four—An OLE DB Primer n
141
ICommandWithParameters::GetParameterInfo This method gets a list of the command’s parameters, their names, and their types. The method makes no logical change to the state of the object. If SetParameterInfo has not been called for any parameters or SetParameterInfo has been called with cParams equal to zero, GetParameterInfo returns information about the parameters only if the OLE DB provider can derive parameter information. If the OLE DB provider cannot derive parameter information, GetParameterInfo returns DB_E_PARAMUNAVAILABLE. If SetParameterInfo has been called for at least one parameter, GetParameterInfo returns the parameter information only for those parameters for which SetParameterInfo has been called. It does this even if the OLE DB provider can derive information about the parameters for which SetParameterInfo was not called. The OLE DB provider does not return a warning in this case because it often cannot determine the number of parameters and therefore cannot determine whether it has returned information for all parameters. It has the following syntax: HRESULT GetParameterInfo ( ULONG * pcParams, DBPARAMINFO ** prgParamInfo, OLECHAR ** ppNamesBuffer);
It has the following parameters: pcParams [out] This parameter gives a pointer to memory in which to return the number of parameters in the command. If an error occurs, *pcParams is set to zero. prgParamInfo [out] This parameter provides a pointer to memory in which to return an array of parameter information structures. The command allocates memory for the array, as well as the strings, and returns the address to this memory. The OLE DB consumer releases the array memory with IMalloc::Free when it no longer needs the parameter information. If *pcParams is zero on output or an error occurs, the OLE DB provider does not allocate any memory and ensures that *prgParamInfo is a null pointer on output. Parameters are returned in ascending order according to the iOrdinal element of the DBPARAMINFO structure. Here is the DBPARAMINFO structure: typedef struct tagDBPARAMINFO { DBPARAMFLAGS dwFlags; ULONG iOrdinal; LPOLESTR pwszName;
142
n Chapter Four—An OLE DB Primer ITypeInfo * ULONG DBTYPE BYTE BYTE } DBPARAMINFO;
pTypeInfo; ulParamSize; wType; bPrecision; bScale;
The elements of this structure are used as follows: Element dwFlags
Description This element provides a bitmask describing parameter characteristics; these values have the following meaning: n DBPARAMFLAGS_ISINPUT—This flag indicates whether a
parameter accepts values on input. It is not set if this is unknown. n DBPARAMFLAGS_ISOUTPUT—This flag shows whether a
parameter returns values on output. Not set if this is unknown. OLE DB providers support only those parameter types that make sense for their data source. n DBPARAMFLAGS_ISSIGNED—This flag indicates whether a
parameter is signed. This is ignored if the type is inherently signed, such as DBTYPE_I2, or if the sign does not apply to the type, such as DBTYPE_BSTR. It is generally used in the SetParameterInfo method so the OLE DB consumer can tell the OLE DB provider if a provider-specific type name refers to a signed or unsigned type. n DBPARAMFLAGS_ISNULLABLE—This flag specifies whether a
parameter accepts NULLs. If nullability is unknown, the flag is set. n DBPARAMFLAGS_ISLONG—This flag shows whether a parameter
iOrdinal
pwszName
contains a BLOB that contains very long data. The definition of very long data is OLE DB provider-specific. The flag setting corresponds to the value of the IS_LONG column in the PROVIDER_TYPES schema rowset for the data type. When this flag is set, the BLOB is best manipulated through one of the storage interfaces. Although such BLOBs can be sent in a single piece with the ICommand::Execute method, there may be OLE DB provider-specific problems in doing so. For example, the BLOB might be truncated due to machine limits on memory. Furthermore, when this flag is set, the OLE DB provider might not be able to accurately return the maximum length of the BLOB data in ulParamSize in the GetParameterInfo method. When this flag is not set, the BLOB can be accessed either through the ICommand::Execute method or through a storage interface. This element provides the ordinal of the parameter. Parameters are numbered from left to right as they appear in the command, with the first parameter in the command having an iOrdinal value of 1. This element gives the name of the parameter; it is a null pointer if there is no name. Names are normal names. The colon prefix (where used within SQL text) is stripped.
Chapter Four—An OLE DB Primer n Element pTypeInfo ulParamSize
143
Description This element, ITypeInfo, describes the type if *pTypeInfo is not a null pointer. This element gives the maximum possible length of a value in the parameter. For parameters that use a fixed-length data type, this is the size of the data type. For parameters that use a variable-length data type, this is one of the following: n The maximum length of the parameters in characters, for DBTYPE_
STR and DBTYPE_WSTR, or bytes, for DBTYPE_BYTES, if one is defined. For example, a parameter for a CHAR(5) column in a SQL table has a maximum length of 5. n The maximum length of the data type in characters, for DBTYPE_
STR and DBTYPE_WSTR, or bytes, for DBTYPE_BYTES, if the parameter does not have a defined length. n ~0 (bitwise, the value is not 0; that is, all bits are set to 1) if neither
the parameter nor the data type has a defined maximum length.
wType bPrecision
bScale
For data types that do not have a length, this is set to ~0 (bitwise, the value is not 0; that is, all bits are set to 1). This element is the indicator of the parameter’s data type. This element states that if wType is a numeric type, bPrecision is the maximum number of digits, expressed in base 10. Otherwise, this is ~0 (bitwise, the value is not 0; that is, all bits are set to 1). This element shows that if wType is a numeric type with a fixed scale, bScale is the number of digits to the right (if bScale is positive) or left (if bScale is negative) of the decimal point. Otherwise, this is ~0 (bitwise, the value is not 0; that is, all bits are set to 1).
ppNamesBuffer [out] This parameter provides a pointer to memory in which to store all string values (names used within the *pwszName element of the DBPARAMINFO structure) with a single, globally allocated buffer. Specifying a null pointer for *ppNamesBuffer suspends the return of parameter names. The command allocates memory for the buffer and returns the address to this memory. The OLE DB consumer releases the memory with IMalloc::Free when it no longer needs the parameter information. If *pcParams is zero on output or an error occurs, the OLE DB provider does not allocate any memory and ensures that *ppNamesBuffer is a null pointer on output. Each of the individual string values stored in this buffer is terminated by a null termination character. Therefore, the buffer may contain one or more strings, each with its own null termination character, and may contain embedded null termination characters. It has the following return codes: S_OK This return code means the method succeeded.
144
n Chapter Four—An OLE DB Primer E_FAIL This return code shows that an OLE DB provider-specific error occurred. E_INVALIDARG This return code indicates that *pcParams or *prgParamInfo was a null pointer. E_OUTOFMEMORY This return code states that the OLE DB provider was unable to allocate sufficient memory in which to return the parameter data array or parameter names. DB_E_NOCOMMAND This return code specifies that the OLE DB provider can derive parameter information, but it does not support command preparation. However, no command text was currently set on the Command object and no parameter information had been specified with SetParameterInfo. DB_E_NOTPREPARED This return code means the OLE DB provider can derive parameter information, and it supports command preparation. However, the command was in an unprepared state and no parameter information was specified with SetParameterInfo. DB_E_PARAMUNAVAILABLE This return code indicates that the OLE DB provider cannot derive parameter information from the command and SetParameterInfo has not been called.
ICommandWithParameters::MapParameterNames This method returns an array of parameter ordinals when given named parameters. It has the following syntax: HRESULT MapParameterNames ULONG const OLECHAR * LONG
( cParamNames, rgParamNames[], rgParamOrdinals[]);
It has the following parameters: cParamNames [in] This parameter provides the number of parameter names to map. If cParamNames is zero, MapParameterNames does nothing and returns S_OK. rgParamNames [in] This parameter gives an array of parameter names of which to determine the parameter ordinals. If a parameter name is not found, the corresponding element of rgParamOrdinals is set to zero and the method returns DB_S_ERRORSOCCURRED.
Chapter Four—An OLE DB Primer n
145
rgParamOrdinals [out] This parameter shows an array of cParamNames ordinals of the parameters identified by the elements of *rgParamNames. The OLE DB consumer allocates (but is not required to initialize) memory for this array and passes the address of this memory to the OLE DB provider. The OLE DB provider returns the parameter ordinals in the array. It has the following return codes: S_OK This return code means the method succeeded. Each element of rgParamOrdinals is set to a nonzero value. DB_S_ERRORSOCCURRED This return code shows that an element of *rgParamNames was invalid. The corresponding element of rgParamOrdinals is set to zero. E_FAIL This return code indicates that an OLE DB provider-specific error occurred. E_INVALIDARG This return code states that cParamNames was not zero and *rgParamNames or rgParamOrdinals was a null pointer. DB_E_ERRORSOCCURRED This return code specifies that all elements of *rgParamNames were invalid. All elements of rgParamOrdinals are set to zero. DB_E_NOCOMMAND This return code means that no command text was currently set on the Command object and no parameter information had been specified with SetParameterInfo. DB_E_NOTPREPARED This return code indicates the OLE DB provider can derive parameter information and supports command preparation. However, the command was in an unprepared state and no parameter information had been specified with SetParameterInfo.
ICommandWithParameters::SetParameterInfo This method specifies the native data type of each parameter. OLE DB providers generally derive parameter type information from the data source and return it to the OLE DB consumer through the GetParameterInfo method. OLE DB consumers then use this information to build parameter accessors for use with ICommand::Execute. Some OLE DB providers—notably many SQL database OLE DB providers— cannot derive parameter type information from the data source. For these OLE DB providers, the OLE DB consumer must supply the native parameter
146
n Chapter Four—An OLE DB Primer type information through SetParameterInfo. The OLE DB provider uses the type information specified by SetParameterInfo to determine how to convert parameter data from the type supplied by the OLE DB consumer (as indicated by the wType value in the binding structure) to the native type used by the data source. When the OLE DB consumer specifies a data type with known precision, scale, and size values, any information supplied by the OLE DB consumer for precision, scale, or size should be ignored by the OLE DB provider. The information that the OLE DB consumer supplies must be correct and must be supplied for all parameters. OLE DB providers that cannot derive parameter type information cannot verify the supplied information against the parameter metadata, although they can determine that the specified values are legal values for the OLE DB provider. Such OLE DB providers sometimes cannot even determine the number of parameters in the command. The result of executing a command using incorrect parameter information or passing parameter information for the wrong number of parameters is undefined. For example, if the parameter type is LONG and the OLE DB consumer specifies a type indicator of DBTYPE_STR in SetParameterInfo, the OLE DB provider converts the data to a string before sending it to the data source. Because the data source is expecting a LONG, this will likely result in an error. When the OLE DB consumer calls SetParameterInfo, it specifies an OLE DB provider-specific data type name (as derived from the PROVIDER_TYPES schema rowset) or a standard type name. If the OLE DB consumer passes a standard type name, the OLE DB provider maps it to an OLE DB providerspecific type name. For example, an OLE DB provider for a SQL DBMS might map “DBTYPE_I2” to “SMALLINT”. The following is a list of standard type names and their associated type indicators. This list contains many commonly known types. Individual OLE DB providers may allow other OLE DB provider-specific names as well. Standard Type Name "DBTYPE_I1" "DBTYPE_I2" "DBTYPE_I4" "DBTYPE_I8" "DBTYPE_UI1" "DBTYPE_UI2" "DBTYPE_UI4" "DBTYPE_UI8" "DBTYPE_R4"
Type Indicator DBTYPE_I1 DBTYPE_I2 DBTYPE_I4 DBTYPE_I8 DBTYPE_UI1 DBTYPE_UI2 DBTYPE_UI4 DBTYPE_UI8 DBTYPE_R4
Chapter Four—An OLE DB Primer n Standard Type Name "DBTYPE_R8" "DBTYPE_CY" "DBTYPE_DECIMAL" "DBTYPE_NUMERIC" "DBTYPE_BOOL" "DBTYPE_ERROR" "DBTYPE_UDT" "DBTYPE_VARIANT" "DBTYPE_IDISPATCH" "DBTYPE_IUNKNOWN" "DBTYPE_GUID" "DBTYPE_DATE" "DBTYPE_DBDATE" "DBTYPE_TIME" "DBTYPE_TIMESTAMP" "DBTYPE_BSTR" "DBTYPE_CHAR" "DBTYPE_VARCHAR" "DBTYPE_LONGVARCHAR" "DBTYPE_WCHAR" "DBTYPE_WVARCHAR" "DBTYPE_WLONGVARCHAR" "DBTYPE_BINARY" "DBTYPE_VARBINARY" "DBTYPE_LONGVARBINARY"
147
Type Indicator DBTYPE_R8 DBTYPE_CY DBTYPE_DECIMAL DBTYPE_NUMERIC DBTYPE_BOOL DBTYPE_ERROR DBTYPE_UDT DBTYPE_VARIANT DBTYPE_IDISPATCH DBTYPE_IUNKNOWN DBTYPE_GUID DBTYPE_DATE DBTYPE_DBDATE DBTYPE_TIME DBTYPE_TIMESTAMP DBTYPE_BSTR DBTYPE_STR DBTYPE_STR DBTYPE_STR DBTYPE_WSTR DBTYPE_WSTR DBTYPE_WSTR DBTYPE_BYTES DBTYPE_BYTES DBTYPE_BYTES
After the OLE DB consumer calls the SetParameterInfo method to specify the parameter type information, it can call the GetParameterInfo method to retrieve the type indicator for each parameter. These values—which are based on the information specified in SetParameterInfo—represent the best fit of OLE DB types to the native parameter types. The OLE DB provider guarantees that, if the OLE DB consumer uses these types in a parameter accessor, it will be able to convert the data from the OLE DB type to the native parameter type. If the OLE DB provider can derive parameter type information and the OLE DB consumer calls the SetParameterInfo method, SetParameterInfo uses the specified type information and returns DB_S_TYPEINFOOVERRIDDEN. Because deriving type information can be an expensive operation, this may result in more efficient code.
148
n Chapter Four—An OLE DB Primer It has the following syntax: HRESULT SetParameterInfo ( ULONG cParams, const ULONG rgParamOrdinals[], const DBPARAMBINDINFO rgParamBindInfo[]);
It has the following parameters: cParams [in] This parameter gives the number of parameters for which to set type information. If cParams is zero, the type information for all parameters is discarded, and rgParamOrdinals and rgParamBindInfo are ignored. rgParamOrdinals [in] This parameter provides an array of cParams ordinals. These are the ordinals of the parameters for which to set type information. Type information for parameters whose ordinals are not specified is not affected. rgParamBindInfo [in] This parameter indicates an array of cParams DBPARAMBINDINFO structures. If rgParamBindInfo is a null pointer, then the type information for the parameters specified by the ordinals in rgParamOrdinals is discarded. The DBPARAMBINDINFO structure is: typedef struct tagDBPARAMBINDINFO { LPOLESTR pwszDataSourceType; LPOLESTR pwszName; ULONG ulParamSize; DBPARAMFLAGS dwFlags; BYTE bPrecision; BYTE bScale; } DBPARAMBINDINFO;
The elements of this structure are used as follows: Element pwszDataSourceType
Description This element gives a pointer to the OLE DB providerspecific name of the parameter’s data type or a standard data type name. This name is not returned by the GetParameterInfo method; instead, the OLE DB provider maps the data type specified by this name to an OLE DB type indicator and returns that type indicator.
Chapter Four—An OLE DB Primer n Element pwszName
ulParamSize
149
Description This element shows the name of the parameter. This is a null pointer if the parameter does not have a name. The OLE DB consumer must specify a name for all or none of the parameters set at any time. If the OLE DB provider does not support named parameters, this argument is ignored and the OLE DB provider is not required to verify that all or none of the parameters are named. This element indicates the maximum possible length of a value in the parameter. For parameters that use a fixed-length data type, this is the size of the data type. For parameters that use a variable-length data type, this is one of the following: n The maximum length of the parameters in characters,
for DBTYPE_STR and DBTYPE_WSTR, or bytes, for DBTYPE_BYTES, if one is defined. For example, a parameter for a CHAR(5) column in a SQL table has a maximum length of 5. n The maximum length of the data type in characters, for
DBTYPE_STR and DBTYPE_WSTR, or bytes, for DBTYPE_BYTES, if the parameter does not have a defined length. n ~0 (bitwise, the value is not 0; that is, all bits are set to 1) if neither the parameter nor the data type has a defined maximum length. For data types that do not have a length, this is set to ~0 (bitwise, the value is not 0; that is, all bits are set to 1). dwFlags bPrecision
bScale
This argument is ignored if pwszDataSourceType is null. For information on this element, please see the dwFlags element of the DBPARAMINFO structure. This element states that if pwszDataSourceType is a numeric type, bPrecision is the maximum number of digits, expressed in base 10. Otherwise, it is ignored. This argument is ignored if pwszDataSourceType is null. This element provides that if pwszDataSourceType is a numeric type with a fixed scale, bScale is the number of digits to the right (if bScale is positive) or left (if bScale is negative) of the decimal point. Otherwise, it is ignored. This argument is ignored if pwszDataSourceType is null.
It has the following return codes: S_OK This return code means that the method succeeded.
150
n Chapter Four—An OLE DB Primer DB_S_TYPEINFOOVERRIDDEN This return code shows that the OLE DB provider was capable of deriving the parameter type information and the SetParameterInfo method was called. The parameter type information specified in SetParameterInfo was used. SetParameterInfo replaced parameter type information specified in a previous call to SetParameterInfo. E_FAIL This return code states that an OLE DB provider-specific error occurred. E_INVALIDARG This return code specifies that cParams was not zero and rgParamOrdinals was a null pointer, or an element of rgParamOrdinals was zero. It can also mean that in an element of rgParamBindInfo, the pwszDataSourceType element was a null pointer and the OLE DB provider does not support default parameter conversions, or the dwFlags element was invalid. DB_E_BADPARAMETERNAME This return code indicates that in an element of rgParamBindInfo, the pwszName element specified an invalid parameter name. The OLE DB provider does not check whether the name was correct for the specified parameter, just whether it was a valid parameter name. It can also mean that in one or more elements of rgParamBindInfo, but not all, pwszName was null. Additionally, it can mean that in one or more elements of rgParamBindInfo, pwszName was null and one or more parameters previously set and not overridden were specified with a non-null pwszName. Finally, it can mean that in one or more elements of rgParamBindInfo, pwszName was non-null and one or more parameters previously set and not overridden were specified with a null pwszName. DB_E_BADTYPENAME This return code states that in an element of rgParamBindInfo, the pwszDataSourceType element specified an invalid data type name. The OLE DB provider does not check whether the data type was correct for the specified parameter, just whether it was a data type name supported by the OLE DB provider. DB_E_OBJECTOPEN This return code means that a rowset was open on the command.
IRowset IRowset is the base rowset interface. It provides methods for fetching rows sequentially, getting the data from those rows, and managing rows. IRowset requires IAccessor and IRowsetInfo. IRowset is required for all OLE DB providers that support general OLE DB consumers. OLE DB consumers
Chapter Four—An OLE DB Primer n
151
use the methods in IRowset for all basic rowset operations, including fetching and releasing rows and getting column values. When an OLE DB consumer first gets an interface pointer on a rowset, usually its first step is to determine the rowset’s capabilities using IRowsetInfo:: GetProperties. This returns information about the interfaces exposed by the rowset as well as those capabilities of the rowset that do not show up as distinct interfaces, such as the maximum number of active rows and how many rows can have pending updates at the same time. For OLE DB consumers, the next step is to determine the characteristics, or metadata, of the columns in the rowset. For this they use either IColumnsInfo or IColumnsRowset, for simple or extended column information, respectively. These interfaces are also available on prepared commands prior to execution, allowing advance planning. The OLE DB consumer determines which columns it needs, either from the metadata or on the basis of knowing the text command that generated the rowset. It determines the ordinals of the needed columns from the ordering of the column information returned by IColumnsInfo or from ordinals in the column metadata rowset returned by IColumnsRowset. Some OLE DB consumers do not use a command or do not want to browse the column information; they may know the name or property identifier for the columns they want to use. They call IColumnsInfo::MapColumnIDs to retrieve the column ordinals. The ordinals are used to specify a binding to a column. A binding is a structure that associates an element of the OLE DB consumer’s structure with a column. The binding can bind the column’s data value, length, and status value. A set of bindings is gathered together in an OLE DB accessor, which is created with IAccessor::CreateAccessor. An OLE DB accessor can contain multiple bindings so that the data for multiple columns can be retrieved or set in a single call. The OLE DB consumer can create several OLE DB accessors to match different usage patterns in different parts of the application. It can create and release OLE DB accessors at any time while the rowset remains in existence. To fetch rows from the database, the OLE DB consumer calls a method such as GetNextRows or IRowsetLocate::GetRowsAt. To create and initialize a new row to be inserted into the data source, the OLE DB consumer calls IRowsetChange::InsertRow. The methods that fetch rows do not actually return data to the OLE DB consumer. Instead, they return the handles to these rows, and a local copy of the rows is stored in the rowset. After the rows are returned, the OLE DB consumer can access the data in the rows. The consumer calls GetData and passes it the handle to a row, the handle to an OLE DB accessor, and a pointer to an OLE DB consumer-allocated buffer. GetData converts the data (if it does not match the native OLE DB
152
n Chapter Four—An OLE DB Primer provider storage) and returns the columns as specified in the bindings used to create the OLE DB accessor. The OLE DB consumer can call GetData more than once for a row, using different OLE DB accessors and buffers; thus, the OLE DB consumer can have multiple copies of the same data. For example, if a column contains a text document, the OLE DB consumer might call GetData with an OLE DB accessor that binds the first 50 bytes of the document. When the user double-clicks on the displayed heading text, the OLE DB consumer could then call GetData with a different OLE DB accessor to retrieve the entire document. Data from variable-length columns may be treated several ways. First, such columns can be bound to a finite section of the OLE DB consumer’s structure, which causes truncation when the length of the data exceeds the length of the buffer. The OLE DB consumer can determine that truncation has occurred by checking if the status is DBSTATUS_S_TRUNCATED. The returned length is always the true length in bytes, so the OLE DB consumer also can determine how much data was truncated. Another way to obtain data from such columns is by reference. For example, if a binary column is bound with a type indicator of DBTYPE_BYTES | DBTYPE_BYREF, the OLE DB provider allocates memory for all of the data in the column and returns this memory to the OLE DB consumer. In both cases, it is likely that such large values may be best optimized as deferred columns and accessed only when necessary. Performance varies with different servers, but in general BLOB columns are stored separately from other records and may be more costly to access than ordinary columns, so they would not routinely be pulled in for browsing or scanning. Another way to handle BLOB columns may be implemented on some providers, and that is to request they be delivered as OLE ILockBytes, IStorage, ISequentialStream, or IStream objects. When the OLE DB consumer is finished fetching or updating rows, it releases them with ReleaseRows. This releases resources from the rowset’s copy of the rows and makes room for new rows. The OLE DB consumer can then repeat its cycle of fetching or creating rows and accessing the data in them. When the OLE DB consumer is done with the rowset, it calls IAccessor:: ReleaseAccessor to release any OLE DB accessors. It calls IUnknown::Release on all interfaces exposed by the rowset to release the rowset. When the rowset is released, it forces the release of any remaining rows or OLE DB accessors the OLE DB consumer may hold. Such handle objects are subordinate to the rowset. That is, they do not take reference counts upon the rowset and cannot cause the rowset to linger beyond the point where all the interfaces for the rowset have been released. The rowset must clean up all such subordinate objects.
Chapter Four—An OLE DB Primer n
153
Note
If the rowset was generated as a result of executing a command that contained output parameters and the OLE DB provider populates output parameters when the rowset is released (that is, DBPROP_OUTPUTPARAMETERAVAILABILITY is DBPROP_OA_ATROWRELEASE), the memory for the output parameters bound at execute time must be valid when the rowset is released. Not doing so is considered a serious programming error and likely will cause a crash.
Method AddRefRows GetData GetNextRows ReleaseRows RestartPosition
Description This method adds a reference count to an existing row handle. This method retrieves data from the rowset’s copy of the row. This method fetches rows sequentially, remembering the previous position. This method releases rows. This method repositions the next fetch position to its initial position; that is, its position when the rowset was first created.
IRowset::AddRefRows This method adds a reference count to an existing row handle. AddRefRows must be supported for implementing multiple references to the same row even if the rowset does not support IRowsetIdentity. It is always possible for an OLE DB consumer to call AddRefRows while it is processing a method in IRowsetNotify. That is, the rowset must be reentrant through AddRefRows during notification. If AddRefRows encounters an error while incrementing the reference count of a row, it sets the corresponding element in rgRowStatus to the appropriate DBROWSTATUS value and continues processing. If a row handle is duplicated in rghRows, the corresponding row will have its reference count incremented by one for each time it appears in the array. It has the following syntax: HRESULT AddRefRows( ULONG cRows, const HROW rghRows[], ULONG rgRefCounts[], DBROWSTATUS rgRowStatus[]);
It has the following parameters: cRows [in] This parameter shows the number of rows for which to increment the reference count.
154
n Chapter Four—An OLE DB Primer rghRows [in] This parameter gives an array of row handles for which to increment the reference count. The reference count of row handles is incremented by one for each time they appear in the array. rgRefCounts [out] This parameter provides an array with cRows elements in which to return the new reference count for each row handle. The OLE DB consumer allocates memory for this array. If rgRefCounts is a null pointer, no reference counts are returned. rgRowStatus [out] This parameter indicates an array with cRows elements in which to return values indicating the status of each row specified in rghRows. If no errors occur while incrementing the reference count of a row, the corresponding element of rgRowStatus is set to DBROWSTATUS_S_OK. If an error occurs while incrementing the reference count of a row, the corresponding element is set as specified in DB_S_ERRORSOCCURRED. The OLE DB consumer allocates memory for this array. If rgRowStatus is a null pointer, no row statuses are returned. It has the following return codes: S_OK This return code means the method succeeded. The reference count of all rows was successfully incremented, and the corresponding element of prgRowStatus contains DBROWSTATUS_S_OK. DB_S_ERRORSOCCURRED This return code shows that an error occurred while incrementing the reference count of a row, but the reference count of at least one row was incremented. Successes can occur for the reason listed under S_OK. The following errors can occur: n
A row handle was invalid. The reference count of the row was not incremented and the corresponding element of rgRowStatus contains DBROWSTATUS_E_INVALID.
n
A row handle referred to a row that had a reference count of zero. The reference count of the row was not incremented and the corresponding element of rgRowStatus contains DBROWSTATUS_E_INVALID.
n
A row handle referred to a row for which a deletion had been transmitted to the data source. The reference count of the row was not incremented and the corresponding element of rgRowStatus contains DBROWSTATUS_E_DELETED.
n
The OLE DB consumer encountered a recoverable, OLE DB provider-specific error, such as an RPC failure when transmitting the change to a remote server. The corresponding element of rgRowStatus contains DBROWSTATUS_E_FAIL.
Chapter Four—An OLE DB Primer n
155
E_FAIL This return code means that an OLE DB provider-specific error occurred. E_INVALIDARG This return code shows that rghRows was a null pointer and cRows was not zero. E_UNEXPECTED This return code means that ITransaction::Commit or ITransaction::Abort was called and the object is in a zombie state. DB_E_ERRORSOCCURRED This return code indicates that errors occurred while incrementing the reference count of all of the rows. Errors can occur for the reasons listed under DB_S_ERRORSOCCURRED.
IRowset::GetData This method retrieves data from the rowset’s copy of the row. The method makes no logical change to the state of the object. An OLE DB consumer calls GetData to retrieve data from rows that have been fetched by prior calls to methods such as GetNextRows. An OLE DB consumer can call GetData any number of times. In each call, it can pass a different OLE DB accessor and the address of a different buffer. This means that the OLE DB consumer can get as many copies of the data as it wants, and it can get data in different types if alternate conversions are available. GetData does not enforce any security restrictions. The OLE DB provider must not create a rowset that includes columns for which the OLE DB consumer does not have read privileges, so GetData never encounters problems accessing the data for a column. The rowset can contain columns to which the OLE DB consumer does not have write permission if DBPROP_COLUMNRESTRICT is VARIANT_TRUE. The methods that fetch rows must not return the handles of rows for which the OLE DB consumer does not have read privileges, so GetData never encounters problems accessing a row. Such rows might exist if the DBPROP_ROWRESTRICT property is VARIANT_TRUE. If GetData fails, the memory to which *pData points is not freed but its contents are undefined. If, before GetData failed, the OLE DB provider allocated any memory for return to the OLE DB consumer, the OLE DB provider frees this memory and does not return it to the OLE DB consumer. GetData must be reentrant during notifications. If the provider calls a method from IRowsetNotify in the consumer, the consumer must be able to call GetData while processing the notification method. It has the following syntax: HRESULT GetData (
156
n Chapter Four—An OLE DB Primer HROW HACCESSOR void *
hRow, hAccessor, pData);
It has the following parameters: hRow [in] This parameter shows the handle of the row from which to get the data. Warning
The OLE DB consumer must ensure that hRow contains a valid row handle; the OLE DB provider might not validate hRow before using it. The result of passing the handle of a deleted row is OLE DB provider-specific, although the OLE DB provider cannot terminate abnormally. For example, the OLE DB provider might return DB_E_BADROWHANDLE, DB_E_DELETEDROW, or it might get data from a different row. The result of passing an invalid row handle in hRow is undefined.
hAccessor [in] This parameter indicates the handle of the OLE DB accessor to use. If hAccessor is the handle of a null accessor (cBindings in IAccessor::CreateAccessor was zero), then GetData does not get any data values. Warning
The OLE DB consumer must ensure that hAccessor contains a valid OLE DB accessor handle; the OLE DB provider might not validate hAccessor before using it. The result of passing an invalid OLE DB accessor handle in hAccessor is undefined.
pData [out] This parameter gives a pointer to a buffer in which to return the data. The OLE DB consumer allocates memory for this buffer. This pointer must be a valid pointer to a contiguous block of OLE DB consumer-owned memory into which the data will be written. It has the following return codes: S_OK This return code means the method succeeded. The status of all columns bound by the accessor is set to DBSTATUS_S_OK, DBSTATUS_S_ISNULL, or DBSTATUS_S_TRUNCATED. DB_S_ERRORSOCCURRED This return code indicates that an error occurred while returning data for one or more columns, but data was successfully returned for at least one column. To determine the columns for which data was returned, the OLE DB consumer checks the status values. E_FAIL This return code shows that an OLE DB provider-specific error occurred.
Chapter Four—An OLE DB Primer n
157
E_INVALIDARG This return code specifies that *pData was a null pointer and the OLE DB accessor was not a null accessor. E_UNEXPECTED This return code states that ITransaction::Commit or ITransaction::Abort was called and the object is in a zombie state. DB_E_BADACCESSORHANDLE This return code shows that hAccessor was invalid. OLE DB providers are not required to check for this condition, because doing so might slow the method significantly. DB_E_BADACCESSORTYPE This return code means the specified OLE DB accessor was not a row accessor. DB_E_BADROWHANDLE This return code shows that hRow was invalid. OLE DB providers are not required to check for this condition, because doing so might slow the method significantly. DB_E_DELETEDROW This return code specifies hRow referred to a pending delete row or a row for which a deletion had been transmitted to the data source. OLE DB providers are not required to check for this condition, because doing so might slow the method significantly. DB_E_ERRORSOCCURRED This return code indicates that errors occurred while returning data for all columns. To determine what errors occurred, the consumer checks the status values. If this method performs deferred OLE DB accessor validation and that validation takes place before any data is transferred, it can also return any of the following return codes for the reasons listed in the corresponding DBBINDSTATUS values in IAccessor::CreateAccessor: E_NOINTERFACE DB_E_BADBINDINFO DB_E_BADORDINAL DB_E_BADSTORAGEFLAGS DB_E_UNSUPPORTEDCONVERSION
IRowset::GetNextRows This method fetches rows sequentially, remembering the previous position. GetNextRows fetches a sequence of rows. The OLE DB provider maintains a next fetch position that is used in subsequent calls to either GetNextRows or FindNextRow. The next fetch position is changed either by calling GetNextRows, or by calling FindNextRow with a null pBookmark value.
158
n Chapter Four—An OLE DB Primer Calling FindNextRow with a non-null pBookmark value has no effect on the next fetch position. If the fetch direction is reversed from the previous call, then the next fetch position in the new direction is the last row that was fetched in the previous direction; otherwise the next fetch position is the row following the last row fetched by GetNextRows, or by FindNextRow with a null pBookmark value. For a newly created rowset, or any time the next fetch position is prior to the first row of the rowset, the next fetch position is computed as follows, where N is the number of rows in the rowset. lRowsOffset can be less than zero only if DBPROP_CANSCROLLBACKWARDS is VARIANT_TRUE. Value of lRowsOffset lRowsOffset > 0 lRowsOffset < 0 lRowsOffset = 0
Next Fetch Position After lRowsOffset After N – abs(lRowsOffset) Before first row if cRows > 0, after last row if cRows < 0
None of the other methods that fetch rows, except for IRowsetFind::FindNextRow with a null pBookmark value, has any effect on the next fetch position. However, IRowsetIndex::Seek sets the next fetch position to the row specified in the seek criteria, and RestartPosition resets the next fetch position to the same position as when the rowset is first created. GetNextRows increments by one the reference count of each row for which it returns a handle. Thus, if a handle is returned for a row that has already been fetched, the reference count of that row will be greater than 1. ReleaseRows must be called once for each time the handle to a row has been returned. If the OLE DB provider encounters a problem fetching a row—for example, data stored in a text file contains a letter in a numeric column—GetNextRows fetches the row normally, returns the row handle, and returns S_OK. However, when the OLE DB consumer calls GetData for the row, the OLE DB provider returns DBSTATUS_E_CANTCONVERTVALUE as the status for the offending column. GetNextRows must always check for the conditions that cause E_INVALIDARG, E_UNEXPECTED, DB_E_CANTFETCHBACKWARDS, DB_E_CANTSCROLLBACKWARDS, DB_E_NOTREENTRANT, and DB_E_ROWSNOTRELEASED before changing the next fetch position. If it returns any other error besides these, the next fetch position is unknown. For example, the OLE DB provider might have to perform actions that change the next fetch position in order to determine that the error DB_E_BADSTARTPOSITION occurred. When the next fetch position is unknown, the OLE DB consumer generally calls RestartPosition to return it to a known position.
Chapter Four—An OLE DB Primer n
159
It has the following syntax: HRESULT GetNextRows ( HCHAPTER hChapter, LONG lRowsOffset, LONG cRows, ULONG * pcRowsObtained, HROW ** prghRows);
It has the following parameters: hChapter [in] This parameter gives the chapter handle. For nonchaptered rowsets, hChapter is ignored. lRowsOffset [in] This parameter provides the signed count of rows to skip before fetching rows. Deleted rows that the OLE DB provider has removed from the rowset are not counted in the skip. If this value is zero and cRows continues in the same direction as the previous call to either GetNextRows, or FindNextRow with a null pBookmark value, then the first row fetched will be the next row after the last one fetched in the previous call. If this value is zero and cRows reverses direction, then the first row fetched will be the last one fetched in the previous call. lRowsOffset can be a negative number only if the value of the DBPROP_CANSCROLLBACKWARDS property is VARIANT_TRUE. There is no guarantee that skipping rows is done efficiently on a sequential rowset. If the data source resides on a remote server, there may be remote support for skipping without transferring the intervening records across the network, but this is not guaranteed. cRows [in] This parameter indicates the number of rows to fetch. A negative number means to fetch backward. cRows can be a negative number only if the value of the DBPROP_CANFETCHBACKWARDS property is VARIANT_ TRUE. If cRows is zero, no rows are fetched; the fetch direction and the next fetch position are unchanged, and the OLE DB provider performs no processing, returning immediately from the method invocation. Specifically, lRowsOffset is ignored in this situation. If the OLE DB provider does not discover any other errors, the method returns S_OK; whether the provider checks for any other errors is OLE DB provider-specific. pcRowsObtained [out] This parameter provides a pointer to memory in which to return the actual number of fetched rows. If a warning condition occurs, this number may be less than the number of rows available or requested, and is
160
n Chapter Four—An OLE DB Primer the number of rows actually fetched before the warning condition occurred. If the OLE DB consumer has insufficient permission to fetch all rows, GetNextRows fetches all rows for which the OLE DB consumer has sufficient permission and skips all other rows. If the method fails, *pcRowsObtained is set to zero. prghRows [out] This parameter specifies a pointer to memory in which to return an array of handles of the fetched rows. If *prghRows is not a null pointer on input, it must be a pointer to memory large enough to return the handles of the requested number of rows. If *prghRows is a null pointer on input, the rowset allocates memory for the row handles and returns the address to this memory; the OLE DB consumer releases this memory with IMalloc::Free after it releases the row handles. If *prghRows is a null pointer on input and *pcRowsObtained is zero on output or if the method fails, the OLE DB provider does not allocate any memory and ensures that *prghRows is a null pointer on output. It has the following return codes: S_OK This return code means the method succeeded. DB_S_ENDOFROWSET This return code indicates that GetNextRows reached the start or the end of the rowset or chapter or the start or end of the range on an index rowset and could not fetch all requested rows because the count extended beyond the end. The next fetch position is before the start or after the end of the rowset. The number of rows actually fetched is returned in *pcRowsObtained; this will be less than cRows. This can also indicate that the rowset is being populated asynchronously and no additional rows are available at this time. To determine whether additional rows may be available, the OLE DB consumer should call IDBAsynchStatus::GetStatus or listen for the IDBAsynchNotify::OnStop notification. Or, finally, lRowsOffset indicated a position either more than one row before the first row of the rowset or more than one row after the last row, and the OLE DB provider was a version 2.0 or greater OLE DB provider. *pcRowsObtained is set to zero and no rows are returned. DB_S_ROWLIMITEXCEEDED Fetching the number of rows specified in cRows would have exceeded the total number of active rows supported by the rowset. The number of rows that were actually fetched is returned in *pcRowsObtained. This condition can occur only when there are more rows available than can be handled by the rowset. Thus, this condition never conflicts with those described in DB_S_ENDOFROWSET and DB_S_STOPLIMITREACHED, both of which imply that no more rows were available.
Chapter Four—An OLE DB Primer n
161
DB_S_STOPLIMITREACHED This return code states that fetching rows required further execution of the command, such as when the rowset uses a server-side cursor. Execution has been stopped because a resource limit has been reached. The number of rows that were actually fetched is returned in *pcRowsObtained. E_FAIL This return code indicates that an OLE DB provider-specific error occurred. E_INVALIDARG This return code shows that *pcRowsObtained or *prghRows was a null pointer. E_OUTOFMEMORY This return code specifies that the OLE DB provider was unable to allocate sufficient memory to complete the request. E_UNEXPECTED This return code shows that ITransaction::Commit or ITransaction::Abort was called and the object is in a zombie state. DB_E_BADCHAPTER This return code means the rowset was chaptered and hChapter was invalid, or the rowset was single-chaptered and the specified chapter was not the currently open chapter. The OLE DB consumer must use the currently open chapter or release the currently open chapter before specifying a new chapter. DB_E_BADSTARTPOSITION This return code states that lRowsOffset indicated a position either more than one row before the first row of the rowset or more than one row after the last row, and the OLE DB provider was a 1.x OLE DB provider. DB_E_CANCELED This return code indicates that fetching rows was canceled during notification. No rows were fetched. DB_E_CANTFETCHBACKWARDS This return code means that cRows was negative and the rowset cannot fetch backward. DB_E_CANTSCROLLBACKWARDS This return code shows lRowsOffset was negative and the rowset cannot scroll backward. DB_E_NOTREENTRANT This return code specifies that the consumer called the method while it was processing a notification; it is an error to call this method while processing the specified DBREASON value.
162
n Chapter Four—An OLE DB Primer DB_E_ROWSNOTRELEASED This return code indicates that the OLE DB provider requires release of existing rows before new ones can be fetched. DB_SEC_E_PERMISSIONDENIED This return code states that the OLE DB consumer did not have sufficient permission to fetch any of the rows and no rows were fetched.
IRowset::ReleaseRows This method releases rows. ReleaseRows decreases the reference count on the specified rows. It must be called once for each time that a row was fetched. For example, if the row was fetched three times, ReleaseRows must be called three times. Furthermore, if a row handle is duplicated in rghRows, the corresponding row will have its reference count decremented by one for each time it appears in the array. If an OLE DB provider doesn’t support exact reference counts on rows, it should return a reference count of 1 while the row is active. OLE DB consumers should be aware of this behavior and should use the returned reference count for debugging purposes only. OLE DB consumers should not rely on the returned reference count to indicate whether the row would survive another release. In this case, an OLE DB provider may choose to return S_OK for any and all calls to ReleaseRows until the rowset itself is released. If an OLE DB consumer calls ReleaseRows on a row with pending changes, the row remains valid and ReleaseRows returns DBROWSTATUS_S_PENDINGCHANGES in rgRowStatus. When the reference count for a row decreases to zero, the row is released: n
Subject to the rules of the current transaction, the rowset is free to discard any resources used by a row that has a reference count of zero. For example, these might include memory, locks, and original values. When the rowset actually discards these resources is OLE DB provider-specific.
n
If the row has pending changes, the row still remains valid even though its reference count is zero. OLE DB consumers should not use the handle of a row that has a reference count of zero, even though the handle might still be valid. If IRowsetUpdate::Update is called to transmit pending changes for a row with a reference count of zero to the data source, it transmits the changes of the row and releases the row and its resources if the update succeeds. If IRowsetUpdate::Undo is called to undo the pending changes for a row with a reference count of zero, it releases the row and its resources.
n
Pending changes are not lost when releasing a reference count obtained from GetRowsAt. A provider may take its own reference count on the row handle or store the row internally until pending changes are committed or dismissed (using Update or Undo).
Chapter Four—An OLE DB Primer n
163
After a row is released, methods called with the handle to that row return DB_E_BADROWHANDLE if the row has pending changes. After the pending changes are transmitted to the data source, methods might continue to return this error. However, the OLE DB provider might have an implementation that recycles row handles and thereafter cannot detect the misuse. Because provider behavior varies, OLE DB consumers should not use the handles of released rows. If ReleaseRows encounters an error while decrementing the reference count of a row or releasing the row, it sets the corresponding element in rgRowStatus to the appropriate DBROWSTATUS value and continues processing. This method can be called while the rowset is in a zombie state to allow the OLE DB consumer to clean up after a transaction has been committed or aborted. It has the following syntax: HRESULT ReleaseRows ( ULONG cRows, const HROW rghRows[], DBROWOPTIONS rgRowOptions[] ULONG rgRefCounts[], DBROWSTATUS rgRowStatus[]);
It has the following parameters: cRows [in] This parameter gives the number of rows to release. If cRows is zero, ReleaseRows does not do anything. rghRows [in] This parameter shows an array of handles of the rows to be released. The row handles need not form a logical cluster; they may have been obtained at separate times and need not be for contiguous underlying rows. They must belong to the current thread. Row handles are decremented by one reference count for each time they appear in the array. rgRowOptions [in] This parameter provides an array of cRows elements containing bitmasks indicating additional options to be specified when releasing a row. This parameter is reserved for future use and should be set to a null pointer. rgRefCounts [out] This parameter specifies an array with cRows elements in which to return the new reference count of each row. If rgRefCounts is a null pointer, no counts are returned. The OLE DB consumer allocates but is not required to initialize memory for this array and passes the address of this memory to the OLE DB provider. The OLE DB provider returns the reference counts in the array.
164
n Chapter Four—An OLE DB Primer rgRowStatus [out] This parameter gives an array with cRows elements in which to return values indicating the status of each row specified in rghRows. If no errors or warnings occur while releasing a row, the corresponding element of rgRowStatus is set to DBROWSTATUS_S_OK. If an error or warning occurs while releasing a row, the corresponding element is set as specified in DB_S_ERRORSOCCURRED. The OLE DB consumer allocates memory for this array. If rgRowStatus is a null pointer, no row statuses are returned. It has the following return codes: S_OK This return code means the method succeeded. All rows were successfully released. The following values can be returned in rgRowStatus: n
The row was successfully released. The corresponding element of rgRowStatus contains DBROWSTATUS_S_OK.
n
A row had a pending change. The row was released and the corresponding element of rgRowStatus contains DBROWSTATUS_S_ PENDINGCHANGES.
DB_S_ERRORSOCCURRED This return code shows that an error occurred while releasing a row, but at least one row was successfully released. Successes and warnings can occur for the reasons listed under S_OK. The following errors can occur: n
A row handle was invalid. The row was not released and the corresponding element of rgRowStatus contains DBROWSTATUS_E_ INVALID.
n
The OLE DB consumer encountered a recoverable, OLE DB provider-specific error, such as an RPC failure when transmitting the change to a remote server. The corresponding element of rgRowStatus contains DBROWSTATUS_E_FAIL.
E_FAIL This return code specifies that a provider-specific error occurred. E_INVALIDARG This return code indicates that rghRows was a null pointer and cRows was not equal to zero. DB_E_ERRORSOCCURRED This return code shows errors occurring while releasing all of the rows. Errors can occur for the reasons listed under DB_S_ERRORSOCCURRED. DB_E_NOTREENTRANT This return code means that the OLE DB consumer called this method while it was processing a notification; it is an error to call this method while processing the specified DBREASON value.
Chapter Four—An OLE DB Primer n
165
IRowset::RestartPosition This method repositions the next fetch position used by GetNextRows or FindNextRow to its initial position; that is, its position when the rowset was first created. If the underlying command contains output parameters, RestartPosition should not reset those parameters. If the rowset was generated as a result of a procedure call, and the rowset is forward-only, the procedure may be reexecuted in order to satisfy the call to RestartPosition. This may cause other side effects. Additionally, if the stored procedure has been changed, the rowset may have a different schema. If the rowset is able to restart the next fetch position without reexecuting the procedure, RestartPosition should not reexecute it. How expensive RestartPosition is depends on the OLE DB provider, the rowset characteristics, and the tables underlying the rowset. If the rowset supports IRowsetLocate, then RestartPosition is always an inexpensive operation. If the rowset is sequential, then RestartPosition might require reexecution of the underlying command. For some OLE DB providers, this is always the case. For other OLE DB providers, a rule of thumb is that rowsets built from a single table are not expensive to restart, but rowsets built by joining two or more tables are expensive to restart. If the OLE DB provider reexecutes the command to restart the next fetch position, then the new rowset might return a different set of rows, differently ordered columns, and, in extreme cases, a different set of columns. Reexecution of a command by RestartPosition does not reinherit OLE DB accessors. An OLE DB provider that reports DBPROP_QUICKRESTART as VARIANT_ FALSE may require that all existing row handles be released prior to successfully processing a call to RestartPosition. An OLE DB consumer can determine whether an OLE DB provider can quickly restart the next fetch position by attempting to set DBPROP_QUICKRESTART to VARIANT_TRUE. Setting this property to VARIANT_TRUE does not guarantee that the rowset can be quickly restarted because the OLE DB provider is not required to honor the property. This behavior is necessary because the OLE DB provider cannot evaluate the command at the time the property is set. For example, the OLE DB consumer can set DBPROP_QUICKRESTART to VARIANT_TRUE and then change the command text. In implementations that require reexecution of a command to reposition the next fetch position to its initial position, the OLE DB provider is responsible for caching all parameters required by the command. If RestartPosition returns DB_S_COLUMNSCHANGED and the OLE DB consumer subsequently calls methods in IColumnsInfo or IColumnsRowset, these methods must reflect the new metadata. Existing rowset OLE DB accessors are not updated to reflect the new metadata. That is, IAccessor::GetBindings
166
n Chapter Four—An OLE DB Primer returns exactly the same information it would have returned before RestartPosition was called. If such OLE DB accessors are subsequently used, such as in a call to GetData, the OLE DB provider must revalidate them. If none of the columns bound by the OLE DB accessor have changed, the OLE DB accessor can be used successfully. If any of the columns have changed, the appropriate error or warning is returned. It has the following styntax: HRESULT RestartPosition ( HCHAPTER hChapter);
It has the following parameters: hChapter [in] This parameter gives the chapter handle. For nonchaptered rowsets, hChapter is ignored. It has the following return codes: S_OK This return code means the method succeeded. The OLE DB provider did not have to reexecute the command, either because the rowset supports positioning on the first row without reexecuting the command or because the rowset is already positioned on the first row. DB_S_COLUMNSCHANGED This return code states that the order of the columns was not specified in the object that created the rowset. The OLE DB provider had to reexecute the command to reposition the next fetch position to its initial position, and the order of the columns changed. The OLE DB provider had to reexecute the command to reposition the next fetch position to its initial position, and columns were added or removed from the rowset. This is generally due to a change in the underlying schema and is extremely uncommon. This return code takes precedence over DB_S_COMMANDREEXECUTED. That is, if the conditions described here and in those described in DB_S_COMMANDREEXECUTED both occur, the OLE DB provider returns this code. A change to the columns generally implies that the command was reexecuted. DB_S_COMMANDREEXECUTED This return code shows that the command associated with this rowset was reexecuted. If the properties DBPROP_OWNINSERT and DBPROP_ OWNUPDATEDELETE are VARIANT_TRUE, then the OLE DB consumer will see its own changes. If the properties DBPROP_OWNINSERT or DBPROP_OWNUPDATEDELETE are VARIANT_FALSE, then the rowset may see its changes. The order of the columns remains unchanged.
Chapter Four—An OLE DB Primer n
167
E_FAIL This return code specifies that an OLE DB provider-specific error occurred. E_UNEXPECTED This return code states that ITransaction::Commit or ITransaction::Abort was called and the object is in a zombie state. DB_E_BADCHAPTER This return code indicates that the rowset was chaptered and hChapter was invalid, or that the rowset was single-chaptered and the specified chapter was not the currently open chapter. The OLE DB consumer must use the currently open chapter or release the currently open chapter before specifying a new chapter. DB_E_CANCELED This return code means that RestartPosition was canceled during notification. The next fetch position remains unmodified. DB_E_CANNOTRESTART This return code shows that the rowset was built over a live data stream (for example, a stock feed) and the position cannot be restarted. DB_E_NOTREENTRANT This return code specifies that the OLE DB provider called a method from IRowsetNotify in the OLE DB consumer and the method has not yet returned. DB_E_ROWSNOTRELEASED This return code indicates that the provider requires release of existing rows before restarting because the rowset will be regenerated. This may be required even if the OLE DB provider supports a value of VARIANT_ TRUE for DBPROP_CANHOLDROWS. DB_SEC_E_PERMISSIONDENIED This return code states that the consumer did not have sufficient permission to reposition the next fetch position.
IRowsetInfo IRowsetInfo provides information about a rowset. All rowsets must implement IRowsetInfo. When an OLE DB consumer gets an interface pointer on a rowset, its first step usually is to determine the rowset’s capabilities using IUnknown:: QueryInterface. It may call GetProperties to learn the properties of the rowset that do not show up as distinct interfaces, such as the maximum number of active rows and how many rows can have pending updates at the same time. IRowsetInfo also provides methods for retrieving objects associated with the rowset. GetSpecification gets the object (command or session) that created
168
n Chapter Four—An OLE DB Primer the rowset. GetReferencedRowset gets the rowset that is referenced by a bookmark-valued column. Method GetProperties GetReferencedRowset GetSpecification
Description This method returns the current setting of all properties supported by the rowset. This method returns an interface pointer to the rowset to which a bookmark applies. This method returns an interface pointer on the object (command or session) that created the rowset.
IRowsetInfo::GetProperties This method returns the current settings of all properties supported by the rowset. The method makes no logical change to the state of the object. Even though IDBProperties::GetPropertyInfo lists a property as being supported by the OLE DB provider, GetProperties will not return a value for it if it does not apply to the current circumstances. For example, the OLE DB provider’s ability to support the property might be affected by the current transaction or the current command text. IRowsetInfo::GetProperties might return a different value for a property than does ICommandProperties::GetProperties. For example, if an OLE DB consumer requests ordered bookmarks if they are possible, it calls ICommandProperties::SetProperties to set the value of DBPROP_ORDEREDBOOKMARKS to VARIANT_TRUE and specifies a dwOptions value of DBPROPOPTIONS_OPTIONAL. If the OLE DB provider cannot determine whether this is possible, ICommandProperties::GetProperties returns a value of VARIANT_TRUE and a dwOptions of DBPROPOPTIONS_OPTIONAL for this property. If the OLE DB provider determines during execution that ordered bookmarks are not possible, IRowsetInfo::GetProperties returns a value of VARIANT_FALSE and a cPropertyIDSets of zero. It has the following syntax: HRESULT GetProperties ( const ULONG cPropertyIDSets, const DBPROPIDSET rgPropertyIDSets[], ULONG * pcPropertySets, DBPROPSET ** prgPropertySets);
It has the following parameters: cPropertyIDSets [in] This parameter gives the number of DBPROPIDSET structures in rgPropertyIDSets. If cPropertyIDSets is zero, the OLE DB provider ignores rgPropertyIDSets and returns the values of all properties in the Rowset property group for which values exist, including properties for which
Chapter Four—An OLE DB Primer n
169
values were not set but for which defaults exist, and also including properties for which values were set automatically because values were set for other properties. If cPropertyIDSets is not zero, the OLE DB provider returns the values of the requested properties. If a property is not supported, the returned value of dwStatus in the returned DBPROP structure for that property is DBPROPSTATUS_NOTSUPPORTED and the value of dwOptions is undefined. rgPropertyIDSets [in] This parameter shows an array of cPropertyIDSets DBPROPIDSET structures. The properties specified in these structures must belong to the Rowset property group. The OLE DB provider returns the values of the properties specified in these structures. If cPropertyIDSets is zero, then this parameter is ignored. pcPropertySets[out] This parameter provides a pointer to memory in which to return the number of DBPROPSET structures returned in *prgPropertySets. If cPropertyIDSets is zero, *pcPropertySets is the total number of property sets for which the OLE DB provider supports at least one property in the Rowset property group. If cPropertyIDSets is greater than zero, *pcPropertySets is set to cPropertyIDSets. If an error other than DB_E_ERRORSOCCURRED occurs, *pcPropertySets is set to zero. prgPropertySets [out] This parameter specifies a pointer to memory in which to return an array of DBPROPSET structures. If cPropertyIDSets is zero, then one structure is returned for each property set that contains at least one property belonging to the Rowset property group. If cPropertyIDSets is not zero, then one structure is returned for each property set specified in rgPropertyIDSets. If cPropertyIDSets is not zero, the DBPROPSET structures in *prgPropertySets are returned in the same order as the DBPROPIDSET structures in rgPropertyIDSets; that is, for corresponding elements of each array, the guidPropertySet elements are the same. If cPropertyIDs, in an element of rgPropertyIDSets, is not zero, the DBPROP structures in the corresponding element of *prgPropertySets are returned in the same order as the DBPROPID values in rgPropertyIDs. Thus, in the case where no column properties are specified in rgPropertyIDSets, corresponding elements of the input rgPropertyIDs and the returned rgProperties have the same property ID. However, if a column property is requested in rgPropertyIDSets, multiple properties may be returned, one for each column, in rgProperties. In this case, corresponding elements of rgPropertyIDs and rgProperties will not have the same property ID, and rgProperties will contain more elements than rgPropertyIDs.
170
n Chapter Four—An OLE DB Primer The OLE DB provider allocates memory for the structures and returns the address to this memory; the OLE DB consumer releases this memory with IMalloc::Free when it no longer needs the structures. Before calling IMalloc::Free for *prgPropertySets, the OLE DB consumer should call IMalloc::Free for the rgProperties element within each element of *prgPropertySets. If *pcPropertySets is zero on output or if an error other than DB_E_ERRORSOCCURRED occurs, the OLE DB provider does not allocate any memory and ensures that *prgPropertySets is a null pointer on output. It has the following return codes: S_OK This return code means the method succeeded. In all DBPROP structures returned by the method, dwStatus is set to DBPROPSTATUS_OK. DB_S_ERRORSOCCURRED This return code provides that no value was returned for one or more properties. The OLE DB consumer checks dwStatus in the DBPROP structure to determine the properties for which values were not returned. GetProperties can fail to return properties for a number of reasons, including: n
The property was not supported by the OLE DB provider.
n
The property was not in the Rowset property group.
n
The property set was not supported by the OLE DB provider. If cPropertyIDs in the DBPROPIDSET structure for the property set was zero, the OLE DB provider cannot set dwStatus in the DBPROP structure because it does not know the IDs of any properties in the property set. Instead, it sets cProperties to zero in the DBPROPSET structure returned for the property set.
E_FAIL This return code states that an OLE DB provider-specific error occurred. E_INVALIDARG This return code shows that cPropertyIDSets was not equal to zero and rgPropertyIDSets was a null pointer. It alternatively can mean that *pcPropertySets or *prgPropertySets was a null pointer. Or this return code can indicate that an element of rgPropertyIDSets, cPropertyIDs, was not zero and rgPropertyIDs was a null pointer. E_OUTOFMEMORY This return code states that the OLE DB provider was unable to allocate sufficient memory in which to return the DBPROPSET or DBPROP structures. E_UNEXPECTED This return code means that ITransaction::Commit or ITransaction::Abort was called and the object is in a zombie state.
Chapter Four—An OLE DB Primer n
171
DB_E_ERRORSOCCURRED This return code specifies that values were not returned for any properties. The OLE DB provider allocates memory for *prgPropertySets and the OLE DB consumer checks dwStatus in the DBPROP structures to determine why properties were not returned. The OLE DB consumer frees this memory when it no longer needs the information.
IRowsetInfo::GetReferencedRowset This method returns an interface pointer to the rowset to which a bookmark or chapter applies. The method makes no logical change to the state of the current rowset. All of the bookmarks or chapter values in a column reference a single rowset. The references can apply to the current rowset (an example would be genealogy relations on a People table), or they can apply to a different rowset (an example of which would be Customer Orders). It has the following syntax: HRESULT GetReferencedRowset ( ULONG iOrdinal, REFIID riid, IUnknown ** ppReferencedRowset);
It has the following parameters: iOrdinal [in] This parameter gives the bookmark or chapter column for which to get the related rowset. riid [in] This parameter shows the IID of the interface pointer to return in *ppReferencedRowset. This interface is conceptually added to the list of required interfaces on the resulting rowset, and the method fails (E_NOINTERFACE) if that interface cannot be supported on the resulting rowset. ppReferencedRowset [out] This parameter provides a pointer to memory in which to return an IUnknown interface pointer on the rowset that interprets values from this column. If this is not a reference column, *ppReferencedRowset is set to a null pointer. It has the following return codes: S_OK This return code means the method succeeded. E_FAIL This return code shows that an OLE DB provider-specific error occurred.
172
n Chapter Four—An OLE DB Primer E_INVALIDARG This return code indicates that *ppReferencedRowset was a null pointer. E_NOINTERFACE This return code states that the interface specified in riid was not implicitly or explicitly specified as a rowset property of the rowset. The current rowset remains valid. E_UNEXPECTED This return code specifies that ITransaction::Commit or ITransaction::Abort was called and the object is in a zombie state. DB_E_BADORDINAL This return code shows that the column specified by iOrdinal did not exist. DB_E_NOTAREFERENCECOLUMN This return code provides that the column specified by iOrdinal did not contain bookmarks or chapter values. DB_E_NOTREENTRANT This return code means that the OLE DB provider called a method from IRowsetNotify in the OLE DB consumer that had not yet returned, and the OLE DB provider does not support reentrancy in this method. DB_E_NOTSUPPORTED This return code indicates that the interface was exposed on an index rowset.
IRowsetInfo::GetSpecification This method returns an interface pointer on the object (Command or Session) that created this rowset. The method makes no logical change to the state of the current rowset. GetSpecification returns an interface pointer on the object that created the rowset. If the rowset was created by ICommand::Execute, this object is a command. If the rowset was created by IOpenRowset::OpenRowset, this object is a session. If the object is not a command, then it must specify the contents of the rowset. That is, it must expose interfaces that can be used to modify the contents of the rowset before the rowset is created, or be used to gain additional information about the rowset. If the object cannot expose such interfaces, GetSpecification should return a null pointer in *ppSpecification. In a simple OLE DB provider, such as an OLE DB provider that creates a rowset over a fixed set of data, there might not be an object that created the rowset; in this case, GetSpecification returns a null pointer in *ppSpecification.
Chapter Four—An OLE DB Primer n
173
It has the following syntax: HRESULT GetSpecification ( REFIID riid, IUnknown ** ppSpecification);
It has the following parameters: riid [in] This parameter gives the IID of the interface on which to return a pointer. ppSpecification [out] This parameter specifies a pointer to memory in which to return the interface pointer. If the OLE DB provider does not have an object that created the rowset, it sets *ppSpecification to a null pointer and returns S_FALSE. If GetSpecification fails, it must attempt to set *ppSpecification to a null pointer. It has the following return codes: S_OK This return code means the method succeeded. S_FALSE This return code shows that the OLE DB provider does not have an object that created the rowset. E_FAIL This return code indicates that an OLE DB provider-specific error occurred. E_INVALIDARG This return code states that *ppSpecification was a null pointer. E_NOINTERFACE This return code provides that the object that created this rowset did not support the interface specified in riid. E_UNEXPECTED This return code means that ITransaction::Commit or ITransaction::Abort was called and the object is in a zombie state. DB_E_NOTREENTRANT This return code shows that the OLE DB provider called a method from IRowsetNotify in the OLE DB consumer that had not yet returned, and OLE DB provider does not support reentrancy in this method.
IRowsetChange The methods in IRowsetChange are used to update the values of columns in existing rows, delete existing rows, and insert new rows. IRowsetChange requires IAccessor and IRowset.
174
n Chapter Four—An OLE DB Primer Rowsets implement IRowsetChange if they support updating, deleting, or inserting rows. They are not required to support all three, but must support at least one of these operations to support IRowsetChange. The rowset reports which operations it supports through the DBPROP_UPDATABILITY property. The OLE DB consumer calls methods in IRowsetChange to modify rows as follows: n
SetData sets column data in an existing row.
n
DeleteRows deletes existing rows.
n
InsertRow creates a new row and sets initial values.
The SetData method and the InsertRow method require the use of an OLE DB accessor. SetData and InsertRow can fail for a number of reasons. The most common of these is that new data values do not meet the schema or integrity constraints of the column. Furthermore, rowsets can have row-by-row and column-by-column access permissions that override the general permissions of the table or column. If IRowsetUpdate is exposed on the rowset, then changes made through IRowsetChange are buffered in the rowset and not transmitted to the data source until IRowsetUpdate::Update is called; this is known as delayed update mode. If IRowsetUpdate is not exposed on the rowset, then changes made through IRowsetChange are immediately transmitted to the data source; this is known as immediate update mode. Method DeleteRows InsertRow SetData
Description This method deletes rows. This method creates and initializes a new row. This method sets data in one or more columns in a row.
IRowsetChange::DeleteRows This method deletes rows. In delayed update mode, DeleteRows marks rows for deletion, rather than actually deleting them. Rows with pending deletes cannot be used in any methods except IRowsetRefresh::GetLastVisibleData, IRowsetUpdate::Undo, IRowsetUpdate::Update, IRowsetUpdate::GetOriginalData, and IRowset::ReleaseRows. The deletion is not transmitted to the data source until Update is called. In immediate update mode, DeleteRows transmits deletions to the data source immediately. After a deletion has been transmitted to the data source, it cannot be undone. The row cannot be used with any method except ReleaseRows. Neither DeleteRows nor Update releases rows after transmitting deletions to the data source. The OLE DB consumer must release the row with ReleaseRows.
Chapter Four—An OLE DB Primer n
175
If DeleteRows is called for a row with a pending insert, the row is placed in the same state as a row for which a deletion has been transmitted to the data source. That is, if a row is inserted and then deleted in delayed update mode, the deletion cannot be undone. The row cannot be used with any method except ReleaseRows, which must be called to release it. If an error occurs while deleting a row, DeleteRows continues deleting the other rows in rghRows and returns DB_S_ERRORSOCCURRED or DB_E_ ERRORSOCCURRED. It returns status information about each row in rgRowStatus. If the DBPROP_ROWRESTRICT property is VARIANT_TRUE, the OLE DB consumer may have permission to delete some rows but not other rows. It has the following syntax: HRESULT DeleteRows HCHAPTER ULONG const HROW DBROWSTATUS
( hChapter, cRows, rghRows[], rgRowStatus[]);
It has the following parameters: hChapter [in] This parameter shows the chapter handle. For nonchaptered rowsets, hChapter is ignored. cRows [in] This parameter gives the number of rows to be deleted. If cRows is zero, DeleteRows does not do anything. rghRows [in] This parameter shows an array of handles of the rows to be deleted. If rghRows includes a duplicate row handle, DeleteRows behaves as follows. If the row handle is valid, it is OLE DB provider-specific whether the returned row status information for each row or a single instance of the row is set to DBROWSTATUS_S_OK. If the row handle is invalid, the row status information for each occurrence of the row contains the appropriate error. rgRowStatus [out] This parameter provides an array with cRows elements in which to return values indicating the status of each row specified in rghRows. If no errors or warnings occur while deleting a row, the corresponding element of rgRowStatus is set to DBROWSTATUS_S_OK. If a warning occurs while deleting a row, the corresponding element is set as specified in S_OK. If an error occurs while deleting a row, the corresponding element is set as specified in DB_S_ERRORSOCCURRED. The OLE DB consumer allocates memory for this array. If rgRowStatus is a null pointer, no row statuses are returned.
176
n Chapter Four—An OLE DB Primer It has the following return codes: S_OK This return code means the method succeeded, and all rows were successfully deleted. The following values can be returned in rgRowStatus: n
The row was successfully deleted and no warning conditions occurred. The corresponding element of rgRowStatus contains DBROWSTATUS_ S_OK.
n
The rowset was in immediate update mode, and deleting a single row caused more than one row to be deleted in the data source. The corresponding element of rgRowStatus contains DBROWSTATUS_S_ MULTIPLECHANGES.
DB_S_ERRORSOCCURRED This return code shows that an error occurred while deleting a row, but at least one row was successfully deleted. Successes and warnings can occur for the reasons listed under S_OK. The following errors can occur: n
An element of rghRows was invalid or was a row handle to which the current thread does not have access rights. The corresponding element of rgRowStatus contains DBROWSTATUS_E_INVALID.
n
Deletion of a row was canceled during notification. The row was not deleted and the corresponding element of rgRowStatus contains DBROWSTATUS_E_CANCELED.
n
An element of rghRows referred to a row with a pending delete or for which a deletion had been transmitted to the data source. The corresponding element of rgRowStatus contains DBROWSTATUS_E_ DELETED.
n
Deleting a row referred to by an element of rghRows violated the integrity constraints for the column or table. The corresponding element of rgRowStatus contains DBROWSTATUS_E_INTEGRITYVIOLATION.
n
The rowset was in immediate update mode and the row was not deleted due to reaching a limit on the server, such as a query execution timing out. The error in the corresponding element of rgRowStatus contains DBROWSTATUS_E_LIMITREACHED.
n
Deleting a row would exceed the limit for pending changes specified by the rowset property DBPROP_MAXPENDINGROWS. The corresponding element of rgRowStatus contains DBROWSTATUS_E_ MAXPENDCHANGESEXCEEDED.
n
DBPROP_CHANGEINSERTEDROWS was VARIANT_FALSE and an element of rghRows referred to a row for which the insertion has been transmitted to the data source. The corresponding element of rgRowStatus contains DBROWSTATUS_E_NEWLYINSERTED.
Chapter Four—An OLE DB Primer n
177
n
The OLE DB consumer did not have sufficient permission to delete a row. This error can be returned only if the value of the DBPROP_ ROWRESTRICT property is VARIANT_TRUE. The corresponding element of rgRowStatus contains DBROWSTATUS_E_PERMISSIONDENIED. If the rowset is in delayed update mode, this error might not be returned until IRowsetUpdate::Update is called.
n
The OLE DB consumer encountered a recoverable OLE DB providerspecific error, such as an RPC failure when transmitting the change to a remote server. The corresponding element of rgRowStatus contains DBROWSTATUS_E_FAIL.
E_FAIL This return code shows that an OLE DB provider-specific error occurred. E_INVALIDARG This return code states that rghRows was a null pointer and cRows was greater than or equal to one. E_UNEXPECTED This return code specifies that ITransaction::Commit or ITransaction:: Abort was called and the object is in a zombie state. DB_E_BADCHAPTER This return code indicates that the rowset was chaptered and hChapter was invalid, or the rowset was single-chaptered and the specified chapter was not the currently open chapter. The OLE DB consumer must use the currently open chapter or release the currently open chapter before specifying a new chapter. DB_E_ERRORSOCCURRED This return code means that errors occurred while deleting all of the rows. Errors can occur for the reasons listed under DB_S_ERRORSOCCURRED. DB_E_NOTREENTRANT This return code shows that the OLE DB consumer called this method while it was processing a notification, and it is an error to call this method while processing the specified DBREASON value. DB_E_NOTSUPPORTED This return code states that the provider does not support this method.
IRowsetChange::InsertRow This method creates and initializes a new row. InsertRow creates a new row and initializes its columns. If phRow is not a null pointer, it then returns the handle of this row to the OLE DB consumer and sets its reference count to one. In delayed update mode, the row is created locally to the rowset and is transmitted to the data source only when IRowsetUpdate::Update is called. In
178
n Chapter Four—An OLE DB Primer immediate update mode, the row is immediately transmitted to the data source. To the OLE DB consumer, newly inserted rows are almost indistinguishable from other rows. For example, they can be deleted with DeleteRows and updated with SetData. However, methods that fetch rows might not be able to return them. Furthermore, they might not contain the correct values for computed columns, including bookmark columns on some OLE DB providers. The DBPROP_COLUMNRESTRICT and DBPROP_ROWRESTRICT properties affect how security is enforced and how security errors are returned. If DBPROP_COLUMNRESTRICT is VARIANT_TRUE, the OLE DB consumer might not have write permission on some columns. If the OLE DB consumer attempts to write to these columns, InsertRows returns a column status of DBSTATUS_E_PERMISSIONDENIED and a return code of DB_S_ERRORSOCCURRED. If the DBPROP_ROWRESTRICT property is VARIANT_TRUE, the OLE DB consumer might not have permission to insert some rows. If the OLE DB consumer attempts to insert one of these rows, InsertRows returns a code of DB_SEC_E_PERMISSIONDENIED and no new row is created. When a row is created, initialization proceeds in an orderly fashion: n
The OLE DB provider sets all columns to their default values. If there is no default value and the column is nullable, it sets the column to NULL. If the column is non-nullable, it sets the column status to DBSTATUS_E_ UNAVAILABLE. If the OLE DB provider is unable or unwilling to determine the default value of a column or whether that column is nullable, it sets the column status to DBSTATUS_E_UNAVAILABLE; the OLE DB provider might be unwilling to determine default values and nullability if doing so requires a call to the data source. If the column status is DBSTATUS_E_UNAVAILABLE, the OLE DB consumer can still send this value to the data source to use the default. In this case, the default is available after the insertion is transmitted to the data source. To see the default, the OLE DB consumer must call GetLastVisibleData or RefreshVisibleData. However, if there is no default for the column and it is non-nullable, this will cause a schema violation.
n
The OLE DB provider then calls IRowsetNotify::OnRowChange with DBREASON_ROW_INSERT if any OLE DB consumer of the rowset is using notifications. This serves as a hook allowing, among other things, more complex nondeclarative default values to be set in the row.
n
InsertRow does not further modify the column values if the OLE DB accessor is a null accessor; it returns the handle to the newly created row. The OLE DB provider uses the OLE DB accessor, if it is not a null accessor, to set columns with the values provided by the OLE DB consumer in *pData. During this process, OLE DB provider does not generate notifications like it does when setting data in SetData. This prevents, for example,
Chapter Four—An OLE DB Primer n
179
DBREASON_COLUMN_SET notifications from being generated for a row that is not yet properly constructed. The OLE DB provider is not required to compute the value of computed columns. If the OLE DB provider does not compute the value of these columns but lets the data source do so, then the computed value is not available until after the change is transmitted to the data source—that is, after InsertRow is called in immediate update mode or after IRowsetUpdate::Update if InsertRow is called in delayed update mode. To retrieve the computed value, the OLE DB consumer calls RefreshVisibleData or GetLastVisibleData in IRowsetRefresh. Note that bookmark columns are often computed, such as when the bookmark is the primary key or is a ROWID assigned by the data source. Domain and schema validation is enforced as it is with SetData. If InsertRow returns an error, it does not create a new row. It is not recommended to use null accessors because some OLE DB providers may not be able to determine default values and may not be able to identify a row inserted with all default values. It has the following syntax: HRESULT InsertRow ( HCHAPTER hChapter, HACCESSOR hAccessor, void * pData, HROW * phRow);
It has the following parameters: hChapter [in] This parameter gives the chapter handle. For nonchaptered rowsets, hChapter is ignored. hAccessor [in] This parameter shows the handle of the OLE DB accessor to use. If hAccessor is a null accessor (that is, an OLE DB accessor for which cBindings in IAccessor::CreateAccessor was zero), then pData is ignored and the rows are initialized as specified above. Thus, the role of a null accessor is to construct a default row; it is a convenient way for an OLE DB consumer to obtain a handle for a new row without having to set any values in that row initially. pData [in] This parameter provides a pointer to memory containing the new data values, at offsets that correspond to the bindings in the OLE DB accessor. phRow [out] This parameter indicates a pointer to memory in which to return the handle of the new row. If this is a null pointer, then no reference count is
180
n Chapter Four—An OLE DB Primer held on the row. OLE DB consumers should set this to null if they do not require the ability to make further changes to, or retrieve data from, the newly inserted row. Whether or not default or computed values from the server are available when calling GetData for this row handle depends on the setting of the DBPROP_SERVERDATAONINSERT. If InsertRow returns an error, and *phRow is not a null pointer on input, *phRow is set to null on output and no row handle is returned. Note
Passing in a null pointer for *phRow, or releasing the row handle returned in *phRow, does not release the row until the change is transmitted to the data source. If DBPROP_CANHOLDROWS is VARIANT_FALSE and the rowset is in deferred update mode, then in addition to freeing any reference counts on the row, the OLE DB consumer must call IRowsetUpdate::Update in order to transmit the pending change to the data source before attempting to insert or retrieve any additional rows.
It has the following return codes: S_OK This return code means the method succeeded. The status of all columns bound by the accessor is set to DBSTATUS_S_OK or DBSTATUS_S_ ISNULL. DB_S_ERRORSOCCURRED This return code shows that an error occurred while setting data for one or more columns, but data was successfully set for at least one column. To determine the columns for which values were invalid, the consumer checks the status values. E_FAIL This return code indicates that an OLE DB provider-specific error occurred. E_INVALIDARG This return code specifies that pData was a null pointer and hAccessor was not a null accessor. E_OUTOFMEMORY This return code states that the OLE DB provider was unable to allocate sufficient memory in which to instantiate the row. E_UNEXPECTED This return code means ITransaction::Commit or ITransaction::Abort was called and the object is in a zombie state. DB_E_ABORTLIMITREACHED This return code shows the rowset was in immediate update mode and the row was not inserted due to reaching a limit on the server, such as a query execution timing out.
Chapter Four—An OLE DB Primer n
181
DB_E_BADACCESSORHANDLE This return code indicates that hAccessor was invalid. DB_E_BADACCESSORTYPE This return code provides that the specified OLE DB accessor was not a row accessor or was a reference accessor. DB_E_BADCHAPTER This return code shows that the rowset was chaptered and hChapter was invalid, or the rowset was single-chaptered and the specified chapter was not the currently open chapter. The OLE DB consumer must use the currently open chapter or release the currently open chapter before specifying a new chapter. DB_E_CANCELED This return code specifies that the insertion was canceled during notification. The row was not inserted. DB_E_CANTCONVERTVALUE This return code means that the data value for one or more columns couldn’t be converted for reasons other than sign mismatch or data overflow, and the OLE DB provider was unable to determine which columns couldn’t be converted. OLE DB providers that can detect which columns could not be converted return DB_S_ERRORSOCCURRED and set the status flag for those columns to DBSTATUS_E_CANTCONVERTVALUE. DB_E_DATAOVERFLOW This return code shows that conversion failed because the data value for one or more columns overflowed the type used by the OLE DB provider and the OLE DB provider was unable to determine which columns caused the overflow. OLE DB providers that can detect which columns caused the overflow return DB_S_ERRORSOCCURRED and set the status flag for the columns in violation to DBSTATUS_E_DATAOVERFLOW. DB_E_ERRORSOCCURRED This return code indicates that an error occurred while setting data for one or more columns and data was not successfully set for any columns. To determine the columns for which values were invalid, the OLE DB consumer checks the status values. DB_E_INTEGRITYVIOLATION This return code specifies that the data violated the integrity constraints for one or more columns of the rowset and the OLE DB provider was unable to determine which columns violated the integrity constraints. OLE DB providers that can detect which columns violated the integrity constraints return DB_S_ERRORSOCCURRED and set the status flag for the columns in violation to DBSTATUS_E_INTEGRITYVIOLATION. DB_E_MAXPENDCHANGESEXCEEDED This return code indicates that the number of rows that have pending
182
n Chapter Four—An OLE DB Primer changes has exceeded the limit specified by the DBPROP_MAXPENDINGROWS property. DB_E_NOTREENTRANT This return code means the OLE DB provider called a method from IRowsetNotify in the OLE DB consumer and the method has not yet returned. DB_E_NOTSUPPORTED This return code shows that the OLE DB provider does not support this method. DB_E_ROWLIMITEXCEEDED This return code states that creating another row would have exceeded the total number of active rows supported by the rowset. DB_E_ROWSNOTRELEASED This return code indicates that the OLE DB consumer attempted to insert a new row before releasing previously retrieved row handles or transmitting pending changes to the data source, and DBPROP_CANHOLDROWS is VARIANT_FALSE. DB_SEC_E_PERMISSIONDENIED This return code provides that the OLE DB consumer did not have sufficient permission to insert a new row. This error can be returned only if the value of the DBPROP_ROWRESTRICT property is VARIANT_TRUE. If the rowset is in delayed update mode, this error might not be returned until IRowsetUpdate::Update is called. If this method performs deferred accessor validation and that validation takes place before any data is transferred, it can also return any of the following return codes for the applicable reasons listed in the corresponding DBBINDSTATUS values in IAccessor::CreateAccessor: E_NOINTERFACE DB_E_BADBINDINFO DB_E_BADORDINAL DB_E_BADSTORAGEFLAGS DB_E_UNSUPPORTEDCONVERSION
IRowsetChange::SetData This method sets data values in one or more columns in a row. SetData sets data values in one or more columns in a row. In delayed update mode, these changes are buffered locally in the rowset and are transmitted to the data source only when IRowsetUpdate::Update is called. In immediate update mode, the changes are immediately transmitted to the data source. If a computed column depends on a column that is changed with SetData, the OLE DB provider is not required to compute the new value of the computed
Chapter Four—An OLE DB Primer n
183
column. If the OLE DB provider computes the new value, it sends a notification to OLE DB consumer. If the OLE DB provider does not compute the new value but lets the data source do so, then the computed value is not available until after the change is transmitted to the data source—that is, after SetData is called in immediate update mode or after IRowsetUpdate::Update if SetData is called in delayed update mode. To retrieve the computed value in this case, the OLE DB consumer calls RefreshVisibleData or GetLastVisibleData in IRowsetRefresh. Bookmark columns are often computed, such as when the bookmark is the primary key or is a ROWID assigned by the data source. If SetData changes a column that is used to order the rowset, the DBPROP_ IMMOBILEROWS property describes whether the row is moved based on its new value. If this property is VARIANT_TRUE, the row is not moved. If this property is VARIANT_FALSE, the row is moved. If the rowset is not ordered, then the position of updated rows is not changed. Note that, if the rowset is built on a set of key columns (typically a rowset for which DBPROP_OTHERUPDATEDELETE is VARIANT_TRUE but DBPROP_ OTHERINSERT is VARIANT_FALSE), changing the value of the key column is generally equivalent to deleting the current row and inserting a new one. Thus, the row may appear to move or even disappear from the rowset (if DBPROP_OWNINSERT is VARIANT_FALSE), even though the DBPROP_ IMMOBILEROWS property is VARIANT_TRUE. When the OLE DB consumer passes a pointer to a storage object to the SetData method, SetData replaces the data in the column with the data in the new storage object. If the OLE DB consumer wants only to delete the data in the column, it sets the column status to DBSTATUS_S_OK and passes a null pointer instead of a pointer to a storage object. If the rowset is in immediate update mode, storage object data is always transmitted immediately to the data source. If it is in delayed update mode, whether it is transmitted immediately or delayed depends on the DBPROP_ DELAYSTORAGEOBJECTS property. Although SetData can detect domain constraint and some table constraint schema violations, it is not required to do so. Such validation can be delayed until the changes are transmitted to the data source with Update or the transaction is committed with ITransaction::Commit. This delay is often necessary because of dependencies on values in other columns or tables. SetData cannot be called for rows with pending or transmitted deletes. The DBPROP_COLUMNRESTRICT and DBPROP_ROWRESTRICT properties affect how security is enforced and how security errors are returned. If DBPROP_ COLUMNRESTRICT is VARIANT_TRUE, the OLE DB consumer might not have write permission on some columns. If the OLE DB consumer attempts to write to these columns, SetData returns a column status of DBSTATUS_E_ PERMISSIONDENIED and a return code of DB_S_ERRORSOCCURRED. If the
184
n Chapter Four—An OLE DB Primer DBPROP_ROWRESTRICT property is VARIANT_TRUE, the OLE DB consumer might not have permission to update some rows. If the OLE DB consumer attempts to update one of these rows, SetData returns a code of DB_SEC_E_PERMISSIONDENIED and no data is set. If any OLE DB consumer of the rowset is using notifications, the OLE DB provider sends notifications. These notifications can be vetoed, in which case the OLE DB provider sends the DBEVENTPHASE_FAILEDTODO phase of the notification. When the OLE DB consumer then calls Update, if the update is in delayed mode, the OLE DB provider does not send additional DBREASON_ COLUMN_SET notifications for the rows. However, if Update computes the value of computed columns, it sends DBREASON_COLUMN_RECALCULATED notifications. In this case, the OLE DB provider must either be prepared to undo all pending changes for the row, and return DBREASON_ROW_UNDOCHANGE, or set the fCantDeny flag to TRUE. The following sequence of notifications occurs for a rowset operating in delayed update mode: n
If the row is being changed for the first time since it was created or changes were transmitted, the OLE DB provider sends the DBEVENTPHASE_OKTODO and DBEVENTPHASE_ABOUTTODO phases of the DBREASON_ROW_FIRSTCHANGE notification. The OLE DB provider sends the DBEVENTPHASE_OKTODO, DBEVENTPHASE_ABOUTTODO, and DBEVENTPHASE_SYNCHAFTER phases of the DBREASON_COLUMN_SET notification, in that order, provided none of the listeners veto any of the phases. The notification covers all the columns defined by the accessor used in the call to SetData.
n
The OLE DB provider sends the DBEVENTPHASE_SYNCHAFTER phase of the DBREASON_ROW_FIRSTCHANGE notification, assuming the OLE DB provider sent the earlier phases of this notification.
n
The OLE DB provider sends the DBEVENTPHASE_DIDEVENT phase of the DBREASON_COLUMN_SET notification.
n
The OLE DB provider sends the DBEVENTPHASE_DIDEVENT phase of the ROW_FIRSTCHANGE notification.
It has the following syntax: HRESULT SetData ( HROW hRow, HACCESSOR hAccessor, void * pData);
Chapter Four—An OLE DB Primer n
185
It has the following parameters: hRow [in] This parameter gives the handle of the row in which to set data. hAccessor [in] This parameter shows the handle of the OLE DB accessor to use. If hAccessor is the handle of a null accessor (cBindings in IAccessor::CreateAccessor was zero), then SetData does not set any data values. pData [in] This parameter provides a pointer to memory containing the new data values, at offsets that correspond to the bindings in the OLE DB accessor. It has the following return codes: S_OK This return code means the method succeeded. The status of all columns bound by the OLE DB accessor is set to DBSTATUS_S_OK or DBSTATUS_ S_ISNULL DB_S_ERRORSOCCURRED This return code indicates that an error occurred while setting data for one or more columns, but data was successfully set for at least one column. To determine the columns for which data was returned, the OLE DB consumer checks the status values. DB_S_MULTIPLECHANGES This return code shows that the rowset was in immediate update mode and updating the row caused more than one row to be updated in the data source. This return code takes precedence over DB_S_ERRORSOCCURRED. That is, if the conditions described here and in those described in DB_S_ERRORSOCCURRED both occur, the OLE DB provider returns this code. When the OLE DB consumer receives this return code, it should also check for the conditions described in DB_S_ERRORSOCCURRED. E_FAIL This return code means that an OLE DB provider-specific error occurred. E_INVALIDARG This return code specifies that *pData was a null pointer and the OLE DB accessor was not a null accessor. E_UNEXPECTED This return code states that ITransaction::Commit or ITransaction::Abort was called and the object is in a zombie state. DB_E_ABORTLIMITREACHED This return code indicates that the rowset was in immediate update mode and the row was not updated due to reaching a limit on the server, such as a query execution timing out.
186
n Chapter Four—An OLE DB Primer DB_E_BADACCESSORHANDLE This return code indicates that hAccessor was invalid. DB_E_BADACCESSORTYPE This return code states that the specified OLE DB accessor was not a row accessor or was a reference accessor. DB_E_BADROWHANDLE This return code shows that hRow was invalid. DB_E_CANCELED This return code specifies that the change was canceled during notification. No columns are changed. DB_E_CANTCONVERTVALUE This return code indicates that the data value for one or more columns couldn’t be converted for reasons other than sign mismatch or data overflow, and the OLE DB provider was unable to determine which columns couldn’t be converted. OLE DB providers that can detect which columns could not be converted return DB_S_ERRORSOCCURRED and set the status flag for the columns that couldn’t be converted to DBSTATUS_E_ CANTCONVERTVALUE. DB_E_CONCURRENCYVIOLATION This return code provides that the rowset was using optimistic concurrency and the value of a column has been changed since the containing row was last fetched or resynchronized. SetData returns this error only when the rowset is in immediate update mode. DB_E_DATAOVERFLOW This return code states that conversion failed because the data value for one or more columns overflowed the type used by the OLE DB provider and the OLE DB provider was unable to determine which columns caused the overflow. OLE DB providers that can detect which columns caused the overflow return DB_S_ERRORSOCCURRED and set the status flag for the columns in violation to DBSTATUS_E_DATAOVERFLOW. DB_E_DELETEDROW This return code shows hRow referred to a row with a pending delete or for which a deletion had been transmitted to the data source. DB_E_ERRORSOCCURRED This return code means that an error occurred while setting data for one or more columns and data was not successfully set for any columns. To determine the columns for which values were invalid, the OLE DB consumer checks the status values. DB_E_INTEGRITYVIOLATION This return code specifies that the data violated the integrity constraints for one or more columns of the rowset and the OLE DB provider was unable to determine which columns violated the integrity constraints. OLE DB providers that can detect which columns violated the integrity
Chapter Four—An OLE DB Primer n
187
constraints return DB_S_ERRORSOCCURRED and set the status flag for the columns in violation to DBSTATUS_E_INTEGRITYVIOLATION. DB_E_MAXPENDCHANGESEXCEEDED This return code states that the number of rows that have pending changes has exceeded the limit specified by the DBPROP_MAXPENDINGROWS property. DB_E_NEWLYINSERTED This return code indicates that DBPROP_CHANGEINSERTEDROWS was VARIANT_FALSE and hRow referred to a row for which the insertion has been transmitted to the data source. DB_E_NOTREENTRANT This return code means that the OLE DB consumer called this method while it was processing a notification; it is an error to call this method while processing the specified DBREASON value. DB_E_NOTSUPPORTED This return code shows that the OLE DB provider does not support this method. DB_SEC_E_PERMISSIONDENIED This return code indicates that the OLE DB consumer did not have sufficient permission to update the row. This error can be returned only if the value of the DBPROP_ROWRESTRICT property is VARIANT_TRUE. If the rowset is in delayed update mode, this error might not be returned until IRowsetUpdate::Update is called. If this method performs deferred OLE DB accessor validation and that validation takes place before any data is transferred, it can also return any of the following return codes for the applicable reasons listed in the corresponding DBBINDSTATUS values in IAccessor::CreateAccessor: E_NOINTERFACE DB_E_BADBINDINFO DB_E_BADORDINAL DB_E_BADSTORAGEFLAGS DB_E_UNSUPPORTEDCONVERSION
IRowsetUpdate IRowsetUpdate enables OLE DB consumers to delay the transmission of changes made with IRowsetChange to the data source. This interface also enables OLE DB consumers to undo changes before transmission. IRowsetUpdate is optional. Rowsets that want to allow delayed transmission of changes and to support undo capabilities implement IRowsetUpdate. Generally, this requires the
188
n Chapter Four—An OLE DB Primer rowset to locally cache a copy of each row as it is changed with IRowsetChange::SetData. If IRowsetUpdate is not requested as a property of the rowset, it must not be exposed by the rowset. If IRowsetUpdate is exposed on the rowset, then changes made through IRowsetChange are buffered in the rowset and not transmitted to the data source until IRowsetUpdate::Update is called; this is known as delayed update mode. If IRowsetUpdate is not exposed on the rowset, then changes made through IRowsetChange are immediately transmitted to the data source; this is known as immediate update mode. The OLE DB consumer calls the methods in IRowsetChange to update, delete, and insert rows. The rowset buffers these changes. When the OLE DB consumer is ready to transmit these changes to the data source, it calls Update. Before calling Update, the OLE DB consumer can back out any changes with Undo. Method GetOriginalData
GetPendingRows GetRowStatus Undo Update
Description This method gets the data most recently fetched from or transmitted to the data source; does not get values based on pending changes. This method transmits any changes made to a row since it was last fetched or since Update was called for it. This method returns the status of rows. This method undoes any changes made to a row since it was last fetched or since Update was called for it. This method returns a list of rows with pending changes.
IRowsetUpdate::GetOriginalData This method gets the data most recently fetched from or transmitted to the data source; it does not get values based on pending changes. This method makes no logical change to the state of the object. GetOriginalData retrieves the values the row contained when it was last fetched or had changes transmitted to the data source. It does not retrieve any pending changes or changes made by other rowsets in the same transaction or other applications in other transactions. It also does not affect the current values for the row. To implement GetOriginalData, the OLE DB provider usually caches the original values just before making a change to a row and discards the cached values when Undo or Update is called for the row. If hRow refers to a pending insert row, GetOriginalData returns the column defaults and, for columns without defaults or for which the OLE DB provider was unable to determine the defaults, NULLs.
Chapter Four—An OLE DB Primer n
189
Whether GetOriginalData can retrieve the original value of an OLE object that is stored in a column, or a storage object that is created over a BLOB after changes have been made to that OLE object or BLOB, depends on the value of the DBPROP_DELAYSTORAGEOBJECTS rowset property. There is a difference between calling GetOriginalData and calling GetLastVisibleData. In a delayed update mode, if data is changed by another OLE DB consumer, different OLE DB consumers may retrieve different data. For example, the value in Column 1 is X. In a delayed update mode, OLE DB consumer A changes the value in that column to Y but does not transmit this action to the data source. OLE DB consumer B then changes the value in Column 1 to Z. If OLE DB consumer A calls GetOriginalData, it gets X. However, if it calls GetLastVisibleData, using a dirty read, it will retrieve Z. GetOriginalData does not enforce any security restrictions. The OLE DB provider must not create a rowset that includes columns for which the OLE DB consumer does not have read privileges, so GetOriginalData never encounters problems accessing the data for a column. The rowset can contain columns to which the OLE DB consumer does not have write permission if DBPROP_ COLUMNRESTRICT is VARIANT_TRUE. The methods that fetch rows must not return the handles of rows for which the OLE DB consumer does not have read privileges, so GetOriginalData never encounters problems accessing a row. Such rows might exist if the DBPROP_ROWRESTRICT property is VARIANT_TRUE. If GetOriginalData fails, the memory to which *pData points is not freed but its contents are undefined. If, before GetOriginalData failed, the OLE DB provider allocated any memory for return to the OLE DB consumer, the OLE DB provider frees this memory and does not return it to the OLE DB consumer. It has the following syntax: HRESULT GetOriginalData HROW HACCESSOR void *
( hRow, hAccessor, pData);
It has the following parameters: hRow [in] This parameter gives the handle of the row for which to get the original data. This can be the handle of a row with a pending change or delete. hAccessor [in] This parameter shows the handle of the OLE DB accessor to use. If hAccessor is the handle of a null accessor (cBindings in IAccessor:: CreateAccessor was zero), then GetOriginalData does not get any data values.
190
n Chapter Four—An OLE DB Primer pData [out] This parameter provides a pointer to a buffer in which to return the data. The OLE DB consumer allocates memory for this buffer. It has the following return codes: S_OK This return code means the method succeeded. The status of all columns bound by the OLE DB accessor is set to DBSTATUS_S_OK, DBSTATUS_S_ ISNULL, or DBSTATUS_S_TRUNCATED. DB_S_ERRORSOCCURRED This return code shows that an error occurred while returning data for one or more columns, but data was successfully returned for at least one column. To determine the columns for which data was returned, the consumer checks the status values. E_FAIL This return code indicates that an OLE DB provider-specific error occurred. E_INVALIDARG This return code provides that *pData was a null pointer and hAccessor was not a null accessor. E_UNEXPECTED This return code specifies that ITransaction::Commit or ITransaction:: Abort was called and the object is in a zombie state. DB_E_BADACCESSORHANDLE This return code means that hAccessor was invalid. It is possible for a reference accessor or an OLE DB accessor that has a binding that uses OLE DB provider-owned memory to be invalid for use with this method, even if the OLE DB accessor is valid for use with IRowset::GetData or IRowsetChange::SetData. DB_E_BADACCESSORTYPE This return code shows that the specified accessor was not a row accessor. DB_E_BADROWHANDLE This return code states that hRow was invalid. DB_E_DELETEDROW This return code provides that hRow referred to a row for which a deletion had been transmitted to the data source. DB_E_ERRORSOCCURRED This return code indicates that errors occurred while returning data for all columns. To determine what errors occurred, the OLE DB consumer checks the status values.
Chapter Four—An OLE DB Primer n
191
DB_E_NOTREENTRANT This return code means the OLE DB provider called a method from IRowsetNotify in the OLE DB consumer that had not yet returned, and the OLE DB provider does not support reentrancy in this method. If this method performs deferred accessor validation and that validation takes place before any data is transferred, it can also return any of the following return codes for the applicable reasons listed in the corresponding DBBINDSTATUS values in IAccessor::CreateAccessor: E_NOINTERFACE DB_E_BADBINDINFO DB_E_BADORDINAL DB_E_BADSTORAGEFLAGS DB_E_UNSUPPORTEDCONVERSION
IRowsetUpdate::GetPendingRows This method returns a list of rows with pending changes. The GetPendingRows method increments the reference of each row handle it returns in *prgPendingRows. The OLE DB consumer must call ReleaseRows for these rows. If multiple changes are made to a single row, GetPendingRows returns the status as follows. n
If IRowsetChange::SetData is called for a pending insert row, the row is still considered a pending insert row.
n
If IRowsetChange::DeleteRows is called for a pending update row, the row is considered a pending delete row.
n
If IRowsetChange::DeleteRows is called for a pending insert row, the row is considered a transmitted delete row; such rows are not returned by GetPendingRows.
n
If IRowsetRefresh::RefreshVisibleData is called for a pending insert row, the row is still considered a pending insert row.
It has the following syntax: HRESULT GetPendingRows ( HCHAPTER hChapter, DBPENDINGSTATUS dwRowStatus, ULONG * pcPendingRows, HROW ** prgPendingRows, DBPENDINGSTATUS ** prgPendingStatus);
It has the following parameters: hChapter [in] This parameter gives the chapter handle. For nonchaptered rowsets, hChapter is ignored.
192
n Chapter Four—An OLE DB Primer dwRowStatus [in] This parameter indicates whether consumers want rows with pending updates, deletes, or inserts. The following DBPENDINGSTATUS values are valid and can be combined: DBPENDINGSTATUS_NEW DBPENDINGSTATUS_CHANGED DBPENDINGSTATUS_DELETED pcPendingRows [out] This parameter provides a pointer to memory in which to return the number of rows with pending changes. If this is a null pointer, *prgPendingRows and *prgPendingStatus are ignored. This is useful when the OLE DB consumer wants to check the returned return code to determine whether there are any pending changes. If an error occurs, *pcPendingRows is set to zero. prgPendingRows [out] This parameter shows a pointer to memory in which to return an array of handles of rows with pending changes. If this is a null pointer, no row handles are returned. The rowset allocates memory for the row handles and returns the address to this memory; the OLE DB consumer releases this memory with IMalloc::Free when it no longer needs the row handles. This argument is ignored if *pcPendingRows is a null pointer. If *pcPendingRows is zero on output or an error occurs, the OLE DB provider does not allocate any memory and ensures that *prgPendingRows is a null pointer on output. prgPendingStatus [out] This parameter gives a pointer to memory in which to return an array of DBPENDINGSTATUS values. These values are in one-to-one correspondence with the row handles returned in *prgPendingRows and indicate the type of pending change. If this is a null pointer, no status information is returned. The rowset allocates memory for the row statuses and returns the address to this memory; the OLE DB consumer releases this memory with IMalloc::Free when it no longer needs the row statuses. This argument is ignored if *pcPendingRows is a null pointer. If *pcPendingRows is zero on output or an error occurs, the OLE DB provider does not allocate any memory and ensures that *prgPendingStatus is a null pointer on output. It has the following return codes: S_OK This return code means the method succeeded and changes were pending.
Chapter Four—An OLE DB Primer n
193
S_FALSE This return code shows that the method succeeded and no changes were pending. E_FAIL This return code indicates that an OLE DB provider-specific error occurred. E_INVALIDARG Thisreturn code states that dwRowStatus was DBPENDINGSTATUS_ INVALIDROW, DBPENDINGSTATUS_UNCHANGED, or any other invalid value. E_OUTOFMEMORY This return code means the OLE DB provider was unable to allocate sufficient memory in which to return the handles of rows with pending changes or the array of DBPENDINGSTATUS values. E_UNEXPECTED This return code indicates that ITransaction::Commit or ITransaction:: Abort was called and the object is in a zombie state. DB_E_BADCHAPTER This return code specifies that the rowset was chaptered and hChapter was invalid, or the rowset was single-chaptered and the specified chapter was not the currently open chapter. The OLE DB consumer must use the currently open chapter or release the currently open chapter before specifying a new chapter.
IRowsetUpdate::GetRowStatus This method returns the status of rows. If multiple changes are made to a single row, GetRowStatus returns the status as described in GetPendingRows. If IRowsetChange::DeleteRows is called for a pending insert row, a status of DBPENDINGSTATUS_INVALIDROW is returned for the row. It has the following syntax: HRESULT GetRowStatus( HCHAPTER hChapter, ULONG cRows, const HROW rghRows[], DBPENDINGSTATUS rgPendingStatus[]);
It has the following parameters: hChapter [in] This parameter gives the chapter handle. For nonchaptered rowsets, hChapter is ignored. cRows [in] This parameter provides the count of elements in rghRows and rgPending-
194
n Chapter Four—An OLE DB Primer Status. If this value is zero, GetRowStatus ignores rghRows and rgPendingStatus and does not return any status. rghRows [in] This parameter indicates an array of handles of rows for which to return the status. This array is allocated by the OLE DB consumer and must not be freed by the OLE DB provider. rgPendingStatus [out] This parameter shows an array of DBPENDINGSTATUS values. GetRowStatus returns the DBPENDINGSTATUS values for all rows specified in the rghRows array. The DBPENDINGSTATUS_INVALIDROW value is used to indicate an invalid row handle.The rgPendingStatus array is allocated, but not necessarily initialized, by the caller and must not be freed by the OLE DB provider. It has the following return codes: S_OK This return code means the method succeeded. Status values were successfully retrieved for all rows and each element of rgPendingStatus is set to DBPENDINGSTATUS_NEW, DBPENDINGSTATUS_CHANGED, DBPENDINGSTATUS_DELETED, or DBPENDINGSTATUS_UNCHANGED. DB_S_ERRORSOCCURRED This return code shows an error occurred while getting the status of a row, but the status of at least one row was successfully retrieved. Successes can occur for the reasons listed under S_OK. An error can occur if a row handle in rghRows was invalid. The corresponding element of rgRowStatus contains DBPENDINGSTATUS_INVALIDROW. E_FAIL This return code indicates that an OLE DB provider-specific error occurred. E_INVALIDARG This return code states that cRows was greater than zero and either rghRows or rgPendingStatus was a null pointer. E_UNEXPECTED This return code provides that ITransaction::Commit or ITransaction:: Abort was called and the object is in a zombie state. DB_E_BADCHAPTER This return code shows that the rowset was chaptered and hChapter was invalid, or the rowset was single-chaptered and the specified chapter was not the currently open chapter. The OLE DB consumer must use the currently open chapter or release the currently open chapter before specifying a new chapter.
Chapter Four—An OLE DB Primer n
195
DB_E_ERRORSOCCURRED This return code means that errors occurred getting the status of all of the rows. Errors can occur for the reason listed under DB_S_ERRORSOCCURRED.
IRowsetUpdate::Undo This method undoes any changes made to a row since it was last fetched or Update was called for it. Undo backs any pending changes out of the specified rows and clears their pending change status. That is, it undoes any changes made to the row since it was last fetched or Update was called for the row. If multiple changes were made to a row, Undo undoes all of these changes; the OLE DB provider does not remember intermediate steps. If Update is called for a row immediately after Undo is called for the row, Update does not transmit any changes to the data source for the row. It has the following syntax: HRESULT Undo ( HCHAPTER ULONG const HROW ULONG * HROW ** DBROWSTATUS **
hChapter, cRows, rghRows[], pcRows, prgRows, prgRowStatus);
It has the following parameters: hChapter [in] This parameter gives the chapter handle. For nonchaptered rowsets, hChapter is ignored. cRows [in] This parameter shows the count of rows to undo. If cRows is nonzero, Undo undoes all pending changes in the rows specified in rghRows. If cRows is zero, Undo ignores rghRows and undoes all pending changes to all rows in the rowset. rghRows [in] This parameter provides an array of handles of the rows to undo. Elements of this array can refer to rows with pending deletes. If rghRows includes a row that does not have any pending changes, Undo does not return an error. Instead, the row remains unchanged from its original state—which is the intention of Undo—and its row status is set to DBROWSTATUS_S_OK. If rghRows includes a duplicate row, Undo treats the occurrences as if the row were passed to the method two times sequentially. Thus, on the first occurrence, Undo undoes any pending changes. On the second
196
n Chapter Four—An OLE DB Primer occurrence, Undo treats the row as a row with no pending changes and leaves it in its current (now original) state. pcRows [out] This parameter gives a pointer to memory in which to return the number of rows Undo attempted to undo. If this is a null pointer, no count of rows is returned. If the method fails with an error other than DB_E_ERRORSOCCURRED, *pcRows is set to zero. prgRows [out] This parameter shows a pointer to memory in which to return an array containing the handles of all the rows Undo attempted to undo. If rghRows is not a null pointer, then the elements of this array are in one-to-one correspondence with those in rghRows. For example, if a row appears twice in rghRows, it appears twice in *prgRows. When rghRows is not a null pointer, Undo does not add to the reference count of the rows it returns in *prgRows; the reason is that the consumer already has these row handles. If rghRows is a null pointer, the elements of this array are the handles of all the rows that had pending changes, regardless of whether Undo was successful at undoing those changes. The OLE DB consumer checks *prgRowStatus to determine which rows were undone. When rghRows is a null pointer, Undo adds to the reference count of the rows it returns in *prgRows; the reason is that the OLE DB consumer is not guaranteed to already have these row handles. A side effect of this is that rows with a reference count of zero but with pending changes at the time Undo is called are brought back into existence; that is, their reference count is increased to 1 and they must be rereleased. The rowset allocates memory for the array of handles and returns the address to this memory; the OLE DB consumer releases this memory with IMalloc::Free when it no longer needs the handles. This argument is ignored if *pcRows is a null pointer, and must not be a null pointer otherwise. If *pcRows is zero on output or the method fails with an error other than DB_E_ERRORSOCCURRED, the OLE DB provider does not allocate any memory and ensures that *prgRows is a null pointer on output. prgRowStatus [out] This parameter indicates a pointer to memory in which to return an array of row status values. The elements of this array correspond one-to-one with the elements of rghRows (if rghRows is not a null pointer) or *prgRows (if rghRows is a null pointer). If no errors occur while undoing a row, the corresponding element of *prgRowStatus is set to DBROWSTATUS_S_OK. If an error occurs while undoing a row, the corresponding element is set as specified in DB_S_ERRORSOCCURRED. If *prgRowStatus is a null pointer, no row status values are returned.
Chapter Four—An OLE DB Primer n
197
The rowset allocates memory for the row status values and returns the address to this memory; the OLE DB consumer releases this memory with IMalloc::Free when it no longer needs the row status values. This argument is ignored if cRows is zero and *pcRows is a null pointer. If Undo does not attempt to undo any rows or the method fails with an error other than DB_E_ERRORSOCCURRED, the OLE DB provider does not allocate any memory and ensures that *prgRowStatus is a null pointer on output. It has the following return codes: S_OK This return code means the method succeeded. The changes in all rows were successfully undone. In this case, the corresponding element of *prgRowStatus contains DBROWSTATUS_S_OK. DB_S_ERRORSOCCURRED This return code shows that an error occurred while undoing the changes in a row, but the changes in at least one row were successfully undone. Successes can occur for the reasons listed under S_OK. The following errors can occur: n
Undoing the changes in a row was canceled during notification. Changes made to the row were not undone and the corresponding element of *prgRowStatus contains DBROWSTATUS_E_CANCELED.
n
A row handle in rghRows referred to a row for which a delete had been transmitted to the data source. The corresponding element of *prgRowStatus contains DBROWSTATUS_E_DELETED.
n
A row handle in rghRows was invalid. The corresponding element of *prgRowStatus contains DBROWSTATUS_E_INVALID.
n
A row handle in rghRows referred to a row on which a storage object or OLE object was open. The corresponding element of *prgRowStatus contains DBROWSTATUS_E_OBJECTOPEN.
n
The OLE DB consumer encountered a recoverable, provider-specific error, such as an RPC failure when transmitting the change to a remote server. The corresponding element of *prgRowStatus contains DBROWSTATUS_E_FAIL.
E_FAIL This return code indicates an OLE DB provider-specific error occurred. E_INVALIDARG This return code states that cRows was not zero and rghRows was a null pointer, or *pcRows was not a null pointer and *prgRows was a null pointer.
198
n Chapter Four—An OLE DB Primer E_OUTOFMEMORY This return code shows the OLE DB provider was unable to allocate sufficient memory in which to return either the handles of the rows Undo attempted to undo or the array of row status values. E_UNEXPECTED This return code means that ITransaction::Commit or ITransaction::Abort was called and the object is in a zombie state. DB_E_BADCHAPTER This return code specifies that the rowset was chaptered and hChapter was invalid, or the rowset was single-chaptered and the specified chapter was not the currently open chapter. The OLE DB consumer must use the currently open chapter or release the currently open chapter before specifying a new chapter. DB_E_ERRORSOCCURRED This return code shows that errors occurred while undoing all of the rows. The OLE DB provider allocates memory for *prgRows and *prgRowStatus and the OLE DB consumer checks the values in *prgRowStatus to determine why the pending changes were not undone. The OLE DB consumer frees this memory when it no longer needs the information. Errors can occur for the reasons listed under DB_S_ERRORSOCCURRED. DB_E_NOTREENTRANT This return code indicates that the consumer called this method while it was processing a notification; it is an error to call this method while processing the specified DBREASON value. To implement Undo, the OLE DB provider generally caches the original values just before making a change to a row and discards the cached values when Undo or Update is called for the row. This same cache can be used by the GetOriginalData method to retrieve the original data for the row. If Undo is called for a row with a pending insert, the row is deleted from the rowset. That is, calls to IRowset::GetData or SetData for the row fail with DB_E_DELETEDROW. The OLE DB consumer must still call IRowset::ReleaseRows to release the row. Whether Undo can undo changes made to an OLE object stored in a column or to a storage object created over a BLOB depends on the value of the DBPROP_DELAYSTORAGEOBJECTS rowset property. If Undo is called for a row that has a reference count of zero and exists only because the row has a pending change, Undo releases the row and all its resources. The only exception to this is when the handle to the row is returned in *prgRows, in which case the reference count is set to one. The order in which Undo processes rows is OLE DB provider-specific. If Undo encounters an error, it continues processing rows until it has attempted to undo all specified rows, then returns the appropriate warning. Because Undo
Chapter Four—An OLE DB Primer n
199
is generally implemented by copying data from a cache of original data, such errors should be extremely rare and generally represent an OLE DB consumer programming error, such as passing an invalid row handle. If any OLE DB consumer of the rowset is using notifications, the OLE DB provider sends notifications that pending changes for the specified rows are being undone.
IRowsetUpdate::Update This method transmits any changes made to a row since it was last fetched or Update was called for it. Update transmits pending changes for the specified rows to the data source and clears their pending change status. That is, it transmits any changes made to the row since it was last fetched or Update was called for the row. The order in which Update processes rows is OLE DB provider-specific. If Update encounters an error, it continues processing rows until it has attempted to update all specified rows, then it returns the appropriate warning. This might leave the rowset in a state where changes have been transmitted for some rows but not others. When the OLE DB consumer repairs the cause of the error, it can call Update again and the OLE DB provider guarantees it will not transmit data to the data source for those rows for which data was already successfully transmitted. If any OLE DB consumer of the rowset is using notifications, the OLE DB provider sends notifications that pending changes for the specified rows are being transmitted to the data source. If Update is called for a row that has a reference count of zero and exists only because the row has a pending change, Update releases the row and all its resources. The only exception to this is when the handle to the row is returned in *prgRows, in which case the reference count is set to 1. If the OLE DB provider cached original values to implement GetOriginalData and Undo, it discards them as part of Update. If the rowset is released with IUnknown:: Release before Update is called, all pending changes are lost. It has the following syntax: HRESULT Update ( HCHAPTER ULONG const HROW ULONG * HROW ** DBROWSTATUS**
hChapter, cRows, rghRows[], pcRows, prgRows, prgRowStatus);
200
n Chapter Four—An OLE DB Primer It has the following parameters: hChapter [in] This parameter gives the chapter handle. For nonchaptered rowsets, hChapter is ignored. cRows [in] This parameter shows the count of rows to update. If cRows is nonzero, Update updates all pending changes in the rows specified in rghRows. If cRows is zero, Update ignores rghRows and updates all pending changes to all rows in the rowset. rghRows [in] This parameter provides an array of handles of the rows to update. If rghRows includes a row that does not have any pending changes, Update does not return an error. Instead, the row remains unchanged and hence has no pending changes after Update returns—which is the intention of Update—and the row status value associated with that row is DBROWSTATUS_S_OK. Furthermore, Update guarantees not to transmit any value for the row to the data source. If rghRows includes a duplicate row, Update behaves as follows. If the row handle is valid, no errors occur and *prgRowStatus contains DBROWSTATUS_S_OK for each occurrence. If the row handle is invalid, *prgRowStatus contains the appropriate error for each occurrence. pcRows [out] This parameter specifies a pointer to memory in which to return the number of rows Update attempted to update. If this is a null pointer, no count of rows is returned. If the method fails with an error other than DB_E_ ERRORSOCCURRED, *pcRows is set to zero. prgRows [out] This parameter gives a pointer to memory in which to return an array containing the handles of all the rows Update attempted to update. If rghRows is not a null pointer, then the elements of this array are in oneto-one correspondence with those in rghRows. For example, if a row appears twice in rghRows, it appears twice in *prgRows. When rghRows is not a null pointer, Update does not add to the reference count of the rows it returns in *prgRows; the reason is that the OLE DB consumer already has these row handles. If rghRows is a null pointer, the elements of this array are handles of all the rows that had pending changes, regardless of whether Update was successful at transmitting those changes to the data source. The OLE DB consumer checks *prgRowStatus to determine which rows were updated. When rghRows is a null pointer, Update adds to the reference count of the rows it returns in *prgRows; the reason is that the OLE DB consumer is not guaranteed to already have these row handles. A side effect of this is that rows with a reference count of zero, but with pending changes at the
Chapter Four—An OLE DB Primer n
201
time Update is called, are brought back into existence; that is, their reference count is increased to 1 and they must be rereleased. The rowset allocates memory for the array of handles and returns the address to this memory; the OLE DB consumer releases this memory with IMalloc:: Free when it no longer needs the handles. This argument is ignored if *pcRows is a null pointer, and must not be a null pointer otherwise. If *pcRows is zero on output or the method fails with an error other than DB_E_ERRORSOCCURRED, the OLE DB provider does not allocate any memory and ensures that *prgRows is a null pointer on output. prgRowStatus [out] This parameter indicates a pointer to memory in which to return an array of row status values. The elements of this array correspond one-to-one with the elements of rghRows (if rghRows is not a null pointer) or *prgRows (if rghRows is a null pointer). If no errors or warnings occur while updating a row, the corresponding element of *prgRowStatus is set to DBROWSTATUS_S_OK. If an error or warning occurs while updating a row, the corresponding element is set as specified in DB_S_ERRORSOCCURRED. If *prgRowStatus is a null pointer, no row status values are returned. The rowset allocates memory for the row status values and returns the address to this memory; the OLE DB consumer releases this memory with IMalloc::Free when it no longer needs the row status values. This argument is ignored if cRows is zero and *pcRows is a null pointer. If Update does not attempt to update any rows or the method fails with an error other than DB_E_ERRORSOCCURRED, the OLE DB provider does not allocate any memory and ensures that *prgRowStatus is a null pointer on output. It has the following return codes: S_OK This return code means the method succeeded. The changes in all rows were successfully updated. The following values can be returned in *prgRowStatus: n
The changes in the row were successfully updated. The corresponding element of *prgRowStatus contains DBROWSTATUS_S_OK.
n
Updating or deleting a single row caused more than one row to be updated or deleted in the data source.. The corresponding element of *prgRowStatus contains DBROWSTATUS_S_MULTIPLECHANGES.
DB_S_ERRORSOCCURRED This return code indicates that an error occurred while updating a row, but at least one row was successfully updated. Successes can occur for the reasons listed under S_OK. The following errors can occur:
202
n Chapter Four—An OLE DB Primer n
Updating a row was canceled during notification. The row was not updated and the corresponding element of *prgRowStatus contains DBROWSTATUS_E_CANCELED.
n
The rowset was using optimistic concurrency, a row was being updated or deleted, and the value of a column in that row has been changed since it was last fetched. The error in the corresponding element of *prgRowStatus contains DBROWSTATUS_E_CONCURRENCYVIOLATION.
n
An element of rghRows referred to a row for which a deletion had been transmitted to the data source. The error in the corresponding element of *prgRowStatus contains DBROWSTATUS_E_DELETED.
n
A row was being inserted or updated and a value specified for that row violated the integrity constraints for the column or table. The error in the corresponding element of *prgRowStatus contains DBROWSTATUS_E_INTEGRITYVIOLATION.
n
A row was being deleted, and doing so violated the integrity constraints for the column or table. The error in the corresponding element of *prgRowStatus contains DBROWSTATUS_E_INTEGRITYVIOLATION.
n
An element of rghRows was invalid. The error in the corresponding element of *prgRowStatus contains DBROWSTATUS_E_INVALID.
n
The OLE DB consumer did not have sufficient permission to update, delete, or insert a row. This error can be returned only if the value of the DBPROP_ROWRESTRICT property is VARIANT_TRUE. The error in the corresponding element of *prgRowStatus contains DBROWSTATUS_E_PERMISSIONDENIED.
n
The update, delete, or insert failed due to reaching a limit on the server, such as a query execution timing out. The error in the corresponding element of *prgRowStatus contains DBROWSTATUS_E_ LIMITREACHED.
n
A column value did not meet the schema requirements for the column. The error in the corresponding element of *prgRowStatus contains DBROWSTATUS_E_SCHEMAVIOLATION.
n
The values for two or more columns did not meet the multiplecolumn schema requirements for those columns. The error in the corresponding element of *prgRowStatus contains DBROWSTATUS_E_ SCHEMAVIOLATION.
n
A row was being inserted, no value was specified for a column, the column does not have a default, and the column is non-nullable. The error in the corresponding element of *prgRowStatus contains DBROWSTATUS_E_SCHEMAVIOLATION.
Chapter Four—An OLE DB Primer n n
203
The OLE DB consumer encountered a recoverable OLE DB providerspecific error, such as an RPC failure when transmitting the change to a remote server. The corresponding element of *prgRowStatus contains DBROWSTATUS_E_FAIL.
E_FAIL This return code specifies that a provider-specific error occurred. E_INVALIDARG This return code indicates that cRows was not zero and rghRows was a null pointer, or *pcRows was not a null pointer and prgRows was a null pointer on input. E_OUTOFMEMORY This return code shows that the provider was unable to allocate sufficient memory in which to return either the handles of the rows Update attempted to update or the array of row status values. E_UNEXPECTED This return code states that ITransaction::Commit or ITransaction::Abort was called and the object is in a zombie state. DB_E_BADCHAPTER This return code means that the rowset was chaptered and hChapter was invalid, or the rowset was single-chaptered and the specified chapter was not the currently open chapter. The OLE DB consumer must use the currently open chapter or release the currently open chapter before specifying a new chapter. DB_E_ERRORSOCCURRED This return code states that errors occurred while updating all of the rows. The OLE DB provider allocates memory for *prgRows and *prgRowStatus and the OLE DB consumer checks the values in *prgRowStatus to determine why the pending changes were not updated. The OLE DB consumer frees this memory when it no longer needs the information. Errors can occur for the reasons listed under DB_S_ERRORSOCCURRED. DB_E_NOTREENTRANT This return code indicates that the OLE DB provider called a method from IRowsetNotify in the consumer and the method has not yet returned.
ITransaction The ITransaction interface is used to commit, abort, and obtain status information about transactions. Method Abort Commit
Description This method aborts a transaction. This method commits a transaction.
204
n Chapter Four—An OLE DB Primer Method GetTransactionInfo
Description This method returns information about a transaction.
ITransaction::Abort This method aborts a transaction. The following table shows how the values of fRetaining and DBPROP_ABORTPRESERVE affect the rowset state and transaction mode. DBPROP_ABORT PRESERVE FALSE FALSE TRUE TRUE
fRetaining FALSE TRUE FALSE TRUE
Rowset State After Abort zombie zombie preserved preserved
Resulting Transaction / Mode of Session implicit / autocommit explicit / manual implicit / autocommit explicit / manual
It has the following syntax: HRESULT Abort( BOID * pboidReason, BOOL fRetaining, BOOL fAsync);
It has the following parameters: pboidReason [in] This parameter gives a pointer to a BOID that indicates why the transaction is being aborted. If this is a null pointer, no reason is provided. fRetaining [in] This parameter shows whether the abort is retaining or nonretaining. fAsync [in] This parameter states that when fAsync is TRUE, an asynchronous abort is performed and the caller must use ITransactionOutcomeEvents to learn the outcome of the transaction. It has the following return codes: S_OK This return code means the transaction was successfully aborted. XACT_S_ABORTING This return code shows that an abort operation was already in progress. This call was ignored. XACT_S_ASYNC This return code indicates that an asynchronous abort was specified. The abort operation has begun but its outcome is not yet known. When the
Chapter Four—An OLE DB Primer n
205
transaction is complete, notification will be sent by ITransactionOutcomeEvents. E_FAIL This return code shows that the transaction failed to abort for an unspecified reason. E_UNEXPECTED This return code states that an unexpected error occurred. The transaction status is unknown. XACT_E_ALREADYINPROGRESS This return code specifies that a commit operation was already in progress. This call was ignored. XACT_E_CANTRETAIN This return code provides that a retaining abort is not supported or a new unit of work could not be created. The abort succeeded and the session is in autocommit mode. XACT_E_CONNECTION_DOWN This return code shows that the connection to the transaction manager failed. The transaction state is unknown. XACT_E_INDOUBT This return code means the transaction status is in doubt. A communication failure occurred or a transaction manager or resource manager has failed. XACT_E_NOTRANSACTION This return code indicates that the transaction cannot be aborted because it already had been implicitly or explicitly committed or aborted. This call was ignored. XACT_E_NOTSUPPORTED This return code states that fAsync was TRUE on input and asynchronous abort operations are not supported.
ITransaction::Commit This method commits a transaction. The following table shows how values of the fRetaining parameter and DBPROP_COMMITPRESERVE affect the rowset state and transaction mode. DBPROP_ COMMIT PRESERVE FALSE FALSE TRUE TRUE
fRetaining FALSE TRUE FALSE TRUE
Rowset State After Commit zombie zombie preserved preserved
Resulting Transaction / Mode of Session implicit / autocommit explicit / manual implicit / autocommit explicit / manual
206
n Chapter Four—An OLE DB Primer If Commit fails for any reason that results in an aborted transaction, the session is left in autocommit mode. It has the following syntax: HRESULT Commit( BOOL fRetaining, DWORD grfTC, DWORD grfRM);
It has the following parameters: fRetaining [in] This parameter shows whether the commit is retaining or nonretaining. grfTC [in] This parameter gives values taken from the enumeration XACTTC. Values that may be specified in grfTC are as follows. These values are mutually exclusive. Flag XACTTC_ ASYNC_PHASEONE XACTTC_ SYNC_PHASEONE XACTTC_ SYNC_PHASETWO XACTTC_SYNC
Description When this flag is specified, an asynchronous commit is performed. When this flag is specified, the call to Commit returns after phase one of the two-phase commit protocol. When this flag is specified, the call to Commit returns after phase two of the two-phase commit protocol. Synonym for XACTTC_SYNC_PHASETWO.
grfRM [in] This parameter must be zero. It has the following return codes: S_OK This return code means the transaction was successfully committed. XACT_S_ASYNC This return code shows that an asynchronous commit was specified. The commit operation has begun but its outcome is not yet known. When the transaction is complete, notification will be sent by ITransactionOutcomeEvents. E_FAIL This return code indicates that an OLE DB provider-specific error occurred. The transaction was aborted. E_UNEXPECTED This return code states that an unexpected error occurred. The transaction status is unknown.
Chapter Four—An OLE DB Primer n
207
XACT_E_ABORTED This return code specifies that the transaction was aborted before Commit was called. XACT_E_ALREADYINPROGRESS This return code means that a commit or abort operation was already in progress. This call was ignored. XACT_E_CANTRETAIN This return code provides that retaining commit is not supported or a new unit of work could not be created. The commit succeeded and the session is in autocommit mode. XACT_E_COMMITFAILED This return code means that the transaction failed to commit for an unknown reason. The transaction was aborted. XACT_E_CONNECTION_DOWN This return code indicates that the connection to the transaction manager failed. The transaction was aborted. XACT_E_INDOUBT This return code shows that the transaction status is in doubt. A communication failure occurred or a transaction manager or resource manager has failed. XACT_E_NOTRANSACTION This return code specifies the transaction is unable to commit because it had already been implicitly or explicitly committed or aborted. This call was ignored. XACT_E_NOTSUPPORTED This return code shows an invalid combination of commit flags was specified or grfRM was not equal to zero. This call was ignored.
ITransaction::GetTransactionInfo This method returns information regarding a transaction. It has the following syntax: HRESULT GetTransactionInfo( XACTTRANSINFO * pInfo);
It has the following parameter: pInfo [out] This parameter gives a pointer to the caller-allocated XACTTRANSINFO structure in which the method returns information about the transaction. *pInfo must not be a null pointer.
208
n Chapter Four—An OLE DB Primer typedef struct XACTTRANSINFO { XACTUOW uow; ISOLEVEL isoLevel; ULONG isoFlags; DWORD grfTCSupported; DWORD grfRMSupported; DWORD grfTCSupportedRetaining; DWORD grfRMSupportedRetaining; } XACTTRANSINFO;
The elements of this structure are used as follows: Element uow isoLevel
isoFlags grfTCSupported grfRMSupported grfTCSupportedRetaining grfRMSupportedRetaining
Description This element shows the unit of work associated with this transaction. This element gives the isolation level associated with this transaction. ISOLATIONLEVEL_ UNSPECIFIED indicates that no isolation level was specified. This element will be zero. This bitmask indicates the XACTTC flags that this transaction implementation supports. This element will be zero. This element will be zero. This element will be zero.
It has the following return codes: S_OK This return code means the method succeeded. E_FAIL This return code shows that an OLE DB provider-specific error occurred. E_INVALIDARG This return code states that *pInfo was a null pointer. E_UNEXPECTED This return code indicates that an unknown error occurred. No information is returned. XACT_E_NOTRANSACTION This return code means the method was unable to retrieve information for the transaction because it was already completed. No information is returned.
Chapter Four—An OLE DB Primer n
209
ITransactionOptions ITransactionOptions gets and sets a suite of options associated with a transaction. Method Get Options SetOptions
Description This method gets a suite of options associated with a transaction. This method sets a suite of options associated with a transaction.
ITransactionOptions::GetOptions This method gets a suite of options associated with a transaction. The method can be called at any time. GetOptions does not make any logical changes to the state of any open transactions. It has the following syntax: HRESULT GetOptions( XACTOPT * pOptions);
It has the following parameter: pOptions [in/out] This parameter gives a pointer to an XACTOPT structure in which to return the options for this transaction. The OLE DB consumer allocates this structure. It has the following return codes: S_OK This return code means success. E_FAIL This return code shows that an OLE DB provider-specific error occurred. E_INVALIDARG This return code indicates that *pOptions was a null pointer. E_UNEXPECTED This return code specifies that an unknown error occurred; the method failed.
ITransactionOptions::SetOptions This method sets a suite of options associated with a transaction. It has the following syntax: HRESULT SetOptions( XACTOPT * pOptions);
210
n Chapter Four—An OLE DB Primer It has the following parameter: pOptions [in] This parameter gives a pointer to an XACTOPT structure containing the options to be set in this transaction. This cannot be a null pointer. typedef struct XACTOPT { ULONG ulTimeout; unsigned char szDescription[MAX_TRAN_DESC]; } XACTOPT
The elements of this structure are used as follows: Element ulTimeout
szDescription
Description This element gives the amount of real time in milliseconds before the transaction is to be aborted automatically. Zero indicates an infinite timeout. If no options have been previously set, ulTimeout is zero. This element provides a pointer to a textual description associated with this transaction. This string is appropriate for display in various end-user administration tools that might monitor or log the transaction. If no options have been previously set, szDescription is an empty string.
It has the following return codes: S_OK This return code means the method succeeded. E_FAIL This return code shows that an OLE DB provider-specific error occurred. E_INVALIDARG This return code states that *pOptions was a null pointer. E_UNEXPECTED This return code indicates that an unknown error occurred; the method failed.
ITransactionObject ITransactionObject enables OLE DB consumers to obtain the transaction object associated with a particular transaction level. Method GetTransactionObject
Description This method returns an interface pointer on the transaction object.
Chapter Four—An OLE DB Primer n
211
ITransactionObject::GetTransactionObject This method returns an interface pointer on the transaction object. It has the following syntax: HRESULT GetTransactionObject ( ULONG ulTransactionLevel, ITransaction ** ppTransactionObject);
It has the following parameters: ulTransactionLevel[in] This parameter gives the level of the transaction. ppTransactionObject [out] This parameter shows a pointer to memory in which to return a pointer to the returned transaction object. It has the following return codes: S_OK This return code means the method succeeded. E_FAIL This return code indicates that an OLE DB provider-specific error occurred. E_INVALIDARG This return code states that ulTransactionLevel was zero or *ppTransactionObject was a null pointer. E_UNEXPECTED This return code shows that an unknown error occurred and the method failed.
ITransactionJoin ITransactionJoin is exposed only by OLE DB providers that support distributed transactions. The OLE DB consumer calls QueryInterface for ITransactioJoin to determine whether the OLE DB provider supports coordinated transactions. Method GetOptionsObject
JoinTransaction
Description This method returns an object that can be used to specify configuration options for a subsequent call to the JoinTransaction method. This method requests that the session enlist in a coordinated transaction.
212
n Chapter Four—An OLE DB Primer
ITransactionJoin::GetOptionsObject This method returns an object that can be used to specify configuration options for a subsequent call to JoinTransaction. It has the following syntax: HRESULT GetOptionsObject ( ITransactionOptions ** ppOptions);
It has the following parameter: ppOptions [out] This parameter gives a pointer to memory in which to return a pointer to the object that can be used to set extended transaction options. It has the following return codes: S_OK This return code means the method succeeded. E_FAIL This return code indicates that an unknown error occurred. E_INVALIDARG This return code shows that *ppOptions was a null pointer. E_OUTOFMEMORY This return code means the method was unable to allocate memory.
ITransactionJoin::JoinTransaction This method requests that the session enlist in a coordinated transaction. The OLE DB provider of the session will generally first register its ITransactionOutcomeEvents notification sink with the transaction coordinator, so that it can be notified of the outcome of the coordinated events, and will then enlist with the transaction coordinator using the interfaces defined in OLE transactions. It is important that the OLE DB provider first register with the outcome events so that it can be advised of any failures which may occur between enlistment registration of the outcome events. It has the following syntax: HRESULT JoinTransaction ( IUnknown * punkTransactionCoord, ISOLEVEL isoLevel, ULONG isoFlags, ITransactionOptions * pOtherOptions);
It has the following parameters: punkTransactionCoord [in] This parameter gives a pointer to the controlling IUnknown of the
Chapter Four—An OLE DB Primer n
213
transaction coordinator, or NULL in order to unenlist from the coordinated transaction. If non-null, QueryInterface can be called for ITransaction on the transaction coordinator. isoLevel [in] This parameter shows the isolation level to be used with this transaction. isoFlags [in] This parameter must be zero. pOtherOptions [in] This parameter is optionally a null pointer. If this is not a null pointer, then it is a pointer to an object previously returned from GetOptionsObject called on this session. It has the following return codes: S_OK This return code means the method succeeded. E_FAIL This return code shows that an unknown error occurred. E_INVALIDARG This return code indicates that *punkTransactionCoord was a null pointer and the OLE DB provider doesn’t support unenlisting from a coordinated transaction. E_UNEXPECTED This return code states that an unknown OLE DB provider-specific error occurred. XACT_E_CONNECTION_DOWN This return code specifies that the connection to the transaction manager failed. XACT_E_CONNECTION_REQUEST_DENIED This return code shows that the transaction manager did not accept a connection request. XACT_E_ISOLATIONLEVEL This return code states that neither the requested isolation level, nor a strengthening of it, can be supported by this transaction implementation or isoLevel was not valid. XACT_E_LOGFULL This return code means a new transaction is unable to begin because the log file is full. XACT_E_NOENLIST This return code indicates a transaction coordinator was specified, but the new transaction was unable to enlist therein. XACT_E_NOISORETAIN This return code specifies that the requested semantics of retention of
214
n Chapter Four—An OLE DB Primer isolation across retaining commit and abort boundaries cannot be supported by this transaction implementation or isoFlags was not equal to zero. XACT_E_NOTIMEOUT This return code shows that a time-out was specified, but time-outs are not supported. XACT_E_TMNOTAVAILABLE This return code means unable to connect to the transaction manager or the transaction manager is unavailable. XACT_E_XTIONEXISTS This return code states that the enlistment request failed for one of the following reasons: n
This session can handle only one extant transaction at a time, and there is presently such a transaction.
n
*punkTransactionCoord was NULL, and the session is enlisted in a coordinated transaction with uncommitted work.
IRowsetIndex IRowsetIndex is the primary interface for exposing index functionality in OLE DB. The IRowsetIndex interface is implemented by OLE DB providers to expose the functionality of a file access method such as a B+-tree, or linear hash. For integrated indexes, IRowsetIndex is exposed on the base table rowset; otherwise, it is exposed on the index rowset. An OLE DB provider may also support other rowset interfaces on indexes, such as IRowsetScroll. The methods in IRowsetIndex are used to define a range of index entries to be read, to position at an index entry within the range, to fetch the index entry, and to access the contents of the index entry. The following table shows how to perform various index operations. Operation Open an index
Close an index Insert an index entry Delete an index entry
Comments This operation serves to get a handle to an IRowsetIndex object. The OLE DB consumer calls IOpenRowset::OpenRowset, passing it the DBID of the index. The method returns a pointer to an index rowset. This operation ensures an index is closed by releasing all references to the index rowset. This operation inserts new key entries into an index by using IRowsetChange. This operation deletes key entries from an index by using IRowsetChange.
Chapter Four—An OLE DB Primer n Operation Update an index entry Traverse the index
Method GetIndexInfo Seek SetRange
215
Comments This operation updates key entries in an index by first deleting the old index entry and then inserting a new index entry. This operation states that to traverse an index, a user calls methods on IRowset. Description This method returns information about the index rowset capabilities. This method allows direct positioning at a key value within the current range. This method restricts the set of row entries visible through calls to IRowset::GetNextRows and IRowsetIndex::Seek.
IRowsetIndex::GetIndexInfo This method returns information about the index rowset capabilities. It has the following syntax: HRESULT GetIndexInfo ( ULONG * pcKeyColumns, DBINDEXCOLUMNDESC **prgIndexColumnDesc, ULONG * pcIndexProperties, DBPROPSET ** prgIndexProperties);
It has the following parameters: pcKeyColumns [out] This parameter gives a pointer to memory in which to return the number of key columns in the index. prgIndexColumnDesc [out] This parameter shows a pointer to memory in which to return an array of DBINDEXCOLUMNDESC structures in key column order. The size of the array is equal to *pcKeyColumns. If an error occurs, *prgIndexColumnDesc is set to a null pointer. pcIndexProperties [out] This parameter indicates a pointer to memory in which to return the number of DBPROPSET structures returned in *prgIndexProperties. *pcIndexProperties is the total number of property sets for which the OLE DB provider supports at least one property in the Index property group. If an error occurs, *pcIndexProperties is set to zero. prgIndexProperties [out] This parameter specifies a pointer to memory in which to return an array of DBPROPSET structures. One structure is returned for each property set that contains at least one property belonging to the Index property group.
216
n Chapter Four—An OLE DB Primer The OLE DB provider allocates memory for the structures and returns the address to this memory; the OLE DB consumer releases this memory with IMalloc::Free when it no longer needs the structures. Before calling IMalloc::Free for *prgPropertySets, the OLE DB consumer should call IMalloc::Free for the rgProperties element within each element of *prgPropertySets. If *pcIndexProperties is zero on output, or if an error occurs, the OLE DB provider does not allocate any memory and ensures that *prgIndexProperties is a null pointer on output. It has the following return codes: S_OK This return code means the method succeeded. E_FAIL This return code shows that an OLE DB provider-specific error occurred. E_INVALIDARG This return code indicates that *pcKeyColumns, *prgIndexColumnDesc, *pcIndexProperties, or *prgIndexProperties was a null pointer. E_OUTOFMEMORY This return code states that the OLE DB provider was unable to allocate sufficient memory in which to return the column description structures or properties of the index. DB_E_NOINDEX This return code specifies that the rowset uses integrated indexes and there is no current index.
IRowsetIndex::Seek This method allows direct positioning at a key value within the current range established by the IRowsetIndex::SetRange method. The Seek method provides the caller more control over the traversal of an index. Consider a relational query processor component implementing a merge join over inputs R1 and R2. R1, the outer input, is a rowset ordered by the joining column R1.x. R2, the inner input, is an indexed rowset on column R2.x. Suppose that R1.x has values {10, 20, 100, 110} and that R2.x has values {10, 20, ..., 30, ..., 40, ..., 50, ..., 100, ...}. When searching R2.x, one could seek directly from 20 to 100 knowing the values of the input R1.x. In some cases, this strategy could be cost effective. It has the following syntax: HRESULT Seek ( HACCESSOR hAccessor, ULONG cKeyValues, void * pData, DBSEEK dwSeekOptions);
Chapter Four—An OLE DB Primer n
217
It has the following parameters: hAccessor [in] This parameter gives the handle of the OLE DB accessor to use. This OLE DB accessor must meet the following criteria, which are illustrated with a key that consists of columns A, B, and C, where A is the most significant column and C is the least significant column: n
For each key column this OLE DB accessor binds, it must also bind all more significant key columns. For example, the OLE DB accessor can bind column A, columns A and B, or columns A, B, and C.
n
Key columns must be bound in order from most significant key column to least significant key column. For example, if the OLE DB accessor binds columns A and B, then the first binding must bind column A and the second binding must bind column B.
n
If the OLE DB accessor binds any non-key columns, key columns must be bound first. For example, if the OLE DB accessor binds columns A, B, and the bookmark column, then the first binding must bind column A, the second binding must bind column B, and the third binding must bind the bookmark column.
If the OLE DB accessor does not meet these criteria, the method returns DB_E_BADBINDINFO or a status of DBSTATUS_E_BADACCESSOR for the offending column. If hAccessor is the handle of a null accessor (cBindings in IAccessor:: CreateAccessor was zero), then Seek does not change the next fetch position. cKeyValues [in] This parameter shows the number of bindings in hAccessor for which *pData contains valid data. SetRange retrieves data from the first cKeyValues key columns from *pData. For example, suppose the OLE DB accessor binds columns A, B, and C of the key in the previous example and cKeyValues is 2. SetRange retrieves data for columns A and B. pData [in] This parameter provides a pointer to a buffer containing the key values to seek, at offsets that correspond to the bindings in the OLE DB accessor. dwSeekOptions [in] This parameter gives a bitmask describing the options for the Seek method. The values in DBSEEKENUM have the following meanings: Value DBSEEK_FIRSTEQ DBSEEK_LASTEQ DBSEEK_GE
Description First key with values equal to the values in *pData. Last key with values equal to the values in *pData. First key with values greater than or equal to the values in *pData.
218
n Chapter Four—An OLE DB Primer Value DBSEEK_GT DBSEEK_LE DBSEEK_LT
Description First key with values greater than the values in *pData. First key with values less than or equal to the values in *pData. First key with values less than the values in *pData.
It has the following return codes: S_OK This return code means the method succeeded. E_FAIL This return code shows that an OLE DB provider-specific error occurred. E_INVALIDARG This return code indicates that dwSeekOptions was invalid, hAccessor was the handle of a null accessor, cKeyValues was zero, or *pData was a null pointer. DB_E_BADACCESSORHANDLE This return code means that hAccessor was invalid. DB_E_BADACCESSORTYPE This return code provides that the specified accessor was not a row accessor. DB_E_ERRORSOCCURRED This return code specifies that an error occurred while transferring data for one or more key columns. To determine the columns for which values were invalid, the OLE DB consumer checks the status values. DB_E_NOINDEX This return code shows that the rowset uses integrated indexes and there is no current index. DB_E_NOTFOUND This return code indicates that no key value matching the described characteristics could be found within the current range. DB_E_NOTREENTRANT This return code shows that the OLE DB consumer called this method while it was processing a notification, and it is an error to call this method while processing the specified DBREASON value. If this method performs deferred accessor validation and that validation takes place before any data is transferred, it can also return any of the following return codes for the reasons listed in the corresponding DBBINDSTATUS values in IAccessor::CreateAccessor: E_NOINTERFACE DB_E_BADBINDINFO DB_E_BADORDINAL
Chapter Four—An OLE DB Primer n
219
DB_E_BADSTORAGEFLAGS DB_E_UNSUPPORTEDCONVERSION
IRowsetIndex::SetRange This method restricts the set of row entries visible through calls to IRowset:: GetNextRows and IRowsetIndex::Seek. A range defines a view in the index containing a contiguous set of key values. The *pStartData and *pEndData values always specify the starting and ending positions in the range, respectively. Thus, for an ascending index, *pStartData contains the smaller value and *pEndData contains the larger value; for a descending index, *pStartData contains the larger value and *pEndData contains the smaller value. A range on the entire index is defined by calling SetRange (hAccessor, 0, NULL, 0, NULL, 0). When a range is set, Seek can only position to rows in the current range. It has the following syntax: HRESULT SetRange ( HACCESSOR hAccessor, ULONG cStartKeyColumns, void * pStartData, ULONG cEndKeyColumns, void * pEndData, DBRANGE dwRangeOptions);
It has the following parameters: hAccessor [in] This parameter gives the handle of the OLE DB accessor to use for both *pStartData and *pEndData. This OLE DB accessor must meet the following criteria, which are illustrated with a key that consists of columns A, B, and C, where A is the most significant column and C is the least significant column: n
For each key column this OLE DB accessor binds, it must also bind all more significant key columns. For example, the OLE DB accessor can bind column A, columns A and B, or columns A, B, and C.
n
Key columns must be bound in order from most significant key column to least significant key column. For example, if the OLE DB accessor binds columns A and B, then the first binding must bind column A and the second binding must bind column B.
n
If the OLE DB accessor binds any nonkey columns, key columns must be bound first. For example, if the OLE DB accessor binds columns A, B, and the bookmark column, then the first binding must bind column A, the second binding must bind column B, and the third binding must bind the bookmark column.
220
n Chapter Four—An OLE DB Primer If the OLE DB accessor does not meet these criteria, the method returns DB_E_BADBINDINFO or a status of DBSTATUS_E_BADACCESSOR for the offending column. If hAccessor is the handle of a null accessor (cBindings in IAccessor::CreateAccessor was zero), then SetRange does not set a range. cStartKeyColumns [in] This parameter gives the number of bindings in hAccessor for which *pStartData contains valid data. SetRange retrieves data from the first cStartKeyValues key columns from *pStartData. For example, suppose the OLE DB accessor binds columns A, B, and C of the key in the previous example and cStartKeyValues is 2. SetRange retrieves data for columns A and B. pStartData [in] This parameter provides a pointer to a buffer containing the starting key values of the range, at offsets that correspond to the bindings in the OLE DB accessor. cEndKeyColumns [in] This parameter indicates the number of bindings in hAccessor for which *pEndData contains valid data. The SetRange method retrieves data from the first cEndKeyValues key columns from *pEndData. For example, suppose the OLE DB accessor binds columns A, B, and C of the key in the previous example and cEndKeyValues is 2. SetRange retrieves data for columns A and B. pEndData [in] This parameter shows a pointer to a buffer containing the ending key values of the range, at offsets that correspond to the bindings in the OLE DB accessor. dwRangeOptions [in] This parameter gives a bitmask describing the options of the range. The values in DBRANGEENUM have the following meanings: Value DBRANGE_ INCLUSIVESTART DBRANGE_ EXCLUSIVESTART DBRANGE_ INCLUSIVEEND DBRANGE_ EXCLUSIVEEND DBRANGE_ EXCLUDENULLS
Description This value shows that the start boundary is inclusive (the default). This value states that the start boundary is exclusive. This value indicates that the end boundary is inclusive (the default). This value means the end boundary is exclusive. This value excludes NULLs from the range.
Chapter Four—An OLE DB Primer n Value DBRANGE_PREFIX
DBRANGE_MATCH
DBRANGE_MATCH_ N_MASK DBRANGE_MATCH_ N_SHIFT
221
Description This value specifies using *pStartData as a prefix. *pEndData must be a null pointer. Prefix matching can be specified entirely using the inclusive and exclusive flags. However, because prefix matching is an important common case, this flag enables the OLE DB consumer to specify only the *pStartData values, and enables the OLE DB provider to interpret this request quickly. This value indicates setting the range to all keys that match *pStartData. *pStartData must specify a full key. *pEndData must be a null pointer. Used for fast equality match. This value is equal to 0xFF000000. This value is equal to 24 to indicate the number of bits to shift to get the number N.
It has the following return codes: S_OK This return code means the method succeeded. E_FAIL This return code shows that an OLE DB provider-specific error occurred. E_INVALIDARG This return code states that dwRangeOptions was invalid; cStartKeyValues was not zero and pStartData was a null pointer; cEndKeyValues was not zero and pEndData was a null pointer; or hAccessor was the handle of a null accessor. DB_E_BADACCESSORHANDLE This return code indicates that hAccessor was invalid. DB_E_BADACCESSORTYPE This return code means the specified OLE DB accessor was not a row accessor. DB_E_ERRORSOCCURRED This return code means that an error occurred while transferring data for one or more key columns. To determine the columns for which values were invalid, the OLE DB consumer checks the status values. DB_E_NOINDEX This return code shows that the rowset uses integrated indexes and there is no current index. If this method performs deferred accessor validation and that validation takes place before any data is transferred, it can also return any of the following return codes for the reasons listed in the corresponding DBBINDSTATUS values in IAccessor::CreateAccessor: E_NOINTERFACE DB_E_BADBINDINFO
222
n Chapter Four—An OLE DB Primer DB_E_BADORDINAL DB_E_BADSTORAGEFLAGS DB_E_UNSUPPORTEDCONVERSION
IErrorRecords IErrorRecords is defined by OLE DB. It is used to add and retrieve records in an OLE DB Error object. Information is passed to and from OLE DB error objects in an ERRORINFO structure. IErrorRecords is implemented by code in the OLE DB SDK. OLE DB consumers use this interface to retrieve information stored in the records of an OLE DB Error object. They call QueryInterface to get a pointer to this interface after retrieving an OLE DB Error object with GetErrorInfo in the Automation DLL. OLE DB providers use this interface to add records to an OLE DB Error object. If they get an existing OLE DB Error object with GetErrorInfo in the Automation DLL, they call QueryInterface on that object to get a pointer to this interface. If they create a new OLE DB Error object through a class factory or with CoCreateInstance, they request that a pointer to this interface be returned. Method AddErrorRecord GetBasicErrorInfo
GetCustomErrorObject
GetErrorParameters GetrRecordCount
Description This method adds a record to an OLE DB Error object. This method returns basic information about the error, such as the return code and provider-specific error number. This method returns a pointer to an interface on the custom error object.GetErrorInfoThis method returns an IErrorInfo interface pointer on the specified record. This method returns the error parameters. This method returns the count of records in the OLE DB error object.
IErrorRecords::AddErrorRecord This method adds a record to an OLE DB Error object. The method should be used only by OLE DB providers; there are no reasons for OLE DB consumers to use it. Records are added to the top of the list. That is, the number of the newly added record is record 0 and the number of all other records is increased by 1. AddErrorRecord adds a reference count on the custom error object. After adding a custom error object to a record in an OLE DB error object, the OLE DB provider must call Release on all interface pointers it holds on that custom error object. This transfers ownership of the custom error object to the
Chapter Four—An OLE DB Primer n
223
OLE DB error object. When it is released, the OLE DB error object will release all custom error objects. It has the following syntax: HRESULT AddErrorRecord ( ERRORINFO * pErrorInfo, DWORD dwLookupID, DISPPARAMS * pdispparams, IUnknown * punkCustomError, DWORD dwDynamicErrorID);
It has the following parameters: pErrorInfo [in] This parameter gives a pointer to an ERRORINFO structure containing information about the error. This structure is allocated and freed by the OLE DB consumer. dwLookupID [in] This parameter shows the value used by the OLE DB provider’s error lookup service in conjunction with the return code to identify the error description, Help file, and context ID for an error. This can be an OLE DB provider-specific value, such as the dwMinor element of *pErrorInfo. It can also be a special value, IDENTIFIER_SDK_ERROR, that tells the implementation of IErrorInfo that is shipped with the OLE DB SDK to ignore the OLE DB provider’s lookup service and use the description supplied in the OLE DB SDK error resource DLL. pdispparams [in] This parameter provides a pointer to the parameters for the error. This is a null pointer if there are no error parameters. The error parameters are inserted into the error text by the error lookup service. This structure is allocated and freed by the OLE DB consumer. punkCustomError [in] This parameter gives an interface pointer to the custom error object. This is a null pointer if there is no custom object for the error. dwDynamicErrorID[in] This parameter indicates that if the error lookup service uses static errors—that is, error information that is hard-coded in the lookup service—dwDynamicErrorID is zero. If the error lookup service uses dynamic errors—that is, error information that is created at run time—dwDynamicErrorID is the ID of the error record. This ID is used to release the error information when the OLE DB error object is released. Although it is not required, it is more efficient for all error records in a single OLE DB error object to have the same dynamic error ID.
224
n Chapter Four—An OLE DB Primer It has the following return codes: S_OK This return code means the method succeeded. E_INVALIDARG This return code shows that pErrorInfo was a null pointer. E_OUTOFMEMORY This return code states that the OLE DB error object was unable to allocate sufficient memory with which to add a new record.
IErrorRecords::GetBasicErrorInfo This method returns basic information about the error, such as the return code and OLE DB provider-specific error number. The method should be used only by OLE DB consumers; there are no reasons for OLE DB providers to use it. It has the following syntax: HRESULT GetBasicErrorInfo ( ULONG ulRecordNum, ERRORINFO * pErrorInfo);
It has the following parameters: ulRecordNum [in] This parameter gives the zero-based number of the record for which to return information. pErrorInfo [out] This parameter shows a pointer to an ERRORINFO structure in which to return basic error information. This structure is allocated and freed by the OLE DB consumer. It has the following return codes: S_OK This return code means the method succeeded. E_INVALIDARG This return code states that pErrorInfo was a null pointer. DB_E_BADRECORDNUM This return code shows that ulRecordNum, which is zero-based, was greater than or equal to the count, which is one-based, of records returned by GetRecordCount.
Chapter Four—An OLE DB Primer n
225
IErrorRecords::GetCustomErrorObject This method returns a pointer to an interface on the custom error object. The method should be used only by OLE DB consumers; there are no reasons for OLE DB providers to use it. It has the following syntax: HRESULT GetCustomErrorObject ULONG REFIID IUnknown **
( ulRecordNum, riid, ppObject);
It has the following parameters: ulRecordNum [in] This parameter gives the zero-based number of the record for which to return a custom error object. riid [in] This parameter shows the IID of the interface to return. ppObject[out] This parameter shows a pointer to memory in which to return an interface pointer on the custom error object. If there is no custom error object, a null pointer is returned; that is, *ppObject is a null pointer. It has the following return codes: S_OK This return code means the method succeeded. E_INVALIDARG This return code shows that *ppObject was a null pointer. E_NOINTERFACE This return code indicates that the custom error object did not support the interface specified in riid. DB_E_BADRECORDNUM This return code means that ulRecordNum, which is zero-based, was greater than or equal to the count, which is one-based, of records returned by GetRecordCount.
IErrorRecords::GetErrorInfo This method returns an IErrorInfo interface pointer on the specified record. The method should be used only by OLE DB consumers; there are no reasons for OLE DB providers to use it.
226
n Chapter Four—An OLE DB Primer It has the following syntax: HRESULT GetErrorInfo ( ULONG ulRecordNum, LCID lcid, IErrorInfo ** ppErrorInfo);
It has the following parameters: ulRecordNum [in] This parameter gives the zero-based number of the record for which to return an IErrorInfo interface pointer. lcid [in] This parameter provides the locale ID for which to return error information. The parameter is checked when it is passed to methods in IErrorInfo. ppErrorInfo [out] This parameter shows a pointer to memory in which to return a pointer to an IErrorInfo interface on the specified record. This IErrorInfo interface pointer is different from the IErrorInfo interface pointer exposed on the OLE DB error object with Queryinterface. It has the following return codes: S_OK This return code means the method succeeded. E_INVALIDARG This return code shows that *ppErrorInfo was a null pointer. DB_E_BADRECORDNUM This return code indicates that ulRecordNum, which is zero-based, was greater than or equal to the count, which is one-based, of records returned by GetRecordCount.
IErrorRecords::GetErrorParameters This method returns the error parameters. The method is used by OLE DB consumers only when the meaning of the error parameters is known to the OLE DB consumer; error parameters are generally passed to the error lookup service and incorporated into error messages by the OLE DB provider through that lookup service. There is no reason for OLE DB providers to use this method. It has the following syntax: HRESULT GetErrorParameters ( ULONG ulRecordNum, DISPPARAMS * pdispparams);
Chapter Four—An OLE DB Primer n
227
It has the following parameters: ulRecordNum[in] This parameter gives the zero-based number of the record for which to return parameters. pdispparams [out] This parameter shows a pointer to a DISPPARAMS structure in which to return the error parameters. The OLE DB consumer allocates the memory for the DISPPARAMS structure itself, but the OLE DB provider allocates the memory for any arrays pointed to by elements of the DISPPARAMS structure. It has the following return codes: S_OK This return code means the method succeeded. E_INVALIDARG This return code indicates that *pdispparams was a null pointer. E_OUTOFMEMORY This return code shows that the provider was unable to allocate sufficient memory in which to return the data pointed to by elements of *pdispparams. DB_E_BADRECORDNUM This return code means that ulRecordNum, which is zero-based, was greater than or equal to the count, which is one-based, of records returned by GetRecordCount.
IErrorRecords::GetRecordCount This method returns the count of records in the OLE DB Error object. The method should be used only by OLE DB consumers; there are no reasons for OLE DB providers to use it. It has the following syntax: HRESULT GetRecordCount ( ULONG * pcRecords);
It has the following parameter: pcRecords [out] This parameter gives a pointer to memory in which to return the count of error records. This is a one-based count, although error records are zero-based. If *pcRecords is zero, there are no records in the OLE DB error object and calls to GetBasicErrorInfo, GetErrorInfo, GetErrorParameters, and GetCustomErrorObject will return DB_E_BADRECORDNUM.
228
n Chapter Four—An OLE DB Primer It has the following return codes: S_OK This return code means the method succeeded. E_INVALIDARG This return code shows that *pcRecords was a null pointer.
IErrorLookup IErrorLookup is used by OLE DB error objects to determine the values of the error message, source, Help file path, and context ID based on the return code and a provider-specific error number. IErrorLookup is exposed by an OLE DB provider-specific error lookup service that is mandatory for all OLE DB providers that return OLE DB error objects. All OLE DB providers that return OLE DB error objects must implement IErrorLookup in a separate lookup service. IErrorLookup is called by the code shipped in the OLE DB SDK that implements OLE DB error objects. It should not be called by general OLE DB consumers. When an error occurs, the following sequence of events takes place: 1. The OLE DB provider creates an OLE DB error object and adds a record to that object. 2. The OLE DB consumer retrieves the error object and gets an IErrorInfo interface on a particular record in that object. It then calls a method in IErrorInfo. 3. Except for the IErrorInfo::GetGUID method, the OLE DB error object code, which is shipped with the OLE DB SDK, loads the error lookup service based on the class ID stored in the error record. 4. The OLE DB error object code returns the hrError element of the ERRORINFO structure, the lookup ID, and the error parameters from the error record. It also returns the locale ID requested in IErrorRecords::GetErrorInfo. It passes all of this information to the appropriate IErrorLookup method. 5. The IErrorLookup method returns the requested information, based on the hrError, dwLookupID, and lcid arguments, integrates the parameters, and returns this to the error object code. 6. The OLE DB error object code returns the requested information to the OLE DB consumer. 7. The OLE DB consumer releases the OLE DB error object. 8. The OLE DB error object code calls IErrorLookup::ReleaseErrors for all error records with a nonzero dynamic error ID to release the error information associated with that record.
Chapter Four—An OLE DB Primer n Method GetErrorDescription
GetHelpInfo ReleaseErrors
229
Description This method returns the error message and source, based on the return code and the OLE DB provider-specific error number. This method returns the path of the Help file and the context ID of the topic that explains the error. This method releases any dynamic error information associated with a dynamic error ID.
IErrorLookup::GetErrorDescription This method returns the error message and source, based on the return code and the provider-specific error number. It has the following syntax: HRESULT GetErrorDescription ( HRESULT hrError, DWORD dwLookupID, DISPPARAMS * pdispparams, LCID lcid, BSTR * pbstrSource, BSTR * pbstrDescription);
It has the following parameters: hrError [in] This parameter gives the code returned by the method that caused the error. dwLookupID [in] This parameter shows the provider-specific number of the error. pdispparams [in] This shows the parameters of the error. If there are no error parameters, this is a null pointer. lcid [in] This parameter indicates the locale ID for which to return the description and source. pbstrSource [out] This parameter provides a pointer to memory in which to return a pointer to the name of the component that generated the error. If an error occurs, *pbstrSource is set to a null pointer. The memory for this string is allocated by the OLE DB provider and must be freed by the OLE DB consumer with a call to SysFreeString. pbstrDescription [out] This parameter gives a pointer to memory in which to return a pointer to a string that describes the error. If *pdispparams was not a null pointer,
230
n Chapter Four—An OLE DB Primer then the error parameters are integrated into this description. If there is no error description or an error occurs, the returned value (*pbstrDescription) is a null pointer. The memory for this string is allocated by the OLE DB provider and must be freed by the consumer with a call to SysFreeString. It has the following return codes: S_OK This return code means the method succeeded. E_FAIL This return code shows that an OLE DB provider-specific error occurred. E_INVALIDARG This return code indicates that *pbstrSource or *pbstrDescription was a null pointer. E_OUTOFMEMORY This return code states that the OLE DB provider was unable to allocate sufficient memory in which to return the error source or description. DB_E_BADHRESULT This return code means that hrError was invalid. DB_E_BADLOOKUPID This return code shows that dwLookupID was invalid. DB_E_NOLOCALE This return code specifies that the locale ID specified in lcid was not supported by the OLE DB provider.
IErrorLookup::GetHelpInfo This method returns the path of the Help file and the context ID of the topic that explains the error. It has the following syntax: HRESULT GetHelpInfo ( HRESULT hrError, DWORD dwLookupID, LCID lcid, BSTR * pbstrHelpFile, DWORD * pdwHelpContext);
It has the following parameters: hrError [in] This parameter gives the code returned by the method that caused the error.
Chapter Four—An OLE DB Primer n
231
dwLookupID[in] This parameter shows the OLE DB provider-specific number of the error. lcid [in] This parameter indicates the locale ID for which to return the Help file path and context ID. pbstrHelpFile [out] This parameter specifies a pointer to memory in which to return a pointer to a string containing the fully qualified path of the Help file. If there is no Help file or an error occurs, the returned value (*pbstrHelpFile) is a null pointer. The memory for this string is allocated by the OLE DB provider and must be freed by the OLE DB consumer with a call to SysFreeString. pdwHelpContext [out] This parameter gives a pointer to memory in which to return the Help context ID for the error. If there is no Help file (*pbstrHelpFile is a null pointer), then the returned value has no meaning. It has the following return codes: S_OK This return code means the method succeeded. E_FAIL This return code shows that an OLE DB provider-specific error occurred. E_INVALIDARG This return code states that *pbstrHelpFile or *pdwHelpContext was a null pointer. E_OUTOFMEMORY This return code indicates that the OLE DB provider was unable to allocate sufficient memory in which to return the Help file path. DB_E_BADHRESULT This return code shows that hrError was invalid. DB_E_BADLOOKUPID This return code means thatr dwLookupID was invalid. DB_E_NOLOCALE This return code specifies that the locale ID specified in lcid was not supported by the OLE DB provider.
IErrorLookup::ReleaseErrors This method releases any dynamic error information associated with a dynamic error ID. Dynamic error information is created at run time. It is released when the OLE DB error object calls ReleaseErrors with the ID of the error information to release. Although it is not required, it is more efficient for OLE DB providers to use the same error ID for all records in a single error
232
n Chapter Four—An OLE DB Primer object. This allows ReleaseErrors to release all of this information in a single call. It has the following syntax: HRESULT ReleaseErrors ( const DWORD dwDynamicErrorID);
It has the following parameter: dwDynamicErrorID [in] This parameter gives the ID of the dynamic error information to release. It has the following return codes: S_OK This return code means the method succeeded. E_FAIL This return code indicates that an OLE DB provider-specific error occurred. DB_E_BADDYNAMICERRORID This return code shows that dwDynamicErrorID was invalid.
ISQLErrorInfo ISQLErrorInfo is used to return the SQLSTATE and native error code. OLE DB providers for SQL databases can implement ISQLErrorInfo on custom error objects returned with the OLE DB error object. OLE DB consumers call the methods on ISQLErrorInfo to retrieve the SQLSTATE and native error code. To retrieve a custom error object, an OLE DB consumer calls IErrorRecords::GetCustomErrorObject. Method GetSQLInfo
Description This method returns the SQLSTATE and native error code associated with an error.
ISQLErrorInfo::GetSQLInfo This method returns the SQLSTATE and native error code associated with an error. It has the following syntax: HRESULT GetSQLInfo ( BSTR * pbstrSQLState, LONG * plNativeError);
Chapter Four—An OLE DB Primer n
233
It has the following parameters: pbstrSQLState [out] This parameter gives a pointer to memory in which to return a pointer to a string that contains the SQLSTATE. An SQLSTATE is a five-character string defined by the ANSI SQL standard. The memory for this string is allocated by the OLE DB provider and must be freed by the OLE DB consumer with a call to SysFreeString. If an error occurs, *pbstrSQLState is set to a null pointer. plNativeError [out] This parameter specifies a pointer to memory in which to return an OLE DB provider-specific, native error code. *plNativeError is not necessarily the same as the dwMinor element in the ERRORINFO structure returned by IErrorRecords::GetErrorInfo. The combination of the hrError and dwMinor elements of the ERRORINFO structure is used to identify an error to the error lookup service, whereas *plNativeError has no such restrictions. It has the following return codes: S_OK This return code means the method succeeded. E_FAIL This return code indicates that an OLE DB provider-specific error occurred. E_INVALIDARG This return code shows that pbstrSQLState or plNativeError was a null pointer. E_OUTOFMEMORY This return code states that the OLE DB provider was unable to allocate sufficient memory in which to return the SQLSTATE.
Where We Go From Here OLE DB is supported by the most powerful database technology yet to come from Microsoft: ActiveX Data Objects, or ADO. The next chapter gives you a complete primer on using these essential elements of the OLE DB picture.
Chapter Five
An ADO Primer Microsoft has two high-level database technologies: OLE DB (which isn’t an acronym) and MTS (which is, and stands for Microsoft Transaction Server). Both of these powerful database systems rely on a single technology to connect with end users: ActiveX Data Objects (ADO). ADO is a set of COM objects (i.e., interfaces) designed to connect with database systems via the OLE DB technology, but users don’t need to know anything about OLE DB to use ADO. Instead, they simply create appropriate ADO objects and make method calls on them to connect with databases and obtain and set information. For OLE DB developers, ADO is the vehicle their users need to connect with the OLE DB providers and consumers they create. For MTS programmers, ADO is the system they use to interact with databases under the umbrella of MTS. (ADO itself has several advanced capabilities, including RDS (Remote Data Service) and MD (Multi-Dimensional) database interactions, which we won’t go into here.) Either way, database programmers using Microsoft technology need a detailed reference about ADO. This chapter provides that reference, with a complete writeup on all the collections, objects, methods, and events of the ADO system. Dive in, and your database development will never be the same!
ADO Collections Collections are groups of things that share at least one similar feature, like a coin collection. In ADO, collections are objects (i.e., interfaces) used to store and provide access to groups of other ADO objects (i.e., interface pointers) that contain specific information or capabilities important to their owning ADO element (like the Errors collection for the Connection object). Each such collection allows clients to access single elements from groups of IPs using either a compile-time name, a run-time string, or a run-time integer. This allows the ADO system to provide general information to its clients while staying inside the COM metaphor. The collections supported in ADO 2.0 are Errors, Fields, Parameters, and Properties. Each has its own section below. In addition, each collection object supports a single property, as explained
235
236
n Chapter Five—An ADO Primer below. Each specific collection will implement its own specialized methods as well, with some overlap to the methods of other collections. Except for the Errors collection, each collection is fixed in number of elements and content when its owning ADO object is created by the system.
Count Property (All Collections) This property is common to all ADO collections. It gives a Long value which is the number of objects (i.e., interface pointers) in the current collection. A value of 0 indicates no interface pointers in the collection. Although Count is 1-based, the Item method used to retrieve collection objects is 0-based, so loops using Count should subtract 1 from it (i.e., (for x=0;x++;x<MyCollection.Count)).
The Errors Collection Any operation involving ADO objects can generate one or more errors. As each error occurs, one or more Error objects are placed in the Errors collection of the current Connection object. When another ADO operation generates an error, the Errors collection is cleared, and the new set of Error objects is placed in the Errors collection. Each Error object represents a specific OLE DB error, not an ADO run-time error (ADO errors are exposed to the run-time exception-handling mechanism). ADO operations that don’t generate an error have no effect on the Errors collection. Developers can use the Clear method to manually clear an Errors collection.The set of Error objects in the Errors collection describes all errors that occurred in response to a single SQL statement. Enumerating the specific errors in the Errors collection enables an application’s error-handling routines to more precisely determine the cause and origin of an error, and take appropriate steps to recover. Some properties and methods return warnings that appear as Error objects in the Errors collection but do not halt an application’s execution. Before an application calls the Resync, UpdateBatch, or CancelBatch methods on a Recordset object or the Open method on a Connection object, or sets the Filter property on a Recordset object, it should call the Clear method on the Errors collection so that it can check the Count property of the Errors collection to test for returned warnings. The Errors collection supports two methods: Clear and Item.
Clear Method (Errors Collection) This method removes all of the objects in the Errors collection. An application should use the Clear method on the Errors collection to remove all existing Error objects from the collection. When an error occurs, ADO automatically clears the Errors collection and fills it with Error objects based on the new error.
Chapter Five—An ADO Primer n
237
The method has the following syntax: pErrorscollection->Clear();
The method has no parameters.
Item Method (Errors Collection) This method returns a specific member of a collection by name or ordinal number. An application should use the Item method to return a specific object in a collection. If the method cannot find an object in the collection corresponding to the Index argument, an ADO error occurs. The method has the following syntax: pError = pErrorscollection->Item(Index);
The method returns an interface pointer to an ADO Error object. The method has the following parameter: Index This parameter is a Variant that evaluates either to the name or to the ordinal number of an object in a collection. Warning
Some collections don’t support named objects; for these collections, an application must use ordinal number references.
The Fields Collection A Recordset object has a Fields collection made up of Field objects. Each Field object corresponds to a column in the Recordset. An application should populate the Fields collection before opening the Recordset by calling the Refresh method on the Fields collection. The Fields collection supports four methods: Append, Delete, Item, and Refresh.
Append Method (Fields Collection) This method appends a new Field object to the collection, which may be created before it is appended. An application must set the CursorLocation property to adUseClient before calling the Fields.Append method. Calling the Fields.Append method for an open Recordset, or a Recordset where the ActiveConnection property has been set, will cause an ADO error. The method has the following syntax: pfields->Append(Name, Type, DefinedSize, Attrib);
238
n Chapter Five—An ADO Primer The method has the following parameters: Name This parameter is a String holding the name of the new Field object, which must not be the same name as any other object in pfields. Type This parameter is a DataTypeEnum, whose default value is adEmpty; it holds the data type of the new field. DefinedSize This optional parameter is a Long and is the defined size in characters or bytes of the new field. The default value for this parameter is derived from Type. (The default Type is adEmpty, and therefore the default DefinedSize is unspecified.) Attrib This optional parameter is a FieldAttributeEnum that specifies attributes for the new field. The default value for this parameter is adFldDefault. If this value is not specified, the field will contain attributes derived from Type.
Delete Method (Fields Collection) This method deletes an object from the Fields collection. Calling the Fields. Delete method on an open Recordset causes an ADO error. The method has the following syntax: pfields->Delete(Field);
The method has the following parameter: Field This parameter is a Variant designating the Field object to delete, which must be the name of the Field object; it cannot be an ordinal position or the Field object itself.
Item Method (Fields Collection) This method returns a specific member of a collection by name or ordinal number. An application should use the Item method to return a specific object in a collection. If the method cannot find an object in the collection corresponding to the Index argument, an ADO error occurs. The method has the following syntax: pField = pFieldscollection->Item(Index);
The method returns an interface pointer to an ADO Field object.
Chapter Five—An ADO Primer n
239
The method has the following parameter: Index This parameter is a Variant that evaluates either to the name or to the ordinal number of an object in a collection. Warning
Some collections don’t support named objects; for these collections, an application must use ordinal number references.
Refresh Method (Fields Collection) This method updates the Field objects in a Fields collection to reflect objects available from and specific to the OLE DB provider. The method has the following syntax: pFieldscollection->Refresh();
The method has no parameters. Note
In ADO 2.0, using the Refresh method on the Fields collection has no visible effect. To retrieve changes from the underlying database structure, an application must use either the Requery method or, if the Recordset object does not support bookmarks, the MoveFirst method.
The Parameters Collection A Command object has a Parameters collection made up of Parameter objects (interface pointers). Using the Refresh method on a Command object’s Parameters collection retrieves parameter information for the stored procedure or parameterized query specified in the Command object. Some providers do not support stored procedure calls or parameterized queries; calling the Refresh method on the Parameters collection when using such a provider will return an error. If an application has not defined its own Parameter objects and it accesses the Parameters collection before calling the Refresh method, ADO will automatically call the method and populate the collection for it. For this reason, an application can minimize calls to the provider to improve performance if it knows the properties of the parameters associated with the stored procedure or parameterized query it wishes to call. An application can use the CreateParameter method to create Parameter objects with the appropriate property settings and use the Append method to add them to the Parameters collection. This lets the application set and return parameter values without having to call the provider for the parameter information. If an application is writing to a provider that does not supply
240
n Chapter Five—An ADO Primer parameter information, it must manually populate the Parameters collection using the CreateParameter method to be able to use parameters at all. An application should use the Delete method to remove Parameter objects from the Parameters collection if necessary. The Parameters collection supports four methods: Append, Delete, Item, and Refresh.
Append Method (Parameters Collection) Use the Append method to add a new Parameter object to the Parameters collection of a Command object. The application must set the Type property of the Parameter object before appending it to the Parameters collection. If an application selects a variable-length data type, it must also set the Size property to a value greater than zero. The method has the following syntax: pParameters->Append(object);
The method has the following parameter: object This parameter is the Parameter interface pointer to be appended.
Delete Method (Parameters Collection) This method deletes an object from the Parameters collection. The application must use the Parameter object’s Name property or its collection index when calling the Delete method; an object variable is not a valid argument. The method has the following syntax: pParameters->Delete(Index);
The method has the following parameter: Index This parameter is a String representing the name of the Parameter object the application wants to delete, or the object’s ordinal position (index) in the collection.
Item Method (Parameters Collection) This method returns a specific member of a collection by name or ordinal number. An application should use the Item method to return a specific object in a collection. If the method cannot find an object in the collection corresponding to the Index argument, an ADO error occurs. The method has the following syntax: pParameter = pParameterscollection->Item(Index);
Chapter Five—An ADO Primer n
241
The method returns an interface pointer to an ADO Parameter object. The method has the following parameter: Index This parameter is a Variant that evaluates either to the name or to the ordinal number of an object in a collection. Warning
Some collections don’t support named objects; for these collections, an application must use ordinal number references.
Refresh Method (Parameters Collection) Using the Refresh method on a Command object’s Parameters collection retrieves provider-side parameter information for the stored procedure or parameterized query specified in the Command object. The collection will be empty for providers that do not support stored procedure calls or parameterized queries. An application should set the ActiveConnection property of the Command object to a valid Connection object, the CommandText property to a valid command, and the CommandType property to adCmdStoredProc before calling the Refresh method. If an application accesses the Parameters collection before calling the Refresh method, ADO will automatically call the method and populate the collection for it. The method has the following syntax: pParameterscollection->Refresh();
The method has no parameters. Note
If an application uses the Refresh method to obtain parameter information from the provider and it returns one or more variable-length data type Parameter objects, ADO may allocate memory for the parameters based on their maximum potential size, which will cause an error when executed. An application should explicitly set the Size property for these parameters before calling the Execute method to prevent errors.
The Properties Collection Some ADO objects (interface pointers) have a Properties collection made up of Property objects. Each Property object corresponds to a characteristic of the ADO object specific to the provider currently in use. The Properties collection supports two methods: Item and Refresh.
242
n Chapter Five—An ADO Primer
Item Method (Properties Collection) This method returns a specific member of a collection by name or ordinal number. An application should use the Item method to return a specific object in a collection. If the method cannot find an object in the collection corresponding to the Index argument, an ADO error occurs. The method has the following syntax: pProperty = pPropertiescollection->Item(Index);
The method returns an interface pointer to an ADO Property object. The method has the following parameter: Index This parameter is a Variant that evaluates either to the name or to the ordinal number of an object in a collection. Warning
Some collections don’t support named objects; for these collections, an application must use ordinal number references.
Refresh Method (Properties Collection) Using the Refresh method on a Properties collection of some objects populates the collection with the dynamic properties the provider exposes in the form of Property interface pointers (objects). These properties provide information about functionality specific to the provider beyond the built-in properties ADO supports; consult the specific OLE DB provider documentation for specific property information. The method has the following syntax: pPropertiescollection->Refresh();
The method has no parameters.
The ADO Connection Object An ADO Connection object represents a unique session with a data source (an OLE DB provider). In the case of a client/server database system, it may be an actual network connection to the server. To execute a query without using a Command object, an application can pass a query string to the Execute method of a Connection object. (A Command object is still required when an application wants to persist the command text and re-execute it, or use query parameters.) Applications can create Connection objects independently of any other previously defined object.
Chapter Five—An ADO Primer n
243
One of the more interesting uses of Connection objects is that they permit an application to execute commands or stored procedures as if they were native methods on the Connection object. To execute such a command, an application must give the command a name using the Command object Name property, then set the Command object’s ActiveConnection property to the connection. Next, the application must include a program statement where the command name is used as if it were a method on the Connection object, followed by any parameters, followed by a Recordset object if any rows are returned. The application can also set the recordset properties to customize the resulting recordset. To execute a stored procedure, an application simply must contain a program statement where the stored procedure name is used as if it were a method on the Connection object, followed by any parameters. (ADO will make a “best guess” of parameter types.) Using the collections, methods, and properties of a Connection object, an application can do any or all of the following: n
Configure the connection before opening it with the ConnectionString, ConnectionTimeout, and Mode properties.
n
Set the CursorLocation property to invoke the client cursor provider, which supports batch updates.
n
Set the default database for the connection with the DefaultDatabase property.
n
Set the level of isolation for the transactions opened on the connection with the IsolationLevel property.
n
Specify an OLE DB provider with the Provider property.
n
Establish, and later break, the physical connection to the data source with the Open and Close methods.
n
Execute a command on the connection with the Execute method and configure the execution with the CommandTimeout property.
n
Manage transactions on the open connection, including nested transactions if the provider supports them, with the BeginTrans, CommitTrans, and RollbackTrans methods and the Attributes property.
n
Examine errors returned from the data source with the Errors collection.
n
Read the version from the ADO implementation in use with the Version property.
n
Obtain schema information about its database with the OpenSchema method.
Warning
Depending on the functionality supported by the OLE DB provider in use, some collections, methods, or properties of a Connection object may not be available.
244
n Chapter Five—An ADO Primer Each of the properties and methods of the Connection object is discussed below.
Attributes Property (Connection Object) This property is read/write. It sets or returns a Long value, which is the sum of any one or more of the XactAttributeEnum values in Table 5-1 (default is zero). Table 5-1
Connection object XactAttributeEnum values
Constant adXactCommitRetaining
adXactAbortRetaining
Description This value indicates that calling CommitTrans automatically starts a new transaction. Not all providers support this feature. This value indicates that calling RollbackTrans automatically starts a new transaction. Not all providers support this feature.
CommandTimeout Property (Connection Object) This property indicates how long the ADO run-time system should wait while executing a command before terminating the attempt and generating an error (presumably due to a network connection failure). The property is read/write (even after a connection is opened), with a default value of 30 seconds. If the interval set in the CommandTimeout property elapses before the command completes execution, an error occurs and ADO cancels the command. If an application sets the property to zero, ADO will wait indefinitely until the execution is complete. Note
The CommandTimeout setting on a Connection object has no effect on the CommandTimeout setting on a Command object on the same Connection; that is, the Command object’s CommandTimeout property does not inherit the value of the Connection object’s CommandTimeout value.
Warning
If an application uses CommandTimeout functionality without support from the underlying OLE DB provider, results are unpredictable.
ConnectionString Property (Connection Object) This property contains the information used to establish a connection to a data source (i.e., an OLE DB provider). The property is a String, and is read/write until a connection is opened and read-only thereafter. Applications use the ConnectionString property to specify a data source by passing a detailed series of argument = value statements separated by semicolons, as explained below. After an application has set the ConnectionString property
Chapter Five—An ADO Primer n
245
and opened the Connection object, the provider may alter the contents of the property by mapping the ADO-defined argument names to their provider equivalents. The ConnectionString property automatically inherits the value used for the ConnectionString argument of the Open method, so an application can override the current ConnectionString property during the Open method call. ADO supports four arguments for the ConnectionString property, which are listed in Table 5-2; any other arguments pass directly to the provider without any processing by ADO. Table 5-2
ConnectionString arguments processed by ADO
Argument Provider= File Name= Remote Provider=
Remote Server=
Description This argument specifies the name of an OLE DB provider to use for the connection. This argument specifies the name of a provider-specific file containing preset connection information. This argument specifies the name of a provider to use when opening a client-side connection. This argument is used by the Remote Data Service only. This argument specifies the path name of the server to use when opening a client-side connection. This argument is used by the Remote Data Service only.
Warning
Because the File Name= argument causes ADO to load the associated provider, an application cannot pass both the Provider= and File Name= arguments to a ConnectionString.
ConnectionTimeout Property (Connection Object) This property indicates how long the ADO run time should wait while establishing a connection before terminating the attempt and generating an error (usually due to a failed network transmission). The property is a Long value with a default of 15 seconds; the property is read/write until a connection is opened and read-only afterwards. If an application sets the property to zero, ADO will wait indefinitely until the connection is opened. Warning
If an application uses ConnectionTimeout functionality without support from the underlying OLE DB provider, results are unpredictable.
CursorLocation Property (Connection Object) This property determines the location of the cursor engine to be used by ADO, based on various cursor libraries accessible to the OLE DB provider in use. The property is a Long value that is read/write regardless of whether a connection is open or not, but the setting affects connections established only after the property has been set; changing this property has no effect on
246
n Chapter Five—An ADO Primer existing connections. The cursor obtained via an Execute call will automatically inherit the setting of this property. Table 5-3 gives the possible values of this property: Table 5-3
Connection object CursorLocation property values
Constant adUseNone
adUseClient
adUseServer
Description This value indicates that no cursor services are used; it is obsolete and should be used only for the sake of backward compatibility with legacy code. This value indicates that the connection uses client-side cursors supplied by a local cursor library. Since local cursor engines often will allow features that driver-supplied cursors may not, using this setting may enable extra features. (adUseClientBatch is an alternative value for this constant.) This value is the default; it indicates that the connection uses OLE DB provider- or driver-supplied cursors. Some features of the Microsoft Client Cursor Provider (such as disassociated recordsets) cannot be simulated with server-side cursors, but other provider-specific features may be supported instead.
DefaultDatabase Property (Connection Object) This property indicates the default database for a Connection object. This property is a String that evaluates to the name of a database available from the current OLE DB provider. If there is a default database, SQL strings sent as commands may use an unqualified syntax to access objects in that database. To access objects in a database other than the one specified in the DefaultDatabase property, SQL strings must qualify object names with the desired database name. Upon connection, the provider will write default database information to the DefaultDatabase property if it uses the property. Warning
Some OLE DB providers allow only one database per connection, in which case an application cannot change the DefaultDatabase property. Also, some OLE DB providers may not support this feature, and will return an error or an empty string.
IsolationLevel Property (Connection Object) This property indicates the level of isolation for a Connection object. The property contains one or more of the IsolationLevelEnum values shown in Table 5-4 below. The setting does not take effect until the next time an application calls the BeginTrans method.
Chapter Five—An ADO Primer n
247
Warning
If the level of isolation an application requests is unavailable, the OLE DB provider may return the next greater level of isolation.
Table 5-4
Connection object IsolationLevelEnum values
Constant adXactUnspecified
adXactChaos
adXactBrowse
adXactReadUncommitted adXactCursorStability
adXactReadCommitted adXactRepeatableRead
adXactIsolated adXactSerializable
Description This value indicates that the provider is using a different isolation level than specified, but that the level cannot be determined. This value is the default; it indicates that an application cannot overwrite pending changes from more highly isolated transactions. This value indicates that from one transaction an application can view uncommitted changes in other transactions. This value is the same as adXactBrowse. This value indicates that from one transaction an application can view changes in other transactions only after they’ve been committed. This value is the same as adXactCursorStability. This value indicates that from one transaction an application cannot see changes made in other transactions, but that requerying can bring new recordsets. This value indicates that transactions are conducted in isolation of other transactions. This value is the same as adXactIsolated.
Mode Property (Connection Object) This property indicates the available permissions for modifying data in a connection. The property is one of the ConnectModeEnum values listed in Table 5-5 below; it is read/write when the connection is not open and read-only when a connection is open. Table 5-5
Connection object ConnectModeEnum and ConnectOptionEnum values
Constant adModeUnknown
adModeRead adModeWrite adModeReadWrite
Description This value is the default, and indicates that the permissions have not yet been set or cannot be determined. This value indicates read-only permissions. This value indicates write-only permissions. This value indicates read/write permissions.
248
n Chapter Five—An ADO Primer Table 5-5 Connection object ConnectModeEnum and ConnectOptionEnum values (cont.) Constant Description adModeShareDenyRead This value indicates that ADO prevents others from opening the connection with read permissions. adModeShareDenyWrite This value indicates that ADO prevents others from opening the connection with write permissions. adModeShareExclusive This value indicates that ADO prevents others from opening the connection. adModeShareDenyNone This value indicates that ADO prevents others from opening the connection with any permissions.
Provider Property (Connection Object) This property indicates the name of the OLE DB provider for a Connection object. The property is a String value that is read/write when a connection is closed and read-only when it is open. If no provider is specified, the property will default to MSDASQL (Microsoft OLE DB provider for ODBC). The setting does not take effect until an application either opens the Connection object or accesses the Properties collection of the Connection object. Warning
This property can also be set by the contents of the ConnectionString property or the ConnectionString argument of the Open method; however, specifying a provider in more than one place while calling the Open method can have unpredictable results. If an invalid setting is placed in the property, an ADO error occurs.
State Property (Connection Object) This is a read-only Long property that indicates whether the Connection object is currently open (interacting with an OLE DB provider) or closed (not interacting with an OLE DB provider). The possible values are listed in Table 5-6. Table 5-6
Connection object State property values
Constant adStateClosed adStateOpen
Description This value is the default; it indicates that the Connection object is closed. This value indicates that the Connection object is open.
Version Property (Connection Object) This property indicates the ADO version number as a String value. An application should use it to determine which ADO implementation is currently available on a given client or server computer, and degrade gracefully if a lesser version than expected is found.
Chapter Five—An ADO Primer n
249
Note
The version information of a given OLE DB provider will usually be available as a dynamic Property object in the Properties collection.
BeginTrans Method (Connection Object) An application should use the BeginTrans, CommitTrans, and RollbackTrans methods with a Connection object when it wants to save or cancel a series of changes made to the source data as a single unit. Once an application calls the BeginTrans method, the OLE DB provider will no longer instantaneously commit any changes the application makes until it calls CommitTrans or RollbackTrans to end the transaction. For OLE DB providers that support nested transactions, calling the BeginTrans method within an open transaction starts a new, nested transaction. The return value indicates the level of nesting: A return value of 1 indicates the application has opened a top-level transaction (that is, the transaction is not nested within another transaction), 2 indicates that the application has opened a second-level transaction (a transaction nested within a top-level transaction), and so forth. Calling CommitTrans or RollbackTrans affects only the most recently opened transaction; the application must close or roll back the current transaction before it can resolve any higher-level transactions. (Note that rolling back a nested transaction does not automatically force a rollback of a higher-level one.) Calling the CommitTrans method saves changes made within an open transaction on the connection and ends the transaction. Calling the RollbackTrans method reverses any changes made within an open transaction and ends the transaction. Calling either method when there is no open transaction generates an ADO error. Note
Depending on the Connection object’s Attributes property, calling either the CommitTrans or RollbackTrans methods may automatically start a new transaction. If the Attributes property is set to adXactCommitRetaining, the OLE DB provider automatically starts a new transaction after a CommitTrans call. If the Attributes property is set to adXactAbortRetaining, the OLE DB provider automatically starts a new transaction after a RollbackTrans call.
Warning
Not all OLE DB providers support transactions. To determine if a given OLE DB provider does support transactions, check that the providerdefined Transaction DDL property appears in the Connection object’s Properties collection. If an application calls any of the transaction-oriented Connection object methods for an OLE DB provider that does not support transactions, an ADO error will result.
250
n Chapter Five—An ADO Primer The method has the following two syntaxes: level = pConnection->BeginTrans(); pConnection->BeginTrans();
The method can be called as a function that returns a Long value indicating the nesting level of the transaction. The method has no parameters.
Cancel Method (Connection Object) Applications should use the Cancel method to terminate execution of an asynchronous Execute or Open method call (that is, the method was invoked with the adConnectAsync, adExecuteAsync, or adFetchAsync option). Cancel will return an ADO error if adRunAsync was not used in the terminated method. The method has the following syntax: pConnection->Cancel();
The method has no parameters.
Close Method (Connection Object) Applications should use the Close method on a Connection object to free any associated system resources needed while it is active. Closing a Connection object does not remove it from memory; the application can change its property settings and open it again later. Using the Close method to close a Connection object also closes any active Recordset objects associated with the connection. A Command object associated with the Connection object the application is closing will persist, but it will no longer be associated with a Connection object; that is, its ActiveConnection property will be set to Nothing. Also, the Command object’s Parameters collection will be cleared of any provider-defined parameters. An application can later call the Open method to re-establish the connection to the same or another data source. While the Connection object is closed, calling any methods that require an open connection to the data source will generate an ADO error. Closing a Connection object while there are open Recordset objects on the connection rolls back any pending changes in all of the Recordset objects. Explicitly closing a Connection object (calling the Close method) while a transaction is in progress generates an ADO error. If a Connection object falls out of scope while a transaction is in progress, ADO automatically rolls back the transaction. The method has the following syntax: pConnection->Close();
Chapter Five—An ADO Primer n
251
The method has no parameters.
CommitTrans Method (Connection Object) An application should use the BeginTrans, CommitTrans, and RollbackTrans methods with a Connection object when it wants to save or cancel a series of changes made to the source data as a single unit. Once an application calls the BeginTrans method, the OLE DB provider will no longer instantaneously commit any changes the application makes until it calls CommitTrans or RollbackTrans to end the transaction. For OLE DB providers that support nested transactions, calling the BeginTrans method within an open transaction starts a new, nested transaction. The return value indicates the level of nesting: A return value of 1 indicates the application has opened a top-level transaction (that is, the transaction is not nested within another transaction), 2 indicates that the application has opened a second-level transaction (a transaction nested within a top-level transaction), and so forth. Calling CommitTrans or RollbackTrans affects only the most recently opened transaction; the application must close or roll back the current transaction before it can resolve any higher-level transactions. (Note that rolling back a nested transaction does not automatically force a rollback of a higher-level one.) Calling the CommitTrans method saves changes made within an open transaction on the connection and ends the transaction. Calling the RollbackTrans method reverses any changes made within an open transaction and ends the transaction. Calling either method when there is no open transaction generates an ADO error. Note
Depending on the Connection object’s Attributes property, calling either the CommitTrans or RollbackTrans methods may automatically start a new transaction. If the Attributes property is set to adXactCommitRetaining, the OLE DB provider automatically starts a new transaction after a CommitTrans call. If the Attributes property is set to adXactAbortRetaining, the OLE DB provider automatically starts a new transaction after a RollbackTrans call.
Warning
Not all OLE DB providers support transactions. To determine if a given OLE DB provider does support transactions, check that the providerdefined Transaction DDL property appears in the Connection object’s Properties collection. If an application calls any of the transaction-oriented Connection object methods for an OLE DB provider that does not support transactions, an ADO error will result.
The method has the following syntax: pConnection->CommitTrans();
252
n Chapter Five—An ADO Primer The method has no parameters.
Execute Method (Connection Object) An application should use the Execute method on a Connection object to execute whatever query the application passes to the method in the CommandText argument on the specified connection. If the CommandText argument specifies a row-returning query, any results the execution generates are stored in a new Recordset object. If the command is not a row-returning query, the provider returns a closed Recordset object. The returned Recordset object is always a read-only, forward-only cursor. If an application needs a Recordset object with more functionality, it should first create a Recordset object with the desired property settings, then use the Recordset object’s Open method to execute the query and return the desired cursor type. The contents of the CommandText argument are specific to the provider and can be standard SQL syntax or any special command format that the provider supports. An ExecuteComplete event will be issued when this operation concludes. The method has the following syntax for a non–row-returning command string: pConnection->Execute(CommandText, RecordsAffected, Options);
The method has the following syntax for a row-returning command string: pRecordset = pConnection->Execute(CommandText, RecordsAffected, Options);
The method returns a Recordset interface pointer reference for the second syntax. The method has the following parameters for both syntax options: CommandText This parameter contains a String with the SQL statement, table name, stored procedure, or provider-specific text to execute. RecordsAffected This optional parameter contains a Long variable through which the provider returns the number of records that the operation affected. Options This optional parameter contains a Long value that indicates how the provider should evaluate the CommandText argument. It can be one of the values in Table 5-7:
Chapter Five—An ADO Primer n Table 5-7
253
Connection object Execute method Options parameter values
Constant adCmdText adCmdTable adCmdTableDirect adCmdTable adCmdStoredProc adCmdUnknown adExecuteAsync adFetchAsync
Description This value indicates that the OLE DB provider should evaluate CommandText as a textual definition of a command. This value indicates that ADO should generate a SQL query to return all rows from the table named in CommandText. This value indicates that the OLE DB provider should return all rows from the table named in CommandText. This value indicates that the OLE DB provider should evaluate CommandText as a table name. This value indicates that the OLE DB provider should evaluate CommandText as a stored procedure. This value indicates that the type of command in the CommandText argument is not known. This value indicates that the command should execute asynchronously. This value indicates that the remaining rows after the initial quantity specified in the CacheSize property should be fetched asynchronously.
Open Method (Connection Object) Applications should use the Open method on a Connection object to establish the physical connection to a data source (usually an OLE DB provider). After this method successfully completes, the connection is live and the application can issue commands against it and process the results. The application can use the optional ConnectionString argument to specify a string containing a series of argument = value statements separated by semicolons. The ConnectionString property automatically inherits the value used for the ConnectionString argument. Therefore, the application can either set the ConnectionString property of the Connection object before opening it, or use the ConnectionString argument to set or override the current connection parameters during the Open method call. If an application chooses to pass user and password information both in the ConnectionString argument and in the optional UserID and Password arguments, the UserID and Password arguments will override the values specified in ConnectionString. When an application has concluded its operations over an open connection, it should use the Close method to free any associated system resources, as noted above. The method has the following syntax: pConnection->Open(ConnectionString, UserID, Password, OpenOptions);
254
n Chapter Five—An ADO Primer The method has the following parameters: ConnectionString Optional. A String containing connection information. See the ConnectionString property for details on valid settings. UserID This optional parameter contains a String with a user name to use when establishing the connection. Password This optional parameter contains a String with a password to use when establishing the connection. OpenOptions This optional parameter contains a ConnectOptionEnum value. If set to adConnectAsync, the connection will be opened asynchronously. A ConnectComplete event will be issued when the connection is available in this case.
OpenSchema Method (Connection Object) Applications should use this method to obtain database schema information from the OLE DB provider. The OpenSchema method returns information about the data source, such as information about the tables on the server and the columns in the tables. The Criteria argument is an array of values that can be used to limit the results of a schema query. Each schema query has a different set of parameters that it supports. The actual schemas are defined by the OLE DB specification under the IDBSchemaRowset interface. The constant adSchemaProviderSpecific is used for the QueryType argument if the OLE DB provider defines its own nonstandard schema queries. When this constant is used, the SchemaID argument is required to pass the GUID of the schema query to execute. If QueryType is set to adSchemaProviderSpecific but SchemaID is not provided, an ADO error will result. OLE DB providers are not required to support all of the OLE DB standard schema queries. Specifically, only adSchemaTables, adSchemaColumns, and adSchemaProviderTypes are required by the OLE DB specification. The method has the following syntax: pRecordset = pConnection->OpenSchema(QueryType, Criteria, SchemaID);
The method returns a Recordset object that contains schema information, which will be opened as a read-only, static cursor. The method has the following parameters: QueryType This parameter contains the type of schema query to run; it can be any of the constants listed in Table 5-8.
Chapter Five—An ADO Primer n
255
Criteria This parameter is optional; if included it is an array of query constraints for each QueryType option shown in Table 5-8. SchemaID This parameter contains the GUID for a provider-schema schema query not defined by the OLE DB specification. This parameter is required if QueryType is set to adSchemaProviderSpecific; otherwise, it is not used. Table 5-8 Connection object OpenSchema method QueryType and Criteria parameter values Constant
Description
adSchemaAsserts
CONSTRAINT_CATALOG CONSTRAINT_SCHEMA CONSTRAINT_NAME
adSchemaCatalogs
CATALOG_NAME
adSchemaCharacterSets
CHARACTER_SET_CATALOG CHARACTER_SET_SCHEMA CHARACTER_SET_NAME
adSchemaCheckConstraints
CONSTRAINT_CATALOG CONSTRAINT_SCHEMA CONSTRAINT_NAME
adSchemaCollations
COLLATION_CATALOG COLLATION_SCHEMA COLLATION_NAME
adSchemaColumnDomainUsage
DOMAIN_CATALOG DOMAIN_SCHEMA DOMAIN_NAME COLUMN_NAME
adSchemaColumnPrivileges
TABLE_CATALOG TABLE_SCHEMA TABLE_NAME COLUMN_NAME GRANTOR GRANTEE
adSchemaColumns
TABLE_CATALOG TABLE_SCHEMA TABLE_NAME COLUMN_NAME
adSchemaConstraintColumnUsage
TABLE_CATALOG TABLE_SCHEMA TABLE_NAME COLUMN_NAME
256
n Chapter Five—An ADO Primer Table 5-8 Connection object OpenSchema method QueryType and Criteria parameter values (cont.) Constant
Description
adSchemaConstraintTableUsage
TABLE_CATALOG TABLE_SCHEMA TABLE_NAME
adSchemaForeignKeys
PK_TABLE_CATALOG PK_TABLE_SCHEMA PK_TABLE_NAME FK_TABLE_CATALOG FK_TABLE_SCHEMA FK_TABLE_NAME
adSchemaIndexes
TABLE_CATALOG TABLE_SCHEMA INDEX_NAME TYPE TABLE_NAME
adSchemaKeyColumnUsage
CONSTRAINT_CATALOG CONSTRAINT_SCHEMA CONSTRAINT_NAME TABLE_CATALOG TABLE_SCHEMA TABLE_NAME COLUMN_NAME
adSchemaPrimaryKeys
PK_TABLE_CATALOG PK_TABLE_SCHEMA PK_TABLE_NAME
adSchemaProcedureColumns
PROCEDURE_CATALOG PROCEDURE_SCHEMA PROCEDURE_NAME COLUMN_NAME
adSchemaProcedureParameters
PROCEDURE_CATALOG PROCEDURE_SCHEMA PROCEDURE_NAME PARAMETER_NAME
adSchemaProcedures
PROCEDURE_CATALOG PROCEDURE_SCHEMA PROCEDURE_NAME PARAMETER_TYPE
adSchemaProviderSpecific
<see above>
adSchemaProviderTypes
DATA_TYPE BEST_MATCH
Chapter Five—An ADO Primer n Table 5-8 Connection object OpenSchema method QueryType and Criteria parameter values (cont.) Constant
Description
adSchemaReferentialConstraints
CONSTRAINT_CATALOG CONSTRAINT_SCHEMA CONSTRAINT_NAME
adSchemaSchemata
CATALOG_NAME SCHEMA_NAME SCHEMA_OWNER
adSchemaSQLLanguages
<none>
adSchemaStatistics
TABLE_CATALOG TABLE_SCHEMA TABLE_NAME
adSchemaTableConstraints
CONSTRAINT_CATALOG CONSTRAINT_SCHEMA CONSTRAINT_NAME TABLE_CATALOG TABLE_SCHEMA TABLE_NAME CONSTRAINT_TYPE
adSchemaTablePrivileges
TABLE_CATALOG TABLE_SCHEMA TABLE_NAME GRANTOR GRANTEE
adSchemaTables
TABLE_CATALOG TABLE_SCHEMA TABLE_NAME TABLE_TYPE
adSchemaTranslations
TRANSLATION_CATALOG TRANSLATION_SCHEMA TRANSLATION_NAME
adSchemaUsagePrivileges
OBJECT_CATALOG OBJECT_SCHEMA OBJECT_NAME OBJECT_TYPE GRANTOR GRANTEE
adSchemaViewColumnUsage
VIEW_CATALOG VIEW_SCHEMA VIEW_NAME
257
258
n Chapter Five—An ADO Primer Table 5-8 Connection object OpenSchema method QueryType and Criteria parameter values (cont.) Constant
Description
adSchemaViewTableUsage
VIEW_CATALOG VIEW_SCHEMA VIEW_NAME
adSchemaViews
TABLE_CATALOG TABLE_SCHEMA TABLE_NAME
RollbackTrans Method (Connection Object) An application should use the BeginTrans, CommitTrans, and RollbackTrans methods with a Connection object when it wants to save or cancel a series of changes made to the source data as a single unit. Once an application calls the BeginTrans method, the OLE DB provider will no longer instantaneously commit any changes the application makes until it calls CommitTrans or RollbackTrans to end the transaction. For OLE DB providers that support nested transactions, calling the BeginTrans method within an open transaction starts a new, nested transaction. The return value indicates the level of nesting: A return value of 1 indicates the application has opened a top-level transaction (that is, the transaction is not nested within another transaction), 2 indicates that the application has opened a second-level transaction (a transaction nested within a top-level transaction), and so forth. Calling CommitTrans or RollbackTrans affects only the most recently opened transaction; the application must close or roll back the current transaction before it can resolve any higher-level transactions. (Note that rolling back a nested transaction does not automatically force a rollback of a higher-level one.) Calling the CommitTrans method saves changes made within an open transaction on the connection and ends the transaction. Calling the RollbackTrans method reverses any changes made within an open transaction and ends the transaction. Calling either method when there is no open transaction generates an ADO error. Note
Depending on the Connection object’s Attributes property, calling either the CommitTrans or RollbackTrans methods may automatically start a new transaction. If the Attributes property is set to adXactCommitRetaining, the OLE DB provider automatically starts a new transaction after a CommitTrans call. If the Attributes property is set to adXactAbortRetaining, the OLE DB provider automatically starts a new transaction after a RollbackTrans call.
Chapter Five—An ADO Primer n
259
Warning
Not all OLE DB providers support transactions. To determine if a given OLE DB provider does support transactions, check that the providerdefined Transaction DDL property appears in the Connection object’s Properties collection. If an application calls any of the transaction-oriented Connection object methods for an OLE DB provider that does not support transactions, an ADO error will result.
The method has the following syntax: pConnection->RollbackTrans();
The method has no parameters.
The ADO Command Object Applications have three uses for a Command object: to query a database and return records in a Recordset object, to execute a bulk operation, and to manipulate the structure of a database. A Command object is required when an application wants to persist command text and re-execute it, or use query parameters. To create a Command object independently of a previously defined Connection object, an application should first set its ActiveConnection property to a valid connection string. ADO will still create a Connection object, but it doesn’t assign that object to an object variable. To associate multiple Command objects with the same connection, an application should explicitly create and open a Connection object; this assigns the Connection object to an object variable. If an application does not set the Command objects’ ActiveConnection property to the Connection object variable, ADO creates a new Connection object for each Command object, even if an application uses the same connection string. To execute a command, simply call it by its Name property on the associated Connection object. The command must have its ActiveConnection property set to the Connection object. If the command has parameters, pass values for them as arguments to the method. With the collections, methods, and properties of a Command object, an application can do the following: n
Define the executable text of the command (for example, a SQL statement) with the CommandText property.
n
Define parameterized queries or stored procedure arguments with Parameter objects and the Parameters collection.
n
Execute a command and return a Recordset object if appropriate with the Execute method.
n
Specify the type of command with the CommandType property prior to execution to optimize performance.
n
Control whether or not the provider saves a prepared (or compiled) version of the command prior to execution with the Prepared property.
260
n Chapter Five—An ADO Primer n
Set the number of seconds a provider will wait for a command to execute with the CommandTimeout property.
n
Associate an open connection with a Command object by setting its ActiveConnection property.
n
Set the Name property to identify the Command object as a method on the associated Connection object.
n
Pass a Command object to the Source property of a Recordset object in order to obtain data.
CommandText Property (Command Object) This property contains the text of a command that an application wants to issue against an OLE DB provider. The property is a String value containing an OLE DB provider command, such as a SQL statement, a table name, or a stored procedure call; its default value is the empty string. Usually, the value of CommandText will be a SQL statement, but it can also be any other type of command statement recognized by the OLE DB provider, such as a stored procedure call. A SQL statement must be of the particular dialect or version supported by the OLE DB provider’s query processor. If the Prepared property of the Command object is set to True and the Command object is bound to an open connection when an application sets the CommandText property, ADO prepares the query (that is, a compiled form of the query is stored by the OLE DB provider) when an application calls the Execute or Open methods. Depending on the CommandType property setting, ADO may alter the CommandText property. An application can read the CommandText property at any time to check the actual text that ADO will use during execution.
CommandTimeout Property (Command Object) This property indicates how long the ADO run time should wait while executing a command before terminating the attempt and generating an error (usually due to a failed network transmission). The property is a Long value with a default of 30 seconds; the property is read/write regardless of whether a connection is open or closed. If an application sets the property to zero, ADO will wait indefinitely until the command is completed. Warning
If an application uses CommandTimeout functionality without support from the underlying OLE DB provider, results are unpredictable.
CommandType Property (Command Object) Applications should use the CommandType property to optimize evaluation of the CommandText property. If the CommandType property value equals adCmdUnknown (the default value), an application may experience diminished performance because ADO must make calls to the provider to determine if the CommandText property is a SQL statement, a stored
Chapter Five—An ADO Primer n
261
procedure, or a table name. If an application knows what type of command it is using, setting the CommandType property instructs ADO to go directly to the relevant code. If the CommandType property does not match the type of command in the CommandText property, an ADO error occurs when you call the Execute method. CommandType can be one or more of the CommandTypeEnum values in Table 5-9. Note
The adExecuteNoRecords constant improves performance by minimizing internal processing. This constant never stands alone; it is always combined with adCmdText or adCmdStoredProc. An ADO error results if adExecuteNoRecords is used with the Recordset.Open method or a Command object used by that method.
Table 5-9
Command object CommandTypeEnum values
Constant adCmdText adCmdTable
adCmdTableDirect adCmdStoredProc adCmdUnknown
adCmdFile adExecuteNoRecords
Description This value indicates that ADO should evaluate CommandText as a textual definition of a command or stored procedure call. This value indicates that ADO should evaluate CommandText as a table name whose columns are all returned by an internally generated SQL query. This value indicates that ADO should evaluate CommandText as a table name whose columns are all returned. This value indicates that ADO should evaluate CommandText as a stored procedure name. This value is the default; it indicates that the type of command in the CommandText property is not known and that ADO cannot perform any internal processing prior to sending the command to the OLE DB provider. This value indicates that ADO should evaluate CommandText as the filename of a persisted Recordset. This value indicates that CommandText is a command or stored procedure that does not return rows; if any rows are retrieved, they are discarded and not returned. This constant is always combined with adCmdText or adCmdStoredProc.
Prepared Property (Command Object) This property indicates whether or not an OLE DB provider should save a compiled version of a command before execution.The property is a read/write Boolean value. Setting it to True may slow a command’s first execution, but once the OLE DB provider compiles a command, the provider will use the compiled version of the command for any subsequent executions, which will result in improved performance.
262
n Chapter Five—An ADO Primer Warning
If the OLE DB provider does not support command preparation, it may return an error as soon as this property is set to True. If it does not return an error, the OLE DB provider simply ignores the request to prepare the command and sets the Prepared property to False.
State Property (Command Object) This is a read-only Long property which indicates whether the Command object is currently open (interacting with an OLE DB provider) or closed (not interacting with an OLE DB provider). The possible values are listed in Table 5-10. Table 5-10
Command object State property values
Constant adStateClosed adStateOpen
Description This value is the default; it indicates that the Command object is closed. This value indicates that the Command object is open.
Cancel Method (Command Object) An application should use the Cancel method to terminate execution of an asynchronous Execute or Open method call (that is, the method was invoked with the adConnectAsync, adExecuteAsync, or adFetchAsync option). Cancel will return a run-time error if adRunAsync was not used in the method it is trying to terminate. The method has the following syntax: pCommand->Cancel();
The method has no parameters.
CreateParameter Method (Command Object) An application should use the CreateParameter method to create a new Parameter object with the specified name, type, direction, size, and value. Any values an application passes in the arguments are written to the corresponding Parameter properties. This method does not automatically append the Parameter object to the Parameters collection of a Command object; the application must explicitly do so using the Append method described earlier in this chapter. This lets the application set additional properties whose values ADO will validate when it appends the Parameter object to the collection. If no parameters are included, a default Parameter object is created. The method has the following syntax: pParameter = command->CreateParameter(Name, Type, Direction, Size, Value);
Chapter Five—An ADO Primer n
263
The method returns a Parameter object (interface pointer). The method has the following parameters: Name This optional parameter is a String representing the name of the Parameter object. Type This optional parameter is a Long value specifying the data type of the Parameter object, with values as noted for the Type property. Direction This optional parameter is a Long value specifying the type of Parameter object, with values as noted for the Direction property. Size This optional parameter is a Long value specifying the maximum length for the parameter value in characters or bytes. Value This optional parameter is a Variant specifying the Value property for the Parameter object.
Execute Method (Command Object) An application should use the Execute method on a Command object to execute the query specified in the CommandText property against the current OLE DB provider. If the CommandText property specifies a row-returning query, any results the execution generates are stored in a new Recordset object. If the command is not a row-returning query, the OLE DB provider returns a closed Recordset object. Some application languages allow an application to ignore this return value if no Recordset is desired. If the query has parameters, the current values for the Command object’s parameters are used unless the application chooses to override these with parameter values passed with the Execute call. An application can override a subset of the parameters by omitting new values for some of the parameters when calling the Execute method. The order in which the application should specify the parameters is the same order in which the method passes them. An ExecuteComplete event will be issued when this operation concludes. Warning
Output parameters will not return correct values when passed in the Parameters parameter.
The method has the following syntax for a row-returning Command object: pRecordset = pCommand.Execute(RecordsAffected, Parameters, Options);
264
n Chapter Five—An ADO Primer The method has the following syntax for a non–row-returning Command object: pCommand.Execute(RecordsAffected, Parameters, Options);
The method returns a Recordset object reference. The method has the following parameters: RecordsAffected This optional parameter is a Long variable from which the provider returns the number of records that the operation affected. Parameters This optional parameter is a Variant array of parameter values passed with a SQL statement. (It is important to remember that output parameters will not return correct values when passed in this argument.) Options This optional parameter is a Long value that indicates how the provider should evaluate the CommandText property of the Command object. It can contain one or more of the constants in Table 5-11. Table 5-11
Command object Execute method Options values
Constant adCmdText adCmdTable adCmdTableDirect adCmdStoredProc adCmdUnknown adExecuteAsync adFetchAsync
Description Indicates that the provider should evaluate CommandText as a textual definition of a command, such as a SQL statement. Indicates that ADO should generate a SQL query to return all rows from the table named in CommandText. Indicates that the provider should return all rows from the table named in CommandText. Indicates that the provider should evaluate CommandText as a stored procedure. Indicates that the type of command in CommandText is not known. Indicates that the command should execute asynchronously. Indicates that the remaining rows after the initial quantity specified in the CacheSize property should be fetched asynchronously.
The ADO Recordset Object An application should use Recordset objects to manipulate data from an OLE DB provider. All Recordset objects are constructed using records (rows) and fields (columns). The most important property for a Recordset is CursorType; it must be set prior to opening the Recordset either directly in the application’s program statements, or via passing a CursorType argument with the Open method. Some OLE DB providers don’t support all cursor types; an application should check the documentation for the specific OLE DB provider.
Chapter Five—An ADO Primer n
265
If an application doesn’t specify a cursor type, ADO opens a forward-only cursor by default. There are four different Recordset cursor types defined in ADO: n
Dynamic cursor This cursor allows an application to view additions, changes, and deletions by other users, and allows all types of movement through the Recordset that don’t rely on bookmarks. It also allows bookmarks if the OLE DB provider supports them.
n
Keyset cursor This cursor behaves like a dynamic cursor except that it prevents an application from seeing records that other users add and prevents access to records that other users delete. Data changes by other users will still be visible. It always supports bookmarks and therefore allows all types of movement through the Recordset.
n
Static cursor This cursor provides a static copy of a set of records for an application to use to find data or generate reports. It always allows bookmarks and therefore allows all types of movement through the Recordset. Additions, changes, or deletions by other users will not be visible. This is the only type of cursor allowed when an application opens a client-side (ADOR) Recordset object.
n
Forward-only cursor This cursor behaves identically to a dynamic cursor except that it allows an application to scroll only forward through records. This improves performance in situations where an application needs to make only a single pass through a Recordset.
When used with some providers (such as the Microsoft ODBC Provider for OLE DB in conjunction with Microsoft® SQL Server™), an application can create Recordset objects independently of a previously defined Connection object by passing a connection string with the Open method. ADO still creates a Connection object, but it doesn’t assign that object to an object variable. However, if an application is opening multiple Recordset objects over the same connection, it should explicitly create and open a Connection object; this assigns the Connection object to an object variable. If an application does not use this object variable when opening its Recordset objects, ADO creates a new Connection object for each new Recordset, even if the application passes the same connection string. An application can create as many Recordset objects as needed, subject to memory limitations. When an application opens a Recordset, the current record is positioned to the first record (if any) and the BOF and EOF properties are set to False. If there are no records, the BOF and EOF property settings are True. An application can use the MoveFirst, MoveLast, MoveNext, and MovePrevious methods, as well as the Move method, and the AbsolutePosition, AbsolutePage, and Filter properties to reposition the current record, assuming the provider supports the relevant functionality. Forward-only Recordset objects support only the MoveNext method. When an application uses the MoveXXX methods to visit each record (or enumerate the Recordset), it can use the
266
n Chapter Five—An ADO Primer BOF and EOF properties to see if it has moved beyond the beginning or end of the Recordset. Recordset objects can support two types of updating: immediate and batched. In immediate updating, all changes to data are written immediately to the underlying data source once an application calls the Update method. An application can also pass arrays of values as parameters with the AddNew and Update methods and simultaneously update several fields in a record. If an OLE DB provider supports batch updating, an application can have the provider cache changes to more than one record and then transmit them in a single call to the database with the UpdateBatch method. This applies to changes made with the AddNew, Update, and Delete methods. After an application calls the UpdateBatch method, it can use the Status property to check for any data conflicts in order to resolve them.
AbsolutePage Property (Recordset Object) An application should use the AbsolutePage property to identify the page number on which the current record is located. First, the application should use the PageSize property to logically divide the Recordset object into a series of pages, each of which has the number of records equal to PageSize (except for the last page, which may have fewer records). The OLE DB provider must support the appropriate functionality for this property to be available. Like the AbsolutePosition property, AbsolutePage is 1-based and equals 1 when the current record is the first record in the Recordset. Aside from a read-only Long numeric page value, AbsolutePage can also have one of the (negative) constant values in Table 5-12: Table 5-12
Recordset object AbsolutePage property values
Constant adPosUnknown
adPosBOF adPosEOF
Description This value indicates that the Recordset is empty, the current position is unknown, or the provider does not support the AbsolutePage property. This value indicates that the current record pointer is at BOF (that is, the BOF property is True). This value indicates that the current record pointer is at EOF (that is, the EOF property is True).
AbsolutePosition Property (Recordset Object) An application should use the AbsolutePosition property to move to a record based on its ordinal position in the Recordset object or to determine the ordinal position of the current record. The OLE DB provider must support the appropriate functionality for this property to be available. Like the AbsolutePage property, AbsolutePosition is 1-based and equals 1 when the current record is the first record in the Recordset. An application can obtain the total number of records in the Recordset object from the RecordCount
Chapter Five—An ADO Primer n
267
property. When an application sets the AbsolutePosition property, even if it is to a record in the current cache, ADO reloads the cache with a new group of records starting with the record it specified. The CacheSize property determines the size of this group. Aside from a read/write Long numeric record value, AbsolutePosition can also have one of the (negative) constant values in Table 5-13: Table 5-13
Recordset object AbsolutePosition property values
Constant adPosUnknown
adPosBOF adPosEOF
Description This value indicates that the Recordset is empty, the current position is unknown, or the provider does not support the AbsolutePosition property. This value indicates that the current record pointer is at BOF (that is, the BOF property is True). This value indicates that the current record pointer is at EOF (that is, the EOF property is True).
Warning
An application should not use the AbsolutePosition property as a surrogate record number. The position of a given record changes when an application deletes a preceding record. There is also no assurance that a given record will have the same AbsolutePosition if the Recordset object is requeried or reopened. Bookmarks are still the recommended way of retaining and returning to a given position and are the only way of positioning across all types of Recordset objects.
ActiveConnection Property (Recordset Object) This property indicates to which Connection object the specified Command or Recordset object currently belongs. The read/write value is a String containing the definition for a connection or a Connection object; the default value is a Null reference.
BOF/EOF Property (Recordset Object) An application should use the BOF and EOF properties to determine whether a Recordset object contains records or whether it has gone beyond the limits of a Recordset object when it moves from record to record. BOF indicates that the current record position is before the first record in a Recordset object; the BOF property returns True (–1) if the current record position is before the first record and False (0) if the current record position is on or after the first record. The EOF property returns True if the current record position is after the last record and False if the current record position is on or before the last record. If either the BOF or EOF property is True, there is no current record. If an application opens a Recordset object containing no records, the BOF and EOF properties are set to True, and the Recordset object’s RecordCount property setting is zero. When an application opens a Recordset object that
268
n Chapter Five—An ADO Primer contains at least one record, the first record is the current record and the BOF and EOF properties are False. If an application deletes the last remaining record in the Recordset object, the BOF and EOF properties may remain False until an application attempts to reposition the current record. Table 5-14 shows which MoveXXX methods are allowed with different combinations of the BOF and EOF properties. Table 5-14
BOF=True, EOF=False BOF=False, EOF=True Both True Both False
MoveXXX method calls permitted by EOF/BOF state combinations MoveFirst, MoveLast Allowed
MovePrevious, Move < 0 Error
Move 0 Error
MoveNext, Move > 0 Allowed
Allowed
Allowed
Error
Error
Error Allowed
Error Allowed
Error Allowed
Error Allowed
Allowing a MoveXXX method doesn’t guarantee that the method will successfully locate a record; it only means that calling the specified MoveXXX method won’t generate an ADO error. Table 5-15 shows what happens to the BOF and EOF property settings when an application calls various MoveXXX methods that are unable to successfully locate a record (due to going past the first or last record in the data source). Table 5-15
EOF/BOF changes due to failed MoveXXX method calls
MoveFirst, MoveLast Move 0 MovePrevious, Move < 0 MoveNext, Move > 0
BOF Set to True No change Set to True No change
EOF Set to True No change No change Set to True
Bookmark Property (Recordset Object) An application should use the Bookmark property to save the position of the current record and return to that record at any time. Bookmarks are available only in Recordset objects that support bookmark functionality via the current OLE DB provider. When an application opens a Recordset object, each of its records has a unique bookmark. To save the bookmark for the current record, an application should assign the value of the Bookmark property to a variable. Then to quickly return to that record at any time after moving to a different record, the application should set the Recordset object’s Bookmark property to the value of that variable. If an application uses the Clone method to create a copy of a Recordset object, the Bookmark property settings for the original and the duplicate Recordset objects are identical and an application can use them interchangeably. However, an application cannot use bookmarks from different Recordset objects interchangeably, even if they were
Chapter Five—An ADO Primer n
269
created from the same source or command. The property is read/write and uses a Variant expression that evaluates to a valid bookmark on the current OLE DB provider. Warning
The application may not be able to view the value of a bookmark since it may be represented in a non-displayable way by the OLE DB provider in use. Also, applications should not expect bookmarks to be directly comparable—two bookmarks that refer to the same record may have different values because of the OLE DB provider’s implementation.
CacheSize Property (Recordset Object) Applications should use the CacheSize property to control how many records the OLE DB provider keeps in its buffer and how many to retrieve at one time into local memory. The property is a Long value that must be greater than 0; the default is 1. After first opening the Recordset object, the OLE DB provider retrieves the first CacheSize records into local memory. As an application moves through the Recordset object, the OLE DB provider returns the data from the local memory buffer. As soon as the application moves past the last record in the cache, the provider retrieves the next CacheSize records from the data source into the cache. The value of this property can be adjusted during the life of the Recordset object, but changing this value only affects the number of records in the cache after subsequent retrievals from the data source. Changing the property value alone will not change the current contents of the cache. If there are fewer records to retrieve than CacheSize specifies, the provider returns the remaining records; no error occurs. Records retrieved from the cache do not reflect concurrent changes that other users may make to the source data; to force an update of all the cached data, use the Resync method. Warning
A CacheSize setting of 0 is not allowed and returns an ADO error.
CursorLocation Property (Recordset Object) This property determines the location of the cursor engine to be used by ADO, based on various cursor libraries accessible to the OLE DB provider in use. The property is a Long value which is read/write regardless of whether a connection is open or not, but the setting affects connections established only after the property has been set; changing this property has no effect on existing connections. The cursor obtained via an Execute call will automatically inherit the setting of this property. Table 5-16 gives the possible values of this property:
270
n Chapter Five—An ADO Primer Table 5-16
Recordset object CursorLocation property values
Constant adUseNone
Description This value indicates that no cursor services are used; it is obsolete and should be used only for the sake of backward compatibility with legacy code. This value indicates that the connection uses client-side cursors supplied by a local cursor library. Since local cursor engines often will allow features that driver-supplied cursors may not, using this setting may enable extra features. (adUseClientBatch is an alternative value for this constant.) This value is the default; it indicates that the connection uses OLE DB provider- or driver-supplied cursors. Some features of the Microsoft Client Cursor Provider (such as disassociated recordsets) cannot be simulated with server-side cursors, but other provider-specific features may be supported instead.
adUseClient
adUseServer
CursorType Property (Recordset Object) Applications should use the CursorType property to specify the type of cursor that should be used when opening the Recordset object. Only a setting of adOpenStatic is supported if the CursorLocation property is set to adUseClient. If an unsupported value is set, then no error will result; the closest supported CursorType will be used instead. If an OLE DB provider does not support the requested cursor type, the provider may return another cursor type. The CursorType property will change to match the actual cursor type in use when the Recordset object is open. To verify specific functionality of the returned cursor, use the Supports method. Although Supports(adUpdateBatch) may be true for dynamic and forward-only cursors, for batch updates an application should use either a keyset or static cursor. Also, the application should set the LockType property to adLockBatchOptimistic, and the CursorLocation property to adUseClient to enable the Microsoft Client Cursor Engine, which is required for batch updates. After an application closes the Recordset, the CursorType property reverts to its original setting. The CursorType property is read/write when the Recordset is closed and read-only when it is open. The property is composed of only one of the CursorTypeEnum values in Table 5-17. Table 5-17
Recordset object CursorTypeEnum values
Constant adOpenForwardOnly
Description This value is the default and is a forward-only cursor. This is identical to a static cursor except that an application can only scroll forward through records.
Chapter Five—An ADO Primer n
271
Table 5-17 Recordset object CursorTypeEnum values (cont.) Constant Description adOpenKeyset This value is for a keyset cursor. It works like a dynamic cursor, except that an application can’t see records that other users add, although records that other users delete are inaccessible from the Recordset. adOpenDynamic This value is for a dynamic cursor. Under it, all additions, changes, and deletions by other users are visible, and all types of movement through the Recordset are allowed, except for bookmarks if the OLE DB provider doesn’t support them. adOpenStatic This value is for a static cursor. This is a static copy of a set of records that an application can use to find data or generate reports.
EditMode Property (Recordset Object) An application should use the EditMode property to determine the editing status of the current record. The application can test for pending changes if an editing process has been interrupted and determine whether it needs to use the Update or CancelUpdate method. ADO maintains an editing buffer associated with the current record. This property indicates whether changes have been made to this buffer, or whether a new record has been created. The property contains one of the EditModeEnum values in Table 5-18. Table 5-18
Recordset object EditModeEnum values
Constant adEditNone adEditInProgress adEditAdd
adEditDelete
Description This value indicates that no editing operation is in progress. This value indicates that data in the current record has been modified but not yet saved. This value indicates that the AddNew method has been invoked, and the current record in the copy buffer is a new record that hasn’t been saved in the database. This value indicates that the current record has been deleted.
Filter Property (Recordset Object) Use the Filter property to selectively screen out records in a Recordset object. The filtered Recordset becomes the current cursor. This affects other properties such as AbsolutePosition, AbsolutePage, RecordCount, and PageCount that return values based on the current cursor, because setting the Filter property to a specific value will move the current record to the first record that satisfies the new value. The criteria string is made up of clauses in the form FieldName Operator Value (for example, “Genus = ‘Pinniped’ ”). An application can create compound clauses by concatenating individual clauses with AND (for example, “Species = Genus AND Habitat = ‘Arctic’ ”) or OR (for example, “Genus = ‘Pinniped’ OR Genus = ‘Cetacean’ ”). FieldName must
272
n Chapter Five—An ADO Primer be a valid field name from the Recordset. If the field name contains spaces, the application must enclose the name in square brackets. Operator must be one of the following: <, >, <=, >=, <>, =, or LIKE. Value is the value with which the OLE DB provider will compare the field values (for example, ‘Pinniped’, #7/27/52#, 158.158 or $158.00). An application should use single quotes with strings and pound signs (#) with dates. For numbers, an application can use decimal points, dollar signs, and scientific notation. If Operator is LIKE, Value can use wild cards. Only the asterisk (*) and percent sign (%) wild cards are allowed, and they must be the last character in the string. Value cannot be Null. There is no precedence between AND and OR. Clauses can be grouped within parentheses. However, an application cannot group clauses joined by an OR and then join the group to another clause with an AND. The filter constants make it easier to resolve individual record conflicts during batch update mode by allowing an application to view only those records that were affected during the last UpdateBatch method call. Setting the Filter property itself may fail because of a conflict with the underlying data (for example, a record has already been deleted by another user); in such a case, the provider returns warnings to the Errors collection but does not halt program execution. An ADO error occurs only if there are conflicts on all the requested records. An application can use the Status property to locate records with conflicts. Setting the Filter property to an empty string (“”) has the same effect as using the adFilterNone constant. Whenever the Filter property is set, the current record position moves to the first record in the filtered subset of records in the Recordset. Similarly, when the Filter property is cleared, the current record position moves to the first record in the Recordset. The property holds a Variant value, which can contain only one of the following types of data: n
Criteria string—a string made up of one or more individual clauses concatenated with AND or OR operators.
n
Array of bookmarks—an array of unique bookmark values that point to records in the Recordset object.
n
One of the FilterGroupEnum values in Table 5-19:
Table 5-19
Recordset object FilterGroupEnum values
Constant adFilterAffectedRecords
adFilterConflictingRecords
Description This value allows an application to view only records affected by the last Delete, Resync, UpdateBatch, or CancelBatch call. This value allows an application to view the records that failed the last batch update attempt.
Chapter Five—An ADO Primer n
273
Table 5-19 Recordset object FilterGroupEnum values (cont.) Constant Description adFilterFetchedRecords This value allows an application to view records in the current cache, that is, the results of the last call to retrieve records from the database. adFilterNone This value removes the current filter and restores all records to view. adFilterPendingRecords This value allows an application to view only records that have changed but have not yet been sent to the server. It is applicable only for batch update mode.
LockType Property (Recordset Object) An application should set the LockType property before opening a Recordset to specify what type of locking the OLE DB provider should use when opening it. An application should read the property to return the type of locking in use on an open Recordset object it did not create, or if it is unsure whether the current OLE DB provider supports a given LockType setting. The LockType property is read/write when the Recordset is closed and read-only when it is open. Not all OLE DB providers support all lock types. If an OLE DB provider cannot support the requested LockType setting, it will substitute another type of locking rather than raising an ADO error. To determine the actual locking functionality available in a Recordset object, an application should use the Supports method with adUpdate and adUpdateBatch. The property contains only one of the LockTypeEnum values in Table 5-20. Table 5-20
Recordset object LockTypeEnum values
Constant adLockReadOnly adLockPessimistic
adLockOptimistic
adLockBatchOptimistic
Description This value is the default. It indicates read-only access; the application cannot alter the data. This value indicates pessimistic locking, record by record. In this case, the OLE DB provider does what is necessary to ensure successful editing of the records, usually by locking records at the data source immediately upon editing. This value indicates optimistic locking, record by record. In this case, the OLE DB provider uses optimistic locking, locking records only when an application calls the Update method. This value indicates optimistic batch updates. It is required for batch update mode as opposed to immediate update mode.
274
n Chapter Five—An ADO Primer Warning
The adLockPessimistic setting is not supported if the CursorLocation property is set to adUseClient.
MarshalOptions Property (Recordset Object) This property indicates which records are to be marshaled back to the server. When using a client-side (ADOR) Recordset, records that have been modified on the client are written back to the middle tier or web server through a technique called marshaling, the process of packaging and sending interface method parameters across thread or process boundaries. Setting the MarshalOptions property can improve performance when modified remote data is marshaled for updating back to the middle tier or web server. The property contains a Long value that can only be one of the constants in Table 5-21. Table 5-21
Recordset object MarshalOptions property values
Constant adMarshalAll adMarshalModifiedOnly
Description This value is the default; it indicates that all rows are returned to the server. This value indicates that only modified rows are returned to the server.
MaxRecords Property (Recordset Object) An application should use the MaxRecords property to limit the number of records the OLE DB provider returns from the data source. A value of 0 means the OLE DB provider returns all requested records. The property is read/write when the Recordset is closed and read-only when it is open. It is a Long value with a default of 0 (no limit).
PageCount Property (Recordset Object) An application should use the PageCount property to determine how many pages of data are in the Recordset object. Pages are groups of records whose size equals the PageSize property setting. Even if the last page is incomplete, because there are fewer records than the PageSize value, it counts as an additional page in the PageCount value. If the Recordset object does not support this property, the value will be –1 to indicate that the PageCount is indeterminable. The property is read-only and is a Long value.
PageSize Property (Recordset Object) An application should use the PageSize property to determine how many records make up a logical page of data. Establishing a page size allows an application to use the AbsolutePage property to move to the first record of a particular page. This is useful in web server scenarios when an application wants to allow the user to page through data, viewing a certain number of
Chapter Five—An ADO Primer n
275
records at a time. This property can be set at any time, and its value will be used for calculating the location of the first record of a particular page. The property is a Long value with a default of 10.
RecordCount Property (Recordset Object) An application should use the RecordCount property to find out how many records are in a Recordset object. The property returns –1 when ADO cannot determine the number of records. If the Recordset object supports approximate positioning or bookmarks, this value will be the exact number of records in the Recordset regardless of whether it has been fully populated. If the Recordset object does not support approximate positioning, this property may be a significant drain on resources because all records will have to be retrieved and counted to return an accurate RecordCount value. The property is a read-only Long value. Warning
Reading the RecordCount property on a closed Recordset causes an
error.
Sort Property (Recordset Object) An application should use this property to set one or more field names on which the Recordset is sorted and specify whether each field is sorted in ascending or descending order. The data is not physically rearranged, but is simply accessed in the sorted order. A temporary index will be created for each field specified in the Sort property if the CursorLocation property is set to adUseClient and an index does not already exist. Setting the Sort property to an empty string will reset the rows to their original order and delete temporary indexes; existing indexes will not be deleted. The property is always read/write, and contains a string of comma-separated field names to sort on, where each name is a field in the Recordset, each of which is optionally followed by a blank and the keyword ASCENDING or DESCENDING, which specifies the field sort order. If no keyword is specified, the default is descending order.
Source Property (Recordset Object) An application should use the Source property to specify a data source for a Recordset object using a Command object variable, a SQL statement, a stored procedure, or a table name. If an application sets the Source property to a Command object, the ActiveConnection property of the Recordset object will inherit the value of the ActiveConnection property for the specified Command object. Reading the Source property does not return a Command object; instead, it returns the CommandText property of the Command object to which the application set the Source property. If the Source property is a SQL statement, a stored procedure, or a table name, an application can
276
n Chapter Five—An ADO Primer optimize performance by passing the appropriate Options argument with the Open method call. The Source property is read/write for closed Recordset objects and read-only for open Recordset objects. The property is set to a String value or Command object reference, but can only return a String value.
State Property (Recordset Object) This property describes for a Recordset object executing an asynchronous method whether the current state of the object is connecting, executing, or fetching; otherwise, it simply reports whether the Recordset is open (communicating with the OLE DB provider) or closed (not communicating with the OLE DB provider). The property can have a combination of values; if a statement is executing, this property will have a combined value of adStateOpen and adStateExecuting. This property is read-only and returns a Long value that can be one or more of the constants in Table 5-22. Table 5-22
Recordset object State property values
Constant adStateClosed adStateOpen adStateConnecting adStateExecuting adStateFetching
Description This value indicates that the Recordset is closed. This value indicates that the Recordset is open. This value indicates that the Recordset object is connecting. This value indicates that the Recordset object is executing a command. This value indicates that the rows of the Recordset object are being fetched.
Status Property (Recordset Object) Applications should use the Status property to see what changes are pending for records modified during batch updating. Applications can also use the Status property to view the status of records that fail during bulk operations, such as when they call the Resync, UpdateBatch, or CancelBatch methods on a Recordset object, or set the Filter property on a Recordset object to an array of bookmarks. The property is read-only and contains a combination of one or more of the RecordStatusEnum values in Table 5-23. Table 5-23
Recordset object RecordStatusEnum values
Constant adRecOK adRecNew adRecModified adRecDeleted adRecUnmodified
Description This value indicates that the record was successfully updated. This value indicates that the record is new. This value indicates that the record was modified. This value indicates that the record was deleted. This value indicates that the record was not modified.
Chapter Five—An ADO Primer n
277
Table 5-23 Recordset object RecordStatusEnum values (cont.) Constant Description adRecInvalid This value indicates that the record was not saved because its bookmark is invalid. adRecMultipleChanges This value indicates that the record was not saved because it would have affected multiple records. adRecPendingChanges This value indicates that the record was not saved because it refers to a pending insert. adRecCanceled This value indicates that the record was not saved because the operation was canceled. adRecCantRelease This value indicates that the new record was not saved because of existing record locks. adRecConcurrencyViolation This value indicates that the record was not saved because optimistic concurrency was in use. adRecIntegrityViolation This value indicates that the record was not saved because the user violated integrity constraints. adRecMaxChangesExceeded This value indicates that the record was not saved because there were too many pending changes. adRecObjectOpen This value indicates that the record was not saved because of a conflict with an open storage object. adRecOutOfMemory This value indicates that the record was not saved because the computer has run out of memory. adRecPermissionDenied This value indicates that the record was not saved because the user has insufficient permissions. adRecSchemaViolation This value indicates that the record was not saved because it violates the structure of the underlying database. adRecDBDeleted This value indicates that the record has already been deleted from the data source.
AddNew Method (Recordset Object) An application should use the AddNew method to create and initialize a new record. First, however, it should use the Supports method with adAddNew to verify whether it can add records to the current Recordset object. After it makes the call to AddNew, the new record becomes the current record and remains current after the call to the Update method. If the Recordset object does not support bookmarks, an application may not be able to access the new record once it moves to another record. Depending on the cursor type, an application may need to call the Requery method to make the new record accessible. If an application calls AddNew while editing the current record or while adding a new record, ADO calls the Update method to save any changes and then creates the new record. The behavior of the AddNew method depends on the updating mode of the Recordset object and whether or not an application chooses to pass the FieldList and Values arguments. In immediate update mode (the provider writes changes to the underlying data
278
n Chapter Five—An ADO Primer source once the application calls the Update method), calling the AddNew method without arguments sets the EditMode property to adEditAdd. The OLE DB provider caches any field value changes locally. Calling the Update method posts the new record to the database and resets the EditMode property to adEditNone. If an application chooses to pass the FieldList and Values arguments, ADO immediately posts the new record to the database (no Update call is necessary); the EditMode property value does not change (adEditNone). In batch update mode (the OLE DB provider caches multiple changes and writes them to the underlying data source only when you call the UpdateBatch method), calling the AddNew method without arguments sets the EditMode property to adEditAdd. The OLE DB provider caches any field value changes locally. Calling the Update method adds the new record to the current Recordset and resets the EditMode property to adEditNone, but the OLE DB provider does not post the changes to the underlying database until the application calls the UpdateBatch method. If an application chooses to pass the FieldList and Values arguments, ADO sends the new record to the provider for storage in a cache; the application then needs to call the UpdateBatch method to post the new record to the underlying database. The method has the following syntax: pRecordset.AddNew(FieldList, Values);
The method has the following parameters: FieldList This optional parameter is a single name or an array of names or ordinal positions of the fields in the new record. Values This optional parameter is a single value or an array of values for the fields in the new record. If FieldList is an array, Values must also be an array with the same number of members; otherwise, an ADO error occurs. The order of field names must match the order of field values in each array.
Cancel Method (Recordset Object) An application should use the Cancel method to terminate execution of an asynchronous Execute or Open method call (that is, the method was invoked with the adConnectAsync, adExecuteAsync, or adFetchAsync option). Cancel will return an ADO error if adRunAsync was not used in the method it is trying to terminate. The method has the following syntax: pRecordset->Cancel();
The method has no parameters.
Chapter Five—An ADO Primer n
279
CancelBatch Method (Recordset Object) An application should use the CancelBatch method to cancel any pending updates in a recordset in batch update mode. If the Recordset is in immediate update mode, calling CancelBatch without adAffectCurrent generates an ADO error. If the application is editing the current record or is adding a new record when it calls CancelBatch, ADO first calls the CancelUpdate method to cancel any cached changes; after that, all pending changes in the recordset are canceled. It is possible that the current record will be indeterminable after a CancelBatch call, especially if the application was in the process of adding a new record. For this reason, it is prudent to set the current record position to a known location in the Recordset after the CancelBatch call. The method has the following syntax: pRecordset->CancelBatch(AffectRecords);
The method has the following parameter: AffectRecords This optional parameter is an AffectEnum value that determines how many records the CancelBatch method will affect. The value is one of the constants in Table 5-24: Table 5-24
Recordset object CancelBatch method AffectEnum values
Constant adAffectCurrent adAffectGroup
adAffectAll
Description This value indicates that ADO should cancel pending updates only for the current record. This value indicates that ADO should cancel pending updates for records that satisfy the current Filter property setting. You must set the Filter property to one of the valid predefined constants in order to use this option. This default value indicates that ADO should cancel pending updates for all the records in the Recordset object, including any hidden by the current Filter property setting.
CancelUpdate Method (Recordset Object) An application should use the CancelUpdate method to cancel any changes made to the current record or to discard a newly added record. An application cannot undo changes to the current record or to a new record after it calls the Update method unless the changes are either part of a transaction that the application can roll back with the RollbackTrans method or part of a batch update that it can cancel with the CancelBatch method. If the application is adding a new record when it calls the CancelUpdate method, the record that was current prior to the AddNew call becomes the current record again. If an application has not changed the current record or added a new record, calling the CancelUpdate method generates an ADO error.
280
n Chapter Five—An ADO Primer The method has the following syntax: pRecordset->CancelUpdate();
The method has no parameters.
Clone Method (Recordset Object) An application should use the Clone method to create multiple, duplicate Recordset objects, particularly if it wants to be able to maintain more than one current record in a given set of records. Using the Clone method is more efficient than creating and opening a new Recordset object with the same definition as the original. The current record of a newly created clone is set to the first record. Changes the application may make to one Recordset object are visible in all of its clones regardless of cursor type. However, once an application executes Requery on the original Recordset, the clones will no longer be synchronized to the original. Closing the original Recordset does not close its copies; closing a copy does not close the original or any of the other copies. An application can only clone a Recordset object that supports bookmarks. Bookmark values are interchangeable; that is, a bookmark reference from one Recordset object refers to the same record in any of its clones. The method has the following syntax: pDuplicateRecordset = pOriginalRecordset->Clone(LockType);
The method returns a Recordset interface pointer reference. The method has the following parameter: LockType This optional parameter is a LockTypeEnum value (given in Table 5-25) that specifies either the lock type of the original Recordset or a read-only Recordset. Table 5-25
Recordset object LockTypeEnum values
Constant adLockReadOnly adLockUnspecified
Description This default value indicates that the clone is created as read-only. This default value indicates that the clone is created with the same lock type as the original.
Delete Method (Recordset Object) An application should use the Delete method to mark the current record or a group of records in a Recordset object for deletion. If the Recordset object doesn’t allow record deletion, an ADO error occurs. If the application is in immediate update mode, deletions occur in the database immediately. Otherwise, the records are marked for deletion from the cache and the actual
Chapter Five—An ADO Primer n
281
deletion happens when the application calls the UpdateBatch method. (Applications can use the Filter property to view the deleted records.) Retrieving field values from the deleted record generates an ADO error. After deleting the current record, the deleted record remains current until an application chooses to move to a different record. Once the application chooses to move away from the deleted record, that deleted record is no longer accessible. If an application should nest deletions in a transaction, however, it can recover deleted records with the RollbackTrans method. If an application is in batch update mode, it can cancel a pending deletion or group of pending deletions with the CancelBatch method. If the attempt to delete records fails because of a conflict with the underlying data (for example, a record has already been deleted by another user), the provider returns warnings to the Errors collection but does not halt program execution with an ADO error. However, an ADO error does occur if there are conflicts on all the requested records. The method has the following syntax: pRecordset->Delete(AffectRecords);
The method has the following parameter: AffectRecords This parameter is an AffectEnum value that determines how many records the Delete method will affect. The AffectEnum constants are given in Table 5-26. Table 5-26
Recordset object AffectEnum values
Constant adAffectCurrent adAffectGroup
Description This default value indicates that ADO should delete only the current record. This value indicates that ADO should delete the records that satisfy the current Filter property setting. An application must set the Filter property to one of the valid predefined constants in order to use this option.
Move Method (Recordset Object) An application should use this method to move the position of the current record in a Recordset object. The Move method is supported on all Recordset objects. If the NumRecords argument is greater than zero, the current record position moves forward (toward the end of the recordset). If NumRecords is less than zero, the current record position moves backward (toward the beginning of the recordset). If the Move call would move the current record position to a point before the first record, ADO sets the current record to the position before the first record in the recordset (BOF is True). An attempt to move backward when the BOF property is already True generates an ADO error. If the Move call would move the current record position to a point after the last record, ADO sets the current record to the position after the last
282
n Chapter Five—An ADO Primer record in the recordset (EOF is True). An attempt to move forward when the EOF property is already True generates an ADO error. Calling the Move method from an empty Recordset object generates an error. If an application chooses to pass the Start argument, the move is relative to the record with this bookmark, assuming the Recordset object supports bookmarks. If this parameter is not specified, the move is relative to the current record. If an application is using the CacheSize property to locally cache records from the provider, passing a NumRecords argument that moves the current record position outside the current group of cached records forces ADO to retrieve a new group of records starting from the destination record. The CacheSize property determines the size of the newly retrieved group, and the destination record is the first record retrieved. If the Recordset object is forward-only, an application can still pass a NumRecords argument less than zero as long as the destination is within the current set of cached records. If the Move call would move the current record position to a record before the first cached record, an ADO error will occur. This technique allows an application to use a record cache that supports full scrolling over a provider that supports only forward scrolling. Because cached records are loaded into memory, however, an application should avoid caching more records than is necessary. Even if a forward-only Recordset object supports backward moves using this technique from the vantage point of the end user, calling the MovePrevious method on any forward-only Recordset interface pointer in program statements still generates an ADO error. The method has the following syntax: pRecordset->Move(NumRecords, Start);
The method has the following parameters: NumRecords This parameter is a signed Long expression specifying the number of records the current record position moves. Start This optional parameter is a String or Variant that evaluates to a bookmark. An application can also use one of the BookmarkEnum values in Table 5-27: Table 5-27
Recordset object BookmarkEnum values
Constant adBookmarkCurrent
Description This default value indicates that ADO should start at the current record. adBookmarkFirst This value indicates that ADO should start at the first record. Table 5-27 Recordset object BookmarkEnum values (cont.) Constant Description
Chapter Five—An ADO Primer n adBookmarkLast
283
This value indicates that ADO should start at the last record.
MoveFirst Method (Recordset Object) An application should use the MoveFirst method to move the current record position to the first record in the Recordset. The Recordset object must support bookmarks or backward cursor movement; otherwise, the method call will generate an ADO error. If the Recordset is forward-only and an application wants to support both forward and backward scrolling, it can use the CacheSize property to create a record cache that will support backward cursor movement through the Move method. Because cached records are loaded into memory, an application should avoid caching more records than is necessary. An application can call the MoveFirst method in a forward-only Recordset object; doing so may cause the OLE DB provider to re-execute the command that generated the Recordset object. The method has the following syntax: pRecordset.MoveFirst();
The method has no parameters.
MoveLast Method (Recordset Object) An application should use the MoveLast method to move the current record position to the last record in the Recordset. The Recordset object must support bookmarks or backward cursor movement; otherwise, the method call will generate an ADO error. If the Recordset is forward-only and an application wants to support both forward and backward scrolling, it can use the CacheSize property to create a record cache that will support backward cursor movement through the Move method. Because cached records are loaded into memory, an application should avoid caching more records than is necessary. The method has the following syntax: pRecordset.MoveLast();
The method has no parameters.
MoveNext Method (Recordset Object) An application should use the MoveNext method to move the current record position one record forward (toward the bottom of the Recordset). If the last record is the current record and an application calls the MoveNext method, ADO sets the current record to the position after the last record in the Recordset (EOF is True). An attempt to move forward when the EOF property is already True generates an ADO error. If the Recordset is forward-only and an application wants to support both forward and backward scrolling, it can use the CacheSize property to create a record cache that will support
284
n Chapter Five—An ADO Primer backward cursor movement through the Move method. Because cached records are loaded into memory, an application should avoid caching more records than is necessary. The method has the following syntax: pRecordset.MoveNext();
The method has no parameters.
MovePrevious Method (Recordset Object) An application should use the MovePrevious method to move the current record position one record backward (toward the top of the recordset). The Recordset object must support bookmarks or backward cursor movement; otherwise, the method call will generate an ADO error. If the first record is the current record and an application calls the MovePrevious method, ADO sets the current record to the position before the first record in the recordset (BOF is True). An attempt to move backward when the BOF property is already True generates an ADO error. If the Recordset object does not support either bookmarks or backward cursor movement, the MovePrevious method will generate an ADO error. If the Recordset is forward-only and an application wants to support both forward and backward scrolling, it can use the CacheSize property to create a record cache that will support backward cursor movement through the Move method. Because cached records are loaded into memory, an application should avoid caching more records than is necessary. The method has the following syntax: pRecordset.MovePrevious();
The method has no parameters.
NextRecordset Method (Recordset Object) An application should use the NextRecordset method to return the results of the next command in a compound command statement or of a stored procedure that returns multiple results. If you open a Recordset object based on a compound command statement using the Execute method on a Command or the Open method on a Recordset, ADO executes only the first command and returns the results to the Recordset. To access the results of subsequent commands in the statement, call the NextRecordset method. As long as there are additional results, the NextRecordset method will continue to return Recordset objects. If a row-returning command returns no records, the returned Recordset object will be empty; test for this case by verifying that the BOF and EOF properties are both True. If a non–row-returning command executes successfully, the returned Recordset object will be closed, which an application can verify by testing the State property on the Recordset. When
Chapter Five—An ADO Primer n
285
there are no more results, the Recordset will be set to Null. If an edit is in progress while in immediate update mode, calling the NextRecordset method generates an ADO error; an application should call the Update or CancelUpdate method first. If an application needs to pass parameters for more than one command in the compound statement by filling the Parameters collection or by passing an array with the original Open or Execute call, the parameters must be in the same order in the collection or array as their respective commands in the command series. An application must finish reading all the results before reading output parameter values. When an application calls the NextRecordset method, ADO executes only the next command in the statement. If an application explicitly closes the Recordset object before stepping through the entire command statement, ADO never executes the remaining commands. The method has the following syntax: pRecordset2 = pRecordset1->NextRecordset(RecordsAffected);
The method returns a Recordset object. In the syntax model, pRecordset1 and pRecordset2 can be the same Recordset object, or the application can use separate objects. The method has the following parameter: RecordsAffected This optional parameter is a Long variable to which the OLE DB provider returns the number of records that the current operation affected.
Open Method (Recordset Object) An application shoud use the Open method on a Recordset object to open a cursor that represents records from a base table, the results of a query, or a previously saved Recordset. The application should use the optional Source argument to specify a data source using one of the following: a Command object variable, a SQL statement, a stored procedure, a table name, or a complete file path name. The ActiveConnection argument corresponds to the ActiveConnection property and specifies in which connection to open the Recordset object. If an application chooses to pass a connection definition for this argument, ADO opens a new connection using the specified parameters. An application can change the value of this property after opening the Recordset to send updates to another provider. Or, an application can set this property to Null to disconnect the Recordset from any provider (after calling Release; see Chapter 1). For the other arguments that correspond directly to properties of a Recordset object (Source, CursorType, and LockType), the relationship of the arguments to the properties is as follows: n
The property is read/write before the Recordset object is opened.
n
The property settings are used unless an application passes the corresponding arguments when executing the Open method. If an application
286
n Chapter Five—An ADO Primer chooses to pass an argument, it overrides the corresponding property setting, and the property setting is updated with the argument value. n
After an application opens the Recordset object, these properties become read-only.
For Recordset objects whose Source property is set to a valid Command object, the ActiveConnection property is read-only, even if the Recordset object isn’t open. If an application passes a Command object in the Source argument and also passes an ActiveConnection argument, an ADO error occurs. The ActiveConnection property of the Command object must already be set to a valid Connection object or connection string. If an application passes something other than a Command object in the Source argument, an ADO error occurs. An application can use the Options argument to optimize evaluation of the Source argument. If the Options argument is not defined, an application may experience diminished performance because ADO must make calls to the OLE DB provider to determine if the argument is a SQL statement, a stored procedure, or a table name. If an application knows what source type it is using, setting the Options argument instructs ADO to jump directly to the relevant code. If the Options argument does not match the source type, an ADO error occurs. The default for the Options argument is adCmdFile if no connection is associated with the recordset. This will typically be the case for persisted Recordset objects. If the data source returns no records, the provider sets both the BOF and EOF properties to True, and the current record position is undefined. An application can still add new data to this empty Recordset object if the cursor type allows it. When an application has concluded its operations over an open Recordset object, it should use the Close method to free any associated system resources. Closing an object does not remove it from memory; an application can change its property settings and use the Open method to open it again later. To completely eliminate an object from memory, set the object variable to Null after calling Release (see Chapter 1). An application can call Open with no operands, and before the ActiveConnection property is set, to create an instance of a Recordset created by appending fields to the Recordset Fields collection. The method has the following syntax: pRecordset->Open(Source, ActiveConnection, CursorType, LockType, Options);
The method has the following parameters: Source This optional parameter is a Variant that evaluates to a valid Command object variable name, a SQL statement, a table name, a stored procedure call, or the filename of a persisted Recordset. ActiveConnection This optional parameter is either a Variant that evaluates to a valid
Chapter Five—An ADO Primer n
287
Connection object variable name or a String containing ConnectionString parameters. CursorType This optional parameter is a CursorTypeEnum value (listed in Table 5-28) that determines the type of cursor the provider should use when opening the Recordset. Table 5-28
Recordset object CursorTypeEnum values
Constant adOpenForwardOnly adOpenKeyset adOpenDynamic adOpenStatic
Description This default value opens a forward-only–type cursor. This value opens a keyset-type cursor. This value opens a dynamic-type cursor. This value opens a static-type cursor.
LockType This optional parameter is a LockTypeEnum value (given in Table 5-29) that determines what type of locking (concurrency) the provider should use when opening the Recordset. Table 5-29
Recordset object LockTypeEnum values
Constant adLockReadOnly adLockPessimistic
adLockOptimistic
adLockBatchOptimistic
Description This value is the default and indicates read-only access; the application cannot alter the data. This value indicates pessimistic locking, record by record. In this case, the OLE DB provider does what is necessary to ensure successful editing of the records, usually by locking records at the data source immediately upon editing. This value indicates optimistic locking, record by record. In this case, the OLE DB provider uses optimistic locking, locking records only when an application calls the Update method. This value indicates optimistic batch updates. It is required for batch update mode as opposed to immediate update mode.
Options This optional parameter is a Long value that indicates how the OLE DB provider should evaluate the Source argument if it represents something other than a Command object, or that the Recordset should be restored from a file where it was previously saved. This parameter is one of the constants in Table 5-30:
288
n Chapter Five—An ADO Primer Table 5-30
Recordset object CommandTypeEnum values
Constant adCmdText adCmdTable adCmdTableDirect adCmdTable adCmdStoredProc adCmdUnknown adExecuteAsync adFetchAsync
Description This value indicates that the OLE DB provider should evaluate CommandText as a textual definition of a command. This value indicates that ADO should generate a SQL query to return all rows from the table named in CommandText. This value indicates that the OLE DB provider should return all rows from the table named in CommandText. This value indicates that the OLE DB provider should evaluate CommandText as a table name. This value indicates that the OLE DB provider should evaluate CommandText as a stored procedure. This value indicates that the type of command in the CommandText argument is not known. This value indicates that the command should execute asynchronously. This value indicates that the remaining rows after the initial quantity specified in the CacheSize property should be fetched asynchronously.
Requery Method (Recordset Object) An application should use the Requery method to refresh the entire contents of a Recordset object from the data source by reissuing the original command and retrieving the data a second time. Calling this method is equivalent to calling the Close and Open methods in succession. If you are editing the current record or adding a new record, an error occurs. While the Recordset object is open, the properties that define the nature of the cursor (CursorType, LockType, MaxRecords, etc.) are read-only. Thus, the Requery method can only refresh the current cursor. To change any of the cursor properties and view the results, an application must use the Close method so that the properties become read/write again. An application can then change the property settings and call the Open method to reopen the cursor. The method has the following syntax: pRecordset->Requery(Options);
The method has the following parameter: Options This optional parameter is a bitmask indicating options affecting this operation. If this parameter is set to adExecuteAsync, this operation will execute asynchronously and a RecordsetChangeComplete event will be issued when it concludes.
Chapter Five—An ADO Primer n
289
Resync Method (Recordset Object) An application should use the Resync method to resynchronize records in the current Recordset with the underlying database. This is useful if an application is using either a static or forward-only cursor but also wants to see any changes in the underlying database. Unlike the Requery method, the Resync method does not re-execute the Recordset object’s underlying command; new records in the underlying database will not be visible. If the attempt to resynchronize fails because of a conflict with the underlying data, the provider returns warnings to the Errors collection and an ADO error occurs. In this case, an application should use the Filter property (adFilterConflictingRecords) and the Status property to locate records with conflicts. The method has the following syntax: pRecordset->Resync(AffectRecords, ResyncValues);
The method has the following parameter: AffectRecords This optional parameter is an AffectEnum value (listed in Table 5-31) that determines how many records the Resync method will affect. Table 5-31
Recordset object AffectEnum values
Constant adAffectCurrent adAffectGroup
adAffectAll
Description This value indicates that ADO should refresh only the current record. This value indicates that ADO should refresh the records that satisfy the current Filter property setting. An application must set the Filter property to one of the valid predefined constants in order to use this option. This default value indicates that ADO should refresh all the records in the Recordset object, including any hidden by the current Filter property setting.
ResyncValues This optional parameter is a ResyncEnum value (given in Table 5-32) that specifies whether underlying values are overwritten. Table 5-32
Recordset object ResyncEnum values
Constant adResyncAllValues adResyncUnderlyingValues
Description This default value indicates that data is overwritten, and pending updates are canceled. This value indicates that data is not overwritten, and pending updates are not canceled.
290
n Chapter Five—An ADO Primer
Save Method (Recordset Object) An application should use this method to save (persist) the Recordset in a file. The Save method can only be invoked on an open Recordset. Use the Open method to later restore the Recordset from FileName. If the Filter property is in effect for the Recordset, then only the rows accessible under the filter are saved. If the Recordset is hierarchical, then the current child recordset and its children are saved, but not the parent recordset. The first time an application saves the Recordset, it must specify the FileName parameter. If you subsequently invoke Save, omit FileName or else a run-time error will occur. If it subsequently invokes Save with a new FileName, the Recordset is saved to the new file. However, the new file and the original file will both be open, since Save does not close Recordset or FileName, so an application can continue to work with the Recordset and save its most recent changes. FileName remains open until the Recordset is closed, during which time other applications can read but not write to FileName. If the Save method is called while an asynchronous Recordset fetch, execute, or update operation is in progress, then Save waits until the asynchronous operation is complete. When the Save method is done, the current row position will be the first row of the Recordset. The method has the following syntax: pRecordset->Save(FileName, PersistFormat);
The method has the following parameters: FileName This optional parameter is the complete path name of the file where the Recordset is to be saved. PersistFormat This optional parameter is the format in which the Recordset is to be saved. In ADO 2.0 the only valid value is adPersistADTG. Warning
For reasons of security, the Save method cannot be used from a script executed by Microsoft Internet Explorer.
Supports Method (Recordset Object) An application should use this method to determine whether a specified Recordset object supports a particular type of functionality. The method has the following syntax: bMyboolean = pRecordset->Supports(CursorOptions);
The method returns a Boolean value that indicates whether all of the features identified by the CursorOptions argument are supported by the provider.
Chapter Five—An ADO Primer n
291
The method has the following parameter: CursorOptions This parameter is a Long expression that consists of one or more of the CursorOptionEnum values given in Table 5-33. Table 5-33
Recordset object CursorOptionEnum values
Constant adAddNew adApproxPosition adBookmark adDelete adHoldRecords
adMovePrevious
adResync
adUpdate adUpdateBatch
Description This value indicates that the application can use the AddNew method to add new records. This value indicates that the application can read and set the AbsolutePosition and AbsolutePage properties. This value indicates that the application can use the Bookmark property to gain access to specific records. This value indicates that the application can use the Delete method to delete records. This value indicates that the application can retrieve more records or change the next retrieve position without committing all pending changes. This value indicates that the application can use the MoveFirst and MovePrevious methods, and Move or GetRows methods to move the current record position backward without requiring bookmarks. This value indicates that the application can update the cursor with the data visible in the underlying database, using the Resync method. This value indicates that the application can use the Update method to modify existing data. This value indicates that the application can use batch updating (UpdateBatch and CancelBatch methods) to transmit changes to the provider in groups.
Update Method (Recordset Object) An application can use this method to set field values, by doing one of the following: n
Assign values to a Field object’s Value property and calling the Update method.
n
Passing a field name and a value as arguments with the Update call.
n
Passing an array of field names and an array of values with the Update call.
When an application chooses to use arrays of fields and values, there must be an equal number of elements in both arrays. Also, the order of field names must match the order of field values. If the number and order of fields and values do not match, an ADO error occurs. If the Recordset object supports batch updating, then an application can cache multiple changes to one or
292
n Chapter Five—An ADO Primer more records locally until it chooses to call the UpdateBatch method. If an application is editing the current record or adding a new record when it calls the UpdateBatch method, ADO will automatically call the Update method to save any pending changes to the current record before transmitting the batched changes to the provider. If an application chooses to move from the record it is adding or editing before calling the Update method, ADO will automatically call Update to save the changes. An application must call the CancelUpdate method if it wants to cancel any changes made to the current record or to discard a newly added record. The current record remains current after the application calls the Update method. The method has the following syntax: pRecordset->Update(Fields, Values);
The method has the following parameters: Fields This optional parameter is a Variant representing a single name or a Variant array representing names or ordinal positions of the field or fields an application may wish to modify. Values This optional parameter is a Variant representing a single value or a Variant array representing values for the field or fields in the new record.
UpdateBatch Method (Recordset Object) An application should use the UpdateBatch method when modifying a Recordset object in batch update mode to transmit all changes made in a Recordset object to the underlying database. If the Recordset object supports batch updating, then an application can cache multiple changes to one or more records locally until it calls the UpdateBatch method. If an application is editing the current record or adding a new record when it calls the UpdateBatch method, ADO will automatically call the Update method to save any pending changes to the current record before transmitting the batched changes to the OLE DB provider. An application should use batch updating only with either a keyset or static cursor. If the attempt to transmit changes fails because of a conflict with the underlying data, the OLE DB provider returns warnings to the Errors collection but does not halt program execution with an ADO error unless there are conflicts on all the requested records. In this case, an application should use the Filter property (adFilterAffectedRecords) and the Status property to locate records with conflicts. To cancel all pending batch updates, an application should use the CancelBatch method. The method has the following syntax: pRecordset->UpdateBatch(AffectRecords);
Chapter Five—An ADO Primer n
293
The method has the following parameter: AffectRecords This optional parameter is an AffectEnum value (given in Table 5-34) that determines how many records the UpdateBatch method will affect. Table 5-34
Recordset object AffectEnum values
Constant adAffectCurrent adAffectGroup
adAffectAll
Description This value indicates that ADO should refresh only the current record. This value indicates that ADO should refresh the records that satisfy the current Filter property setting. An application must set the Filter property to one of the valid predefined constants in order to use this option. This default value indicates that ADO should refresh all the records in the Recordset object, including any hidden by the current Filter property setting.
The ADO Field Object A Recordset object has a Fields collection made up of Field objects. Each Field object corresponds to a column in the Recordset. Applications should use the Value property of Field objects to set or return data for the current record. With the collections, methods, and properties of a Field object, an application can: n
Return the name of a field with the Name property.
n
View or change the data in the field with the Value property.
n
Return the basic characteristics of a field with the Type, Precision, and NumericScale properties.
n
Return the declared size of a field with the DefinedSize property.
n
Return the actual size of the data in a given field with the ActualSize property.
n
Determine what types of functionality are supported for a given field with the Attributes property and Properties collection.
n
Manipulate the values of fields containing long binary or long character data with the AppendChunk and GetChunk methods.
n
If the provider supports batch updates, resolve discrepancies in field values during batch updating with the OriginalValue and UnderlyingValue properties.
All of the metadata properties (Name, Type, DefinedSize, Precision, and NumericScale) are available before opening the Field object’s Recordset; this is useful for dynamically constructing forms.
294
n Chapter Five—An ADO Primer
ActualSize Property (Field Object) An application should use the ActualSize property to return the actual length of a Field object’s value. For all fields, the ActualSize property is read-only. If ADO cannot determine the length of the Field object’s value, the ActualSize property returns adUnknown.
Attributes Property (Field Object) For a Field object, the Attributes property is read-only; its value can be the sum of any one or more of the FieldAttributeEnum values in Table 5-35. Table 5-35
Field object FieldAttributeEnum values
Constant adFldMayDefer
adFldUpdatable adFldUnknownUpdatable adFldFixed adFldIsNullable adFldMayBeNull adFldLong
adFldRowID
adFldRowVersion adFldCacheDeferred
Description This value indicates that the field is deferred—that is, the field values are not retrieved from the data source with the whole record, but only when an application explicitly accesses them. This value indicates that an application can write to the field. This value indicates that the OLE DB provider cannot determine if you can write to the field. This value indicates that the field contains fixed-length data. This value indicates that the field accepts Null values. This value indicates that an application can read Null values from the field. This value indicates that the field is a Long binary field. Also indicates that an application can use the AppendChunk and GetChunk methods. This value indicates that the field contains a persistent row identifier that cannot be written to and has no meaningful value except to identify the row (such as a record number, unique identifier, and so forth). This value indicates that the field contains some kind of time or date stamp used to track updates. This value indicates that the OLE DB provider caches field values and that subsequent reads are done from the cache.
DefinedSize Property (Field Object) Applications should use the DefinedSize property to determine the data capacity of a Field object. The DefinedSize and ActualSize properties are different.
Name Property (Field Object) An application should use the Name property to assign a name to or retrieve the name of a Command, Field, Parameter, or Property object. For Parameter
Chapter Five—An ADO Primer n
295
objects not yet appended to the Parameters collection, the Name property is read/write. For appended Parameter objects and all other objects, the Name property is read-only. Names do not have to be unique within a collection. An application can retrieve the Name property of an object by an ordinal reference, after which it can refer to the object directly by name.
NumericScale Property (Field Object) An application should use the NumericScale property to determine how many digits to the right of the decimal point will be used to represent values for a numeric Parameter or Field object. The NumericScale property is read-only.
OriginalValue Property (Field Object) An application should use the OriginalValue property to return the original field value for a field from the current record. In immediate update mode (the OLE DB provider writes changes to the underlying data source once an application calls the Update method), the OriginalValue property returns the field value that existed prior to any changes (that is, since the last Update method call). This is the same value that the CancelUpdate method uses to replace the Value property. In batch update mode (the OLE DB provider caches multiple changes and writes them to the underlying data source only when an application calls the UpdateBatch method), the OriginalValue property returns the field value that existed prior to any changes (that is, since the last UpdateBatch method call). This is the same value that the CancelBatch method uses to replace the Value property. When an application uses this property with the UnderlyingValue property, it can resolve conflicts that arise from batch updates.
Precision Property (Field Object) An application should use the Precision property to determine the maximum number of digits used to represent values for a numeric Parameter or Field object. The property is a Byte value, indicating the maximum total number of digits used to represent values. The value is read-only.
Type Property (Field Object) This read-only property indicates the operational type or data type of a Field object. It contains one of the DataTypeEnum values in Table 5-36. The corresponding OLE DB type indicator is shown in parentheses. Table 5-36 Constant adArray
Field object DataTypeEnum values Description This value indicates that the type is joined in a logical OR together with another type to indicate that the data is a safe-array of that type (DBTYPE_ARRAY).
296
n Chapter Five—An ADO Primer Table 5-36 Constant adBigInt
Field object DataTypeEnum values (cont.) Description This value indicates that the type is an 8-byte signed integer (DBTYPE_I8). adBinary This value indicates that the type is a binary value (DBTYPE_BYTES). adBoolean This value indicates that the type is a Boolean value (DBTYPE_BOOL). adByRef This value indicates that the type is joined in a logical OR together with another type to indicate that the data is a pointer to data of the other type (DBTYPE_BYREF). adBSTR This value indicates that the type is a null-terminated character string (Unicode) (DBTYPE_BSTR). adChar This value indicates that the type is a String value (DBTYPE_STR). adCurrency This value indicates that the type is a currency value (DBTYPE_CY). Currency is a fixed-point number with four digits to the right of the decimal point. It is stored in an 8-byte signed integer scaled by 10,000. adDate This value indicates that the type is a Date value (DBTYPE_DATE). A date is stored as a Double, the whole part of which is the number of days since December 30, 1899, and the fractional part of which is the fraction of a day. adDBDate This value indicates that the type is a date value (yyyymmdd) (DBTYPE_DBDATE). adDBTime This value indicates that the type is a time value (hhmmss) (DBTYPE_DBTIME). adDBTimeStamp This value indicates that the type is a date-time stamp (yyyymmddhhmmss plus a fraction in billionths) (DBTYPE_DBTIMESTAMP). adDecimal This value indicates that the type is an exact numeric value with a fixed precision and scale (DBTYPE_DECIMAL). adDouble This value indicates that the type is a double-precision floating-point value (DBTYPE_R8). adEmpty This value indicates that no type value was specified (DBTYPE_EMPTY). adError This value indicates that the type is a 32-bit error code (DBTYPE_ERROR). adGUID This value indicates that the type is a globally unique identifier (GUID) (DBTYPE_GUID). adIDispatch This value indicates that the type is a pointer to an IDispatch interface on an OLE object (DBTYPE_IDISPATCH). adInteger This value indicates that the type is a 4-byte signed integer (DBTYPE_I4).
Chapter Five—An ADO Primer n
297
Table 5-36 Field object DataTypeEnum values (cont.) Constant Description adIUnknown This value indicates that the type is a pointer to an IUnknown interface on an OLE object (DBTYPE_IUNKNOWN). adNumeric This value indicates that the type is an exact numeric value with a fixed precision and scale (DBTYPE_NUMERIC). adSingle This value indicates that the type is a single-precision floating-point value (DBTYPE_R4). adSmallInt This value indicates that the type is a 2-byte signed integer (DBTYPE_I2). adTinyInt This value indicates that the type is a 1-byte signed integer (DBTYPE_I1). adUnsignedBigInt This value indicates that the type is an 8-byte unsigned integer (DBTYPE_UI8). adUnsignedInt This value indicates that the type is a 4-byte unsigned integer (DBTYPE_UI4). adUnsignedSmallInt This value indicates that the type is a 2-byte unsigned integer (DBTYPE_UI2). adUnsignedTinyInt This value indicates that the type is a 1-byte unsigned integer (DBTYPE_UI1). adUserDefined This value indicates that the type is a user-defined variable (DBTYPE_UDT). adVariant This value indicates that the type is an Automation Variant (DBTYPE_VARIANT). adVector This value indicates that the type is joined in a logical OR together with another type to indicate that the data is a DBVECTOR structure, as defined by OLE DB, that contains a count of elements and a pointer to data of the other type (DBTYPE_VECTOR). adWChar This value indicates that the type is a null-terminated Unicode character string (DBTYPE_WSTR).
UnderlyingValue Property (Field Object) An application should use the UnderlyingValue property to return the current field value from the database. The field value in the UnderlyingValue property is the value that is visible to an application’s transaction and may be the result of a recent update by another transaction. This may differ from the OriginalValue property, which reflects the value that was originally returned to the Recordset. This is similar to using the Resync method, but the UnderlyingValue property returns only the value for a specific field from the current record. This is the same value that the Resync method uses to replace the Value property.
298
n Chapter Five—An ADO Primer
Value Property (Field Object) An application should use the Value property to set or return data from Field objects. ADO allows setting and returning Long amounts of binary data with the Value property. Otherwise, the rules for manipulating Value depend on the data type (see Table 5-36).
AppendChunk Method (Field Object) An application should use the AppendChunk method on a Field object to fill it with Long binary or character data. In situations where system memory is limited, an application can use the AppendChunk method to manipulate Long values in portions rather than in their entirety. If the adFldLong bit in the Attributes property of a Field object is set to True, an application can use the AppendChunk method for that field. The first AppendChunk call on a Field object writes data to the field, overwriting any existing data. Subsequent AppendChunk calls add to existing data. If an application is appending data to one field and then chooses to set or read the value of another field in the current record, ADO assumes that the application is done appending data to the first field. If an application calls the AppendChunk method on the first field again, ADO interprets the call as a new AppendChunk operation and overwrites the existing data. Accessing fields in other Recordset objects that are not clones of the first Recordset object will not disrupt AppendChunk operations. If there is no current record when an application calls AppendChunk on a Field object, an ADO error occurs. The method has the following syntax: pField->AppendChunk(Data);
The method has the following parameter: Data This parameter is a Variant containing the data the application wants to append to the Field object.
GetChunk Method (Field Object) An application should use the GetChunk method on a Field object to retrieve part or all of its Long binary or character data. In situations where system memory is limited, an application can use the GetChunk method to manipulate Long values in portions rather than in their entirety. The data that a GetChunk call returns is assigned to pMyVariant. If Size is greater than the remaining data, the GetChunk method returns only the remaining data without padding pMyVariant with empty spaces. If the field is empty, the GetChunk method returns Null. Each subsequent GetChunk call retrieves data starting from where the previous GetChunk call left off. However, if an application is retrieving data from one field and then it chooses to set or read the value of another field in the current record, ADO assumes the application is
Chapter Five—An ADO Primer n
299
done retrieving data from the first field. If an application calls the GetChunk method on the first field again, ADO interprets the call as a new GetChunk operation and starts reading from the beginning of the data. Accessing fields in other Recordset objects that are not clones of the first Recordset object will not disrupt GetChunk operations. If the adFldLong bit in the Attributes property of a Field object is set to True, an application can use the GetChunk method for that field. If there is no current record when an application uses the GetChunk method on a Field object, an ADO error occurs. The method has the following syntax: pMyVariant = pField->GetChunk(Size);
The method returns a Variant. The method has the following parameter: Size This parameter is a Long expression equal to the number of bytes or characters an application wants to retrieve.
The ADO Error Object Any operation involving ADO objects can generate one or more OLE DB provider errors. As each error occurs, one or more Error objects are placed in the Errors collection of the Connection object. When another ADO operation generates an error, the Errors collection is cleared, and the new set of Error objects is placed in the Errors collection. Each Error object represents a specific provider error, not an ADO error. An application can read an Error object’s properties to obtain specific details about each error, including the following: n
The Description property, which contains the text of the error.
n
The Number property, which contains the Long integer value of the error constant.
n
The Source property, which identifies the object that raised the error. This is particularly useful when an application has several Error objects in the Errors collection following a request to a data source.
n
The SQLState and NativeError properties, which provide information from SQL data sources.
When an OLE DB provider error occurs, it is placed in the Errors collection of the Connection object. ADO supports the return of multiple errors by a single ADO operation to allow for error information specific to the provider. The Errors collection on the Connection object is cleared and populated only when the OLE DB provider generates a new error, or when the Clear method is called. Some properties and methods return warnings that appear as Error objects in the Errors collection but do not halt a program’s execution with an
300
n Chapter Five—An ADO Primer ADO error. Before an application calls the Resync, UpdateBatch, or CancelBatch methods on a Recordset object or the Open method on a Connection object, or sets the Filter property on a Recordset object, it should first call the Clear method on the Errors collection so that it can read the Count property of the Errors collection to test for returned warnings. Note
All properties of the Error object are read-only.
Description Property (Error Object) An application should use the Description property to obtain a short description of the error. It should display this property to alert the user to an error that it cannot or does not want to handle. The string will come from either ADO or an OLE DB provider. OLE DB providers are responsible for passing specific error text to ADO. ADO adds an Error object to the Errors collection for each OLE DB provider error or warning it receives. Applications should enumerate the Errors collection to trace the errors that the OLE DB provider passes. The property holds a String value.
NativeError Property (Error Object) Applications should use the NativeError property to retrieve the database-specific error information for a particular Error object. When using the Microsoft ODBC Provider for OLE DB with a Microsoft SQL Server database, native error codes that originate from SQL Server pass through ODBC and the ODBC provider to the ADO NativeError property. The property holds a Long value.
Number Property (Error Object) Applications should use the Number property to determine which error occurred. The value of the property is a unique number that corresponds to the error condition (defined by the OLE DB provider). The property holds a Long value.
Source Property (Error Object) Applications should use the Source property on an Error object to determine the name of the object or application that originally generated an error. This could be the object’s class name or programmatic ID. For errors in ADODB, the property value will be ADODB.ObjectName, where ObjectName is the name of the object that triggered the error. The property holds a String value.
SQLState Property (Error Object) Applications should use the SQLState property to read the five-character error code that the provider returns when an error occurs during the
Chapter Five—An ADO Primer n
301
processing of a SQL statement. When using the Microsoft OLE DB Provider for ODBC with a Microsoft SQL Server database, SQL state error codes originate from ODBC based either on errors specific to ODBC or on errors that originate from Microsoft SQL Server and are then mapped to ODBC errors. These error codes are documented in the ANSI SQL standard, but may be implemented differently by different data sources. The property holds a String value.
The ADO Property Object ADO objects have two types of properties: built-in and dynamic. Built-in properties are those properties implemented in ADO and immediately available to any new object. They do not appear as Property objects in an object’s Properties collection, so although an application can change their values, it cannot modify their characteristics. Dynamic properties are defined by the underlying OLE DB provider, and appear in the Properties collection for the appropriate ADO object. For example, a property specific to the provider may indicate if a Recordset object supports transactions or updating. These additional properties will appear as Property objects in that Recordset object’s Properties collection. Dynamic properties can be referenced only through the collection. An application must consult the documentation for each OLE DB provider as to the names, data types, and semantics of its dynamic properties. A dynamic Property object in ADO has four built-in properties of its own: n
The Attributes property is a Long value that indicates characteristics of the property specific to the OLE DB provider.
n
The Name property is a String that identifies the property.
n
The Type property is an Integer that specifies the property data type.
n
The Value property is a Variant that contains the property setting.
Attributes Property (Property Object) For a Property object, the Attributes property is read-only. Its value can be the sum of any one or more of the PropertyAttributesEnum values in Table 5-37: Table 5-37
Property object PropertyAttributesEnum values
Constant adPropNotSupported adPropRequired adPropOptional adPropRead adPropWrite
Description This value indicates that the property is not supported by the provider. This value indicates that the user must specify a value for this property before the data source is initialized. This value indicates that the user does not need to specify a value for this property before the data source is initialized. This value indicates that the user can read the property. This value indicates that the user can set the property.
302
n Chapter Five—An ADO Primer
Name Property (Property Object) Applications should use the Name property to retrieve the name of a Property object. The Name property is read-only. Names do not have to be unique within a collection. An application can retrieve the Name property of a Property object by an ordinal reference, after which it can refer to the object directly by name. For example, if MyConnection.Properties(15).Name yields TransactionState, an application can subsequently refer to this property as MyConnection.Properties(“TransactionState”).
Type Property (Property Object) This read-only property indicates the operational type or data type of a Property object. It contains one of the DataTypeEnum values in Table 5-38. The corresponding OLE DB type indicator is shown in parentheses. Table 5-38 Constant adArray
adBigInt adBinary adBoolean adByRef
adBSTR adChar adCurrency
adDate
adDBDate adDBTime
Property object DataTypeEnum values Description This value indicates that the type is joined in a logical OR together with another type to indicate that the data is a safe-array of that type (DBTYPE_ARRAY). This value indicates that the type is an 8-byte signed integer (DBTYPE_18). This value indicates that the type is a binary value (DBTYPE_BYTES). This value indicates that the type is a Boolean value (DBTYPE_BOOL). This value indicates that the type is joined in a logical OR together with another type to indicate that the data is a pointer to data of the other type (DBTYPE_BYREF). This value indicates that the type is a null-terminated character string (Unicode) (DBTYPE_BSTR). This value indicates that the type is a String value (DBTYPE-STR). This value indicates that the type is a currency value (DBTYPE_CY). Currency is a fixed-point number with four digits to the right of the decimal point. It is stored in an 8-byte signed integer scaled by 10,000. This value indicates that the type is a Date value (DBTYPE_DATE). A date is stored as a Double, the whole part of which is the number of days since December 30, 1899, and the fractional part of which is the fraction of a day. This value indicates that the type is a date value (yyyymmdd) (DBTYPE_DBDATE). This value indicates that the type is a time value (hhmmss) (DBTYPE_DBTIME).
Chapter Five—An ADO Primer n
303
Table 5-38 Property object DataTypeEnum values (cont.) Constant Description adDBTimeStamp This value indicates that the type is a date-time stamp (yyyymmddhhmmss plus a fraction in billionths) (DBTYPE_DBTIMESTAMP). adDecimal This value indicates that the type is an exact numeric value with a fixed precision and scale (DBTYPE_DECIMAL). adDouble This value indicates that the type is a double-precision floating-point value (DBTYPE_R8). adEmpty This value indicates that no type value was specified (DBTYPE_EMPTY). adError This value indicates that the type is a 32-bit error code (DBTYPE_ERROR). adGUID This value indicates that the type is a globally unique identifier (GUID) (DBTYPE_GUID). adIDispatch This value indicates that the type is a pointer to an IDispatch interface on an OLE object (DBTYPE_IDISPATCH). adInteger This value indicates that the type is a 4-byte signed integer (DBTYPE_I4). adIUnknown This value indicates that the type is a pointer to an IUnknown interface on an OLE object (DBTYPE_IUNKNOWN). adNumeric This value indicates that the type is an exact numeric value with a fixed precision and scale (DBTYPE_NUMERIC). adSingle This value indicates that the type is a single-precision floating-point value (DBTYPE_R4). adSmallInt This value indicates that the type is a 2-byte signed integer (DBTYPE_I2). adTinyInt This value indicates that the type is a 1-byte signed integer (DBTYPE_I1). adUnsignedBigInt This value indicates that the type is an 8-byte unsigned integer (DBTYPE_UI8). adUnsignedInt This value indicates that the type is a 4-byte unsigned integer (DBTYPE_UI4). adUnsignedSmallInt This value indicates that the type is a 2-byte unsigned integer (DBTYPE_UI2). adUnsignedTinyInt This value indicates that the type is a 1-byte unsigned integer (DBTYPE_UI1). adUserDefined This value indicates that the type is a user-defined variable (DBTYPE_UDT). adVariant This value indicates that the type is an Automation Variant (DBTYPE_VARIANT).
304
n Chapter Five—An ADO Primer Table 5-38 Constant adVector
adWChar
Property object DataTypeEnum values (cont.) Description This value indicates that the type is joined in a logical OR together with another type to indicate that the data is a DBVECTOR structure, as defined by OLE DB, that contains a count of elements and a pointer to data of the other type (DBTYPE_VECTOR). This value indicates that the type is a null-terminated Unicode character string (DBTYPE_WSTR).
Value Property (Property Object) Applications should use the Value property to set or return property settings with Property objects; the property is a Variant and can contain any appropriate data as indicated in Table 5-38. ADO allows setting and returning Long binary data with the Value property.
The ADO Parameter Object Many providers support parameterized commands. These are commands in which the desired action is defined once, but variables (or parameters) are used to alter some details of the command. For example, a SQL SELECT statement could use a parameter to define the matching criteria of a WHERE clause, and another to define the column name for a SORT BY clause. Parameter objects represent parameters associated with parameterized queries, or the in/out arguments and the return values of stored procedures. If an application knows the names and properties of the parameters associated with the stored procedure or parameterized query it wishes to call, it can use the CreateParameter method to create Parameter objects with the appropriate property settings and use the Append method to add them to the Parameters collection. This lets an application set and return parameter values without having to call the Refresh method on the Parameters collection to retrieve the parameter information from the OLE DB provider, a lengthy and resource-intensive operation. With the collections, methods, and properties of a Parameter object, an application can: n
Set or return the name of a parameter with the Name property.
n
Set or return the value of a parameter with the Value property.
n
Set or return parameter characteristics with the Attributes, Precision, NumericScale, Size, and Type properties.
Attributes Property (Parameter Object) The Attributes property describes several behavioral capabilities of the Parameter object; its value can be the sum of any one or more of the ParameterAttributesEnum values in Table 5-39. The property is read-only.
Chapter Five—An ADO Primer n Table 5-39
305
Parameter object ParameterAttributesEnum values
Constant adParamSigned adParamNullable adParamLong
Description This default value indicates that the parameter accepts signed values. This value indicates that the parameter accepts Null values. This value indicates that the parameter accepts Long binary data.
Name Property (Parameter Object) Applications should use the Name property to assign a name to or retrieve the name of a Parameter object. For Parameter objects not yet appended to the Parameters collection, the Name property is read/write. For appended Parameter objects, the Name property is read-only. Names do not have to be unique within a collection. An application can retrieve the Name property of an object by an ordinal reference, after which it can refer to the object directly by name. For example, if MyConnection.Parameters(13).Name yields BinaryParameter, an application can subsequently refer to this parameter as MyConnection.Parameters(“BinaryParameter”).
NumericScale Property (Parameter Object) An application should use the NumericScale property to determine how many digits to the right of the decimal point will be used to represent values for a numeric Parameter object. For Parameter objects, the NumericScale property is read/write.
Precision Property (Parameter Object) An application should use the Precision property to determine the maximum number of digits used to represent values for a numeric Parameter object. The property is read-only and a Long value.
Size Property (Parameter Object) Applications should use the Size property to determine the maximum size for values written to or read from the Value property of a Parameter object. The Size property is read/write. If an application chooses to specify a variablelength data type for a Parameter object (for example, any String type, such as adVarChar), it must set the object’s Size property before appending it to the Parameters collection; otherwise an ADO error occurs. If an application has already appended the Parameter object to the Parameters collection of a Command object and it chooses to change its type to a variable-length data type, it must set the Parameter object’s Size property before executing the Command object; otherwise an ADO error occurs. If an application uses the Refresh method to obtain parameter information from the OLE DB provider and it returns one or more variable-length data type Parameter objects, ADO
306
n Chapter Five—An ADO Primer may allocate memory for the parameters based on their maximum potential size, which could cause an error during execution. To prevent an error, an application should explicitly set the Size property for all parameters before executing the command.
Type Property (Parameter Object) This read-write property indicates the operational type or data type of a Parameter object. It contains one of the DataTypeEnum values in Table 5-40. The corresponding OLE DB type indicator is shown in parentheses. Table 5-40
Parameter object DataTypeEnum values
Constant adArray
adBigInt adBinary adBoolean adByRef
adBSTR adChar adCurrency
adDate
adDBDate adDBTime adDBTimeStamp
adDecimal adDouble
Description This value indicates that the type is joined in a logical OR together with another type to indicate that the data is a safe-array of that type (DBTYPE_ARRAY). This value indicates that the type is an 8-byte signed integer (DBTYPE_I8). This value indicates that the type is a binary value (DBTYPE_BYTES). This value indicates that the type is a Boolean value (DBTYPE_BOOL). This value indicates that the type is joined in a logical OR together with another type to indicate that the data is a pointer to data of the other type (DBTYPE_BYREF). This value indicates that the type is a null-terminated character string (Unicode) (DBTYPE_BSTR). This value indicates that the type is a String value (DBTYPE_STR). This value indicates that the type is a currency value (DBTYPE_CY). Currency is a fixed-point number with four digits to the right of the decimal point. It is stored in an 8-byte signed integer scaled by 10,000. This value indicates that the type is a Date value (DBTYPE_DATE). A date is stored as a Double, the whole part of which is the number of days since December 30, 1899, and the fractional part of which is the fraction of a day. This value indicates that the type is a date value (yyyymmdd) (DBTYPE_DBDATE). This value indicates that the type is a time value (hhmmss) (DBTYPE_DBTIME). This value indicates that the type is a date-time stamp (yyyymmddhhmmss plus a fraction in billionths) (DBTYPE_DBTIMESTAMP). This value indicates that the type is an exact numeric value with a fixed precision and scale (DBTYPE_DECIMAL). This value indicates that the type is a double-precision floating-point value (DBTYPE_R8).
Chapter Five—An ADO Primer n Table 5-40 Constant adEmpty
307
Parameter object DataTypeEnum values (cont.) Description This value indicates that no type value was specified (DBTYPE_EMPTY). adError This value indicates that the type is a 32-bit error code (DBTYPE_ERROR). adGUID This value indicates that the type is a globally unique identifier (GUID) (DBTYPE_GUID). adIDispatch This value indicates that the type is a pointer to an IDispatch interface on an OLE object (DBTYPE_IDISPATCH). adInteger This value indicates that the type is a 4-byte signed integer (DBTYPE_I4). adIUnknown This value indicates that the type is a pointer to an IUnknown interface on an OLE object (DBTYPE_IUNKNOWN). adLongVarBinary This value indicates that the type is a Long binary value. adLongVarChar This value indicates that the type is a Long String value. adLongVarWChar This value indicates that the type is a Long null-terminated string value. adNumeric This value indicates that the type is an exact numeric value with a fixed precision and scale (DBTYPE_NUMERIC). adSingle This value indicates that the type is a single-precision floating-point value (DBTYPE_R4). adSmallInt This value indicates that the type is a 2-byte signed integer (DBTYPE_I2). adTinyInt This value indicates that the type is a 1-byte signed integer (DBTYPE_I1). adUnsignedBigInt This value indicates that the type is an 8-byte unsigned integer (DBTYPE_UI8). adUnsignedInt This value indicates that the type is a 4-byte unsigned integer (DBTYPE_UI4). adUnsignedSmallInt This value indicates that the type is a 2-byte unsigned integer (DBTYPE_UI2). adUnsignedTinyInt This value indicates that the type is a 1-byte unsigned integer (DBTYPE_UI1). adUserDefined This value indicates that the type is a user-defined variable (DBTYPE_UDT). adVarBinary This value indicates that the type is a binary value. adVarChar This value indicates that the type is a String value. adVariant This value indicates that the type is an Automation Variant (DBTYPE_VARIANT). adVector This value indicates that the type is joined in a logical OR together with another type to indicate that the data is a DBVECTOR structure, as defined by OLE DB, that contains a count of elements and a pointer to data of the other type (DBTYPE_VECTOR).
308
n Chapter Five—An ADO Primer Table 5-40 Parameter object DataTypeEnum values (cont.) Constant Description adVarWChar This value indicates that the type is a null-terminated Unicode character string. adWChar This value indicates that the type is a null-terminated Unicode character string (DBTYPE_WSTR).
Value Property (Parameter Object) Applications should use the Value property to set or return parameter values with Parameter objects. The property holds a Variant which can assume any of the allowed data types in Table 5-40. ADO allows setting and returning Long binary data with the Value property.
ADO Events Events are notifications between applications, or between the operating system and applications, that some state of the application or system has changed. An example are windows messages in response to a mouse action; these result in mouse events. ADO has its own unique set of events, each of which has a section below that explains when it happens, what information it provides, and what ADO-using applications should do about the event.
BeginTransComplete Event This event handler is called after the associated operation on the Connection object finishes executing; BeginTransComplete is called after the BeginTrans operation. In Visual C++ multiple Connection objects can share the same event handling method. The method implementation should use the returned Connection object to determine which object (interface pointer) caused the event. If the Attributes property is set to adXactCommitRetaining or adXactAbortRetaining, a new transaction is started after committing or rolling back a transaction. A BeginTransComplete event handler routine should ignore all but the first transaction start event. The event handler function has the following syntax: BeginTransComplete(TransactionLevel, pError, adStatus, pConnection);
The event handler function has the following parameters: TransactionLevel This parameter is a Long that contains the new transaction level of the BeginTrans operation that caused this event. pError This parameter is an Error object that describes the error that occurred if
Chapter Five—An ADO Primer n
309
the value of EventStatusEnum is adStatusErrorsOccurred; otherwise it is not set. adStatus This parameter is an EventStatusEnum status value which, when any of these methods is called, is set to adStatusOK if the operation that caused the event was successful, or adStatusErrorsOccurred if the operation failed. The event handler can prevent subsequent notifications by setting this parameter to adStatusUnwantedEvent before the method returns. pConnection This parameter is the Connection object for which this event occurred.
CommitTransComplete Event This event handler is called after the associated operation on the Connection object finishes executing; CommitTransComplete is called after the CommitTrans operation. In Visual C++ multiple Connection objects can share the same event handling method. The method implementation should use the returned Connection object to determine which object (interface pointer) caused the event. If the Attributes property is set to adXactCommitRetaining or adXactAbortRetaining, a new transaction is started after committing or rolling back a transaction. The event handler function has the following syntax: CommitTransComplete(pError, adStatus, pConnection);
The event handler function has the following parameters: pError This parameter is an Error object that describes the error that occurred if the value of EventStatusEnum is adStatusErrorsOccurred; otherwise it is not set. adStatus This parameter is an EventStatusEnum status value that is set to adStatusOK if the operation that caused the event was successful, or adStatusErrorsOccurred if the operation failed. In RollbackTransComplete, an application can prevent subsequent notifications by setting this parameter to adStatusUnwantedEvent before the method returns. pConnection This parameter is the Connection object for which this event occurred.
ConnectComplete Event This event handler is called after a connection starts. The Disconnect event is called after a connection ends.
310
n Chapter Five—An ADO Primer The event handler function has the following syntax: ConnectComplete(pError, adStatus, pConnection);
The event handler function has the following parameters: pError This parameter is an Error object that describes the error that occurred if the value of adStatus is adStatusErrorsOccurred; otherwise it is not set. adStatus This parameter is an EventStatusEnum status value that is set to adStatusOK if the operation that caused the event was successful, or adStatusErrorsOccurred if the operation failed. When ConnectComplete is called, this parameter is set to adStatusCancel if a WillConnect method has requested cancellation of the pending connection. Before ConnectComplete returns, an application can set this parameter to adStatusUnwantedEvent to prevent subsequent notifications. pConnection This parameter is the Connection object for which this event applies.
Disconnect Event This event handler is called after a connection ends. The ConnectComplete event handler is called after a connection starts. The event handler function has the following syntax: Disconnect(pError, adStatus, pConnection);
The event handler function has the following parameters: pError This parameter is an Error object that describes the error that occurred if the value of adStatus is adStatusErrorsOccurred; otherwise it is not set. adStatus This parameter is an EventStatusEnum status value that is set to adStatusOK if the operation that caused the event was successful, or adStatusErrorsOccurred if the operation failed. Before Disconnect returns, an application can set this parameter to adStatusUnwantedEvent to prevent subsequent notifications. pConnection This parameter is the Connection object for which this event applies.
EndOfRecordset Event This event handler is called when there is an attempt to move to a row past the end of the Recordset. An EndOfRecordset event may also occur if the
Chapter Five—An ADO Primer n
311
Recordset.MoveNext operation fails for other reasons. This event handler is called when the application attempts to move past the end of the records in the pRecordset parameter, perhaps as a result of calling MoveNext. However, while in this event handler the application could retrieve more records from a database and append them to the end of pRecordset. In that case, the event handler implementation would set fMoreData to VARIANT_TRUE, and return from EndOfRecordset. Then the application could safely call MoveNext again to access the newly retrieved records. The event handler function has the following syntax: EndOfRecordset(fMoreData, adStatus, pRecordset);
The event handler function has the following parameters: fMoreData This parameter is a VARIANT_BOOL flag, which makes it possible to append new records to pRecordset while processing this event. Before EndOfRecordset returns, the implementation should add the new data, then set this parameter to True to indicate that there is a new end to the Recordset. adStatus This parameter is an EventStatusEnum status value that is set to adStatusOK if the operation that caused the event was successful. It is set to adStatusCantDeny if this method cannot request cancellation of the operation that caused this event. Before EndOfRecordset returns, an application can set this parameter to adStatusUnwantedEvent to prevent subsequent notifications. pRecordset This parameter is the Recordset object for which this event occurred.
ExecuteComplete Event This event handler is called after a command has finished executing. An ExecuteComplete event can occur due to Connection.Execute, Command.Execute, Recordset.Open, or Recordset.NextRecordset. The event handler function has the following syntax: ExecuteComplete(RecordsAffected, pError, adStatus, pCommand, pRecordset, pConnection);
The event handler function has the following parameters: RecordsAffected This parameter is a Long containing the number of records affected by the command.
312
n Chapter Five—An ADO Primer pError This parameter is an Error object that describes the error that occurred if the value of adStatus is adStatusErrorsOccurred; otherwise it is not set. adStatus This parameter is an EventStatusEnum status value that is set to adStatusOK if the operation that caused the event was successful, or adStatusErrorsOccurred if the operation failed. Before this method returns, an application can set this parameter to adStatusUnwantedEvent to prevent subsequent notifications. pCommand This parameter is the Command object that was executed. If the execution was not via a Command object, then this parameter is empty. pRecordset This parameter is a Recordset object containing the result of the execution, which may be empty. pConnection This parameter is a Connection object on which the command was executed.
FetchComplete Event This event handler is called after all the records in a lengthy asynchronous operation have been retrieved (fetched) into the Recordset. It is normally used to dismiss a visual progress indicator for the operation. The event handler function has the following syntax: FetchComplete(pError, adStatus, pRecordset);
The event handler function has the following parameters: pError This parameter is an Error object that describes the error that occurred if the value of adStatus is adStatusErrorsOccurred; otherwise it is not set. adStatus This parameter is an EventStatusEnum status value that is set to adStatusOK if the operation that caused the event was successful, or adStatusErrorsOccurred if the operation failed. Before this method returns, an application can set this parameter to adStatusUnwantedEvent to prevent subsequent notifications. pRecordset This parameter is a Recordset object containing the result of the fetch operation, which may be empty.
Chapter Five—An ADO Primer n
313
FetchProgress Event This event handler is called periodically during a lengthy asynchronous operation to report how many rows have currently been retrieved (fetched) into the current Recordset. Its normal use is to update a progress bar or other visual display to maintain a user comfort level during the lengthy operation to prevent their aborting it. The event handler function has the following syntax: FetchProgress(Progress, MaxProgress, pRecordset);
The event handler function has the following parameters: Progress This parameter is a Long containing the number of records that have currently been retrieved. MaxProgress This parameter is a Long containing the maximum number of records expected to be retrieved. pRecordset This parameter is a Recordset object for which the records are being retrieved.
FieldChangeComplete Event This event handler is called after the value of one or more Field objects has changed. A FieldChangeComplete event can occur due to the following Recordset operations: calls to Value and Update with field and value array parameters. The event handler function has the following syntax: FieldChangeComplete(cFields, Fields, pError, adStatus, pRecordset);
The event handler function has the following parameters: cFields This parameter is a Long and is the number of Field objects in Fields. Fields This parameter is an array of Variants that contains Field objects with pending changes. pError This parameter is an Error object that describes the error that occurred if the value of adStatus is adStatusErrorsOccurred; otherwise it is not set. adStatus This parameter is an EventStatusEnum status value that is set to adStatusOK if the operation that caused the event was successful, or
314
n Chapter Five—An ADO Primer adStatusErrorsOccurred if the operation failed. Before FieldChangeComplete returns, an application can set this parameter to adStatusUnwantedEvent to prevent subsequent notifications. pRecordset This parameter is a Recordset object for which this event occurred.
InfoMessage Event This event handler is called whenever a ConnectionEvent operation completes successfully and additional information is returned by an OLE DB provider. The event handler function has the following syntax: InfoMessage(pError, adStatus, pConnection);
The event handler function has the following parameters: pError This parameter is an Error object that describes the ADO error that occurred if the value of adStatus is adStatusErrorsOccurred; otherwise it is not set. Multiple OLE DB provider warnings can be returned, which can be found by enumerating the Errors collection. adStatus This parameter is an EventStatusEnum status value that is set to adStatusOK if the operation that caused the event was successful, or adStatusErrorsOccurred if the operation failed. Before this method returns, an application can set this parameter to adStatusUnwantedEvent to prevent subsequent notifications. pConnection This parameter is a Connection object on which the command was executed.
MoveComplete Event The MoveComplete method is called after the current position in the Recordset changes. A MoveComplete event can occur due to the following Recordset method calls (depending on whether they cause the position to change): Open, Move, MoveFirst, MoveLast, MoveNext, MovePrevious, Bookmark, AddNew, Delete, Requery, and Resync. The event handler function has the following syntax: MoveComplete(adReason, pError, adStatus, pRecordset);
Chapter Five—An ADO Primer n
315
The event handler function has the following parameters: adReason This parameter is an EventReasonEnum value that specifies the reason for this event. Its value can be adRsnMoveFirst, adRsnMoveLast, adRsnMoveNext, adRsnMovePrevious, adRsnMove, or adRsnRequery. Each such enumeration value corresponds to the name of the method that caused the event to fire (i.e., a value of adRsnMoveLast means that a call was made to MoveLast, resulting in a position change that fired the event). pError This parameter is an Error object that describes the error that occurred if the value of adStatus is adStatusErrorsOccurred; otherwise it is not set. adStatus This parameter is an EventStatusEnum status value that is set to adStatusOK if the operation that caused the event was successful, or adStatusErrorsOccurred if the operation failed. Before MoveComplete returns, an application can set this parameter to adStatusUnwantedEvent to prevent subsequent notifications. pRecordset This parameter is a Recordset object for which this event occurred.
OnError Event This method is called whenever an error occurs during an ADO object operation (this is different from the errors that are put in the Errors collection; those come from the OLE DB provider itself). The event handler function has the following syntax: onError(SCode, Description, Source, CancelDisplay);
The event handler function has the following parameters: SCode This parameter is an integer that contains the status code (HRESULT) of the error. Description This parameter is a String that contains a description of the error. Source This parameter is a String that contains the SQL query or command that caused the error. CancelDisplay This parameter is a Boolean that if set to True prevents the error from being displayed in a dialog box (useful for remote server applications where there is no one to see and dismiss the dialog box).
316
n Chapter Five—An ADO Primer
OnReadyStateChange Event This event handler is called whenever the value of the ReadyState property changes. The ReadyState property reflects the progress of an RDS.Data Control object as it asynchronously fetches data into its Recordset object. Use the onReadyStateChange event handler to monitor changes in the ReadyState property whenever they occur, rather than periodically checking the property’s value. The event handler function has the following syntax: onReadyStateChange();
The event handler function has no parameters.
RecordChangeComplete Event This event handler is called after one or more records change. A RecordChangeComplete event can occur due to the following Recordset operations: Update, Delete, CancelUpdate, AddNew, UpdateBatch, and CancelBatch (depending on whether they actually change one or more records). The event handler function has the following syntax: RecordChangeComplete(adReason, cRecords, pError, adStatus, pRecordset);
The event handler function has the following parameters: adReason This parameter is an EventReasonEnum value that specifies the reason for this event. Its value can be adRsnAddNew, adRsnDelete, adRsn- Update, adRsnUndoUpdate, adRsnUndoAddNew, adRsnUndoDelete, or adRsnFirstChange, with the value of the enumeration giving the name of the method that fired the event (i.e., adRsnDelete means that a Delete method call caused the event to fire). cRecords This parameter is a Long that is the number of records changing (affected). pError This parameter is an Error object that describes the error that occurred if the value of adStatus is adStatusErrorsOccurred; otherwise it is not set. adStatus This parameter is an EventStatusEnum status value that is set to adStatusOK if the operation that caused the event was successful, or adStatusErrorsOccurred if the operation failed. Before RecordChangeComplete returns, an application can set this parameter to adStatusUnwantedEvent to prevent subsequent notifications.
Chapter Five—An ADO Primer n
317
pRecordset This parameter is a Recordset object containing the result of the change.
RecordsetChangeComplete Event This event handler is called after the Recordset has changed. A RecordsetChangeComplete event can occur due to the following Recordset operations: Requery, Resync, Close, Open, and Filter (depending on whether they change the Recordset). The event handler function has the following syntax: RecordsetChangeComplete(adReason, pError, adStatus, pRecordset);
The event handler function has the following parameters: adReason This parameter is an EventReasonEnum value that specifies the reason for this event. Its value can be adRsnReQuery, adRsnReSynch, adRsnClose, or adRsnOpen, with the name of the enumeration giving the name of the method that fired the event (i.e., adRsnClose means that the Close method call fired the current event). pError This parameter is an Error object that describes the error that occurred if the value of adStatus is adStatusErrorsOccurred; otherwise it is not set. adStatus This parameter is an EventStatusEnum status value that is set to adStatusOK if the operation that caused the event was successful, adStatusErrorsOccurred if the operation failed, or adStatusCancel if the operation associated with the previously accepted WillChangeRecordset event has been canceled. Before RecordsetChangeComplete returns, an application can set this parameter to adStatusUnwantedEvent to prevent subsequent notifications. pRecordset This parameter is a Recordset object for which this event occurred.
RollbackTransComplete Event This event handler will be called after the associated operation on the Connection object finishes executing. RollbackTransComplete is called after the RollbackTrans operation. In Visual C++ multiple connections can share the same event handling method. The event handler uses the returned Connection object to determine which object caused the event. If the Attributes property is set to adXactCommitRetaining or adXactAbortRetaining, a new transaction is started after committing or rolling back a transaction.
318
n Chapter Five—An ADO Primer The event handler function has the following syntax: RollbackTransComplete(pError, adStatus, pConnection);
The event handler function has the following parameters: pError This parameter is an Error object that describes the error that occurred if the value of EventStatusEnum is adStatusErrorsOccurred; otherwise it is not set. adStatus This parameter is an EventStatusEnum status value that is set to adStatusOK if the operation that caused the event was successful, or adStatusErrorsOccurred if the operation failed. In RollbackTransComplete, an application can prevent subsequent notifications by setting this parameter to adStatusUnwantedEvent before the method returns. pConnection This parameter is the Connection object for which this event occurred.
WillChangeField Event This event handler is called before a pending operation changes the value of one or more Field objects in the Recordset. A WillChangeField event can occur due to the following Recordset operations: calls to Value and Update with field and value array parameters. The event handler function has the following syntax: WillChangeField(cFields, Fields, adStatus, pRecordset);
The event handler function has the following parameters: cFields This parameter is a Long and is the number of Field objects in Fields. Fields This parameter is an array of Variants that contains Field objects with pending changes. adStatus This parameter is an EventStatusEnum status value that is set to adStatusOK if the operation that caused the event was successful. It is set to adStatusCantDeny if this method cannot request cancellation of the pending operation. Before WillChangeField returns, an application can set this parameter to adStatusCancel to request cancellation of the pending operation. pRecordset This parameter is a Recordset object for which this event occurred.
Chapter Five—An ADO Primer n
319
WillChangeRecord Event This event handler is called before one or more records (rows) in the Recordset change. A WillChangeRecord event can occur due to the following Recordset operations: Update, Delete, CancelUpdate, AddNew, UpdateBatch, and CancelBatch (depending on whether they actually change one or more records). During the WillChangeRecord event, the Recordset Filter property is set to adFilterAffectedRecords. It is illegal to change this property while processing the event. The event handler function has the following syntax: WillChangeRecord(adReason, cRecords, adStatus, pRecordset);
The event handler function has the following parameters: adReason This parameter is an EventReasonEnum value that specifies the reason for this event. Its value can be adRsnAddNew, adRsnDelete, adRsnUpdate, adRsnUndoUpdate, adRsnUndoAddNew, adRsnUndo- Delete, or adRsnFirstChange, with the value of the enumeration giving the name of the method that fired the event (i.e., adRsnDelete means that a Delete method call caused the event to fire). cRecords This parameter is a Long and is the number of records changing (affected). adStatus This parameter is an EventStatusEnum status value that is set to adStatusOK if the operation that caused the event was successful. It is set to adStatusCantDeny if this method cannot request cancellation of the pending operation. Before WillChangeRecord returns, an application can set this parameter to adStatusCancel to request cancellation of the operation that caused this event. pRecordset This parameter is a Recordset object for which this event occurred.
WillChangeRecordset Event This event handler is called before a pending operation changes the Recordset. A WillChangeRecordset event can occur due to the following Recordset operations: Requery, Resync, Close, Open, and Filter (depending on whether they change the Recordset). The event handler function has the following syntax: WillChangeRecordset(adReason, adStatus, pRecordset);
320
n Chapter Five—An ADO Primer The event handler function has the following parameters: adReason This parameter is an EventReasonEnum value that specifies the reason for this event. Its value can be adRsnReQuery, adRsnReSynch, adRsnClose, or adRsnOpen, with the name of the enumeration giving the name of the method that fired the event (i.e., adRsnClose means that the Close method call fired the current event). adStatus This parameter is an EventStatusEnum status value that is set to adStatusOK if the operation that caused the event was successful. It is set to adStatusCantDeny if this method cannot request cancellation of the pending operation. Before WillChangeRecordset returns, an application can set this parameter to adStatusCancel to request cancellation of the pending operation or to adStatusUnwantedEvent to prevent subsequent notifications. pRecordset This parameter is a Recordset for which this event occurred.
WillConnect Event This event handler is called before a connection starts. The parameters to be used in the pending connection are supplied as input parameters and can be changed before the method returns. This event handler may return a request that the pending connection be canceled. When this method is called, the ConnectionString, UserID, Password, and Options parameters are set to the values established by the operation that caused this event. When this method is canceled, ConnectComplete will be called with its adStatus parameter set to adStatusErrorsOccurred. The event handler function has the following syntax: WillConnect(ConnectionString, UserID, Password, Options, adStatus, pConnection);
The event handler function has the following parameters: ConnectionString This parameter is a String containing connection information for the pending connection. UserID This parameter is a String containing a user name for the pending connection. Password This parameter is a String containing a password for the pending connection.
Chapter Five—An ADO Primer n
321
Options This parameter is a Long value that indicates how the provider should evaluate ConnectionString, using the CommandTypeEnum values. adStatus This parameter is an EventStatusEnum status value. When this event handler is called, this parameter is set to adStatusOK if the operation that caused the event was successful. This parameter is set to adStatusCantDeny if this method cannot request cancellation of the pending operation. Before this method returns, an application can set this parameter to adStatusUnwantedEvent to prevent subsequent notifications or to adStatusCancel to request the connection operation that caused cancellation of this notification. pConnection This parameter is the Connection object for which this event notification applies.
WillExecute Event This event handler is called just before a pending command executes on the current Connection object. The event gives the application an opportunity to examine and modify the pending execution parameters. This method may return a request that the pending command be canceled. A WillExecute event may occur due to Connection.Execute, Command.Execute, or Recordset.Open. The corresponding pConnection, pCommand, or pRecordset parameter will be set to the object causing the event and the remaining two will be set to Null. The event handler function has the following syntax: WillExecute(Source, CursorType, LockType, Options, adStatus, pCommand, pRecordset, pConnection);
The event handler function has the following parameters: Source This parameter is a String that contains a SQL command or a stored procedure name. CursorType This parameter is a CursorTypeEnum that contains the type of cursor for the recordset that will be opened. This parameter cannot be changed if it is set to adOpenUnspecified when this method is called. LockType This parameter is a LockTypeEnum that contains the lock type for the recordset that will be opened. This parameter cannot be changed if it is set to adLockUnspecified when this method is called.
322
n Chapter Five—An ADO Primer Options This parameter is a Long value that contains settings that can be used to execute the command or open the recordset. adStatus This parameter is an EventStatusEnum status value that may be adStatusCantDeny or adStatusOK when this method is called. If it is adStatusCantDeny, this method may not request cancellation of the pending operation. Before this method returns, it can set this parameter to adStatusUnwantedEvent to prevent subsequent notifications or to adStatusCancel to request cancellation of the operation that caused this event. pCommand This parameter is the Command object for which this event notification applies. pRecordset This parameter is the Recordset object for which this event notification applies. pConnection This parameter is the Connection object for which this event notification applies.
WillMove Event The WillMove method is called before a pending operation changes the current position in the Recordset. A WillMove event can occur due to the following Recordset method calls (depending on whether they cause the position to change): Open, Move, MoveFirst, MoveLast, MoveNext, MovePrevious, Bookmark, AddNew, Delete, Requery, and Resync. The event handler function has the following syntax: WillMove(adReason, adStatus, pRecordset);
The event handler function has the following parameters: adReason This parameter is an EventReasonEnum value that specifies the reason for this event. Its value can be adRsnMoveFirst, adRsnMoveLast, adRsnMoveNext, adRsnMovePrevious, adRsnMove, or adRsnRequery. Each such enumeration value corresponds to the name of the method that caused the event to fire (i.e., a value of adRsnMoveLast means that a call was made to MoveLast, resulting in a position change that fired the event). adStatus This parameter is an EventStatusEnum status value that is set to adStatusOK if the operation that caused the event was successful. It is set to adStatusCantDeny if this method cannot request cancellation of the
Chapter Five—An ADO Primer n
323
pending operation. Before WillMove returns, an application can set this parameter to adStatusCancel to request cancellation of the pending operation. pRecordset This parameter is a Recordset object for which this event occurred.
Where We Go From Here At this point, you’re up to speed on COM, ATL, MFC, MTS, and ADO. With this alphabet soup under your belt, it is time to fire up Visual C++ and start doing some real programming! The next three chapters take you through MTS development in ATL and MFC, for both components and clients. After that come two more chapters that show you how to install and administer MTS components and clients, and how to deploy them on the Internet and WWW.
Chapter Six
Creating and Using Simple OLE DB Providers and Consumers with ATL and MFC At this point, you understand how to use COM, ATL, MFC, OLE DB, and ADO. All these acronyms add up to the most powerful database technology the PC world has ever seen. Now it is time to do some actual programming and put all this information into practice. This chapter will provide you with a start in using the OLE DB templates for ATL and MFC, showing you how to create a simple (but really very powerful) OLE DB provider in ATL, and then a matching consumer in MFC. Chapter 7 will move on to a more powerful ATL provider that implements a three-tier client/server architecture via COM. Chapter 8 demonstrates creating a sophisticated OLE DB consumer via ATL templates that manipulates the Microsoft Agent HCI system via an Access database. Chapter 9 shows the steps needed to use MFC with OLE DB to implement a powerful OLE DB provider schema viewer with a graphical user interface. Chapter 10 finishes up our book with a detailed examination of using COM over the Internet so that your OLE DB components can be safely deployed and used inside the modern Active browser technologies.
OLE DB Template Changes are Required in ATL Unlike normal ATL programming, using the OLE DB templates requires a lot of rewriting of the wizard-generated code. What portions you change depends on what you want your provider or consumer to do: All must change the default implementations for their data mappings, and most will want to add support for updating and bookmarks as well. Making these changes is an intricate job, and for this reason this book departs a bit from my usual format.
325
326
n Chapter Six—Creating and Using Simple OLE DB Providers and Consumers Normally, I use a two-step process in guiding readers through development of projects: a step-by-step section that details interactions with the user interface, and a line-by-line section that gives the code entries and an explanation of what they mean. This system is based on the assumption that little or none of the wizard-generated code is changed. For OLE DB programming, this approach won’t work, so in this and the subsequent project chapters I use a modified system called Before and After and the Wizard’s Wand. The Before section gives a listing of the wizard-generated code, and then provides directions in a step-by-step fashion as to which elements to remove, add, or modify using line numbers. The After section gives the finished code along with a line-by-line explanation of what the code (both changed and unchanged) does. The Wizard’s Wand section gives a listing of wizard-generated code and details what it does in the line-by-line format.
Creating an OLE DB Provider COM DLL with ATL One of the most common tasks performed in personal computing is reading data from text files; with OLE DB, this chore can be promoted into the powerful realm of database programming. The following sections take you through the user interface portions of creating an OLE DB provider project that reads a text file of e-mail addresses and URLs and provides them as an OLE DB rowset. It also supports bookmarks, including dynamic column information. As noted in the previous section, the project is presented in my well-received step-by-step system, then shows you the code you need to write and an analysis using the Before and After and Wizard’s Wand techniques. Warning
You must have Visual C++ 6.0 to create this and the other projects in the book; Visual C++ 5.0 does not support the OLE DB system in the same way.
Step-By-Step Start Visual C++ 6.0. From the File menu, select New. The New dialog box opens; click on the ATL COM AppWizard entry. Next, select an appropriate directory, then enter a project name of URLProv. Figure 6-1 shows how the New dialog should appear when you are done. Press OK to open the ATL COM AppWizard.
Chapter Six—Creating and Using Simple OLE DB Providers and Consumers n
327
Figure 6-1 The New dialog box creating the URLProv ATL project
In the ATL AppWizard, select the DLL project, and make sure that the Support MFC, Merge Proxy/Stub Code, and Support MTS check boxes are unchecked since we don’t need those options; Figure 6-2 illustrates how the dialog should appear when you are done. Press Finish.
Figure 6-2 Setting the OLE DB COM server project properties
328
n Chapter Six—Creating and Using Simple OLE DB Providers and Consumers A project creation confirmation dialog box appears as shown in Figure 6-3; press OK to create the ATL project.
Figure 6-3 The confirmation dialog
From the Insert menu, select New ATL Object. The ATL Object Wizard dialog appears, as shown in Figure 6-4. Select the Data Access entry in the left-hand list box and the Provider entry in the right-hand list box. Press Next to move to the OLE DB settings page of the wizard. Figure 6-4 Selecting an OLE DB Provider Object in the ATL Object Wizard
There is only one tab in the OLE DB Provider settings wizard dialog. As shown in Figure 6-5, enter URLProvider as the name of the implementation class; the other boxes will fill in automatically. Accept the default values and press OK to add the OLE DB Provider Component object to your ATL project. Save the project to protect your work. Figure 6-5 Entering name information in the ATL Object Wizard dialog
Chapter Six—Creating and Using Simple OLE DB Providers and Consumers n 329
Figure 6-6 Adding a new C++ header file to the project
Finally, before we start examining and adding code, we need to provide for the addition of a new header file to the project to store a custom implementation we are going to write. To do this, select Project|Add To Project|New from the menus. In the dialog box shown in Figure 6-6, select the Files tab, then a C++ header file and give it a name of urlrowloc.h. Click on OK to add the blank file to the project in the proper location. Save the project to protect your work.
What the Wizard Wrought Since this is the first time we’ve created an OLE DB project in ATL, let’s spend some time reviewing what the wizard created for us before we begin changing it. This will serve as a reference for later projects in the remaining chapters in case you’re not sure what a given bit of code does. The ATL OLE DB Object Wizard creates five source code files (plus some support files we won’t go into) with automatically generated code to provide basic OLE DB provider functionality. Listing 6-1 gives the source code for the URLPROV.CPP file, which is the core implementation of the COM DLL itself. 1. 2. 3. 4. 5. 6. 7.
// URLProv.cpp : Implementation of DLL Exports.
// Note: Proxy/Stub Information // To build a separate proxy/stub DLL, // run nmake -f URLProvps.mk in the project directory.
330 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54.
n Chapter Six—Creating and Using Simple OLE DB Providers and Consumers #include #include #include #include
“stdafx.h” “resource.h” “URLProv.h”
#include “URLProv_i.c” #include “URLProviderSess.h” #include “URLProviderDS.h”
CComModule _Module; BEGIN_OBJECT_MAP(ObjectMap) OBJECT_ENTRY(CLSID_URLProvider, CURLProviderSource) END_OBJECT_MAP() ///////////////////////////////////////////////////////////////////////////// // DLL Entry Point extern “C” BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID/*lpReserved*/) { if (dwReason == DLL_PROCESS_ATTACH) { _Module.Init(ObjectMap, hInstance, &LIBID_URLPROVLib); DisableThreadLibraryCalls(hInstance); } else if (dwReason == DLL_PROCESS_DETACH) _Module.Term(); return TRUE; // ok } ///////////////////////////////////////////////////////////////////////////// // Used to determine whether the DLL can be unloaded by OLE STDAPI DllCanUnloadNow(void) { return (_Module.GetLockCount()==0) ? S_OK : S_FALSE; } ///////////////////////////////////////////////////////////////////////////// // Returns a class factory to create an object of the requested type STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv) { return _Module.GetClassObject(rclsid, riid, ppv); }
Chapter Six—Creating and Using Simple OLE DB Providers and Consumers n 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73.
331
///////////////////////////////////////////////////////////////////////////// // DllRegisterServer - Adds entries to the system registry STDAPI DllRegisterServer(void) { // registers object, typelib and all interfaces in typelib return _Module.RegisterServer(TRUE); } ///////////////////////////////////////////////////////////////////////////// // DllUnregisterServer - Removes entries from the system registry STDAPI DllUnregisterServer(void) { return _Module.UnregisterServer(TRUE); }
Listing 6-1
URLPROV.CPP source code before modifications
The list below gives the important code blocks of the listing and explains what they do; particular attention is paid to macros and template definitions as appropriate: n
Lines 14-15 This code contains the #include directives for the header files implementing the additional functionality of the provider. OLE DB developers who add additional functionality in new files must be sure to include the header files they create here.
n
Lines 20-22 This code contains the vital OBJECT_MAP macros. These macros are essential for the COM aspects of the provider to work; they determine what COM interfaces the server will support. OLE DB developers who add new interfaces (a common task for advanced providers) must carefully modify these macros to add additional OBJECT_ENTRY elements.
n
Lines 24-38 This code is the entry point for the DLL application; it also handles initialization for the server as well as shutdown via calls to the _Module implementation object. Notice that it calls the DisableThreadLibraryCalls function; this is an optimization feature which supports a high level of thread usage by the DLL. In some cases, OLE DB developers may need to add additional initialization code to this function.
n
Lines 40-46 This code handles determining whether the DLL can be removed from memory by COM because it has no outstanding interface pointers (see Chapter 1 for an explanation of COM reference counting and memory management), via a call to the _Module implementation object.
332
n Chapter Six—Creating and Using Simple OLE DB Providers and Consumers This is a COM requirement and won’t be changed by OLE DB development. n
Lines 48-54 This code handles creating an instance of the underlying object for the COM interface supported by the DLL, via the _Module implementation object. This is a COM requirement and won’t be changed by OLE DB development.
n
Lines 56-63 This code handles registering the server (adding its entries to the Registry for use by COM) when requested, via the call to the _Module implementation object. This is a COM requirement and won’t be changed by OLE DB development.
n
Lines 65-73 This code handles unregistering the server (removing its entries from the Registry) when requested via the call to the _Module implementation object. This is a COM requirement and won’t be changed by OLE DB development.
Listing 6-2 gives the source code for the URLPROVIDERRS.CPP file, which is the implementation of the rowset functionality of the provider. (Remember from Chapter 4 that all OLE DB data is returned in the form of rowsets.) Note that unlike the DataSource implementation, the rowset implementation has a separate header file with its definitions. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13.
// Implementation of the CURLProviderCommand #include “stdafx.h” #include “URLProv.h” #include “URLProviderRS.h” ///////////////////////////////////////////////////////////////////////////// // CURLProviderCommand HRESULT CURLProviderCommand::Execute(IUnknown * pUnkOuter, REFIID riid, DBPARAMS * pParams, LONG * pcRowsAffected, IUnknown ** ppRowset) { CURLProviderRowset* pRowset; return CreateRowset(pUnkOuter, riid, pParams, pcRowsAffected,ppRowset,pRowset); }
Listing 6-2
URLPROVIDERRS.CPP source code before modifications
The list below gives the important code blocks of the listing and explains what they do; particular attention is paid to macros and template definitions as appropriate: n
Lines 5-13 This code is the default implementation of the IRowset::Execute function. It calls an internal implementation which returns a default rowset defined in the header file described below. Notice that it moves its data as parameters and returns an HRESULT return code (see Chapter 1 for COM HRESULT information). OLE DB developers who
Chapter Six—Creating and Using Simple OLE DB Providers and Consumers n
333
want to change what rowsets are returned don’t modify this code but rather the implementation in the header file. Listing 6-3 gives the source code for the URLPROVIDERDS.H file, which is the definition of the data source functionality of the provider. (Remember from Chapter 4 that the data source implementation is where all clients connect to the OLE DB provider.) Note that it does not have a CPP file and so contains both its definitions and implementations. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39.
// URLProviderDS.h : Declaration of the CURLProviderSource #ifndef __CURLProviderSource_H_ #define __CURLProviderSource_H_ #include “resource.h” // main symbols #include “URLProviderRS.h” ///////////////////////////////////////////////////////////////////////////// // CDataSource class ATL_NO_VTABLE CURLProviderSource : public CComObjectRootEx, public CComCoClass, public IDBCreateSessionImpl, public IDBInitializeImpl, public IDBPropertiesImpl, public IPersistImpl, public IInternalConnectionImpl { public: HRESULT FinalConstruct() { return FInit(); } DECLARE_REGISTRY_RESOURCEID(IDR_URLPROVIDER) BEGIN_PROPSET_MAP(CURLProviderSource) BEGIN_PROPERTY_SET(DBPROPSET_DATASOURCEINFO) PROPERTY_INFO_ENTRY(ACTIVESESSIONS) PROPERTY_INFO_ENTRY(DATASOURCEREADONLY) PROPERTY_INFO_ENTRY(BYREFACCESSORS) PROPERTY_INFO_ENTRY(OUTPUTPARAMETERAVAILABILITY) PROPERTY_INFO_ENTRY(PROVIDEROLE DBVER) PROPERTY_INFO_ENTRY(DSOTHREADMODEL) PROPERTY_INFO_ENTRY(SUPPORTEDTXNISOLEVELS) PROPERTY_INFO_ENTRY(USERNAME) END_PROPERTY_SET(DBPROPSET_DATASOURCEINFO) BEGIN_PROPERTY_SET(DBPROPSET_DBINIT) PROPERTY_INFO_ENTRY(AUTH_PASSWORD) PROPERTY_INFO_ENTRY(AUTH_PERSIST_SENSITIVE_AUTHINFO) PROPERTY_INFO_ENTRY(AUTH_USERID) PROPERTY_INFO_ENTRY(INIT_DATASOURCE) PROPERTY_INFO_ENTRY(INIT_HWND)
334 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58.
n Chapter Six—Creating and Using Simple OLE DB Providers and Consumers PROPERTY_INFO_ENTRY(INIT_LCID) PROPERTY_INFO_ENTRY(INIT_LOCATION) PROPERTY_INFO_ENTRY(INIT_MODE) PROPERTY_INFO_ENTRY(INIT_PROMPT) PROPERTY_INFO_ENTRY(INIT_PROVIDERSTRING) PROPERTY_INFO_ENTRY(INIT_TIMEOUT) END_PROPERTY_SET(DBPROPSET_DBINIT) CHAIN_PROPERTY_SET(CURLProviderCommand) END_PROPSET_MAP() BEGIN_COM_MAP(CURLProviderSource) COM_INTERFACE_ENTRY(IDBCreateSession) COM_INTERFACE_ENTRY(IDBInitialize) COM_INTERFACE_ENTRY(IDBProperties) COM_INTERFACE_ENTRY(IPersist) COM_INTERFACE_ENTRY(IInternalConnection) END_COM_MAP() public: }; #endif //__CURLProviderSource_H_
Listing 6-3
URLPROVIDERDS.H source code before modifications
The list below gives the important code blocks of the listing and explains what they do; particular attention is paid to macros and template definitions as appropriate: n
Lines 6-15 This code contains the template inheritance hierarchy for the data source implementation. If an OLE DB developer wants to add functionality to the default implementation, he must alter these lines to inherit from replacement and/or additional template classes, either supplied with OLE DB or custom ones he created or obtained from third-party sources.
n
Lines 23-48 This code contains the vital PROPSET_MAP, PROPERTY_ SET, and PROPERTY_INFO_ENTRY macros for the provider’s data source implementation. These macros define the OLE DB properties supported by the provider’s DataSource object. While most of these are automatically supported by the templates, OLE DB developers may want to remove or add property sets and individual entries via adding or removing the macros in this section. (It is important to remember that each OLE DB implementation object has its own set of supported OLE DB properties.)
n
Lines 49-55 This code contains the other critical code for the COM interfaces supported by the data source implementation via COM_MAP and COM_INTERFACE_ENTRY macros. If OLE DB developers want to enhance the data source functionality of the provider, they must be sure to add appropriate interface entries to these macros.
Chapter Six—Creating and Using Simple OLE DB Providers and Consumers n
335
Listing 6-4 gives the source code for the URLPROVIDERRS.H file, which is the definition file for the recordSet functionality of the provider. Note that it has an associated CPP file and so does not contain implementation code. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44.
// URLProviderRS.h : Declaration of the CURLProviderRowset #ifndef __CURLProviderRowset_H_ #define __CURLProviderRowset_H_ #include “resource.h” // main symbols class CURLProviderWindowsFile: public WIN32_FIND_DATA { public: BEGIN_PROVIDER_COLUMN_MAP(CURLProviderWindowsFile) PROVIDER_COLUMN_ENTRY(“FileAttributes”, 1, dwFileAttributes) PROVIDER_COLUMN_ENTRY(“FileSizeHigh”, 2, nFileSizeHigh) PROVIDER_COLUMN_ENTRY(“FileSizeLow”, 3, nFileSizeLow) PROVIDER_COLUMN_ENTRY(“FileName”, 4, cFileName) PROVIDER_COLUMN_ENTRY(“AltFileName”, 5, cAlternateFileName) END_PROVIDER_COLUMN_MAP() }; // CURLProviderCommand class ATL_NO_VTABLE CURLProviderCommand : public CComObjectRootEx, public IAccessorImpl, public ICommandTextImpl, public ICommandPropertiesImpl, public IObjectWithSiteImpl, public IConvertTypeImpl, public IColumnsInfoImpl { public: BEGIN_COM_MAP(CURLProviderCommand) COM_INTERFACE_ENTRY(ICommand) COM_INTERFACE_ENTRY(IObjectWithSite) COM_INTERFACE_ENTRY(IAccessor) COM_INTERFACE_ENTRY(ICommandProperties) COM_INTERFACE_ENTRY2(ICommandText, ICommand) COM_INTERFACE_ENTRY(IColumnsInfo) COM_INTERFACE_ENTRY(IConvertType) END_COM_MAP() // ICommand public: HRESULT FinalConstruct() { HRESULT hr = CConvertHelper::FinalConstruct(); if (FAILED (hr)) return hr; hr = IAccessorImpl::FinalConstruct();
336 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91.
n Chapter Six—Creating and Using Simple OLE DB Providers and Consumers if (FAILED(hr)) return hr; return CUtlProps::FInit(); } void FinalRelease() { IAccessorImpl::FinalRelease(); } HRESULT WINAPI Execute(IUnknown * pUnkOuter, REFIID riid, DBPARAMS * pParams, LONG * pcRowsAffected, IUnknown ** ppRowset); static ATLCOLUMNINFO* GetColumnInfo(CURLProviderCommand* pv, ULONG* pcInfo) { return CURLProviderWindowsFile::GetColumnInfo(pv,pcInfo); } BEGIN_PROPSET_MAP(CURLProviderCommand) BEGIN_PROPERTY_SET(DBPROPSET_ROWSET) PROPERTY_INFO_ENTRY(IAccessor) PROPERTY_INFO_ENTRY(IColumnsInfo) PROPERTY_INFO_ENTRY(IConvertType) PROPERTY_INFO_ENTRY(IRowset) PROPERTY_INFO_ENTRY(IRowsetIdentity) PROPERTY_INFO_ENTRY(IRowsetInfo) PROPERTY_INFO_ENTRY(IRowsetLocate) PROPERTY_INFO_ENTRY(BOOKMARKS) PROPERTY_INFO_ENTRY(BOOKMARKSKIPPED) PROPERTY_INFO_ENTRY(BOOKMARKTYPE) PROPERTY_INFO_ENTRY(CANFETCHBACKWARDS) PROPERTY_INFO_ENTRY(CANHOLDROWS) PROPERTY_INFO_ENTRY(CANSCROLLBACKWARDS) PROPERTY_INFO_ENTRY(LITERALBOOKMARKS) PROPERTY_INFO_ENTRY(ORDEREDBOOKMARKS) END_PROPERTY_SET(DBPROPSET_ROWSET) END_PROPSET_MAP() }; class CURLProviderRowset : public CRowsetImpl< CURLProviderRowset, CURLProviderWindowsFile, CURLProviderCommand> { public: HRESULT Execute(DBPARAMS * pParams, LONG* pcRowsAffected) { USES_CONVERSION; BOOL bFound = FALSE; HANDLE hFile; LPTSTR szDir = (m_strCommandText == _T(“”)) ? _T(“*.*”) : OLE2T(m_strCommandText); CURLProviderWindowsFile wf; hFile = FindFirstFile(szDir, &wf);
Chapter Six—Creating and Using Simple OLE DB Providers and Consumers n
337
92. if (hFile == INVALID_HANDLE_VALUE) 93. return DB_E_ERRORSINCOMMAND; 94. LONG cFiles = 1; 95. BOOL bMoreFiles = TRUE; 96. while (bMoreFiles) 97. { 98. if (!m_rgRowData.Add(wf)) 99. return E_OUTOFMEMORY; 100. bMoreFiles = FindNextFile(hFile, &wf); 101. cFiles++; 102. } 103. FindClose(hFile); 104. if (pcRowsAffected != NULL) 105. *pcRowsAffected = cFiles; 106. return S_OK; 107. } 108. }; 109. #endif //__CURLProviderRowset_H_ Listing 6-4
URLPROVIDERRS.H source code before modifications
The list below gives the important code blocks of the listing and explains what they do; particular attention is paid to macros and template definitions as appropriate: n
Lines 5-16 This code provides the definition of the data storage and retrieval component of the implementation, contained in the CURLProviderWindowsFile class. The PROVIDER_COLUMN_MAP and PROVIDER_COLUMN_ENTRY macros here must be changed by OLE DB developers to accurately represent the structure of their underlying data.
n
Lines 17-27 This code defines the template inheritance tree for the implementation of the Command object for the provider. OLE DB developers who want to change the functionality of the Command object must add or delete entries here.
n
Lines 28-36 This code contains the COM_MAP and COM_INTERFACE_ ENTRY macros that define which interfaces are supported for the Command object in the provider. Developers who want to add or remove supported interfaces must add or delete entries here.
n
Lines 37-58 This code provides default implementations for the supported Command object functionality. OLE DB developers who want to override or enhance this functionality must replace this code or alter it as needed.
n
Lines 59-78 This code contains the OLE DB property macros for supported properties of the Command object. OLE DB developers who want to alter the properties of the Command object must add or delete appropriate macros in this section.
338
n Chapter Six—Creating and Using Simple OLE DB Providers and Consumers n
Lines 79-108 This code contains a default implementation of rowset generation when the Execute method is called. It. provides a directory listing as a rowset, which matches the column macro entries earlier in the file. OLE DB developers must override this code to provide their own Execute implementation.
Listing 6-5 gives the source code for the URLPROVIDERSESS.H file, which is the definition of the session functionality of the provider. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38.
// Session.h : Declaration of the CURLProviderSession #ifndef __CURLProviderSession_H_ #define __CURLProviderSession_H_ #include “resource.h” // main symbols #include “URLProviderRS.h” class CURLProviderSessionTRSchemaRowset; class CURLProviderSessionColSchemaRowset; class CURLProviderSessionPTSchemaRowset; ///////////////////////////////////////////////////////////////////////////// // CURLProviderSession class ATL_NO_VTABLE CURLProviderSession : public CComObjectRootEx, public IGetDataSourceImpl, public IOpenRowsetImpl, public ISessionPropertiesImpl, public IObjectWithSiteSessionImpl, public IDBSchemaRowsetImpl, public IDBCreateCommandImpl { public: CURLProviderSession() { } HRESULT FinalConstruct() { return FInit(); } STDMETHOD(OpenRowset)(IUnknown *pUnk, DBID *pTID, DBID *pInID, REFIID riid, ULONG cSets, DBPROPSET rgSets[], IUnknown **ppRowset) { CURLProviderRowset* pRowset; return CreateRowset(pUnk, pTID, pInID, riid, cSets, rgSets, ppRowset, pRowset); } BEGIN_PROPSET_MAP(CURLProviderSession) BEGIN_PROPERTY_SET(DBPROPSET_SESSION) PROPERTY_INFO_ENTRY(SESS_AUTOCOMMITISOLEVELS) END_PROPERTY_SET(DBPROPSET_SESSION)
Chapter Six—Creating and Using Simple OLE DB Providers and Consumers n 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85.
END_PROPSET_MAP() BEGIN_COM_MAP(CURLProviderSession) COM_INTERFACE_ENTRY(IGetDataSource) COM_INTERFACE_ENTRY(IOpenRowset) COM_INTERFACE_ENTRY(ISessionProperties) COM_INTERFACE_ENTRY(IObjectWithSite) COM_INTERFACE_ENTRY(IDBCreateCommand) COM_INTERFACE_ENTRY(IDBSchemaRowset) END_COM_MAP() BEGIN_SCHEMA_MAP(CURLProviderSession) SCHEMA_ENTRY(DBSCHEMA_TABLES, CURLProviderSessionTRSchemaRowset) SCHEMA_ENTRY(DBSCHEMA_COLUMNS, CURLProviderSessionColSchema-Rowset) SCHEMA_ENTRY(DBSCHEMA_PROVIDER_TYPES, CURLProviderSessionPT-SchemaRowset) END_SCHEMA_MAP() }; class CURLProviderSessionTRSchemaRowset : public CRowsetImpl< CURLProviderSessionTRSchemaRowset, CTABLESRow, CURLProviderSession> { public: HRESULT Execute(LONG* pcRowsAffected, ULONG, const VARIANT*) { USES_CONVERSION; CURLProviderWindowsFile wf; CTABLESRow trData; lstrcpyW(trData.m_szType, OLESTR(“TABLE”)); lstrcpyW(trData.m_szDesc, OLESTR(“The Directory Table”)); HANDLE hFile = INVALID_HANDLE_VALUE; TCHAR szDir[MAX_PATH + 1]; DWORD cbCurDir = GetCurrentDirectory(MAX_PATH, szDir); lstrcat(szDir, _T(“\\*.*”)); hFile = FindFirstFile(szDir, &wf); if (hFile == INVALID_HANDLE_VALUE) return E_FAIL; // User doesn’t have a c:\ drive FindClose(hFile); lstrcpynW(trData.m_szTable, T2OLE(szDir), SIZEOF_MEMBER(CTABLESRow, m_szTable)); if (!m_rgRowData.Add(trData)) return E_OUTOFMEMORY; *pcRowsAffected = 1; return S_OK; } }; class CURLProviderSessionColSchemaRowset : public CRowsetImpl< CURLProviderSessionColSchemaRowset, CCOLUMNSRow, CURLProviderSession> {
339
340 86. 87. 88. 89. 90. 91. 92. 93. 94. 95. 96. 97. 98. 99. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117.
n Chapter Six—Creating and Using Simple OLE DB Providers and Consumers public: HRESULT Execute(LONG* pcRowsAffected, ULONG, const VARIANT*) { USES_CONVERSION; CURLProviderWindowsFile wf; HANDLE hFile = INVALID_HANDLE_VALUE; TCHAR szDir[MAX_PATH + 1]; DWORD cbCurDir = GetCurrentDirectory(MAX_PATH, szDir); lstrcat(szDir, _T(“\\*.*”)); hFile = FindFirstFile(szDir, &wf); if (hFile == INVALID_HANDLE_VALUE) return E_FAIL; // User doesn’t have a c:\ drive FindClose(hFile);// szDir has got the tablename DBID dbid; memset(&dbid, 0, sizeof(DBID)); dbid.uName.pwszName = T2OLE(szDir); dbid.eKind = DBKIND_NAME; return InitFromRowset < _RowsetArrayType > (m_rgRowData, &dbid, NULL, m_spUnkSite, pcRowsAffected); } }; class CURLProviderSessionPTSchemaRowset : public CRowsetImpl< CURLProviderSessionPTSchemaRowset, CPROVIDER_TYPERow, CURLProviderSession> { public: HRESULT Execute(LONG* pcRowsAffected, ULONG, const VARIANT*) { return S_OK; } }; #endif //__CURLProviderSession_H_
Listing 6-5
URLPROVIDERSESS.H source code before modifications
The list below gives the important code blocks of the listing and explains what they do; particular attention is paid to macros and template definitions as appropriate: n
Lines 9-18 This code defines the template inheritance tree for the implementation of the Session object for the provider. OLE DB developers who want to change the functionality of the Session object must add or delete entries here.
n
Lines 20-34 This code provides default implementations for the supported Session object functionality. OLE DB developers who want to override or enhance this functionality must replace this code or alter it as needed.
Chapter Six—Creating and Using Simple OLE DB Providers and Consumers n
341
n
Lines 35-39 This code contains the OLE DB property macros for supported properties of the Session object. OLE DB developers who want to alter the properties of the Session object must add or delete appropriate macros in this section.
n
Lines 40-47 This code contains the COM_MAP and COM_ INTERFACE_ENTRY macros that define which interfaces are supported for the Session object in the provider. Developers who want to add or remove supported interfaces must add or delete entries here.
n
Lines 48-52 This code contains the SCHEMA_MAP and SCHEMA_ENTRY macros that define which schema implementation objects are supported for the Session object in the provider. Developers who want to add or remove supported schemas must add or delete entries here.
n
Lines 54-81 This code contains a default implementation of rowset generation when the Execute method is called for a table schema. It provides a directory listing as a rowset, which matches the column macro entries earlier in the file. OLE DB developers must override this code to provide their own Execute implementation for table schemas if they want to use them.
n
Lines 82-106 This code contains a default implementation of rowset generation when the Execute method is called for a column schema. It provides a directory listing as a rowset, which matches the column macro entries earlier in the file. OLE DB developers must override this code to provide their own Execute implementation for column schemas if they want to use them.
n
Lines 107-116 This code contains a default implementation of rowset generation when the Execute method is called for a provider-implemented schema. It provides no implementation, only a success return code. OLE DB developers must override this code to provide their own Execute implementation for provider-implemented schemas if they want to use them.
Gentlemen, Start Your Keyboards! Now that you’ve got some familiarity with the wizard-generated code for a basic OLE DB provider, it is time to change it to meet the needs of our specific provider. The sections below give you before and after code samples, followed by explanations of what code to change and what the changes do. When you need to create your own OLE DB provider, you can use these guidelines as a template for your own specific changes.
342
n Chapter Six—Creating and Using Simple OLE DB Providers and Consumers
Returning a Custom Rowset Listing 6-6 gives the source code from the URLPROVIDERRS.H file that implements the Execute functionality for rowsets. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30.
class CURLProviderRowset : public CRowsetImpl< CURLProviderRowset, CURLProviderWindowsFile, CURLProviderCommand> { public: HRESULT Execute(DBPARAMS * pParams, LONG* pcRowsAffected) { USES_CONVERSION; BOOL bFound = FALSE; HANDLE hFile; LPTSTR szDir = (m_strCommandText == _T(“”)) ? _T(“*.*”) : OLE2T(m_strCommandText); CURLProviderWindowsFile wf; hFile = FindFirstFile(szDir, &wf); if (hFile == INVALID_HANDLE_VALUE) return DB_E_ERRORSINCOMMAND; LONG cFiles = 1; BOOL bMoreFiles = TRUE; while (bMoreFiles) { if (!m_rgRowData.Add(wf)) return E_OUTOFMEMORY; bMoreFiles = FindNextFile(hFile, &wf); cFiles++; } FindClose(hFile); if (pcRowsAffected != NULL) *pcRowsAffected = cFiles; return S_OK; } };
Listing 6-6
URLPROVIDERRS.H source code before modifications
The directions below tell you what to remove and what to add to modify this code to meet our needs: n
Lines 7-25 Listing 6-7.
Delete these lines. Replace them with the grayed code in
Listing 6-7 gives the source code from the URLPROVIDERRS.H file that implements your changes.
Chapter Six—Creating and Using Simple OLE DB Providers and Consumers n 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47.
class CURLProviderRowset : public CRowsetImpl< CURLProviderRowset, CURLProviderWindowsFile, CURLProviderCommand> { public: HRESULT Execute(DBPARAMS * pParams, LONG* pcRowsAffected) { //@@@ macros @@@ USES_CONVERSION; //@@@ file handle @@@ FILE* pFile; //@@@ string holder @@@ TCHAR szString[256]; //@@@ filename holder @@@ TCHAR szFile[MAX_PATH]; //@@@ length holder @@@ size_t nLength; //@@@ if there is no command text @@@ if (m_strCommandText == (BSTR)NULL) { //@@@ signal error @@@ return E_FAIL; } //@@@ put the filename and path into the holder @@@ _tcscpy(szFile, OLE2T(m_strCommandText)); //@@@ if bad filename or can’t open file @@@ if (szFile[0] == _T(‘\0’) || ((pFile = fopen(&szFile[0], “r”)) == NULL)) { //@@@ signal error per OLE DB spec @@@ return DB_E_NOTABLE; } //@@@ number of records holder @@@ LONG cFiles = 0; //@@@ get first string of pair until run out @@@ while (fgets(szString, 256, pFile) != NULL) { //@@@ get the length of the input string @@@ nLength = strlen(szString); //@@@ make it null terminated @@@ szString[nLength-1] = ‘\0’; //@@@ define record holder @@@ CURLProviderWindowsFile ur; //@@@ put this into string @@@ _tcscpy(ur.szCommand, szString ); //@@@ put it into other string @@@ _tcscpy(ur.szCommand2, szString ); //@@@ get second string if present @@@ if (fgets(szString,256,pFile) != NULL)
343
344 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72.
n Chapter Six—Creating and Using Simple OLE DB Providers and Consumers { //@@@ get length of input string @@@ nLength = strlen(szString); //@@@ make it null terminated @@@ szString[nLength-1] = ‘\0’; //@@@ put into other string holder @@@ _tcscpy(ur.szText, szString ); //@@@ and second string holder @@@ _tcscpy(ur.szText2, szString ); } //@@@ update the bookmark @@@ ur.dwBookmark = ++cFiles; //@@@ try to put it into the recordset @@@ if (!m_rgRowData.Add(ur)) { //@@@ otherwise abort with error @@@ return E_FAIL: } } //@@@ send back record count and return good HRESULT @@@ if (pcRowsAffected !=NULL) *pcRowsAffected = cFiles: return S_OK: } }; #endif //__CURLProviderRowset_H_
Listing 6-7
URLPROVIDERRS.H source code after modifications
The following entries explain what the new code lines do: n
Lines 7-26 This code uses the m_strCommandText member variable to find out whether a filename has been supplied as the “command” to the provider. If so, it puts it into a holder and opens the specified text file for reading.
n
Lines 27-30 If the provider is not able to locate or open the file, rather than returning E_FAIL, the routine returns DB_E_NOTABLE, to maintain compliance with the OLE DB spec.
n
Lines 31-34 This code begins the process of reading in strings from the opened file; it is designed to loop until there are no more strings to be read.
n
Lines 35-47 This code creates a local copy of a CURLProviderWindowsFile user record. (It will be defined in the next section from its OLE DB wizard default.) It then places the string just obtained into two different members of the user record.
Chapter Six—Creating and Using Simple OLE DB Providers and Consumers n
345
n
Lines 48-57 This code just repeats the process of getting a string and putting it into the already-created user record. Note that it does not break the loop if it fails; rather the loop ends with the next read attempt.
n
Lines 58-59 This code updates the counter for number of records read. It will be used later for implementing bookmarks for the provider (thus its name).
n
Lines 60-65 This code uses the member variable m_rgRowData (a CSimpleArray object) and its Add method to put the user record into internal storage. Note that it returns E_FAIL if this bombs (due to memory lack, usually).
n
Lines 68-69 This code sets the number of string pairs read into the imported parameter pcRowsAffected to maintain compliance with the OLE DB spec.
n
Line 70 This code returns the HRESULT S_OK to indicate a valid rowset was obtained.
Defining a Custom User Record Next, we need to alter the user record to contain our custom data fields and also support for dynamic column returns to accommodate bookmarks. Listing 6-8 gives the source code from the URLPROVIDERRS.H file that implements the CURLProviderWindowsFile user record. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13.
#include “resource.h” // main symbols class CURLProviderWindowsFile: public WIN32_FIND_DATA { public: BEGIN_PROVIDER_COLUMN_MAP(CURLProviderWindowsFile) PROVIDER_COLUMN_ENTRY(“FileAttributes”, 1, dwFileAttributes) PROVIDER_COLUMN_ENTRY(“FileSizeHigh”, 2, nFileSizeHigh) PROVIDER_COLUMN_ENTRY(“FileSizeLow”, 3, nFileSizeLow) PROVIDER_COLUMN_ENTRY(“FileName”, 4, cFileName) PROVIDER_COLUMN_ENTRY(“AltFileName”, 5, cAlternateFileName) END_PROVIDER_COLUMN_MAP() };
Listing 6-8
URLPROVIDERRS.H source code before modifications
The directions below tell you what to remove and what to add to modify this code to meet our needs: n
Lines 1-2 here.
n
Lines 3-13 Delete these lines and add lines 12 through 58 from Listing 6-9 in place of them.
Insert lines 2 through 10 of Listing 6-9 between lines 1 and 2
346
n Chapter Six—Creating and Using Simple OLE DB Providers and Consumers Listing 6-9 gives the source code from the URLPROVIDERRS.H file that implements the CURLProviderWindowsFile user record after your changes.
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44.
#include “resource.h” //main symbols #include <stdio.h> //@@@ ole chars library @@@ #include //@@@ bring in custom bookmark implementation @@@ #include “urlrowloc.h” //@@@ forward definitions for compiler @@@ class CURLProviderRowset; class CURLProviderCommand; //@@@ this was already here @@@ class CURLProviderWindowsFile { public: //@@@ hold the bookmark @@@ DWORD dwBookmark; //@@@ command string @@@ TCHAR szCommand[256]; //@@@ text string @@@ TCHAR szText[256]; //@@@ command string 2 @@@ TCHAR szCommand2[256]; //@@@ text 2 @@@ TCHAR szText2[256]; //@@@ override getcolumninfo for rowsets @@@ static ATLCOLUMNINFO* GetColumnInfo(CURLProviderRowset* pThis, ULONG*pcCols); //@@@ override getcolumninfo for commands @@@ static ATLCOLUMNINFO* GetColumnInfo(CURLProviderCommand* pThis, ULONG*pcCols); }; //@@@ define a new macro to allow us to create a COLUMN entry on the fly @@@ #define ADD_COLUMN_ENTRY(ulCols, name, ordinal, colSize, type, precision, scale, guid, \ dataClass, member) \ _rgColumns[ulCols].pwszName = (LPOLESTR)name; \ _rgColumns[ulCols].pTypeInfo = (ITypeInfo*)NULL; \ _rgColumns[ulCols].iOrdinal = (ULONG)ordinal; \ _rgColumns[ulCols].dwFlags = 0; \ _rgColumns[ulCols].ulColumnSize = (ULONG)colSize; \ _rgColumns[ulCols].wType = (DBTYPE)type; \ _rgColumns[ulCols].bPrecision = (BYTE)precision; \ _rgColumns[ulCols].bScale = (BYTE)scale; \ _rgColumns[ulCols].cbOffset = offsetof(dataClass, member); \ memset(&(_rgColumns[ulCols].columnid), 0, sizeof(DBID));\ _rgColumns[ulCols].columnid.uName.pwszName =(LPOLESTR)name;
Chapter Six—Creating and Using Simple OLE DB Providers and Consumers n 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58.
347
//@@@ define a new macro to let us create another COLUMN entry on the fly @@@ #define ADD_COLUMN_ENTRY_EX(ulCols, name, ordinal, colSize, type, precision, scale, \ guid, dataClass, member, flags) \ _rgColumns[ulCols].pwszName = (LPOLESTR)name; \ _rgColumns[ulCols].pTypeInfo = (ITypeInfo*)NULL; \ _rgColumns[ulCols].iOrdinal = (ULONG)ordinal; \ _rgColumns[ulCols].dwFlags = flags; \ _rgColumns[ulCols].ulColumnSize = (ULONG)colSize; \ _rgColumns[ulCols].wType = (DBTYPE)type; \ _rgColumns[ulCols].bPrecision = (BYTE)precision; \ _rgColumns[ulCols].bScale = (BYTE)scale; \ _rgColumns[ulCols].cbOffset = offsetof(dataClass, member); \ memset(&(_rgColumns[ulCols].columnid), 0, sizeof(DBID)); \ _rgColumns[ulCols].columnid.uName.pwszName = (LPOLESTR)name;
Listing 6-9
URLPROVIDERRS.H source code after modifications
The following entries explain what the new code lines do: n
Lines 2-9 This code brings in the standard io library, the ole chars library, and an IRowsetLocator header file we’ll create shortly. It also provides essential forward definitions for our two custom classes that are used in the template (the compiler will not accept them without these definitions!).
n
Lines 13-21 This code defines the variables for the user record which will store data for returned rowsets. Notice that previously there weren’t any such definitions.
n
Lines 23-26 This code overrides the GetColumnInfo functions for both rowsets and commands to allow dynamic column information to be returned when bookmarks are used.
n
Lines 30-58 This code demonstrates a grungy aspect of OLE DB coding at present; these are reproductions of the basic macros used to define column entries. By defining them here, they can be used later inside other code.
Redefining the Rowset Implementation Template to Support Bookmarks Next, we need to alter the code by inserting some template code for later use by our implementation of the IRowsetLocate interface. Listing 6-10 gives the source code from the URLPROVIDERRS.H file where the insertion will be made.
348 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23.
n Creating and Using Simple OLE DB Providers and Consumers BEGIN_PROPSET_MAP(CURLProviderCommand) BEGIN_PROPERTY_SET(DBPROPSET_ROWSET) PROPERTY_INFO_ENTRY(IAccessor) PROPERTY_INFO_ENTRY(IColumnsInfo) PROPERTY_INFO_ENTRY(IConvertType) PROPERTY_INFO_ENTRY(IRowset) PROPERTY_INFO_ENTRY(IRowsetIdentity) PROPERTY_INFO_ENTRY(IRowsetInfo) PROPERTY_INFO_ENTRY(IRowsetLocate) PROPERTY_INFO_ENTRY(BOOKMARKS) PROPERTY_INFO_ENTRY(BOOKMARKSKIPPED) PROPERTY_INFO_ENTRY(BOOKMARKTYPE) PROPERTY_INFO_ENTRY(CANFETCHBACKWARDS) PROPERTY_INFO_ENTRY(CANHOLDROWS) PROPERTY_INFO_ENTRY(CANSCROLLBACKWARDS) PROPERTY_INFO_ENTRY(LITERALBOOKMARKS) PROPERTY_INFO_ENTRY(ORDEREDBOOKMARKS) END_PROPERTY_SET(DBPROPSET_ROWSET) END_PROPSET_MAP() }; class CURLProviderRowset : public CRowsetImpl< CURLProviderRowset, CURLProviderWindowsFile, CURLProviderCommand> {
Listing 6-10
URLPROVIDERRS.H source code before modifications
The directions below tell you what to remove and what to add to modify this code to meet our needs: n
Lines 21-23 Listing 6-11.
Replace these lines with the grayed code in lines 21-52 in
Listing 6-11 gives the source code from the URLPROVIDERRS.H file that implements the template class after your changes. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14.
BEGIN_PROPSET_MAP(CURLProviderCommand) BEGIN_PROPERTY_SET(DBPROPSET_ROWSET PROPERTY_INFO_ENTRY(IAccessor) PROPERTY_INFO_ENTRY(IColumnsInfo) PROPERTY_INFO_ENTRY(IConvertType) PROPERTY_INFO_ENTRY(IRowset) PROPERTY_INFO_ENTRY(IRowsetIdentity) PROPERTY_INFO_ENTRY(IRowsetInfo) PROPERTY_INFO_ENTRY(IRowsetLocate) PROPERTY_INFO_ENTRY(BOOKMARKS) PROPERTY_INFO_ENTRY(BOOKMARKSKIPPED) PROPERTY_INFO_ENTRY(BOOKMARKTYPE) PROPERTY_INFO_ENTRY(CANFETCHBACKWARDS) PROPERTY_INFO_ENTRY(CANHOLDROWS)
Chapter Six—Creating and Using Simple OLE DB Providers and Consumers n 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53.
349
PROPERTY_INFO_ENTRY(CANSCROLLBACKWARDS) PROPERTY_INFO_ENTRY(LITERALBOOKMARKS) PROPERTY_INFO_ENTRY(ORDEREDBOOKMARKS) END_PROPERTY_SET(DBPROPSET_ROWSET) END_PROPSET_MAP() }; //@@@ define our new template and implementation @@@ template > class CURLProviderRowsetImpl: public CRowsetImpl > { //@@@ COM MAP for our new class (note chaining) @@@ BEGIN_COM_MAP(CURLProviderRowsetImpl) COM_INTERFACE_ENTRY(IRowsetLocate) COM_INTERFACE_ENTRY_CHAIN(_RowsetBaseClass) END_COM_MAP() //@@@ Implementation of the class method @@@ HRESULT ValidateCommandID(DBID* pTableID, DBID* pIndexID) { //@@@ call ancestor method @@@ HRESULT hr = _RowsetBaseClass::ValidateCommandID(pTableID,pIndexID); if (hr != S_OK) return hr; //@@@ if bad HR return @@@ if (pIndexID != NULL) return DB_E_NOINDEX; //@@@ abort since we don’t do indexes @@@ return S_OK; //@@@ otherwise OK @@@ } }; //@@@ note change in base template class @@@ class CURLProviderRowset : public CURLProviderRowsetImpl< CURLProviderRowset, CURLProviderWindowsFile, CURLProviderCommand> { public:
Listing 6-11
URLPROVIDERRS.H source code after modifications
The following entries explain what the new code lines do: n
Lines 21-27 These lines provide the template definition and usage for the CURLProviderRowsetImpl class which will replace the standard CRowsetImpl provided with the OLE DB templates. We do this so we can
350
n Chapter Six—Creating and Using Simple OLE DB Providers and Consumers implement bookmarks; note the support for the IRowsetLocateImpl template which we will write shortly. n
Lines 28-32 These lines provide the COM functionality for the interfaces supported by our new implementation; note the use of the CHAIN macro to tie in to previous COM_MAP definitions to save rewriting them.
n
Lines 34-46 This code makes sure that we got a valid command ID using an ancestor class.
n
Lines 49-52 This code replaces the previous implementation of CURLProviderRowset with our new template class.
Implementing Bookmarks in the Custom Rowset Implementation Next, we need to alter the implementation code for the CURLProviderRowset class to add support for bookmarks. Listing 6-12 gives the source code from the URLPROVIDERRS.H file that implements the class. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31.
class CURLProviderRowset : public CRowsetImpl< CURLProviderRowset, CURLProviderWindowsFile, CURLProviderCommand> { public: HRESULT Execute(DBPARAMS * pParams, LONG* pcRowsAffected) { //@@@ macros @@@ USES_CONVERSION; //@@@ file handle @@@ FILE* pFile; //@@@ string holder @@@ TCHAR sString(256); //@@@ filename holder @@@ TCHAR szFile(MAX_PATH); //@@@ length holder @@@ size_t nLength; //@@@ if there is no command text @@@ if (!m_szCommandText) { //@@@ signal error @@@ return E_FAIL; } //@@@ put the filename and path into the holder @@@ _tcscpy(szFile, m_szCommandText); //@@@ if bad filename or can’t open file @@@ if (szFile[0] == _T(‘\0’) || ((pFile = fopen(&szFile[0], “r”)) == NULL)) { //@@@ signal error per OLE DB spec @@@ return DB_E_NOTABLE; } //@@@ number of records holder @@@
Chapter Six—Creating and Using Simple OLE DB Providers and Consumers n 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73.
351
LONG cFiles = 0; //@@@ get first string of pair until run out @@@ while (fgets(szString, 256, pFile) != NULL) { //@@@ get the length of the input string @@@ nLength = strlen(szString); //@@@ make it null terminated @@@ szString[nLength-1] = ‘\0’; //@@@ define record holder @@@ CURLProviderWindowsFile ur; //@@@ put this into string @@@ _tcscpy(ur.szCommand, szString ); //@@@ put it into other string @@@ _tcscpy(ur.szCommand2, szString ); //@@@ get second string if present @@@ if (fgets(szString,256,pFile) != NULL) { //@@@ get length of input string @@@ nLength = strlen(szString); //@@@ make it null terminated @@@ szString[nLength-1] = ‘\0’; //@@@ put into other string holder @@@ _tcscpy(ur.szText, szString ); //@@@ and second string holder @@@ _tcscpy(ur.szText2, szString ); } //@@@ update the bookmark @@@ ur.dwBookmark = ++cFiles; //@@@ try to put it into the recordset @@@ if (!m_rgRowData.Add(ur)) { //@@@ otherwise abort with error @@@ return E_FAIL; } } //@@@ send back record count and return good HRESULT @@@ if (pcRowsAffected != NULL) *pcRowsAffected = cFiles; return S_OK; } }; #endif //__CURLProviderRowset_H_
Listing 6-12
URLPROVIDERRS.H source code before modifications
The directions below tell you what to remove and what to add to modify this code to meet our needs:
352
n Chapter Six—Creating and Using Simple OLE DB Providers and Consumers n
Lines 4-5 Insert the grayed code in lines 5-11 of Listing 6-13 here. Don’t delete or change anything otherwise.
n
Lines 71-72 Insert the grayed code in lines 79-113 of Listing 6-13 here. Don’t delete or change anything otherwise.
Listing 6-13 gives the source code from the URLPROVIDERRS.H file that implements the CURLProviderRowset class after your changes. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40.
class CURLProviderRowset : public CRowsetImpl< CURLProviderRowset, CURLProviderWindowsFile, CURLProviderCommand> { public: //@@@ We must implement this because we’re using bookmarks @@@ virtual DBSTATUS GetDBStatus(CSimpleRow* , HACCESSOR) { //@@@ we don’t have a status for the database @@@ return DBSTATUS_S_ISNULL; } HRESULT Execute(DBPARAMS * pParams, LONG* pcRowsAffected) { //@@@ macros @@@ USES_CONVERSION; //@@@ file handle @@@ FILE* pFile; //@@@ string holder @@@ TCHAR sString(256); //@@@ filename holder @@@ TCHAR szFile(MAX_PATH); //@@@ length holder @@@ size_t nLength; //@@@ if there is no command text @@@ if (!m_szCommandText) { //@@@ signal error @@@ return E_FAIL; } //@@@ put the filename and path into the holder @@@ _tcscpy(szFile, m_szCommandText); //@@@ if bad filename or can’t open file @@@ if (szFile[0] == _T(‘\0’) || ((pFile = fopen(&szFile[0], “r”)) == NULL)) { //@@@ signal error per OLE DB spec @@@ return DB_E_NOTABLE; } //@@@ number of records holder @@@ LONG cFiles = 0; //@@@ get first string of pair until run out @@@
Chapter Six—Creating and Using Simple OLE DB Providers and Consumers n 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87.
while (fgets(szString, 256, pFile) != NULL) { //@@@ get the length of the input string @@@ nLength = strlen(szString); //@@@ make it null terminated @@@ szString[nLength-1] = ‘\0’; //@@@ define record holder @@@ CURLProviderWindowsFile ur; //@@@ put this into string @@@ _tcscpy(ur.szCommand, szString ); //@@@ put it into other string @@@ _tcscpy(ur.szCommand2, szString ); //@@@ get second string if present @@@ if (fgets(szString,256,pFile) != NULL) { //@@@ get length of input string @@@ nLength = strlen(szString); //@@@ make it null terminated @@@ szString[nLength-1] = ‘\0’; //@@@ put into other string holder @@@ _tcscpy(ur.szText, szString ); //@@@ and second string holder @@@ _tcscpy(ur.szText2, szString ); } //@@@ update the bookmark @@@ ur.dwBookmark = ++cFiles; //@@@ try to put it into the recordset @@@ if (!m_rgRowData.Add(ur)) { //@@@ otherwise abort with error @@@ return E_FAIL; } } //@@@ send back record count and return good HRESULT @@@ if (pcRowsAffected != NULL) *pcRowsAffected = cFiles; return S_OK; } //@@@ we have to implement this because we do bookmarks @@@ HRESULT OnPropertyChanged(ULONG iCurSet, DBPROP* pDBProp) { //@@@ abort if we have a null pointer @@@ ATLASSERT( pDBProp != NULL ); //@@@ get a working copy to play with @@@ DWORD dwPropertyID = pDBProp->dwPropertyID; //@@@ if we’ve changed anything relating to bookmarks handle it @@@ if (dwPropertyID == DBPROP_IRowsetLocate ||
353
354
n Chapter Six—Creating and Using Simple OLE DB Providers and Consumers
88. dwPropertyID == DBPROP_LITERALBOOKMARKS || 89. dwPropertyID == DBPROP_ORDEREDBOOKMARKS) 90. { //@@@ get the value sent in @@@ 91. CComVariant var = pDBProp->vValue; 92. //@@@ if we’re turning it on @@@ 93. if (var.boolVal == VARIANT_TRUE) 94. { 95. //@@@ Set the bookmarks property since they are chained @@@ 96. CComVariant bookVar(true); 97. CDBPropSet set(DBPROPSET_ROWSET); 98. set.AddProperty(DBPROP_BOOKMARKS, bookVar); 99. 100. //@@@ handle backward scrolling @@@ 101. if (dwPropertyID == DBPROP_IRowsetLocate) 102 set.AddProperty(DBPROP_CANSCROLLBACKWARDS, bookVar); 103. //@@@ GUID manipulation @@@ 104. const GUID* ppGuid[1]; 105. ppGuid[0] = &DBPROPSET_ROWSET; 106. ///@@@ try setting the properties and return result @@@ 107. return SetProperties(0, 1, &set, 1, ppGuid); 108. } 109. } 110. //@@@ otherwise just ignore it @@@ 111. return S_OK; 112. } 113. 114. }; 115. #endif //__CURLProviderRowset_H_ Listing 6-13
URLPROVIDERRS.H source code after modifications
The following entries explain what the new code lines do: n
Lines 6-10 This code implements the required function for database status when bookmarks are supported. Since our text file doesn’t have a status, we always return none.
n
Lines 80-112 This code is required because since we are supporting bookmarks, we have to implement bookmark property changes by clients. The code essentially uses internal implementations to handle this, and only accepts turning on bookmarks since this is a technology demonstrator.
Chapter Six—Creating and Using Simple OLE DB Providers and Consumers n
355
Implementing Bookmarks in the Custom IRowsetLocate Implementation Next, we need to add code to the blank header file we created earlier. Bring up the URLROWLOC.H file in the editor and enter all the grayed code in Listing 6-14 (since the file is blank you don’t need to worry about modifying anything). Save the project to protect your work. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39.
#include “stdafx.h” //@@@ define our template @@@ template class ATL_NO_VTABLE IRowsetLocateImpl : public IRowsetImpl { public: //@@@ Implement a comparison of our bookmarks @@@ STDMETHOD (Compare)(HCHAPTER hReserved, ULONG cbBookmark1, const BYTE * pBookmark1, ULONG cbBookmark2, const BYTE * pBookmark2, DBCOMPARE * pComparison) { //@@@ call our validate bookmark to be sure they’re real ones @@@ HRESULT hr = ValidateBookmark(cbBookmark1, pBookmark1); //@@@ abort if not ok @@@ if (hr != S_OK) return hr; //@@@ call our validate bookmark to be sure they’re real ones @@@ hr = ValidateBookmark(cbBookmark2, pBookmark2); //@@@ abort if not ok @@@ if (hr != S_OK) return hr; //@@@ Return based on simple number comparisons @@@ if (*pBookmark1 == *pBookmark2) *pComparison = DBCOMPARE_EQ; if (*pBookmark1 < *pBookmark2) *pComparison = DBCOMPARE_LT; if (*pBookmark1 > *pBookmark2) *pComparison = DBCOMPARE_GT; //@@@ tell COM we’re cool @@@ return S_OK; } //@@@ This is the biggie; it returns a Rowset based on the bookmark + offset @@@ STDMETHOD (GetRowsAt)(HWATCHREGION hReserved1, HCHAPTER hReserved2, ULONG cbBookmark, const BYTE * pBookmark, LONG lRowsOffset, LONG cRows, ULONG * pcRowsObtained, HROW ** prghRows) { //@@@ we need this to be thread safe @@@ T* pT = (T*)this;
356 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86.
n Chapter Six—Creating and Using Simple OLE DB Providers and Consumers //@@@ Check bookmark via our method @@@ HRESULT hr = ValidateBookmark(cbBookmark, pBookmark); //@@@ abort if not good @@@ if (hr != S_OK) return hr; //@@@ Make sure the other pointers are valid @@@ if (pcRowsObtained == NULL || prghRows == NULL) return E_INVALIDARG; //@@@ enter critical section for free threading @@@ pT->Lock(); //@@@ if our offset is negative and we can’t scroll back then error out @@@ if (lRowsOffset < 0 && !pT->m_bCanScrollBack) return DB_E_CANTSCROLLBACKWARDS; //@@@ save the current rowset in case we need it @@@ LONG iRowsetTemp = pT->m_iRowset; //@@@ get our previous position @@@ pT->m_iRowset = *pBookmark; //@@@ if we are at the beginning then make sure it’s 1 @@@ if ((cbBookmark == 1) && (*pBookmark == DBBMK_FIRST)) pT->m_iRowset = 1; //@@@ if we are at the end make sure it’s the last record number @@@ if ((cbBookmark == 1) && (*pBookmark == DBBMK_LAST)) pT->m_iRowset = pT->m_rgRowData.GetSize() + 1; //@@@ Set the start position to m_iRowset + lRowsOffset once normalized @@@ pT->m_iRowset += lRowsOffset; //@@@ do fancy position calculation if greater or less than zero @@@ if (lRowsOffset >= 0) (cRows >= 0) ? pT->m_iRowset -= 1 : pT->m_iRowset +=0; else (cRows >= 0) ? pT->m_iRowset -= 1 : pT->m_iRowset +=0; //@@@ If we’re too far off abort with OLE DB error message @@@ if (pT->m_iRowset < 0 || pT->m_iRowset >(DWORD)pT->m_rgRowData.GetSize()) { pT->m_iRowset = iRowsetTemp; return DB_E_BADSTARTPOSITION; } //@@@ Call IRowsetImpl::GetNextRows to actually get the rows @@@. hr = pT->GetNextRows(hReserved2, 0, cRows, pcRowsObtained, prghRows); //@@@ put them into the output variable @@@ pT->m_iRowset = iRowsetTemp; //@@@ exit the critical section @@@ pT->Unlock(); //@@@ return the value from the actual data acquisition call @@@ return hr; } //@@@ This is the other biggie; it gets a Rowset based on a bookmark @@@ STDMETHOD (GetRowsByBookmark)(HCHAPTER hReserved, ULONG cRows,
Chapter Six—Creating and Using Simple OLE DB Providers and Consumers n 87. 88. 89. 90. 91. 92. 93. 94. 95. 96. 97. 98. 99. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132. 133.
const ULONG rgcbBookmarks[], const BYTE * rgpBookmarks[], HROW rghRows[], DBROWSTATUS rgRowStatus[]) { //@@@ define our HRESULT holder @@@ HRESULT hr = S_OK; //@@@ do this for thread safety @@@ T* pT = (T*)this; //@@@ abort if any parameter is invalid @@@ if (rgcbBookmarks == NULL || rgpBookmarks == NULL || rghRows == NULL) return E_INVALIDARG; //@@@ boundary case where no rows are requested @@@ if (cRows == 0) return S_OK; //@@@ set up error holder @@@ bool bErrors = false; //@@@ lock for free threading safety @@@ pT->Lock(); //@@@ get some rows to return @@@ for (ULONG l=0; lCreateRow((long)*rgpBookmarks[l], ulRowsObtained, &rghRows[l]) != S_OK) { //@@@ blew it @@@ bErrors = TRUE; } else {
357
358
n Chapter Six—Creating and Using Simple OLE DB Providers and Consumers
134. 135. 136. 137. 138. 139. 140. 141. 142. 143. 144. 145. 146. 147. 148. 149. 150. 151. 152. 153. 154. 155. 156. 157. 158. 159. 160. 161. 162. 163. 164. 165. 166. 167. 168. 169. 170. 171. 172. 173. 174. 175. 176. 177. 178. 179. 180.
//@@@ if we didn’t have internal failure then we’re cool @@@ if (rgRowStatus != NULL) rgRowStatus[l] = DBROWSTATUS_S_OK; } } //@@@ exit critical section @@@ pT->Unlock(); //@@@ handle errors or return last call to CreateRow’s HRESULT @@@ if (bErrors) return DB_S_ERRORSOCCURRED; else return hr; } //@@@ we have to implement this but don’t do anything @@@ STDMETHOD (Hash)(HCHAPTER hReserved, ULONG cBookmarks, const ULONG rgcbBookmarks[], const BYTE * rgpBookmarks[], DWORD rgHashedValues[], DBROWSTATUS rgBookmarkStatus[]) { return E_NOTIMPL; //@@@ you would implement this for fancy bookmarks @@@ } //@@@ this is hidden @@@ protected: //@@@ we need this to check for valid numbers @@@ HRESULT ValidateBookmark(ULONG cbBookmark, const BYTE* pBookmark) { //@@@ use this to get size of our data @@@ T* pT = (T*)this; //@@@ we don’t accept zero or NULL bookmarks @@@ if (cbBookmark == 0 || pBookmark == NULL) return E_INVALIDARG; //@@@ All of our bookmarks are DWORDs, if they are anything other than @@@ //@@@ sizeof(DWORD) then we have an invalid bookmark @@@ if ((cbBookmark != sizeof(DWORD)) && (cbBookmark != 1)) { //@@@ note OLE DB error code @@@ return DB_E_BADBOOKMARK; } //@@@ If the contents of our bookmarks are less than 0 or greater than @@@ //@@@ rowcount, then they are invalid @@@ UINT nRows = pT->m_rgRowData.GetSize(); if ((*pBookmark <= 0 || *pBookmark > nRows) && *pBookmark != DBBMK_FIRST && *pBookmark != DBBMK_LAST) { //@@@ note OLE DB error code @@@ return DB_E_BADBOOKMARK; }
Chapter Six—Creating and Using Simple OLE DB Providers and Consumers n
359
181. //@@@ valid bookmark @@@ 182. return S_OK; 183. } 184. }; Listing 6-14
URLROWLOC.H source code after modifications
The following entries explain what this code does: n
Lines 3-7 This code defines the template class and its implementation for the IRowsetLocate interface.
n
Lines 8-32 This code handles the comparison of two bookmarks to determine which is smaller or larger, or if they are the same. Notice that it first does a validation check using a custom routine written later in the listing. Also note the return codes are HRESULTS defined via OLE DB.
n
Lines 39, 49, 81 These three lines are essential to most OLE DB implementations; they define and perform “critical section” behavior, which is code that cannot be interrupted by threading. This makes the code “thread safe” and therefore available for the most powerful type of COM threading called free threading. You’ll see this code several more times in the listing; it normally should be used in the portions of any implementation that modifies the database or related pointers like bookmarks.
n
Lines 45-47 This set of code is an example of OLE DB parameter checking. Notice that it returns the E_INVALIDARG COM HRESULT error code rather than just E_FAIL.
n
Lines 50-52 This code handles a situation where a negative offset is requested but the provider cannot support it by moving backwards in the data set; it returns an OLE DB HRESULT error code signaling this.
n
Lines 57-62 This code is the first major bookmark manipulation segment; notice that it uses the DBBMK_FIRST and DBBMK_LAST constants (which are not real bookmark values). These are always valid bookmarks as will be shown in the next routine. This segment converts the bookmark into a real number pointer into our list of strings.
n
Lines 63-75 This is the position calculation segment of the bookmark manipulation code; it makes sure the bookmark plus offset still points to a valid record number, or returns an OLE DB HRESULT error code indicating a bad bookmark position.
n
Lines 76-79 Finally, this code gets the actual rows via a call to an internal implementation method that will eventually call our own Execute function. This returns the actual rowset to the user.
n
Lines 85-146 The previous function took a single valid bookmark and returned a rowset based on an offset with a specified number of rows. This code is its complement; it takes an array of bookmarks and returns a single row for each of them, but without an offset. This requires validating
360
n Chapter Six—Creating and Using Simple OLE DB Providers and Consumers each bookmark (lines 107-122), then requesting one row at a time per bookmark (lines 123-140). Its complexity comes in working with arrays of rows, thus requiring a separate status code for each one (lines 117-119 and 134-137). Note the OLE DB HRESULT return code if an error happens along the way. n
Lines 147-153 Some provider implementations may require complex bookmarks that are computationally expensive to process. In this case, a technique called hashing (determining a general start position for the bookmark using precomputed values) is often used. This code provides an opportunity for implementation of bookmark hash calculations; our simple integer bookmarks don’t need it and so we have no implementation code.
n
Lines 157-183 This code implements the needed bookmark validation function; notice that it is not exported but intended solely for internal use. It has to check for three things: first, that the bookmarks are not NULL or zero (lines 162-164); second, that the data elements are DWORD size (lines 165-171; note the OLE DB custom HRESULT for a bad bookmark); and finally, whether the number points to a valid record in the database (lines 172-180). In the last check, use of the DBBMK_XXX constants will not give an error because they are explicitly checked for.
Implementing Variable COLUMNIFO in the Custom Rowset Implementation Finally, we need to enter some additional code to the implementation of the Rowset object. Listing 6-15 gives the source code from the URLPROVIDERRS.CPP file that implements the Rowset object. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14.
// Implementation of the CURLProviderCommand #include “stdafx.h” #include “URLProv.h” #include “URLProviderRS.h” ///////////////////////////////////////////////////////////////////////////// // CURLProviderCommand HRESULT CURLProviderCommand::Execute(IUnknown * pUnkOuter, REFIID riid, DBPARAMS * pParams, LONG * pcRowsAffected, IUnknown ** ppRowset) { CURLProviderRowset* pRowset; return CreateRowset(pUnkOuter, riid, pParams, pcRowsAffected, ppRowset, pRowset); }
Listing 6-15
URLPROVIDERRS.CPP source code before modifications
Chapter Six—Creating and Using Simple OLE DB Providers n
361
The directions below tell you what to remove and what to add to modify this code to meet our needs: n
Line 14 Insert the code in lines 14-101 of Listing 6-16 at line 14. Don’t delete or change anything else.
Listing 6-16 gives the source code from the URLPROVIDERRS.CPP file that implements the Rowset object after your changes. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39.
// Implementation of the CURLProviderCommand #include “stdafx.h” #include “URLProv.h” #include “URLProviderRS.h” ///////////////////////////////////////////////////////////////////////////// // CURLProviderCommand HRESULT CURLProviderCommand::Execute(IUnknown * pUnkOuter, REFIID riid, DBPARAMS * pParams, LONG * pcRowsAffected, IUnknown ** ppRowset) { CURLProviderRowset* pRowset; return CreateRowset(pUnkOuter, riid, pParams, pcRowsAffected, ppRowset, pRowset); } //@@@ define our template @@@ template ATLCOLUMNINFO* CommonGetColInfo(IUnknown* pPropsUnk, ULONG*pcCols) { //@@@ define structure @@@ static ATLCOLUMNINFO _rgColumns[5]; //@@@ set working variable @@@ ULONG ulCols = 0; //@@@ put the IUnknown pointer into a CComQIPtr @@@ CComQIPtr<TInterface> spProps = pPropsUnk; //@@@ create a working set @@@ CDBPropIDSet set(DBPROPSET_ROWSET); //@@@ put in bookmarks property @@@ set.AddPropertyID(DBPROP_BOOKMARKS); //@@@ get a property set variable @@@ DBPROPSET* pPropSet = NULL; //@@@ set another variable @@@ ULONG ulPropSet = 0; //@@@ define HRESULT holder @@@ HRESULT hr; //@@@ if we have a valid interface pointer get its properties @@@ if (spProps) hr = spProps->GetProperties(1, &set, &ulPropSet, &pPropSet); //@@@ if we have properties @@@ if (pPropSet) {
n Chapter Six—Creating and Using Simple OLE DB Providers and Consumers
362 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86.
//@@@ get the value of the properties @@@ CComVariant var = pPropSet->rgProperties[0].vValue; //@@@ free memory @@@ CoTaskMemFree(pPropSet->rgProperties); CoTaskMemFree(pPropSet); //@@@ if we got properties and our property is TRUE @@@ if ((SUCCEEDED(hr) && (var.boolVal == VARIANT_TRUE))) { //@@@ put in bookmark property @@@ ADD_COLUMN_ENTRY_EX(ulCols, OLESTR(“Bookmark”), 0, sizeof(DWORD), DBTYPE_BYTES, 0, 0, GUID_NULL, CURLProviderWindowsFile, dwBookmark, DBCOLUMNFLAGS_ISBOOKMARK) //@@@ increment total columns @@@ ulCols++; } } //@@@ use our macro to put in the column info @@@ ADD_COLUMN_ENTRY(ulCols, OLESTR(“Command”), 1, 256,DBTYPE_STR, 0xFF, 0xFF, GUID_NULL, CURLProviderWindowsFile, szCommand) //@@@ update the total columns @@@ ulCols++; //@@@ use our macro to put in the column info @@@ ADD_COLUMN_ENTRY(ulCols, OLESTR(“Text”), 2, 256,DBTYPE_STR,0xFF, 0xFF, GUID_NULL, CURLProviderWindowsFile, szText) //@@@ update the total columns @@@ ulCols++; //@@@ use our macro to put in the column info @@@ ADD_COLUMN_ENTRY(ulCols, OLESTR(“Command2"), 3, 256, DBTYPE_STR, 0xFF, 0xFF, GUID_NULL, CURLProviderWindowsFile, szCommand2) //@@@ update the total columns @@@ ulCols++; //@@@ use our macro to put in the column info @@@ ADD_COLUMN_ENTRY(ulCols, OLESTR(“Text2"), 4, 256, DBTYPE_STR, 0xFF, 0xFF, GUID_NULL, CURLProviderWindowsFile, szText2) //@@@ update the total columns @@@ ulCols++; //@@@ return our columns @@@ if (pcCols != NULL) *pcCols = ulCols; //@@@ send back the columnsinfo structure @@@ return _rgColumns; }
//@@@ implement command-based column info @@@
Chapter Six—Creating and Using Simple OLE DB Providers and Consumers n 87. 88. 89. 90. 91. 92. 93. 94. 95. 96. 97. 98. 99. 100. 101.
363
ATLCOLUMNINFO*CURLProviderWindowsFile::GetColumnInfo (CURLProviderCommand* pThis, ULONG* pcCols) { //@@@ use internal implementation @@@ return CommonGetColInfo(pThis->GetUnknown(), pcCols); } //@@@ implement rowset-based column info @@@ ATLCOLUMNINFO* CURLProviderWindowsFile::GetColumnInfo(CURLProviderRowset* pThis, ULONG* pcCols) { //@@@ use internal implementation @@@ return CommonGetColInfo(pThis->GetUnknown(), pcCols); }
Listing 6-16
URLPROVIDERRS.CPP source code after modifications
The following entries explain what the new code lines do: n
Lines 14-17 This code defines and implements the template for the COLUMNINFO override class.
n
Lines 18-19 This segment defines a static structure which will return either four or five elements depending on whether we are getting bookmark-enabled COLUMNINFO (the purpose of defining this function).
n
Lines 20-21 Here we define our holder for the number of columns returned. We start it at zero so we can increment it.
n
Lines 22-23 This transfers the input IUnknown interface pointer into a CComQIPtr template class so we can manipulate it more easily.
n
Lines 24-25 Here we create a working property set variable to determine whether or not we support bookmarks.
n
Lines 26-27
We add bookmark support to the working set here.
n
Lines 28-29
This defines our other property set variable.
n
Lines 30-31 We create a holder for the total properties returned to the second property set.
n
Lines 32-33
n
Lines 34-36 Now, if we got a valid interface pointer, we determine what properties it actually supports via a call to its OLE DB template implementation for property support.
n
Lines 37-44 This is where our sleight of hand begins. Remember that we are doing this to handle in one routine both COLUMNINFO with bookmark support and COLUMNINFO without bookmark support. Therefore, the only thing that can change is whether bookmarks are currently supported. This code gets the boolean VARIANT value from the property set
Defines an HRESULT holder.
364
n Chapter Six—Creating and Using Simple OLE DB Providers and Consumers returned from the implementation code, and then disposes of the remaining results because they are fixed. n
Lines 45-57 If we got a valid function return HRESULT and if the support value is True, then call our previously defined (Listing 6-9, lines 45-58) ADD_COLUMN_ENTRY_EX macro to put in the bookmark property support at the first position and increment our counter.
n
Lines 58-77 This code puts in the fixed columninfo data via the ADD_COLUMN_ENTRY macros defined in Listing 6-9 above. Notice that due to the use of the counter, the entries are put in the right place regardless of whether bookmarks are supported.
n
Lines 79-82 If we got a valid pointer to hold the returned columns number, we put our counter into it. Either way, we send back the COLUMNSINFO structure.
n
Lines 87-101 Here we use our common implementation to get the columns information for both Command and Rowset objects.
Disabling Schema Support Finally, we need to alter the Session object implementation to remove schema support, since rewriting the code to support our schema is a lot of work and will be covered in more detail in later chapters. Listing 6-17 gives the source code from the URLPROVIDERSESS.H file that implements the Session object. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23.
// Session.h : Declaration of the CURLProviderSession #ifndef __CURLProviderSession_H_ #define __CURLProviderSession_H_ #include “resource.h” // main symbols #include “URLProviderRS.h” class CURLProviderSessionTRSchemaRowset; class CURLProviderSessionColSchemaRowset; class CURLProviderSessionPTSchemaRowset; ///////////////////////////////////////////////////////////////////////////// // CURLProviderSession class ATL_NO_VTABLE CURLProviderSession : public CComObjectRootEx, public IGetDataSourceImpl, public IOpenRowsetImpl, public ISessionPropertiesImpl, public IObjectWithSiteSessionImpl, public IDBSchemaRowsetImpl, public IDBCreateCommandImpl { public: CURLProviderSession() { }
Chapter Six—Creating and Using Simple OLE DB Providers and Consumers n 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70.
365
HRESULT FinalConstruct() { return FInit(); } STDMETHOD(OpenRowset)(IUnknown *pUnk, DBID *pTID, DBID *pInID, REFIID riid, ULONG cSets, DBPROPSET rgSets[], IUnknown **ppRowset) { CURLProviderRowset* pRowset; return CreateRowset(pUnk, pTID, pInID, riid, cSets, rgSets, ppRowset, pRowset); } BEGIN_PROPSET_MAP(CURLProviderSession) BEGIN_PROPERTY_SET(DBPROPSET_SESSION) PROPERTY_INFO_ENTRY(SESS_AUTOCOMMITISOLEVELS) END_PROPERTY_SET(DBPROPSET_SESSION) END_PROPSET_MAP() BEGIN_COM_MAP(CURLProviderSession) COM_INTERFACE_ENTRY(IGetDataSource) COM_INTERFACE_ENTRY(IOpenRowset) COM_INTERFACE_ENTRY(ISessionProperties) COM_INTERFACE_ENTRY(IObjectWithSite) COM_INTERFACE_ENTRY(IDBCreateCommand) COM_INTERFACE_ENTRY(IDBSchemaRowset) END_COM_MAP() BEGIN_SCHEMA_MAP(CURLProviderSession) SCHEMA_ENTRY(DBSCHEMA_TABLES, CURLProviderSessionTRSchemaRowset) SCHEMA_ENTRY(DBSCHEMA_COLUMNS, CURLProviderSessionColSchemaRowset) SCHEMA_ENTRY(DBSCHEMA_PROVIDER_TYPES, CURLProviderSessionPTSchemaRowset) END_SCHEMA_MAP() }; class CURLProviderSessionTRSchemaRowset : public CRowsetImpl< CURLProviderSessionTRSchemaRowset, CTABLESRow, CURLProviderSession> { public: HRESULT Execute(LONG* pcRowsAffected, ULONG, const VARIANT*) { USES_CONVERSION; CURLProviderWindowsFile wf; CTABLESRow trData; lstrcpyW(trData.m_szType, OLESTR(“TABLE”)); lstrcpyW(trData.m_szDesc, OLESTR(“The Directory Table”)); HANDLE hFile = INVALID_HANDLE_VALUE; TCHAR szDir[MAX_PATH + 1]; DWORD cbCurDir = GetCurrentDirectory(MAX_PATH, szDir); lstrcat(szDir, _T(“\\*.*”));
366 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93. 94. 95. 96. 97. 98. 99. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117.
n Chapter Six—Creating and Using Simple OLE DB Providers and Consumers hFile = FindFirstFile(szDir, &wf); if (hFile == INVALID_HANDLE_VALUE) return E_FAIL; // User doesn’t have a c:\ drive FindClose(hFile); lstrcpynW(trData.m_szTable, T2OLE(szDir), SIZEOF_MEMBER(CTABLESRow, m_szTable)); if (!m_rgRowData.Add(trData)) return E_OUTOFMEMORY; *pcRowsAffected = 1; return S_OK; } }; class CURLProviderSessionColSchemaRowset : public CRowsetImpl< CURLProviderSessionColSchemaRowset, CCOLUMNSRow, CURLProviderSession> { public: HRESULT Execute(LONG* pcRowsAffected, ULONG, const VARIANT*) { USES_CONVERSION; CURLProviderWindowsFile wf; HANDLE hFile = INVALID_HANDLE_VALUE; TCHAR szDir[MAX_PATH + 1]; DWORD cbCurDir = GetCurrentDirectory(MAX_PATH, szDir); lstrcat(szDir, _T(“\\*.*”)); hFile = FindFirstFile(szDir, &wf); if (hFile == INVALID_HANDLE_VALUE) return E_FAIL; // User doesn’t have a c:\ drive FindClose(hFile);// szDir has got the tablename DBID dbid; memset(&dbid, 0, sizeof(DBID)); dbid.uName.pwszName = T2OLE(szDir); dbid.eKind = DBKIND_NAME; return InitFromRowset < _RowsetArrayType > (m_rgRowData, &dbid, NULL, m_spUnkSite, pcRowsAffected); } }; class CURLProviderSessionPTSchemaRowset : public CRowsetImpl< CURLProviderSessionPTSchemaRowset, CPROVIDER_TYPERow, CURLProviderSession> { public: HRESULT Execute(LONG* pcRowsAffected, ULONG, const VARIANT*) { return S_OK; } };
Chapter Six—Creating and Using Simple OLE DB Providers and Consumers n
367
118. #endif //__CURLProviderSession_H_ Listing 6-17
URLPROVIDERSESS.H source code before modifications
Remove all the code in the above listing. Then replace it with the grayed code in Listing 6-18. Listing 6-18 gives the source code from the URLPROVIDERSESS.H file that implements the Session object after your changes. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38.
// Session.h : Declaration of the CURLProviderSession #ifndef __CURLProviderSession_H_ #define __CURLProviderSession_H_ #include “resource.h” // main symbols #include “URLProviderRS.h” ///////////////////////////////////////////////////////////////////////////// // CURLProviderSession class ATL_NO_VTABLE CURLProviderSession : public CComObjectRootEx, public IGetDataSourceImpl, public IOpenRowsetImpl, public ISessionPropertiesImpl, public IObjectWithSiteSessionImpl, public IDBCreateCommandImpl { public: CURLProviderSession() { } HRESULT FinalConstruct() { return FInit(); } STDMETHOD(OpenRowset)(IUnknown *pUnk, DBID *pTID, DBID*pInID, REFIID riid, ULONG cSets, DBPROPSET rgSets[], IUnknown **ppRowset) { CURLProviderRowset* pRowset; return CreateRowset(pUnk, pTID, pInID, riid, cSets, rgSets, ppRowset, pRowset); } BEGIN_PROPSET_MAP(CURLProviderSession) BEGIN_PROPERTY_SET(DBPROPSET_SESSION) PROPERTY_INFO_ENTRY(SESS_AUTOCOMMITISOLEVELS) END_PROPERTY_SET(DBPROPSET_SESSION) END_PROPSET_MAP() BEGIN_COM_MAP(CURLProviderSession) COM_INTERFACE_ENTRY(IGetDataSource)
368 39. 40. 41. 42. 43. 44. 45.
n Chapter Six—Creating and Using Simple OLE DB Providers and Consumers COM_INTERFACE_ENTRY(IOpenRowset) COM_INTERFACE_ENTRY(ISessionProperties) COM_INTERFACE_ENTRY(IObjectWithSite) COM_INTERFACE_ENTRY(IDBCreateCommand) END_COM_MAP() }; #endif //__CURLProviderSession_H_
Listing 6-18
URLPROVIDERSESS.H source code after modifications
If you examine the difference, you’ll see that all entries relating to schema support are gone, but everything else is the same. This is necessary because the column information this schema depends on has been changed to fit our database, and so won’t compile.
Building the OLE DB Provider DLL Once you are satisfied you’ve entered the code from the above listings correctly, build the OLE DB COM server from the Build menu. If you encounter any errors, please check the code in the listing to find your mistake (it has been exhaustively debugged and is correct). Once you have compiled the component successfully it is automatically registered on the local machine; if you need to move it to another machine, the directions are in Chapter 10. Otherwise, you’re ready to create the OLE DB consumer project in MFC to test the provider.
Creating an OLE DB Consumer Application with MFC MFC does not directly support OLE DB (although it does have one MFC class that does support basic recordset functionality). However, with Visual C++ 6.0 this isn’t much of a problem, since we can import ATL templates directly from their header files and use them. The following application will allow you to learn how to create a basic MFC OLE DB consumer and how to connect with an OLE DB provider and obtain information from it. While this application is quite simple, it can easily be scaled up to do very sophisticated things!
Step-By-Step Start Visual C++ 6.0. From the File menu, select New. The New dialog box opens; click on the MFC AppWizard (exe) entry. Next, select an appropriate directory, then enter a project name of TestURLProv. Figure 6-7 shows how the New dialog should appear when you are done. Click OK to open the MFC AppWizard.
Chapter Six—Creating and Using Simple OLE DB Providers and Consumers n
369
Figure 6-7 The New dialog box creating the TestURLProv MFC project
In the first page of the MFC AppWizard dialog, make sure the Dialog based radio button is selected, as indicated in Figure 6-8. Click on the Next button to move to the next page of the wizard.
Figure 6-8 Setting the MFC application project basic properties
On the second page of the MFC AppWizard, you have a number of options that control the behavior of the application; leave everything at the default except to make sure that Automation support is checked. Figure 6-9 illustrates how the dialog should appear when you are done. Click on the Next button to move to the next page of the AppWizard.
370
n Chapter Six—Creating and Using Simple OLE DB Providers and Consumers
Figure 6-9 Setting the MFC application project COM properties
On the third page of the MFC AppWizard, select the only available option for the user interface (MFC Standard), and enable source file comments and use of MFC as a shared DLL (to save executable size). Figure 6-10 illustrates how the dialog should appear when you are done. Click on the Next button to move to the next page of the AppWizard.
Figure 6-10 Setting the MFC application project user interface properties
On the fourth page of the MFC AppWizard, you see the results of the choices you made in the previous dialogs. Figure 6-11 illustrates how the dialog should appear when you are done. Click on the Finish button to create the MFC application.
Chapter Six—Creating and Using Simple OLE DB Providers and Consumers n
371
Figure 6-11 Confirming the MFC application project user interface properties
A confirmation dialog box appears as shown in Figure 6-12; press OK to create the MFC project.
Figure 6-12 The confirmation dialog
Back in the Visual C++ IDE, bring up the workspace viewer, and select the ResourceView tab. Expand the Dialogs entry and double-click on its only element, which is the main dialog resource for the application. Remove the pre-existing static text and button controls. Using Figure 6-13 as your guide, lay out a new list box and button control as shown on the figure, accepting the default ID values generated by Visual C++. Save the project to protect your work.
372
n Chapter Six—Creating and Using Simple OLE DB Providers and Consumers
Figure 6-13 The main dialog display of the MFC application in the Resource Editor
Next, select the list box and right-click to bring up its context menu; select the Properties entry. Move to the Styles tab and uncheck the Sort check box, as illustrated in Figure 6-14. Click on the pushpin button to Figure 6-14 keep the Properties Setting the list dialog visible while box Sort propyou make the other erty to off in two changes needed. the MFC Resource Editor
Figure 6-15 Setting the button caption in the MFC Resource Editor
Next, select the button control; its properties appear in the Properties dialog. Move to the General tab and enter a caption of Get OLE DB Provider Data, as illustrated in Figure 6-15.
Chapter Six—Creating and Using Simple OLE DB Providers and Consumers n
Figure 6-16 Setting the dialog box title in the MFC Resource Editor
373
Finally, select the dialog box itself (click on it away from the two controls). Move to the General tab of the still-on-top Properties dialog and enter a caption of OLE DB Provider Test as illustrated in Figure 6-16. (This will become the dialog box title.) Click on the pushpin button again to free the Properties dialog, and save the project to protect your work.
As the next-to-last user interface step, you need to set up a member variable to facilitate interaction with the list box control. Hold down the Ctrl key and double-click on the list box control. The Add Member Variable dialog appears as shown in Figure 6-17. Enter m_ctlstrings as the name of the member variable and set its category to Control (which will automatically set its type to CListBox), and click on the OK butFigure 6-17 ton. This will connect the user Adding a interface of the list box with an member variMFC control, which is much easier able control to interact with! for the list box in MFC
Finally, you need to create an event handler for dealing with user clicks on the button control. To save time, simply hold down the Ctrl key and doubleclick on the button control. MFC will automatically create a default event handler for you and place you in the text editor ready to enter code. Save the project at this point.
Line-By-Line Now you are ready to enter the small amount of source code needed to connect the user interface with the OLE DB provider you created in ATL earlier. Bring up the TESTURLPROVDLG.H file in the text editor. Scan through it and enter or change any grayed line of source code in Listing 6-19. Save the project. The section after the code explains what these changes do.
374 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46.
n Chapter Six—Creating and Using Simple OLE DB Providers and Consumers // TestURLProvDlg.h : header file // #if !defined(AFX_TESTURLPROVDLG_H__C04E85BB_F97F_11D2_B7EE_00E02916C424__ INCLUDED_) #define AFX_TESTURLPROVDLG_H__C04E85BB_F97F_11D2_B7EE_00E02916C424__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 //@@@ ESSENTIAL!!! Otherwise MFC will expand the macros incorrectly!!! @@@ #include //@@@ define our handler class @@@ class CProvider { public: //@@@ put in the bookmark field @@@ CBookmark<4> bookmark; //@@@ put in the four text fields @@@ TCHAR szCommand[256]; TCHAR szText[256]; TCHAR szCommand2[256]; TCHAR szText2[256]; //@@@ put in the database map @@@ BEGIN_COLUMN_MAP(CProvider) //@@@ this is a bookmark @@@ BOOKMARK_ENTRY( bookmark ); //@@@ these are string fields @@@ COLUMN_ENTRY(1, szCommand ); COLUMN_ENTRY(2, szText ); COLUMN_ENTRY(3, szCommand2 ); COLUMN_ENTRY(4, szText2 ); END_COLUMN_MAP() }; class CTestURLProvDlgAutoProxy; ///////////////////////////////////////////////////////////////////////////// // CTestURLProvDlg dialog class CTestURLProvDlg : public CDialog { DECLARE_DYNAMIC(CTestURLProvDlg); friend class CTestURLProvDlgAutoProxy; // Construction
Chapter Six—Creating and Using Simple OLE DB Providers and Consumers n 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89.
public: CTestURLProvDlg(CWnd* pParent = NULL);// standard constructor virtual ~CTestURLProvDlg(); // Dialog Data //{{AFX_DATA(CTestURLProvDlg) enum { IDD = IDD_TESTURLPROV_DIALOG }; CListBox m_ctlstrings; //}}AFX_DATA // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CTestURLProvDlg) protected: virtual void DoDataExchange(CDataExchange* pDX);// DDX/DDV support //}}AFX_VIRTUAL // Implementation protected: CTestURLProvDlgAutoProxy* m_pAutoProxy; HICON m_hIcon; BOOL CanExit(); // Generated message map functions //{{AFX_MSG(CTestURLProvDlg) virtual BOOL OnInitDialog(); afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); afx_msg void OnClose(); virtual void OnOK(); virtual void OnCancel(); afx_msg void OnButton1(); //}}AFX_MSG DECLARE_MESSAGE_MAP() }; //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional declarations immediately // before the previous line. #endif // !defined(AFX_TESTURLPROVDLG_H__C04E85BB_F97F_11D2_B7EE_00E02916C424__ INCLUDED_)
Listing 6-19
TESTURLPROVDLG.H source code after modifications
375
376
n Chapter Six—Creating and Using Simple OLE DB Providers and Consumers n
Lines 10-11 This code makes sure that the ATL OLE DB templates are imported from their header file. Without this include file, MFC will interpret the macros using its definitions, which won’t work!
n
Lines 14-23 This code defines the custom class we need to send to other OLE DB templates to interact with our OLE DB provider; it is a “stub” definition of the provider’s user record functionality. The code defines the actual variables that will hold the information from the OLE DB provider.
n
Lines 24-34 These macros are used to actually create the database functionality for our stub provider. Notice that they are somewhat different from the macros we used in the provider.
With the database record defined, you next need to actually connect to the database and get information from it into the list box. You do this in the event handler for the button control’s BN_CLICKED message. Bring up the TESTURLPROVDLG.CPP file in the text editor since this is where MFC put the code for the event handler when it created it. Scan through the listing and enter or change any grayed line of source code in Listing 6-20. Save the project. The section after the code explains what these changes do. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27.
// TestURLProvDlg.cpp : implementation file // #include “stdafx.h” #include “TestURLProv.h” #include “TestURLProvDlg.h” #include “DlgProxy.h” //@@@ put this in to be sure @@@ #include #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // CAboutDlg dialog used for App About class CAboutDlg : public CDialog { public: CAboutDlg(); // Dialog Data //{{AFX_DATA(CAboutDlg) enum { IDD = IDD_ABOUTBOX };
Chapter Six—Creating and Using Simple OLE DB Providers and Consumers n 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74.
//}}AFX_DATA // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CAboutDlg) protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support //}}AFX_VIRTUAL // Implementation protected: //{{AFX_MSG(CAboutDlg) //}}AFX_MSG DECLARE_MESSAGE_MAP() }; CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD) { //{{AFX_DATA_INIT(CAboutDlg) //}}AFX_DATA_INIT } void CAboutDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CAboutDlg) //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(CAboutDlg, CDialog) //{{AFX_MSG_MAP(CAboutDlg) // No message handlers //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CTestURLProvDlg dialog IMPLEMENT_DYNAMIC(CTestURLProvDlg, CDialog); CTestURLProvDlg::CTestURLProvDlg(CWnd* pParent /*=NULL*/) : CDialog(CTestURLProvDlg::IDD, pParent) { //{{AFX_DATA_INIT(CTestURLProvDlg) // NOTE: the ClassWizard will add member initialization here //}}AFX_DATA_INIT // Note that LoadIcon does not require a subsequent DestroyIcon in Win32 m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
377
n Chapter Six—Creating and Using Simple OLE DB Providers and Consumers
378 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93. 94. 95. 96. 97. 98. 99. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121.
m_pAutoProxy = NULL; } CTestURLProvDlg::~CTestURLProvDlg() { // If there is an automation proxy for this dialog, set // its back pointer to this dialog to NULL, so it knows // the dialog has been deleted. if (m_pAutoProxy != NULL) m_pAutoProxy->m_pDialog = NULL; } void CTestURLProvDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CTestURLProvDlg) DDX_Control(pDX, IDC_LIST1, m_ctlstrings); //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(CTestURLProvDlg, CDialog) //{{AFX_MSG_MAP(CTestURLProvDlg) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_WM_CLOSE() ON_BN_CLICKED(IDC_BUTTON1, OnButton1) //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CTestURLProvDlg message handlers BOOL CTestURLProvDlg::OnInitDialog() { CDialog::OnInitDialog(); // Add “About...” menu item to system menu. // IDM_ABOUTBOX must be in the system command range. ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); ASSERT(IDM_ABOUTBOX < 0xF000); CMenu* pSysMenu = GetSystemMenu(FALSE); if (pSysMenu != NULL) { CString strAboutMenu;
Chapter Six—Creating and Using Simple OLE DB Providers and Consumers n 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132. 133. 134. 135. 136. 137. 138. 139. 140. 141. 142. 143. 144. 145. 146. 147. 148. 149. 150. 151. 152. 153. 154. 155. 156. 157. 158. 159. 160. 161. 162. 163. 164. 165. 166. 167. 168.
strAboutMenu.LoadString(IDS_ABOUTBOX); if (!strAboutMenu.IsEmpty()) { pSysMenu->AppendMenu(MF_SEPARATOR); pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu); } } // Set the icon for this dialog. The framework does this automatically // when the application’s main window is not a dialog SetIcon(m_hIcon, TRUE); // Set big icon SetIcon(m_hIcon, FALSE); // Set small icon // TODO: Add extra initialization here return TRUE;
// return TRUE
unless you set the focus to a control
} void CTestURLProvDlg::OnSysCommand(UINT nID, LPARAM lParam) { if ((nID & 0xFFF0) == IDM_ABOUTBOX) { CAboutDlg dlgAbout; dlgAbout.DoModal(); } else { CDialog::OnSysCommand(nID, lParam); } } // If you add a minimize button to your dialog, you will need the code below // to draw the icon. For MFC applications using the document/view model, // this is automatically done for you by the framework. void CTestURLProvDlg::OnPaint() { if (IsIconic()) { CPaintDC dc(this); // device context for painting SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0); // Center icon in client rectangle int cxIcon = GetSystemMetrics(SM_CXICON); int cyIcon = GetSystemMetrics(SM_CYICON); CRect rect;
379
n Chapter Six—Creating and Using Simple OLE DB Providers and Consumers
380 169. 170. 171. 172. 173. 174. 175. 176. 177. 178. 179. 180. 181. 182. 183. 184. 185. 186. 187. 188. 189. 190. 191. 192. 193. 194. 195. 196. 197. 198. 199. 200. 201. 202. 203. 204. 205. 206. 207. 208. 209. 210. 211. 212. 213. 214. 215.
GetClientRect(&rect); int x = (rect.Width() - cxIcon + 1) / 2; int y = (rect.Height() - cyIcon + 1) / 2; // Draw the icon dc.DrawIcon(x, y, m_hIcon); } else { CDialog::OnPaint(); } } // The system calls this to obtain the cursor to display while the user drags // the minimized window. HCURSOR CTestURLProvDlg::OnQueryDragIcon() { return (HCURSOR) m_hIcon; } // Automation servers should not exit when a user closes the UI // if a controller still holds on to one of its objects. These // message handlers make sure that if the proxy is still in use, // then the UI is hidden but the dialog remains around if it // is dismissed. void CTestURLProvDlg::OnClose() { if (CanExit()) CDialog::OnClose(); } void CTestURLProvDlg::OnOK() { if (CanExit()) CDialog::OnOK(); } void CTestURLProvDlg::OnCancel() { if (CanExit()) CDialog::OnCancel(); } BOOL CTestURLProvDlg::CanExit() { // If the proxy object is still around, then the automation
Chapter Six—Creating and Using Simple OLE DB Providers and Consumers n
381
216. // controller is still holding on to this application. Leave 217. // the dialog around, but hide its UI. 218. if (m_pAutoProxy != NULL) 219. { 220. ShowWindow(SW_HIDE); 221. return FALSE; 222. } 223. 224. return TRUE; 225. } 226. 227. void CTestURLProvDlg::OnButton1() 228. { 229. // TODO: Add your control notification handler code here 230. //@@@ put in template code to send a command-based string to the provider @@@ 231. CCommand > table; 232. //@@@ define our datasource variable @@@ 233. CDataSource source; 234. //@@@ define our session variable @@@ 235. CSession session; 236. //@@@ get the OLE DB provider via COM technique @@@ 237. if (source.Open(“URLProv.URLProvider.1",NULL,NULL,NULL,NULL) !=S_OK) 238. { 239. //@@@ signal error to user @@@ 240. AfxMessageBox( “Can’t contact URLProv Server!” ); 241. //@@@ leave @@@ 242. return; 243. } 244. //@@@ open the data source object from our provider @@@ 245. if (session.Open( source ) != S_OK) 246. { 247. //@@@ signal error to user @@@ 248. AfxMessageBox( “Can’t open URLProv data source!” ); 249. //@@@ leave @@@ 250. return; 251. } 252. //@@@ create a property holder @@@ 253. CDBPropSet propset(DBPROPSET_ROWSET); 254. //@@@ tell it we support bookmarks @@@ 255. propset.AddProperty(DBPROP_IRowsetLocate, true); 256. //@@@ try to open the URLS.TXT database (created later) @@@ 257. if (table.Open(session, _T(“c:\\urls.txt”),&propset) != S_OK) 258. { 259. //@@@ signal error to user @@@ 260. AfxMessageBox( “Can’t open URLS.TXT database!” ); 261. //@@@ leave @@@ 262. return;
382 263. 264. 265. 266. 267. 268. 269. 270. 271. 272. 273. 274. 275. 276. 277. 278. 279. 280. 281. 282. 283. 284. 285. 286. 287. }
n Chapter Six—Creating and Using Simple OLE DB Providers and Consumers } //@@@ create a bookmark holder @@@ CBookmark<4> tempBookmark; //@@@ create a position counter just to show off @@@ ULONG ulCount=0; //@@@ loop through all the strings in the database @@@ while (table.MoveNext() == S_OK) { //@@@ if we have just done second record make it bookmark @@@ if (ulCount == 2 ) tempBookmark = table.bookmark; //@@@ put first string into list box @@@ m_ctlstrings.AddString(table.szCommand); //@@@ put second string into list box @@@ m_ctlstrings.AddString(table.szText); //@@@ increment our count @@@ ulCount++; } //@@@ show off bookmark functionality @@@ table.MoveToBookmark(tempBookmark); //@@@ get second entry again into list box @@@ m_ctlstrings.AddString(table.szCommand); //@@@ ditto @@@ m_ctlstrings.AddString(table.szText);
Listing 6-20
TESTURLPROVDLG.CPP source code after modifications n
Lines 8-9 This makes sure that MFC will use the ATL macros for our OLE DB templates.
n
Lines 230-235 This segment defines a template-created Command object and a DataSource and Session object. As noted in Chapter 4, all interaction with OLE DB providers must be via a DataSource object, which is then used to create a Session object, which finally executes commands or gets rowsets.
n
Lines 236-243 This code gets our basic DataSource object. It does so using the COM ProgID value for the OLE DB provider (which is in the RGS file of that project if you forget it). We test for a COM success, and if it doesn’t happen we signal the user with an informative error message so they can fix the problem. All this does, however, is make contact with the COM server that contains our OLE DB provider; we haven’t done any database work yet!
n
Lines 244-251 Next we have to get a Session object, and this code does that chore. Notice that it takes our source variable as a parameter (so it has the right object to connect with), and returns a COM HRESULT
Chapter Six—Creating and Using Simple OLE DB Providers and Consumers n
383
error code, which we can check and then return a user-friendly error message if needed. n
Lines 253-263 This is the most vital code, because it actually sends a command to the OLE DB provider. Up to this point we were using “canned” OLE DB functionality from our ATL templates. This is where our code gets tested! First, an OLE DB property object is created that informs the provider that we support bookmarks (lines 253-255; note that the IRowsetLocate interface is what turns on bookmarks). Then in line 257 the big call is made: It sends a command string to the provider via the table object’s Open method. This call contains as parameters the Session object, a string with a path to a text file containing names and URL values that we’ll create later and place on the root in C: drive, and our OLE DB Property object that indicates bookmark support. This will be sent to the various routines we wrote or modified so that a rowset is returned with all the strings stored in the text file in records consisting of two strings each. If a problem occurs, the COM error code is checked and an error message sent to the user.
n
Lines 264-267 This segment simply defines a bookmark holder object and a counter variable. We’ll use them together to illustrate a successful bookmark implementation.
n
Lines 268-279 This is the loop that gets all the records in our database and puts them into the list box. It is quite straightforward, using the MoveNext function of our Table object to increment along the returned rowset. Pay a bit of attention to our arbitrary setting of the second record for the bookmark; we could have used any record value or one of the DBBMK_XXX constants. As each set of strings is acquired we put them into the list box via our MFC control linked to it earlier via the user interface. The loop ends when MoveNext returns an error code, which we ignore otherwise.
n
Lines 280-285 This code illustrates using a bookmark; it takes the arbitrary bookmark value we acquired in the earlier loop and obtains the record for it, then puts the returned strings at the end of the list box list.
Building the MFC OLE DB Consumer Application Once you are satisfied you’ve entered the code from the above listings correctly, build the OLE DB consumer test application from the Build menu. If you encounter any errors, please check the code in the listing to find your mistake (it has been exhaustively debugged and is correct). Now you are ready to create the text file “database” and test the consumer and provider.
384
n Chapter Six—Creating and Using Simple OLE DB Providers and Consumers
Creating the Text File Database and Testing the OLE DB Provider with the MFC Consumer Application As the final part of this chapter’s work, you need to create a simple text file with at least 20 strings in it. Although they could be anything, for this illustration I’ve used paired descriptions and URLs. Once you’ve created this “database” and saved it into the filename and directory you hard-coded into the application, you can then run the application and test the OLE DB provider you wrote earlier. Warning
Be sure you have an even number of strings in the file if you choose to alter what is shown in the listing.
Step-By-Step Open Notepad or any other text file editor you like (Visual C++, for example). Enter the text in Listing 6-21 and save it as URLS.TXT in the root directory of your C: drive (or wherever you hard-coded it in Listing 6-20 above). 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24.
Wordware Publishing http://www.wordware.com/ Microsoft http://www.microsoft.com/ COM and DCOM http://www.microsoft.com/com/ OLE DB http://www.microsoft.com/oledb/ MTS http://www.microsoft.com/com/mts/ COM+ http://www.microsoft.com/com/com+/ Scripting http://www.microsoft.com/scripting/ Internet Explorer http://www.microsoft.com/ie/ Windows 2000 http://www.microsoft.com/w2k/ Visual Studio http://www.microsoft.com/vstudio/ FrontPage Express http://www.microsoft.com/fpe/ Office 2000 http://www.microsoft.com/msoffice/
Chapter Six—Creating and Using Simple OLE DB Providers and Consumers n 25. 26.
385
MSDN http://msdn.microsoft.com/
Listing 6-21
The URLS.TXT database file
Now start the TESTURLPROV application from inside Visual C++ or from the Explorer. When the dialog appears, click on the button. After a moment (if you’ve successfully completed all the above steps), the list of URLs and descriptions should appear in the list box as shown in Figure 6-18. If you get one of the error messages or the text is garbled, check over your code and make sure you have compiled and Figure 6-18 registered the OLE The MFC OLE DB consumer DB provider COM application server. (As a last displays the resort, the applicafile database tion and provider are from the OLE on the CD as Visual DB provider C++ 6.0 projects.)
Where We Go From Here You’ve completed the first OLE DB provider and consumer projects for our book now, and should have a good handle on the basics of OLE DB programming with both ATL templates and MFC classes. The next chapter ratchets things up a significant amount, providing directions for creating a much more powerful ATL OLE DB provider that is designed to work in a three-tier client/ server environment.
Chapter Seven
Creating OLE DB Service Providers with ATL Now that you’ve implemented your first OLE DB simple provider, you’re ready to move to a higher level in OLE DB: service providers. These OLE DB elements are both providers and consumers (although not always using OLE DB templates) that play a major role in three-tier applications for OLE DB. This chapter will show you how to implement a service provider for Access databases as dual COM servers, one of which as a consumer acquires a database record, then sends this information as COM properties to another COM server which provides it as an OLE DB provider record. An MFC application that accesses the OLE DB provider is also shown.
Service Providers Have the Best of Both Worlds in OLE DB Although the provider-consumer model is a good one and encompasses most of the jobs OLE DB will be called upon to do, there are some that don’t fit. An example is the need to obtain results from a database without incurring the overhead of having to write database access code. Say a company needs to allow its traveling representatives to find out their remaining travel allowance so they can determine whether an increase is needed. Creating software to connect with a database over the Internet is not trivial, particularly if there is a need to maintain security. The standard OLE DB provider-consumer model just doesn’t fit this situation. A better approach would be to put a single COM server on the database machine which would contain the database code, and have the remote applications contact it via DCOM. This requires the database server to contain OLE DB Consumer code (to contact Access) and the database consumer on the client machine to contain OLE DB provider code (to interact with the remote applications). This is the concept behind an OLE DB service provider.
387
388
n Chapter Seven—Creating OLE DB Service Providers with ATL
Creating an OLE DB Service Provider COM DLL Consumer with ATL The first step in our three-tier application is to create the OLE DB service provider using ATL. This COM DLL will contain code to obtain data from an Access database, and encode it into a set of COM BSTR and long properties. Note
Before you perform this step, however, you need to copy the ACCOUNTS. MDB file from the Ch07 folder of the companion CD to the development computer, and then create a user DSN for it in the ODBC 32-bit control panel applet named CheckingAccount. (See Chapter 8 if you need help with Access and ODBC.)
Warning
You must have Visual C++ 6.0. to create this and the other projects in the book; Visual C++ 5.0 does not support the OLE DB system in the same way.
Step-By-Step Start Visual C++ 6.0. From the File menu, select New. The New dialog box opens; click on ATL COM AppWizard. Next select an appropriate directory, then enter a project name of atl3c. Figure 7-1 shows how the New dialog should appear when you are done. Click OK to open the ATL COM AppWizard.
Figure 7-1 The New dialog box creating the atl3c ATL project
Chapter Seven—Creating OLE DB Service Providers with ATL n
Figure 7-2 Setting the OLE DB COM server project properties
389
In the ATL COM AppWizard, select the DLL project, and make sure that the Support MFC and Merge Proxy/Stub Code check boxes are checked, and that the Support MTS check box is unchecked since we don’t need that option. Figure 7-2 illustrates how the dialog should appear when you are done. Click Finish.
A confirmation dialog box appears as shown in Figure 7-3; press OK to create the ATL project.
Figure 7-3 The confirmation dialog
Figure 7-4 Selecting Simple Object in the ATL Object Wizard
From the Insert menu, select New ATL Object. The ATL Object Wizard dialog appears, as shown in Figure 7-4. Select the Objects entry in the left-hand list box, and the Simple Object entry in the right-hand list box. Press Next to move to the Names tab of the settings page of the wizard.
390
n Chapter Seven—Creating OLE DB Service Providers with ATL
Figure 7-5 Entering name information in the ATL Object Wizard
Figure 7-6 Entering attribute information in the ATL Object Wizard dialog
As shown in Figure 7-5, enter AccountInfo as the short name of the implementation class; the other boxes will fill in automatically. Accept the default values and move to the Attributes tab. As shown in Figure 7-6, be sure that Aggregation is on and that Support ISupportErrorInfo is checked. Press OK to add the simple Server object to your ATL project. Save the project to protect your work. Next, activate the ClassView pane of the workspace window, and expand the nodes until the IAccountInfo interface entry is seen. Right-click on it to bring up its context menu, and select Add Method. In the Add Method Wizard dialog, set a method name of OpenAccounts. Figure 7-7 shows how the dialog should look when you are done. Click on OK to add this method to the OLE DB service provider ATL project. Figure 7-7 Setting the OpenAccounts method in the Method Wizard dialog
Chapter Seven—Creating OLE DB Service Providers with ATL n
Figure 7-8 Setting the CloseAccounts method in the Method Wizard dialog
Figure 7-9 Setting the AccountName property in the Property Wizard dialog
391
Repeat the above process to add another method, named CloseAccounts. Figure 7-8 shows how the Add Method Wizard should look when you are done. Click on OK to add this method, and save the project to protect your work.
Next, right-click on the IAccountInfo interface in ClassWizard to bring up its context menu, and select Add Property. In the Add Property Wizard dialog, set a property name of AccountName, a property type of BSTR, and uncheck the Put Function to make the property read-only. Figure 7-9 shows how the dialog should look when you are done. Click on OK to add this property to the OLE DB Service Provider ATL project.
Repeat the above process to add another property, named BackupAccount, also of type BSTR and read-only. Figure 7-10 shows how the Add Property Wizard should look when you are done. Click on OK to add this property, and save the project to protect your work. Figure 7-10 Setting the BackupAccount property in the Property Wizard dialog
392
n Chapter Seven—Creating OLE DB Service Providers with ATL Repeat the above process to add another property, named AccountNumber, of type long and read-only. Figure 7-11 shows how the Add Property Wizard should look when you are done. Click on OK to add this property, and save the project.
Figure 7-11 Setting the AccountNumber property in the Property Wizard dialog
Repeat the above process to add another property, named AccountBalance, of type long and read-only. Figure 7-12 shows how the Add Property Wizard should look when you are done. Click on OK to add this property, and save the project.
Figure 7-12 Setting the AccountBalance property in the Property Wizard dialog
Chapter Seven—Creating OLE DB Service Providers with ATL n
393
Repeat the above process to add another property, named LookupKey, of type long and read-write (i.e., both Get and Put methods enabled). Figure 7-13 shows how the Add Property Wizard should look when you are done. Click on OK to add this property, and save the project to protect your work. Figure 7-13 Setting the Lookup key property in the Property Wizard dialog
Gentlemen, Start Your Keyboards! Now that you have created one OLE DB project, you should be more easily able to work with new ones. For this project, you’ll create a hand-coded OLE DB consumer using the listing below. In Chapter 8, you’ll learn how to do this from the OLE DB templates. For now, create a new blank C++ header file named CHECKINGACCOUNT.H. Copy the text in Listing 7-1 into it and save it. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21.
// CheckingAccount.H : Declaration of the CCheckingAccount class #ifndef __CHECKINGACCOUNT_H_ #define __CHECKINGACCOUNT_H_ class CCheckingAccountAccessor { public: TCHAR m_Account[14]; SHORT m_OwnerID; double m_Balance; TCHAR m_BackupAccount[14]; BEGIN_COLUMN_MAP(CCheckingAccountAccessor) COLUMN_ENTRY(1, m_Account) COLUMN_ENTRY(2, m_OwnerID) COLUMN_ENTRY(3, m_Balance) COLUMN_ENTRY(4, m_BackupAccount) END_COLUMN_MAP() DEFINE_COMMAND(CCheckingAccountAccessor, _T(“ \
394 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68.
n Chapter Seven—Creating OLE DB Service Providers with ATL SELECT \ Account, \ Owner_ID, \ Balance, \ Backup_Account \ FROM Checking_Account")) // You may wish to call this function if you wish to // initialize all the fields void ClearRecord() { memset(this, 0, sizeof(*this)); } }; class CCheckingAccount : public CCommand > { public: HRESULT Open() { HRESULT hr; hr = OpenDataSource(); if (FAILED(hr)) return hr; return OpenRowset(); } HRESULT OpenDataSource() { HRESULT hr; CDataSource db; CDBPropSet dbinit(DBPROPSET_DBINIT); dbinit.AddProperty( DBPROP_AUTH_PERSIST_SENSITIVE_AUTHINFO, false); dbinit.AddProperty(DBPROP_AUTH_USERID, OLESTR(“Admin”)); dbinit.AddProperty(DBPROP_INIT_DATASOURCE, OLESTR(“CheckingAccount”)); dbinit.AddProperty(DBPROP_INIT_MODE, (long)1); dbinit.AddProperty(DBPROP_INIT_PROMPT, (short)4); dbinit.AddProperty(DBPROP_INIT_LCID, (long)1033); hr = db.Open(_T(“MSDASQL”), &dbinit); if (FAILED(hr)) return hr;
Chapter Seven—Creating OLE DB Service Providers with ATL n 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79.
395
return m_session.Open(db); } HRESULT OpenRowset() { return CCommand >::Open(m_session); } CSession m_session; }; #endif // __CHECKINGACCOUNT_H_
Listing 7-1
CHECKINGACCOUNT.H source code after modifications
The following entries explain what this code does: Lines 6-12 This code defines the data record you’ll use to get information from the Access database. Lines 14-19 This code calls the OLE DB macros to create support code for the database record fields. Lines 21-27 This code defines the default command to get the entire recordset in one pass. Lines 37-76 This code defines the basic access methods for the class; it basically creates and opens a data source, and then opens a default rowset on it with all records. Next, we need to alter the header file for our server to create our OLE DB Consumer when needed. Locate the ACCOUNTINFO.H file in the text editor, and enter the grayed lines in Listing 7-2 and save the file. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17.
// AccountInfo.h : Declaration of the CAccountInfo #ifndef __ACCOUNTINFO_H_ #define __ACCOUNTINFO_H_ #include “resource.h” // main symbols //@@@ bring in OLE DB Consumer Class @@@ #include “CheckingAccount.H” ////////////////////////////////////////////////////////// // CAccountInfo class ATL_NO_VTABLE CAccountInfo : public CComObjectRootEx, public CComCoClass, public ISupportErrorInfo, public IDispatchImpl {
396 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61.
n Chapter Seven—Creating OLE DB Service Providers with ATL public: CAccountInfo() { } DECLARE_REGISTRY_RESOURCEID(IDR_ACCOUNTINFO) DECLARE_PROTECT_FINAL_CONSTRUCT() BEGIN_COM_MAP(CAccountInfo) COM_INTERFACE_ENTRY(IAccountInfo) COM_INTERFACE_ENTRY(IDispatch) COM_INTERFACE_ENTRY(ISupportErrorInfo) END_COM_MAP() // ISupportsErrorInfo STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid); //@@@ put in private data section for our member vars @@@ private: //@@@ hold the OLE DB Consumer class @@@ CCheckingAccount* m_pCheckingAccount; //@@@ hold the account name @@@ CComBSTR m_ccbstrAccountName; //@@@ hold the backup account @@@ CComBSTR m_ccbstrBackupAccount; //@@@ hold the account number @@@ long m_lAccountNumber; //@@@ hold the account balance @@@ long m_lAccountBalance; //@@@ hold the lookup key @@@ long m_lLookupKey; // IAccountInfo public: STDMETHOD(get_LookupKey)(/*[out, retval]*/ long *pVal); STDMETHOD(put_LookupKey)(/*[in]*/ long newVal); STDMETHOD(get_AccountBalance)(/*[out, retval]*/ long *pVal); STDMETHOD(get_AccountNumber)(/*[out, retval]*/ long *pVal); STDMETHOD(get_BackupAccount)(/*[out, retval]*/ BSTR *pVal); STDMETHOD(get_AccountName)(/*[out, retval]*/ BSTR *pVal); STDMETHOD(CloseAccounts)(); STDMETHOD(OpenAccounts)(); }; #endif //__ACCOUNTINFO_H_
Listing 7-2
ACCOUNTINFO.H source code after modifications
Chapter Seven—Creating OLE DB Service Providers with ATL n
397
The following entries explain what the new code lines do: n
Lines 7-8 This code brings in the CHECKINGACCOUNT.H header file so we can use its class.
n
Lines 35-48 This code defines our member variables for the class to hold property data and the Consumer class.
Finally, we need to add in the implementations of the properties and methods. Locate the ACCOUNTINFO.CPP file in the text editor. Enter the grayed lines in Listing 7-3 and save the file. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37.
// AccountInfo.cpp : Implementation of CAccountInfo #include “stdafx.h” #include “Atl3c.h” #include “AccountInfo.h” /////////////////////////////////////////////////////////// // CAccountInfo STDMETHODIMP CAccountInfo::InterfaceSupportsErrorInfo(REFIID riid) { static const IID* arr[] = { &IID_IAccountInfo }; for (int i=0; i < sizeof(arr) / sizeof(arr[0]); i++) { if (InlineIsEqualGUID(*arr[i],riid)) return S_OK; } return S_FALSE; } STDMETHODIMP CAccountInfo::OpenAccounts() { AFX_MANAGE_STATE(AfxGetStaticModuleState()) // TODO: Add your implementation code here //@@@ create OLE DB Consumer class @@@ m_pCheckingAccount = new CCheckingAccount; //@@@ open it to get recordset @@@ m_pCheckingAccount->Open(); //@@@ look along for record with matching key @@@ while (m_pCheckingAccount->m_OwnerID != m_lLookupKey) m_pCheckingAccount->MoveNext(); //@@@ for this demo we always assume we find it @@@ m_ccbstrAccountName = m_pCheckingAccount->m_Account; //@@@ get the data from the Access db into our member vars @@@
n Chapter Seven—Creating OLE DB Service Providers with ATL
398 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84.
m_ccbstrBackupAccount = m_pCheckingAccount->m_BackupAccount; m_lAccountBalance = m_pCheckingAccount->m_Balance; m_lAccountNumber = m_pCheckingAccount->m_OwnerID; //@@@ say we’re good to go @@@ return S_OK; } STDMETHODIMP CAccountInfo::CloseAccounts() { AFX_MANAGE_STATE(AfxGetStaticModuleState()) // TODO: Add your implementation code here //@@@ close the accounts db @@@ m_pCheckingAccount->Close(); //@@@ remove the class from memory @@ delete m_pCheckingAccount; //@@@ signal OK @@@ return S_OK; } STDMETHODIMP CAccountInfo::get_AccountName(BSTR *pVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) // TODO: Add your implementation code here //@@@ return the member variable data @@@ *pVal = m_ccbstrAccountName.Copy(); return S_OK; } STDMETHODIMP CAccountInfo::get_BackupAccount(BSTR *pVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) // TODO: Add your implementation code here //@@@ return the member variable data @@@ *pVal = m_ccbstrBackupAccount.Copy(); return S_OK; } STDMETHODIMP CAccountInfo::get_AccountNumber(long *pVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) // TODO: Add your implementation code here //@@@ return the member variable data @@@ *pVal = m_lAccountNumber;
Chapter Seven—Creating OLE DB Service Providers with ATL n 85. 86. 87. 88. 89. 90. 91. 92. 93. 94. 95. 96. 97. 98. 99. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116.
399
return S_OK; } STDMETHODIMP CAccountInfo::get_AccountBalance(long *pVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) // TODO: Add your implementation code here //@@@ return the member variable data @@@ *pVal = m_lAccountBalance; return S_OK; } STDMETHODIMP CAccountInfo::get_LookupKey(long *pVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) // TODO: Add your implementation code here //@@@ return the member variable data @@@ *pVal = m_lLookupKey; return S_OK; } STDMETHODIMP CAccountInfo::put_LookupKey(long newVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) // TODO: Add your implementation code here //@@@ set the member variable data @@@ m_lLookupKey = newVal; return S_OK; }
Listing 7-3
ACCOUNTINFO.CPP source code after modifications
The following entries explain what the new code lines do: n
Lines 28-41 These lines provide the core functionality of the server. Lines 28 and 29 create the OLE DB Consumer class (notice that it is not a COM server, just a C++ class). Lines 30 and 31 open it and obtain a complete recordset (see CHECKINGACCOUNT.H for the implementation). Lines 32-34 cover searching for the input LookupKey property (note that we assume it is always set before this method is called) in the Accounts database in the Owner_ID field; it is assumed that the match is always made (but a robust server would implement error handling here). Lines 35-41 set all our COM properties to the values of that particular record.
400
n Chapter Seven—Creating OLE DB Service Providers with ATL n
Lines 50-54 These lines provide for closing the OLE DB consumer class and makes sure the next call to the database will work properly.
n
Lines 63-64 This code sets the AccountName property to the member variable when called. It assumes the database has been opened and the property member variables set already.
n
Lines 73-74 This code sets the BackupAccount property to the member variable when called. It assumes the database has been opened and the property member variables set already.
n
Lines 83-84 This code sets the AccountNumber property to the member variable when called. It assumes the database has been opened and the property member variables set already.
n
Lines 93-94 This code sets the AccountBalance property to the member variable when called. It assumes the database has been opened and the property member variables set already.
n
Lines 103-104 This code sets the LookupKey property to the member variable when called.
n
Lines 113-114 This code sets the LookupKey member variable when called. It is required prior to opening the database or inconsistent behavior will result.
Building the OLE DB Service Provider Consumer DLL Once you are satisfied you’ve entered the code from the above listings correctly, build the OLE DB COM server from the Build menu. If you encounter any errors, please check the code in the listing to find your mistake (it has and destroying its instance in memory. This both prevents a memory leak been exhaustively debugged and is correct). Once you have compiled the component successfully it is automatically registered on the local machine; if you need to move it to another machine the directions are in Chapter 10. Otherwise, you’re ready to modify the OLE DB provider project in ATL as the middle tier of the application.
Creating an OLE DB Service Provider COM DLL Provider with ATL At this point you may be thinking, “Wait a minute, he’s got it backwards!” But no, OLE DB service providers don’t follow the normal rules. This particular one reverses the normal arrangement to simulate sending data over a network via DCOM so that the receiver can use a remote database. It will obtain the data from the OLE DB consumer you just created and provide it in the form of a standard OLE DB provider record (each recordset will have only one record for this provider).
Chapter Seven—Creating OLE DB Service Providers with ATL n
401
Warning
You must have Visual C++ 6.0. to create this and the other projects in the book; Visual C++ 5.0 does not support the OLE DB system in the same way.
Gentlemen, Start Your Keyboards! Rather than go through all the grunt work of re-creating another OLE DB provider, let’s reuse the URLProv sample from Chapter 6. (Copy it from the companion CD, or use the modified one from the Ch07 folder if you like.) Open it in the Visual C++ IDE and go to the URLPROVIDERRS.H file. Listing 7-4 gives you the relevant section of the source code from the file; change all the grayed areas to match those in the listing. Save the file. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33.
public: //@@@ hold the bookmark @@@ DWORD dwBookmark; //@@@ command string @@@ TCHAR szAccountName[256]; //@@@ text string @@@ TCHAR szBackupAccount[256]; //@@@ command string 2 @@@ TCHAR szAccountNumber[256]; //@@@ text 2 @@@ TCHAR szAccountBalance[256]; //@@@ override getcolumninfo for rowsets @@@ static ATLCOLUMNINFO* GetColumnInfo(CURLProviderRowset* pThis, ULONG* pcCols); //@@@ override getcolumninfo for commands @@@ static ATLCOLUMNINFO* GetColumnInfo(CURLProviderCommand* pThis, ULONG* pcCols); //@@@ note change in base template class @@@ class CURLProviderRowset : public CURLProviderRowsetImpl { public: //@@@ We must implement this because we’re using bookmarks @@@ virtual DBSTATUS GetDBStatus(CSimpleRow* , HACCESSOR) { //@@@ we don’t have a status for the database @@@ return DBSTATUS_S_ISNULL; } HRESULT Execute(DBPARAMS * pParams, LONG* pcRowsAffected) { //@@@ macros @@@ USES_CONVERSION; //@@@ critical section @@@
402 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80.
n Chapter Seven—Creating OLE DB Service Providers with ATL ObjectLock lock(this); //@@@ if there is no command text @@@ if (m_strCommandText == (BSTR)NULL) { //@@@ signal error @@@ return E_FAIL; } //@@@ create our server using standard COM techniques @@@ IAccountInfo* pAccountInfo = NULL; //@@@ get the IP via CCI @@@ HRESULT hr = CoCreateInstance(CLSID_AccountInfo, NULL,CLSCTX_ALL, IID_IUnknown, (void**)&pAccountInfo); //@@@ if no good blow up @@@ if (!SUCCEEDED(hr)) return E_FAIL; //@@@ now we need the command string as a long @@@ long lholder = atol(OLE2T(m_strCommandText)); //@@@ ESSENTIAL!!!! if we don’t do this first blows up!!!@@@ pAccountInfo->put_LookupKey( lholder ); //@@@ open the accounts database and get info via server @@@ pAccountInfo->OpenAccounts(); //@@@ define our string holder @@@ TCHAR szString[256]; //@@@ define our BSTR holder @@@ CComBSTR ccbstrHolder; //@@@ our database record @@@ CURLProviderWindowsFile ur; //@@@ get the BSTR from the server @@@ pAccountInfo->get_AccountName( &ccbstrHolder.m_str ); //@@@ put this into our record via macro @@@ _tcscpy(ur.szAccountName, OLE2T( ccbstrHolder.Copy())); //@@@ release the pointer to avoid memory leak @@@ ccbstrHolder.Empty(); //@@@ get other string from server pAccountInfo->get_BackupAccount( &ccbstrHolder.m_str ); //@@@ put this into record via macro @@@ _tcscpy(ur.szBackupAccount, OLE2T( ccbstrHolder.Copy())); //@@@ get first number via long holder @@ pAccountInfo->get_AccountNumber( &lholder ); //@@@ copy it into our string via fn @@@ wsprintf(szString,"%ld",lholder); //@@@ then copy into record @@@ _tcscpy(ur.szAccountNumber, szString); //@@@ get other number @@@ pAccountInfo->get_AccountBalance( &lholder ); //@@@ put into our string @@@ wsprintf(szString,"%ld",lholder);
Chapter Seven—Creating OLE DB Service Providers with ATL n 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93. 94. 95. 96. 97. 98.
403
//@@@ then put into database record @@@ _tcscpy(ur.szAccountBalance, szString); //@@@ try to add our only record @@@ if (!m_rgRowData.Add(ur)) { //@@@ otherwise abort with error @@@ return E_FAIL; } //@@@ we always have only one record @@@ if (pcRowsAffected != NULL) *pcRowsAffected = 1; //@@@ do this to close the DB for next time @@@ pAccountInfo->CloseAccounts(); //@@@ release pointer for memory reclamation pAccountInfo->Release(); //@@@ say we are good to go @@@ return S_OK; }
Listing 7-4
URLPROVIDERRS.H source code after modifications
The following entries explain what the new code lines do: n
Lines 4-11 This code redefines the four data elements to match those from the COM server and Accounts Access database.
n
Lines 31-49 This code brings in the third-tier component; we’ll just include it and call the local server, but a real application would include it via DCOM.
n
Lines 50-54 This code sets up the data from the input string and calls the third-tier server to open its database and set its properties.
n
Lines 55-97 This code sets up the record values for the provider’s data (note only one record is ever created) from the properties of the COM server. The intricacy of the code is due in large part to converting from OLE data types to standard C++ ones.
Building the OLE DB Provider DLL Once you are satisfied you’ve entered the code from the above listings correctly, build the OLE DB COM server from the Build menu. If you encounter any errors, please check the code in the listing to find your mistake (it has been exhaustively debugged and is correct). Once you have compiled the component successfully it is automatically registered on the local machine; if you need to move it to another machine, the directions are in Chapter 10. Otherwise, you’re ready to create the MFC executable, which will be the top tier of the application.
404
n Chapter Seven—Creating OLE DB Service Providers with ATL
Creating an OLE DB Service Provider Consumer Application with MFC Since we are not going into true OLE DB consumers until the next chapter, this application will simply hard-code its consumer to the OLE DB provider we created in the previous step. This will allow us to get to the meat of the system without worrying about unnecessary complexity!
Step-By-Step Start Visual C++ 6.0. From the File menu, select New. The New dialog box opens; click on the MFC AppWizard (exe), select an appropriate directory, then enter a project name of mfc1e. Figure 7-14 shows how the New dialog should appear when you are done.
Figure 7-14 The New dialog box creating the mfc1 MFC project
In the first page of the MFC AppWizard dialog, make sure the Dialog based radio button is selected, as indicated in Figure 7-15. Click on the Next button to move to the next page of the wizard.
Chapter Seven—Creating OLE DB Service Providers with ATL n
405
Figure 7-15 Setting the MFC application project basic properties
On the second page of the MFC AppWizard, there are a number of options that control the behavior of the application; leave everything at the default except to make sure that Automation support is checked. Figure 7-16 illustrates how the dialog should appear when you are done. Click on the Next button to move to the next page.
Figure 7-16 Setting the MFC application project COM properties
On the third page of the MFC AppWizard, select the only available option for the user interface (MFC Standard), and enable source file comments and use of MFC as a shared DLL (to save executable size). Figure 7-17 illustrates how the dialog should appear when you are done. Click on the Next button to move to the next page of the AppWizard.
406
n Chapter Seven—Creating OLE DB Service Providers with ATL
Figure 7-17 Setting the MFC application project user interface properties
On the fourth page of the MFC AppWizard, you see the results of the choices you made in the previous dialogs. Figure 7-18 illustrates how the dialog should appear when you are done. Click on the Finish button to create the MFC application.
Figure 7-18 Confirming the MFC application project user interface properties
A confirmation dialog box appears as shown in Figure 7-19; press OK to create the MFC project.
Chapter Seven—Creating OLE DB Service Providers with ATL n
407
Figure 7-19 The confirmation dialog
Back in the Visual C++ IDE, bring up the workspace viewer, and select the ResourceView tab. Expand the Dialogs entry and double-click on its only element, which is the main dialog resource for the application. Remove the pre-existing static text and button controls. Using Figure 7-20 as your guide, lay out the frame and edit controls and the button control as shown on the figure, accepting the default ID values generated by Visual C++. Save the project to protect your work.
Figure 7-20 The main dialog display of the MFC application in the Resource Editor
408
n Chapter Seven—Creating OLE DB Service Providers with ATL
As the next user interface step you need to set up a member variable to facilitate interaction with the top edit control. Hold down the Ctrl key and double-click on the edit control labeled Lookup Key. The Add Member Variable Wizard dialog appears as shown in Figure 7-21. Enter m_LookupKey as the name of the member variable, set its category to Value and its type to long, and click Figure 7-21 on the OK button. This will connect Adding a the user interface of the edit control member variwith a simple member variable, able control which is much easier to interact for an edit with. control in MFC
As the next user interface step you need to set up a member variable to facilitate interaction with the next edit control. Hold down the Ctrl key and double-click on the edit control labeled Account Name. The Add Member Variable Wizard dialog appears as shown in Figure 7-22. Enter m_AccountName as the name of the member variable, set its category to Value and its type to Figure 7-22 CString, and click on the OK button. Adding a This will connect the user interface member variof the edit control with a simple able control member variable, which is much for an edit easier to interact with. control in MFC
Set up a member variable to facilitate interaction with the next edit control. Hold down the Ctrl key and double-click on the edit control labeled Account Balance. The Add Member Variable Wizard dialog appears. Enter m_AccountBalance as the name of the member variable, set its category to Value and its type to long, and click on the OK button. Set up a member variable to facilitate interaction with the next edit control by holding down the Ctrl key and double-clicking on the edit control labeled Acount Number. The Add Member Variable Wizard dialog appears. Enter m_AccountNumber as the name of the member variable, set its category to Value and its type to long, and click on the OK button. Set up the final member variable to facilitate interaction with the bottom edit control by holding down the Ctrl key and double-clicking on the edit control labeled Backup Account. The Add Member Variable Wizard dialog appears.
Chapter Seven—Creating OLE DB Service Providers with ATL n
409
Enter m_BackupAccount as the name of the member variable, set its category to Value and its type to CString, and click on the OK button. Finally, you need to create an event handler for dealing with user clicks on the button control. To save time, simply hold down the Ctrl key and double-click on the button control. MFC will automatically create a default event handler for you and place you in the text editor ready to enter code. Save the project at this point.
Line-By-Line Now you are ready to enter the small amount of source code needed to connect the user interface with the OLE DB provider you created in ATL earlier. Bring up the MFC1EDLG.H file in the text editor. Scan through it and enter or change any grayed line of source code in Listing 7-5. Save the project. The section after the code explains what these changes do. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31.
// mfc1eDlg.h : header file // #if !defined( AFX_MFC1EDLG_H__60517009_3F5B_11D3_96F0_DE8523A96746__INCLUDED_) #define AFX_MFC1EDLG_H__60517009_3F5B_11D3_96F0_DE8523A96746__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 //@@@ ESSENTIAL!!! MFC will expand the macros incorrectly!!! @@@ #include //@@@ define our handler class @@@ class CProvider { public: //@@@ put in the bookmark field @@@ CBookmark<4> bookmark; //@@@ put in the four text fields @@@ TCHAR szAccountName[256]; TCHAR szBackupAccount[256]; TCHAR szAccountNumber[256]; TCHAR szAccountBalance[256]; //@@@ put in the database map @@@ BEGIN_COLUMN_MAP(CProvider) //@@@ this is a bookmark @@@ BOOKMARK_ENTRY( bookmark ); //@@@ these are string fields @@@ COLUMN_ENTRY(1, szAccountName ); COLUMN_ENTRY(2, szBackupAccount );
410 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78.
n Chapter Seven—Creating OLE DB Service Providers with ATL COLUMN_ENTRY(3, szAccountNumber ); COLUMN_ENTRY(4, szAccountBalance ); END_COLUMN_MAP() }; class CMfc1eDlgAutoProxy; ////////////////////////////////////////////////////////////// // CMfc1eDlg dialog class CMfc1eDlg : public CDialog { DECLARE_DYNAMIC(CMfc1eDlg); friend class CMfc1eDlgAutoProxy; // Construction public: CMfc1eDlg(CWnd* pParent = NULL); // standard constructor virtual ~CMfc1eDlg(); // Dialog Data //{{AFX_DATA(CMfc1eDlg) enum { IDD = IDD_MFC1E_DIALOG }; long m_LookupKey; CString m_AccountName; long m_AccountBalance; long m_AccountNumber; CString m_BackupAccount; //}}AFX_DATA // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CMfc1eDlg) protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support //}}AFX_VIRTUAL // Implementation protected: CMfc1eDlgAutoProxy* m_pAutoProxy; HICON m_hIcon; BOOL CanExit(); // Generated message map functions //{{AFX_MSG(CMfc1eDlg) virtual BOOL OnInitDialog();
Chapter Seven—Creating OLE DB Service Providers with ATL n 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93.
411
afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); afx_msg void OnClose(); virtual void OnOK(); virtual void OnCancel(); afx_msg void OnButton1(); //}}AFX_MSG DECLARE_MESSAGE_MAP() }; //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional declarations #endif // !defined (AFX_MFC1EDLG_H__60517009_3F5B_11D3_96F0_DE8523A96746__INCLUDED_)
Listing 7-5
MFC1EDLG.H source code after modifications n
Lines 12-13 This code makes sure that the ATL OLE DB templates are imported from their header file. Without this include file, MFC will interpret the macros using its definitions, which won’t work!
n
Lines 14-24 This code defines the custom class we need to send to other OLE DB templates to interact with our OLE DB provider; it is a “stub” definition of the provider’s user record functionality. The code defines the actual variables that will hold the information from the OLE DB provider.
n
Lines 25-34 These macros are used to actually create the database functionality for our stub provider. Notice that they are somewhat different from the macros we used in the provider.
With the database record defined, you now need to actually connect to the database and get information from it into the edit controls. You do this in the event handler for the button control’s BN_CLICKED message. Bring up the MFC1EDLG.CPP file in the text editor, since this is where MFC put the code for the event handler when it created it. Scan through the listing and enter or change any grayed line of source code in Listing 7-6. Save the project. The section after the code explains what these changes do. 1. 2. 3. 4. 5. 6. 7. 8. 9.
// mfc1eDlg.cpp : implementation file // #include “stdafx.h” #include “mfc1e.h” #include “mfc1eDlg.h” #include “DlgProxy.h” //@@@ put this in to be sure @@@ #include
412 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56.
n Chapter Seven—Creating OLE DB Service Providers with ATL #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif /////////////////////////////////////////////////////////////// // CMfc1eDlg dialog IMPLEMENT_DYNAMIC(CMfc1eDlg, CDialog); CMfc1eDlg::CMfc1eDlg(CWnd* pParent /*=NULL*/) : CDialog(CMfc1eDlg::IDD, pParent) { //{{AFX_DATA_INIT(CMfc1eDlg) m_LookupKey = 0; m_AccountName = _T(“”); m_AccountBalance = 0; m_AccountNumber = 0; m_BackupAccount = _T(“”); //}}AFX_DATA_INIT // Note that LoadIcon does not require a DestroyIcon in Win32 m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); m_pAutoProxy = NULL; } CMfc1eDlg::~CMfc1eDlg() { // If there is an automation proxy for this dialog, set // its back pointer to this dialog to NULL, so it knows // the dialog has been deleted. if (m_pAutoProxy != NULL) m_pAutoProxy->m_pDialog = NULL; } void CMfc1eDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CMfc1eDlg) DDX_Text(pDX, IDC_EDIT1, m_LookupKey); DDX_Text(pDX, IDC_EDIT2, m_AccountName); DDX_Text(pDX, IDC_EDIT4, m_AccountBalance); DDX_Text(pDX, IDC_EDIT3, m_AccountNumber); DDX_Text(pDX, IDC_EDIT5, m_BackupAccount); //}}AFX_DATA_MAP }
Chapter Seven—Creating OLE DB Service Providers with ATL n 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93. 94. 95. 96. 97. 98. 99. 100. 101. 102. 103.
BEGIN_MESSAGE_MAP(CMfc1eDlg, CDialog) //{{AFX_MSG_MAP(CMfc1eDlg) ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_WM_CLOSE() ON_BN_CLICKED(IDC_BUTTON1, OnButton1) //}}AFX_MSG_MAP END_MESSAGE_MAP() //////////////////////////////////////////////////////////// // CMfc1eDlg message handlers BOOL CMfc1eDlg::OnInitDialog() { CDialog::OnInitDialog(); // Set the icon. The framework does this automatically // when the application’s main window is not a dialog SetIcon(m_hIcon, TRUE); // Set big icon SetIcon(m_hIcon, FALSE); // Set small icon // TODO: Add extra initialization here return TRUE; // return TRUE unless you set the focus to a control } // If you add a minimize button, you will need the code below // to draw the icon.For MFC applications using the doc/view model, // this is automatically done for you by the framework. void CMfc1eDlg::OnPaint() { if (IsIconic()) { CPaintDC dc(this); // device context for painting SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0); // Center icon in client rectangle int cxIcon = GetSystemMetrics(SM_CXICON); int cyIcon = GetSystemMetrics(SM_CYICON); CRect rect; GetClientRect(&rect); int x = (rect.Width() - cxIcon + 1) / 2; int y = (rect.Height() - cyIcon + 1) / 2; // Draw the icon
413
n Chapter Seven—Creating OLE DB Service Providers with ATL
414 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132. 133. 134. 135. 136. 137. 138. 139. 140. 141. 142. 143. 144. 145. 146. 147. 148. 149. 150.
dc.DrawIcon(x, y, m_hIcon); } else { CDialog::OnPaint(); } } // The system calls this to obtain the cursor while the user drags // the minimized window. HCURSOR CMfc1eDlg::OnQueryDragIcon() { return (HCURSOR) m_hIcon; } // Automation servers should not exit when a user closes the UI // if a controller still holds on to one of its objects. These // message handlers make sure that if the proxy is still in use, // then the UI is hidden but the dialog remains around if it // is dismissed. void CMfc1eDlg::OnClose() { if (CanExit()) CDialog::OnClose(); } void CMfc1eDlg::OnOK() { if (CanExit()) CDialog::OnOK(); } void CMfc1eDlg::OnCancel() { if (CanExit()) CDialog::OnCancel(); } BOOL CMfc1eDlg::CanExit() { // If the proxy object is still around, then the automation // controller is still holding on to this application. Leave // the dialog around, but hide its UI. if (m_pAutoProxy != NULL) { ShowWindow(SW_HIDE);
Chapter Seven—Creating OLE DB Service Providers with ATL n 151. return FALSE; 152. } 153. 154. return TRUE; 155. } 156. 157. void CMfc1eDlg::OnButton1() 158. { 159. // TODO: Add your control notification handler code here 160. //@@@ get the data from the edit control into the member var @@@ 161. UpdateData(true); 162. //@@@ put in template code to send string to the provider @@@ 163. CCommand > table; 164. //@@@ define our datasource variable @@@ 165. CDataSource source; 166. //@@@ define our session variable @@@ 167. CSession session; 168. //@@@ get the OLE DB provider via COM technique @@@ 169. if (source.Open(“URLProv.URLProvider.1",NULL,NULL,NULL,NULL) 170. !=S_OK) 171. { 172. //@@@ signal error to user @@@ 173. AfxMessageBox( “Can’t contact URLProv Server!” ); 174. //@@@ leave @@@ 175. return; 176. } 177. //@@@ open the data source object from our provider @@@ 178. if (session.Open( source ) != S_OK) 179. { 180. //@@@ signal error to user @@@ 181. AfxMessageBox( “Can’t open URLProv data source!” ); 182. //@@@ leave @@@ 183. return; 184. } 185. //@@@ create a property holder @@@ 186. CDBPropSet propset(DBPROPSET_ROWSET); 187. //@@@ tell it we support bookmarks @@@ 188. propset.AddProperty(DBPROP_IRowsetLocate, true); 189. //@@@ we need to send the lookup key as a string @@@ 190. TCHAR szHolder[256]; 191. //@@@ so put it into a TCHAR array @@@ 192. wsprintf( szHolder,"%ld",m_LookupKey); 193. //@@@ then shove it into a CString to be safe @@@ 194. CString strHolder(szHolder); 195. //@@@ try to open the database @@@ 196. if (table.Open(session, strHolder ,&propset) != S_OK) 197. {
415
416 198. 199. 200. 201. 202. 203. 204. 205. 206. 207. 208. 209. 210. 211. 212. }
n Chapter Seven—Creating OLE DB Service Providers with ATL //@@@ signal error to user @@@ AfxMessageBox( “Can’t open database!” ); //@@@ leave @@@ return; } //@@ must do this to get to first record or get garbage! @@@ table.MoveNext(); //@@@ get the four fields and put them into the member vars @@@ m_AccountName = table.szAccountName; m_BackupAccount = table.szBackupAccount; m_AccountNumber = atol( table.szAccountNumber ); m_AccountBalance = atol( table.szAccountBalance ); //@@@ put the member vars into the ui @@@ UpdateData(false);
Listing 7-6
MFC1EDLG.CPP source code after modifications n
Lines 8-9 This makes sure that MFC will use the ATL macros for our OLE DB templates.
n
Lines 160-161 This brings the value from the edit control into the member variable for the lookup key value so we can use it.
n
Lines 162-167 This segment defines a template-created Command object and a DataSource and Session object. As noted in Chapter 4, all interaction with OLE DB providers must be via a DataSource object, which is then used to create a Session object, which finally executes commands or get rowsets.
n
Lines 168-176 This code gets our basic DataSource object. It does so using the COM ProgID value for the OLE DB provider (which is in the RGS file of that project if you forget it). We test for a COM success, and if it doesn’t happen we signal the user with an informative error message so they can fix the problem. All this does, however, is make contact with the COM server that contains our OLE DB provider; we haven’t done any database work yet!
n
Lines 177-184 Next we have to get a Session object, and this code does that chore. Notice that it takes our source variable as a parameter, (so it has the right object to connect with) and returns a COM HRESULT error code, which we can check and then return a user-friendly error message if needed.
n
Lines 185-202 This is the most vital code, because it actually sends a command to the OLE DB provider. Up to this point we were using “canned” OLE DB functionality from our ATL templates. This is where our code gets tested! First an OLE DB Property object is created that informs the provider that we support bookmarks (lines 185-188; note that the IRowsetLocate interface is what turns on bookmarks). In lines 189-195 we
Chapter Seven—Creating OLE DB Service Providers with ATL n
417
turn our long m_LookupKey member variable into an appropriate string. Then in line 196 the big call is made: It sends this command string to the provider along via the Table object’s Open method. This call contains as parameters the Session object, a string with a path to a text file containing names and URL values that we’ll create shortly and place on the root in C: drive, and our OLE DB Property object that indicates bookmark support. This will be sent to the various routines we wrote or modified so that a Rowset is returned with all the strings stored in the text file in records consisting of two strings each. If a problem occurs, the COM error code is checked and an error message sent to the user. n
Lines 203-211 This is the code that gets all the records in our database and puts them into the edit controls via member variables. It is quite straightforward, using the MoveNext function of our Table object to move to the only record of the returned rowset. Conversion is then done to put the data into member variables, and the UpdateData function called to show them in the user interface.
Building the MFC OLE DB Consumer Application Once you are satisfied you’ve entered the code from the above listings correctly, build the OLE DB consumer test application from the Build menu. If you encounter any errors, please check the code in the listing to find your mistake (it has been exhaustively debugged and is correct). Now you are ready to test the consumer and service provider.
Testing the OLE DB Service Provider with the MFC Consumer Application Now you’re ready to test the whole shebang! Make sure the Accounts database is set up correctly, and that the two COM servers are registered properly. Then run MFC1E.EXE. When you see the dialog appear, enter a number from 1 to 7 in the Lookup Key edit control and click on the button. After a moment, as shown in Figure 7-23, a set of values will appear from the Accounts Access database, sent Figure 7-23 along via the COM The MFC OLE server OLE DB conDB consumer sumer and the OLE application displays the DB service provider. Access dataTry entering any of base from the the other numbers OLE DB serand the appropriate vice provider values will promptly show up.
418
n Chapter Seven—Creating OLE DB Service Providers with ATL
Where We Go From Here You’ve completed two OLE DB project suites now; you are ready to move up to the more sophisticated and powerful OLE DB consumer template system, which is the topic of the next chapter.
Chapter Eight
Creating and Using Simple OLE DB Consumers in ATL In Chapter 6, you learned how to create a simple OLE DB provider with ATL, and how to develop a simple MFC front-end consumer for it. In Chapter 7, you developed a powerful three-tier ATL provider and MFC consumer supported by an MFC client. In this chapter, you’ll learn how to take advantage of the template functionality of an ATL OLE DB consumer to create a simple but very powerful COM server that can function in a web page to connect to an Access database and drive the Microsoft Agent ActiveX control. When you’ve finished this chapter, you’ll be ready to move on to a powerful MDI (Multiple Document Interface) OLE DB consumer written in MFC.
OLE DB Consumer Templates in Action Unlike OLE DB provider templates, OLE DB consumer templates are designed to automate much of the process of writing the application. Their user interface (via the ATL Object Wizard) locates the OLE DB provider you wish to use and interrogates it for the information needed to create the code to instantiate an OLE DB consumer for it. Boilerplate methods are provided that connect to the OLE DB provider and obtain a default rowset, storing it in a member variable. This approach has two drawbacks: First, you may not want to obtain a rowset as soon as you connect, or you may want to send a command string when you do (as is the case in our application in this chapter); in either case, you’ll need to modify the automatically generated code to change the behavior. Second, an OLE DB consumer is not a COM interface, oddly enough! Instead, it is a group of C++ classes only; if you want to expose its functionality via COM, you must add an interface-based object (like a Simple Object) and expose methods and/or properties on it to let your clients interact with the OLE DB consumer.
419
420
n Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL In the project that follows, you’ll use both of these techniques to modify the default OLE DB consumer the wizard provides you. In the process, you’ll observe many of the basics needed to create a working, usable OLE DB consumer application that does a real job. You can then take the code and modify it as needed for your task, or use the same process to create your own custom component with OLE DB consumer functionality.
Creating an OLE DB Consumer COM Server with ATL You create an OLE DB consumer much in the same way you created OLE DB providers in the previous two chapters. The exceptions are that you need a working OLE DB provider with a database and registered ODBC connection already in place before you can create the consumer. The following directions will take you through the process of creating an Access database and registering it with the ODBC system as well as showing you how to create the actual OLE DB consumer application in Visual C++ 6.0 with ATL.
Creating an ODBC-Compliant Database with Access For this project, you will need a database application that creates ODBCcompliant databases and which has an ODBC driver installed on both the development and target machines. For the step-by-step section below, I used Microsoft Access, but any similar database application will work also. Note
You can skip this step if you wish by copying over the project files for Chapter 8 from the companion CD; they contain the completed Access database in an MDB file.
Step-By-Step Start Access from the Start Menu or a shortcut icon. From the File menu, select New. The dialog shown in Figure 8-1 appears. Select the Blank Database radio button and click on the OK button.
Figure 8-1 The new database dialog for Access for creating a blank database
Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL n
Figure 8-2 Browsing to the directory and naming the database file in Access 95
421
As shown in Figure 8-2, a file browser dialog appears. Use it to locate the directory where you will create the project files for the simple consumer (use the icon button for a new folder if needed). Enter a filename of commands.mdb, and click on the Create button.
After a moment, a blank database child window will appear inside the Access IDE. Maximize it and select the Tables tab as shown in Figure 8-3. Then click on the New button to start creating a table for the database.
Figure 8-3 The Tables tab of the Access 95 database IDE
The New Table dialog appears as shown in Figure 8-4. Select the Design View item from the right-hand list box and click on the OK button to start laying out the table.
Figure 8-4 Creating a new table in Access 95
422
n Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL
The Access Table Editor child window appears, similar to that shown in Figure 8-5. Enter a name of ID for the first column in the Field Name cell, then give it an entry of AutoNumber in the Data Type cell. Select the ID cell again and click on the small key icon in the toolbar; this makes the selected field the primary key for the database. In similar fashion, enter a value of Command in the second Field Name cell, with a default Data Type cell value of Text, and a value of Text in the third Field Name cell, also with a default Data Type cell value of Text. The Figure 8-5 editor should look very Creating three similar to Figure 8-5 when columns in you are finished. Access 95’s Table Editor
Figure 8-6 Naming the database table prior to saving
Figure 8-7 The Database Editor in Access 95 showing 10 entries
Now click on the Save icon or choose File|Save from the menu. The Save As dialog will appear as shown in Figure 8-6. Enter Instructions as the table name and click on the OK button to save the table and the database. From the menu, now select View|Database View. The IDE changes to show the columns you just designed, currently with no data. Notice that since you set it to a data type of AutoNumber, you cannot enter data in the ID cell; rather the application sets this value as you enter data in the other cells. Enter the values in Table 8-1 into the Command and Text cells in the order shown to create the data for the beginning
Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL n
423
database. When you are done, the editor should look very similar to the display in Figure 8-7. Save your work and close Access. Table 8-1 ID 1 2 3 4 5 6 7 8 9 10
Entries for the Instructions table of the COMMAND.MDB Access database Command Text Show Speak Welcome to OLE DB MoveTo 300,300 Speak I am being controlled from an OLE DB consumer Play Acknowledge MoveTo 300,600 GestureAt 300,300 Play Wave Speak Now it is your turn to command me Hide
Creating an ODBC Connection with Control Panel Now that you have created the database for our project, you must create an ODBC connection to it via the Control Panel. This will allow our OLE DB consumer to actually contact the database. Warning
If you have copied the database supplied with the CD for this book, you still must perform this step!
Step-By-Step Start the Control Panel from the Start menu or a shortcut icon. As shown in Figure 8-8, double-click on the ODBC Data Sources (32bit) icon to start the ODBC configuration applet.
Figure 8-8 The Control Panel showing the ODBC Data Sources icon
424
n Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL The ODBC Data Source Administrator dialog appears. Select the User DSN tab as shown in Figure 8-9. Click on the Add button to start adding the database for the project.
Figure 8-9 ODBC Data Source Administrator User DSN tab
The Create New Data Source dialog appears next; select the Microsoft Access Driver (*.mdb) entry (or the driver for the type of database file you created in the previous step if you didn’t use Access). Figure 8-10 shows how the dialog should appear at this point; click on Finish to move from the ODBC Administrator to the driver’s user interface. Figure 8-10 Selecting a driver for the database file
Figure 8-11 Naming the Access data source
The dialog that appears next depends entirely on the driver you selected for your database file. Figure 8-11 illustrates the one for Microsoft Access; if you used a different database, your dialog may be different. Regardless of the exact configuration, however, you will need to perform some version of the following steps. For Access, enter a Data Source Name of Command, and a Description of test database for OLE DB
Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL n
425
consumer. Leave the System Database None radio button selected since this is a user database. Then, click on the Select button.
Figure 8-12 Selecting the actual database file for the Access Data Source
In the Access driver, a file browser dialog appears next. Use it to locate the commands.mdb file you created in the previous section. Select it as shown in Figure 8-12 and click on OK.
Back in the Access setup dialog, you should see the filename you selected displayed inside the Database group box, as shown in Figure 8-13. Next, click on the Advanced button. Figure 8-13 The ODBC database setup dialog showing the selected database file
The Access Set Advanced Options dialog appears; enter a Login Name of Admin as shown in Figure 8-14. Click on OK to set this advanced option and return to the main setup dialog. Click on OK again to complete adding the new data source.
Figure 8-14 Setting the login name for the Access database
426
n Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL After a moment the ODBC Data Source Administrator dialog reappears. As illustrated by Figure 8-15, it should now display the Command data source with the driver you previously selected. Click on OK to complete adding the new ODBC data source for the OLE DB consumer project.
Figure 8-15 The ODBC Data Source Administrator showing the new Data Source
Creating an OLE DB Provider COM DLL with ATL One of the most common tasks performed in personal computing is reading data from text files; with OLE DB, this chore can be promoted into the powerful realm of database programming. The following sections take you through the user interface portions of creating an OLE DB provider project that reads a text file of e-mail addresses and URLs and provides them as an OLE DB rowset. It also supports bookmarks, including dynamic column information. As noted in Chapter 6, the project is presented in my well-received step-bystep system, then shows you the code you need to write and an analysis using the Before and After and Wizard’s Wand techniques. Warning
You must have Visual C++ 6.0 to create this and the other projects in the book; Visual C++ 5.0 does not support the OLE DB system in the same way.
Step-By-Step Start Visual C++ 6.0. From the File menu, select New. The New dialog box opens; click on ATL COM AppWizard and select an appropriate directory. Then enter a project name of ATLSimpleC. Figure 8-16 shows how the New dialog should appear when you are done.
Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL n
427
Figure 8-16 The New dialog box creating the ATLSimpleC ATL project
In the ATL COM AppWizard, select the DLL project, and make sure that the Support MFC, Merge Proxy/Stub Code, and Support MTS check boxes are unchecked since we don’t need those options; Figure 8-17 illustrates how the dialog should appear when you are done.
Figure 8-17 Setting the OLE DB COM server project properties
428
n Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL A confirmation dialog box appears as shown in Figure 8-18; press OK to create the ATL project.
Figure 8-18 The confirmation dialog
Figure 8-19 Selecting an OLE DB Consumer Object in the ATL Object Wizard
From the Insert menu, select New ATL Object. The ATL Object Wizard dialog appears, as shown in Figure 8-19. Select the Data Access entry in the left-hand list box, and the Consumer entry in the right-hand list box. Press Next to move to the OLE DB settings page of the wizard.
Figure 8-20 The initial Properties dialog
As illustrated by Figure 8-20, the initial Properties dialog has most of its user interface disabled, including the ability to complete it. This is because it requires selecting an OLE DB provider via the Select Datasource button shown in the figure. First make sure the Command radio button is selected in the Type group box, then click on the Select Datasource button.
Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL n
429
The Data Link Properties dialog appears; select the Provider tab as shown in Figure 8-21. Since we have configured an ODBC database, select Microsoft OLE DB Provider for ODBC Drivers as illustrated in the figure. Then click on the Next button, or select the Connection tab.
Figure 8-21 The Data Link Properties dialog Provider tab
Either action brings you to the Connection tab of the Data Link Properties dialog box. Select the Use data source name radio button, and select Command from the drop- down list. Enter a User name of Admin, and check the Blank password check box. From the bottom drop-down list, select the only entry since our database only has one catalog. Figure 8-22 shows how the dialog should look at this point. Now click on the Test Connection button. Figure 8-22 The Data Link Properties dialog Connection tab
Figure 8-23 The success dialog
If you have configured the ODBC data source correctly in the previous section and entered its name and login correctly in the previous step, you should see the success dialog shown in Figure 8-23. If not, please recheck the previous work until you find the problem, and repeat the steps in this section. Once you see the success dialog, move to the Advanced tab of the dialog and check the read/write check box to allow updates, deletions, and insertions to the database. Then click on OK to dismiss the dialog and provide the information to the ATL Object Wizard.
430
n Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL
Figure 8-24 The Select Database Table dialog for the OLE DB consumer ATL object
Figure 8-25 The ATL Object Wizard Properties dialog for the OLE DB consumer
Figure 8-26 The ATL Object Wizard
At this point you have exited the Data Link Properties dialog and are back in the OLE DB ATL Object Wizard. The next dialog you’ll see is the one shown in Figure 8-24, which should display the Instructions table from the Command database we created in the first section of this chapter. Select it as shown in the figure and click on the OK button. This returns you to the ATL Object Wizard Properties dialog. Now the user interface is active; enter Instructions as the Short Name; the other name edit controls fill in automatically. Select all three of the Change, Insert, and Delete check boxes to enable that functionality. When you are done, the dialog should look very similar to Figure 8-25. Click on OK to add the OLE DB consumer object to the project. Save the project to protect your work. Remember that earlier we mentioned one of the drawbacks to the OLE DB consumer in ATL is that it is not a COM object. To overcome this, we need to add a COM interface to the project. Start the process by selecting Insert|New ATL Object from the menu. The ATL Object Wizard appears as shown in Figure 8-26. Select the Simple Object icon of the Objects line of the wizard as illustrated, and click on the Next button.
Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL n
431
Select the Names tab of the Properties dialog that appears next. As illustrated by Figure 8-27, enter AgentController in the Short Name edit control; the other edit controls will fill in automatically. Now click on the Attributes tab of the dialog. Figure 8-27 The Names tab of the ATL Object Wizard
Figure 8-28 The Attributes tab of the ATL Object Wizard
On the Attributes tab, leave everything in its default position except for checking the Support ISupportErrorInfo check box as shown in Figure 8-28. Click on OK to dismiss the wizard and add this COM interface to the OLE DB consumer project. Save the project to protect your work.
To make the new COM interface useful, you need to add method functions to it. To do this, make the workspace visible if it isn’t already, and select the ClassView tab. Expand the classes tree until the IAgent- Controller entry becomes visible. Right-click on it to bring up its context menu, and select Add Method. The Add Method to Interface Wizard dialog appears as shown in Figure 8-29. Enter a Method Name of ExecuteCommands. Leave all the other settings unchanged and Figure 8-29 Adding a click on the OK button to add the method to the method to the COM interface. COM interface via the Method Wizard
432
n Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL Repeat the previous step, giving the new method a Method Name of AddCommand. Again repeat the process to add a new method, with a Method Name of DeleteCommand. Add a final method to the interface with a Method Name of ChangeCommand.
Figure 8-30 Adding a property to the COM interface via the Property Wizard
You may have noticed that none of these methods have parameters. Instead, we’ll send them their information via COM interface properties. Right-click on the IAgentController interface again in the workspace ClassView tab, and select the Add Property menu entry. The Add Property to Interface Wizard dialog appears as illustrated in Figure 8-30. Select short from the Property Type combo box, then enter a Property Name of CommandID. Leave the other elements unchanged as shown in the figure, and click on OK to add the COM property to the IAgentController interface. Repeat the above step to add another property to the COM interface, this time giving it a Property Type of BSTR and a Property Name of CommandName. Again repeat the process to add a final property to the COM interface; give it a Property Type of BSTR and a Property Name of CommandText. Save the project.
Figure 8-31 Using the OLE/COM Object Viewer to determine if the Microsoft Agent ActiveX control is installed
As a final user interface step for this project, you need to make sure that the Microsoft Agent ActiveX control is installed on your development computer and on the client computer for the project (if different). You can use the OLE/COM Object Viewer
Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL n
433
from the Tools menu to determine this; bring up the application, then expand the Controls item in the left-hand tree control. When it finishes opening, scroll down to the Microsoft controls group and check to see if Microsoft Agent 2.0 is present in the alphabetical list, as illustrated in Figure 8-31. If you find the control is already installed, your next step is to locate the Agentsvr.exe file (usually found in the C:\WINDOWS\ MSAGENT\directory). Make a note as to its exact path; you’ll need it later during code entry. Otherwise, navigate to the following URL: http:// msdn. microsoft.com/ workshop/media/ agent/agentdl.asp, as shown in Figure 8-32. Use the link shown to download the core Agent files and install them by running the downloaded executable on Figure 8-32 the development and, if Downloading appropriate, target the Microsoft machine(s). Once this is Agent Control done, locate Agentsvr.exe on from the the development computer Microsoft web and note its path as site explained above.
What the Wizard Wrought Since this is the first time we’ve created an OLE DB consumer project in ATL, let’s spend some time reviewing what the wizard created for us before we begin changing it. This will serve as a reference for later projects you may undertake in case you’re not sure what a given bit of code does. The ATL OLE DB Object Wizard creates only one source code file (plus some support files we won’t go into including the core COM DLL file, which is identical for practical purposes with the one discussed in Chapter 7) with automatically generated code to provide basic OLE DB consumer functionality. Listing 8-1 gives the source code for the INSTRUCTIONS.H file, which is the OLE DB consumer implementation based on the Instructions table of our Commands database. Other projects, of course, will give this file a different name based on the table name for their particular database.
434 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46.
n Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL // Instructions.H : Declaration of the CInstructions class #ifndef __INSTRUCTIONS_H_ #define __INSTRUCTIONS_H_ class CInstructionsAccessor { public: LONG m_ID; TCHAR m_Command[51]; TCHAR m_Text[51]; BEGIN_COLUMN_MAP(CInstructionsAccessor) COLUMN_ENTRY(1, m_ID) COLUMN_ENTRY(2, m_Command) COLUMN_ENTRY(3, m_Text) END_COLUMN_MAP() DEFINE_COMMAND(CInstructionsAccessor, _T(“ \ SELECT \ ID, \ Command, \ Text \ FROM Instructions")) // You may wish to call this function if you are inserting a record and wish to // initialize all the fields, if you are not going to explicitly set all of them. void ClearRecord() { memset(this, 0, sizeof(*this)); } }; class CInstructions : public CCommand > { public: HRESULT Open() { HRESULT hr; hr = OpenDataSource(); if (FAILED(hr)) return hr; return OpenRowset(); }
Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL n 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82.
435
HRESULT OpenDataSource() { HRESULT hr; CDataSource db; CDBPropSet dbinit(DBPROPSET_DBINIT); dbinit.AddProperty(DBPROP_AUTH_PASSWORD, OLESTR(“”)); dbinit.AddProperty(DBPROP_AUTH_PERSIST_SENSITIVE_AUTHINFO, false); dbinit.AddProperty(DBPROP_AUTH_USERID, OLESTR(“Admin”)); dbinit.AddProperty(DBPROP_INIT_DATASOURCE, OLESTR(“Command”)); dbinit.AddProperty(DBPROP_INIT_PROMPT, (short)4); dbinit.AddProperty(DBPROP_INIT_LCID, (long)1033); dbinit.AddProperty(DBPROP_INIT_CATALOG, OLESTR( “J:\\wordware\\Learn OLE DB Programming\\chap08\\projects\\ATLSimpleC\\commands” )); hr = db.Open(_T(“MSDASQL”), &dbinit); if (FAILED(hr)) return hr; return m_session.Open(db); } HRESULT OpenRowset() { // Set properties for open CDBPropSet propset(DBPROPSET_ROWSET); propset.AddProperty(DBPROP_IRowsetChange, true); propset.AddProperty(DBPROP_UPDATABILITY, DBPROPVAL_UP_CHANGE | DBPROPVAL_UP_INSERT | DBPROPVAL_UP_DELETE); return CCommand>::Open(m_session, NULL, &propset); } CSession m_session; }; #endif // __INSTRUCTIONS_H_
Listing 8-1
INSTRUCTIONS.H source code before modifications
The list below gives the important code blocks of the listing and explains what they do; particular attention is paid to macros and template definitions as appropriate: n
Lines 9-11 This code contains the three member variables that will hold the data from each record in the database; their names are automatically assigned and typed based on the schema for the database.
436
n Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL n
Lines 13-17 This code contains the COLUMN_MAP macro entries that define the three fields for the database record for the OLE DB consumer template.
n
Lines 19-24 This code contains the macro to define the SQL command used to obtain each record of the database.
n
Lines 26-32 This code contains a function to clear a record prior to manipulating it for insertion, which can be used instead of writing your own.
n
Lines 37-46 This code contains the default implementation of the Open method of the CInstructions class used to interact with the OLE DB provider. Notice that it automatically opens a DataSource object via a call to the OpenDataSource method, and if the call succeeds it then opens a default rowset via the m_session member variable and a call to the OpenRowset method. This behavior may need to be changed (and in fact we will change it) by many consumer applications.
n
Lines 47-67 This code contains the implementation of the OpenDataSource method of the CInstructions class. It creates local CDataSource and CDBPropSet variables and initializes them with information from the ODBC data source we entered in the wizard dialog. It then opens the DataSource object, and if that succeeds, initializes the m_session variable via its Open method to complete the connection with the database. Normally this code won’t be changed for applications.
n
Lines 68-77 This code contains the default implementation of the OpenRowset method of the CInstructions class. It adds bookmark, update, insert, and delete functionality to the object via changes to its properties, and opens a default rowset with all available data elements. The code in lines 76 and 77 is the most likely to be changed depending on what elements a default rowset needs for a given application.
n
Line 79 class.
This code contains the definition of the Session object for the
Customizing the OLE DB Consumer Now that you’ve got some familiarity with the wizard-generated code for a basic OLE DB consumer, it is time to change it to meet the needs of our specific consumer. The sections below give you before and after code samples, followed by explanations of what code to change and what the changes do. When you need to create your own OLE DB consumer, you can use these guidelines as a template for your own specific changes.
Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL n
437
Disabling Rowset Return on Database Open Listing 8-2 gives the source code from the INSTRUCTIONS.H file that implements the Open functionality for the CInstructions class. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14.
class CInstructions : public CCommand > { public: HRESULT Open() { HRESULT hr; hr = OpenDataSource(); if (FAILED(hr)) return hr; return OpenRowset(); }
Listing 8-2
INSTRUCTIONS.H source code before modifications
The directions below tell you what to remove and what to add to modify this code to meet our needs: n
Line 12
Replace this line with the grayed code in Listing 8-3.
Listing 8-3 gives the source code from the INSTRUCTIONS.H file that implements the Open method after your changes. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14.
class CInstructions : public CCommand > { public: HRESULT Open() { HRESULT hr; hr = OpenDataSource(); if (FAILED(hr)) return hr; return hr;//@@@ return OpenRowset(); disable open and return hr }
Listing 8-3
INSTRUCTIONS.H source code after modifications
438
n Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL The following entries explain what the new code lines do: n
Line 12 This code returns the HRESULT from OpenDataSource and disables the default opening of a rowset. This allows access to various Rowset objects based on more sophisticated interactions from the user interface.
Adding OLE DB Consumer and Microsoft Agent Support The remaining actions you need to take to complete the OLE DB consumer server are standard line-by-line modifications. Start by bringing up the AGENTCONTROLLER.H file in the text editor and entering the grayed code in Listing 8-4. Save the project. The grayed entries are explained in the section below. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33.
// Agentcontroller.h : Declaration of the CAgentcontroller #ifndef __AGENTCONTROLLER_H_ #define __AGENTCONTROLLER_H_ #include “resource.h” // main symbols //@@@ include CInstructions OLE DB Consumer @@@ #include “Instructions.h” //@@@ change these to match your MSAgent location @@@ #import “C:\Windows\MSAgent\agentsvr.exe” no_namespace #define AGENT_CHARACTER “C:\\Windows\\MSAgent\\Chars\\merlin.acs” ///////////////////////////////////////////////////////////////////////////// // CAgentcontroller class ATL_NO_VTABLE CAgentcontroller : public CComObjectRootEx, public CComCoClass, public ISupportErrorInfo, public IDispatchImpl { public: CAgentcontroller() { } HRESULT FinalConstruct(); void FinalRelease(); DECLARE_REGISTRY_RESOURCEID(IDR_AGENTCONTROLLER) DECLARE_PROTECT_FINAL_CONSTRUCT() BEGIN_COM_MAP(CAgentcontroller) COM_INTERFACE_ENTRY(IAgentcontroller)
Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL n 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70.
439
COM_INTERFACE_ENTRY(IDispatch) COM_INTERFACE_ENTRY(ISupportErrorInfo) END_COM_MAP() // ISupportsErrorInfo STDMETHOD(interfaceSupportsErrorInfo)(REFIID riid); // IAgentcontroller public: STDMETHOD(GetAllCommands)(/*[out]*/ BSTR* bstrCommandsString); STDMETHOD(get_CommandText)(/*[out, retval]*/ BSTR *pVal); STDMETHOD(put_CommandText)(/*[in]*/ BSTR newVal); STDMETHOD(get_CommandName)(/*[out, retval]*/ BSTR *pVal); STDMETHOD(put_CommandName)(/*[in]*/ BSTR newVal); STDMETHOD(get_CommandID)(/*[out, retval]*/ short *pVal); STDMETHOD(put_CommandID)(/*[in]*/ short newVal); STDMETHOD(ChangeCommand)(); STDMETHOD(DeleteCommand)(); STDMETHOD(AddCommand)(); STDMETHOD(ExecuteCommands)(); //@@@ hold the OLE DB Consumer @@@ CInstructions* instructions; //@@@ hold the id value @@@ short sID; //@@@ hold the command @@@ CComBSTR ccbstrCommand; //@@@ hold the text @@@ CComBSTR ccbstrText; //@@@ get agent pointer @@@ IAgentPtr m_pAgent; //@@@ get agent character pointer @@@ IAgentCharacterPtr m_pCharacter; //@@@ Store Agent ID @@@ long m_lRequestID; }; #endif //__AGENTCONTROLLER_H_
Listing 8-4
AGENTCONTROLLER.H source code after modifications
The following entries explain what the new code lines do: n
Lines 7-11 This code brings in the header file so we can declare a variable of the OLE DB consumer’s specific class, as well as the two Microsoft Agent files. Note that the Agent files may be in different places for your system; if so, you must modify the code as noted in the comment lines.
440
n Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL n
Lines 26-27 This code declares the two overridden methods for FinalConstruct and FinalRelease, so that we can use them to create the OLE DB Consumer class and Agent control in memory and later release them.
n
Lines 54-67 This code declares all the member variables we need for the COM server, including the one to hold the OLE DB Consumer class.
Adding the COM Server Code to Use the OLE DB Consumer Next bring up the AGENTCONTROLLER.CPP file in the text editor and enter the grayed code in Listing 8-5. Save the project. The grayed entries are explained in the section below.
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33.
// Agentcontroller.cpp : Implementation of CAgentcontroller #include “stdafx.h” #include “ATLSimpleC.h” #include “Agentcontroller.h” ///////////////////////////////////////////////////////////////////////////// // CAgentcontroller HRESULT CAgentcontroller::FinalConstruct() { //@@@ Define HRESULT holder @@@ HRESULT hr; //@@@ define a character id holder @@@ long lCharID; //@@@ define an IDispatch IP holder @@@ CComPtr pdCharacter; //@@@ get the MSAgent ActiveX control @@@ hr = m_pAgent.CreateInstance(__uuidof(Agentserver)); //@@@ inform user if fail @@@ if (!SUCCEEDED(hr)) { return Error(_T(“Unable To Contact Microsoft Agent control!”)); } //@@@ get the defined character’s ID values @@@ hr = m_pAgent->Load(AGENT_CHARACTER,&lCharID,&m_lRequestID); //@@@ inform user if fail @@@ if (!SUCCEEDED(hr)) { return Error(_T(“Unable To Contact Microsoft Agent control!”)); } //@@@ get the designated character @@@ hr = m_pAgent->GetCharacter(lCharID,&pdCharacter); //@@@ inform user if fail @@@
Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL n 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80.
if (!SUCCEEDED(hr)) { return Error(_T(“Unable To Contact Microsoft Agent control!”)); } //@@@ put IDispatch IP into our member variable @@@ m_pCharacter = pdCharacter; //@@@ create the instructions object @@@ instructions = new CInstructions; //@@@ open the database @@@ return instructions->Open(); } void CAgentcontroller::FinalRelease() { //@@@ delete the database if not null @@@ if (instructions) delete instructions; //@@@ release the two IP’s @@@ if (m_pCharacter) m_pCharacter.Release(); if (m_pAgent) m_pAgent.Release(); } STDMETHODIMP CAgentcontroller::interfaceSupportsErrorInfo(REFIID riid) { static const IID* arr[] = { &IID_IAgentcontroller }; for (int i=0; i < sizeof(arr) / sizeof(arr[0]); i++) { if (InlineIsEqualGUID(*arr[i],riid)) return S_OK; } return S_FALSE; } STDMETHODIMP CAgentcontroller::ExecuteCommands() { // TODO: Add your implementation code here //@@@ bring in conversion macros @@@ USES_CONVERSION; //@@@ define HRESULT holder @@@ HRESULT hr; //@@@ open the current rowset @@@ hr = instructions->OpenRowset(); //@@@ if fail return IDispatch Error @@@ if (!SUCCEEDED(hr)) {
441
442 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93. 94. 95. 96. 97. 98. 99. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127.
n Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL return Error(_T(“Unable to Open Rowset for Excuting Commands!”)); } //@@@ play instructions one at a time @@@ while (instructions->MoveNext() == S_OK) { //@@@ do if else cascade to check for supported commands @@@ if (lstrcmpi(instructions->m_Command,_T(“Speak”)) == 0) { //@@@ speak command @@@ m_pCharacter->Show(FALSE,&m_lRequestID); m_pCharacter->Speak( (BSTR)CComBSTR (instructions->m_Text), “”, &m_lRequestID); } //@@@ play command @@@ else if (lstrcmpi(instructions->m_Command, _T(“Play”)) == 0) m_pCharacter->Play( (BSTR)CComBSTR (instructions->m_Text), &m_lRequestID); //@@@ hide command @@@ else if (lstrcmpi(instructions->m_Command, _T(“Hide”)) == 0) m_pCharacter->Hide(FALSE, &m_lRequestID); //@@@ show command @@@ else if (lstrcmpi(instructions->m_Command, _T(“Show”)) == 0) m_pCharacter->Show(FALSE, &m_lRequestID); //@@@ moveto command @@@ else if (lstrcmpi(instructions->m_Command, _T(“MoveTo”)) == 0) { //@@@ define position variables @@@ short x, y; #ifdef _UNICODE //@@@ unicode scanf @@@ wscanf(instructions->m_Text, _T(“%d, %d”), &x, &y); #else //@@@ ansi scanf @@@ sscanf(instructions->m_Text, _T(“%d, %d”), &x, &y); #endif m_pCharacter->MoveTo(x, y, 50 , &m_lRequestID); } //@@@ gestureat command @@@ else if (lstrcmpi(instructions->m_Command, _T(“GestureAt”)) == 0) { //@@@ position variables @@@ short x, y; #ifdef _UNICODE //@@@ unicode scanf @@@ wscanf(instructions->m_Text, _T(“%d, %d”), &x, &y); #else //@@@ ansi scanf @@@ sscanf(instructions->m_Text, _T(“%d, %d”), &x, &y); #endif m_pCharacter->GestureAt(x, y, &m_lRequestID); } } //@@@ close the rowset @@@
Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL n 128. instructions->Close(); 129. //@@@ if we get here we’re okay @@@ 130. return S_OK; 131. } 132. 133. STDMETHODIMP CAgentcontroller::AddCommand() 134. { 135. // TODO: Add your implementation code here 136. //@@@ bring in conversion macros @@@ 137. USES_CONVERSION; 138. //@@@ define HRESULT holder @@@ 139. HRESULT hr; 140. //@@@ open the current rowset @@@ 141. hr = instructions->OpenRowset(); 142. //@@@ if fail return IDispatch Error @@@ 143. if (!SUCCEEDED(hr)) 144. { 145. return Error(_T(“Unable to Open Rowset for Adding Command!”)); 146. } 147. //@@@ insert a new record at the end @@@ 148. hr = instructions->Insert(); 149. //@@@ signal error @@@ 150. if (!SUCCEEDED(hr)) 151. { 152. return Error(_T(“Unable to Add Command!”)); 153. } 154. //@@@ move to the end @@@ 155. hr = instructions->MoveLast(); 156. //@@@ signal error @@@ 157. if (!SUCCEEDED(hr)) 158. { 159. return Error(_T(“Unable to locate new command in database!”)); 160. } 161. //@@@ set the command ID @@@ 162. instructions->m_ID = sID; 163. //@@@ set the command text @@@ 164. wsprintf( &instructions->m_Command[0], OLE2T( ccbstrCommand. Copy()) ); 165. //@@@ set the text @@@ 166. wsprintf( &instructions->m_Text[0], OLE2T( ccbstrText.Copy()) ); 167. //@@@ update the database @@@ 168. hr = instructions->Update(); 169. //@@@ signal error @@@ 170. if (!SUCCEEDED(hr)) 171. { 172. return Error(_T(“Unable to Update Rowset after Adding Command!”)); 173. } 174. //@@@ close the rowset @@@
443
444
n Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL
175. instructions->Close(); 176. //@@@ signal ok @@@ 177. return S_OK; 178. } 179. 180. STDMETHODIMP CAgentcontroller::DeleteCommand() 181. { 182. // TODO: Add your implementation code here 183. //@@@ bring in conversion macros @@@ 184. USES_CONVERSION; 185. //@@@ define HRESULT holder @@@ 186. HRESULT hr; 187. //@@@ open the current rowset @@@ 188. hr = instructions->OpenRowset(); 189. //@@@ if fail return IDispatch Error @@@ 190. if (!SUCCEEDED(hr)) 191. { 192. return Error(_T(“Unable to Open Rowset for Deleting Command!”)); 193. } 194. //@@@ define loop flag and set to false 195. BOOL bFlag = FALSE; 196. //@@@ loop while still false @@@ 197. while (!bFlag) 198. { 199. //@@@ if you match the id then done else move to next record @@@ 200. if (instructions->m_ID == sID) 201. { 202. bFlag = TRUE; 203. } 204. else 205. { 206. hr = instructions->MoveNext(); 207. } 208. //@@@ if you hit the end then bad id @@@ 209. if (!SUCCEEDED(hr)) 210. { 211. //@@@ signal error @@@ 212. return Error(_T(“Unable to locate Command ID in database!”)); 213. } 214. } 215. //@@@ otherwise fall out at right record and delete it @@@ 216. hr = instructions->Delete(); 217. //@@@ signal error @@@ 218. if (!SUCCEEDED(hr)) 219. { 220. return Error(_T(“Unable to Delete Command!”)); 221. }
Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL n 222. //@@@ update the database from the altered recordset @@@ 223. hr = instructions->Update(); 224. //@@@ signal error @@@ 225. if (!SUCCEEDED(hr)) 226. { 227. return Error(_T(“Unable to Update Rowset after Deleting Command!”)); 228. } 229. //@@@ close the recordset @@@ 230. instructions->Close(); 231. //@@@ if we make it to here we’re okay @@@ 232. return S_OK; 233. } 234. 235. STDMETHODIMP CAgentcontroller::ChangeCommand() 236. { 237. // TODO: Add your implementation code here 238. //@@@ bring in conversion macros @@@ 239. USES_CONVERSION; 240. //@@@ define HRESULT holder @@@ 241. HRESULT hr; 242. //@@@ open the current rowset @@@ 243. hr = instructions->OpenRowset(); 244. //@@@ if fail return IDispatch Error @@@ 245. if (!SUCCEEDED(hr)) 246. { 247. return Error(_T(“Unable to Open Rowset for Changing Command!”)); 248. } 249. //@@@ define loop flag and set to false 250. BOOL bFlag = FALSE; 251. //@@@ loop while still false @@@ 252. while (!bFlag) 253. { 254. //@@@ if you match the id then done else move to next record @@@ 255. if (instructions->m_ID == sID) 256. { 257. bFlag = TRUE; 258. } 259. else 260. { 261. hr = instructions->MoveNext(); 262. } 263. //@@@ if you hit the end then bad id @@@ 264. if (!SUCCEEDED(hr)) 265. { 266. //@@@ signal error @@@ 267. return Error(_T(“Unable to locate Command ID in database!”)); 268. }
445
n Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL
446 269. 270. 271. 272. 273. 274. 275. 276. 277. 278. 279. 280. 281. 282. 283. 284. 285. 286. 287. 288. 289. 290. 291. 292. 293. 294. 295. 296. 297. 298. 299. 300. 301. 302. 303. 304. 305. 306. 307. 308. 309. 310. 311. 312. 313. 314. 315.
} //@@@ set the command ID @@@ instructions->m_ID = sID; //@@@ set the command text @@@ wsprintf( &instructions->m_Command[0], OLE2T( ccbstrCommand.Copy()) ); //@@@ set the text @@@ wsprintf( &instructions->m_Text[0], OLE2T( ccbstrText.Copy()) ); //@@@ update the database @@@ hr = instructions->Update(); //@@@ signal error @@@ if (!SUCCEEDED(hr)) { return Error(_T(“Unable to Update Rowset after Changing Command!”)); } //@@@ close the rowset @@@ instructions->Close(); //@@@ signal ok @@@ return S_OK; } STDMETHODIMP CAgentcontroller::get_CommandID(short *pVal) { // TODO: Add your implementation code here //@@@ send back our member variable @@@ *pVal = sID; return S_OK; } STDMETHODIMP CAgentcontroller::put_CommandID(short newVal) { // TODO: Add your implementation code here //@@@ set our member variable @@@ sID = newVal; return S_OK; } STDMETHODIMP CAgentcontroller::get_CommandName(BSTR *pVal) { // TODO: Add your implementation code here //@@@ send back our member variable @@@ *pVal = ccbstrCommand.Copy(); return S_OK; } STDMETHODIMP CAgentcontroller::put_CommandName(BSTR newVal) { // TODO: Add your implementation code here
Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL n 316. 317. 318. 319. 320. 321. 322. 323. 324. 325. 326. 327. 328. 329. 330. 331. 332. 333. 334. 335. 336. 337. 338. 339. 340. 341. 342. 343. 344. 345. 346. 347. 348. 349. 350. 351. 352. 353. 354. 355. 356. 357. 358. 359. 360. 361. 362.
//@@@ set our member variable @@@ ccbstrCommand = newVal; return S_OK; } STDMETHODIMP CAgentcontroller::get_CommandText(BSTR *pVal) { // TODO: Add your implementation code here //@@@ send back our member variable @@@ *pVal = ccbstrText.Copy(); return S_OK; } STDMETHODIMP CAgentcontroller::put_CommandText(BSTR newVal) { // TODO: Add your implementation code here //@@@ set our member variable @@@ ccbstrText = newVal; return S_OK; } STDMETHODIMP CAgentcontroller::GetAllCommands(BSTR*bstrCommandsString) { // TODO: Add your implementation code here //@@@ bring in conversion macros @@@ USES_CONVERSION; //@@@ define working buffer @@@ CComBSTR ccbstrHolder(_T(“”)); //@@@ number holder @@@ TCHAR buf[51]; //@@@ define HRESULT holder @@@ HRESULT hr; //@@@ open the current rowset @@@ hr = instructions->OpenRowset(); //@@@ if fail return IDispatch Error @@@ if (!SUCCEEDED(hr)) { return Error(_T(“Unable to Open Rowset for Getting All Commands!”)); } //@@@ read instructions one at a time @@@ wsprintf( instructions->m_Text,""); while (instructions->MoveNext() == S_OK) { //@@@ put number into string buffer @@@ itoa( instructions->m_ID, &buf[0] , 10); //@@@ put it into the holding string @@@ ccbstrHolder += T2OLE( buf );
447
448 363. 364. 365. 366. 367. 368. 369. 370. 371. 372. 373. 374. 375. 376. 377. 378. 379. 380. 381. }
n Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL //@@@ add padding for display @@@ ccbstrHolder += _T(“ ”); //@@@ add command string @@@ ccbstrHolder += (BSTR)CComBSTR(instructions->m_Command); //@@@ add padding for display @@@ ccbstrHolder += _T(“ ”); //@@@ put in text string @@@ ccbstrHolder += (BSTR)CComBSTR(instructions->m_Text); //@@@ put in terminator @@@ ccbstrHolder += _T(“*”); wsprintf( instructions->m_Text,""); } //@@@ kill the rowset @@@ instructions->Close(); //@@@ send back copy of working string @@@ *bstrCommandsString = ccbstrHolder.Copy(); //@@@ if we get here we’re okay @@@ return S_OK;
Listing 8-5
AGENTCONTROLLER.CP source code after modifications
The following entries explain what the new code lines do: n
Lines 9-44 This code handles the chores of creating the CInstructions class, and of obtaining the working interface pointer to the Microsoft Agent control. Notice that it uses COM error handling if it encounters problems.
n
Lines 46-53 This code disposes of the three memory-using objects created in the FinalConstruct method if they contain valid pointers.
n
Lines 72-129 This code illustrates using the OLE DB consumer to read data from the database. First the Recordset is opened, then a loop is set up to read records until no more are found, generating a failure HRESULT (lines 83-84). Each successful read puts new data into the Recordset in the instructions variable; its member variables are checked in a nested if..else loop (lines 85-127). Each recognized directive is then sent to the Agent control, possibly with parsing for additional parameters (lines 105-113). Once all commands are read, the Recordset is closed to avoid errors later.
n
Lines 136-176 This code handles inserting a new command. First it opens a Rowset (lines 137-146), then it calls the Insert method (lines 147-153). Next it moves to the last record (lines 154-160), and then sets the data in that record from the member variables (lines 161-166). Finally, it updates the database (lines 167-173) and closes the Recordset (lines 174-175). All OLE DB consumer database operations follow a similar pattern, except for the specific operation performed.
Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL n
449
n
Lines 183-231 This code handles deleting a command. It uses the same techniques described above.
n
Lines 238-285 This code handles changing the data in a command. It uses the same techniques described above.
n
Lines 292-293, 308-309, 324-325 ables to the passed-in data.
This code sets our member vari-
n
Lines 300-301, 316-317, 332-333 parameter to member var data.
This code sets the passed-in
n
Lines 340-379 This code handles the important chore of getting all the data from the database and returning it to the caller as a single BSTR. Notice that the returned string is marked with * characters so it can be decoded later. Also notice in line 376 that the rowset is closed when we are done with it; if this is not done, ATLASSERT errors will happen in our client!
Building the OLE DB Consumer DLL Once you are satisfied you’ve entered the code from the above listings correctly, build the OLE DB COM server from the Build menu. If you encounter any errors, please check the code in the listing to find your mistake (it has been exhaustively debugged and is correct). Once you have compiled the component successfully it is automatically registered on the local machine; if you need to move it to another machine, the directions are in Chapter 10. Otherwise, you’re ready to create the client project in MFC to test the consumer.
Creating a Client Application for an OLE DB Consumer with MFC In previous chapters we implemented OLE DB consumers directly in MFC using imported ATL classes. For this project, we will examine how to use an ATL OLE DB consumer with an MFC client application. This involves treating the consumer as simply another COM server, connecting it with our user interface and calling its methods. This scenario is a very common one for ATL-based OLE DB consumer DLLs.
450
n Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL
Step-By-Step Start Visual C++ 6.0. From the File menu, select New. The New dialog box opens; click on the MFC AppWizard (exe) entry. Next, select an appropriate directory, then enter a project name of AgentPlayer. Figure 8-33 shows how the New dialog should appear when you are done.
Figure 8-33 The New dialog box creating the AgentPlayer MFC project
In the first page of the MFC AppWizard dialog, make sure the Dialog based radio button is selected, as indicated in Figure 8-34. Click on the Next button to move to the next page of the wizard.
Figure 8-34 Setting the MFC application project basic properties
Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL n
451
On the second page of the MFC AppWizard, there are a number of options that control the behavior of the application; leave everything at the default except to make sure that Automation support is checked. Figure 8-35 illustrates how the dialog should appear when you are done. Click on the Next button to move to the next page of the AppWizard.
Figure 8-35 Setting the MFC application project COM properties
On the third page of the MFC AppWizard, select the only available option for the user interface (MFC Standard), and enable source file comments and use of MFC as a shared DLL (to save executable size). Figure 8-36 illustrates how the dialog should appear when you are done. Click on the Next button to move to the next page of the AppWizard.
Figure 8-36 Setting the MFC application project user interface properties
452
n Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL On the fourth page of the MFC AppWizard, you see the results of the choices you made in the previous dialogs. Figure 8-37 illustrates how the dialog should appear when you are done. Click on the Finish button to create the MFC application.
Figure 8-37 Confirming the MFC application project user interface properties
A confirmation dialog box appears as shown in Figure 8-38; press OK to create the MFC project.
Figure 8-38 The confirmation dialog
Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL n
453
Back in the Visual C++ IDE, bring up the workspace viewer, and select the ResourceView tab. Expand the Dialogs entry and double-click on its only element, which is the main dialog resource for the application. Remove the pre-existing static text and button controls. Using Figure 8-39 as your guide, lay out the group box, edit, list box, and button controls as shown on the figure, accepting the default ID values generated by Visual C++. Figure 8-39 The main Save the prodialog display ject to of the MFC protect your application in work. the Resource Editor
Next, select the list box and right-click to bring up its context menu; select the Properties entry. Move to the Styles tab and uncheck the Sort check box. Click on the pushpin button to keep the Properties dialog visible while you make the other changes needed. Using Figure 8-40 as your guide, enter the various Caption properties shown for the buttons, group Figure 8-40 Setting the boxes, and various propmain dialog. erties of conUnclick the trols in the pushpin and MFC Resource save your Editor work.
454
n Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL
Now you need to set up a member variable to facilitate interaction with the list box control. Hold down the Ctrl key and double-click on the list box control. The Add Member Variable Wizard dialog appears as shown in Figure 8-41. Enter m_commandslb as the name of the member variable and set its category to Control (which will automatically set its type to CListBox), and click on the OK butFigure 8-41 ton. This will connect the user Adding a interface of the list box with an member variMFC control, which is much easier able control for the list box to interact with. in MFC
To set up a member variable to facilitate interaction with the uppermost edit control, hold down the Ctrl key and double-click on the edit control. The Add Member Variable Wizard dialog appears. Enter m_idnumber as the name of the member variable and set its category to Value (which will automatically set its type to CString), and click on the OK button. This will connect the user interface of the edit control with an MFC CString class. To set up a member variable to facilitate interaction with the middle edit control, hold down the Ctrl key and double-click on the edit control. The Add Member Variable Wizard dialog appears. Enter m_commandstring as the name of the member variable and set its category to Value (which will automatically set its type to CString), and click on the OK button. This will connect the user interface of the edit control with an MFC CString class. To set up a member variable to facilitate interaction with the bottommost edit control, hold down the Ctrl key and double-click on the edit control. The Add Member Variable Wizard dialog appears. Enter m_commandtext as the name of the member variable and set its category to Value (which will automatically set its type to CString), and click on the OK button. This will connect the user interface of the edit control with an MFC CString class. Next, you need to create an event handler for user double-clicks on the list box control (which should transfer information to the edit controls). To do this, hold down the Shift key and double-click on the list box control. The MFC ClassWizard dialog appears; select the Message Maps tab, then the IDC_LIST1 entry and the LBN_DBLCLICK entry, respectively, and then click on the Add Figure 8-42 The MFC Add Function button. A dialog similar Member to that shown in Figure 8-42 Function appears; accept the default dialog
Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL n
Figure 8-43 The MFC ClassWizard showing an added event handler for the list box
Figure 8-44 The MFC ClassWizard showing added event handlers for the button controls
455
name by clicking on the OK button. The ClassWizard dialog should then look very similar to the one shown in Figure 8-43 (except it has a different list box ID value). Click on OK to accept the new event handler and close the ClassWizard dialog. Now repeat the process for each of the button controls: Select their BN_CLICKED message in the Message Maps tab of the ClassWizard dialog and click on the Add Function button; accept the default names by clicking on the OK button each time. When you are done, the ClassWizard dialog’s Message Maps tab should look quite similar to that shown in Figure 8-44. Click on OK to accept the changes. Save the project.
Next you need to add a new member function to the dialog box class. Display the workspace if it is not already visible. Then select the ClassView tab, and expand the tree control until the CAgentPlayerDlg entry is shown. Right-click on this entry as shown in Figure 8-45, and select the Add Member Function menu option as indicated in the figure.
Figure 8-45 The MFC workspace context menu for the CAgentPlayer Dlg class
456
n Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL
Figure 8-46 Adding a new member function to the CAgentPlayerDlg class
The Add Member Function Wizard dialog appears as illustrated in Figure 8-46 Enter a Function Type of void and a Function Declaration of UpdateListbox Display() (be sure to type exactly what is given, since the wizard simply inserts the text directly into the source code!). Click on the OK button to accept the remaining default settings and add the member function to the dialog class.
As the final user interface step for this project, you need to add the OLE DB consumer DLL you created in the previous section. To do this, select View|ClassWizard from the menu bar. In ClassWizard, select the Automation tab, then click on the New Class button, and select From A Type Library in the pop-up menu that appears. The Import From Type Library dialog appears as shown in Figure 8-47. Navigate to the directory where you placed the ATLSimpleC project files, and locate its TLB file as shown in the figure. Figure 8-47 Click on Open to start adding Adding a new the new Automation class Automation from the Type Library. class from a Type Library
After a moment, the Confirm Classes dialog appears as shown in Figure 8-48. It should contain only the IAgentController class as the figure indicates; this class will be added in the files shown at the bottom of the dialog to the current project. Click on OK to add the OLE DB consumer support. Save the project.
Figure 8-48 The IAgentController COM interface class added to the MFC project
Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL n
457
Line-By-Line Now you are ready to enter the small amount of source code needed to connect the user interface with the OLE DB consumer you created in ATL earlier. Bring up the AGENTPLAYERDLG.H file in the text editor. Scan through it and enter or change any grayed line of source code in Listing 8-6. Save the project. The section after the code explains what these changes do. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39.
// AgentPlayerDlg.h : header file // #if !defined(AFX_AGENTPLAYERDLG_H__367C4E0A_000C_11D3_B7EE_00E02916C424__ INCLUDED_) #define AFX_AGENTPLAYERDLG_H__367C4E0A_000C_11D3_B7EE_00E02916C424__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 //@@@ bring in the imported OLE DB Consumer DLL @@@ #include “atlsimplec.h” class CAgentPlayerDlgAutoProxy; ///////////////////////////////////////////////////////////////////////////// // CAgentPlayerDlg dialog class CAgentPlayerDlg : public CDialog { DECLARE_DYNAMIC(CAgentPlayerDlg); friend class CAgentPlayerDlgAutoProxy; // Construction public: void UpdateListboxDisplay(); CAgentPlayerDlg(CWnd* pParent = NULL); // standard constructor virtual ~CAgentPlayerDlg(); //@@@ member variable for the COM interface of the OLE DB Consumer @@@ IAgentcontroller pAgentcontroller; // Dialog Data //{{AFX_DATA(CAgentPlayerDlg) enum { IDD = IDD_AGENTPLAYER_DIALOG }; CListBox m_commandslb; CString m_idnumber; CString m_commandstring; CString m_commandtext; //}}AFX_DATA // ClassWizard generated virtual function overrides
458 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77.
n Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL //{{AFX_VIRTUAL(CAgentPlayerDlg) protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support //}}AFX_VIRTUAL // Implementation protected: CAgentPlayerDlgAutoProxy* m_pAutoProxy; HICON m_hIcon; BOOL CanExit(); // Generated message map functions //{{AFX_MSG(CAgentPlayerDlg) virtual BOOL OnInitDialog(); afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); afx_msg void OnClose(); virtual void OnOK(); virtual void OnCancel(); afx_msg void OnDblclkList2(); afx_msg void OnButton1(); afx_msg void OnButton2(); afx_msg void OnButton3(); afx_msg void OnButton4(); afx_msg void OnButton5(); afx_msg void OnButton6(); //}}AFX_MSG DECLARE_MESSAGE_MAP() }; //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional declarations immediately before the previous line. #endif // !defined(AFX_AGENTPLAYERDLG_H__367C4E0A_000C_11D3_B7EE_00E02916C424__INCLUDED_)
Listing 8-6
AGENTPLAYERDLG.H source code after modifications
The following entries explain what the new code lines do: n
Lines 10-11 This code makes the ATL OLE DB consumer available via the class file created by the ClassWizard in the previous steps.
n
Lines 28-29 This code declares a member variable to hold the OLE DB Consumer class for use by the MFC application.
Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL n
459
Next bring up the AGENTPLAYERDLG.CPP file in the text editor. Scan through it and enter or change any grayed line of source code in Listing 8-7. Save the project. The section after the code explains what these changes do.
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42.
// AgentPlayerDlg.cpp : implementation file // #include “stdafx.h” #include “AgentPlayer.h” #include “AgentPlayerDlg.h” #include “DlgProxy.h” #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // CAboutDlg dialog used for App About class CAboutDlg : public CDialog { public: CAboutDlg(); // Dialog Data //{{AFX_DATA(CAboutDlg) enum { IDD = IDD_ABOUTBOX }; //}}AFX_DATA // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CAboutDlg) protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support //}}AFX_VIRTUAL // Implementation protected: //{{AFX_MSG(CAboutDlg) //}}AFX_MSG DECLARE_MESSAGE_MAP() }; CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD) { //{{AFX_DATA_INIT(CAboutDlg)
n Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL
460 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89.
//}}AFX_DATA_INIT } void CAboutDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CAboutDlg) //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(CAboutDlg, CDialog) //{{AFX_MSG_MAP(CAboutDlg) // No message handlers //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CAgentPlayerDlg dialog IMPLEMENT_DYNAMIC(CAgentPlayerDlg, CDialog); CAgentPlayerDlg::CAgentPlayerDlg(CWnd* pParent /*=NULL*/) : CDialog(CAgentPlayerDlg::IDD, pParent) { //{{AFX_DATA_INIT(CAgentPlayerDlg) m_idnumber = _T(“”); m_commandstring = _T(“”); m_commandtext = _T(“”); //}}AFX_DATA_INIT // Note that LoadIcon does not require a subsequent DestroyIcon in Win32 m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); m_pAutoProxy = NULL; } CAgentPlayerDlg::~CAgentPlayerDlg() { // If there is an automation proxy for this dialog, set // its back pointer to this dialog to NULL, so it knows // the dialog has been deleted. if (m_pAutoProxy != NULL) m_pAutoProxy->m_pDialog = NULL; } void CAgentPlayerDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CAgentPlayerDlg)
Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL n 90. 91. 92. 93. 94. 95. 96. 97. 98. 99. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132. 133. 134. 135. 136.
DDX_control(pDX, IDC_LIST2, m_commandslb); DDX_Text(pDX, IDC_EDIT1, m_idnumber); DDX_Text(pDX, IDC_EDIT2, m_commandstring); DDX_Text(pDX, IDC_EDIT3, m_commandtext); //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(CAgentPlayerDlg, CDialog) //{{AFX_MSG_MAP(CAgentPlayerDlg) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_WM_CLOSE() ON_LBN_DBLCLK(IDC_LIST2, OnDblclkList2) ON_BN_CLICKED(IDC_BUTTON1, OnButton1) ON_BN_CLICKED(IDC_BUTTON2, OnButton2) ON_BN_CLICKED(IDC_BUTTON3, OnButton3) ON_BN_CLICKED(IDC_BUTTON4, OnButton4) ON_BN_CLICKED(IDC_BUTTON5, OnButton5) ON_BN_CLICKED(IDC_BUTTON6, OnButton6) //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CAgentPlayerDlg message handlers BOOL CAgentPlayerDlg::OnInitDialog() { CDialog::OnInitDialog(); // Add “About...” menu item to system menu. // IDM_ABOUTBOX must be in the system command range. ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); ASSERT(IDM_ABOUTBOX < 0xF000); CMenu* pSysMenu = GetSystemMenu(FALSE); if (pSysMenu != NULL) { CString strAboutMenu; strAboutMenu.LoadString(IDS_ABOUTBOX); if (!strAboutMenu.IsEmpty()) { pSysMenu->AppendMenu(MF_SEPARATOR); pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu); } }
461
n Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL
462 137. 138. 139. 140. 141. 142. 143. 144. 145. 146. 147. 148. 149. 150. 151. 152. 153. 154. 155. 156. 157. 158. 159. 160. 161. 162. 163. 164. 165. 166. 167. 168. 169. 170. 171. 172. 173. 174. 175. 176. 177. 178. 179. 180. 181. 182. 183.
// Set the icon for this dialog. The framework does this automatically // when the application’s main window is not a dialog SetIcon(m_hIcon, TRUE); // Set big icon SetIcon(m_hIcon, FALSE); // Set small icon // TODO: Add extra initialization here //@@@ get the COM interface for the OLE DB Consumer via its ProgID @@@ pAgentcontroller.CreateDispatch(“ATLSimpleC.Agentcontroller.1"); //@@@ handle error @@@ if (pAgentcontroller.m_lpDispatch == NULL) { AfxMessageBox(“Unable To Connect With Agentcontroller OLEDB server”); return FALSE; } return TRUE; // return TRUE unless you set the focus to a control } void CAgentPlayerDlg::OnSysCommand(UINT nID, LPARAM lParam) { if ((nID & 0xFFF0) == IDM_ABOUTBOX) { CAboutDlg dlgAbout; dlgAbout.DoModal(); } else { CDialog::OnSysCommand(nID, lParam); } } // If you add a minimize button to your dialog, you will need the code below // to draw the icon. For MFC applications using the document/view model, // this is automatically done for you by the framework. void CAgentPlayerDlg::OnPaint() { if (IsIconic()) { CPaintDC dc(this); // device context for painting SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0); // Center icon in client rectangle int cxIcon = GetSystemMetrics(SM_CXICON); int cyIcon = GetSystemMetrics(SM_CYICON); CRect rect;
Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL n 184. 185. 186. 187. 188. 189. 190. 191. 192. 193. 194. 195. 196. 197. 198. 199. 200. 201. 202. 203. 204. 205. 206. 207. 208. 209. 210. 211. 212. 213. 214. 215. 216. 217. 218. 219. 220. 221. 222. 223. 224. 225. 226. 227. 228. 229. 230.
GetclientRect(&rect); int x = (rect.Width() - cxIcon + 1) / 2; int y = (rect.Height() - cyIcon + 1) / 2; // Draw the icon dc.DrawIcon(x, y, m_hIcon); } else { CDialog::OnPaint(); } } // The system calls this to obtain the cursor to display while the user drags // the minimized window. HCURSOR CAgentPlayerDlg::OnQueryDragIcon() { return (HCURSOR) m_hIcon; } // Automation servers should not exit when a user closes the UI // if a controller still holds on to one of its objects. These // message handlers make sure that if the proxy is still in use, // then the UI is hidden but the dialog remains around if it // is dismissed. void CAgentPlayerDlg::OnClose() { pAgentcontroller.ReleaseDispatch();//@@@ shut down correctly @@@ PostQuitMessage(0); DestroyWindow(); } void CAgentPlayerDlg::OnOK() { if (CanExit()) CDialog::OnOK(); } void CAgentPlayerDlg::OnCancel() { if (CanExit()) CDialog::OnCancel(); } BOOL CAgentPlayerDlg::CanExit() {
463
464
n Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL
231. // If the proxy object is still around, then the automation 232. // controller is still holding on to this application. Leave 233. // the dialog around, but hide its UI. 234. if (m_pAutoProxy != NULL) 235. { 236. ShowWindow(SW_HIDE); 237. return FALSE; 238. } 239. 240. return TRUE; 241. } 242. 243. void CAgentPlayerDlg::OnDblclkList2() 244. { 245. // TODO: Add your control notification handler code here 246. //@@@ define holders for strings @@@ 247. CString strHolder; 248. CString strID; 249. CString strCommand; 250. CString strText; 251. //@@@ define position holder @@@ 252. int iPos; 253. //@@@ define working buffer @@@ 254. char buf[256]; 255. //@@@ get the text at the point of selection @@@ 256. m_commandslb.GetText( m_commandslb.GetCurSel(), &buf[0] ); 257. //@@@ put it into the CString @@@ 258. strHolder = buf; 259. //@@@ get first space group @@@ 260. iPos = strHolder.Find(“ ”,0); 261. //@@@ pull out the value to the left as id number string @@@ 262. strID = strHolder.Left( iPos ); 263. //@@@ put remaining string back into working string without spaces @@@ 264. strHolder = strHolder.Mid( iPos + 5 ); 265. //@@@ get next spacer @@@ 266. iPos = strHolder.Find(“ ”,0); 267. //@@@ put its string into command text @@@ 268. strCommand = strHolder.Left( iPos ); 269. //@@@ then put the rest as the text @@@ 270. strText = strHolder.Mid( iPos + 5 ); 271. //@@@ assign to member variables for edit controls @@@ 272. m_idnumber = strID; 273. m_commandstring = strCommand; 274. m_commandtext = strText; 275. //@@@ put the data into the controls @@@ 276. UpdateData(FALSE); 277.
Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL n 278. 279. 280. 281. 282. 283. 284. 285. 286. 287. 288. 289. 290. 291. 292. 293. 294. 295. 296. 297. 298. 299. 300. 301. 302. 303. 304. 305. 306. 307. 308. 309. 310. 311. 312. 313. 314. 315. 316. 317. 318. 319. 320. 321. 322. 323. 324.
} void CAgentPlayerDlg::OnButton1() { // TODO: Add your control notification handler code here //@@@ do our internal update call (it handles errors so we don’t) @@@ UpdateListboxDisplay(); } void CAgentPlayerDlg::OnButton2() { // TODO: Add your control notification handler code here //@@@ error handling block @@@ try { //@@@ invoke execute handler of contained server @@@ pAgentcontroller.ExecuteCommands(); } catch (COleDispatchException e) //@@@ handle OLE exception @@@ { //@@@ display OLE error message @@@ AfxMessageBox( e.m_strDescription ); } } void CAgentPlayerDlg::OnButton3() { // TODO: Add your control notification handler code here //@@@ get the data from the controls @@@ UpdateData(TRUE); //@@@ error handling block @@@ try { //@@@ set the contained server properties from the member vars @@@ pAgentcontroller.SetCommandID( atoi( m_idnumber ) ); pAgentcontroller.SetCommandName( m_commandstring ); pAgentcontroller.SetCommandText( m_commandtext ); //@@@ invoke the add function on the contained server @@@ pAgentcontroller.AddCommand(); //@@@ redo display @@@ UpdateListboxDisplay(); } catch (COleDispatchException e) //@@@ handle OLE exception @@@ { //@@@ display OLE error message @@@ AfxMessageBox( e.m_strDescription ); }
465
n Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL
466 325. 326. 327. 328. 329. 330. 331. 332. 333. 334. 335. 336. 337. 338. 339. 340. 341. 342. 343. 344. 345. 346. 347. 348. 349. 350. 351. 352. 353. 354. 355. 356. 357. 358. 359. 360. 361. 362. 363. 364. 365. 366. 367. 368. 369. 370. 371.
} void CAgentPlayerDlg::OnButton4() { // TODO: Add your control notification handler code here //@@@ get the data from the controls @@@ UpdateData(TRUE); //@@@ error handling block @@@ try { //@@@ set the contained server properties from the member vars @@@ pAgentcontroller.SetCommandID( atoi( m_idnumber ) ); pAgentcontroller.SetCommandName( m_commandstring ); pAgentcontroller.SetCommandText( m_commandtext ); //@@@ invoke the delete function on the contained server @@@ pAgentcontroller.DeleteCommand(); //@@@ redo display @@@ UpdateListboxDisplay(); } catch (COleDispatchException e) //@@@ handle OLE exception @@@ { //@@@ display OLE error message @@@ AfxMessageBox( e.m_strDescription ); } } void CAgentPlayerDlg::OnButton5() { // TODO: Add your control notification handler code here //@@@ get the data from the controls @@@ UpdateData(TRUE); //@@@ error handling block @@@ try { //@@@ set the contained server properties from the member vars @@@ pAgentcontroller.SetCommandID( atoi( m_idnumber ) ); pAgentcontroller.SetCommandName( m_commandstring ); pAgentcontroller.SetCommandText( m_commandtext ); //@@@ invoke the change function on the contained server @@@ pAgentcontroller.ChangeCommand(); //@@@ redo display @@@ UpdateListboxDisplay(); } catch (COleDispatchException e) //@@@ handle OLE exception @@@ { //@@@ display OLE error message @@@ AfxMessageBox( e.m_strDescription );
Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL n 372. 373. 374. 375. 376. 377. 378. 379. 380. 381. 382. 383. 384. 385. 386. 387. 388. 389. 390. 391. 392. 393. 394. 395. 396. 397. 398. 399. 400. 401. 402. 403. 404. 405. 406. 407. 408. 409. 410. 411. 412. 413. 414. 415. 416. 417. 418.
467
} } void CAgentPlayerDlg::OnButton6() { // TODO: Add your control notification handler code here pAgentcontroller.ReleaseDispatch();//@@@ shut down correctly @@@ PostQuitMessage(0); DestroyWindow(); } void CAgentPlayerDlg::UpdateListboxDisplay() { //@@@ define strings for display manipulation @@@ CString strHolder = _T(“ ”); CString strWorking; //@@@ define position holder @@@ int iPos; //@@@ define BSTR to get from server @@@ BSTR bstrHolder = strHolder.AllocSysString(); //@@@ clear the list box if needed @@@ while (m_commandslb.GetCount() > 0) m_commandslb.DeleteString ( 0 ); //@@@ error handler @@@ try { //@@@ get the long string with all commands in it as text separated by * @@@ pAgentcontroller.GetAllCommands( &bstrHolder ); //@@@ move the BSTR into the CString @@@ strHolder = bstrHolder; //@@@ define boolean holder @@@ BOOL bFlag; //@@@ set it to false @@@ bFlag = FALSE; //@@@ loop till we die @@@ while (!bFlag) { //@@@ get next instance of * iPos = strHolder.Find(“*”,0); //@@@ if out of * then done since last entry has one @@@ if (iPos < 1 ) return; //@@@ pull out the entry to the left @@@ strWorking = strHolder.Left(iPos ); //@@@ move working string to the right @@@ strHolder = strHolder.Mid( iPos + 1 ); //@@@ add it to the list box @@@ m_commandslb.AddString( strWorking );
468 419. 420. 421. 422. 423. 424. 425. 426. }
n Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL } } catch (COleDispatchException e) //@@@ handle OLE Exception @@@ { //@@@ show user OLE error string @@@ AfxMessageBox( e.m_strDescription ); }
Listing 8-7
AGENTPLAYERDLG.CPP source code after modifications
The following entries explain what the new code lines do: n
Lines 144-151 This code handles obtaining an interface pointer to the OLE DB consumer COM server. Notice that if it fails it aborts creation of the main dialog box.
n
Lines 213-214 This code closes the application properly by telling the MFC code to quit and destroying the displayed dialog.
n
Lines 246-276 This code handles the tricky but essential job of updating the three edit controls from the list box data. First, the string from the selected line of the list box is obtained (lines 246-256), then it is scanned for spaces (lines 257-260) and the first data segment pulled out and put into the member variable for the edit control (lines 261-264). The process is repeated two more times to get the remaining data. Finally, MFC is told to update the display controls (line 276).
n
Lines 283-284 list box.
n
Lines 290-300 This code handles the basic task of calling the OLE DB consumer with a request to play what is in the database through the Agent control. It handles any problem by simply displaying the error message from the OLE exception thrown by the OLE DB consumer (since we set it to use COM error handling).
n
Lines 306-324 This code is one of three virtually identical blocks that trigger database updates via the OLE DB consumer (this one adds a new command). First, the data is moved from the edit controls into the member variables (lines 306-307). Then the variables’ data is placed into the properties of the OLE DB consumer member variable interface pointer (lines 311-314). Finally, the appropriate method is called to manipulate the database (lines 315-316). The list box display is then automatically updated (lines 317-318). If any problem occurs, an OLE exception is thrown by the OLE DB consumer via its COM error handler, and taken care of by simply reporting the error message to the user (lines 320-324).
n
Lines 330-348 This code repeats the database manipulation operation described above to delete a command.
This code just calls our internal update method for the
Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL n
469
n
Lines 354-372 This code repeats the database manipulation operation described above to change the contents of a command.
n
Lines 378-380 This code deals with exiting via the button control rather than the close button; it works identically to the previous closure code.
n
Lines 386-425 This code does the really rather difficult operation of clearing the list box control and updating it with the latest set of commands from the database via the OLE DB consumer. First, a manual clearing of the list box is done (since MFC, rather strangely, doesn’t have this method supported) (lines 393-394). Then the OLE DB consumer is called to obtain the BSTR containing the complete commands list (lines 398-399). This string is then taken apart in a loop which terminates when no more asterisks are found (lines 400-412). After each asterisk is found, that portion of the string is pulled off (lines 413-416) and put into the list box (lines 417-418). If an error occurs, the user is informed (lines 422-424).
Building the MFC OLE DB Consumer Application Once you are satisfied you’ve entered the code from the above listings correctly, build the OLE DB consumer test application from the Build menu. If you encounter any errors, please check the code in the listing to find your mistake (it has been exhaustively debugged and is correct). Now you are ready to test the consumer.
Testing the OLE DB Consumer with the MFC Application Now you are ready to test the OLE DB consumer with the MFC client. This will show you an amusing visual display, but also demonstrate how OLE DB consumers work.
Step-By-Step Start the AGENTPLAYER application from inside Visual C++ or from the Explorer. When the dialog appears, click on the Update Listbox From Database button; the list box should fill with the list of commands you placed in the database earlier in this chapter. Now click on the Execute Commands With Agent button. As shown in Figure 8-49, the Merlin Agent character will appear, moving around and speaking several statements about OLE DB.
470
n Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL
Figure 8-49 The MFC application displays the database via Microsoft Agent from the OLE DB consumer
Experiment with deleting, adding, and changing commands (only use the ones shown because our consumer doesn’t support any others!). You’ll find your changes persist even between sessions because they are stored in the database by the OLE DB consumer.
Where We Go from Here You now have successfully created two OLE DB provider applications and an OLE DB consumer as well, using ATL templates along with various hand-built ATL and MFC programs to support them. There is one major arena where OLE DB will be used that still needs exploration: MFC MDI (Multiple Document Interface) applications. Chapter 9 provides you with a completely worked out MFC MDI application to use OLE DB consumer templates to locate and examine OLE DB provider schema information.
Chapter Nine
Creating and Using Complex OLE DB Consumers in MFC In Chapter 6, you learned how to create a simple OLE DB provider with ATL, and how to develop a simple MFC front-end consumer for it. In Chapter 7, you developed a powerful three-tier ATL provider and MFC consumer supported by an MFC client. In Chapter 8, you learned how to take advantage of the template functionality of an ATL OLE DB consumer to create a simple but very powerful COM server that can function in an application to connect to an Access database and drive the Microsoft Agent ActiveX control. When you’ve finished this chapter, you’ll have created a powerful MDI (Multiple Document Interface) OLE DB consumer written in MFC.
OLE DB Consumer Templates in MFC OLE DB support in Visual C++ is currently entirely within ATL. Fortunately, it is quite easy to include ATL capabilities in an MFC application! As the following code will demonstrate, you can simply include the appropriate header file, and then reference the needed templates. This allows MFC applications (even complex MDI ones) to use all the power of OLE DB.
Creating an OLE DB Consumer MDI Application with MFC This chapter’s work entails a different approach to OLE DB consumer programming: namely, merging it with the MFC MDI system. This requires essentially three steps: designing the MDI application itself using the AppWizard, adding the OLE DB code using ATL templates, and connecting the data from the database with the MFC user interface. Except for the design process, all the action takes place in added code.
471
472
n Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC
Step-By-Step Start Visual C++ 6.0. From the File menu, select New. The New dialog box opens; click on the MFC AppWizard (exe) entry. Next, select an appropriate directory, then enter a project name of SchemaDB. Figure 9-1 shows how the New dialog should appear when you are done.
Figure 9-1 The New dialog box creating the SchemaDB MFC project
In the first page of the MFC AppWizard dialog, make sure the Multiple Documents radio button is selected, and that Document/View Architecture is enabled, as indicated in Figure 9-2. Click on the Next button to move to the next page of the wizard.
Figure 9-2 Setting the MFC application project basic properties
Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC n
Figure 9-3 Setting the MFC application project MFC database properties
Figure 9-4 Setting the MFC application project compound document properties
Figure 9-5 Setting the MFC application project user interface properties
473
On the second page of the MFC AppWizard, you get to set the MFC database support for the application; since OLE DB is not yet supported by MFC, leave the None radio button selected as indicated in Figure 9-3. Click on the Next button to move to the next page of the AppWizard. On the third page of the MFC AppWizard, you get to select how your MFC application interacts with the OLE compound document system; since we aren’t implementing a compound document, make sure the None radio button is selected. Since we will be using Automation for our OLE DB consumer, make sure the Automation check box is checked at the bottom of the wizard dialog. Figure 9-4 illustrates how the dialog should appear when you are done. Click on the Next button to move to the next page of the AppWizard. On the fourth page of the MFC AppWizard, you select the various user interface features you want for the MFC application. Leave all the default settings unchanged; Figure 9-5 shows how the dialog should look. Click on the Advanced button next.
474
n Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC The Advanced Options dialog appears; enter a File Extension of dbs. Leave all the other settings unchanged as shown in Figure 9-6. Click on Close to return to the main AppWizard dialog. Then click on the Next button to move to the fifth page of the wizard.
Figure 9-6 Setting the MFC application project advanced properties
Figure 9-7 Setting the MFC application project user interface properties
On the fifth page of the MFC AppWizard, you get to select from the various UI choices for the project; make sure the MFC Standard radio button is selected and leave the other radio buttons at their default settings. Figure 9-7 illustrates how the dialog should appear when you are done. Click on the Next button to move to the next page of the AppWizard.
On the sixth page of the MFC AppWizard, you see the results of the choices you made in the previous dialogs. Figure 9-8 illustrates how the dialog should appear when you are done. Click on the Finish button to create the MFC application. Figure 9-8 The MFC application project summary dialog
Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC n
475
A confirmation dialog box appears as shown in Figure 9-9; press OK to create the MFC project.
Figure 9-9 The confirmation dialog
Figure 9-10 Adding dialog boxes to the MFC application via the Resource Editor
Figure 9-11 Using ClassWizard to add a class for the dialog boxes
Back in the Visual C++ IDE, bring up the workspace viewer, and select the ResourceView tab. Expand the Dialogs entry and right-click on it to bring up its context menu. Select the Insert Dialog menu option as shown in Figure 9-10. Repeat the process a second time to add another dialog to the project. Save the project to protect your work.
Bring up the second dialog you added in the Resource Editor. Hold down the Ctrl key and double-click on the dialog box; the ClassWizard dialog appears, and then a modal dialog shown in Figure 9-11. This Adding a Class dialog allows you to either create a new class for the
476
n Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC dialog or use an existing one. Select the Create a new class radio button as shown in the figure and click on OK. The New Class Wizard dialog appears as shown in Figure 9-12. Enter a Name of colpage and leave the remaining settings unchanged. Click on OK to add the new class for the dialog box.
Figure 9-12 Using the New Class Wizard to add a class for the dialog boxes
Figure 9-13 ClassWizard showing the new dialog class
Figure 9-14 The Tables dialog display of the MFC application in the Resource Editor
The ClassWizard dialog now appears again, this time on the Class Info tab showing the added class. Click on OK to add the new class. Repeat the previous set of steps for the first dialog you added, giving its class the name of tablepag. Save the project.
Back in the Visual C++ IDE, bring up the workspace viewer, and select the ResourceView tab. Expand the Dialogs entry and double-click on the first dialog you added. Remove the pre-existing button controls. Using Figure 9-14 as your guide, lay out the three check box controls as shown in the figure, accepting the default ID values generated by Visual C++. Give them and the dialog itself the captions indicated in the figure using the Properties dialog. Save the project to protect your work.
Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC n
Figure 9-15 The Column Data dialog display of the MFC application in the Resource Editor
477
In the Dialogs entry of the Resource View of the workspace, double-click on the second dialog you added. Remove the pre-existing button controls. Using Figure 9-15 as your guide, lay out the three check box controls as shown in the figure, accepting the default ID values generated by Visual C++. Give them and the dialog itself the captions indicated in the figure using the Properties dialog. Save the project to protect your work.
Now you need to set up a member variable to facilitate interaction with the uppermost check box control of the Column Data dialog. Hold down the Ctrl key and double-click on the top check box control. The Add Member Variable Wizard dialog appears as shown in Figure 9-16. Enter m_bLength as the name of the member variable and set its category to Value; this will set the Variable type to BOOL automatically. Figure 9-16 Click on the OK button. This will conAdding a nect the user interface of the check member varibox with an MFC member variable, able for a which is much easier to interact with! check box in MFC
Now you need to set up a member variable to facilitate interaction with the middle check box control of the Column Data dialog. Hold down the Ctrl key and double-click on the middle check box control. The Add Member Variable Wizard dialog appears. Enter m_bPrecision as the name of the member variable and set its category to Value; this will set the Variable type to BOOL automatically. Click on the OK button. This will connect the user interface of the check box with an MFC member variable. Set up a member variable to facilitate interaction with the bottommost check box control of the Column Data dialog by holding down the Ctrl key and double-clicking on the bottom check box control. The Add Member Variable Wizard dialog appears. Enter m_bNullability as the name of the member variable and set its category to Value; this will set the Variable type to BOOL automatically. Click on the OK button. This will connect the user interface of the check box with an MFC member variable.
478
n Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC Set up a member variable to facilitate interaction with the uppermost check box control of the Tables dialog, hold down the Ctrl key and double-click on the top check box control. The Add Member Variable Wizard dialog appears. Enter m_bSystemTables as the name of the member variable and set its category to Value; this will set the Variable type to BOOL automatically. Click on the OK button. To set up a member variable to facilitate interaction with the middle check box control of the tables dialog, hold down the Ctrl key and double-click on the middle check box control. The Add Member Variable Wizard dialog appears. Enter m_bViews as the name of the member variable and set its category to Value; this will set the Variable type to BOOL automatically. Click on the OK button. To set up a member variable to facilitate interaction with the bottommost check box control of the tables dialog, hold down the Ctrl key and double-click on the bottom check box control. The Add Member Variable Wizard dialog appears. Enter m_bSynonyms as the name of the member variable and set its category to Value; this will set the Variable type to BOOL automatically. Click on the OK button. Next, you need to manipulate the default menu items MFC’s AppWizard created. To do this, activate the ResourceView tab of the workspace and open the Menu entry. Select the IDR_MAINFRAME entry, and click on the File menu item. Remove all elements except the Open and Exit commands. Next,
Figure 9-17 The MFC Menu Editor showing the new View menu
Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC n
479
select the View menu item. Add and delete items from the menu until it is the same as the one illustrated in Figure 9-17. Save the project.
Figure 9-18 The MFC event handler dialog
Next, you need to add event handlers for the menu items. To do this, display the ClassWizard dialog and select the identifier for the File|Open menu command, then select its COMMAND event. Click on the Add Function button, and you’ll see the dialog shown in Figure 9-18. Click on OK to add this event handler to the project.
As your last user interface step, repeat the process for the other menu commands you added. Figure 9-19 shows how the dialog should look when you are done. Click on OK to add the new event handlers. Save the project.
Figure 9-19 The MFC ClassWizard showing added menu item event handlers
Line-By-Line Now you are ready to enter the source code needed to connect the user interface with the OLE DB consumer templates and databases. Bring up the SCHEMADBDOC.H file in the text editor. Scan through it and enter or change any grayed line of source code in Listing 9-1. Save the project. The section after the code explains what these changes do. 1. 2. 3. 4. 5. 6. 7. 8.
// SchemaDBDoc.h : interface of the CSchemaDBDoc class // ///////////////////////////////////////////////////////////////////////////// #if !defined(AFX_SCHEMADBDOC_H__D9C40DD1_0339_11D3_B7EE_00E02916C424__INCLUDED_) #define AFX_SCHEMADBDOC_H__D9C40DD1_0339_11D3_B7EE_00E02916C424__INCLUDED_ #if _MSC_VER > 1000
480 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55.
n Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC #pragma once #endif // _MSC_VER > 1000
class CSchemaDBDoc : public CDocument { protected: // create from serialization only CSchemaDBDoc(); DECLARE_DYNCREATE(CSchemaDBDoc) // Attributes public: CString m_strConnect; // Database Connection CDataSource m_source; CSession m_session; // Table Information CTables* m_pTableset; BOOL m_bSystemTables; BOOL m_bViews; BOOL m_bSynonyms; // Column Information CColumns* m_pColumnset; BOOL m_bLength; BOOL m_bPrecision; BOOL m_bNullability; // Level Information enum Level { levelNone, levelTable, levelColumn }; Level m_nLevel; CString m_strTableName; // Operations public: void SetLevel(Level nLevel); CString GetDSN(); void FetchColumnInfo(LPCSTR lpszName); BOOL FetchTableInfo();
Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC n 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93. 94. 95. 96. 97. 98. 99.
481
// Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CSchemaDBDoc) public: virtual BOOL OnNewDocument(); virtual void Serialize(CArchive& ar); virtual BOOL OnOpenDocument(); virtual void OnCloseDocument(); //}}AFX_VIRTUAL // Implementation public: virtual ~CSchemaDBDoc(); #ifdef _DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif protected: // Generated message map functions protected: //{{AFX_MSG(CSchemaDBDoc) // NOTE - the ClassWizard will add and remove member functions here. // DO NOT EDIT what you see in these blocks of generated code ! afx_msg void OnSettings(); //}}AFX_MSG DECLARE_MESSAGE_MAP() // Generated OLE dispatch map functions //{{AFX_DISPATCH(CSchemaDBDoc) // NOTE - the ClassWizard will add and remove member functions here. // DO NOT EDIT what you see in these blocks of generated code ! //}}AFX_DISPATCH DECLARE_DISPATCH_MAP() DECLARE_INTERFACE_MAP() }; ///////////////////////////////////////////////////////////////////////////// //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional declarations immediately before the previous line.
482
n Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC
100. #endif // !defined(AFX_SCHEMADBDOC_H_D9C40DD1_0339_11D3_B7EE_00E02916C424_ INCLUDED_) Listing 9-1
SCHEMADBDOC.H source code after modifications
The following entries explain what the new code lines do: n
Lines 21-47 This code declares the various member variables needed for the OLE DB consumer and its data.
n
Lines 51-54 This code declares the helper functions we’ll use to interact with the OLE DB consumer.
Next bring up the SCHEMADBDOC.CPP file in the text editor. Scan through it and enter or change any grayed line of source code in Listing 9-2. Save the project. The section after the code explains what these changes do.
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31.
// SchemaDBDoc.cpp : implementation of the CSchemaDBDoc class // #include “stdafx.h” #include “SchemaDB.h” #include “SchemaDBDoc.h” #include “tablepag.h” #include “colpage.h” #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // CSchemaDBDoc IMPLEMENT_DYNCREATE(CSchemaDBDoc, CDocument) BEGIN_MESSAGE_MAP(CSchemaDBDoc, CDocument) //{{AFX_MSG_MAP(CSchemaDBDoc) // NOTE - the ClassWizard will add and remove mapping macros here. // DO NOT EDIT what you see in these blocks of generated code! ON_COMMAND(IDM_SETTINGS, OnSettings) //}}AFX_MSG_MAP END_MESSAGE_MAP() BEGIN_DISPATCH_MAP(CSchemaDBDoc, CDocument) //{{AFX_DISPATCH_MAP(CSchemaDBDoc)
Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC n 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78.
483
// NOTE - the ClassWizard will add and remove mapping macros here. // DO NOT EDIT what you see in these blocks of generated code! //}}AFX_DISPATCH_MAP END_DISPATCH_MAP() // Note: we add support for IID_ISchemaDB to support typesafe binding // from VBA. This IID must match the GUID that is attached to the // dispinterface in the .ODL file. // {D9C40DC5-0339-11D3-B7EE-00E02916C424} static const IID IID_ISchemaDB = { 0xd9c40dc5, 0x339, 0x11d3, { 0xb7, 0xee, 0x0, 0xe0, 0x29, 0x16, 0xc4, 0x24 } }; BEGIN_INTERFACE_MAP(CSchemaDBDoc, CDocument) INTERFACE_PART(CSchemaDBDoc, IID_ISchemaDB, Dispatch) END_INTERFACE_MAP() ///////////////////////////////////////////////////////////////////////////// // CSchemaDBDoc construction/destruction CSchemaDBDoc::CSchemaDBDoc() { // TODO: add one-time construction code here m_pTableset = NULL; m_pColumnset = NULL; m_strTableName = _T(“”); EnableAutomation(); AfxOleLockApp(); } CSchemaDBDoc::~CSchemaDBDoc() { AfxOleUnlockApp(); } BOOL CSchemaDBDoc::OnNewDocument() { if (!CDocument::OnNewDocument()) return FALSE; // TODO: add reinitialization code here // (SDI documents will reuse this document) // initialize current view level m_nLevel = levelNone;
n Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC
484 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93. 94. 95. 96. 97. 98. 99. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125.
// initialize table settings m_bSystemTables = GetProfileValue(_T(“TableSettings”), _T(“SystemTables”)); m_bViews = GetProfileValue(_T(“TableSettings”), _T(“Views”)); m_bSynonyms = GetProfileValue(_T(“TableSettings”), _T(“SystemTables”)); // initialize column info settings m_bLength = rofileVal m_bPrecision = GetProfileValue(_T(“ColumnInfoSettings”),_T(“Precision”)); m_bNullability = GetProfileValue(_T(“ColumnInfoSettings”),_T(“Nullability”)); return TRUE; }
///////////////////////////////////////////////////////////////////////////// // CSchemaDBDoc serialization void CSchemaDBDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) { // TODO: add storing code here } else { // TODO: add loading code here } } ///////////////////////////////////////////////////////////////////////////// // CSchemaDBDoc diagnostics #ifdef _DEBUG void CSchemaDBDoc::AssertValid() const { CDocument::AssertValid(); } void CSchemaDBDoc::Dump(CDumpContext& dc) const { CDocument::Dump(dc); } #endif //_DEBUG ///////////////////////////////////////////////////////////////////////////// // CSchemaDBDoc commands
Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC n 126. 127. 128. 129. 130. 131. 132. 133. 134. 135. 136. 137. 138. 139. 140. 141. 142. 143. 144. 145. 146. 147. 148. 149. 150. 151. 152. 153. 154. 155. 156. 157. 158. 159. 160. 161. 162. 163. 164. 165. 166. 167. 168. 169. 170. 171. 172.
void CSchemaDBDoc::SetLevel(Level nLevel) { m_nLevel = nLevel; UpdateAllViews(NULL); } CString CSchemaDBDoc::GetDSN() { // Check to see if the database is open if (!m_pTableset) return _T(“[No Data Source Selected]”); // pull DSN from database connect string return m_strConnect; } void CSchemaDBDoc::FetchColumnInfo(LPCSTR lpszName) { if (m_pColumnset) { delete m_pColumnset; m_pColumnset = NULL; } m_pColumnset = new CColumns; HRESULT hr = m_pColumnset->Open(m_session, NULL, NULL, lpszName); if (FAILED(hr)) { AfxMessageBox(_T(“Couldn’t open column rowset”)); delete m_pColumnset; m_pColumnset = NULL; } } BOOL CSchemaDBDoc::FetchTableInfo() { if (m_pTableset != NULL) { delete m_pTableset; m_pTableset = NULL; } m_pTableset = new CTables; // Must use char array for ODBC interface // (can simply hard code max size) char lpszType[64];
485
486
n Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC
173. strcpy(lpszType, “TABLE”); 174. if (m_bViews) 175. strcat(lpszType, “,VIEW”); 176. if (m_bSystemTables) 177. strcat(lpszType, “,SYSTEM TABLE”); 178. if (m_bSynonyms) 179. strcat(lpszType, “,ALIAS,SYNONYM”); 180. 181. if (m_pTableset->Open(m_session, NULL, NULL, NULL, lpszType)!= S_OK) 182. { 183. delete m_pTableset; 184. m_pTableset = NULL; 185. return FALSE; 186. } 187. 188. return TRUE; 189. } 190. 191. BOOL CSchemaDBDoc::OnOpenDocument() 192. { 193. USES_CONVERSION; 194. 195. // close and delete any open recordsets 196. if (m_pTableset) 197. { 198. delete m_pTableset; 199. m_pTableset = NULL; 200. } 201. 202. if (m_pColumnset) 203. { 204. delete m_pColumnset; 205. m_pColumnset = NULL; 206. } 207. 208. if (m_session.m_spOpenRowset != NULL) 209. m_session.m_spOpenRowset.Release(); 210. 211. // close the database 212. if (!m_strConnect.IsEmpty()) 213. m_strConnect = “”; 214. 215. // open the database 216. if (m_source.Open(AfxGetMainWnd()->GetSafeHwnd()) != S_OK) 217. { 218. AfxMessageBox(_T(“Couldn’t connect to data source”)); 219. m_strConnect = _T(“”);
Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC n 220. return FALSE; 221. } 222. else 223. { 224. USES_CONVERSION; 225. if (m_session.Open(m_source) != S_OK) 226. { 227. AfxMessageBox(_T(“Couldn’t create session on provider”)); 228. return FALSE; 229. } 230. CComVariant var; 231. m_source.GetProperty(DBPROPSET_DATASOURCEINFO, 232. DBPROP_DATASOURCENAME, &var); 233. m_strConnect = OLE2T(var.bstrVal); 234. } 235. 236. if (FetchTableInfo()) 237. return TRUE; 238. else 239. return FALSE; 240. } 241. 242. void CSchemaDBDoc::OnViewSettings() 243. { 244. CPropertySheet sheet(_T(“Settings”)); 245. CTablePage pageTable; 246. CColumnPage pageColumn; 247. 248. // initialize and add table settings page 249. sheet.AddPage(&pageTable); 250. pageTable.m_bSystemTables = m_bSystemTables; 251. pageTable.m_bViews = m_bViews; 252. pageTable.m_bSynonyms = m_bSynonyms; 253. 254. // initialize and add column info settings page 255. sheet.AddPage(&pageColumn); 256. pageColumn.m_bLength = m_bLength; 257. pageColumn.m_bPrecision = m_bPrecision; 258. pageColumn.m_bNullability = m_bNullability; 259. 260. // execute property sheet and update settings 261. if (sheet.DoModal() == IDOK) 262. { 263. BOOL bTableModified = FALSE; 264. BOOL bColumnModified = FALSE; 265. 266. if (m_bSystemTables != pageTable.m_bSystemTables)
487
488 267. 268. 269. 270. 271. 272. 273. 274. 275. 276. 277. 278. 279. 280. 281. 282. 283. 284. 285. 286. 287. 288. 289. 290. 291. 292. 293. 294. 295. 296. 297. 298. 299. 300. 301. 302. 303. 304. 305. 306. 307. 308. 309. 310. 311. 312. 313.
n Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC { m_bSystemTables = pageTable.m_bSystemTables; AfxGetApp()->WriteProfileInt(_T(“TableSettings”), _T(“SystemTables”),m_bSystemTables); bTableModified = TRUE; } if (m_bViews != pageTable.m_bViews) { m_bViews = pageTable.m_bViews; AfxGetApp()->WriteProfileInt(_T(“TableSettings”), _T(“Views”),m_bViews); bTableModified = TRUE; } if (m_bSynonyms != pageTable.m_bSynonyms) { m_bSynonyms = pageTable.m_bSynonyms; AfxGetApp()->WriteProfileInt(_T(“TableSettings”), _T(“Synonyms”),m_bSynonyms); bTableModified = TRUE; } if (m_bLength != pageColumn.m_bLength) { m_bLength = pageColumn.m_bLength; AfxGetApp()->WriteProfile93Int (_T(“ColumnInfoSettings”), _T(“Length”),m_bLength); bColumnModified = TRUE; } if (m_bPrecision != pageColumn.m_bPrecision) { m_bPrecision = pageColumn.m_bPrecision; AfxGetApp()->WriteProfileInt(_T(“ColumnInfoSettings”), _T(“Precision”),m_bPrecision); bColumnModified = TRUE; } if (m_bNullability != pageColumn.m_bNullability) { m_bNullability = pageColumn.m_bNullability; AfxGetApp()->WriteProfileInt(_T(“ColumnInfoSettings”), _T(“Nullability”),m_bNullability); bColumnModified = TRUE; }
Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC n 314. 315. 316. 317. 318. 319. 320. 321. 322. 323. 324. 325. 326. 327. 328. 329. 330. 331. 332. 333. 334. 335. 336. 337. 338. 339. 340. 341. 342. 343. 344. 345. 346. 347. 348. 349. 350. 351. 352. 353. 354. 355. 356. 357. 358. 359. 360.
// check for table modification first if (bTableModified && (m_nLevel == CSchemaDBDoc::levelTable)) { // close and delete any open recordsets if (m_pTableset) { delete m_pTableset; m_pTableset = 0; } if (m_pColumnset) { delete m_pColumnset; m_pColumnset = 0; } // refresh table data FetchTableInfo(); UpdateAllViews(NULL); } // if table settings not modified, check column info else if (bColumnModified && (m_nLevel == CSchemaDBDoc::levelColumn)) { FetchColumnInfo(m_strTableName); UpdateAllViews(NULL); } } } int CSchemaDBDoc::GetProfileValue(LPCTSTR lpszSection,LPCTSTR lpszItem) { int nValue = AfxGetApp()->GetProfileInt(lpszSection, lpszItem,-1); if (nValue == -1) { nValue = 0; AfxGetApp()->WriteProfileInt(lpszSection,lpszItem,nValue); } return nValue; } void CSchemaDBDoc::OnCloseDocument() { if (m_pTableset) { delete m_pTableset; m_pTableset = 0;
489
490 361. 362. 363. 364. 365. 366. 367. 368. 369. 370. }
n Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC } if (m_pColumnset) { delete m_pColumnset; m_pColumnset = 0; } CDocument::OnCloseDocument();
Listing 9-2
SCHEMADBDOC.CPP source code after modifications
The following entries explain what the new code lines do: n
Lines 8-9
n
Lines 55-57
This code sets default values for the UI.
n
Lines 76-82 information.
This code updates the table settings from the schema
n
Lines 85-87 information.
This code updates the column settings from the schema
n
Lines 126-130 actual UI.
This code simply flips the display level and updates the
n
Lines 132-140
This code obtains the DSN string as needed.
n
Lines 142-158 This code actually opens an OLE DB Recordset and provides its column schema information via the internal member variables.
n
Lines 160-189 This code actually opens an OLE DB Recordset and provides its table schema information via the internal member variables.
n
Lines 193-239 This code opens the database specified in the Open dialog. It takes care of closing open Recordsets and deleting existing OLE DB consumers first. Notice that it always starts in table display mode.
n
Lines 244-340 This code handles interaction with the property sheet dialog for the settings for display of column and table information. It’s pretty straightforward MFC code; you’ll write it many times if you do MFC OLE DB development.
n
Lines 357-369 This code makes sure that the two data elements used for the table and column data are deleted before the application closes.
This code brings in the property page dialogs.
Finally, bring up the SCHEMADBVIEW.CPP file in the text editor. Scan through it and enter or change any grayed line of source code in Listing 9-3. Save the project. The section after the code explains what these changes do.
Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC n 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47.
// SchemaDBView.cpp : implementation of the CSchemaDBView class // #include “stdafx.h” #include “SchemaDB.h” #include “SchemaDBDoc.h” #include “SchemaDBView.h” #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // CSchemaDBView IMPLEMENT_DYNCREATE(CSchemaDBView, CView) BEGIN_MESSAGE_MAP(CSchemaDBView, CView) //{{AFX_MSG_MAP(CSchemaDBView) // NOTE - the ClassWizard will add and remove mapping macros here. // DO NOT EDIT what you see in these blocks of generated code! //}}AFX_MSG_MAP // Standard printing commands ON_COMMAND(ID_FILE_OPEN, OnFileOpen) ON_COMMAND(IDM_COLINFO, OnColinfo) ON_COMMAND(IDM_TABLES, OnTables) ON_UPDATE_COMMAND_UI(IDM_COLINFO, OnUpdateColinfo) ON_UPDATE_COMMAND_UI(IDM_TABLES, OnUpdateTables) ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview) END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CSchemaDBView construction/destruction CSchemaDBView::CSchemaDBView() { // TODO: add construction code here } CSchemaDBView::~CSchemaDBView() {
491
n Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC
492 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93. 94.
} BOOL CSchemaDBView::PreCreateWindow(CREATESTRUCT& cs) { // TODO: Modify the Window class or styles here by modifying // the CREATESTRUCT cs return CView::PreCreateWindow(cs); } ///////////////////////////////////////////////////////////////////////////// // CSchemaDBView drawing void CSchemaDBView::OnDraw(CDC* pDC) { CSchemaDBDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here } ///////////////////////////////////////////////////////////////////////////// // CSchemaDBView printing BOOL CSchemaDBView::OnPreparePrinting(CPrintInfo* pInfo) { // default preparation return DoPreparePrinting(pInfo); } void CSchemaDBView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo*/*pInfo*/) { // TODO: add extra initialization before printing } void CSchemaDBView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo*/*pInfo*/) { // TODO: add cleanup after printing } ///////////////////////////////////////////////////////////////////////////// // CSchemaDBView diagnostics #ifdef _DEBUG void CSchemaDBView::AssertValid() const { CView::AssertValid(); }
Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC n 95. 96. 97. 98. 99. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132. 133. 134. 135. 136. 137. 138. 139. 140. 141.
void CSchemaDBView::Dump(CDumpContext& dc) const { CView::Dump(dc); } CSchemaDBDoc* CSchemaDBView::GetDocument() // non-debug version is inline { ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CSchemaDBDoc))); return (CSchemaDBDoc*)m_pDocument; } #endif //_DEBUG ///////////////////////////////////////////////////////////////////////////// // CSchemaDBView message handlers void CSchemaDBView::OnFileOpen() { // TODO: Add your command handler code here CSchemaDBDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (pDoc->OnOpenDocument()) pDoc->SetLevel(CSchemaDBDoc::levelTable); else pDoc->SetLevel(CSchemaDBDoc::levelNone); } void CSchemaDBView::OnColinfo() { // TODO: Add your command handler code here CSchemaDBDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // determine list control selection CListCtrl& control = GetListCtrl(); int nCount = control.GetItemCount(); for (int i = 0; i < nCount; i++) { if (control.GetItemState(i,LVIS_SELECTED)) break; } if (i < nCount) { // pull table name to send to document pDoc->m_strTableName = control.GetItemText(i,0);
493
n Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC
494 142. 143. 144. 145. 146. 147. 148. 149. 150. 151. 152. 153. 154. 155. 156. 157. 158. 159. 160. 161. 162. 163. 164. 165. 166. 167. 168. 169. 170. 171. 172. 173. 174. 175. 176. 177. 178. 179. 180. 181. 182. 183. 184. 185. 186. 187. 188.
#ifndef _UNICODE LPCSTR lpszName; lpszName = pDoc->m_strTableName; #else LPSTR lpszName; char rgchTableName[257]; lpszName = rgchTableName; int nSize; nSize = ::WideCharToMultiByte(CP_ACP,0,pDoc->m_strTableName, -1, lpszName, 257, NULL, NULL); // Notify on failure ASSERT(nSize); #endif // _UNICODE pDoc->FetchColumnInfo(lpszName); pDoc->SetLevel(CSchemaDBDoc::levelColumn); } } void CSchemaDBView::OnTables() { // TODO: Add your command handler code here CSchemaDBDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); pDoc->m_strTableName.Empty(); pDoc->FetchTableInfo(); pDoc->SetLevel(CSchemaDBDoc::levelTable); }
void CSchemaDBView::OnUpdateColinfo(CCmdUI* pCmdUI) { // TODO: Add your command update UI handler code here CSchemaDBDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (pDoc->m_nLevel == CSchemaDBDoc::levelTable && GetListCtrl().GetSelectedCount()) { pCmdUI->Enable(); } else pCmdUI->Enable(FALSE);
Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC n 189. } 190. 191. void CSchemaDBView::OnUpdateTables(CCmdUI* pCmdUI) 192. { 193. USES_CONVERSION; 194. CSchemaDBDoc* pDoc = GetDocument(); 195. ASSERT_VALID(pDoc); 196. 197. // delete all items and columns 198. CListCtrl& control = GetListCtrl(); 199. control.DeleteAllItems(); 200. while (control.DeleteColumn(0)); 201. 202. // set up view based on the document’s level 203. switch (pDoc->m_nLevel) 204. { 205. case CSchemaDBDoc::levelNone: 206. 207. // set the document title 208. pDoc->SetTitle(pDoc->GetDSN()); 209. break; 210. 211. case CSchemaDBDoc::levelTable: 212. { 213. // set the document title 214. CString strDataSource = pDoc->GetDSN(); 215. strDataSource += _T(“ [Tables]”); 216. pDoc->SetTitle(strDataSource); 217. 218. // add columns to display 219. control.InsertColumn(0,_T(“Name”),LVCFMT_LEFT,100,-1); 220. control.InsertColumn(1,_T(“Type”),LVCFMT_LEFT,100,1); 221. control.InsertColumn(2,_T(“Catalog”),LVCFMT_LEFT,100,2); 222. control.InsertColumn(3,_ T(“Schema”),LVCFMT_LEFT,100,3); 223. control.InsertColumn(4,_T(“Description”),LVCFMT_LEFT,100,4); 224. 225. // traverse the table recordset 226. // displaying the table information 227. int item = 0; 228. while (pDoc->m_pTableset->MoveNext() == S_OK) 229. { 230. control.InsertItem(item, 231. pDoc->m_pTableset->m_szName); 232. control.SetItem(item,1,LVIF_TEXT, 233. pDoc->m_pTableset->m_szType,0,0,0,0); 234. control.SetItem(item,2,LVIF_TEXT, 235. pDoc->m_pTableset->m_szCatalog,0,0,0,0);
495
496 236. 237. 238. 239. 240. 241. 242. 243. 244. 245. 246. 247. 248. 249. 250. 251. 252. 253. 254. 255. 256. 257. 258. 259. 260. 261. 262. 263. 264. 265. 266. 267. 268. 269. 270. 271. 272. 273. 274. 275. 276. 277. 278. 279. 280. 281. 282.
n Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC control.SetItem(item,3,LVIF_TEXT, pDoc->m_pTableset->m_szSchema,0,0,0,0); control.SetItem(item,4,LVIF_TEXT, pDoc->m_pTableset->m_szDescription,0,0,0,0); item++; } break; } case CSchemaDBDoc::levelColumn: { int column; // set the document title CString strDataSource = pDoc->GetDSN(); strDataSource += _T(“ - ”); strDataSource += pDoc->m_strTableName; strDataSource += _T(“ [Column Info]”); pDoc->SetTitle(strDataSource); // add columns to display // respect the column info settings values column = 0; control.InsertColumn(column++,_ T(“Name”),LVCFMT_LEFT,100,-1); control.InsertColumn(column,_ T(“Type”),LVCFMT_LEFT,100,column++); if (pDoc->m_bLength) control.InsertColumn(column,_ T(“Length”),LVCFMT_LEFT,80,column++); if (pDoc->m_bPrecision) { control.InsertColumn(column,_ T(“Precision”),LVCFMT_LEFT,80,column++); control.InsertColumn(column,_ T(“Scale”),LVCFMT_LEFT,50,column++); } if (pDoc->m_bNullability) control.InsertColumn(column,_ T(“Nullable”),LVCFMT_LEFT,50,column++); // traverse the column info recordset // respect the column info settings values int item = 0; // If the column rowset couldn’t be opened, don’t attempt to fetch // any data. if (pDoc->m_pColumnset == NULL) break; while (pDoc->m_pColumnset->MoveNext() == S_OK) {
Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC n 283. 284. 285. 286. 287. 288. 289. 290. 291. 292. 293. 294. 295. 296. 297. 298. 299. 300. 301. 302. 303. 304. 305. 306. 307. 308. 309. 310. 311. 312. 313. 314. 315. 316. 317. 318. 319. 320. 321. 322. 323. 324. 325. 326. 327. 328. 329.
CString strValue; // always insert the column name control.InsertItem(item, pDoc->m_pColumnset>m_szColumnName); // always insert the column type column = 1; CString strType; strType.Format(“%d”, pDoc->m_pColumnset->m_nDataType); control.SetItem(item,column++,LVIF_TEXT, strType,0,0,0,0); // only show type if requested if (pDoc->m_bLength) { strValue.Format(_T(“%ld”),pDoc->m_pColumnset>m_nMaxLength); control.SetItem(item,column++,LVIF_TEXT,strValue,0,0,0,0); } // only show precision,scale,radix if requested if (pDoc->m_bPrecision) { // precision strValue.Format(_T(“%d”),pDoc->m_pColumnset>m_nNumericPrecision); control.SetItem(item,column++,LVIF_TEXT,strValue,0,0,0,0); // scale int nOrdinal = pDoc->m_pColumnset>m_nOrdinalPosition; strValue.Format(_T(“%d”),pDoc->m_pColumnset>m_nNumericScale); control.SetItem(item,column++,LVIF_TEXT,strValue,0,0,0,0); } // only show nullability if requested if (pDoc->m_bNullability) { if (pDoc->m_pColumnset->m_bIsNullable == FALSE) control.SetItem(item,column++,LVIF_TEXT,_T(“No”),0,0,0,0); else control.SetItem(item,column++,LVIF_TEXT,_T(“Yes”),0,0,0,0); } item++; }
497
498
n Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC
330. break; 331. } 332. } 333. 334. } Listing 9-3
SCHEMADBVIEW.CPP source code after modifications
The following entries explain what the new code lines do: n
Lines 113-120 display.
n
Lines 126-158 This code connects to the database and gets its information appropriately so the display logic can show it.
n
Lines 165-170
This code updates whether the tables display is shown.
n
Lines 178-187 shown.
This code updates whether the columns display is
n
Lines 193-332 This code is the core display logic. It is pretty well commented, and basically just outputs the information from the OLE DB consumer into whichever view is currently enabled. Most of it is oriented towards the specific requirements of MFC display behaviors.
This code gets the initial database information for
Building the MFC OLE DB Consumer Application Once you are satisfied you’ve entered the code from the above listings correctly, build the OLE DB consumer test application from the Build menu. If you encounter any errors, please check the code in the listing to find your mistake (it has been exhaustively debugged and is correct). Now you are ready to test the consumer.
Testing the MFC OLE DB Consumer Application Now you are ready to test the OLE DB MFC client. This will allow you to browse any OLE DB provider on your system and examine its table and column schemas.
Step-By-Step Start the SCHEMADB application from inside Visual C++ or from the Explorer. When the dialog appears, select the File|Open menu item. Using the dialog, navigate to a database for an OLE DB provider and open it. As shown in Figure 9-20, the database will open and display its tables. Select a table and then click on the Columns Info menu item; this will change the display to show the column names and characteristics of the selected table.
Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC n
499
Figure 9-20 The MFC application displays the database schema for OLE DB providers
Experiment with other databases that are OLE DB providers; you’ll find the application happily shows you all their schema information as well.
Where We Go From Here You have conquered all the basics and some of the advanced aspects of OLE DB as of this point. But OLE DB doesn’t live in its own world anymore; modern developers must understand how to deploy and interact with OLE DB providers and consumers in a world increasingly dominated by the Internet and World Wide Web. Chapter 10 brings you up to speed on how to work in this incredible environment!
Chapter Ten
Putting OLE DB on the Internet At this point, you’re fully up to speed on all that Visual C++ 6.0 has to offer in terms of OLE DB; you can create and use OLE DB components and applications with the best of them. You understand the issues involved in creating OLE DB Automation servers and ActiveX controls and clients in both ATL and MFC, and you’re an ace with both the Visual C++ IDE and the underlying concepts behind OLE DB. So what’s left? The Internet, that’s what! More and more database developers are tasked by their clients to put DBMS (and thus OLE DB) functionality onto web pages, either on a local intranet or the big, bad Internet/World Wide Web itself. Doing this requires an understanding of arcane issues totally unrelated to OLE DB or COM, and so the following sections will fill that essential gap. This chapter will add mastery of the LPK_TOOL application for creating HTML license keys, understanding of the ActiveX Control Pad and the ActiveX SDK’s code-signing tools, and familiarity with the two major tools of Internet and World Wide Web functionality, FTP and Internet Explorer, to your OLE DB skills inventory. Let’s dig in! (Before we start, however, it is important to remember that in most places where you see “ActiveX” below, you can also substitute “COM,” as in our OLE DB COM Automation components.)
Using the LPK_TOOL Application to Create HTML Run-time License LPK Files Some components are sufficiently expensive that developers will want to control even their usage on WWW pages; the mechanism COM provides for this is called an LPK file. Visual C++ 6.0 includes the LPK_TOOL.EXE application to assist with the creation of these files. Figure 10-1 shows the location of the LPK_TOOL.EXE file on the Visual C++ distribution CD. If you have floppy disks instead of a CD, see the “Downloading the ActiveX SDK” section below for an alternative location of the program.
501
502
n Chapter Ten—Putting OLE DB on the Internet
Figure 10-1 The LPK_ TOOL.EXE file on the Visual C++ 5.0 distribution CD
An LPK file is a text file that contains two very important pieces of information relative to Internet usage of COM components. The first element is a copyright notice that tells users not to download the file without permission; this hardly prevents it from happening, but provides legal protection if a copyright infringement suit must be brought against unlicensed users of the software. The second is a MIME-encoded text string that contains the actual LPK license information. Figure 10-2 shows the dialog that is the interface for LPK_TOOL.EXE. It consists of two list boxes and several command buttons. The left-hand list box shows all the COM elements currently registered on your computer; you can restrict it to only those controls that support licensing by checking the lower left-hand check box. To move a component to the right-hand list box, select it in the left-hand list box, and press the Add -> button. To remove a selected component from the right-hand list box, select it and press <-Remove.
Figure 10-2 The LPK_ TOOL.EXE file user interface
The way that LPK files are used is vital in creating them! LPK files are required for each combination of licensed components on a given web page. In other words, if you have three instances of licensed controls on one page and two on another, you have to create a different LPK file for each page, containing the license keys for each licensed control to be used on the page. Only one LPK file is allowed for a page (pages inside frames count as separate pages, however!), and every licensed control used on that page must have its license key in the LPK file or the control will fail to create properly. Therefore, be sure to select all the components that will be used on a given HTML page when creating that page’s LPK file.
Chapter Ten—Putting OLE DB on the Internet n
503
Figure 10-3 shows the Save As dialog that appears when you press the Save & Exit button on the LPK_TOOL main dialog box. Use it to navigate to the same location where you store the components to be uploaded to the web site for your OCX file and its HTML page(s); assign it a unique name based on the page where the Figure 10-3 control will appear. Then press The LPK_ Save to actually create the LPK file. TOOL Save As dialog for creating the LPK file
Figure 10-4 shows the final dialog the LPK_TOOL application will display after successfully creating the LPK file for the selected component group. Listing 10-1 shows the contents of a sample LPK file. The garbled characters at the end of the file contain the MIME-encoded license key.
Figure 10-4 The LPK_ TOOL success message final dialog LPK License Package ////////////////////////////////////////////////////////////////////// // WARNING: The information in this file is protected by // // copyright law and international treaty provisions. // // Unauthorized reproduction or distribution of this file, or // // any portion of it, may result in severe criminal and civil // // penalties, and will be prosecuted to the maximum extent // // possible under the law. Further, you may not reverse // // engineer, decompile, or disassemble the file. // ////////////////////////////////////////////////////////////////////// {3d25aba1-caec-11cf-b34a-00aa00a28331} AUCU+GTT0BG32kRFU1QAAA= AQAAAA=1FByMCfS0BG32kRFU1QAABQAAABtAGsAagBpAG0AawBuAGkAbwBpAG4AbQB1AGoAbwBpAG4AZwBuAGcA=
Listing 10-1
A sample LPK file
504
n Chapter Ten—Putting OLE DB on the Internet
Downloading the ActiveX SDK
Figure 10-5 The download page at Microsoft’s web site for the ActiveX SDK
Figure 10-5 shows Internet Explorer at the download page for the ActiveX SDK from the Microsoft web site. If you wish to digitally sign your COM components, you will need to obtain a copy of the SDK from this web site. The URL is: http://www. microsoft.com/ intdev/sdk/.
Click on the Download link and follow the directions; a self-extracting compressed file containing the SDK will be downloaded by your browser and placed on your hard drive. Once you have downloaded the file (and its optional documentation), run the self-installing executable; it will place a copy of the SDK on your hard drive. Figure 10-6 shows the BIN subdirectory of the ActiveX SDK once it has been installed on a local computer. Notice it has two very important applications: LPK_TOOL.EXE (this is where to find it if you don’t have the Visual C++ 6.0 CD) and SIGNCODE.EXE. The second file will be of vital importance in the Figure 10-6 next section on code The ActiveX signing. The rest of the SDK BIN subSDK consists of various directory help files, documentashowing tion, and other tools to LPK_TOOL and SIGNassist in creating CODE appliInternet components cations for Microsoft Windows.
Chapter Ten—Putting OLE DB on the Internet n
505
Code Signing using Authenticode™—Obtaining an SPC Authenticode™ is Microsoft’s proprietary alternative to the Java sandbox security system. Java’s sandbox is a term for the severe restrictions placed on Java applets; they cannot write to the local hard or floppy drives, cannot use most OS API calls, and otherwise can do very little beside show pictures and accept keyboard input. COM goes beyond this to allow much of the functionality of true application programs in its COM system, but at a price. The COM code element must be digitally “signed” for Internet Explorer or another Authenticode-based system to allow it to operate on the local machine. (You can alter the security settings to permit this, but it certainly is not recommended!)
Figure 10-7 The VeriSign home page
The process of adding a digital signature to an COM element is called code signing, and it requires two steps. The first you have already become acquainted with in the previous section, namely downloading the ActiveX SDK with the SIGNCODE.EXE application. The other step is to obtain an SPC (Software Protection Certificate) from a provider of such digital signatures. The one most commonly used is VeriSign. Figure 10-7 shows the VeriSign home page to acquire a digital signature. Its URL is: http:// digitalid.verisign. com/. The following steps direct you through the process of obtaining your own personal SPC from VeriSign. (If you already have one or don’t wish to obtain one, skip this section and the following one on actual code signing.)
Step-By-Step Click on the Request a Digital ID image button. You should see a display similar to that shown in Figure 10-8 after the browser has navigated to the new page. These are the various kinds of SPCs available from VeriSign.
506
n Chapter Ten—Putting OLE DB on the Internet
Figure 10-8 The VeriSign SPC Services Page
Click on the Software Publisher link. After the navigation is complete, you should see a display similar to Figure 10-9. There are two types of Software Publisher SPCs currently available, both of which are explained here. (Note that to use either service, a valid credit card with at least $20 credit is required. Also, Class 2 SPCs are only available at this time to United States residents.)
Figure 10-9 The VeriSign Software Publisher SPC classes page
If you are an individual or a small business, click the Class 2 link or image. If you are a large business, click the Class 3 link or image. The following steps are for the Class 2 SPC, but the Class 3 steps are very similar. You will see a display similar to Figure 10-10, which is the initial data entry screen for the SPC.
Chapter Ten—Putting OLE DB on the Internet n
507
Figure 10-10 Initial data entry screen for Class 2 VeriSign SPC
Figure 10-11 SPC Subscriber Agreement page
Figure 10-12 SPC submission start screen
Fill out the data entry form very carefully. Obtaining the Class 2 SPC requires that your information be completely accurate and up to date! When you are finished, press the submit image at the bottom of the page. You will then see a display similar to Figure 10-11, the subscriber agreement required to obtain the SPC. The subscriber agreement is an absolute requirement for obtaining the SPC; it legally binds you to behave responsibly with whatever software is encoded using your SPC. After reading and agreeing with it, press the Accept image at the bottom of the page. You will then see a display similar to Figure
508
n Chapter Ten—Putting OLE DB on the Internet 10-12, which is the display page to begin the actual process of obtaining your SPC.
Figure 10-13 Initial PVK Wizard display dialog
Figure 10-14 Path entry dialog for the PVK Private Key File
Make sure there is a removable disk with about 500 K of space in whatever device you previously set up to hold your license files. Then press the Submit button. The browser will begin displaying a series of wizard dialog boxes, the first of which is shown in Figure 10-13. These wizards guide you through the automated process of acquiring your private portion of the SPC. Press the Next button on the first wizard dialog box, and you will see a data entry dialog similar to that shown in Figure 10-14. It is used to enter the location of the removable media device you will use to store your Private Key File (PVK) for use in digitally signing files.
Use the Browse button to navigate to the drive and directory where you plan to store your PVK and SPC files. Then press Finish. The PVK file will be created and stored, and you will see a display similar to Figure 10-15, giving you the URL to return and obtain your SPC file.
Figure 10-15 URL display for retrieval of SPC file
Chapter Ten—Putting OLE DB on the Internet n
Figure 10-16 E-mail client displaying VeriSign confirmation message
Figure 10-17 VeriSign SPC PIN entry form
Figure 10-18 SPC installation preparation screen
509
At this point there will be a delay; if you are obtaining a Class 2 SPC, it will be only a few moments, but if you are obtaining a Class 3 SPC, it may be several days. Eventually you will receive an e-mail message similar to that shown in Figure 10-16, indicating you have been awarded the SPC you requested or explaining why it was not awarded and giving you the steps to follow to correct the problem.
At this point you should reactivate your copy of Internet Explorer and navigate to the URL: http:// digitalid.verisign. com/msgetiscs.htm. You should see a display similar to that shown in Figure 10-17, which is a form to allow entry of your PIN to retrieve the SPC file.
The PIN is in the e-mail message from step 9. Enter it in the text box and press the Submit button. There will be a pause of up to several minutes while secured information is exchanged between VeriSign and your copy of Internet Explorer. Eventually you will see a display similar to Figure 10-18, unless a
510
n Chapter Ten—Putting OLE DB on the Internet problem is encountered. In the latter case, you will be given a phone number to call and will be able to clear up the matter and resubmit your PIN. The display indicates the SPC file is ready to be placed on your removable media device.
Pressing the Install button on the display will bring up the Credentials Enrollment Wizard dialog box shown in Figure 10-19. Make sure the removable disk with your PVK file is available to the system and enter the same path for the SPC file. The IE application will, under control of the VeriSign page, write out your SPC file. You will see a success dialog and should then remove the media with the software key, restoring it to the drive only when using it to Figure 10-19 actually sign code. SPC Enrollment Wizard dialog box
Code Signing using Authenticode™—Digitally Signing Code Now that you have obtained your SPC, here are the steps to actually sign code with it.
Step-By-Step
Figure 10-20 Code Signing Wizard initial dialog
First, bring up an MS-DOS window in the directory INETSDK\BIN of the ActiveX SDK. Enter SIGNCODE and press Enter. Although you are running a DOS mode program, a set of wizard dialog boxes will appear to guide you through the code-signing process, with the initial dialog shown in Figure 10-20. (SIGNCODE.EXE can also be used from the DOS command line; see the ActiveX SDK documentation for details.)
Chapter Ten—Putting OLE DB on the Internet n
Figure 10-21 Code Signing Wizard initial data acquisition dialog
511
Press Next on the initial dialog box. A new wizard dialog appears as shown in Figure 10-21. Fill in the top text entry control with the path and name of the CAB file you wish to sign. Give it a descriptive name in the middle text control. Finally, enter a URL for your web site or a README filename on the local drive.
Make sure the removable media with your SPC and PVK files on it is available to the system. Press Next; the wizard dialog shown in Figure 10-22 will appear. It has two text controls which should already be filled in with the name and paths to the PVK and SPC files created in the “Obtaining an SPC” section. If they are not, it is probably because you have not inserted the removable disk in the drive containing them; do so and use the Browse buttons to obtain the paths and filenames for your Figure 10-22 PVK and SPC files. Code Signing Wizard PVK/SPC path entry dialog
Pressing Next after completing the PVK and SPC file entry will bring up the dialog shown in Figure 10-23. It is a confirmation dialog that allows you to go back if needed and change parts of the code-signing data.
Figure 10-23 Code Signing Wizard data confirmation dialog
512
n Chapter Ten—Putting OLE DB on the Internet
Figure 10-24 Code Signing Wizard final confirmation dialog
Pressing Next moves you to the dialog shown in Figure 10-24. It is the final gateway prior to activating the code-signing process. If for some reason you do not wish to continue the process, press Cancel; if you remember a needed data change, press Back. Otherwise press Sign to actually sign your code.
Once the signing process is complete, you will see a success dialog similar to that shown in Figure 10-25. It indicates the file has been digitally signed and is ready for secure Internet, intranet, or World Wide Web usage. If for any reason you recompile or re-create the file, however, you must repeat the entire signing process! If the code-signing process fails for any reason, an explanatory dialog box will appear; consult the ActiveX SDK documentation files for directions as to fixing the problem. (It is usually either an invalid SPC or PVK file, or a problem with locatFigure 10-25 ing the file to be signed or the PVK Code-signing or SPC files.) success dialog
Figure 10-26 Confirmation dialog showing code-signing certificate
Once a file has been digitally signed, Microsoft has provided a way to check the file prior to using it over the networks. This utility is called CHKTRUST, and it is another MS-DOS utility in the same directory as SIGNCODE. Activate another MS-DOS window and move to the \INETSDK\BIN\ directory. Enter CHKTRUST -C and the path to the signed CAB file. After a moment, you should see a certificate display similar to that shown in Figure 10-26 except using your name and the distinctive name you gave the control. If this dialog does not appear, make sure you have the added -C and the correct filename and path on the command line
Chapter Ten—Putting OLE DB on the Internet n
513
for CHKTRUST. You now have a signed, guaranteed-safe CAB file with a COM component ready to be distributed over the World Wide Web!
Obtaining and Using ActiveX Control Pad from Microsoft Microsoft has kindly provided a very powerful utility for adding COM components to HTML pages, called ActiveX Control Pad. The URL to obtain it from Microsoft is http://www.micro soft.com/workshop/author/cpad. Figure 10-27 Figure 10-27 shows Download the download location page for ActiveX Conat Microsoft’s web site trol Pad from for obtaining it. Click Microsoft on the Download link and follow the directions to place the self-extracting install program on your hard drive. Then execute it, and it will install itself to your computer. Bring up the application, and you will see a display similar to Figure 10-28. There are four critical areas of functionality that the ActiveX Control Pad provides, over and above its Notepad-like text editor (which has no special HTML features, unfortunately): automatic insertion of tags for COM components, including visual editing of the component desired, creation of the powerful new HTML Layout control layout files (called ALX Figure 10-28 but still HTML-based), visual ActiveX Conediting and insertion of HTML trol Pad’s initial user Layouts into HTML pages, and interface ActiveX scripting automation for both HTML pages and HTML Layouts. Each of these areas is vital to effective use of COM components over the Internet, and is explained below.
514
n Chapter Ten—Putting OLE DB on the Internet
Automatic COM Tags/Visual Editing Figure 10-29 shows the menu used to insert an ActiveX control into an HTML page. Selecting it causes the Control Pad to search the Registry for the computer and produce the dialog box shown in Figure 10-30, which lists all the available COM components on the host machine. Since this is a design activity, those controls that require license keys will not work unless the key is installed on the host computer.
Figure 10-29 The Edit| Insert ActiveX control menu item Figure 10-30 A dialog box listing all available COM components
Once you select the component to use (you can choose only one at a time since that’s the way the tag works), be prepared for a significant delay while the Control Pad convinces OLE to let it have the control; the less RAM and the more ActiveX controls you have on your system, the longer this will take (on my machine it’s about five minutes!). Figure 10-31 shows the various parts of the ActiveX Control Pad Editor interface. The window marked “A” is the main visual editor pane. It allows you to size the control and observe its visual characteristics; however, it does not keep position information (unlike the HTML Layout control discussed below), so moving the control away from the top of the editor window has no net effect. The window marked “B” is the core of AXC editing in the Control Pad. It contains all the available properties for the control, and either accepts a value for them via typing into the upper text control (next to the Apply button) or supplies a drop-down list of values. Sometimes a custom editor
Chapter Ten—Putting OLE DB on the Internet n
515
button will appear to the right of the upper text control; pressing it will bring up a custom property editor for that particular property. The window marked “C” is not available for all controls, only those that support Property Pages. It’s accessible from the right-click shortcut menu of the object inside the visual editing window, and is always the lowest “properties” entry there. It supplies user-written visual editing of property values for the control, as shown in the figure.
Figure 10-31 ActiveX Control Pad’s AXC editor interface
Once you have made all the changes you like to the visual and informational state of the ActiveX control in the editors, click the close button of the main visual editor. This will close all the ActiveX control editor windows and cause the system to write out an tag into the HTML file at the position of the cursor when the Edit menu option was invoked. This is shown in Figure 10-32, where I have accidentally placed the tag in an invalid position, inside the block. (I moved it after discovering this...:)). Notice the tags written after the main tag; these are where the custom values set in the ActiveX Editor are stored so the browser or other application can re-create them. Some Figure 10-32 defective controls don’t corAn rectly store their visual editing results this way, which will tag inserted result in those settings being Into the lost when the control is HTML by re-created in the target ActiveX Conenvironment. trol Pad
Creation of HTML Layout Controls The big question is: What the heck is an HTML Layout control, and what has it got to do with Visual C++ ActiveX control creation and usage? The answer lies in the usage part; HTML Layout controls are the hottest thing to come along since frames, and maybe even since HTML itself! Right now they are a
516
n Chapter Ten—Putting OLE DB on the Internet supported extra requiring a download from Microsoft when a layout is first encountered. However, their functionality is already built into Internet Explorer 5.0 and Windows 2000, and so learning about them now will only put you ahead of the curve! As to what they do, the answer is also simple: They provide three key elements previously missing from HTML: exact, pre-calculated two-dimensional placement of ActiveX controls on the HTML page, overlapping of controls along with transparency, and three-dimensional ordering of control display (also called z-ordering). With these capabilities, an HTML Layout allows a group of ActiveX controls on a web page to behave exactly like a Windows dialog: They always come up in the same positions with the same arrangement, no matter what HTML environment they are placed within. By clever use of z-ordering and transparency, sophisticated effects equal to those found in commercial games and multimedia presentations can be easily achieved. At present, the only way to create HTML Layouts is with ActiveX Control Pad. They end up becoming data files for the HTML Layout control itself (which is itself an ActiveX control and must be directly inserted into the HTML to function), supplied with a nonstandard ALX file extension since although they are HTML based, their tags are not supported by other browsers at the present time (such as Netscape’s products). The ALX file(s) must be placed on the web site along with the HTM file that references them; Internet Explorer will automatically download both the HTM and ALX files to reconstruct the page as needed.
Using HTML Layouts requires configuring their principal visual component, called the Toolbox. To start the process, select the File|New HTML Layout menu option. After a relatively lengthy delay (about the same as for adding an ActiveX control, and heavily dependent on the individual state of your computer), the Layout Editor will appear, along with the Toolbox dialog. The Toolbox will initially have two tabs on its display. Click on the one marked Additional to move it into view; notice that it has room for additional controls. Right-click on the Toolbox dialog’s Additional page to bring up its shortcut menu, and select AddiFigure 10-33 tional Controls. You will see a display Choosing to similar to Figure 10-33, as another add available dialog appears showing all the availcontrols to able controls on your computer. the HTML Unlike the ActiveX Editor, however, Layout Editor Toolbox you can select multiple controls with this dialog box. When you select OK, all the controls you have checked will have a small image placed on the current palette of the Toolbox. You will then be able to visually click on them and drag their size on the Layout to position a new copy, just like in the Forms Editor Window of Visual C++!
Chapter Ten—Putting OLE DB on the Internet n
517
Select an interesting control or two, press OK, and observe as the system adds their icons to the palette. There is also a way to add or modify the pages of the Toolbox dialog. Right-click on the tabs themselves, and a new shortcut menu will appear, as shown in Figure 10-34. This menu permits a number of important operations on the pages, including adding, deleting, or renaming a page, and changing its tooltip text. Also, pages can be saved into files for moving between copies of the Control Pad, either over the Internet or on different computers at the same location. Pages can also be moved from one position to another in the sequence of display. Once more pages Figure 10-34 are available than fit on the current size of Adding and the display, two small scroll buttons appear modifying to the right of the tabs, as shown in the pages of the Toolbox dialog figure.
Visual Editing of HTML Layout Controls Figure 10-35 shows a sample HTML Layout being edited after construction. Notice the precise way in which the various controls are laid out; the control will maintain this appearance regardless of what HTML page it is displayed within! The controls shown in this page come from the Standard tab of the Toolbox, which includes Internet-ready versions of most of the controls provided with Visual C++ 6.0. Shown in the figure are a TextBox, a CommandButton, and an Image control. Also shown is a custom control created in another project; it was added using the functionality in the previous section and works Figure 10-35 just like the other controls. An HTML Layout under construction
To place a control on the Layout, simply click on it in the Toolbox, then click and drag on the Layout Editor pane to the desired size of the control. It will draw itself and add itself to the Layout as soon as you release the mouse. If you want to move it, simply click on it and drag its center; to change its size, click on it and drag an edge, just like in Visual C++. You can also cut, copy, and paste the controls just like in Visual C++; they always appear on the center of the Layout when pasted. Clicking outside the
518
n Chapter Ten—Putting OLE DB on the Internet area of a control and dragging around multiple controls selects them as a group; there are special group menu options that are mainly used for OptionButton controls. Controls can overlap, simply by placing them in such a way as to cause the effect. To determine which control is drawn “on top of” another control, use the four buttons showing overlapping controls on the toolbar, or use the Format menu options. You can make a given control go all the way in front of other controls, go all the way behind them, or incrementally move it forward or backward. This is called “z-ordering,” and is very powerful because, among other things, it can be controlled at run time with ActiveX scripting (discussed below). Controls have properties; these are accessible via the Properties dialog just like in Visual C++ and the other ActiveX Editor. There is a feature which is unique to the Layout, however: BackStyle. This property can be set to either opaque (the default) or transparent. If set to transparent, the background brush for the control is set to bsClear, meaning that when it draws itself, its background color won’t show up, allowing things “behind it” (i.e., drawn previously in z-order) to be visible. Obviously, the control must not explicitly draw itself with an opaque brush style, or the effect won’t take place; since many controls let Windows draw their background, however, the effect can be quite easily obtained and is very striking! The other properties of the control are also accessible via the Properties dialog, and can trigger custom editors like the other ActiveX Editor did. Also, there is a shortcut menu for the controls in the Layout Editor that includes bringing up their Property Pages, showing the Script Wizard (explained below), and saving the HTML Layout and showing it in text form in Notepad or other default text editor. In addition, the shortcut menu supports the four z-order positioning choices. Once a Layout is constructed as desired, it must be saved using the Save options of the Toolbar or menus. Once saved, to show it in the HTML page, the menu option Edit|Insert HTML Layout must be chosen at the spot desired in the HTML code. A dialog will appear, prompting for the choice of Layout file; an tag will then be written into the HTML to show the Layout at that point in the HTML stream. Unfortunately, there is no way at present to position two Layout controls effectively on either the same HTML page or a single Layout. In the case of multiple Layout controls on the same HTML page, they simply get treated like any other object and drawn in the sequence found in the HTML. The Layout control itself will not permit another copy of itself to be placed on it, to prevent stack explosions and other fun recursive catastrophes.
Chapter Ten—Putting OLE DB on the Internet n
519
ActiveX Script Wizard for HTML and ALX But the real power of the ActiveX Control Pad, and in may ways of COM itself, is the feature called ActiveX scripting. It is a way for small program fragments, called scripts, written directly inside the HTML or ALX page itself, to directly manipulate the browser and other ActiveX controls, including HTML Layout controls and their embedded controls! Books can and have been written on the subject of ActiveX scripting (including one by the author), so the subject is much too large to be covered here in any real detail. But to give you enough information to get started, here is the scenario: Once you place an ActiveX control or other COM element on an HTML page, you cannot manipulate it using the editor tools used to create the page itself. Running in the browser, it can only be changed via the facility of scripting. By including small program elements written in VCScript language (a subset of Visual C++ itself) or in JScript (Microsoft’s proprietary version of Netscape’s JavaScript), you can achieve at run time in the browser what was previously only possible in the development environment! You can change properties, call methods, and respond to events, just as if the control was running in Visual C++ or the ActiveX Control Pad.
Figure 10-36 The Script Wizard in the HTML Layout Editor
The key to including this incredible capability is the Script Wizard. It is shown in Figure 10-36, invoked from the HTML Layout Editor; it can also be called up from a regular HTML page in the main editor. The Script Wizard consists of two large treeview controls; the left-hand one is called the Events Pane and the right hand one is the Actions Pane. At the bottom is the Code Pane, which is shown in List View mode in the figure.
The procedure to utilize the Script Wizard is quite simple: You select the event to which you wish to add behavior in the Events Pane (in the figure it is the Click event of the Command Button control on the Layout), and then select either the method you wish to invoke or the property you wish to change in the Actions Pane. In the figure, the custom control’s BackColor property is being changed. In List View mode, dialog boxes will appear to prompt you for values to add, and the calls to methods will simply be placed into the list of statements. This method is very useful for simple scripts and does not require a great deal of understanding of scripting, since the environment handles most of the work.
520
n Chapter Ten—Putting OLE DB on the Internet However, if you need to get down and dirty, there is an alternative mode: Code View. In this mode, you must enter all the code yourself; clicking on methods and properties merely adds a line of code referencing that element without placing any other components like equals signs and without prompting you for new values. The advantage of Code View mode, shown in Figure 10-37, is that you can enter most scripting statements by hand, such as variable declarations, changes to the parameters of procedures and functions, and new procedures and functions. The shortcut menu for the Action Pane also provides this capability, somewhat restrictedly. (It cannot declare local variables, for example.) Also, to call methods with parameters, you must use Code View mode, since those methods will not even be shown in List View mode.
Figure 10-37 The Script Wizard in Code View mode
When you are finished adding your script, press OK and the appropriate tags will be written into the HTML or ALX file as appropriate. Figure 10-38 shows the results of creating an HTML page with an ActiveX control, an HTML Layout control, and a script. Each of these code blocks has a small glyph in the left-hand gutter; click on it to bring up that element in the appropriate editor (ActiveX, HTML Layout, or Script Wizard). Saving the HTML file will allow all this useful functionality to come to life in Internet Explorer.
Figure 10-38 A very functional HTML page
Chapter Ten—Putting OLE DB on the Internet n
521
Obtaining and Using File Transfer Protocol (FTP) Software to Place COM Elements on the Internet Now that you’ve gone to all the trouble of creating COM elements just for use on the Internet and World Wide Web, it seems appropriate to put them where they can be available on those networks! Unfortunately, neither Visual C++, nor ActiveX Control Pad, nor even Internet Explorer will help you do this. Instead, you need a special type of Internet software called an FTP client. While there is a text-mode version (shudder!) available from the DOS command line in Windows 95 and Windows NT, it seems more reasonable to find one that is Windows-based and nicely laid out. Fortunately, there is a shareware program called WS_FTP, written over ten years ago by John A. Junod, and now distributed commercially. Figure 10-39 shows the web site of the company that now markets WS_FTP, with the download location for a free evaluation copy. If you prefer freeware, you can also obtain older freeware versions of the program from many FTP sites using InfoSeek or Yahoo’s search facilities. Also, many Internet service providers (ISPs) distribute the freeware version of WS_FTP to their customers, so check your computer for that filename, because it is very likely you already have it Figure 10-39 installed. Downloading WS_FTP FTP client
At this point, you may be wondering why all this bother! Well, the answer lies in the way the Internet and World Wide Web work. Browsers are designed to retrieve files from web and FTP server computers, not send them. The only way to move files around the Internet is FTP; it was one of the original protocols and is still the most widely used, since most download requests by browser software actually use the FTP protocol. In order to transfer your hard-written COM components and HTML/ALX files up to the web site you will be using, you need FTP! Figure 10-40 shows the WS_FTP client’s startup screen. It is fairly typical of FTP software, so if you are using another graphical package, it should have the same functionality, just arranged a little differently.
522
n Chapter Ten—Putting OLE DB on the Internet
Figure 10-40 The WS_FTP client startup screen
Most of the problems encountered with FTP happen at login, so this section will attempt to anticipate most of them so you won’t have to. First, notice the Profile Name box. It is a very good idea to enter a unique informational name for the web server you’ll be using and click the Auto Save Config check box so that your information only has to be entered once. If you choose not to save the configuration automatically, you can still save it manually from the File menu before you close the connection. Next, note the Host Name box. This is where you enter the Internet address of the web server you will use. For this example, it is my web provider’s address, www.tde.com; you would enter your own address, of course. You can also enter the “dotted decimal” version, like “158.158.12.140”, but make sure you have it exactly right, or no end of problems will ensue! Do not enter path information to your own web pages here, or you’ll hopelessly confuse the poor thing. Below Host Name is Host Type, a drop-down combo box. The UNIX option is standard, but you can also choose to autodetect the operating system of the web server, or use one of the other choices. (If your provider is supporting Microsoft’s Internet Information Server, the operating system may not appear in older versions and must be autodetected.) This is a vital choice; making the wrong one will either crash the program or cause it to send bad data to your server. The User ID field is your user name for the web server. If the server is for your personal web pages, it will be your ISP account user name; if it is for your business, it will be the company account’s name. The Password field is the password, of course; make sure not to use it elsewhere on the display (like the comment field!), as the records for the WS_FTP program are kept in ordinary text that anyone can examine if they get access to your computer.
Chapter Ten—Putting OLE DB on the Internet n
523
The Account field is optional; it is used for larger servers that have account names besides their user name. The Initial Directories fields are used to store the information about startup directories the program will change to, both on the local machine and the remote machine. If either path is invalid, an error message will display but the connection will not be lost. The Comment field can be used to store short text descriptions of the location to make it easier to distinguish between sites with similar names and profiles. Aside from the Auto Save Config option mentioned earlier, there are two other possible settings for the login dialog. Anonymous Login is used for FTP sites that allow such connections; it won’t be used for web server interactions. The Save Password option allows saving the password in an encrypted text block inside the normal text of the record; it is relatively safe and normally poses no security risk (not to mention making it a lot easier to use the system!). Once you have filled out all the fields to your satisfaction, press OK and you will be connected to the server, if possible. There are several situations where connecting to the server may be impossible. One is that the server itself is down, making contact out of the question. Another common problem is traffic overload on larger and more popular servers, making getting a connection difficult and requiring multiple connection attempts over time. Finally, the network itself sometimes is either impossibly slow or down in some segments; in this case, you will receive either an “unable to locate hostname” error from Winsock or hang after initial contact is made. In all these cases, the only solution is patience; either try again later, or keep trying until you get a good connection.
Figure 10-41 The WS_FTP client adding a remote directory
Figure 10-41 shows the WS_FTP client performing one of the most vital chores of maintaining a web site: adding new directories. The procedure is quite simple; navigate in the right-hand list box until you find the directory on your web server where you need the new directory created. Then press the MkDir button, and the dialog box shown in the figure will appear. Enter a valid name (remember operating system
524
n Chapter Ten—Putting OLE DB on the Internet peculiarities!) and press OK, and the client will attempt to create the directory on the remote computer. If it succeeds, the display will flip to show the new directory in the list box; otherwise, an error message dialog will appear. Once the directory is created, it can be navigated into, have subdirectories created under it, and so forth, just like any other directory on the remote machine. Other options available both on the remote and the local computer are explicitly changing the directory with ChgDir and removing a selected directory with RmDir. (Directories cannot be renamed; they have to be deleted and re-created.) Files can be viewed (downloaded into a viewer, best with text files only), executed either locally or remotely (where possible, often not on web servers), deleted, and renamed. In addition, the display can be refreshed and the actual text of the FTP directory output viewed with the DirInfo command. There are two other very important functions shown in the figure: the type option buttons and the Log Window. The type options are either ASCII or Binary, with an Auto check box as well. The default mode is Binary, which is an absolute requirement for all ZIP, CAB, EXE, DLL, OCX, and image files; they are all binary images and must be transmitted exactly as found. The other mode, ASCII, is used only for text files, but has an advantage over binary: If the source computer is a Windows machine but the server is a UNIX computer, text files are handled differently between the two computers, which can result in inconsistencies in the display and manipulation of the text. Choosing ASCII mode will allow the system to determine which format is needed on each end, and add or remove control characters as needed. The Auto check box allows the program to attempt to determine for itself which mode is needed, by checking file extensions and other clues. The best way to use this feature is to keep it at Binary except when sending HTML and ALX files. The Log Window displays the actual text conversation going on between the local computer and the web server. It is only of real use to techno-weenies like the author, but if you start experiencing odd problems with files going up to your web server, this is the first place to look for error or warning messages or unusual events of any sort. Usually your web server administrator will ask for a copy of this data; you can use the LogWnd button at the top of the dialog box to bring the text up in Notepad and save it as a text file. The other buttons at the top of the display control closing the current connection, making a new connection, and setting options for the program, most of which only apply for special, heavy-duty FTP use that we won’t worry about. (They can be useful if you need them, though.) Figure 10-42 shows the guts of FTP: a file upload. Here are the steps you take to perform this task: First, navigate to the directory on the local computer where the files to be stored are located; you can use the tree control or the explicit ChgDir dialog box. Once there, use Ctrl and Shift to select the files you want to upload; then make sure you have the Binary/ASCII type setting
Chapter Ten—Putting OLE DB on the Internet n
525
correct (only use ASCII for HTML, TXT, and ALX files). Then press the -> button in the center of the two treeview controls. This will cause the upload to begin. As each file is uploaded, a small dialog box will appear as shown in the Figure 10-42 figure; it will give the size The WS_FTP of the file, the total bytes client uploaduploaded so far, and a ing a file to visual gauge of the perthe web centage of completion. server The dialog is destroyed and re-created for each file, which makes things rather annoying with a lot of small files, because it unfortunately grabs both the mouse and keyboard focus each time it does this. Therefore, don’t plan on doing anything else while you upload or download with WS_FTP. Once the upload is complete, the new files will appear in the right-hand treeview control. Note that overwriting is normally silent; make sure you are where you want to be before sending files up. Also, be aware that UNIX is case-sensitive on filenames; MyFile and MYFILE are totally different files under UNIX, unlike DOS or Windows. (Windows NT is case-sensitive, however!) A final caveat regarding pathnames for web servers. Your account on the web server computer will have a somewhat different directory path than that used on the Internet or World Wide Web. The reason is something called “aliasing.” It is a trick UNIX and Windows NT administrators use to allow them to restructure their hard disks at will without breaking thousands of web pages every time they do so. Most paths that are appended to a web server Internet address begin with the tilde character “~”. (For example, http://www.tde.com/~ciupkc/index.html.) The tilde character is the syntax for an alias; it causes the operating system to look up where “ciupkc” actually is and redirect the path there. This happens invisibly, and so your users think that your web files live in a “ciupkc” directory when they actually live in “usr/home/ciupkc/public_html/”. The reason this is important is that FTP never heard of tildes! Instead, you have to know where your account actually lives with FTP. Fortunately, most FTP servers for web servers look up the account of the user logging in (since they normally don’t have access to other directories, for obvious reasons.) and place the user in the directory that corresponds to their
526
n Chapter Ten—Putting OLE DB on the Internet “tilde” address. Some don’t, however, and in other cases you may need to access a different alias, such as with large corporate accounts. The key factor to remember in all of this is that the directory path you observe in the FTP client is, in most cases, not the one the user will see on the Internet. Consult your web server administrator for serious difficulties in this area; only they know for sure.
Using Internet Explorer to Access COM Over the Internet and World Wide Web At last, you are ready to look at the spiffy COM content you have laboriously authored, compressed, digitally signed, and painstakingly uploaded to your web server. Let’s do it! How? Internet Explorer! Figure 10-43 shows Internet Explorer displaying a custom COM control created with Visual C++. Notice that all that is required is to type in the correct URL (Uniform Resource Locator) for the web page; IE does the rest, downloading the COM components referenced in the HTML, creating the components in memory and obtaining any required support files from the web server or Figure 10-43 other location. All this is done autoViewing a matically; all the user sees is the COM HTML neato-keen COM functionality. And page with this is why COM will rule the Internet Internet and WWW! Rather than Explorer having to mess around with locating and downloading endless new plug-ins (many of which require payment and are often buggy), the browser silently and quickly locates the needed COM elements, brings them onto the local hard drive, and runs them. Like HAL in the movie 2001, Internet Explorer is almost too good a servant! Fortunately, Microsoft has included several very important configuration options in Internet Explorer relating to Active content. To access them, select View|Options from the main menu bar. Figure 10-44 shows the Security tab of the Options dialog, which is where most of the Active Content features live.
Chapter Ten—Putting OLE DB on the Internet n
527
Figure 10-44 IE’s Active Content Options settings
There are four major sections to this page, and each deserves some scrutiny. The top section is used to control content ratings, which are ways to keep younger children from encountering web sites with inappropriate content. IE uses an industry standard rating system, which most commercial content providers and many individual pages support. The details on it are in the online help for IE. The next section is an enhancement to the digital signature system you were shown how to use earlier in this chapter. Rather than having to download a certificate each time a signed control is encountered, this section allows setting a default acceptance for your own signature or that of other individuals (Personal), that of entire web sites (Sites), and those from publishers (Publishers). The increasing level of acceptance does not compromise security since all the accepted items must still have a valid digital signature; instead, it removes the time penalty to display repeated signature certificates from the same person or business when their content is regularly used. The third section contains the most important settings in regard to Active content. This group of four check box controls determines whether your copy of IE will permit downloading of COM elements not already on your local machine, whether it will allow COM elements to function once downloaded, whether it will permit embedded COM scripts to be run once downloaded, and whether Java applets and applications can be run once downloaded. Turning off all these options effectively kills COM for the browser, but there might be reasons for any one of them to be turned off briefly in a security threat environment. The fourth element is contained behind the Safety Level button. Pressing it brings up the dialog box displayed in Figure 10-45.
528
n Chapter Ten—Putting OLE DB on the Internet
Figure 10-45 IE’s ActiveX safety settings
The default setting is High, which refuses all ActiveX content that does not have a digital signature. The Medium setting is best used by developers such as ourselves, and only when we are developing our own work. This setting will allow non-signed ActiveX content to be downloaded and run, but will warn first and provide option levels for safety. The Low setting is only appropriate for completely isolated intranet environments, or those behind extremely tight firewall security. It performs no signature checking and does not warn when any content is downloaded and executed. You have probably heard quite a bit about the “security holes” in Internet Explorer. All of these “holes” require using the browser on Low or Medium security. None of them will happen on High security! So if in doubt, stay on High. (Also be sure you have the latest copy of IE. Microsoft is still giving it away for free as of this writing, so all it will cost you is a little download time and a few megabytes on your hard drive.)
Figure 10-46 IE’s Favorites dialog
Figure 10-46 shows the final figure for this chapter: the Favorites dialog of IE. This dialog is activated whenever you cruise to a useful web site and select Add to Favorites from the menu. It allows you to create new Favorites folders, navigate among existing ones, and save the page in the one of your choice, with a modified title if desired. At first glance, this is pretty spiffy, right?
Chapter Ten—Putting OLE DB on the Internet n
529
Well, it gets better! It turns out that the Favorites system of IE is nothing more than a set of directories under WINDOWS\ FAVORITES\ on the hard drive! The actual favorites entries are stored as Internet shortcuts inside the directories, just like the shortcuts used for the Start menu or other program groups. What that means is that you can use your copy of Explorer to manipulate the Favorites system yourself! You can add directories, move them around, and even add and remove items all with Explorer, without ever using the Favorites system of IE itself. And if you do choose the Organize Favorites menu item, don’t be surprised at how similar the dialog used for this option is to Explorer; it’s actually a junior copy of it. Why bring up Favorites here? Well, in the process of testing the deployment of even the simplest of web sites, you’ll be making a lot of trips to the same URL. Rather than typing it in over and over again, saving it once as a Favorite and then using the Favorite shortcut to go back to it can save huge amounts of typing.
Mastering COM Over the Internet Even though there isn’t a thing about Visual C++ or OLE DB programming in this chapter, I think you’ll agree it is one of the hardest! You’ve been on a whirlwind tour of the Internet aspects of the ActiveX SDK and code signing, ActiveX Control Pad, WS_FTP, and Internet Explorer. You’ve learned how to digitally sign your COM elements and how to create sophisticated HTML and ALX pages with ActiveX controls, HTML Layouts, and ActiveX scripting. You understand how to upload files onto your web server with WS_FTP, and how to download them with Internet Explorer, as well as how to determine what types of Active content IE will permit under security considerations.
Where We Go From Here And so we’ve come to the end of our OLE DB journey in this book. Hopefully you’ve learned how to start unlocking the power of OLE DB for your applications in both ATL and MFC. You have a thorough reference to OLE DB and ADO, which you can refer back to when needed, and a set of clear directions for using the user interfaces in ATL and MFC to create OLE DB provider and consumer projects. You also have four very effective projects that you can tinker with to start your OLE DB applications. But there is much more to OLE DB than the basics we’ve covered here. If I had time and space, I’d love to show you all the extra power under the OLE DB hood. You have to go beyond the simple provider and consumer templates into thick and hairy C++ code to get to the meat of OLE DB; when you do, however, it is well worth it. I am currently developing an OLE DB provider for Outlook Express data files for e-mail and newsgroup postings; keep an eye on my web site (www.ciupkc.com) for details and possible source code. Happy OLE DB coding!
Index A ActiveX, 3-4, 16 scripting, 4, 519-520 ActiveX Control Pad, 513 ActiveX controls, 3-4, 13, 15, 16-17 sizing, 17 ActiveX Data Objects, see ADO ActiveX Script Wizard, 519-520 ActiveX SDK, downloading, 504 ActiveX Template Library, see ATL AddRef method, 9 ADO, 235 collections, 235-242 events, 308-323 objects, 242-308 ambient properties, 19 AppWizard, see ATL AppWizard ATL, 23 and Visual C++, 23-24 downloading, 24 online documentation, 24-25 ATL 3.0, 34-39 and AppWizard, 34 and Object Wizard, 35-36 and shortcut menus, 37-39 ATL AppWizard, 25-26 in ATL 3.0, 34 ATL Object Wizard, 27-30 in ATL 3.0, 35-36 ATL projects creating an OLE DB consumer COM server, 420-426 creating an OLE DB provider COM DLL, 326-368, 426-449 creating an OLE DB service provider COM DLL consumer, 388-400
creating an OLE DB service provider COM DLL provider, 400-403 Authenticode, 505 Automating, 10 Automation, 3, 11, 13-14 see also OLE Automation B behavior, 2 C classes, 52 ClassFactory, 8 ClassWizard, see MFC ClassWizard code listings ACCOUNTINFO.CPP, 397-399 ACCOUNTINFO.H, 395-396 AGENTCONTROLLER.CPP, 440-448 AGENTCONTROLLER.H, 438-439 AGENTPLAYERDLG.CPP, 459-468 AGENTPLAYERDLG.H, 457-458 CHECKINGACCOUNT.H, 393-395 INSTRUCTIONS.H, 434-435, 437 MFC1EDLG.CPP, 411-416 MFC1EDLG.H, 409-411 SCHEMADBDOC.CPP, 482-490 SCHEMADBDOC.H, 479-482 SCHEMADBVIEW.CPP, 491-498 TESTURLPROVDLG.CPP, 376-382 TESTURLPROVDLG.H, 373-375 URLPROV.CPP, 329-331 URLPROVIDERDS.H, 333-334 URLPROVIDERRS.CPP, 332, 360, 361-363 URLPROVIDERRS.H, 335-337, 342, 345, 348, 350-351, 401-403 URLPROVIDERRS.H (modified), 342-344, 346-347, 348-349, 352-354
532
n Index
URLPROVIDERSESS.H, 338-340, 364-367, 367-368 URLROWLOC.H, 355-359 URLS.TXT, 384-385 code signing, 505 digitally signing code, 510-513 obtaining an SPC, 505-510 collections, 235 COM, 1-2, 5-8 accessing over the Internet, 526-529 and Automation, 12-16 and Windows, 10 client, 10 elements, 8-10 interface, 8-9 server, 8 COM ClassFactory, 8 Command object, 259-260 methods, 262-264 properties, 260-262 common controls, 14 Component Object Model, see COM component software, 2, 10 Composite controls, 36 compound document, 3 Connection object, 242-244 methods, 249-259 properties, 244-249 Connection Point system, 19 Connection Points, 20 connection topologies, 20 consumers, 53 containers, 13 ControlWizard, see MFC ActiveX ControlWizard Count property, 236 custom methods, 18 custom properties, 18 D data type names, 146-147 DBCOLUMNINFO structure, 69-71 DBLITERALINFO structure, 84-88 DBPARAMBINDINFO structure, 148-149 DBPARAMINFO structure, 141-143
DBPARAMS structure, 112-113 Dispinterface, 11 DLL, 6-7 dual interfaces, 11 dynamic invocation, 11 dynamic-link library, see DLL E encapsulation, 2 Error object, 299-300 properties, 300-301 Errors collection, 236 methods, 236-237 events, 308 F Field object, 293 methods, 298-299 properties, 294-298 Fields collection, 237 methods, 237-239 File Transfer Protocol, see FTP FTP, 521 client, 521 using, 522-526 H HTML controls, 36 HTML Layout controls, creating, 515-517 editing, 517-518 I IAccessor interface, 53-54 methods, 54-66 IColumnsInfo interface, 66-67 methods, 67-75 IColumnsRowset interface, 126-127 methods, 127-137 ICommand interface, 109 methods, 109-117 ICommandPrepare interface, 138 methods, 138-140 ICommandProperties interface, 117-118 methods, 118-123
Index n ICommandText interface, 123-124 methods, 124-126 ICommandWithParameters interface, 140 methods, 140-150 IConnectionPoint interface, 20 IConnectionPointContainer interface, 20 IDBCreateCommand interface, 99 methods, 99-100 IDBCreateSession interface, 79 methods, 79-80 IDBInfo interface, 80 methods, 80-90 identity, 2 IDispatch interface, 11 IErrorLookup interface, 228-229 methods, 229-232 IErrorRecords interface, 222 methods, 222-228 IGetDataSource interface, 90 methods, 90-91 inheritance, 2 interfaces, 8 sink, 19-20 source, 20 Internet, 12-13, 15 Internet Explorer, 13 using to access COM, 526-529 Internet Information Server, 4 IOpenRowset interface, 91 methods, 91-95 IRowset interface, 150-153 methods, 153-167 IRowsetChange interface, 173-174 methods, 174-187 IRowsetIndex interface, 214-215 methods, 215-222 IRowsetInfo interface, 167-168 methods, 168-173 IRowsetUpdate interface, 187-188 methods, 188-203 ISessionProperties interface, 95 methods, 95-99 ISourcesRowset interface, 75 methods, 75-78 ISQLErrorInfo interface, 232
533
methods, 232-233 ITableDefinition interface, 100 methods, 100-109 ITransaction interface, 203-204 methods, 204-208 ITransactionJoin interface, 211 methods, 212-214 ITransactionObject interface, 210 methods, 211 ITransactionOptions interface, 209 methods, 209-210 IUnknown interface, 9 J Java, 3, 15 sandbox, 505 JavaScript, 13 JScript, 16 L license keys, 502 Lite Composite controls, 36 Lite HTML controls, 36 LPK file, 502 LPK_TOOL, using, 501-503 M macro, 33, 52 methods, 12 adding in ATL, 31 adding in MFC, 48 custom, 18 stock, 18 MFC, 41, 52 and Visual C++, 41 online documentation, 41-42 MFC ActiveX ControlWizard, 42-44 MFC ClassWizard, 44-49 MFC projects creating an OLE DB consumer application, 368-383 creating an OLE DB consumer client application, 449-469 creating an OLE DB consumer MDI application, 471-498
534
n Index
creating an OLE DB service provider consumer application, 404-417 Microsoft Foundation Classes, see MFC Microsoft Transaction Server, see MTS MTS, 4, 235 O Object Linking and Embedding, see OLE object-oriented programming, 1-2 tags, 514-515 Object Wizard, see ATL Object Wizard OCX controls, 3-4, 15 OLE, 2-4 OLE 1.0, 2-4 OLE 2, 2-4 OLE Automation, 3, 10, 12-13 OLE DB, 4, 53, 235 and the Internet, 501 consumer templates, 419 keywords, 80-83 OLE DB projects creating client application with MFC, 449-469 creating consumer application with MFC, 368-383 creating consumer COM server with ATL, 420-426 creating consumer MDI application with MFC, 471-498 creating provider COM DLL with ATL, 326-368, 426-449 creating service provider COM DLL consumer with ATL, 388-400 creating service provider COM DLL provider with ATL, 400-403 creating service provider consumer with MFC, 404-417 testing consumer, 469-470 testing consumer application, 498-499 testing provider, 384-385 testing service provider, 417 P Parameter object, 304 properties, 304-308
Parameters collection, 239-240 methods, 240-241 persistence, 21 polymorphism, 2 properties, 11-12 adding in ATL, 31-32 adding in MFC, 48-49 ambient, 19 custom, 18 stock, 18 Properties collection, 241 methods, 242 Property object, 301 properties, 301-304 Property Pages, 50-51 providers, 53 Proxy Generator, 32-33 R Recordset object, 264-266 methods, 277-293 properties, 266-277 reference count, 9 Release method, 9 S script, 11, 519 scripting, see ActiveX scripting service providers, 387 sink interfaces, 19-20 source interfaces, 20 SPC, 505 obtaining, 505-510 using to sign code, 510-513 state, 2 static invocation, 11 stock methods, 18 stock properties, 18 storage, 21 stream, 21 structured storages, 21 T templates, 33 testing
Index n OLE DB consumer, 469-470 OLE DB consumer application, 498-499 OLE DB provider, 384-385 OLE DB service provider, 417 Type Library, 11 U updating, batch, 266 immediate, 266 V VBA, 12 VBScript, 13 VBX controls, 3, 13-15
limitations of, 14-15 VCL controls, 3 Visual Basic, 12 Visual C++, 14 and ATL, 23-24 and MFC, 41 Visual J++, 13 W Windows and COM, 10 X XACTTRANSINFO structure, 208
535
On the CD The companion CD-ROM contains complete project source code for all the material in the book. To install the CD files: 1. Use the DOS XCOPY command as follows: Open a DOS window, then change to the drive containing the companion CD-ROM. Change to the lodbpcppweb directory. (You must do this to avoid copying all the utilities to your hard drive.) Type XCOPY /S*.* followed by the drive and directory you want the files copied into, then press Enter. DOS will copy all the files (including the web files) to your hard drive. To avoid copying the web portion of the CD, change to the lodbpcpp directory before you type XCOPY. 2. Or navigate using your favorite web browser to the readme.htm file, located in the lodbpcppweb folder, for links to install the projects for the book as zip files. The zip files have been created using directory names; to unzip them and create the directory structure for the projects, simply use WinZip or PKUnzip with the same “use folder names” or “-d” options. The CD also includes several third-party applications and utilities for creating HTML, CDF, OSD, and ASP files; and trial versions of Borland tools. Warning:
Opening the CD package makes this book nonreturnable.