555

Click here to load reader

Learn OLE DB Development With Visual C++ 6.0

Embed Size (px)

Citation preview

Page 1: Learn OLE DB Development With Visual C++ 6.0
Page 2: Learn OLE DB Development With Visual C++ 6.0

Learn OLE DBDevelopment with

Visual C++ 6.0

Nathan Wallace

Wordware Publishing, Inc.

Page 3: Learn OLE DB Development With Visual C++ 6.0

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 meanswithout permission in writing from Wordware Publishing, Inc.

Printed in the United States of America

ISBN 1-55622-634-910 9 8 7 6 5 4 3 2 10001

Product names mentioned are used for identification purposes only and may be trademarks of theirrespective 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

Page 4: Learn OLE DB Development With Visual C++ 6.0

Dedication

To Beth Kohler. Thanks for all the help and patience.

Page 5: Learn OLE DB Development With Visual C++ 6.0

Acknowledgments

Like so many complex undertakings, this book would not have existed with-out support from many people beside the guy who tickled the keyboard. I’dlike to take a moment to thank Beth Kohler, who tried to make vague sense ofmy frantic scribblings. Any leftover typos or illogic is entirely mine.

A big thank you to Russell Stultz, owner of Wordware Publishing, for his careand thoughtfulness in a business all too often filled with the reverse.

And a final thanks to my wife, Laura, for her kindness, support, and patienceduring the creative process.

Above all, I need to thank the Totoro, because without him, I wouldn’t behere at all!

Page 6: Learn OLE DB Development With Visual C++ 6.0

Contents

Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiii

Chapter One A COM Primer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1

What is COM? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1How Did COM Happen? . . . . . . . . . . . . . . . . . . . . . . . . . . . 2Why Do We Need COM? . . . . . . . . . . . . . . . . . . . . . . . . . . . 5How COM Works. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8

The COM Server. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8The COM ClassFactory . . . . . . . . . . . . . . . . . . . . . . . . . . 8The COM Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8The COM Reference Count . . . . . . . . . . . . . . . . . . . . . . . . 9The COM System in Windows . . . . . . . . . . . . . . . . . . . . . . 10The COM Client . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

COM is Static, Automation is Dynamic. . . . . . . . . . . . . . . . . . . 10Automation Opens COM to the World . . . . . . . . . . . . . . . . . . . 12From Automation to OCX to ActiveX . . . . . . . . . . . . . . . . . . . . 13ActiveX Controls are Automation Servers with a User Interface . . . . . . 16Stock Properties and Property Pages Standardize ActiveX Control

Behavior . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17Connection Points Allow Events to be Sent from ActiveX Controls

to Containers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19Persistence Allows ActiveX Controls to Save Their State Over Time. . . . 21Where We Go From Here . . . . . . . . . . . . . . . . . . . . . . . . . . 21

Chapter Two An ATL Primer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

ATL in Visual C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23Getting ATL for Older Visual C++ Versions . . . . . . . . . . . . . . . 24ATL’s Online Documentation . . . . . . . . . . . . . . . . . . . . . . . 24

Creating ATL Projects with the ATL AppWizard . . . . . . . . . . . . . . 25The New Dialog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25The ATL AppWizard . . . . . . . . . . . . . . . . . . . . . . . . . . . 26The Confirmation Dialog. . . . . . . . . . . . . . . . . . . . . . . . . 26

Adding COM Interfaces with the ATL Object Wizard . . . . . . . . . . . 27The Object Wizard Objects. . . . . . . . . . . . . . . . . . . . . . . . 27The Object Wizard Controls . . . . . . . . . . . . . . . . . . . . . . . 27The Object Wizard Miscellaneous Elements . . . . . . . . . . . . . . . 28The Object Wizard Data Access Elements . . . . . . . . . . . . . . . . 28The Object Wizard Names Tab . . . . . . . . . . . . . . . . . . . . . . 28The Object Wizard Attributes Tab . . . . . . . . . . . . . . . . . . . . 29

Page 7: Learn OLE DB Development With Visual C++ 6.0

Specialized Object Wizard Tabs . . . . . . . . . . . . . . . . . . . . . 29Creating Functions and Properties with the ATL Interface Wizards . . . . 30

The ClassView Shortcut Menu . . . . . . . . . . . . . . . . . . . . . . 30The Add Method to Interface Dialog. . . . . . . . . . . . . . . . . . . 30The Add Property to Interface Dialog . . . . . . . . . . . . . . . . . . 31The Edit Attributes Dialog . . . . . . . . . . . . . . . . . . . . . . . . 32

Some Advanced Topics for ATL Projects . . . . . . . . . . . . . . . . . . 32The Proxy Generator . . . . . . . . . . . . . . . . . . . . . . . . . . . 32Advanced Servers . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33

ATL Code (Templates and Macros, Oh My!) . . . . . . . . . . . . . . . . 33Templates Create Custom Classes from Standard C++ Code . . . . . . 33Macros Expand into Customized Code. . . . . . . . . . . . . . . . . . 33

What’s New with ATL Version 3.0? . . . . . . . . . . . . . . . . . . . . . 34Version 3.0 Changes to the AppWizard . . . . . . . . . . . . . . . . . 34Version 3.0 Changes to the ATL Object Wizard . . . . . . . . . . . . . 35Version 3.0 Changes to the ClassView Context Menus . . . . . . . . . . 36

The Add Windows Message Handler Option . . . . . . . . . . . . 36The Implement Connection Point Option . . . . . . . . . . . . . 38The Implement Interface Option . . . . . . . . . . . . . . . . . . 39

Where We Go From Here . . . . . . . . . . . . . . . . . . . . . . . . . . 40

Chapter Three An MFC Primer. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41

MFC in Visual C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41MFC’s Online Documentation . . . . . . . . . . . . . . . . . . . . . . 41

Creating MFC Projects with the MFC ActiveX ControlWizard . . . . . . . 42The New Dialog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42The MFC ActiveX ControlWizard . . . . . . . . . . . . . . . . . . . . 43The Confirmation Dialog. . . . . . . . . . . . . . . . . . . . . . . . . 44

Working MFC Magic with the ClassWizard. . . . . . . . . . . . . . . . . 44The ClassWizard Message Maps Tab . . . . . . . . . . . . . . . . . . . 45The ClassWizard Member Variables Tab . . . . . . . . . . . . . . . . . 45The ClassWizard Automation Tab . . . . . . . . . . . . . . . . . . . . 46The ClassWizard ActiveX Events Tab. . . . . . . . . . . . . . . . . . . 46The ClassWizard Class Info Tab . . . . . . . . . . . . . . . . . . . . . 47

Augmenting ActiveX Control Features with ClassWizard Dialogs . . . . . 47The ClassWizard New Class Dialog . . . . . . . . . . . . . . . . . . . 47The ClassWizard Add Method Dialog . . . . . . . . . . . . . . . . . . 48The ClassWizard Add Property Dialog . . . . . . . . . . . . . . . . . . 48The ClassWizard Add Event Dialog . . . . . . . . . . . . . . . . . . . 49

Some Advanced Topics for MFC ActiveX Control Projects . . . . . . . . . 49Using Windows Events with ClassWizard . . . . . . . . . . . . . . . . 49Editing Property Pages . . . . . . . . . . . . . . . . . . . . . . . . . . 50Connecting Property Page Controls to ActiveX Control Properties

with ClassWizard . . . . . . . . . . . . . . . . . . . . . . . . . . . 51MFC Code (Classes and Macros, Oh My!) . . . . . . . . . . . . . . . . . 52

MFC Classes Encapsulate Much Windows Functionality in C++Wrappers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52

Macros Expand into Customized Code. . . . . . . . . . . . . . . . . . 52

vi � Contents

Page 8: Learn OLE DB Development With Visual C++ 6.0

Where We Go From Here . . . . . . . . . . . . . . . . . . . . . . . . . . 52

Chapter Four An OLE DB Primer. . . . . . . . . . . . . . . . . . . . . . . . . . . . 53

IAccessor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53IAccessor::AddRefAccessor. . . . . . . . . . . . . . . . . . . . . . . . 54IAccessor::CreateAccessor . . . . . . . . . . . . . . . . . . . . . . . . 55IAccessor::GetBindings. . . . . . . . . . . . . . . . . . . . . . . . . . 63IAccessor::ReleaseAccessor . . . . . . . . . . . . . . . . . . . . . . . 65

IColumnsInfo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66IColumnsInfo::GetColumnInfo. . . . . . . . . . . . . . . . . . . . . . 67IColumnsInfo::MapColumnIDs. . . . . . . . . . . . . . . . . . . . . . 74

ISourcesRowset . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75ISourcesRowset::GetSourcesRowset . . . . . . . . . . . . . . . . . . . 75

IDBCreateSession. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79IDBCreateSession::CreateSession . . . . . . . . . . . . . . . . . . . . 79

IDBInfo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80IDBInfo::GetKeywords . . . . . . . . . . . . . . . . . . . . . . . . . . 80IDBInfo::GetLiteralInfo . . . . . . . . . . . . . . . . . . . . . . . . . 84

IGetDataSource . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90IGetDataSource::GetDataSource . . . . . . . . . . . . . . . . . . . . . 90

IOpenRowset . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91IOpenRowset::OpenRowset . . . . . . . . . . . . . . . . . . . . . . . 91

ISessionProperties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95ISessionProperties::GetProperties . . . . . . . . . . . . . . . . . . . . 95ISessionProperties::SetProperties . . . . . . . . . . . . . . . . . . . . 97

IDBCreateCommand . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99IDBCreateCommand::CreateCommand . . . . . . . . . . . . . . . . . 99

ITableDefinition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100ITableDefinition::AddColumn . . . . . . . . . . . . . . . . . . . . . 100ITableDefinition::CreateTable . . . . . . . . . . . . . . . . . . . . . 102ITableDefinition::DropColumn . . . . . . . . . . . . . . . . . . . . . 107ITableDefinition::DropTable . . . . . . . . . . . . . . . . . . . . . . 108

ICommand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109ICommand::Cancel . . . . . . . . . . . . . . . . . . . . . . . . . . . 109ICommand::Execute . . . . . . . . . . . . . . . . . . . . . . . . . . 110ICommand::GetDBSession . . . . . . . . . . . . . . . . . . . . . . . 116

ICommandProperties . . . . . . . . . . . . . . . . . . . . . . . . . . . 117ICommandProperties::GetProperties . . . . . . . . . . . . . . . . . . 118ICommandProperties::SetProperties . . . . . . . . . . . . . . . . . . 121

ICommandText . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123ICommandText::GetCommandText . . . . . . . . . . . . . . . . . . . 124ICommandText::SetCommandText . . . . . . . . . . . . . . . . . . . 125

IColumnsRowset . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126IColumnsRowset::GetAvailableColumns . . . . . . . . . . . . . . . . 127IColumnsRowset::GetColumnsRowset . . . . . . . . . . . . . . . . . 128Required Metadata Columns . . . . . . . . . . . . . . . . . . . . . . 132Optional Metadata Columns . . . . . . . . . . . . . . . . . . . . . . 134

ICommandPrepare . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138

Contents � vii

Page 9: Learn OLE DB Development With Visual C++ 6.0

ICommandPrepare::Prepare . . . . . . . . . . . . . . . . . . . . . . 138ICommandPrepare::Unprepare . . . . . . . . . . . . . . . . . . . . . 140

ICommandWithParameters . . . . . . . . . . . . . . . . . . . . . . . . 140ICommandWithParameters::GetParameterInfo . . . . . . . . . . . . . 141ICommandWithParameters::MapParameterNames . . . . . . . . . . . 144ICommandWithParameters::SetParameterInfo . . . . . . . . . . . . . 145

IRowset . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150IRowset::AddRefRows . . . . . . . . . . . . . . . . . . . . . . . . . 153IRowset::GetData . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155IRowset::GetNextRows . . . . . . . . . . . . . . . . . . . . . . . . . 157IRowset::ReleaseRows . . . . . . . . . . . . . . . . . . . . . . . . . 162IRowset::RestartPosition . . . . . . . . . . . . . . . . . . . . . . . . 165

IRowsetInfo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167IRowsetInfo::GetProperties . . . . . . . . . . . . . . . . . . . . . . . 168IRowsetInfo::GetReferencedRowset . . . . . . . . . . . . . . . . . . 171IRowsetInfo::GetSpecification . . . . . . . . . . . . . . . . . . . . . 172

IRowsetChange . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173IRowsetChange::DeleteRows . . . . . . . . . . . . . . . . . . . . . . 174IRowsetChange::InsertRow . . . . . . . . . . . . . . . . . . . . . . . 177IRowsetChange::SetData . . . . . . . . . . . . . . . . . . . . . . . . 182

IRowsetUpdate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187IRowsetUpdate::GetOriginalData. . . . . . . . . . . . . . . . . . . . 188IRowsetUpdate::GetPendingRows . . . . . . . . . . . . . . . . . . . 191IRowsetUpdate::GetRowStatus . . . . . . . . . . . . . . . . . . . . . 193IRowsetUpdate::Undo . . . . . . . . . . . . . . . . . . . . . . . . . 195IRowsetUpdate::Update . . . . . . . . . . . . . . . . . . . . . . . . 199

ITransaction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203ITransaction::Abort . . . . . . . . . . . . . . . . . . . . . . . . . . . 204ITransaction::Commit . . . . . . . . . . . . . . . . . . . . . . . . . 205ITransaction::GetTransactionInfo. . . . . . . . . . . . . . . . . . . . 207

ITransactionOptions. . . . . . . . . . . . . . . . . . . . . . . . . . . . 209ITransactionOptions::GetOptions . . . . . . . . . . . . . . . . . . . . 209ITransactionOptions::SetOptions . . . . . . . . . . . . . . . . . . . . 209

ITransactionObject . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210ITransactionObject::GetTransactionObject . . . . . . . . . . . . . . . 211

ITransactionJoin. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211ITransactionJoin::GetOptionsObject . . . . . . . . . . . . . . . . . . 212ITransactionJoin::JoinTransaction . . . . . . . . . . . . . . . . . . . 212

IRowsetIndex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214IRowsetIndex::GetIndexInfo . . . . . . . . . . . . . . . . . . . . . . 215IRowsetIndex::Seek. . . . . . . . . . . . . . . . . . . . . . . . . . . 216IRowsetIndex::SetRange . . . . . . . . . . . . . . . . . . . . . . . . 219

IErrorRecords . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222IErrorRecords::AddErrorRecord . . . . . . . . . . . . . . . . . . . . 222IErrorRecords::GetBasicErrorInfo. . . . . . . . . . . . . . . . . . . . 224IErrorRecords::GetCustomErrorObject . . . . . . . . . . . . . . . . . 225IErrorRecords::GetErrorInfo . . . . . . . . . . . . . . . . . . . . . . 225IErrorRecords::GetErrorParameters. . . . . . . . . . . . . . . . . . . 226

viii � Contents

Page 10: Learn OLE DB Development With Visual C++ 6.0

IErrorRecords::GetRecordCount . . . . . . . . . . . . . . . . . . . . 227IErrorLookup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 228

IErrorLookup::GetErrorDescription. . . . . . . . . . . . . . . . . . . 229IErrorLookup::GetHelpInfo . . . . . . . . . . . . . . . . . . . . . . . 230IErrorLookup::ReleaseErrors . . . . . . . . . . . . . . . . . . . . . . 231

ISQLErrorInfo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232ISQLErrorInfo::GetSQLInfo. . . . . . . . . . . . . . . . . . . . . . . 232

Where We Go From Here . . . . . . . . . . . . . . . . . . . . . . . . . 233

Chapter Five An ADO Primer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235

ADO Collections. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235The Errors Collection . . . . . . . . . . . . . . . . . . . . . . . . . . 236The Fields Collection . . . . . . . . . . . . . . . . . . . . . . . . . . 237The Parameters Collection . . . . . . . . . . . . . . . . . . . . . . . 239The Properties Collection . . . . . . . . . . . . . . . . . . . . . . . . 241The ADO Connection Object . . . . . . . . . . . . . . . . . . . . . . 242The ADO Command Object . . . . . . . . . . . . . . . . . . . . . . . 259The ADO Recordset Object . . . . . . . . . . . . . . . . . . . . . . . 264The ADO Field Object . . . . . . . . . . . . . . . . . . . . . . . . . 293The ADO Error Object . . . . . . . . . . . . . . . . . . . . . . . . . 299The ADO Property Object. . . . . . . . . . . . . . . . . . . . . . . . 301The ADO Parameter Object . . . . . . . . . . . . . . . . . . . . . . . 304

ADO Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 308BeginTransComplete Event . . . . . . . . . . . . . . . . . . . . . . . 308CommitTransComplete Event. . . . . . . . . . . . . . . . . . . . . . 309ConnectComplete Event . . . . . . . . . . . . . . . . . . . . . . . . 309Disconnect Event . . . . . . . . . . . . . . . . . . . . . . . . . . . . 310EndOfRecordset Event . . . . . . . . . . . . . . . . . . . . . . . . . 310ExecuteComplete Event . . . . . . . . . . . . . . . . . . . . . . . . 311FetchComplete Event . . . . . . . . . . . . . . . . . . . . . . . . . . 312FetchProgress Event . . . . . . . . . . . . . . . . . . . . . . . . . . 313FieldChangeComplete Event . . . . . . . . . . . . . . . . . . . . . . 313InfoMessage Event . . . . . . . . . . . . . . . . . . . . . . . . . . . 314MoveComplete Event . . . . . . . . . . . . . . . . . . . . . . . . . . 314OnError Event . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315OnReadyStateChange Event . . . . . . . . . . . . . . . . . . . . . . 316RecordChangeComplete Event . . . . . . . . . . . . . . . . . . . . . 316RecordsetChangeComplete Event. . . . . . . . . . . . . . . . . . . . 317RollbackTransComplete Event . . . . . . . . . . . . . . . . . . . . . 317WillChangeField Event . . . . . . . . . . . . . . . . . . . . . . . . . 318WillChangeRecord Event . . . . . . . . . . . . . . . . . . . . . . . . 319WillChangeRecordset Event . . . . . . . . . . . . . . . . . . . . . . 319WillConnect Event . . . . . . . . . . . . . . . . . . . . . . . . . . . 320WillExecute Event . . . . . . . . . . . . . . . . . . . . . . . . . . . 321WillMove Event . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 322

Where We Go From Here . . . . . . . . . . . . . . . . . . . . . . . . . 323

Contents � ix

Page 11: Learn OLE DB Development With Visual C++ 6.0

Chapter Six Creating and Using Simple OLE DB Providers and Consumerswith ATL and MFC . . . . . . . . . . . . . . . . . . . . . . . . . . . 325

OLE DB Template Changes are Required in ATL . . . . . . . . . . . . . 325Creating an OLE DB Provider COM DLL with ATL . . . . . . . . . . . . 326

What the Wizard Wrought . . . . . . . . . . . . . . . . . . . . . . . 329Gentlemen, Start Your Keyboards! . . . . . . . . . . . . . . . . . . . 341Returning a Custom Rowset . . . . . . . . . . . . . . . . . . . . . . 342Defining a Custom User Record . . . . . . . . . . . . . . . . . . . . 345Redefining the Rowset Implementation Template to Support

Bookmarks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 347Implementing Bookmarks in the Custom Rowset Implementation . . . 350Implementing Bookmarks in the Custom IRowsetLocate

Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . 355Implementing Variable COLUMNIFO in the Custom Rowset

Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . 360Disabling Schema Support . . . . . . . . . . . . . . . . . . . . . . . 364Building the OLE DB Provider DLL . . . . . . . . . . . . . . . . . . . 368

Creating an OLE DB Consumer Application with MFC . . . . . . . . . . 368Building the MFC OLE DB Consumer Application . . . . . . . . . . . 383

Creating the Text File Database and Testing the OLE DB Provider withthe MFC Consumer Application . . . . . . . . . . . . . . . . . . . . 384

Where We Go From Here . . . . . . . . . . . . . . . . . . . . . . . . . 385

Chapter Seven Creating OLE DB Service Providers with ATL. . . . . . . . . . . 387

Service Providers Have the Best of Both Worlds in OLE DB . . . . . . . 387Creating an OLE DB Service Provider COM DLL Consumer with ATL . . 388

Gentlemen, Start Your Keyboards! . . . . . . . . . . . . . . . . . . . 393Building the OLE DB Service Provider Consumer DLL . . . . . . . . . 400

Creating an OLE DB Service Provider COM DLL Provider with ATL . . . 400Gentlemen, Start Your Keyboards! . . . . . . . . . . . . . . . . . . . 401Building the OLE DB Provider DLL . . . . . . . . . . . . . . . . . . . 403

Creating an OLE DB Service Provider Consumer Application with MFC . 404Building the MFC OLE DB Consumer Application . . . . . . . . . . . 417Testing the OLE DB Service Provider with the MFC Consumer

Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 417Where We Go From Here . . . . . . . . . . . . . . . . . . . . . . . . . 418

Chapter Eight Creating and Using Simple OLE DB Consumers in ATL. . . . . 419

OLE DB Consumer Templates in Action. . . . . . . . . . . . . . . . . . 419Creating an OLE DB Consumer COM Server with ATL . . . . . . . . . . 420

Creating an ODBC-Compliant Database with Access . . . . . . . . . . 420Creating an ODBC Connection with Control Panel . . . . . . . . . . . 423

Creating an OLE DB Provider COM DLL with ATL . . . . . . . . . . . . 426What the Wizard Wrought . . . . . . . . . . . . . . . . . . . . . . . 433Customizing the OLE DB Consumer . . . . . . . . . . . . . . . . . . 436Disabling Rowset Return on Database Open . . . . . . . . . . . . . . 437Adding OLE DB Consumer and Microsoft Agent Support . . . . . . . 438Adding the COM Server Code to Use the OLE DB Consumer. . . . . . 440

x � Contents

Page 12: Learn OLE DB Development With Visual C++ 6.0

Building the OLE DB Consumer DLL . . . . . . . . . . . . . . . . . . 449Creating a Client Application for an OLE DB Consumer with MFC. . . . 449

Building the MFC OLE DB Consumer Application . . . . . . . . . . . 469Testing the OLE DB Consumer with the MFC Application . . . . . . . 469

Where We Go from Here . . . . . . . . . . . . . . . . . . . . . . . . . 470

Chapter Nine Creating and Using Complex OLE DB Consumers in MFC . . . 471

OLE DB Consumer Templates in MFC. . . . . . . . . . . . . . . . . . . 471Creating an OLE DB Consumer MDI Application with MFC . . . . . . . 471

Building the MFC OLE DB Consumer Application . . . . . . . . . . . 498Testing the MFC OLE DB Consumer Application . . . . . . . . . . . . 498

Where We Go From Here . . . . . . . . . . . . . . . . . . . . . . . . . 499

Chapter Ten Putting OLE DB on the Internet . . . . . . . . . . . . . . . . . . . 501

Using the LPK_TOOL Application to Create HTML Run-time LicenseLPK Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 501

Downloading the ActiveX SDK . . . . . . . . . . . . . . . . . . . . . . 504Code Signing using Authenticode™—Obtaining an SPC . . . . . . . . . 505Code Signing using Authenticode™—Digitally Signing Code . . . . . . 510Obtaining and Using ActiveX Control Pad from Microsoft . . . . . . . . 513

Automatic COM <OBJECT> Tags/Visual Editing . . . . . . . . . . . 514Creation of HTML Layout Controls . . . . . . . . . . . . . . . . . . . 515Visual Editing of HTML Layout Controls . . . . . . . . . . . . . . . . 517ActiveX Script Wizard for HTML and ALX . . . . . . . . . . . . . . . 519

Obtaining and Using File Transfer Protocol (FTP) Software to PlaceCOM Elements on the Internet . . . . . . . . . . . . . . . . . . . . . 521

Using Internet Explorer to Access COM Over the Internet and WorldWide Web. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 526

Mastering COM Over the Internet . . . . . . . . . . . . . . . . . . . . 529Where We Go From Here . . . . . . . . . . . . . . . . . . . . . . . . . 529

Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 531

Contents � xi

Page 13: Learn OLE DB Development With Visual C++ 6.0

LearnActiveX

DevelopmentUsing

Visual C++ 6.0

LearnMicrosoft

TransactionServer

DevelopmentUsing

Visual C++ 6.0

LearnActiveX

TemplateLibrary

Developmentwith

Visual C++ 6.0

LearnOLE DB

Developmentwith

Visual C++ 6.0

A note to our readers:

Wordware Publishing, Inc. is offering a complete series ofActiveX/COM-oriented books for Visual C++ programmers.Much of the information within these books overlaps toensure that each book stands alone. However, each focuseson a specific ActiveX/COM-related technology. Therefore,each book provides an essential set of resources for program-mers who must either write stand-alone COM programs orinclude one or more COM modules as part of otherapplications.

Page 14: Learn OLE DB Development With 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 thevarious online and printed documentation for OLE DB. I became intriguedwith OLE DB when it was first released, since for the first time it permittedany type of data to be accessed using a standard syntax. When support forOLE DB was added to Visual C++ 6.0, I proposed doing a book on it, and theindustry agreed one was needed. I hope you enjoy the book, and find it asexciting as I do!

Who Can Use This Book?Who Can Use This Book?

Anyone can use this book, provided they are at least familiar with the syntaxof 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 limita-tions). You can be a rank beginner with COM, ATL, and/or MFC and stillmake 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 aboutATL, MFC, OLE DB, and COM together. If you are knowledgeable in one ormore areas but not in others, then you can skip around as you developincreasingly powerful projects in a hands-on fashion.

Hands-On ExplanationsHands-On Explanations

When I was learning how to use computers, I absolutely hated computerbooks that were all generalities and no real instructions. This book com-pletely avoids that pitfall by using detailed, step-by-step guides to creating allthe projects and support systems needed by OLE DB. You can identify wherethe 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 ordevelop an OLE DB application, flip through the chapter until you see thissymbol, fire up your computer, and dive in!

Page 15: Learn OLE DB Development With Visual C++ 6.0

The other thing I’ve disliked about computer books (even some that I’ve writ-ten) is that they contain lots and lots of code, and then an explanatoryparagraph or two which has little connection with the code. You find a lineyou don’t understand in the listing, and search for hours trying to see whereit is explained or why it seems unusual. This book remedies this problem,with the line-by-line approach. You can identify where this information startswith the icon at the left.

These sections first contain a numbered code listing, and then a breakdownof every major line or group of lines that isn’t automatically generated. Theexplanations tell you what the code does and why it’s important (althoughthey don’t do a blow-by-blow coverage because that’s the province of thesource code comments).

For OLE DB programming, this approach needs some modification. While thestep-by-step and line-by-line systems work just fine, I also use a modified sys-tem called Before and After and the Wizard’s Wand.

The Before section gives a listing of the wizard-generated code, and then pro-vides 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 explana-tion of what the code (both changed and unchanged) does.

The Wizard’s Wand section gives a listing of wizard-generated code anddetails what it does in the line-by-line format.

What’s In This Book?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 ObjectModel is, how it works, and why it matters. This chapter will give you all thatinformation, and in the process bring you up to speed on both the history andunderlying technologies behind COM, Automation, and ActiveX.

Chapter 2—An ATL Primer

ATL stands for “ActiveX Template Library,” a name that encompasses a lot ofterritory. First, it invokes ActiveX, which in this context means all of COMexcept COM+. The template portion of ATL’s name indicates that this systemuses the C++ template mechanism heavily, along with a lot of very sophisti-cated macros. The library part of ATL indicates that, unlike some othertemplate-based systems, ATL provides extensive source code rather thanbinary 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

xiv � Introduction

Page 16: Learn OLE DB Development With Visual C++ 6.0

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 mate-rial in the ATL system for two or three full-sized books; this particular book isgoing to focus on the basics. This chapter will cover how ATL fits into VisualC++ and what its various wizard dialogs do. A brief look is taken at the tem-plates and macros ATL uses, but because these are more advanced topics,they won’t be covered in great detail. (Individual chapters will cover the spe-cific macros and template classes they use.) When you’ve finished readingthis chapter, you’ll be ready to start creating powerful ATL-based COM serversand 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 programmingaspects, including the GDI (graphical device interface), file operations, win-dow creation, message handling, and various specialized aspects of the OS.One of these specialized aspects is ActiveX; support for it is added via existingsupport 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 APIcalls 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 coverhow MFC fits into Visual C++ and what its various wizard dialogs do. A brieflook is taken at the classes MFC uses, but because these are more advancedtopics, they won’t be covered in great detail. (Individual chapters will coverthe 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. Theseinterfaces (along with a couple of API calls) are what developers use to maketheir database components work with OLE DB seamlessly. This chapter willgive you complete information on these API calls and interfaces, and in theprocess explain what OLE DB is and how it works. There is one important pointto remember, however: OLE DB has absolutely no database functionality of itsown! 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 anacronym) and MTS (which is, and stands for Microsoft Transaction Server).Both of these powerful database systems rely on a single technology to con-nect with end users: ActiveX Data Objects (ADO). ADO is a set of COM

Introduction � xv

Page 17: Learn OLE DB Development With Visual C++ 6.0

objects (i.e., interfaces) designed to connect with database systems via theOLE DB technology, but users don’t need to know anything about OLE DB touse ADO. Instead, they simply create appropriate ADO objects and makemethod calls on them to connect with databases and obtain and set informa-tion. For OLE DB developers, ADO is the vehicle their users need to connectwith the OLE DB providers and consumers they create. For MTS program-mers, ADO is the system they use to interact with databases under theumbrella of MTS. (ADO itself has several advanced capabilities, includingRDS (Remote Data Service) and MD (Multi-Dimensional) database interac-tions, which we won’t go into here.)

Either way, database programmers using Microsoft technology need adetailed reference about ADO. This chapter provides that reference, with acomplete writeup on all the collections, objects, methods, and events of theADO system. Dive in, and your database development will never be the same!

Chapter 6—Creating and Using Simple OLE DBProviders 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 PCworld has ever seen. Now it is time to do some actual programming and put allthis information into practice. This chapter will provide you with a start inusing the OLE DB templates for ATL and MFC, showing you how to create asimple (but really very powerful) OLE DB provider in ATL, and then a matchingconsumer in MFC.

Chapter 7—Creating OLE DB Service Providers with ATL

Now that you’ve implemented your first OLE DB simple provider, you’reready to move to a higher level in OLE DB: service providers. These OLE DBelements are both providers and consumers (although not always using OLEDB 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 Accessdatabases as dual COM servers, one of which as a consumer acquires a data-base record, then sends this information as COM properties to another COMserver which provides it as an OLE DB provider record. An MFC applicationthat 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 sup-ported by an MFC client. In this chapter, you’ll learn how to take advantageof the template functionality of an ATL OLE DB consumer to create a simplebut very powerful COM server that can function in a web page to connect to

xvi � Introduction

Page 18: Learn OLE DB Development With Visual C++ 6.0

an Access database and drive the Microsoft Agent ActiveX control. Whenyou’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 Consumersin 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 sup-ported by an MFC client. In Chapter 8, you learned how to take advantage ofthe template functionality of an ATL OLE DB consumer to create a simple butvery powerful COM server that can function in an application to connect toan Access database and drive the Microsoft Agent ActiveX control. This chap-ter 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 offerin terms of OLE DB; you can create and use OLE DB components and applica-tions with the best of them. You understand the issues involved in creatingOLE DB Automation servers and ActiveX controls and clients in both ATL andMFC, and you’re an ace with the underlying concepts behind OLE DB. Sowhat’s left? The Internet, that’s what! More and more database developersare tasked by their clients to put DBMS (and thus OLE DB) functionality ontoweb pages, either on a local intranet or the big, bad Internet/World WideWeb itself. Doing this requires an understanding of arcane issues totally unre-lated to MTS or COM, and so this chapter will fill that essential gap. Thischapter will add mastery of the LPK_TOOL application for creating HTMLlicense keys, understanding of the ActiveX Control Pad and the ActiveX SDK’scode signing tools, and familiarity with the two major tools of Internet andWorld Wide Web functionality, FTP and Internet Explorer, to your OLE DBskills inventory.

Source Code DuplicationSource 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 readand written books that try many different approaches to this dilemma. Thisbook goes to one extreme: I include full source code for every project, even ifit duplicates a previous project substantially. I do this so that a reader who isonly interested in one type of project does not have to go hunting for thecode he or she wants. For those reading the book through, this is a bit redun-dant, but hopefully not too much so. I feel the utility for the average“deadline reader” (like me!) is worth it.

Introduction � xvii

Page 19: Learn OLE DB Development With Visual C++ 6.0

Project Source and the CDProject Source and the CD

The CD distributed with this book contains complete project source code forall the material in the book, organized into directories named after the chap-ters. In addition, there are several third-party applications and utilities. Sinceeach hands-on section assumes you start from scratch, you don’t need any ofthese 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, changedrives to your CD-ROM drive, and type in “XCOPY /S *.* [your target driveand directory]”. The XCOPY command using the /S switch copies all the filesand directories into the new directory for you, and sets their read-only attrib-utes 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 anylast-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!Get Updates on the World Wide Web!

The author maintains a Web presence, and this book is one of the ones keptcurrent there. The URL is: http://www.ciupkc.com/books/lodbdcpp/default.htm. Along with the README.TXT file on the CD, this is the placeto go for updates on bug and typo fixes, new and more sophisticated versionsof the book’s projects, and a host of information on ActiveX, Visual C++, andWWW programming. Please drop by!

OLE DB Forever!OLE DB Forever!

This book is not intended to be the most definitive resource on OLE DB outthere; what it does do, and does well, is bring you up to speed on the tech-niques needed to create a robust, fully functional set of OLE DB componentsand applications in the C++ development framework of your choice, andthen demonstrates the truly amazing power of OLE DB in a real-world set-ting. So go ahead, join the OLE DB revolution! We’re way past the sky; it’sAlpha Centauri or bust!

xviii � Introduction

Page 20: Learn OLE DB Development With Visual C++ 6.0

Chapter One

A COM PrimerA 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 ObjectModel is, how it works, and why it matters. This chapter gives you all thatinformation, and in the process brings you up to speed on both the historyand underlying technologies behind COM, Automation, and ActiveX. (If youalready know all you want about COM, Automation, and ActiveX, feel free tomove on to Chapters 2 or 3, depending on whether you want to program inATL or MFC.)

What is COM?What is COM?

COM is an acronym that stands for the Component Object Model. It wasdeveloped at Microsoft to allow applications to share data and program codewhile running on an end user’s computer. Initially a limited system for allow-ing programs like Word and Excel to display each other’s files, it now hasbecome a centerpiece technology of the Windows operating system. InternetExplorer 5, Internet Information Server, Microsoft Transaction Server, ActiveServer Pages, and Universal Data Access are only some of the current releasesfrom Microsoft that depend wholly or partially on COM.

The Component Object Model is a specification for writing applications thatcan be used by other applications. The specification provides details on howcompilers must output their executable files for the applications to communi-cate, and on how the operating system must support this communication.One of the benefits of this approach is that it permits COM to be language-and even platform-independent, so that a COM application written in VisualBasic can easily work with one written in Java or C++.

Since COM stands for Component Object Model, you would expect objects tobe involved with it somehow. COM provides for six key features of object-oriented programming (OOP):

1

Page 21: Learn OLE DB Development With Visual C++ 6.0

� Identity This means that each COM component must be identified asdifferent from all other COM components. This is done by their GUIDattributes.

� State This means that COM components must keep track of changesmade to their status in some way. COM components have at least theinformation on their usage count, and many keep internal data as well.

� Behavior This means that COM components must implement methodsto provide standard and custom functionality of some sort. They do thisvia their interfaces.

� Encapsulation This means that the internal data and functionalityused by a COM component is hidden from its users, available as a “blackbox.” Since COM only supports functions and then only their names andparameters, it nicely supports encapsulation. All the internal functionsand data of COM components are completely inaccessible to users.

� Inheritance This means that one COM component can be derived fromanother, using the ancestor component’s functions while adding its own.COM supports inheritance but in a special way.

� Polymorphism This means that any two COM components that imple-ment the same interaction can be used interchangeably. COM fullysupports this capability.

The component part of COM’s name is the major driving force behind thetechnology. Although initially conceived as a way for Microsoft applicationsto display and edit each other’s data, COM has since become the leadingmechanism for creating reusable applications. This is in some part due to theadvent of the Internet, which provided a new and unexpected market forcomponent software. Also, Bill Gates, the CEO of Microsoft, has long been aproponent of “component software,” which he viewed as applications createdby end users out of many smaller “applets,” or components. COM was writtento some degree to further this strategy.

How Did COM Happen?How Did COM Happen?

COM did not come about as part of a single development process. Instead, itbegan 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 andExcel, to display another application’s data without the user having to explic-itly open up the second application. Although it was exciting in its time, OLE1.0 had two serious limitations: The embedded data could not be edited inthe containing document, and there was no standardized system for storingthe embedded information.

These limitations of OLE 1.0 caused Microsoft to produce OLE 2, which wasconsidered a name (pronounced “oh lay two”) rather than an acronym. OLE2 came out with Windows 3.1. OLE 2 was the first true COM technology,

2 � Chapter One—A COM Primer

Page 22: Learn OLE DB Development With Visual C++ 6.0

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 ofdata called a compound document, which included information from anynumber 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 ownlimitations, the most noticeable of which was the requirement to open a newwindow whenever editing of an embedded data element was required. Thisled to the development of the next release of OLE, called OLE Automation.This technology allowed editing inside the application where the data wasembedded (called in-place editing), but also added two other major improve-ments over OLE 2 to the implementation of COM: It provided access to COMservices from non-C++ application development environments like VisualBasic, and it provided for the creation of COM-based components that existedoutside the world of compound documents. Automation was supported inWindows 3.11.

This last enhancement was to have the most lasting impact on COM, becauseto this point Mr. Gates’ dream of component software was still not realizedwith the OLE implementation of COM. Instead, it had become a realitythrough a most unexpected mechanism: the VBX control. VBX controls wereadd-ins for the Visual Basic development environment, created in C++ ini-tially and later in VB itself. They allowed VB programmers to create usefulsoftware elements like custom list boxes or printer utilities, and distribute thebinary file only for reuse by other programmers, either for free or after pay-ment for a license. But VBX controls were not implemented in COM, andwhile they were soon available for C++, problems remained in this crossoversystem. Also, Borland released its breakthrough Delphi product, which alsofeatured proprietary reusable binary components called VCL controls.

The COM team was tasked with making a reusable control that worked onCOM principles rather than proprietary ones. They succeeded, and the OCXcontrol joined the COM family. It was quickly supported in C++, VisualBasic, and Delphi, and seemed to be the end of the line for COM, since it hadfinally achieved its goal of reusable component software at the binary level.Then along came Java and the Internet. Just as no one expects the SpanishInquisition (as they used to say on Monty Python), no one expected Java orthe Internet, in particular the World Wide Web, to explode onto the computerstage with the force of a hydrogen bomb. Microsoft had long assumed that itsdominance of the PC desktop was beyond challenge, but Java and the webbrowser, coupled with the Internet, represented a whole new way ofapproaching personal computing, one controlled by Sun and Netscape ratherthan Microsoft. Microsoft scrambled to meet the Java/WWW challenge, andBill Gates developed the Internet Initiative. This led to the retooling of COMinto ActiveX, with compound documents becoming ActiveX documents, OCXcontrols becoming ActiveX controls, and so forth. ActiveX COM elements

Chapter One—A COM Primer � 3

Page 23: Learn OLE DB Development With Visual C++ 6.0

were enhanced for Internet use, with added security features, smaller codesizes, and data elements designed for asynchronous downloading. Soon Javawas corralled into the COM camp, with support for ActiveX added inMicrosoft’s Visual J++ environment for Java programming. An enhancedversion of Automation was added to Microsoft’s web browser, InternetExplorer, and called ActiveX scripting. Despite legal and government squab-bles, ActiveX soon became a dominant technology for the Internet and WorldWide Web, and continues to gain in strength both there and on the emergingcorporate and public intranet/extranet systems.

OLE 1.0, OLE 2, OLE Automation, OCX controls, ActiveX, and COM+ are allimplementations of COM that are part of the Windows operating system.They allow developers to actually create and use COM components in variousways without having to write all the COM support code themselves. COMfunctionality is provided via various API functions that developers include intheir programs along with appropriate references, just like the core WindowsAPI. Although Windows was COM’s original home, it has begun to branch outrecently. Microsoft has just released an implementation of COM for theSolaris variant of the UNIX and Apple Macintosh operating systems.

COM has now become a central element of Microsoft’s product line. Once rel-egated to the application data-sharing niche, then to the VBX-replacementslot, and finally to the Java-competitor position, COM has been made thecore technology for a host of new releases:

� Internet Explorer 5 This cutting-edge web browser includes full sup-port for ActiveX controls and in fact uses one for its display interface.

� Windows 98 Along with its integration of IE 5 into the operating sys-tem, Windows 98 also supports the Active Desktop, a COM-based systemfor placing web-enabled applications on the Windows desktop.

� Internet Information Server Microsoft’s entry in the web serverwars, IIS includes many powerful COM-based systems, including ActiveServer Pages, ISAPI Extensions, and ISAPI Filters.

� Microsoft Transaction Server A database-oriented system, MTSuses COM to allow the combination of transactions from many differentdatabase systems and vendors, as well as non-database actions, into singleactivities that succeed or fail and can be “rolled back” (returned to a validprevious state in the database) without losing information if they fail.

� OLE DB Harking back to the OLE 2 roots of COM, OLE DB allowsnon-database applications to interact with database-oriented technologies(like MTS) using the same techniques as ODBC databases.

4 � Chapter One—A COM Primer

Page 24: Learn OLE DB Development With Visual C++ 6.0

Why Do We Need COM?Why Do We Need COM?

As you look at all the technologies that are part of COM now, you may won-der why there has been all the bother. After all, programmers have beenexchanging code for years, long before Microsoft was around; why the bigfuss? The answer lies in four separate aspects of PC software creation: howcomputer code is turned into working programs, how developers actuallywrite programs, how memory is used in Windows, and how Windows DLLfiles 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 andhave written many such files. You know how much of an investment eachproject takes in terms of your time, your expertise, and your company’sresources. Source code is literally money in the bank, a sizable investment.The concept of giving this away to other companies, perhaps even competingcompanies, is hardly a good idea.

But prior to COM, giving away source code was precisely how programmershad to exchange their code! A prime example is the C and C++ libraries usedfor many years prior to COM. These are binary files (LIB) rather than sourcecode (C or CPP), but in most cases they were accompanied by their C andCPP 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 styleof individual developers.

The reason for this lies in the incompatibility of binary computer code at thecompiler level. Each compiler produces a set of machine language instruc-tions (binary numbers) that are designed to be fed by the operating systeminto the CPU of the computer in structured ways to produce program behav-ior. While there are standards for programming languages like C++, thereare currently no standards for the binary output of compilers. Thus, withoutthe source code to draw upon, one development environment has no way toeffectively interpret and use the binary output of another programming sys-tem, even one that uses the same language.

Even more difficulty lies in the area of reusing binary files at the applicationlevel. Once source code has been turned into a binary executable file, it hasno natural capability to import and use binary files produced by anotherapplication as part of its own running machine language code. There is no“roadmap” as to what the various number codes mean inside a machine lan-guage file. A number may represent a data item, a processor directive, or aninternal value used in some calculation; the outside program has no way toknow this.

An example of this problem is the need for filters for word processing docu-ments. Each modern word processor application stores its information in abinary format, including not only the text characters typed in by the writer,

Chapter One—A COM Primer � 5

Page 25: Learn OLE DB Development With Visual C++ 6.0

but also formatting data, fonts, graphics, annotations, and so on. Each pro-gram has a different format for storing this information; the result is that aWordPerfect WP file cannot be used automatically by Microsoft Word, andvice versa.

This problem was solved by the adoption of open formats. Each word pro-cessing application company published its internal data format freely; thisallowed competing and complementary products to create filters that usedthe data format specification as a roadmap to read in and display and manip-ulate the other application’s binary data. Once the roadmap was available,writing the filtering code was easy; this allowed binary compatibility betweenthe different applications’ data files.

This led to the conclusion that what programmers need is a version of theirown open formats and roadmap-based filters. Once there is a standard way tocreate 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 insideanother application.

Interestingly enough, the developers of the Windows 3.0 operating system atMicrosoft ran into this problem from a different angle; they needed to sharelow-level system functions like graphics and file management between partsof the OS being developed by different teams. Their solution, also developedfor efficiency reasons, was the DLL, or dynamic-link library. Initially onlyavailable to the operating system, DLLs would become the mechanism usedto implement COM itself.

A dynamic-link library is an executable file, like an EXE, that is never allowedto run on its own. Instead, it is loaded into memory by the Windows operat-ing system when requested by an actual application. The requestingapplication then uses code from the DLL just as if the DLL’s code had beenincluded in its own binary file; this is the long-desired binary compatibilityfor applications!

As long as reusable software components are not easily available,a companymust spend money time and again to “reinvent the wheel,” redoing simpleuser interface or sorting programming just because a previous project was notavailable as source code, or the code did not fit the programming style of thecurrent development team.

The NIH syndrome (Not Invented Here) has been a major stumbling block tothe adoption of software components; it manifests itself as the attitude ofdevelopers that any code they did not personally write is inferior at best.Analysts have suggested that this tendency by programmers to distrust thework of others continues to be a major obstacle to the adoption of any stan-dard for reusable code. Reusable code at the binary level gets around thissyndrome by isolating programmers from the internal behavior of compo-nents, making the components into cost-effective “black boxes.”

6 � Chapter One—A COM Primer

Page 26: Learn OLE DB Development With Visual C++ 6.0

Once again, the DLL file concept provides this sort of functionality. DLLs pro-vide a list of “exported functions,” which developers can call with a simpleAPI mechanism, but give no hint as to their internal implementation. DLLfiles serve as perfect “black boxes” for reusable code. The only difficulty, ini-tially, was that they were creatable only at Microsoft and usable only insideWindows itself. That eventually changed about the same time as COM itselfwas initially released.

Without some form of component reuse at the operating system level, eachapplication must contain all of its executable code, even the code that doessimple calculation or graphics tasks, such as drawing a line. This can result in“code bloat,” huge executable files occupying limited hard drive and RAMspace. The DLL system also solved this problem.

The Windows operating system actually inserts a DLL’s code into the samememory space as the application that calls it. A DLL makes a group of func-tions available (called exported functions) to Windows; whenever the binarycode of the application using the DLL wants to use one of these exportedfunctions, it first uses the LoadLibrary API call to cause the DLL code to beplaced in memory, obtains the function address via another API call, and theninvokes the function, just as if it had written the code itself.

In Windows 3.x, DLL code actually resided in physical RAM only once; ifmore than one application was using the same DLL at the same time, theoperating system did some fancy pointer arithmetic to keep them both happy.Windows 95 no longer uses this system except for some operating systemDLLs, mainly for performance reasons and because RAM is so much moreplentiful and cheaper than a few years ago. But what makes DLLs useful forending 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-wheelproblem, and the binary incompatibility problem, they had difficulties of theirown. Their locations were “hard coded,” or else they had to reside in theWINDOWS\SYSTEM directory. They also had no way to let an applicationknow which version of the DLL was currently on a computer, which couldresult in inconsistent behavior when an older version of an application triedto use the new version’s functions.

An even worse problem with DLL files lay in how they were reused by multi-ple applications from different vendors. Both vendors might buy a DLL from athird party and distribute it, but the second vendor’s application would use anewer version of the DLL in question. When it was installed, the other appli-cation that used the previous DLL version would break, because it eitherexpected functions to exist that were no longer supported, or because thesupported functions worked in different ways.

This “broken application” problem was what led, more than any of the otherconcerns, to the creation of COM. COM took the DLL mechanism and

Chapter One—A COM Primer � 7

Page 27: Learn OLE DB Development With Visual C++ 6.0

upgraded its behavior to a consistent standard that still met the other threeneeds of reusable component software (binary compatibility, effective reus-ability, and efficient executable size) and also supported versioning andenforcement of backwards compatibility that prevented new COM compo-nents from breaking applications that used older ones.

How COM WorksHow COM Works

COM, in its basic incarnation, consists of six elements working in a somewhatintricate but relatively straightforward way to produce reusable componentsoftware. These elements are the server application, the ClassFactory imple-mentation, the interface specification, the reference count mechanism, theWindows COM system, and the client application.

The COM Server

The server application is where you, the COM component developer, put yourwork. It is an executable file, in either DLL or EXE format, that contains allthe code to create and maintain the COM elements you provide to COM cli-ents. When a COM client asks the COM system for one of the interfaces yourserver supports, the COM system will load your server into memory and runit. A server must provide some very specific functions and behaviors to allowthe COM system to use it successfully.

The COM ClassFactory

A ClassFactory is an implementation of one of the requirements for a COMserver: the need to return an interface pointer when the COM systemrequests 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 thespecific entry points COM expects. The client for a COM server never sees anyaspect of the ClassFactory; only the COM system uses it. A ClassFactory alsohas some specific requirements in its behavior.

The COM Interface

The COM interface is the only part of the functionality of a COM server thatis visible to a COM client application. For this reason, considerable confusionhas arisen as to just what interfaces really are. Simply put, an interface is aspecification of what functions a given server provides for a COM componentwith 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 isexpected to provide implementation code for all the interfaces it exposes toclients; how it does the implementation is not specified. When asked for aninterface pointer, a server provides a pointer to the client via the server’sClassFactory. The client will dereference this pointer and make function calls

8 � Chapter One—A COM Primer

Page 28: Learn OLE DB Development With Visual C++ 6.0

on its implementation. What actually happens is that these function calls areprocessed into the VTable, which is simply a list of function pointers. Thesefunction pointers, when called, execute the implementation code in theserver’s application. This is where the confusion often arises. While from theclient’s standpoint an interface is an object, in fact its pointer is a link to a setof function pointers and nothing more. The server does not actually provide apointer to its internal implementation object (if any) when asked for an inter-face pointer, but only the pointer to a set of functions. The interface specifieswhich functions and in what order, but other than that has no informationabout how those functions are implemented. So in fact an interface has nophysical existence; instead, what exists are the implementations of whateverfunctions it has agreed to provide, and a special pointer that gives clientsaccess to these functions. All COM interfaces derive from IUnknown, thestandard interface that the system rests upon. COM interfaces based onIUnknown are called “custom” interfaces, because their functions arenon-standard, implemented differently in each new interface.

The COM Reference Count

COM servers are loaded into memory by the COM system. For this reason, cli-ents cannot manage the memory used by the servers. To help the COMsystem decide when a server is no longer needed in memory, the referencecounting 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 trackof how many times an interface has been requested by the client. The call toAddRef 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 devel-oper. When a client is finished with an interface pointer, it is required to callthe Release method on that pointer; this method is expected to decrementthe same internal counter used by AddRef. This way, the COM server alwaysknows how many clients are using it at any given time.

At appropriate times, the COM system checks with each server to see whetherit thinks it should no longer be in memory. The server is required to be ableto accurately know how many interfaces are still in use by clients; if thisvalue is 0, it must inform the COM system it is no longer needed and can beunloaded, but otherwise it must tell the COM system that it needs to stay inmemory.

Chapter One—A COM Primer � 9

Page 29: Learn OLE DB Development With Visual C++ 6.0

The COM System in Windows

Since clients for COM servers are never allowed to know about either theserver’s DLL or EXE file, or the ClassFactory implementation, clearly someother application must be able to deal with these essential aspects of usingCOM. Windows provides a set of API calls, similar to those used for Windowsoperating system API functions, that initialize the COM internal system for aclient 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 puz-zle fit together. COM clients initialize the COM system, make the API call toobtain a specific interface pointer for a specific server, and then use the func-tions supported by that interface as they wish. When they are done with agiven interface, they call its Release method to tell the server they are donewith it. When they are finished using COM services, they shut down theiraccess to the COM system.

What makes COM work, and work so well, is that neither the server nor theclient need to know anything about each other! As long as both follow therules for COM behavior, they can mesh together as if one single developerwrote both applications.

COM is Static, Automation is DynamicCOM is Static, Automation is Dynamic

Remember that COM was developed to allow Microsoft programs to commu-nicate with each other, and only for that reason. It therefore was not orientedtowards client/server interactions, but more towards peer interactionsbetween applications. A concept had been growing for some time, however,that the big, feature-laden application was a dinosaur that was about to bedone in by a new breed of program called component software.

Component software was conceived as a combination of many smaller ele-ments, sometimes referred to as applets, which would be connected by endusers in a simple scripting language like Perl. The scripted portion of thecomponent software would create the applet objects as needed, and tell themwhat to do in response to user interactions. This type of embedding came tobe called Automating. Microsoft began to seriously consider supporting com-ponent software, and developed a system using a variant of its Visual Basiclanguage. This system was based on COM, which then was implemented asOLE 2. It was called OLE Automation, but is now usually just called Automa-tion. It has become, to many users, the dominant version of COM in usetoday.

10 � Chapter One—A COM Primer

Page 30: Learn OLE DB Development With Visual C++ 6.0

Basic COM is entirely fixed at compile time in its function invocations. Theonly way to call a function on a server’s interface pointer in the client is togive the client a header file or two that gives precise type information aboutthat server’s interfaces. If the server has since changed, and supports moreinterfaces than are present in the header files the client is using, the clientwill never know about those interfaces and so can never ask for them or usetheir functions. This type of interface function calling is called static

invocation.

Automation, on the other hand, is not fixed at compile time in what functionsit can call on a given interface pointer. Instead, it can ask the Automationserver for a list of its available interfaces and functions, check them againstthe source code of its script, and connect the script’s function calls with theactual interface functions entirely at run time. This type of interface functioncalling is known as dynamic invocation. The information as to which func-tions a given Automation server supports and their parameters and data typesare stored for each Automation server in a file called a Type Library. A TypeLibrary can be thought of as a compiled version of a C .h header file, opti-mized for use by the Automation system. Compilers that create Automationservers also create their Type Libraries; clients that support Automation serv-ers have the capacity to locate and obtain Type Library information, usuallyas part of their development framework.

IDispatch is a COM interface, derived from IUnknown just like custom inter-faces, that supports Automation via four functions: GetTypeInfoCount,GetTypeInfo, GetIDsOfNames, and Invoke. Each of these functions must beinvoked using original static invocation in the client’s executable file.

What makes Automation work is that the GetIDsOfNames and Invoke func-tions can and do call any Automation-enabled function of their IDispatch-derived custom interface, by obtaining a number called a DISPID fromGetIDsOfNames against a textual version of the function’s name, and passingthat number along with any appropriate parameters to Invoke. To allow theclient to figure out which of the custom functions is being called, a comple-mentary component called a script parser must be provided. This componenttakes the text file, called a script, and turns it into symbols that the client canunderstand and translate via GetIDsOfNames and Invoke into real Automa-tion interface function calls in its executable code. The client itself isresponsible for obtaining data from the script, storing it internally, andreporting it when asked, but these are not COM issues. Since the functions ofan Automation server’s IDispatch interface are dynamic rather than static, theIDispatch interface became known as a Dispinterface rather than an interfaceto highlight this distinction. A later development merged interface andDispinterface capabilities into one object; these are called dual interfaces.

To make Automation servers more programmer-friendly, the COM teamadded two new terms to the COM vocabulary: properties (data stored inside

Chapter One—A COM Primer � 11

Page 31: Learn OLE DB Development With Visual C++ 6.0

the instance of a given IDispatch-derived Automation server) and methods(COM functions given a new name). Although properties seemed to work likeC++ class member variables to the developers of Visual Basic, Java, orVBScript, in fact they were a trick supplied by Automation based on gettingand 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 WorldAutomation Opens COM to the World

The scripting language used for OLE Automation was called VBA, or VisualBasic for Applications. Major applications like Word and Excel were givenAutomation capabilities, and still have them. But the market for componentsoftware never materialized, perhaps because of the massive increase inmemory and hard drive capacities coupled with drastically reduced costs asthe PC market exploded. OLE Automation probably would have been only aniche version of COM, had not two additional factors intervened: Visual Basicand the Internet. The latter aspect led to the merging of Automation with theWindows Desktop, and to the next evolutionary step of COM: COM+.

Visual Basic was Microsoft’s entry into the RAD market (Rapid ApplicationDevelopment) where application user interfaces and even program function-ality can be quickly laid out and coded, in exchange for somewhat poorrun-time performance. Once the prototype was created in VB, it would berecoded 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 OLEand COM. The VB development team was assigned the task of supplying thisaccess for the next version of Visual Basic.

Because at that time Visual Basic was not compiled into executable machinecode, 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 otherhand, by treating the tokens in the parsed VB source code like a VBA script,OLE Automation could very easily provide COM functionality to Visual Basicprograms. This provided a substantial new market for OLE Automation, andit became known simply as Automation since many of its uses had little to dowith the compound documents which were the focus of OLE.

Another player entered the PC world at about this time: the Internet. Takenfrom its comfortable academic niche and thrust into the mainstream of theworld’s lifestyle via the user-friendly World Wide Web and Mosaic andNetscape browser applications, the Internet upset everyone’s calculations asto the future of PC computing development. Adding to the furor was Sun’spowerful and revolutionary Java language, which was platform-independent

12 � Chapter One—A COM Primer

Page 32: Learn OLE DB Development With Visual C++ 6.0

and could send its code over the Internet safely to run on client machinesworldwide from a single source.

Microsoft retaliated with two powerful new products: Visual J++, its versionof a Java compiler that provided increasing support for Windows at the costof losing platform-independence, and Internet Explorer, a web browser tocompete with Netscape Navigator.

Visual J++ features the ability for its Java programs to both import and useAutomation-enabled COM servers, and to even create COM servers that sup-port Automation. Like Visual Basic, it too must rely on the Automationdynamic invocation system to connect its Java code with the COM system.

But the most important fallout of the Internet for COM was the decision toemulate the JavaScript capability of Netscape Navigator in Microsoft’s webbrowser, Internet Explorer. IE 3 offered relatively complete support forJavaScript, but also had its own scripting language, VBScript, based of courseon Visual Basic for Applications. VBScript was part of HTML, and ran on theclient 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 Javaapplets and special COM Automation servers called ActiveX controls.

But it is vital to remember that no matter how sophisticated and user-friendlyAutomation technology becomes, it is still based on four static-invocationfunctions of the COM IDispatch interface.

From Automation to OCX to ActiveXFrom Automation to OCX to ActiveX

When Automation was released, many developers saw it as the culminationof COM. It provided access to COM for Visual Basic and allowed creatingCOM servers not tied to compound documents, but added considerable sup-port for compound documents as well. Many felt things couldn’t get betterthan Automation. But trouble was brewing on the horizon, as the increasingpopularity of the Visual Basic development environment led to a new type ofsoftware component, the VBX control. VBX controls had two important fea-tures that Automation servers did not: a user interface and the ability tocommunicate with their clients (called containers). The unprecedented suc-cess of VBX controls led Microsoft to task the COM team with creating anAutomation equivalent. The result was the OCX control specification, whichsupported all the functionality of VBX controls with COM technology with theadded bonus of being 32-bit. Then, before OCX controls really got their feeton the ground, the arrival of the Internet and Java caused them to be recastas ActiveX controls, with both added and removed functionality.

Automation was a powerful COM technology, with many useful features. Butit had limitations as well. Perhaps the most serious feature that Automationlacked was the ability for a non-compound document server to display a user

Chapter One—A COM Primer � 13

Page 33: Learn OLE DB Development With Visual C++ 6.0

interface. While compound document servers could create visual images,manipulate them, and even interact with their containing application viain-place editing, Automation servers could do none of these things. Also, evenwith in-place editing, there was no effective mechanism for a server to con-tact its client or container with information asynchronously (like theWindows messaging system).

These limitations tended to keep COM and Automation from becoming main-stream development tools. Programmers moved instead towards Visual C++and its MFC system for application development, and to Visual Basic for rapidprototyping and hobbyist computer programming. In these environments,emphasis soon shifted towards components that had a sophisticated graphicaluser interface along with underlying program functionality. In Visual C++,these components were provided by Microsoft and became known as com-mon controls. In Visual Basic, however, these elements were called VBXcontrols, and they paved the way for a whole new form of componentsoftware.

A VBX control is a type of DLL application written with special support toallow it to hook into the Visual Basic system. VBX controls supported twoimportant features: a graphical user interface and the ability to communicatewith their containing application (which became known as a container). Thisallowed VBX controls to behave like C++ common controls, but with one bigdifference: They could be created by outside developers, using an SDKreleased by Microsoft.

It seems likely that Microsoft had no concept when it released the SDK forVBX control creation, initially for C++ and then for VB itself, just what genieit was releasing from the bottle. Within a year, the VBX control marketexploded into a multimillion dollar industry, and took sales of the VisualBasic product along with it. Soon, bulletin boards around the world werefilled with sophisticated freeware and shareware applications written inVisual Basic using the thousands of powerful third-party VBX controls nowavailable.

The C++ development community agitated for the ability to use VBX con-trols, and they got it, although not as cleanly as some would have liked.There were several inherent limitations of VBX controls which made themunsuitable for use in other environments:

� VBX controls are inherently 16-bit. This meant that a massive porting jobwould be needed to move them to the 32-bit environment of Windows 95and Windows NT, which Microsoft viewed as the future of Windows.

� VBX controls don’t use the Windows messaging system. This meant that touse them outside of Visual Basic, an interpreter had to be added to con-vert their VB-specific communication system to the standard Windowsarrangement.

14 � Chapter One—A COM Primer

Page 34: Learn OLE DB Development With Visual C++ 6.0

� VBX controls had only eight available functions. This meant that to definea custom method for a VBX control, you had to define the clumsy Actionproperty, which was hard to use in Visual Basic, let alone any otherlanguage.

� The VBX controls event mechanism, because of its tie to the Visual Basicsystem, was inflexible; adding new events was difficult, if not impossible.

All these problems led Microsoft to decide that a general system for controlswas needed, and COM and Automation were the obvious technologies toimplement it. The result was the OCX control, released in 1994 as a specifica-tion and SDK.

OCX controls were specialized Automation DLL servers (you cannot create anEXE OCX control) with the extension OCX rather than DLL on their file-names. OCX controls could only be used by applications that were written asOCX 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 controlcontainer, and they had the ability to asynchronously send messages to thecontainer in response either to user actions or to internal state changes.

Soon OCX controls were everywhere, from Visual C++ to Delphi to Java, andwere viewed by the developer community as another COM triumph. Butthere were storm clouds on the horizon, named the Internet and Java. Theirwinds of change would blow OCX controls away and replace them withsomething both similar and very different: the ActiveX control.

When AOL dumped millions of its users on the academic network called theInternet in late 1994, no one really understood what it would mean for PCsoftware development. But when combined with the brand-new graphicalinformation display technology called HTML and the World Wide Web, theInternet became the wildfire technology of the last half of the 1990s. TheNetscape web browser became synonymous with the WWW, and it was not aMicrosoft product. Microsoft’s browser, Internet Explorer, lagged behind incapability and was virtually ignored by the developer community.

Then in 1995, Sun Microsystems released the Java programming language. Itprovided for the first time a secure way to send programs over the Internetand WWW, producing Java applets, which were very similar to OCX controlsbut worked either by themselves or as part of an HTML web page. In combi-nation with the JavaScript programming language, they behaved very muchlike Automation servers in web pages or standalone applications. Java, unlikeCOM, was platform-independent; it ran on Windows, on UNIX workstations,and on the Macintosh. Java was touted as the language of the future, andtogether with Netscape, it became a serious candidate to challenge the domi-nance of Microsoft Windows on the PC desktop.

Chapter One—A COM Primer � 15

Page 35: Learn OLE DB Development With Visual C++ 6.0

Microsoft was taken by surprise by the WWW and Java, but it soon cameback fighting. Its response to Netscape was Internet Explorer version 3, whichfeatured both a JavaScript clone called JScript and its own programming lan-guage called VBScript. To counter the Java initiative, Microsoft produced aversion of COM called ActiveX. ActiveX encompassed most existing COM andAutomation technologies; OCX controls became known as ActiveX controls,for example. Compound documents were called ActiveX documents, andCOM servers and Automation servers were renamed ActiveX servers orActiveX code components.

Although initially resisted, soon the innate power of ActiveX moved it to theforefront 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 reap-peared, now oriented towards a host of new technologies like TransactionServer, OLE DB, and the Windows DNA system. Internet Explorer moved toversion 4 and into the operating system itself in Windows 98. ActiveX con-trols, however, have remained unchanged since their release in 1996.

ActiveX Controls are Automation Servers with a UserActiveX Controls are Automation Servers with a User

Interface

By now you may be a bit tired of hearing that ActiveX controls have a userinterface. After all, what’s so special about that? What makes the ability tocreate a user interface so special is that ActiveX controls are not compounddocuments. A compound document, the previous user interface system sup-ported for COM servers, is a very limited display. It is literally a “window intoanother application,” and as such does not need to know about things likeresizing, background colors, font settings, or display optimization. Controls,on the other hand, are designed to blend in with a consistent user interfacearound them. They need to understand how to resize when needed, andoften need to support display optimization to enhance applicationperformance.

The ActiveX control at its basic level maintains its own display window, com-plete with a device context. While this window is a child window of thecontainer, it nonetheless has its own message queue and refreshes itself whenneeded. This has some good and bad points. The good points are that ActiveXcontrols are responsible for drawing themselves, taking this job away fromthe container, and that they can receive messages using the normal Windowsmessaging system if needed. The bad points are that ActiveX controls must berectangular, and cannot be transparent or easily overlaid with non-windowedelements of their container’s display. To get around these limitations, anActiveX control can be made windowless.

16 � Chapter One—A COM Primer

Page 36: Learn OLE DB Development With Visual C++ 6.0

Windowless ActiveX controls are a relatively recent innovation; some ActiveXcontrol containers may not support them. Those that do, provide the win-dowless control with its display window and also take care of sending andreceiving windows messages for it. What the container gets in return for thiseffort is a control that can be non-rectangular and even transparent ifdesired. Also, the control will often redisplay faster due to less overheadinternally.

ActiveX controls must always draw themselves, and thus are fully responsiblefor what the user sees on their window. This requires their container to pro-vide them with all the messages that might require refreshing their display.This can range from a simple Paint message to resize requests or moreadvanced data acquisition calls (needed to store the image of the control inthe container as a metafile).

Why this matters is that some drawing commands don’t work on a devicecontext for a metafile. The ActiveX control must be able to distinguishbetween normal drawing requests and those that are needed to create ametafile, and alter its drawing code appropriately.

The other major effect of having a user interface for ActiveX controls is thatthey must be able to change size when their container changes its size. TheActiveX system supports three types of sizing for ActiveX controls:

� Autosizing This type of sizing makes the control entirely responsiblefor its own display extents. The container will tell the control its ownextents and let the control figure out how big it wants to be.

� Content Sizing This type of sizing makes the container responsible forsetting the size of the control initially, but allows the control to decide onits final size.

� Integral Sizing This type of sizing makes the container responsible forthe size of the control. The control must resize itself to the desired values.

Aside from these sizing aspects, ActiveX controls must also decide whichmapping mode they will use in their drawing operations. The default isMM_TEXT, but this can be changed if the control wishes.

Stock Properties and Property Pages Standardize ActiveXStock Properties and Property Pages Standardize ActiveX

Control BehaviorControl Behavior

Automation servers are quite free-form; aside from the needed methods ofIDispatch, they are free to support whatever properties and methods theylike. Since their clients presumably have access to their Type Library, they willknow what custom elements are supported and deal with them appropriately.But ActiveX controls are designed to be used in a general environment, some-times with little prior knowledge about their behavior. To help compensatefor this, the ActiveX system provides stock and ambient properties and

Chapter One—A COM Primer � 17

Page 37: Learn OLE DB Development With Visual C++ 6.0

methods to help ActiveX controls blend in with their containers and functionconsistently for end users.

Custom properties and methods of ActiveX controls are just that: custom. Tosave on naming overhead, if a property or method isn’t qualified as stock orambient, it is custom by default. They are implemented by the control andare different for each given control, based on its unique functionality.List-oriented controls will expose a property that is an iterator over theirinternal list of items. Graphical controls will provide an image property to settheir 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 andchange its orientation.

From the container standpoint, the custom methods and properties of anActiveX control are available just like Automation-based properties and meth-ods. End users will access the custom properties and methods with scripts orprogram code, which will be sent to the control via the usual Automation sys-tem (either Dispinterface or dual interface, depending on how the control isimplemented). From a display standpoint, custom methods and propertieshave no effect (although they may change what is displayed, they are notcentral to how the display of the control is rendered).

The Windows system (where most ActiveX controls live and work) has consis-tency as one of its major features. Once a user has learned how to manipulatescroll bars, buttons, list boxes, radio buttons, check boxes, and so on in oneapplication, he can reasonably expect the same user interface behaviors fromother Windows applications. As long as the Windows applications are allusing common controls or API-based user interface elements, this will be thecase. But ActiveX controls (and their precursors, VBX controls) have theirown custom user interfaces in many cases. The behavior of these elementscan differ widely despite a similar appearance, resulting in considerable userconfusion.

To help work around this, ActiveX controls are provided by the ActiveX sys-tem with a list of so-called stock properties and methods. These stockfeatures are designed to allow both containers and users to have a consistentand understandable set of appearances and behaviors from ActiveX controls.

ActiveX controls live on in the display of their container. This means that theyhave two choices in their own appearance: They can be a complete iconoclastand set their colors, fonts, and other styles solely to please themselves, orthey can set their display values to match those of the container except whenit would interfere with their own functionality. In order for an ActiveX controlto 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 iscalled ambient properties.

18 � Chapter One—A COM Primer

Page 38: Learn OLE DB Development With Visual C++ 6.0

Ambient properties are actually properties of the ActiveX control container.They are exposed to the control itself via a special COM interface the controlmust implement. An ActiveX control container will obtain this interface fromthe control when the control is first loaded, and call its functions with the ini-tial values of the ambient properties it supports. Also, whenever one of theseambient properties changes, the ActiveX control will be informed of the newvalue by another call to the interface’s notification function.

Another thorny aspect of the various properties of ActiveX controls is makingthem 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 Prop-erty Sheet dialogs introduced in Windows 95; these are tabbed dialogs thatgroup similar properties together and provide consistent, Windows UI-basedmethods for manipulating them.

Connection Points Allow Events to be Sent from ActiveXConnection Points Allow Events to be Sent from ActiveX

Controls to ContainersControls to Containers

The need for two-way communication between programs has been aroundsince Windows programming began. In Windows, the system to provide thisfunctionality is the Callback function. For COM, however, the interface is theclear choice to implement communication between servers and clients; theseinterfaces are known as outgoing and incoming interfaces. The problems liein keeping track of obtaining and notifying many incoming and outgoinginterfaces between clients and servers. To handle this difficulty, ActiveX pro-vides the Connection Point system.

The Windows system by its very design needs some way for applications tocommunicate with Windows itself. Probably the most well-known example isthe so-called Window procedure, required for every window created by anapplication. This function is called by Windows to deal with dispatched mes-sages. Another common callback function is the one used to enumeratewindow objects (like those on the Desktop).

This system works because Windows is the operating system, and thusexempt from the problems of interprocess communication. Also, the functionsinvolved are strictly standardized so that Windows can support them. To pro-vide a generalized interprocess method of inter-application communication, aCOM interface is the vehicle of choice.

Since ActiveX controls are COM servers via Automation and OCX, it makessense for them to use interfaces for their communication with their contain-ers and vice versa. The communication system that ActiveX implements isprovided by sink interfaces (incoming) and source interfaces (outgoing).

Sink interfaces are called that because they sink, or consume, notificationsfrom a client to a server. In technical terms, all server interfaces (COM and

Chapter One—A COM Primer � 19

Page 39: Learn OLE DB Development With Visual C++ 6.0

Automation) are sink interfaces since they are called by the client or con-tainer. They are incoming because they leave the client and come in to theserver.

Source interfaces are called that because they source, or provide, notificationsfrom a server to a client. They are outgoing because they go out to the clientfrom the server, reversing the normal interaction. These interfaces are what isunique to the ActiveX system, since COM and Automation servers and clientsdo not have them. It is the source interface that tells a client or container thatsomething of importance has happened in the server; this notification iscalled an event. In order for a server to use an outgoing interface, the clientmust implement it and provide a registration of this implementation so theCOM system can find it. This makes a client into a server, and vice versa, tosupport source interfaces. For this reason, the implementation of a sourceinterface in the client then becomes a sink interface (because it is consumingevent notifications from the server).

The problem that arises with the sink and source interface system lies in theconcepts of one-to-many, many-to-one, and many-to-many connectiontopologies:

� One to Many This topology results from a single implementation of anoutgoing interface being used by two or more servers in a single client.The client must be able to determine which server has sent a notificationto the single outgoing interface.

� Many to One This topology results from a single server supporting twoor more outgoing interfaces which are implemented on a single client. Theclient must keep track of all of the incoming notifications for all the imple-mented source interfaces.

� Many to Many This topology is a combination of the above two; itoccurs when two or more servers are providing notification to the sameclient on the same outgoing interface, but to two different instances ofthat interface in the client. The server and the client both must keep trackof 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 inter-faces; these are collectively known as Connection Points. These two staticallyinvoked COM interfaces allow clients and servers to inform each other ofwhat sink and source interfaces they support, and hook up the implementedones to allow effective two-way communication.

20 � Chapter One—A COM Primer

Page 40: Learn OLE DB Development With Visual C++ 6.0

Persistence Allows ActiveX Controls to Save Their StatePersistence Allows ActiveX Controls to Save Their State

Over TimeOver Time

Compound documents save their information in special DocObject files,which are also called structured storages and are supported by OLE interfacesand functions. Automation did not need this complexity, since Automationservers rarely needed to save their state; those that did used files or othercustom implementations. ActiveX controls, however, need the same general-ized information persistence as compound documents, since they aredesigned to be edited in one environment and displayed in another. Thiscapability is called persistence. The ActiveX system implemented three inter-faces to encapsulate persistence: IPersistStorage, IPersistStream, andIPersistPropertyBag. Each of these interfaces has a unique place in theActiveX data storage arrangement. All persistence is the responsibility of thecontainer; the control only is required to support the interfaces that providethe data.

An ActiveX storage is very similar to a folder on a hard drive (also called adirectory). 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 IPersistStorage-XXX interfaces provide storage support for ActiveX controls, allowing them tocreate and manipulate persistence storages as needed, adding other storagesand streams to them.

An ActiveX stream is equivalent to a file on a hard drive. It contains data in aformat appropriate to whatever application created it, but cannot containother files or folders. The IPersistStreamXXX interfaces provide stream sup-port for ActiveX controls, allowing them to create, read, and write persistencedata streams as needed.

Both of the previous persistence implementations are binary, and meant foruse in applications that can deal with binary data (like Microsoft Word).Other applications, like Visual Basic or Internet Explorer, need their persis-tence data stored as text with named associations. To implement this type ofpersistence, ActiveX provides the IPersistPropertyBagXXX interfaces. Theseinterfaces permit ActiveX controls to read and write their data as HTML<PARAM> tags or named fields in FRM or other text files as needed.

Where We Go From HereWhere We Go From Here

This chapter has been a whirlwind tour of some of the most powerful andcomplex technology in the modern computing environment. This book, dueto space limitations, cannot cover all the wonders of COM; instead it focuseson 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. Ifyou need a more detailed coverage of COM itself, both theoretically and

Chapter One—A COM Primer � 21

Page 41: Learn OLE DB Development With Visual C++ 6.0

structurally, check out the author’s web site at http://www.ciupkc.com/books/. There are references to several good books on COM and COM-related subjects there.

Meanwhile, the next chapter takes us into the wonderful world of ATL and ofthe Visual C++ IDE that hosts it. We’ll learn how to create projects in VisualC++ 6.0, and how to use the various ATL wizards and dialogs to automate agreat 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. (Ifyou don’t use ATL but MFC instead, move on to Chapter 3, which does thesame job for Microsoft Foundation Classes.)

22 � Chapter One—A COM Primer

Page 42: Learn OLE DB Development With Visual C++ 6.0

Chapter Two

An ATL PrimerAn ATL Primer

ATL stands for “ActiveX Template Library,” a name that encompasses a lot ofterritory. First, it invokes ActiveX, which in this context means all of COMexcept COM+. The template portion of ATL’s name indicates that this systemuses the C++ template mechanism heavily, along with a lot of very sophisti-cated macros. The library part of ATL indicates that, unlike some othertemplate-based systems, ATL provides extensive source code rather thanbinary 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 veryfast, 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 howATL fits into Visual C++, and what its various wizard dialogs do. A brief lookis taken at the templates and macros ATL uses, but because these are moreadvanced topics, they won’t be covered in great detail. (Individual chapterswill cover the specific macros and template classes they use.) When you’vefinished reading this chapter, you’ll be ready to start creating powerfulATL-based OLE DB consumers and Providers as Automation servers andActiveX controls!

As of Visual C++ 6.0, ATL enters version 3.0. Most of the features of 3.0 arethe same as those of 2.1, and so the following sections are written from theperspective of an ATL 2.1 user. A section at the end of the chapter covers thenew features of ATL version 3.0.

ATL in Visual C++ATL in Visual C++

ATL does not work by itself; it is designed for, and only works with, MicrosoftVisual C++ 4.2, 5.0, or 6.0. It enhances the Integrated Development Envi-ronment of Visual C++ with wizard dialogs and automatic code generators.It also provides extensive online help and a detailed tutorial and sampleproject.

23

Page 43: Learn OLE DB Development With Visual C++ 6.0

Getting ATL for Older Visual C++ Versions

If you have purchased Microsoft C++ 5.0 or 6.0, you automatically haveATL; it is part of the application. If you have version 4, then you may nothave 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 version2.1 to use this book.

Fortunately, Microsoft has released version 2 of ATL as a free download forowners of Visual C++ 4.2b or higher. To get the self-installing executable forATL 2.1, activate a web browser and go to the URL http://www.microsoft.com/visualc/prodinfo/archives/download/default.htm. Figure 2-1shows this web site, which provides considerable information about ATL aswell as the download link.

Once you download the file, run it either by double-clicking on it in Exploreror using Run from the Start menu. It will extract itself and allow you tochoose various simple installation options. Once it is finished, reboot yourcomputer and you will find ATL installed and ready for use in Visual C++.

ATL’s Online Documentation

As mentioned above, ATL is a complex system. It has many useful featuresthat can make COM programming easier and faster, but at a price in time andeffort to familiarize yourself with it. Since you’ve purchased (or are consider-ing purchasing) this book, you are obviously interested in ATL. One very goodway to learn about it is to work through all the online documentationinstalled with it (either by default in Visual C++ 5.0 or 6.0, or when youinstall version 2.0 after downloading it).

24 � Chapter Two—An ATL Primer

Figure 2-1

DownloadingATL from theMicrosoft website

Page 44: Learn OLE DB Development With Visual C++ 6.0

To access the online documents for ATL, use the Help menu option and selectSearch. In the edit control, enterATL and press the Search but-ton. A number of results willappear; scroll down until you seethe “” option, as illustrated inFigure 2-2. As shown in the fig-ure, clicking on it will bring up agroup of topics; the best place tostart is “ATL Article Overview”;select it and press the Displaybutton. From there all the ATLdocuments are hyperlinked intoone detailed unit, which you canbrowse and print at your leisure.

Creating ATL Projects with the ATL AppWizardCreating ATL Projects with the ATL AppWizard

An ATL project starts with the ATL AppWizard. It is found in the ubiquitousNew dialog of Visual C++, and provides a simple wizard dialog that hides alot of power behind its plain face. With this wizard and its attendant confir-mation 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 Newdialog appears; select the Projects tab to produce the display shown in Figure2-3. By clicking on the ATL COM AppWizard entry as illustrated in the figure,

you enable entry ofthe project name(which should be dif-ferent from theplanned name(s) ofany interfaces to avoidconflicts) and selec-tion of its directory onthe hard drive of thedevelopment com-puter. When you haveentered this informa-tion, pressing OK willstart the actual ATLAppWizard.

Chapter Two—An ATL Primer � 25

Figure 2-3

The VisualC++ Newdialog creatinga new ATLAppWizardproject

Figure 2-2

The ATL onlinedocumenta-tion in VisualC++

Page 45: Learn OLE DB Development With Visual C++ 6.0

The ATL AppWizard

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 asmall number of choices. As illustrated by the figure, you can select a DLL, anEXE, or an NT Service basic project type. You also have two check boxes to

enable support forMicrosoft FoundationClasses (MFC) and toenable the inclusion ofproxy/stub functionsdirectly in the serverDLL (if a DLL project isselected) rather thanrequiring them in aseparate DLL as is thestandard approach.Once you have madethe desired choices,press the Finish buttonto bring up a confir-mation dialog.

The Confirmation Dialog

Pressing OK in the ATL AppWizard brings up a Confirmation dialog, as shownin Figure 2-5. This dialog lists the project name, the name of the COM server

that will be generated and itsfile type (EXE or DLL), whichfile contains the DLLintialization code if there is one,what the IDL filename is, andwhat filename containsproxy/stub makefile code.Pressing OK actually generatesthe new ATL project with theselected options and displayedfilenames.

26 � Chapter Two—An ATL Primer

Figure 2-4

The ATLAppWizarddialog

Figure 2-5

The ATLAppWizardconfirmationdialog

Page 46: Learn OLE DB Development With Visual C++ 6.0

Adding COM Interfaces with the ATL Object WizardAdding COM Interfaces with the ATL Object Wizard

Once you have created your ATL project via the ATL AppWizard, all you reallyhave is a shell that does nothing. The AppWizard does not add any COMfunctionality! Instead, you must do that yourself. Fortunately, ATL provides(as of version 2.0) the ATL Object Wizard dialog system to help with thistask. You access it via the Insert|New ATL Object menu option, which bringsup the Object Wizard dialog box. There are four types of interfaces you cancreate using the version 2.0 ATL Object Wizard, each of which is discussedbelow. Once you’ve decided which type of interface object to use, select it inthe dialog box and press Next to move to the tabbed dialog of the ATL ObjectWizard.

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 basicCOM interfaces, ranging fromthe Simple Object which is apure IUnknown interface, tothe Microsoft TransactionServer Component, which is apowerful COM interface capa-ble of working in the MTSdatabase management system.

The Object Wizard Controls

The Controls entry in the left-hand list box brings up the three ActiveX con-trol objects shown in Figure 2-7. The Full control is just that, a completeActiveX control suitable for use with Visual C++, Visual Basic, or Delphi. TheInternet Explorer ActiveX control object is a “lightweight” version of an

ActiveX control, which reducesits size for Internet downloadbut does not seriously degradeits functionality. The PropertyPage object implements theActiveX control property pagesystem. This book will cover allthree of these controls in detailfor our OLE DB ActiveXcontrols.

Chapter Two—An ATL Primer � 27

Figure 2-6

Basic and spe-cialized COMinterfaceobjects in theATL ObjectWizard

Figure 2-7

ActiveX con-trol and Prop-erty PageObjects in theATL ObjectWizard

Page 47: Learn OLE DB Development With Visual C++ 6.0

The Object Wizard Miscellaneous Elements

If you select the Miscellaneous entry in the left-hand list box, you’ll see a dis-play very much like that of Figure 2-8. It contains one entry in version 2.0, a

Dialog object. This objectallows ATL projects to displaystandard Windows dialogboxes, which can be edited inVisual C++’s dialog editor justlike MFC dialogs. We won’t usethis object in this book, but youmight want to in one of yourown projects.

The Object Wizard Data Access Elements

The Data Access entry in the left-hand list box brings up the display shown inFigure 2-9. It has one entry in version 2.0, the OLE DB Provider object, and

two entries in version 3.0, withthe addition of the OLE DBConsumer object (see “Version3.0 Changes to the ATL ObjectWizard”). These are advancedinterfaces that allow any type ofdata to be accessed usingActiveX Data Objects in a man-ner identical with a relationaldatabase. This book will coverthese objects in great detail.

The Object Wizard Names Tab

Once you’ve selected thetype of interface object tocreate for your ATL projectand pressed the Next but-ton, you’ll see a displaysimilar to that of Figure2-10. This is the Names tabof the ATL Object Wizardtabbed dialog box. All youneed to do is enter aunique name in the ShortName edit control. The

28 � Chapter Two—An ATL Primer

Figure 2-8

The ATL Dia-log object inthe ATLObjectWizard

Figure 2-9

The OLE DBProviderobject in theATL ObjectWizard

Figure 2-10

Setting thenames for anATL interfaceobject withthe ATLObjectWizard

Page 48: Learn OLE DB Development With Visual C++ 6.0

wizard then automatically fills in all the other edit controls for you. Whileyou can alter these values, it is best not to, to avoid name conflicts later. Onceyou’ve done this, select the Attributes tab to set basic properties for yourCOM interface.

The Object Wizard Attributes Tab

Pressing the Attributes tab of the ATL Object Wizard tabbed dialog box showsyou a display very much like that of Figure 2-11. There are four basic attrib-utes you can select for a COM interface from this tab of the dialog: theThreading Model (a complex topic involving using two or more copies of thesame COM interface at the same time in the same process), whether theinterface is a custom one (basic COM) or a dual one (usable by Automationclients), whether or not the interface can be aggregated (used “blind” byanother COM interface, a topic not covered in this book), and which of three

specialized features theinterface will support:ISupportErrorInfo (RichError Information for Auto-mation), Connection Points(interfaces for ActiveXevents), and the FreeThreaded Marshaler (avery advanced topic forspeeding up threading forCOM servers that is notcovered in this book).

Specialized Object Wizard Tabs

The previous two tabs are always present for ATL interface objects. Otherobjects, like ActiveX controls, have additional tabs. These will be covered inthe appropriate chapters where they are used. When you have set the desirednames and options for the ATL interface object, press OK to add it to the cur-rent ATL project.

Chapter Two—An ATL Primer � 29

Figure 2-11

Setting basicattributes foran ATL inter-face objectwith the ATLObjectWizard

Page 49: Learn OLE DB Development With Visual C++ 6.0

Creating Functions and Properties with the ATLCreating Functions and Properties with the ATL

Interface WizardsInterface Wizards

Even after you’ve added an interface to your ATL project, you have a lot ofwork cut out for you. The interface you added contains only basic methodsand properties, which in many cases do little or nothing. You must add yourown 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 win-dow of Visual C++.

The ClassView Shortcut Menu

Figure 2-12 shows the shortcut (right-click) menufor a COM interface in the ClassView pane of theProject Viewer window of Visual C++. It has anumber of useful options, but the ones we areinterested in are Add Property and Add Method.Selecting Add Method brings up the Add MethodWizard for the ATL COM interface; selecting AddProperty brings up the Add Property Wizard for theATL Automation Dispinterface.

Figure 2-12

The shortcut menu foran ATL interface in theClassView pane of VisualC++

The Add Method to Interface Dialog

Selecting Add Method from the ATL COM interface shortcut menu brings upthe wizard dialog shown in Figure 2-13. As illustrated by the figure, thereturn type of the method is fixed as HRESULT. You enter a unique name forthe 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 vari-ous buttons and make various entries, update itself with the IDL source codeyour 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.

30 � Chapter Two—An ATL Primer

Page 50: Learn OLE DB Development With Visual C++ 6.0

The Add Property to Interface Dialog

Selecting Add Property from the ATL COM interface shortcut menu brings upthe wizard dialog shown in Figure 2-14. As illustrated by the figure, thereturn type of the method is fixed as HRESULT. You enter a unique name forthe property and set any desired parameters it will receive, using IDL syntax.More importantly, you can select the Property Type value from the combobox; this list contains all the Automation data types ATL supports. While thefunction itself won’t return this data type in C++, other languages likeVBScript or Delphi Pascal will treat the property as if it does return that data

type. The Get and Put Functioncheck boxes allow you to deter-mine whether the property isread/write, read-only, orwrite-only. The read-only editcontrol at the bottom of the dia-log will, as you press variousbuttons and make variousentries, update itself with theIDL source code your actionshave generated. To add themethod to the selected interface,simply press OK. To add special-ized IDL attributes for themethod function, press theAttributes button (coveredbelow) before you press OK.

Chapter Two—An ATL Primer � 31

Figure 2-14

The Add Prop-erty Wizarddialog in ATL

Figure 2-13

The AddMethod Wiz-ard dialog inATL

Page 51: Learn OLE DB Development With Visual C++ 6.0

The Edit Attributes Dialog

In either wizard dialog, you can press the Attributes button to bring up a dia-log very similar to that shown in Figure 2-15. This dialog is used to addspecialized information to the IDL file source code generated by the wizard

for the property or method. Theleft-hand combo box allows you toselect from the available attributes;once you have selected one, you canenter a desired initial value in aright-hand edit control or combobox. The read-only edit control willshow you what your choices havedone to the IDL source code for theproperty or method. Press OK toadd your changes to the generatedIDL source code or Cancel to abortthem. You can also edit to somedegree the generated attributesalready shown.

Some Advanced Topics for ATL ProjectsSome Advanced Topics for ATL Projects

ATL is oriented towards COM development, and since more and more ofMicrosoft’s technologies use COM, ATL has acquired more and more capabili-ties. Among these are proxy generation of specialized classes for SmartPointers 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 somecases the basic ATL system cannot create needed code. To help with this, theATL Proxy Generator component was developed. The Proxy Generator out-puts code that transforms one type of interface into another so that it can bemore easily used in ATL. As of this writing, the Proxy Generator can handletwo types of proxies: Smart Pointers (special wrapper classes that avoid mostof the grunt work of using COM interfaces in C++) and Connection Points(specialized interfaces used for events in ActiveX controls). It is located onthe Insert menu, under Components and Controls. You will use this compo-nent several times during the course of this book; since it is very specialized,it is covered when it is used rather than here.

32 � Chapter Two—An ATL Primer

Figure 2-15

The Add Prop-erty EditAttributesWizard dialogin ATL

Page 52: Learn OLE DB Development With Visual C++ 6.0

Advanced Servers

The OLE DB provider and consumer interface objects are the focus of thisbook’s non-ActiveX code; they contain the same functionality as a basic COMAutomation server, but also include full support for OLE DB interfaces of theappropriate type (explained in Chapter 4 and beyond).

ATL Code (Templates and Macros, Oh My!)ATL Code (Templates and Macros, Oh My!)

ATL’s code is not straightforward. As we’ll see in examining the various filesoutput by the ATL wizards, there are many unusual software constructs inthem. 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 soft-ware syntax used in C++ to allow generation of code from a basic pattern ortemplate. This template has generic symbols for its variables, which thereforeend up being untyped. When a template is given real variables with realtypes, the template “fills in” these actual data types and variables and per-forms the indicated actions on them. What makes ATL work is that thissystem also works for class variables. You can write a template that generatesa custom class from a template, replacing key variables and functions in thetemplate class with the imported class’s functionality, while maintaining allthe standard code of the template class unchanged. You’ll see a number ofconcrete examples in later chapters as to how this works in COM serverprojects.

Macros Expand into Customized Code

Macros came before templates; they do much the same thing, but in a simplerway. A macro is expanded during the preprocessor step of compilation, andadds its code to that of the basic source file, replacing parameters of themacro with real variables from the macro’s invocation. Unlike templates,however, macros are typed and so cannot be generic. You’ll see a number ofkey macros in the coming chapters; where each is used, a detailed explana-tion and source listing is provided to help you understand how the ATL codeworks.

Chapter Two—An ATL Primer � 33

Page 53: Learn OLE DB Development With Visual C++ 6.0

What’s New with ATL Version 3.0?What’s New with ATL Version 3.0?

With the release of Visual C++ 6.0, a new version of ATL has been releasedas well: 3.0. It has a number of important changes, some of which are onlyused at the advanced level, a number of which affect intermediate develop-ers, and a few of which impact all users. The most fundamental changes arethe addition of two new shortcut menu entries: an event handler for Win-dows messages and a Connection Point interface (rather than having to usethe Proxy Generator). Also, a number of new ATL objects are provided in theObject Wizard, and there is a change to the basic ATL AppWizard itself. Forintermediate users, there are some changes to the functionality of a numberof ATL classes, as well as the addition of some new classes. At the advancedlevel, there are new macros, changes to how templates work, and support forATL in MFC projects.

Version 3.0 Changes to the AppWizard

Figure 2-16 shows the ATL COM AppWizard dialog for version 3.0. You’llnotice it looks very much like the one for version 2.1 except for the newcheck box at the bottom labeled Support MTS. This check box enables auto-matic support in the server for the Microsoft Transaction Server system whenit is checked. This is somewhat subtle: It does not make the server an

MTS-aware component; youhave to do that yourself byadding an MTS Object fromthe Object Wizard. It does,however, put in the properheader files for compilationof MTS objects, interfaces,and API calls rather thanrequiring you to find andinclude them by hand. Italso causes the MTSEX.DLLdynamic-link library to bedelay-loaded when theserver is loaded, forefficiency.

34 � Chapter Two—An ATL Primer

Figure 2-16

The ATL COMAppWizarddialog for Ver-sion 3.0

Page 54: Learn OLE DB Development With Visual C++ 6.0

Version 3.0 Changes to the ATL Object Wizard

The changes to the ATL Object Wizard are more substantial, because a num-ber of new and different ATL objects have been made available. Each page ofthe wizard is covered below, along with a brief synopsis of its new objects.

Figure 2-17 shows the ATL Object Wiz-ard dialog’s Objects page, with the topsix objects visible. They are the same asthe ones for ATL 2.1, but have some-what more elegant icons.

Figure 2-18 shows the seventh object inthe Objects page, the MMC SnapInobject. This ATL object is new for ver-sion 3.0, and is used to create acomponent that can connect with theWindows NT Microsoft ManagementConsole application. The object pro-vides support for all the commoninterfaces needed to interact with theMMC UI; the developer provides thecode to actually manage some aspectof NT behavior. This book does notcover MMC SnapIn components.

Figure 2-19 shows the ATL Object Wiz-ard dialog’s Controls page, with thetop six objects visible. There are signif-icant changes from the 2.1 version, asfollows:

� The Internet Explorer Control isnow the Lite Control. It otherwisehas the same capabilities.

� The Full Control and Property Pageobjects have new icons but other-wise work the same.

Four new controls have been added(the fourth is shown in Figure 2-20).They are:

� Composite Control This objectis a dialog-based template thatallows visual creation of controluser interfaces from existing Win-dows common controls. It is a Fullcontrol otherwise.

Chapter Two—An ATL Primer � 35

Figure 2-17

The ATLObject Wiz-ard dialog forversion 3.0showing thefirst sixobjects

Figure 2-18

The ATLObject Wiz-ard dialog forversion 3.0showing theMMC SnapInObject

Figure 2-19

The ATLObject Wizarddialog for ver-sion 3.0 show-ing the first sixcontrol objects

Figure 2-20

The ATLObject Wizarddialog for ver-sion 3.0 show-ing the LiteHTML Controlobject

Page 55: Learn OLE DB Development With Visual C++ 6.0

� Lite Composite Control This object is identical in behavior to theComposite control, but only supports the interfaces of a Lite (InternetExplorer) control for faster downloading and performance.

� HTML Control This object is a dialog-based template hosting theWebBrowser control and connecting its DHTML text with COM interfacesin C++ to allow using DHTML as a user interface. This book does notcover DHTML components.

� Lite HTML Control This object is identical in behavior to the HTMLcontrol, but only supports the interfaces of a Lite (Internet Explorer) con-trol for faster downloading and performance. This book does not coverDHTML components.

The Miscellaneous page has no changes; it still only contains the Dialogobject for giving an ATL server a user interface.

Figure 2-21 shows the Object Wizard dialog’s Data Access page, with both theConsumer and Provider objects shown.The Provider object was supported inversion 2.1; version 3.0 adds a Con-sumer object as well. This book willcover these OLE DB components ingreat detail.

Version 3.0 Changes to the ClassView Context Menus

There are three exciting new context (“shortcut”) menu options available formany ATL classes; each is discussed below and then covered in more detailwhen it is used in later chapters.

The Add Windows Message Handler Option

Figure 2-22 shows the ATL ClassView context menu displaying the Add Win-dows Message Handler option. Selecting it brings up the dialog box shown inFigure 2-23.

Warning This option is only available for classes that “wrap” a window, suchas Dialog objects, and Composite, Lite Composite, HTML, and Lite HTMLcontrols. Other objects will not have this option since they do not normallyreceive Windows messages.

36 � Chapter Two—An ATL Primer

Figure 2-21

The ATLObject Wizarddialog for ver-sion 3.0 show-ing theConsumer andProvider OLEDB objects

Page 56: Learn OLE DB Development With Visual C++ 6.0

The New Windows Message Handler Wizard allows you to select from allappropriate Windows messages the ATL class can receive, displayed in theleft-hand list box. You move a message to the right-hand list box by pressingeither the Add Handler button or the Add and Edit button. (The latter choicewill bring up code where the event handler is added so you can enter customstatements to respond to the message.) You can edit an existing handler bypressing the Edit Existing button. To accept the new event handler, press OK.To exit without saving the changes made in the wizard, press Cancel (unlessyou’ve pressed Add and Edit; this constitutes pressing OK first and cannot beundone).

Chapter Two—An ATL Primer � 37

Figure 2-22

The ATLClassViewcontext menushowing theAdd WindowsMessage Han-dler option

Figure 2-23

The ATL NewWindows Mes-sage HandlerWizard dialog

Page 57: Learn OLE DB Development With Visual C++ 6.0

The Implement Connection Point Option

Figure 2-24 shows the ATL ClassView context menu displaying the ImplementConnection Point option. Selecting it brings up the dialog box shown in Fig-ure 2-25.

Warning This option is only available after you have compiled your ATL pro-ject at least once, or at the minimum compiled its IDL file to produce a TypeLibrary. Otherwise, an error dialog will appear when you try to select thisoption.

The Implement Connection Point Wizard replaces the Proxy Generator, whichpreviously had to be used to compile a header file implementing an inlinefunction dealing with adding support for a Connection Point to an ATL object.

38 � Chapter Two—An ATL Primer

Figure 2-24

The ATLClassViewcontext menushowing theImplementConnectionPoint option

Figure 2-25

The ATL Connec-tion Point Wizarddialog

Page 58: Learn OLE DB Development With Visual C++ 6.0

The Implement Interface Option

Figure 2-26 shows the ATLClassView context menu display-ing the Implement Interfaceoption. Selecting it brings up theImplement Interface Wizard dia-log box; press the Add TypeLibbutton to bring up the dialogshown in Figure 2-27.

Shown in the dialog are all the Type Libraries registered on the current devel-opment computer. Selecting one will make its interfaces available forimplementation in the current class; you can select as many as you need.

Pressing OK brings up the dia-log box shown in Figure 2-28.

The figure shows a sample listof the interfaces supported by achosen Type Library’s server.Selecting them will cause “stub”code to be placed for them inthe current object, allowing adeveloper to automaticallyaggregate or contain the inter-face as desired.

Chapter Two—An ATL Primer � 39

Figure 2-26

The ATLClassViewcontext menushowing theImplementInterfaceoption

Figure 2-27

The ATL BrowseType Librariesdialog

Figure 2-28

The ATLImplementInterface Wiz-ard dialog

Page 59: Learn OLE DB Development With Visual C++ 6.0

Where We Go From HereWhere We Go From Here

Now that you’re familiar with the basic mechanics of ATL in Visual C++, youare ready to add your understanding of COM to this knowledge and start cre-ating OLE DB consumers and providers as ActiveX controls and Automationservers. Chapter 4 begins the process with basic OLE DB programming infor-mation. (Chapter 3 goes over MFC; you can skip it if you don’t use thatenvironment.) Put on your Component Object Model hardhat and fire up theATL rivet gun: OLE DB awaits!

40 � Chapter Two—An ATL Primer

Page 60: Learn OLE DB Development With Visual C++ 6.0

Chapter Three

An MFC PrimerAn MFC Primer

MFC stands for the Microsoft Foundation Classes. MFC is a set of C++objects that encapsulate access to a number of Windows programmingaspects, including the GDI (graphical device interface), file operations, win-dow creation, message handling, and various specialized aspects of the OS.One of these specialized aspects is ActiveX; support for it is added via existingsupport 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 APIcalls 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 howMFC fits into Visual C++ and what its various wizard dialogs do. A brief lookis 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 spe-cific classes they use.) When you’ve finished reading this chapter, you’ll beready to start creating MFC-based COM servers for OLE DB.

MFC in Visual C++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 Environmentof Visual C++ with wizard dialogs and automatic code generators. It alsoprovides extensive online help and many detailed sample projects.

MFC’s Online Documentation

As mentioned above, MFC is a complex system. It has many useful featuresthat can make ActiveX programming easier and faster, but at a price in timeand effort to familiarize yourself with it. Since you’ve purchased (or are con-sidering purchasing) this book, you are obviously interested in MFC andActiveX. One very good way to learn about it is to work through all the onlinedocumentation installed with it.

41

Page 61: Learn OLE DB Development With Visual C++ 6.0

To access the online documents for MFC, use the Help menu option andselect Search. In the edit control, enter MFC and press the Search button. Anumber of results will appear; scroll down until you see the feature you areinterested in. Clicking on it will bring up a group of topics; the best place tostart is “MFC Article Overview”; select it and press the Display button. Fromthere, all the MFC documents are hyperlinked into one detailed unit, whichyou can browse and print at your leisure.

Creating MFC Projects with the MFC ActiveXCreating MFC Projects with the MFC ActiveX

ControlWizard

An MFC project starts with the MFC ActiveX ControlWizard. It is found in theubiquitous New dialog of Visual C++, and provides a simple wizard dialogthat hides a lot of power behind its plain face. With this wizard and its atten-dant confirmation dialog, you unleash all the power of MFC for ActiveXprogramming.

The New Dialog

To start an MFC project, select File|New from the Visual C++ menu. TheNew dialog appears; select the Projects tab to produce the display shown inFigure 3-1. By clicking on the MFC ActiveX ControlWizard entry as illustratedin the figure, you enable entry of the project name (which should be differentfrom the planned name(s) of any other controls to avoid conflicts) and selec-tion of its directory on the hard drive of the development computer. When

you haveentered thisinformation,pressing OKwill start theactual MFCActiveXControlWizard.

42 � Chapter Three—An MFC Primer

Figure 3-1

The VisualC++ Newdialog creatinga new MFCActiveXControlWizardproject

Page 62: Learn OLE DB Development With Visual C++ 6.0

The MFC ActiveX ControlWizard

Once you press OK in the New dialog, the MFC ActiveX ControlWizard dialogappears, very similar in appearance to Figure 3-2. It contains two pages, with

a number of choices. As illus-trated by the figure, you canenter how many controls youwant to create in the project(default is 1), whether or notto use a run-time license (ameans of protecting openlydistributed controls frombeing reused after they aredownloaded for display),whether or not to generatesource file comments (defaultis yes, a good idea), andwhether or not to create abasic WinHelp helpfile project(default is no). Once youhave made the desiredchoices, press the Next buttonto bring up the second pageof the wizard.

Pressing Next brings up thesecond page of the wizard, asshown in Figure 3-3. As illus-trated in the figure, you selectthe active control for its set-tings from a drop-down list.For each control you can editits filenames (shown in Fig-ure 3-4) or set a number ofspecialized options (shown inFigure 3-5). You can alsoselect a base Windows com-mon control to use as thebasis for the currentlyselected ActiveX control, andset a number of advancedoptions. Once you have madethe desired choices, press theFinish button to bring up theconfirmation dialog.

Chapter Three—An MFC Primer � 43

Figure 3-2

The MFCActiveXControlWizarddialog firstpage

Figure 3-3

The MFCActiveXControlWizarddialog secondpage

Figure

3-4 TheMFC ActiveXControlWizardEdit Namesdialog

Page 63: Learn OLE DB Development With Visual C++ 6.0

The Confirmation Dialog

Pressing OK in the MFC ActiveX ControlWizard brings up a confirmation dia-log, as shown in Figure 3-6. This dialog lists the project name, the name of

the ActiveX control that willbe generated, and its defaultProperty Page, as well as thefilenames for these objects.Pressing OK actually gener-ates the new MFC projectwith the selected options anddisplayed filenames.

Working MFC Magic with the ClassWizardWorking MFC Magic with the ClassWizard

Once you have created your MFC project viathe MFC ActiveX ControlWizard, all youreally have is a shell that does nothing. TheControlWizard does not add any ActiveXfunctionality! Instead, you must do thatyourself. Fortunately, MFC provides theenormously powerful ClassWizard dialogsystem to help with this task. You access itvia the View|ClassWizard menu option asshown in Figure 3-7, which brings up theClassWizard dialog box. There are five tabsfor the ClassWizard, each of which isdiscussed below.

44 � Chapter Three—An MFC Primer

Figure 3-6

The MFCActiveXControlWizardconfirmationdialog

Figure 3-7

The MFC ClassWizard menu option

Figure 3-5

The MFCActiveX Con-trolWizardAdvancedActiveX Fea-tures dialog

Page 64: Learn OLE DB Development With Visual C++ 6.0

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 messageswith the currently selected ActiveX control’s MFC class.

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 ActiveXMFC programming: to store information for control use, to connect with

exposedActiveX controlproperties, andto move infor-mation in andout of Windowscontrols onActiveX controlProperty Pages.

Chapter Three—An MFC Primer � 45

Figure 3-8

The MFCClassWizardMessageMaps tab

Figure 3-9

The MFCClassWizardMember Vari-ables tab

Page 65: Learn OLE DB Development With Visual C++ 6.0

The ClassWizard Automation Tab

When you select the Automation tab of the MFC ClassWizard, you’ll see a dis-play very much like that of Figure 3-10. It contains two drop-down lists, oneof all the projects in the current workspace on the left and one of all theclasses in the current project on the right. If the currently selected class in the

right-handcombo box isAutoma-tion-enabled(by default allActiveX controlclasses are),the various but-tons to theright areenabled aswell. They areused to addcustom andstock methodsand propertiesto the ActiveXcontrol.

The ClassWizard ActiveX Events Tab

The ActiveX Events tab of the MFC ClassWizard brings up the display shownin Figure 3-11. It has the same basic layout as the Automation tab coveredabove, with its project and class selection combo boxes. Its left-hand list box

gives all thecurrentlyenabledActiveX events(which are dif-ferent fromWindowsevents) for thecurrent Auto-mation-enabledclass. To add anew event, usethe Add Eventbutton to theright.

46 � Chapter Three—An MFC Primer

Figure 3-10

The MFCClassWizardAutomationtab

Figure 3-11

The MFCClassWizardActiveXEvents tab

Page 66: Learn OLE DB Development With Visual C++ 6.0

The ClassWizard Class Info Tab

If you select the rightmost tab of the ClassWizard, you’ll see a display similarto that of Figure 3-12. This is the Class Info tab of the MFC ClassWizardtabbed dialog box. Its layout is similar to the two previous tabs, with its pro-ject and class selection combo boxes. For each selected class, the tab displays

the header file,source file,base class, andresource fileinformation. Italso allowsselection of aWindows mes-sage filterclass, and of“foreign”classes andvariables foruse in the classcurrentlyselected.

Augmenting ActiveX Control Features withAugmenting ActiveX Control Features with

ClassWizard DialogsClassWizard Dialogs

There are a number of impor-tant dialogs accessed throughClassWizard’s various tabs; eachone is shown below with aquick tour of its major features.

The ClassWizard New ClassDialog

Figure 3-13 shows the NewClass dialog for the MFCClassWizard. It is accessed fromthe Automation tab of the wiz-ard via the Add Class button’sNew Class menu option. Whileyou won’t use this dialog in nor-mal ActiveX programming with

Chapter Three—An MFC Primer � 47

Figure 3-12

The MFCClassWizardClass Info tab

Figure 3-13 The MFC ClassWizard New Classdialog in Visual C++ 6.0

Page 67: Learn OLE DB Development With Visual C++ 6.0

MFC, it is a vital way to add Automation/ActiveX features to a basic MFC pro-ject. To use the New Class dialog, simply enter a new class name in the upperedit control, then select a base class from the middle combo box(CCmdTarget is a good one). Finally, decide whether to use basic Automationor TypeID support (shown in the figure). TypeID support is best for mostAutomation projects.

The ClassWizard Add Method Dialog

Selecting the Add Method button from the ClassWizard Automation tabbrings up the Add Method dialog shown in Figure 3-14. As illustrated in the

figure, there are stock methodsavailable from the upper combobox, or you can enter a newname for a custom method. Aninternal name will be created,which you can edit if desired.The return type is void for stockmethods, but you can set it toother values if desired (the MFCsystem will actually move thesein and out as parameters).Finally, you can add any desiredparameters to the method via thetwo combo boxes at the bottomof the dialog.

The ClassWizard Add Property Dialog

Selecting the Add Property but-ton from the ClassWizardAutomation tab brings up theAdd Property dialog shown inFigure 3-15. As illustrated bythe figure, this is a much moreinvolved dialog box. First, youmust enter an external namefor the property; this is whatclients of your control or otherAutomation class will workwith. Then you must select adata type for the property fromthe available list in the combobox. The MFC system will addautomatic support for Get and

48 � Chapter Three—An MFC Primer

Figure 3-14

The MFCClassWizardAdd Methoddialog inVisual C++6.0

Figure 3-15

The MFCClassWizardAdd Propertydialog inVisual C++6.0

Page 68: Learn OLE DB Development With Visual C++ 6.0

Put methods (explained in Chapter 2) if you select that radio button in theImplementation section; if you select Member Variable, these function editcontrols are disabled. This dialog also allows you to select from a number ofstock properties; in this case the Implementation radio button is automati-cally Stock and you cannot change it. Finally, you can add parameters of anysupported data type to the property; this is because ActiveX/Automationproperties are actually functions.

The ClassWizard Add Event Dialog

Selecting the Add Event button from the ClassWizard ActiveX Events tabbrings up the Add Event dialog shown in Figure 3-16. As illustrated by the

figure, you can either select astock event (two are supported)or enter a new external name fora custom one. An internal namebased on the external name isautomatically generated; you canedit it if you like. If you select aCustom implementation, you canalso add parameters of any sup-ported data type to the event;this is because ActiveX events arereally just method functionsexchanged between clients andcontrols.

Some Advanced Topics for MFC ActiveX Control ProjectsSome Advanced Topics for MFC ActiveX Control Projects

There are three other topics MFC ActiveX developers need to understandbeyond those covered already in this chapter: connecting Windows events toActiveX controls, editing Property Page dialogs, and connecting Property Pagedialog 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 Win-dows 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 enablethe Add Function button on the right- hand side. Clicking on it will bring up adialog allowing editing of the default name given to the event handler; oncethis dialog has been accepted, the new event and its handler function areshown in the lower list box display. You can then write custom code in the

Chapter Three—An MFC Primer � 49

Figure 3-16

The MFCClassWizardAdd Event dia-log in VisualC++ 6.0

Page 69: Learn OLE DB Development With Visual C++ 6.0

actual handler itself (the Edit Code button is a handy way to find the exactfunction in the class file).

Editing Property Pages

MFC automatically creates a Property Page dialog resource for all ActiveXcontrol projects. It is editable in the Dialog Editor of Visual C++, as shown inFigure 3-18. You use the standard dialog box editing techniques to put onWindows common controls, position them, and change their captions andother 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 “flipsup” and the dialogstays on top evenwhen you edit themain dialogresource. Other-wise, the EditProperties dialogbox is hidden onceyou return to edit-ing the maindisplay.)

50 � Chapter Three—An MFC Primer

Figure 3-18

The DialogEditor inVisual C++6.0

Figure 3-17

The MFCClassWizardMessageMaps tab inVisual C++6.0

Page 70: Learn OLE DB Development With Visual C++ 6.0

Connecting Property Page Controls to ActiveX Control Propertieswith ClassWizard

Once you have a Property Page dialog all ready to go, MFC will happily dis-play it for you, but it lets you decide which controls connect to what ActiveXcontrol properties. Unlike the ATL system, MFC provides help in this connec-tion process via the ClassWizard. Its Member Variables tab shown in Figure3-19 illustrates how this works. As shown in the figure, once you have a con-trol on your Property Page dialog that can be connected to an ActiveX control

property, itsidentifier islisted in thelower list boxof the dialogpage. To con-nect it with anActiveX controlproperty, sim-ply select thedesired controlidentifier andclick on theAdd Variablebutton to theright.

Once you do this, the Add Member Variable dialog appears, as shown in Fig-ure 3-20. The upper edit control allows you to enter the desired name; notethat it already contains the appropriate prefix for the Hungarian notation

used by MFC. Once you enter thedesired name, select either a Valuecategory or a Reference one; theformer passes the data from thecontrol as a value, while the latterputs it in directly as a pointer.Next, select the variable type,which can be any of those Auto-mation supports plus some MFCspecialties like CString. Thenselect the optional property namefrom those available, includingboth custom and stock properties.

Chapter Three—An MFC Primer � 51

Figure 3-20

The MFCClassWizardAdd MemberVariable dialogin VisualC++ 6.0

Figure 3-19

The MFCClassWizardMember Vari-ables tab inVisual C++6.0

Page 71: Learn OLE DB Development With Visual C++ 6.0

MFC Code (Classes and Macros, Oh My!)MFC Code (Classes and Macros, Oh My!)

MFC’s code is not straightforward. As we’ll see in examining the various filesoutput by the MFC wizards, there are many unusual software constructs inthem. 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 functionalityof Windows programming in the C++ metaphor (the actual Windows API isC-based, and so are just functions rather than classes). This was done initiallyto speed developers’ understanding of the powerful but very complex andoften unintuitive Windows programming process. The MFC system is a com-promise; it does use C++ classes and syntax, but doesn’t do much more.Other vendors such as Inprise (aka Borland) have other wrapper classes thatare much more sophisticated, but developers pay a price in that they dothings much the way the framework wants them to, rather than their ownway. MFC’s compromise allows much developer flexibility while providingconsiderable C++ capability.

Macros Expand into Customized Code

A macro is a defined identifier that represents a block of source code. Amacro is expanded during the preprocessor step of compilation, and adds itscode to that of the basic source file, replacing parameters of the macro withreal 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 macrosin the coming chapters; it is often useful to select them in the IDE and exam-ine their online help entry.

Where We Go From HereWhere 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 startcreating OLE DB consumer and provider ActiveX controls and Automationservers. Chapter 4 begins with a detailed overview of OLE DB programmingand the COM interfaces it requires. Put on your Component Object Modelhardhat and fire up the MFC assembly line: OLE DB awaits!

52 � Chapter Three—An MFC Primer

Page 72: Learn OLE DB Development With Visual C++ 6.0

Chapter Four

An OLE DB PrimerAn OLE DB Primer

OLE DB is based on a set of COM interfaces that together provide the func-tionality of consumers (which provide access to databases via OLE DBproviders) and providers (which give information from various data sourcesto their consumers). The following sections cover all the major interfaces ofOLE DB in detail.

IAccessor

The IAccessor interface provides methods for OLE DB accessor management.All rowsets and commands must implement the IAccessor interface. OLE DBaccessors can be used for rowset data, parameter data, or both. Row andparameter OLE DB accessors can be created on a command. The OLE DB con-sumer must verify that row OLE DB accessors created on the command arestill valid following a change to the command. OLE DB accessors created on acommand are not persisted with that command. OLE DB accessors created ona 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 tothe rowset: The bindings, flags, and handle of each OLE DB accessor are thesame on both the rowset and the command.

OLE DB accessors created on a rowset are only available to that rowset. Theyare not available to the command that created the rowset or other rowsetscreated by the command. When an OLE DB accessor is created on a rowsetor command, it has a reference count of 1. If a rowset inherits an OLE DBaccessor from a command, the OLE DB accessor has a reference count of 1 onthe rowset, regardless of the reference count of the same OLE DB accessor onthe command.

Calls to the AddRefAccessor method increment the reference count of theOLE DB accessor, and calls to the ReleaseAccessor method decrement the ref-erence count of the OLE DB accessor. When the reference count of an OLE DBaccessor reaches zero, the OLE DB accessor and all resources used by thatOLE DB accessor are released. If the OLE DB accessor is created on a

53

Page 73: Learn OLE DB Development With Visual C++ 6.0

command and inherited by the rowset, releasing the OLE DB accessor on thecommand does not affect the “copy” of the OLE DB accessor on the rowsetand vice versa.

When the reference count of a rowset reaches zero, all OLE DB accessors cre-ated on that rowset or inherited from the command that created it arereleased completely. If any OLE DB accessors were inherited from the com-mand, the “copies” of these parent OLE DB accessors on the command arenot affected.

When the reference count of a command reaches zero, all OLE DB accessorscreated on the command are released completely. If any rowsets inheritedOLE DB accessors from the command, the “copies” of these OLE DB accessorson the rowsets are not affected.

To create an OLE DB accessor, an OLE DB consumer calls the CreateAccessormethod. The OLE DB consumer may create and release OLE DB accessors atany time while the rowset or command remains in existence. When onethread of an OLE DB consumer shares an OLE DB accessor with anotherthread, it calls the AddRefAccessor method to increment the reference countof that OLE DB accessor.

When the OLE DB consumer is done with a rowset, it calls theReleaseAccessor method to release any OLE DB accessors on the rowset,including OLE DB accessors inherited from the command. When the OLE DBconsumer is done with a command, it calls the ReleaseAccessor method torelease any OLE DB accessors created on the command. In both cases, theOLE DB consumer must call the ReleaseAccessor method once for each refer-ence count on the OLE DB accessor.

Method Description

AddRefAccessor This method adds a reference count to an existing OLEDB accessor.

CreateAccessor This method creates an OLE DB accessor from a set ofbindings.

GetBindings This method returns the bindings in an OLE DB accessor.

ReleaseAccessor This method releases an OLE DB accessor.

IAccessor::AddRefAccessor

This method adds a reference count to an existing OLE DB accessor. The OLEDB consumer must increment the reference count on an OLE DB accessor bycalling the AddRefAccessor method before passing it to another thread. It hasthe following syntax:

HRESULT AddRefAccessor (HACCESSOR hAccessor,ULONG * pcRefCount);

54 � Chapter Four—An OLE DB Primer

Page 74: Learn OLE DB Development With Visual C++ 6.0

It has the following parameters:

hAccessor [in]This parameter is the handle of the OLE DB accessor for which to incre-ment the reference count.

pcRefCount [out]This parameter is a pointer to memory in which to return the referencecount of the OLE DB accessor handle. If *pcRefCount is a null pointer, noreference count is returned.

It has the following return codes:

S_OKThis return code indicates that the method succeeded.

E_FAILThis return code indicates that a provider-specific error occurred.

E_UNEXPECTEDThis return code indicates that ITransaction::Commit orITransaction::Abort was called and the object is in a zombie state. Thiserror can be returned only when the method is called on a rowset.

DB_E_BADACCESSORHANDLEThis return code indicates that hAccessor was invalid.

IAccessor::CreateAccessor

The CreateAccessor method creates an OLE DB accessor from a set of bind-ings. The CreateAccessor method always checks all error conditions that donot require it to validate the OLE DB accessor against the metadata. As a gen-eral rule, this means it checks the error conditions for all of the return codesexcept those that return DB_E_ERRORSOCCURRED.

The CreateAccessor method may validate the OLE DB accessor against themetadata for row OLE DB accessors created on the rowset; it never validatesthe OLE DB accessor against the metadata for row or parameter OLE DBaccessors created on the command. When the CreateAccessor method vali-dates the OLE DB accessor against the metadata, it validates each binding inthe OLE DB accessor, setting the appropriate DBBINDSTATUS values as itgoes. If the CreateAccessor method fails in any way, it does not create theOLE DB accessor and sets *phAccessor to a null handle.

If the CreateAccessor method does not validate the OLE DB accessor againstthe metadata, then the validation is said to be delayed. The CreateAccessormethod simply creates the OLE DB accessor and validation is done by the firstmethod that uses the OLE DB accessor. If the OLE DB accessor is found to beinvalid, it remains in existence and can be used again.

If the OLE DB accessor validation is delayed, the OLE DB provider determineswhether the method validating the OLE DB accessor validates it against the

Chapter Four—An OLE DB Primer � 55

Page 75: Learn OLE DB Development With Visual C++ 6.0

metadata before or during data transfer. If the method validates the OLE DBaccessor before transferring any data, it can return any of the return codeslisted below. If the method validates the OLE DB accessor while transferringthe data, it sets the status value of any column or parameter for which theOLE DB accessor is invalid (within the context of the method) toDBSTATUS_E_BADACCESSOR and returns DB_S_ERRORSOCCURRED orDB_E_ERRORSOCCURRED. Whether the method continues processing othercolumns or parameters depends on both the method and the provider.

The following return codes are returned by methods that perform delayedOLE DB accessor validation before transferring any data. The DBBINDSTATUSvalue to which each corresponds is also listed.

Return Code DBBINDSTATUS Value

E_NOINTERFACE DBBINDSTATUS_NOINTERFACE

DB_E_BADBINDINFO DBBINDSTATUS_BADBINDINFO

DB_E_BADORDINAL DBBINDSTATUS_BADORDINAL

DB_E_BADSTORAGEFLAGS 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 DBaccessor and how it is to be used. These flags have the followingmeanings:

Value Description

DBACCESSOR_INVALID This flag is used by GetBindings to indicate that themethod failed.

DBACCESSOR_PASSBYREF This flag indicates that the OLE DB accessor is areference OLE DB accessor.

The value passed in the OLE DB consumer buffer is apointer to the passer’s internal buffer. This pointer neednot point to the start of the internal buffer, as long asthe relative offsets of all elements of the buffer align

56 � Chapter Four—An OLE DB Primer

Page 76: Learn OLE DB Development With Visual C++ 6.0

Value Description

DBACCESSOR_PASSBYREF(cont.)

with the offsets specified in the OLE DB accessor. Thepassee must know the internal structure of the passer’sbuffer in order to read information from it. The passeemust not free the buffer at the pointer, nor may it writeto this buffer.

For row OLE DB accessors, this buffer is the rowset’scopy of the row. The OLE DB consumer reads infor-mation directly from this copy of the row at a laterpoint in time, so the OLE DB provider must guaranteethat the pointer remains valid.

For parameter OLE DB accessors, this buffer is theOLE DB consumer’s buffer. The OLE DB providerreads data from this buffer only when ICommand::Execute is called, so the pointer is not required toremain valid after Execute returns.

Support for this flag is optional. An OLE DB consumerdetermines whether an OLE DB provider supports thisbit by calling IDBProperties::GetProperties for theDBPROP_BYREFACCESSORS property.

When this flag is used, the dwMemOwner in theDBBINDING structure is ignored. If the OLE DBaccessor is used for row data, the OLE DB accessorrefers to the OLE DB provider’s memory; the OLE DBconsumer must not write to or free this memory. If theOLE DB accessor is used for input parameters, the OLEDB provider copies the row of data without assumingownership.

It is an error to specify an output or input/outputparameter in a reference OLE DB accessor.

DBACCESSOR_ROWDATA

This flag indicates that the OLE DB accessor is a rowOLE DB accessor and describes bindings to columns inthe rowset. An OLE DB accessor may be a row OLEDB accessor, a parameter OLE DB accessor, or both.

DBACCESSOR_PARAMETERDATA

This flag indicates that the OLE DB accessor is aparameter OLE DB accessor and describes bindings toparameters in the command text. In a parameter OLEDB accessor, it is an error to bind an input or aninput/output parameter more than once.

DBACCESSOR_OPTIMIZED

This flag indicates that the row OLE DB accessor is tobe optimized. This hint may affect how a providerstructures its internal buffers. A particular column canbe bound by only one optimized OLE DB accessor. Thecolumn can also be bound by other, nonoptimized OLEDB accessors, but the types specified in the nonopti-mized OLE DB accessors must be convertible from thetype in the optimized OLE DB accessor. All optimized

Chapter Four—An OLE DB Primer � 57

Page 77: Learn OLE DB Development With Visual C++ 6.0

Value Description

DBACCESSOR_OPTIMIZED (cont.)

OLE DB accessors must be created before the first rowis 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 DBaccessor. Null OLE DB accessors are used only by IRowsetChange::InsertRow to create a new row in which each column is set to its defaultvalue, NULL, or a status of DBSTATUS_E_UNAVAILABLE. OLE DB provid-ers that support InsertRow must support the creation of null OLE DBaccessors.

rgBindings [in]This parameter indicates an array of DBBINDING structures.

cbRowSize [in]This parameter indicates the numer of bytes allocated for a single set ofparameters or criteria values in the OLE DB consumer’s buffer. cbRowSizeis used by ICommand::Execute to process multiple sets of parameters,and by IViewFilter::GetFilter and IViewFilter::SetFilter to get and set mul-tiple OR conditions in criteria. In both cases, a single OLE DB accessormay describe multiple sets of values. cbRowSize is generally the size ofthe structure that contains a single set of parameter or criteria values andis used as the offset to the start of the next set of values within the arrayof structures. For example, if cParamSets is greater than 1 in theDBPARAMS structure passed to the Execute method, the OLE DB providerassumes that the pData element of this structure points to an array ofstructures containing parameter values, each cbRowSize bytes in size.Similarly, if cRows is greater than 1 in the SetFilter method, the OLE DBprovider assumes that the pCriteriaData argument points to an array ofstructures containing criteria values, each cbRowSize bytes in size.cbRowSize must be large enough to contain the structure defined by thebindings 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 han-dle of the created OLE DB accessor. If the CreateAccessor method fails, itmust attempt to set *phAccessor to a null handle.

rgStatus [out]This parameter indicates an array of cBindings DBBINDSTATUS values inwhich the CreateAccessor method returns the status of each binding; that

58 � Chapter Four—An OLE DB Primer

Page 78: Learn OLE DB Development With Visual C++ 6.0

is, whether it was successfully validated or not. If rgStatus is a nullpointer, no bind status values are returned. The OLE DB consumer allo-cates and owns the memory for this array. The bind status values arereturned for the following reasons:

Value Description

DBBINDSTATUS_OK This flag shows that no errors were found in thebinding. Because OLE DB accessor validation can bedeferred, a status of DBBINDSTATUS_OK does notnecessarily mean that the binding was successfullyvalidated.

DBBINDSTATUS_BADORDINAL

This flag indicates that a parameter ordinal was zero ina parameter OLE DB accessor.

If the OLE DB accessor is validated against themetadata when the CreateAccessor method is called,DBBINDSTATUS_BADORDINAL can be returned forthe following reasons:

� In a row OLE DB accessor, a column ordinal in abinding was outside the range of available columnson the rowset.

� In a parameter OLE DB accessor, a parameterordinal was greater than the number of parametersin the command text.

These reasons cause a status value of DBSTATUS_E_BADACCESSOR to be returned if the OLE DBaccessor is validated when used.

Some OLE DB providers may support binding moreparameters than the number of parameters in thecommand text, and such OLE DB providers do not

return DBBINDSTATUS_BADORDINAL in this case.

DBBINDSTATUS_UNSUPPORTEDCONVERSION

This flag shows that if the OLE DB accessor isvalidated against the metadata when the Create-Accessor method is called, DBBINDSTATUS_UNSUPPORTEDCONVERSION can be returned forthe following reason:

� The specified conversion was not supported by theOLE DB provider.

� The OLE DB consumer attempted to convert acolumn to a storage object (ISequentialStream,IStorage, IStream, ILockBytes) and the particularconversion is not supported by the provider.

These reasons cause a status value of DBSTATUS_E_BADACCESSOR to be returned if the OLE DBaccessor is validated when used.

Chapter Four—An OLE DB Primer � 59

Page 79: Learn OLE DB Development With Visual C++ 6.0

Value Description

DBBINDSTATUS_BADBINDINFO

This flag indicates that dwPart in a binding was not oneof the following:DBPART_VALUEDBPART_LENGTHDBPART_STATUS

eParamIO in a binding in a parameter OLE DB accessorwas not one of the following:DBPARAMIO_INPUTDBPARAMIO_OUTPUTDBPARAMIO_INPUT | DBPARAMIO_OUTPUT

A row OLE DB accessor was optimized and a columnordinal in a binding was already used in anotheroptimized OLE DB accessor.

In a parameter OLE DB accessor, two or morebindings contained the same ordinal for an input orinput/output parameter.

wType in a binding was DBTYPE_EMPTY or DBTYPE_NULL.

wType in a binding was one of the following:DBTYPE_BYREF | DBTYPE _EMPTYDBTYPE_BYREF | DBTYPE_NULL orDBTYPE_BYREF | DBTYPE_RESERVED

wType in a binding was used with more than one of thefollowing mutually exclusive type indicators: DBTYPE_BYREF, DBTYPE_ARRAY, or DBTYPE_VECTOR.

wType in a binding was DBTYPE_IUNKNOWN, andpObject in the same binding was a null pointer.

Provider-owned memory was specified for a non-pointer type in a nonreference row OLE DB accessor.

OLE DB provider-owned memory was specified for acolumn and the OLE DB provider does not supportbinding to OLE DB provider-owned memory for thiscolumn.

OLE DB provider-owned memory was specified for acolumn for which IColumnsInfo::GetColumnInforeturned DBCOLUMNFLAGS_ISLONG.

In a nonreference parameter OLE DB accessor, abinding specified OLE DB provider-owned memory.

An output or input/output parameter was specified ina 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.

60 � Chapter Four—An OLE DB Primer

Page 80: Learn OLE DB Development With Visual C++ 6.0

Value Description

DBBINDSTATUS_BADBINDINFO (cont.)

If the OLE DB accessor is validated against the meta-data when the CreateAccessor method is called,DBBINDSTATUS_BADBINDINFO can be returned forthe following reasons:

� A row OLE DB accessor was not optimized, acolumn number in a binding specified a column thatwas already used in an optimized OLE DB accessor,and the OLE DB provider did not support aconversion from the type specified in the optimizedOLE DB accessor for the column to the typespecified in wType.

� In a parameter OLE DB accessor, eParamIO in abinding specified the incorrect I/O type for theparameter. Some OLE DB providers cannotdetermine parameter I/O types and never returnDBBINDSTATUS_BADBINDINFO in this case.

� In a reference OLE DB accessor, the value specifiedfor dwPart, obValue, cbMaxLen, or wType in a bindingdid not match the format of the correspondingelement in the rowset’s copy of the row.

� In a nonreference OLE DB accessor, a bindingspecified provider-owned memory, wType was X |DBTYPE_BYREF, and the data type of thecorresponding element of the rowset’s copy of therow was not X or X | DBTYPE_BYREF.

� In a nonreference OLE DB accessor, a bindingspecified OLE DB provider-owned memory, wTypewas DBTYPE_BSTR, and the data type of thecorresponding element of the rowset’s copy of therow was not DBTYPE_BSTR.

� The OLE DB accessor was used for passing keycolumn values in IRowsetIndex::Seek orIRowsetIndex::SetRange, and the order in which thekey columns were bound did not match the order inwhich they were returned in IColumnsInfo::GetColumnInfo.

� The OLE DB accessor was used for passing keycolumn values in the Seek method or the SetRangemethod, and a less significant key column wasbound without binding all more significant keycolumns.

� The OLE DB accessor was used for passing keycolumn values in the Seek method or the SetRangemethod, and a non-key column was bound beforethe last bound key column.

Chapter Four—An OLE DB Primer � 61

Page 81: Learn OLE DB Development With Visual C++ 6.0

Value Description

DBBINDSTATUS_BADBINDINFO (cont.)

These reasons cause a status value of DBSTATUS_E_BADACCESSOR to be returned if the OLE DBaccessor is validated when used.

DBBINDSTATUS_BADSTORAGEFLAGS

This flag indicates that dwFlags, in the DBOBJECTstructure pointed to by a binding, specified invalidstorage flags.

If the OLE DB accessor is validated against the meta-data when the CreateAccessor method is called,DBBINDSTATUS_BADSTORAGEFLAGS can bereturned for the following reason:

� dwFlags, in the DBOBJECT structure pointed to bya binding, specified a valid storage flag that was notsupported by the object.

These reasons cause a status value of DBSTATUS_E_BADACCESSOR to be returned if the OLE DBaccessor is validated when used.

DBBINDSTATUS_NOINTERFACE

This flag shows that if the OLE DB accessor is vali-dated against the metadata when the CreateAccessormethod is called, DBBINDSTATUS_NOINTERFACEcan be returned for the following reasons:

� The OLE DB provider did not support the storageinterface specified in iid in the DBOBJECT structurepointed to by a binding in the OLE DB accessor.

� The OLE object in a column or parameter did notsupport the interface specified in iid in theDBOBJECT structure pointed to by thecorresponding binding in the OLE DB accessor.

� The OLE DB provider supports only one openstorage object at a time—that is,DBPROP_MULTIPLESTORAGEOBJECTS isVARIANT_FALSE—and wType in more than onebinding was DBTYPE_IUNKNOWN.

These reasons cause a status value of DBSTATUS_E_BADACCESSOR to be returned if the OLE DBaccessor is validated when used.

It has the following return codes:

S_OKThis return code indicates that the method succeeded. If rgStatus is not anull pointer, each element is set to DBBINDSTATUS_OK.

E_FAILThis return code means that an OLE DB provider-specific error occurred.

62 � Chapter Four—An OLE DB Primer

Page 82: Learn OLE DB Development With Visual C++ 6.0

E_INVALIDARGThis return code shows that *phAccessor was a null pointer, or cBindings

was not zero and rgBindings was a null pointer.

E_UNEXPECTEDThis return code indicates that ITransaction::Commit or ITransaction::Abort was called and the object is in a zombie state. This error can bereturned only when the method is called on a rowset.

DB_E_BADACCESSORFLAGSThis return code indicates that dwAccessorFlags was invalid; theDBACCESSOR_PARAMETERDATA bit was set in dwAccessorFlags, and theOLE DB provider does not support parameters; neither theDBACCESSOR_PARAMETERDATA bit nor the DBACCESSOR_ROWDATAbit was set in dwAccessorFlags; or a method that fetches rows (IRowset::GetNextRows, IRowsetLocate::GetRowsAt, IRowsetLocate::GetRowsBy-Bookmark, or IRowsetScroll::GetRowsAtRatio) had already been calledand the DBACCESSOR_OPTIMIZED bit in dwAccessorFlags was set. Thisreturn code can also inndicate that the DBACCESSOR_PARAMETER-DATA bit was set and the CreateAccessor method was called on a rowset.

DB_E_BYREFACCESSORNOTSUPPORTEDThis return code shows that dwAccessorFlags was DBACCESSOR_PASSBYREF and the value of the DBPROP_BYREFACCESSORS property isVARIANT_FALSE.

DB_E_ERRORSOCCURREDThis return code means OLE DB accessor validation failed. To determinewhich bindings failed, the OLE DB consumer checks the values returnedin rgStatus, at least one of which is not DBBINDSTATUS_OK.

DB_E_NOTREENTRANTThis return code indicates that the OLE DB provider called a method fromIRowsetNotify in the OLE DB consumer and the method has not yetreturned.

DB_E_NULLACCESSORNOTSUPPORTEDThis return code shows that cBindings was zero and the rowset does notsupport IRowsetChange::InsertRow, or the CreateAccessor method wascalled on a command.

IAccessor::GetBindings

This method returns the bindings in an OLE DB accessor. It makes no logicalchange to the state of the object. If the OLE DB accessor is null, then themethod sets *pcBindings to zero and *prgBindings to a null pointer. It has thefollowing syntax:

HRESULT GetBindings (HACCESSOR hAccessor,

Chapter Four—An OLE DB Primer � 63

Page 83: Learn OLE DB Development With Visual C++ 6.0

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 toreturn the bindings.

pdwAccessorFlags [out]This parameter indicates a pointer to memory in which to return abitmask that describes the properties of the OLE DB accessor and how itis intended to be used. If this method fails, *pdwAccessorFlags is set toDBACCESSOR_INVALID.

pcBindings [out]This parameter provides a pointer to memory in which to return the num-ber of bindings in the OLE DB accessor. If this method fails, *pcBindings isset to zero.

prgBindings [out]This parameter indicates a pointer to memory in which to return an arrayof DBBINDING structures. One DBBINDING structure is returned for eachbinding in the OLE DB accessor. OLE DB provider allocates memory forthese structures and any structures pointed to by elements of these struc-tures; for example, if pObject in a binding structure is not a null pointer,the OLE DB provider allocates a DBOBJECT structure for return to theOLE DB consumer. The OLE DB provider returns the address to the mem-ory for these structures; the OLE DB consumer releases the memory forthese structures with IMalloc::Free when it no longer needs the bindings.If *pcBindings is zero on output or the method fails, the OLE DB providerdoes not allocate any memory and ensures that *prgBindings is a nullpointer on output.

It has the following return codes:

S_OKThis return code shows that the method succeeded.

E_FAILThis return code indicates that an OLE DB provider-specific erroroccurred.

E_INVALIDARGThis return code shows that *pdwAccessorFlags, *pcBindings, or*prgBindings were null pointers.

E_OUTOFMEMORYThis return code shows that the OLE DB provider was unable to allocatesufficient memory in which to return the binding structures.

64 � Chapter Four—An OLE DB Primer

Page 84: Learn OLE DB Development With Visual C++ 6.0

E_UNEXPECTEDThis return code indicates that ITransaction::Commit or ITransaction::Abort was called and the object is in a zombie state. This error can bereturned only when the method is called on a rowset.

DB_E_BADACCESSORHANDLEThis return code shows that hAccessor was invalid.

DB_E_NOTREENTRANTThis return code indicates that the OLE DB provider called a method fromIRowsetNotify in the OLE DB consumer and the method has not yetreturned.

IAccessor::ReleaseAccessor

This method releases an OLE DB accessor. ReleaseAccessor decrements thereference count of the OLE DB accessor. If the reference count reaches zero, itreleases 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 thatOLE DB accessor return DB_E_BADACCESSORHANDLE.

On rowsets, OLE DB accessors are read-only and can be shared amongthreads in a free-threaded style without synchronization. The OLE DB con-sumer must call the ReleaseAccessor method to decrement the referencecount on an OLE DB accessor that has been passed to a thread and is no lon-ger needed by that thread.

This method can be called while the rowset is in a zombie state to enable theOLE DB consumer to clean up after a transaction has been committed oraborted.

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 remain-ing reference count of the OLE DB accessor handle. If *pcRefCount is anull pointer, no reference count is returned.

Chapter Four—An OLE DB Primer � 65

Page 85: Learn OLE DB Development With Visual C++ 6.0

It has the following return codes:

S_OKThis return code indicates that the method succeeded.

E_FAILThis return code shows that an OLE DB provider-specific error occurred.

DB_E_BADACCESSORHANDLEThis return code indicates that hAccessor was invalid.

DB_E_NOTREENTRANTThis return code shows that the OLE DB provider called a method fromIRowsetNotify in the OLE DB consumer and the method has not yetreturned.

IColumnsInfo

IColumnsInfo is the simpler of two interfaces that can be used to exposeinformation about columns of an OLE DB rowset or prepared OLE DB com-mand. It provides a limited set of information in an array.

All OLE DB rowsets and OLE DB commands implement IColumnsInfo. Servicecomponents can synthesize IColumnsRowset from this so that OLE DB con-sumers and OLE DB providers have independent choice of whether they wantto code for simple limited information, or to work with flexible and open-ended column descriptions.

IColumnsInfo is required on both commands and rowsets. The GetColumn-Info method returns the most commonly used metadata: column IDs, datatypes, updatability, and so on. The GetColumnInfo method returns themetadata in an array of structures, which can be created and accessedquickly. The metadata returned, however, is limited. OLE DB consumers thatrequire more complete metadata can obtain it by calling IColumnsRowset::GetColumnsRowset.

Note For OLE DB commands that expose ICommandPrepare, the methods onthis interface can be called only after the OLE DB command is prepared or theOLE DB rowset is instantiated. If an OLE DB command text is set but not pre-pared, any calls to methods on IColumnsInfo return DB_E_NOTPREPARED.For OLE DB commands that do not expose ICommandPrepare, the methodson this interface can be called only after the OLE DB command text has beenset.

66 � Chapter Four—An OLE DB Primer

Page 86: Learn OLE DB Development With Visual C++ 6.0

Method Description

GetColumnInfo This method returns the column metadata needed by most OLE DBconsumers.

MapColumnIDs This method returns an array of ordinals of the columns in an OLEDB rowset that are identified by the specified column IDs.

IColumnsInfo::GetColumnInfo

This method returns the column metadata needed by most OLE DB consum-ers. 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 num-ber of columns in the OLE DB rowset; this number includes the bookmarkcolumn, if there is one. If the GetColumnInfo method is called on an OLEDB command that does not return rows, *pcColumns is set to zero. If thismethod terminates due to an error, *pcColumns is set to zero.

prgInfo [out]This parameter indicates a pointer to memory in which to return an arrayof DBCOLUMNINFO structures. One structure is returned for each col-umn in the OLE DB rowset. The OLE DB provider allocates memory forthe structures and returns the address to this memory; the OLE DB con-sumer releases this memory with IMalloc::Free when it no longer needsthe column information. If *pcColumns is 0 on output or terminates dueto an error, the OLE DB provider does not allocate any memory andensures that *prgInfo is a null pointer on output.

ppStringsBuffer [out]This parameter shows a pointer to memory in which to return a pointerto storage for all string values (names used either within columnid or for*pwszName) within a single allocation block. If no returned columns haveeither form of string name, or if this method terminates due to error, thisparameter returns a null pointer. If there are any string names, then thiswill be a buffer containing all the values of those names. The OLE DBconsumer should free the buffer with IMalloc::Free when finished work-ing with the names.

If *pcColumns is zero on output, the OLE DB provider does not allocate anymemory and ensures that *ppStringsBuffer is a null pointer on output. Each of

Chapter Four—An OLE DB Primer � 67

Page 87: Learn OLE DB Development With Visual C++ 6.0

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 embeddednull-termination characters.

It has the following return codes:

S_OKThis return code shows that the method succeeded.

E_FAILThis return code indicates that an OLE DB provider-specific erroroccurred.

E_INVALIDARGThis return code shows *pcColumns, *prgInfo, or *ppStringsBuffer as nullpointers.

E_OUTOFMEMORYThis return code indicates that the OLE DB provider was unable to allo-cate sufficient memory in which to return the column informationstructures.

E_UNEXPECTEDThis return code shows that ITransaction::Commit or ITransaction::Abortwas called and the object is in a zombie state. This error can be returnedonly when the method is called on an OLE DB rowset.

DB_E_ERRORSINCOMMANDThis return code means that the OLE DB command text contained one ormore errors. OLE DB providers should use OLE DB error objects to returndetails about the errors.

DB_E_NOCOMMANDThis return code shows that no command text was set. This error can bereturned only when this method is called from the OLE DB Commandobject.

DB_E_NOTPREPAREDThis return code indicates that the OLE DB command exposedICommandPrepare and the OLE DB command text was set, but the OLEDB command was not prepared. This error can be returned only whenthis method is called from the OLE DB Command object.

DB_E_NOTREENTRANTThis return code shows that the OLE DB provider called a method fromIRowsetNotify in the OLE DB consumer and the method has not yetreturned.

DB_SEC_E_PERMISSIONDENIEDThis return code means that the OLE DB consumer did not have sufficientpermission to retrieve the column information.

68 � Chapter Four—An OLE DB Primer

Page 88: Learn OLE DB Development With Visual C++ 6.0

The GetColumnInfo method returns a fixed set of column metadata in anarray of DBCOLUMNINFO structures, one per column. The returnedmetadata is that most commonly used by OLE DB consumers, such as thedata type and column ID. The order of the structures is the order in whichthe columns appear in the OLE DB rowset (column ordinal order). This is thesame order as they appear in IColumnsRowset, and is predictable from theordering of requested columns in the command text.

Bookmark columns may be returned on any OLE DB rowset, regardless of itssource (for example, ICommand::Execute, IOpenRowset::OpenRowset,IColumnsRowset::GetColumnsRowset, or IDBSchemaRowset::GetRowset) orwhether bookmarks were requested. When bookmarks are returned, they willalways 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 book-mark columns appropriately (that is, not display them to the user if notappropriate for the application).

The GetColumnInfo method provides a quick alternative to the GetColumns-Rowset method. While the GetColumnsRowset method returns all availablecolumn metadata, it does so in an OLE DB rowset. To get the metadata, theOLE DB consumer must therefore create the column metadata rowset, createone or more OLE DB accessors, fetch each row in the OLE DB rowset, and getthe data from the OLE DB rowset. Calling the GetColumnInfo method on anOLE DB command before the OLE DB command is executed may be anexpensive 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;

Chapter Four—An OLE DB Primer � 69

Page 89: Learn OLE DB Development With Visual C++ 6.0

The elements of the DBCOLUMNINFO structure are used as follows:

Element Description

pwszName This flag provides a pointer to the name of the column;this might not be unique. If this cannot be determined, anull pointer is returned.

The name can be different from the string part of thecolumn ID if the column has been renamed by the OLEDB command text. This name always reflects the mostrecent renaming of the column in the current view or OLEDB command text.

pTypeInfo This flag is reserved for future use. OLE DB providersshould return a null pointer in *pTypeInfo.

iOrdinal This flag is the ordinal of the column. This is zero for thebookmark column of the row, if any. Other columns arenumbered starting from one.

dwFlags This flag indicates a bitmask that describes columncharacteristics. The DBCOLUMNFLAGS enumerated typespecifies the bits in the bitmask.

ulColumnSize This flag provides the maximum possible length of a valuein the column. For columns that use a fixed-length datatype, this is the size of the data type. For columns that usea variable-length data type, this is one of the following:

� The maximum length of the column in characters, forDBTYPE_STR and DBTYPE_WSTR, or bytes, forDBTYPE_BYTES, if one is defined. For example, aCHAR(5) column in a SQL table has a maximum lengthof 5.

� The maximum length of the data type in characters, forDBTYPE_STR and DBTYPE_WSTR, or bytes, forDBTYPE_BYTES, if the column does not have a definedlength.

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 to1).

� ~0 (bitwise, the value is not 0; that is, all bits are set to1) if neither the column nor the data type has a definedmaximum length.

wType This flag provides the indicator of the column’s data type.If the data type of the column varies from row to row, thismust be DBTYPE_VARIANT.

bPrecision This flag shows that if wType is a numeric data type, this isthe maximum precision of the column. The precision ofcolumns with a data type of DBTYPE_DECIMAL orDBTYPE_NUMERIC depends on the definition of thecolumn.

70 � Chapter Four—An OLE DB Primer

Page 90: Learn OLE DB Development With Visual C++ 6.0

Element Description

bPrecision (cont.) For DBTYPE_DBTIMESTAMP data types, this is the lengthof the string representation (assuming the maximumallowed precision of the fractional seconds component). Ifthe 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).

bScale This flag indicates that if wType is DBTYPE_DECIMAL orDBTYPE_NUMERIC, this is the number of digits to theright 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 lengthof the string representation of the fractional secondscomponent.

columnid This flag shows the column ID of the column.

The column ID of a base table should be invariant underviews.

DBCOLUMNFLAGS Enumerated Type

The dwFlags element of the DBCOLUMNINFO structure is a bitmask thatdescribes column characteristics. The DBCOLUMNFLAGS enumerated typespecifies the bits in the bitmask; the meanings of these values are as follows:

Value Description

DBCOLUMNFLAGS_CACHEDEFERRED

This flag is set if, when a deferred column is first read,its value is cached by the OLE DB provider. Later readsof the column are done from this cache. The contentsof the cache can be overwritten by IRowsetChange::SetData or IRowsetRefresh::RefreshVisibleData. Thecached value is released when the row handle isreleased. Otherwise, it is not set.

This flag can be set through the DBPROP_CACHE-DEFERRED property in the OLE DB Rowset propertygroup.

DBCOLUMNFLAGS_ISBOOKMARK

This flag is set if the column contains a bookmark.Otherwise, it is not set.

DBCOLUMNFLAGS_ISCHAPTER

This flag is set if the column contains a chapter value.Otherwise, it is not set.

DBCOLUMNFLAGS_ISFIXEDLENGTH

This flag is set if all data in the column has the samelength. Otherwise, it is not set.

The flag is always set for fixed-length data types exceptDBTYPE_BSTR. The flag is set for variable-length datatypes (DBTYPE_STR, DBTYPE_WSTR, DBTYPE_BSTR, and DBTYPE_BYTES) if all the values are thesame length. The flag is set for DBTYPE_VECTOR orDBTYPE_ARRAY columns if all elements in each

Chapter Four—An OLE DB Primer � 71

Page 91: Learn OLE DB Development With Visual C++ 6.0

Value Description

DBCOLUMNFLAGS_ISFIXEDLENGTH (cont.)

vector or array are of the same length and each vectoror array has the same number of elements. Otherwise,the flag is not set.

The flag is not affected by DBTYPE_BYREF.

DBCOLUMNFLAGS_ISLONG

This flag is set if the column contains a BLOB thatcontains very long data. The definition of very long datais OLE DB provider-specific. The setting of this flagcorresponds to the value of the IS_LONG column inthe PROVIDER_TYPES schema rowset for the datatype. When this flag is set, the OLE DB providersupports reading the value through a storage interface.Although such BLOBs can be retrieved in a single piecewith the IRowset::GetData method, there may be OLEDB provider-specific problems in doing so. Forexample, the BLOB might be truncated due to machinelimits on memory or the GetData method might fail ifcalled more than once for the BLOB. Furthermore,when this flag is set, the OLE DB provider might notbe able to accurately return the maximum length of theBLOB data in ulColumnSize in the DBCOLUMNINFOstructure. When this flag is not set, the BLOB can beaccessed directly through the GetData method. It isOLE DB provider-specific whether columns withoutthis flag can be read through a storage interface.

DBCOLUMNFLAGS_ISNULLABLE

This flag is set if the column can be set to NULL or ifthe OLE DB provider cannot determine whether ornot the column can be set to NULL. Otherwise, it isnot set. This reflects only declarative rules.

DBCOLUMNFLAGS_ISNULLABLE is generally used byOLE DB consumers attempting to set data todetermine whether or not a particular column mightbe able to hold a null value.

DBCOLUMNFLAGS_ISROWID

This flag is set if the column contains a persistent rowidentifier that cannot be written to and has nomeaningful value except to identify the row.

DBCOLUMNFLAGS_ISROWVER

This flag is set if the column contains a timestamp orother versioning mechanism that cannot be written todirectly and that is automatically updated to a new,increasing value when the row is updated andcommitted. The data type of a version column is OLEDB provider-specific. How a version column is used—for example, how two version values are compared—is

also OLE DB provider-specific.

72 � Chapter Four—An OLE DB Primer

Page 92: Learn OLE DB Development With Visual C++ 6.0

Value Description

DBCOLUMNFLAGS_MAYBENULL

This flag is set if NULL can be gotten from the column,or if the OLE DB provider cannot guarantee thatNULLs cannot be gotten from the column. Otherwise,it is not set. DBCOLUMNFLAGS_MAYBENULL isgenerally used by OLE DB consumers reading data todetermine whether or not they might encounter a nullvalue. When DBCOLUMNINFO is returned in theCOLUMNS rowset, DBCOLUMNFLAGS_MAYBENULL should not be set if a simple select overthe single column of that table would return no nullvalues.

DBCOLUMNFLAGS_MAYBENULL can be set even ifDBCOLUMNFLAGS_ISNULLABLE is not set. Forexample, in a left outer join, if there is no row on theright side that matches a row on the left side, thecolumns from the right side in the joined row containNULLs, even if the underlying columns are non-nullable; that is, if DBCOLUMNFLAGS_ISNULLABLEis not set.

DBCOLUMNFLAGS_MAYDEFER

This flag is set if the column is deferred. Otherwise, itis not set.

A deferred column is one for which the data is notfetched from the data source until the OLE DBconsumer attempts to get it. That is, the data is notfetched when the row is fetched, but the IRowset::GetData method is called. This flag can be set throughthe DBPROP_DEFERRED property in the OLE DBRowset property group.

DBCOLUMNFLAGS_WRITE

This flag is set if the IRowsetChange::SetData methodcan be called for the column. Otherwise, it is not set.

This flag can be set through the DBPROP_MAYWRITECOLUMN property in the OLE DB Rowsetproperty group.

OLE DB providers never set both DBCOLUMN-FLAGS_WRITE and DBCOLUMNFLAGS_WRITEUNKNOWN; they are mutually exclusive.Absence of these flags means the column is read-only.

DBCOLUMNFLAGS_WRITEUNKNOWN

This flag is set if it is not known whether theIRowsetChange::SetData method can be called for thecolumn.

OLE DB providers never set both DBCOLUMN-FLAGS_WRITE and DBCOLUMNFLAGS_WRITEUNKNOWN; they are mutually exclusive.Absence of these flags means the column is read-only.

Chapter Four—An OLE DB Primer � 73

Page 93: Learn OLE DB Development With Visual C++ 6.0

IColumnsInfo::MapColumnIDs

This method returns an array of ordinals of the columns in an OLE DB rowsetthat are identified by the specified column IDs. This method makes no logicalchange 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 iszero, MapColumnIDs does nothing and returns S_OK.

rgColumnIDs [in]This parameter shows an array of IDs of the columns for which to deter-mine the column ordinals. If rgColumnIDs contains a duplicate columnID, a column ordinal is returned once for each occurrence of the columnID. If the column ID is invalid, the corresponding element of rgColumns isset to DB_INVALIDCOLUMN.

rgColumns [out]This parameter indicates an array of cColumnIDs ordinals of the columnsidentified by the elements of rgColumnIDs. The OLE DB consumer allo-cates but is not required to initialize memory for this array and passes theaddress of this memory to the OLE DB provider. The OLE DB providerreturns the column IDs in the array.

It has the following return codes:

S_OKThis return code indicates the method succeeded. All elements ofrgColumns are set to values other than DB_INVALIDCOLUMN.

DB_S_ERRORSOCCURREDThis return code shows that an element of rgColumnIDs was invalid. Ifthe column ID is invalid, the corresponding element of rgColumns is set toDB_INVALIDCOLUMN.

E_FAILThis return code shows that a provider-specific error occurred.

E_INVALIDARGThis return code indicates that cColumnIDs was not zero and rgColumnIDs

was a null pointer, or rgColumns was a null pointer.

E_UNEXPECTEDThis return code means that ITransaction::Commit or ITransaction::Abort

74 � Chapter Four—An OLE DB Primer

Page 94: Learn OLE DB Development With Visual C++ 6.0

was called and the object is in a zombie state. This error can be returnedonly when the method is called on an OLE DB rowset.

DB_E_ERRORSOCCURREDThis return code indicates that all elements of rgColumnIDs were invalid.All elements of rgColumns are set to DB_INVALIDCOLUMN.

DB_E_NOCOMMANDThis return code means that no command text was set. This error can bereturned only when this method is called from the Command object.

DB_E_NOTPREPAREDThis return code indicates that the OLE DB command exposedICommandPrepare and the OLE DB command text was set, but the OLEDB command was not prepared. This error can be returned only whenthis method is called from the OLE DB Command object.

DB_E_NOTREENTRANTThis return code shows that the OLE DB provider called a method fromIRowsetNotify in the OLE DB consumer and the method has not yetreturned.

MapColumnIDs returns the ordinals of the columns in the OLE DB rowsetthat are identified by the elements of rgColumnIDs. These column ordinals donot change during the life of the OLE DB rowset. Column ordinals maychange between different instantiations of an OLE DB rowset if the OLE DBcommand 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 theother contains a pointer to a GUID—are equivalent if both use the sameGUID. Both are mapped to the same column ordinal.

ISourcesRowset

ISourcesRowset returns an OLE DB rowset of data sources and enumeratorsvisible from the current enumerator.

Method Description

GetSourcesRowset This method returns an OLE DB rowset of the datasources and enumerators visible from the currentenumerator.

ISourcesRowset::GetSourcesRowset

This method returns an OLE DB rowset of the data sources and enumeratorsvisible from the current enumerator. GetSourcesRowset returns the followingrowset. The rowset is read-only. The columns are returned in the ordershown.

Chapter Four—An OLE DB Primer � 75

Page 95: Learn OLE DB Development With Visual C++ 6.0

Column Name Type Indicator Description

SOURCES_NAME DBTYPE_WSTR This column contains the name of thedata source or enumerator.

SOURCES_PARSENAME DBTYPE_WSTR This column contains the string topass to IParseDisplayName to obtain amoniker for the data source orenumerator.

SOURCES_DESCRIPTION DBTYPE_WSTR This column contains the description

of the data source or enumerator.

SOURCES_TYPE DBTYPE_UI2 This column contains information asto whether the row describes a datasource or an enumerator:

� DBSOURCETYPE_DATASOURCE

� DBSOURCETYPE_ENUMERATOR

If a single piece of code is capable ofbeing used both as a data source andas an enumerator, it is listed in therowset twice, once in each role.

SOURCES_ISPARENT DBTYPE_BOOL This column shows if the rowdescribes an enumerator. SOURCES_ISPARENT is VARIANT_TRUE if theenumerator is the parent enumerator;that is, the enumerator whoseenumeration contains the enumeratoron which ISourcesRowset::GetSourcesRowset was just called.This allows the OLE DB consumer togo backward through the enumer-ation. Whether an enumerator is ableto enumerate its parent is OLE DBprovider-specific. Otherwise,SOURCES_ISPARENT is VARIANT_FALSE.

If the row describes a data source,SOURCES_ISPARENT is ignored bythe OLE DB consumer.

It has the following syntax:

HRESULT GetSourcesRowset(IUnknown pUnkOuter,REFIID riid,ULONG cPropertySets,DBPROPSET rgPropertySets[],IUnknown ** ppSourcesRowset);

76 � Chapter Four—An OLE DB Primer

Page 96: Learn OLE DB Development With Visual C++ 6.0

It has the following parameters:

pUnkOuterThis parameter provides a pointer to the controlling IUnknown interfaceif the source’s OLE DB rowset is being created as part of an aggregate. Ifthe OLE DB rowset is not part of an aggregate, this must be set to a nullpointer.

riidThis parameter shows the IID of the interface on which to return apointer. This interface is conceptually added to the list of required inter-faces on the resulting OLE DB rowset, and the method fails(E_NOINTERFACE) if that interface cannot be supported on the resultingOLE DB rowset.

cPropertySets [in]This parameter gives the number of DBPROPSET structures inrgPropertySets. If this is zero, the OLE DB provider ignores rgPropertySets.

rgPropertySets [in/out]This parameter shows an array of DBPROPSET structures containingproperties and values to be set. The properties specified in these struc-tures must belong to the OLE DB Rowset property group. If the sameproperty is specified more than once in rgPropertySets, then it is OLE DBprovider-specific which value is used. If cPropertySets is zero, this argu-ment is ignored.

ppSourcesRowsetThis parameter gives a pointer to memory in which to return therequested interface pointer on the OLE DB rowset. If an error occurs, thereturned pointer is null.

It has the following return codes:

S_OKThis return code indicates that the method succeeded. In all DBPROPstructures passed to the method, dwStatus is set to DBPROPSTATUS_OK.

DB_S_ERRORSOCCURREDThis return code indicates that the OLE DB rowset was opened but one ormore properties—for which the dwOptions element of the DBPROP struc-ture was DBPROPOPTIONS_OPTIONAL—were not set. The OLE DBconsumer checks dwStatus in the DBPROP structures to determine whichproperties were not set. The method can fail to set properties for a num-ber of reasons, including:

� The property was not supported by the OLE DB provider.

� The property was not in the OLE DB Rowset property group.

� The property set was not supported by the OLE DB provider.

� It was not possible to set the property.

Chapter Four—An OLE DB Primer � 77

Page 97: Learn OLE DB Development With Visual C++ 6.0

� The colid in the DBPROP structure was invalid.

� The data type in vValue in the DBPROP structure was not the data type ofthe property or was not VT_EMPTY.

� The value in vValue in the DBPROP structure was invalid.

� The property’s value conflicted with an existing property.

� A property was specified to be applied to all columns, but could not beapplied to one or more columns.

E_FAILThis return code shows that an OLE DB provider-specific error occurred.

E_INVALIDARGThis return code indicates that ppSourcesRowset was a null pointer;cPropertySets was not zero and rgPropertySets was a null pointer; or in anelement of rgPropertySets, cProperties was not zero and rgProperties was anull pointer.

E_NOINTERFACEThis return code shows that the OLE DB rowset did not support the inter-face specified in riid.

E_OUTOFMEMORYThis return code indicates that the OLE DB provider did not have enoughmemory to create the OLE DB Rowset object.

E_UNEXPECTEDThis return code shows that the Enumerator object was in anuninitialized state.

DB_E_ABORTLIMITREACHEDThis return code states that the method failed because a resource limithas been reached. For example, a query used to implement the methodtimed out. No OLE DB rowset is returned.

DB_E_ERRORSOCCURREDThis return code indicates that the OLE DB rowset was opened but one ormore properties were not set. These properties had the dwOptions ele-ment of the DBPROP structure set to DBPROPOPTIONS_OPTIONAL. TheOLE DB consumer checks dwStatus in the DBPROP structures to deter-mine which properties were not set. None of the satisfiable properties areremembered. The method can fail to set properties for any of the reasonsspecified in DB_S_ERRORSOCCURRED, except the reason that states itwas not possible to set the property.

DB_E_NOAGGREGATIONThis return code indicates that pUnkOuter was not a null pointer and theOLE DB rowset did not support aggregation or that pUnkOuter wasnon-null and riid was not IID_Unknown.

78 � Chapter Four—An OLE DB Primer

Page 98: Learn OLE DB Development With Visual C++ 6.0

IDBCreateSession

OLE DB consumers call IDBCreateSession::CreateSession on a data sourceobject to obtain a new session.

Method Description

CreateSession This method creates a new session from the data sourceobject and returns the requested interface on the newlycreated session.

IDBCreateSession::CreateSession

This method creates a new session from the data source object and returnsthe requested interface on the newly created session. It has the followingsyntax:

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 ifthe new session is being created as part of an aggregate. It is a nullpointer 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 interfacepointer.

It has the following return codes:

S_OKThis return code shows that the method succeeded.

E_FAILThis return code means that an OLE DB provider-specific error occurred.

E_INVALIDARGThis return code states that *ppDBSession was a null pointer.

E_NOINTERFACEThis return code indicates that the session did not support the interfacespecified in riid.

Chapter Four—An OLE DB Primer � 79

Page 99: Learn OLE DB Development With Visual C++ 6.0

E_OUTOFMEMORYThis return code shows that the OLE DB provider did not have enoughmemory to create the session.

E_UNEXPECTEDThis return code indicates that the data source object was in anuninitialized state.

DB_E_NOAGGREGATIONThis return code shows that *pUnkOuter was not a null pointer and thesession being created does not support aggregation or that *pUnkOuter

was non-null and riid was not IID_Unknown.

DB_E_OBJECTCREATIONLIMITREACHEDThis return code states that the maximum number of sessions supportedby the OLE DB provider has already been created. The OLE DB consumermust release one or more currently held sessions before obtaining a newsession object. This error is only returned by OLE DB providers that havea fixed maximum number of sessions as returned by DBPROP_ACTIVE-SESSIONS. It is not returned due to other resource constraints, such asavailable memory (for which the provider returns E_OUTOFMEMORY).

DB_E_OBJECTOPENThis return code shows that the OLE DB provider would have to open anew connection to support the operation and DBPROP_MULTIPLE-CONNECTIONS is set to VARIANT_FALSE.

IDBInfo

IDBInfo returns information about the keywords and literals a provider sup-ports. It is an optional interface on the data source objects.

Method Description

GetKeywords This method returns a list of OLE DB provider-specifickeywords.

GetLiteralInfo This method returns information about literals used in text OLEDB commands and in ITableDefinition and IIndexDefinition.

IDBInfo::GetKeywords

This method returns a list of OLE DB provider-specific keywords. The follow-ing list contains the keywords in OLE DB:

ABSOLUTE INTO

ACTION IS

ADD ISOLATION

ALL JOIN

80 � Chapter Four—An OLE DB Primer

Page 100: Learn OLE DB Development With Visual C++ 6.0

ALLOCATE KEY

ALTER LANGUAGE

AND LAST

ANY LEADING

ARE LEFT

AS LEVEL

ASC LIKE

ASSERTION LOCAL

AT LOWER

AUTHORIZATION MATCH

AVG MAX

BEGIN MIN

BETWEEN MINUTE

BIT MODULE

BIT_LENGTH MONTH

BOTH NAMES

BY NATIONAL

CASCADE NATURAL

CASCADED NCHAR

CASE NEXT

CAST NO

CATALOG NOT

CHAR NULL

CHAR_LENGTH NULLIF

CHARACTER NUMERIC

CHARACTER_LENGTH OCTET_LENGTH

CHECK OF

CLOSE ON

COALESCE ONLY

COLLATE OPEN

COLLATION OPTION

COLUMN OR

COMMIT ORDER

CONNECT OUTER

CONNECTION OUTPUT

CONSTRAINT OVERLAPS

CONSTRAINTS PARTIAL

CONTINUE POSITION

CONVERT PRECISION

CORRESPONDING PREPARE

COUNT PRESERVE

Chapter Four—An OLE DB Primer � 81

Page 101: Learn OLE DB Development With Visual C++ 6.0

CREATE PRIMARY

CROSS PRIOR

CURRENT PRIVILEGES

CURRENT_DATE PROCEDURE

CURRENT_TIME PUBLIC

CURRENT_TIMESTAMP READ

CURRENT_USER REAL

CURSOR REFERENCES

DATE RELATIVE

DAY RESTRICT

DEALLOCATE REVOKE

DEC RIGHT

DECIMAL ROLLBACK

DECLARE ROWS

DEFAULT SCHEMA

DEFERRABLE SCROLL

DEFERRED SECOND

DELETE SECTION

DESC SELECT

DESCRIBE SESSION

DESCRIPTOR SESSION_USER

DIAGNOSTICS SET

DISCONNECT SIZE

DISTINCT SMALLINT

DISTINCTROW SOME

DOMAIN SQL

DOUBLE SQLCODE

DROP SQLERROR

ELSE SQLSTATE

END SUBSTRING

END-EXEC SUM

ESCAPE SYSTEM_USER

EXCEPT TABLE

EXCEPTION TEMPORARY

EXEC THEN

EXECUTE TIME

EXISTS TIMESTAMP

EXTERNAL TIMEZONE_HOUR

EXTRACT TIMEZONE_MINUTE

FALSE TO

FETCH TRAILING

82 � Chapter Four—An OLE DB Primer

Page 102: Learn OLE DB Development With Visual C++ 6.0

FIRST TRANSACTION

FLOAT TRANSLATE

FOR TRANSLATION

FOREIGN TRIGGER

FOUND TRIM

FROM TRUE

FULL UNION

GET UNIQUE

GLOBAL UNKNOWN

GO UPDATE

GOTO UPPER

GRANT USAGE

GROUP USER

HAVING USING

HOUR VALUE

IDENTITY VALUES

IMMEDIATE VARCHAR

IN VARYING

INDICATOR VIEW

INITIALLY WHEN

INNER WHENEVER

INPUT WHERE

INSENSITIVE WITH

INSERT WORK

INT WRITE

INTEGER YEAR

INTERSECT ZONE

INTERVAL

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 addressof a string. The string contains a comma-separated list of all keywordsunique to the OLE DB provider. The OLE DB provider allocates memoryfor the string and returns the address to this memory; the OLE DB con-sumer releases this memory with IMalloc::Free when it no longer needsthe string.

Chapter Four—An OLE DB Primer � 83

Page 103: Learn OLE DB Development With Visual C++ 6.0

It has the following return codes:

S_OKThis return code indicates the method succeeded.

E_FAILThis return code shows that an OLE DB provider-specific error occurred.

E_INVALIDARGThis return code indicates that *ppwszKeywords was a null pointer.

E_OUTOFMEMORYThis return code indicates that the OLE DB provider was unable to allo-cate sufficient memory in which to return the keywords.

E_UNEXPECTEDThis return code shows that the data source object was in an uninitializedstate.

IDBInfo::GetLiteralInfo

This function returns information about literals used in text commands,ITableDefinition, IIndexDefinition, and IOpenRowset, or any interface thattakes DBIDs as arguments. In the context of the GetLiteralInfo method, a lit-eral is one of several things:

� A special character or characters used by text commands, such as thecharacter used to quote identifiers. The GetLiteralInfo method returns thecharacter or characters.

� A literal data value, such as a character literal in a SQL statement. Forsuch literal data values, the GetLiteralInfo method returns the maximumlength of the literal in characters, a list of the characters that cannot beused in the literal, and a list of the characters that cannot be used as thefirst character of the literal.

� 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 incharacters, a list of the characters that cannot be used in the name, and alist of the characters that cannot be used as the first character of thename.

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;

84 � Chapter Four—An OLE DB Primer

Page 104: Learn OLE DB Development With Visual C++ 6.0

The elements of this structure are used as follows:

Element Description

pwszLiteralValue This element gives a pointer to a string in the *ppCharBufferbuffer containing the actual literal value. For example, if lt isDBLITERAL_LIKE_PERCENT, and the percent character (%)is used to match zero or more characters in a LIKE clause, thiswould 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, andDBLITERAL_SCHEMA_SEPARATOR. For all otherDBLITERAL values, pwszLiteralValue is not used and is set to anull pointer.

pwszInvalidChars This element provides a pointer to a string in the*ppCharBuffer buffer containing the characters that are notvalid in the literal.

For example, if table names can contain anything other than anumeric character, this would be “0123456789" when lt isDBLITERAL_TABLE_NAME. If the literal can contain any validcharacter, this is a null pointer. This is not used forDBLITERAL_BINARY_LITERAL, DBLITERAL_CATALOG_SEPARATOR, DBLITERAL_ESCAPE_PERCENT_PREFIX,DBLITERAL_ESCAPE_UNDERSCORE_PREFIX,DBLITERAL_LIKE_PERCENT, DBLITERAL_LIKE_UNDERSCORE, DBLITERAL_QUOTE_PREFIX, andDBLITERAL_SCHEMA_SEPARATOR; pwszInvalidChars is setto 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 notvalid as the first character of the literal. If the literal can startwith any valid character, this is a null pointer.

For example, if table names can begin with anything other thana numeric character, this would be “0123456789" when lt isDBLITERAL_TABLE_NAME. This is not used forDBLITERAL_BINARY_LITERAL, DBLITERAL_CATALOG_SEPARATOR, DBLITERAL_ESCAPE_PERCENT_PREFIX,DBLITERAL_ESCAPE_UNDERSCORE_PREFIX,DBLITERAL_LIKE_PERCENT, DBLITERAL_LIKE_UNDERSCORE, DBLITERAL_QUOTE_PREFIX, andDBLITERAL_SCHEMA_SEPARATOR; pwszInvalidStarting-Chars is set to a null pointer for these DBLITERAL values.

lt This element indicates the literal described in the structure.

Chapter Four—An OLE DB Primer � 85

Page 105: Learn OLE DB Development With Visual C++ 6.0

Element Description

fSupported This element isTRUE if the OLE DB provider supports theliteral specified by It. If cLiterals is 0, this is always TRUE,because the GetLiteralInfo method only returns informationabout literals it supports in this case.

It is FALSE if the OLE DB provider does not support theliteral, or the value of the corresponding element of thergLiterals array was not a valid value in the DBLITERALenumerated type.

cchMaxLen This element provides the maximum number of characters inthe literal. If there is no maximum or the maximum isunknown, 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, andDBLITERAL_SCHEMA_SEPARATOR, this is the actual numberof characters in the literal.

The following values of DBLITERAL are supported:

Value Description

DBLITERAL_INVALID This is an invalid value.

DBLITERAL_BINARY_LITERAL

This value shows a binary literal in a text command.

DBLITERAL_CATALOG_NAME

This value gives a catalog name in a text command.

DBLITERAL_CATALOG_SEPARATOR

This value shows the character that separates the catalogname from the rest of the identifier in a text command.

DBLITERAL_CHAR_LITERAL

This value indicates a character literal in a textcommand.

DBLITERAL_COLUMN_ALIAS

This value shows a column alias in a text command.

DBLITERAL_COLUMN_NAME

This value gives a column name used in a text commandor in a data-definition interface.

DBLITERAL_CORRELATION_NAME

This value shows a correlation name (table alias) in a textcommand.

DBLITERAL_CURSOR_NAME

This value provides a cursor name in a text command.

DBLITERAL_ESCAPE_PERCENT_PREFIX

This value indicates that the character used in a LIKEclause to escape the character returned for theDBLITERAL_LIKE_PERCENT literal. For example, if apercent sign (%) is used to match zero or more

86 � Chapter Four—An OLE DB Primer

Page 106: Learn OLE DB Development With Visual C++ 6.0

Value Description

DBLITERAL_ESCAPE_PERCENT_PREFIX (cont.)

characters and this is a backslash (\), the characters“abc\%%” match all character values that start with“abc%”. Some SQL dialects support a clause (theESCAPE clause) that can be used to override this value.

DBLITERAL_ESCAPE_PERCENT_SUFFIX

This value shows the escape character, if any, used tosuffix the character returned for the DBLITERAL_LIKE_PERCENT literal. For example, if a percent sign (%) isused to match zero or more characters, and percentsigns are escaped by enclosing in open and close squarebrackets, DBLITERAL_ESCAPE_PERCENT_PREFIX is“[”, DBLITERAL_ESCAPE_PERCENT_SUFFIX is “]”,and the characters “abc[%]%” match all charactervalues that start with “abc%”. OLE DB providers thatdo not use a suffix character to escape the DBLITERAL_ESCAPE_PERCENT character do not return this literalvalue, and set the lt member of the DBLITERALstructure to DBLITERAL_INVALID if requested.

DBLITERAL_ESCAPE_UNDERSCORE_PREFIX

This value gives the character used in a LIKE clause toescape the character returned for the DBLITERAL_LIKE_UNDERSCORE literal. For example, if anunderscore (_) is used to match exactly one characterand this is a backslash (\), the characters “abc\_ _” matchall character values that are five characters long and startwith “abc_”. Some SQL dialects support a clause (theESCAPE clause) that can be used to override this value.

DBLITERAL_ESCAPE_UNDERSCORE_SUFFIX

This value provides the escape character, if any, used tosuffix the character returned for the DBLITERAL_LIKE_UNDERSCORE literal. For example, if an underscore (_)is used to match exactly one character, and underscoresare escaped by enclosing in open and close squarebrackets, DBLITERAL_ESCAPE_UNDERSCORE_PREFIX is “[”, DBLITERAL_ESCAPE_UNDERSCORE_SUFFIX is “]”, and the characters “abc[_]_” match allcharacter values that are five characters long and startwith “abc_”. OLE DB providers that do not use a suffixcharacter to escape theDBLITERAL_ESCAPE_UNDERSCORE character do notreturn this literal value, and set the lt member of theDBLITERAL structure to DBLITERAL_INVALID ifrequested.

DBLITERAL_INDEX_NAME

This value shows an index name used in a text commandor in a data-definition interface.

DBLITERAL_LIKE_PERCENT

This value indicates the character used in a LIKE clauseto match zero or more characters. For example, if this isa percent sign (%), the characters “abc%” match allcharacter values that start with “abc”.

Chapter Four—An OLE DB Primer � 87

Page 107: Learn OLE DB Development With Visual C++ 6.0

Value Description

DBLITERAL_LIKE_UNDERSCORE

This value shows the character used in a LIKE clause tomatch exactly one character. For example, if this is anunderscore (_), the characters “abc_” match allcharacter values that are four characters long and startwith “abc”.

DBLITERAL_PROCEDURE_NAME

This value gives a procedure name in a text command.

DBLITERAL_SCHEMA_NAME

This value indicates a schema name in a text command.

DBLITERAL_SCHEMA_SEPARATOR

This value shows the character that separates theschema name from the rest of the identifier in a textcommand.

DBLITERAL_TABLE_NAME

This value gives a table name used in a text command or

in a data-definition interface.

DBLITERAL_TEXT_COMMAND

This value provides a text command, such as a SQLstatement.

DBLITERAL_USER_NAME

This value specifies a user name in a text command.

DBLITERAL_VIEW_NAME

This value shows a view name in a text command.

DBLITERAL_QUOTE_PREFIX

This value gives the character used in a text command asthe opening quote for quoting identifiers that containspecial characters.

DBLITERAL_QUOTE_SUFFIX

This value indicates the character used in a textcommand as the closing quote for quoting identifiersthat contain special characters. 1.x providers that usethe same character as the prefix and suffix may notreturn this literal value, and set the lt member of theDBLITERAL structure to DBLITERAL_INVALID ifrequested.

If an OLE DB provider returns non-null CATALOG or SCHEMA column valuesin schema rowsets, then it must support the IDBInfo method to describe howa fully qualified name is assembled. An OLE DB provider that does not sup-port the IDBInfo method must return null values for CATALOG and SCHEMAcolumns in all schema rowsets. It has the following syntax:

HRESULT GetLiteralInfo(ULONG cLiterals,const DBLITERAL rgLiterals[],ULONG * pcLiteralInfo,DBLITERALINFO ** prgLiteralInfo,OLECHAR ** ppCharBuffer);

88 � Chapter Four—An OLE DB Primer

Page 108: Learn OLE DB Development With Visual C++ 6.0

It has the following parameters:

cLiterals [in]This parameter gives the number of literals being asked about. If this iszero, the OLE DB provider ignores rgLiterals and returns informationabout all of the literals it supports.

rgLiterals [in]This parameter shows an array of cLiterals literals about which to returninformation. If the OLE DB consumer specifies an invalid DBLITERALvalue in this array, GetLiteralInfo returns False in fSupported in the corre-sponding element of the *prgLiteralInfo array. If cLiterals is zero, thisparameter is ignored.

pcLiteralInfo [out]This parameter provides a pointer to memory in which to return the num-ber of literals for which information was returned. If cLiterals is zero, thisis the total number of literals supported by the OLE DB provider. If anerror other than DB_E_ERRORSOCCURRED occurs, *pcLiteralInfo is set tozero.

prgLiteralInfo [out]This parameter shows a pointer to memory in which to return a pointerto an array of DBLITERALINFO structures. One structure is returned foreach literal. The OLE DB provider allocates memory for the structuresand returns the address to this memory; the OLE DB consumer releasesthis memory with IMalloc::Free when it no longer needs the structures. If*pcLiteralInfo is zero on output or an error other than DB_E_ERRORS-OCCURRED occurs, the OLE DB provider does not allocate any memoryand ensures that *prgLiteralInfo is a null pointer on output.

ppCharBuffer [out]This parameter provides a pointer to memory in which to return a pointerfor all string values (pwszLiteralValue, pwszInvalidChars, and pwsz-

InvalidStartingChars) within a single allocation block. The OLE DB pro-vider allocates this memory and the OLE DB consumer releases it withIMalloc::Free when it no longer needs it. If *pcLiteralInfo is zero on out-put or an error occurs, the OLE DB provider does not allocate anymemory and ensures that *ppCharBuffer is a null pointer on output. Eachof the individual string values stored in this buffer is terminated by anull-termination character. Therefore, the buffer may contain one ormore strings, each with its own null-termination character, and may con-tain embedded null-termination characters.

It has the following return codes:

S_OKThis return code means the method succeeded. In each structure returnedin *prgLiteralInfo, the fSupported element is set to True.

Chapter Four—An OLE DB Primer � 89

Page 109: Learn OLE DB Development With Visual C++ 6.0

DB_S_ERRORSOCCURREDThis return code shows that rgLiterals contained at least one unsupportedor invalid literal. In the structures returned in *prgLiteralInfo for unsup-ported or invalid literals, the fSupported element is set to False.

E_FAILThis return code specifies that a provider-specific error occurred.

E_INVALIDARGThis return code shows that cLiterals was not equal to zero and rgLiteralswas a null pointer, or that pcLiteralInfo, prgLiteralInfo, or ppCharBufferwas a null pointer.

E_OUTOFMEMORYThis return code indicates that the OLE DB provider was unable to allo-cate sufficient memory in which to return the DBLITERALINFO structuresor the strings containing the valid and starting characters.

E_UNEXPECTEDThis return code shows that the data source object was in an uninitializedstate.

DB_E_ERRORSOCCURREDThis return code means that all literals were either invalid or unsup-ported. The OLE DB provider allocates memory for *prgLiteralInfo andsets 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 theinformation.

IGetDataSource

This is a mandatory interface on the session for obtaining an interface pointerto the data source object.

Method Description

GetDataSource Returns an interface pointer on the data source object thatcreated this session.

IGetDataSource::GetDataSource

This method returns an interface pointer on the data source object that cre-ated the session. It has the following syntax:

HRESULT GetDataSource (REFIID riid,IUnknown ** ppDataSource);

90 � Chapter Four—An OLE DB Primer

Page 110: Learn OLE DB Development With Visual C++ 6.0

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 theinterface pointer. If GetDataSource fails, it must attempt to set*ppDataSource to a null pointer.

It has the following return codes:

S_OKThis return code means that the method succeeded.

E_FAILThis return code indicates that an OLE DB provider-specific erroroccurred.

E_INVALIDARGThis return code shows *ppDataSource was a null pointer.

E_NOINTERFACEThis return code means that the object that created this session did notsupport the interface specified in riid.

IOpenRowset

IOpenRowset is a required interface on the session. It can be supported byproviders that do not support creating rowsets through commands.

The IOpenRowset model enables consumers to open and work directly withindividual tables or indexes in a data source by using IOpenRowset::OpenRowset, which generates a rowset of all rows in the table or index.

Method Description

OpenRowset This method opens and returns a rowset that includes allrows from a single base table or index.

IOpenRowset::OpenRowset

This function opens and returns a rowset that includes all rows from a singlebase table or index. If the table or index has no rows, the rowset is still cre-ated. The resulting rowset is fully functional and can be used, for example, toinsert new rows or determine column metadata.

pTableID and pIndexID are used in the following combinations:

� If pTableID is not a null pointer and pIndexID is a null pointer, the tableidentified by *pTableID is opened.

Chapter Four—An OLE DB Primer � 91

Page 111: Learn OLE DB Development With Visual C++ 6.0

� If pTableID is a null pointer and pIndexID is not a null pointer, *pIndexIDmust uniquely and fully identify an index; this index is opened. If*pIndexID does not uniquely and fully identify an index, DB_E_NOINDEXis returned.

� If neither pTableID nor pIndexID is a null pointer, *pIndexID must identifyan 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.

� If both pTableID and pIndexID are null pointers, E_INVALIDARG isreturned.

OLE DB consumers must supply fully qualified names as pTableID on OLE DBproviders that support catalog or schema names.The threading model of thereturned rowset is determined by the propertyDBPROP_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 beaggregated; 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 onthe resulting rowset, and the method fails (E_NOINTERFACE) if thatinterface cannot be supported on the resulting rowset. This must be aninterface that the rowset supports, even when *ppRowset is set to a nullpointer and no rowset is created.

cPropertySets [in]This parameter gives the number of DBPROPSET structures inrgPropertySets. If this is zero, the OLE DB provider ignores rgPropertySets.

92 � Chapter Four—An OLE DB Primer

Page 112: Learn OLE DB Development With Visual C++ 6.0

rgPropertySets [in/out]This parameter shows an array of DBPROPSET structures containingproperties and values to be set. The properties specified in these struc-tures must belong to the Rowset property group. If the same property isspecified more than once in rgPropertySets, then it is OLE DB pro-vider-specific which value is used. If cPropertySets is zero, this argumentis ignored.

ppRowset [in/out]This parameter provides a pointer to memory in which to return theinterface pointer to the created rowset. If *ppRowset is a null pointer, norowset is created; properties are verified and if a required property can-not be set, DB_E_ERRORSOCCURRED is returned. If the OpenRowsetmethod fails, *ppRowset is set to a null pointer.

It has the following return codes:

S_OKThis return code means the method succeeded and the rowset is opened.In all DBPROP structures passed to the method, dwStatus is set toDBPROPSTATUS_OK.

DB_S_ASYNCHRONOUSThis return code shows that the method has initiated asynchronous cre-ation of the rowset. The OLE DB consumer can call IDBAsynchStatus topoll for status or IConnectionPointContainer to obtain theIID_IDBAsynchNotify Connection Point. Attempting to call any otherinterfaces may fail, and the full set of interfaces may not be available onthe object until asynchronous initialization of the rowset has completed.

DB_S_ERRORSOCCURREDThis return code indicates the rowset was opened but one or more prop-erties—for which the dwOptions element of the DBPROP structure wasDBPROPOPTIONS_OPTIONAL—were not set. The OLE DB consumerchecks dwStatus in the DBPROP structure to determine which propertieswere not set. The method can fail to set properties for a number of rea-sons, including:

� colid in the DBPROP structure was invalid.

� The data type in vValue in the DBPROP structure was not the data typeof the property or was not VT_EMPTY.

� The value in vValue in the DBPROP structure was invalid.

� The property’s value conflicted with an existing property.

� A property was specified to be applied to all columns, but could not beapplied to one or more columns.

� The property was not supported by the OLE DB provider.

� It was not possible to set the property.

Chapter Four—An OLE DB Primer � 93

Page 113: Learn OLE DB Development With Visual C++ 6.0

DB_S_STOPLIMITREACHEDThis return code shows that execution has been stopped because aresource limit has been reached. The results obtained so far have beenreturned. This return code takes precedence over DB_S_ERRORS-OCCURRED; that is, if the conditions described here and those describedin DB_S_ERRORSOCCURRED both occur, the OLE DB provider returnsthis code. When the OLE DB consumer receives this return code, it shouldalso check for the conditions described in DB_S_ERRORSOCCURRED.

E_FAILThis return code indicates an OLE DB provider-specific error occurred.

E_INVALIDARGThis return code shows that *pTableID and *pIndexID were both nullpointers; that cPropertySets was not zero and prgPropertySets was a nullpointer; or that in an element of rgPropertySets, cProperties was not zeroand rgProperties was a null pointer.

E_NOINTERFACEThis return code means the rowset did not support the interface specifiedin riid or riid was IID_NULL.

DB_E_ERRORSOCCURREDThis return code indicates no rowset was returned because one or moreproperties—for which the dwOptions element of the DBPROP structurewas DBPROPOPTIONS_REQUIRED or an invalid value—were not set.The OLE DB consumer checks dwStatus in the DBPROP structures todetermine which properties were not set. None of the satisfiable proper-ties are remembered. The method can fail to set properties for any of thereasons specified in DB_S_ERRORSOCCURRED except the reason thatstates that it was not possible to set the property.

DB_E_ABORTLIMITREACHEDThis return code shows the method failed because a resource limit hasbeen reached. For example, a query used to implement the method timedout. No rowset is returned.

DB_E_NOAGGREGATIONThis return code indicates pUnkOuter was not a null pointer and therowset being created does not support aggregation. pUnkOuter wasnon-null and riid was not IID_Unknown.

DB_E_NOINDEXThis return code means the specified index does not exist in the currentdata source or did not apply to the specified table. The provider does notsupport opening indexes through the IOpenRowset method.

DB_E_NOTABLEThis return code provides that the specified table does not exist in thecurrent data source.

94 � Chapter Four—An OLE DB Primer

Page 114: Learn OLE DB Development With Visual C++ 6.0

DB_E_PARAMNOTOPTIONALThis return code shows that the table specified by *pTableID is a proce-dure that requires one or more parameters.

DB_SEC_E_PERMISSIONDENIEDThis return code indicates that the OLE DB consumer did not have suffi-cient permission to open the rowset. For example, a rowset included acolumn for which the OLE DB consumer does not have read permission.

DB_E_OBJECTOPENThis return code shows the OLE DB provider would have to open a newconnection to support the operation and DBPROP_MULTIPLE-CONNECTIONS is set to VARIANT_FALSE.

ISessionProperties

ISessionProperties returns information about the properties a session sup-ports and the current settings of those properties. It is a mandatory interfaceon sessions.

Method Description

GetProperties This method returns the list of properties in the Sessionproperty group that are currently set on the session.

Set Properties This method sets properties in the Session property group.

ISessionProperties::GetProperties

This method returns the list of properties in the Session property group thatare 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 inrgPropertyIDSets. If cPropertyIDSets is zero, the OLE DB provider ignoresrgPropertyIDSets and returns the values of all properties in the Sessionproperty group for which values have been set or defaults exist. It doesnot return the values of properties in the Session property group forwhich values have not been set and no defaults exist.If cPropertyIDSets is not zero, the OLE DB provider returns the values ofthe requested properties. If a property is not supported, the returnedvalue of dwStatus in the returned DBPROP structure for that property is

Chapter Four—An OLE DB Primer � 95

Page 115: Learn OLE DB Development With Visual C++ 6.0

DBPROPSTATUS_NOTSUPPORTED and the value of dwOptions isundefined.

rgPropertyIDSets [in]This parameter gives an array of cPropertyIDSets DBPROPIDSET struc-tures. The properties specified in these structures must belong to theSession property group. The OLE DB provider returns the values of infor-mation about the properties specified in these structures. IfcPropertyIDSets is 0, then this parameter is ignored.

pcProperty Sets [out]This parameter provides a pointer to memory in which to return the num-ber of DBPROPSET structures returned in *prgPropertySets. IfcPropertyIDSets is zero, *pcPropertySets is the total number of propertysets for which the OLE DB provider supports at least one property in theSession property group. If cPropertyIDSets is greater than zero,*pcPropertySets is set to cPropertyIDSets. If an error other thanDB_E_ERRORSOCCURRED occurs, *pcPropertySets is set to zero.

prgPropertySets [out]This parameter shows a pointer to memory in which to return an array ofDBPROPSET structures. If cPropertyIDSets is zero, then one structure isreturned for each property set that contains at least one property belong-ing to the Session property group. If cPropertyIDSets is not zero, then onestructure is returned for each property set specified in rgPropertyIDSets.

If cPropertyIDSets is not zero, the DBPROPSET structures in *prgPropertySetsare returned in the same order as the DBPROPIDSET structures inrgPropertyIDSets; that is, for corresponding elements of each array, theguidPropertySet elements are the same. If cPropertyIDs, in an element ofrgPropertyIDSets, is not zero, the DBPROP structures in the correspondingelement of *prgPropertySets are returned in the same order as the DBPROPIDvalues in rgPropertyIDs; that is, for corresponding elements of each array, theproperty IDs are the same.

The OLE DB provider allocates memory for the structures and returns theaddress to this memory; the OLE DB consumer releases this memory with theIMalloc::Free method when it no longer needs the structures. Before callingthe IMalloc::Free method for *prgPropertySets, the OLE DB consumer shouldcall IMalloc::Free for the rgProperties element within each element of*prgPropertySets. If *pcPropertySets is zero on output or if an error other thanDB_E_ERRORSOCCURRED occurs, the OLE DB provider does not allocateany memory and ensures that *prgPropertySets is a null pointer on output.

It has the following return codes:

S_OKThis return code indicates that the method succeeded. In all DBPROP

96 � Chapter Four—An OLE DB Primer

Page 116: Learn OLE DB Development With Visual C++ 6.0

structures returned by the method, dwStatus is set toDBPROPSTATUS_OK.

DB_S_ERRORSOCCURREDThis return code shows that no value was returned for one or more prop-erties. The OLE DB consumer checks dwStatus in the DBPROP structure todetermine the properties for which values were not returned. TheGetProperties method can fail to return properties for a number of rea-sons, including:

� The property was not supported by the OLE DB provider.

� The property was not in the Session property group.

� The property set was not supported by the OLE DB provider. IfcPropertyIDs in the DBPROPIDSET structure for the property set waszero, the OLE DB provider cannot set dwStatus in the DBPROP struc-ture because it does not know the IDs of any properties in theproperty set. Instead, it sets cProperties to zero in the DBPROPSETstructure returned for the property set.

E_FAILThis return code means that an OLE DB provider-specific error occurred.

E_INVALIDARGThis return code shows that cPropertyIDSets was not equal to zero andrgPropertyIDSets was a null pointer; *pcPropertySets or *prgPropertySets

was a null pointer; or in an element of rgPropertyIDSets, cPropertyIDs wasnot zero and rgPropertyIDs was a null pointer.

E_OUTOFMEMORYThis return code indicates the OLE DB provider was unable to allocatesufficient memory in which to return the DBPROPSET or DBPROPstructures.

DB_E_ERRORSOCCURREDThis return code means values were not returned for any properties. TheOLE DB provider allocates memory for *prgPropertySets and the OLE DBconsumer checks dwStatus in the DBPROP structures to determine whyproperties were not returned. The OLE DB consumer frees this memorywhen it no longer needs the information.

ISessionProperties::SetProperties

This method sets properties in the Session property group. It has the follow-ing syntax:

HRESULT SetProperties (ULONG cPropertySets,DBPROPSET rgPropertySets[]);

Chapter Four—An OLE DB Primer � 97

Page 117: Learn OLE DB Development With Visual C++ 6.0

It has the following parameters:

cPropertySets [in]This parameter shows the number of DBPROPSET structures inrgPropertySets. If this is zero, OLE DB provider ignores rgPropertySets andthe method does not do anything.

rgPropertySets [in/out]This parameter gives an array of DBPROPSET structures containing prop-erties and values to be set. The properties specified in these structuresmust belong to the Session property group. If the same property is speci-fied more than once in rgPropertySets, then which value is used is OLE DBprovider-specific. If cPropertySets is zero, this parameter is ignored.

It has the following return codes:

S_OKThis return code shows that the method succeeded. In all DBPROP struc-tures passed to the method, dwStatus is set to DBPROPSTATUS_OK.

DB_S_ERRORSOCCURREDThis return code indicates that one or more properties were not set. Prop-erties not in error remain set. The OLE DB consumer checks dwStatus inthe DBPROP structures to determine which properties were not set. TheSetProperties method can fail to set properties for a number of reasons,including:

� The property was not supported by the OLE DB provider.

� The property was not in the Session property group.

� The property set was not supported by the OLE DB provider.

� The property is read-only and was not set to its default value.

� It was not possible to set the property.

� colid in the DBPROP structure was not DB_NULLID.

� The value of dwOptions in the DBPROP structure was invalid.

� The data type in vValue in the DBPROP structure was not the data typeof the property or was not VT_EMPTY.

� The value in vValue in the DBPROP structure was invalid.

� The property’s value conflicted with an existing property.

E_FAILThis return code means that an OLE DB provider-specific error occurred.

E_INVALIDARGThis return code shows that cPropertySets was not equal to zero andrgPropertySets was a null pointer, or that in an element of rgPropertySets,cProperties was not zero and rgProperties was a null pointer.

DB_E_ERRORSOCCURREDThis return code indicates all property values were invalid and no

98 � Chapter Four—An OLE DB Primer

Page 118: Learn OLE DB Development With Visual C++ 6.0

properties were set. The OLE DB consumer checks dwStatus in theDBPROP structures to determine why properties were not set.

IDBCreateCommand

Consumers call IDBCreateCommand::CreateCommand on a session to obtaina new command.

Method Description

CreateCommand This method creates a new command.

IDBCreateCommand::CreateCommand

This method creates a new command. If the session is transacted, the com-mand and any actions performed as a result of executing that command arewithin 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 ifthe new command is being created as part of an aggregate. It is a nullpointer 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 theinterface pointer on the newly created command.

It has the following return codes:

S_OKThis return code means the method succeeded.

E_FAILThis return code indicates that an OLE DB provider-specific erroroccurred.

E_INVALIDARGThis return code shows *ppCommand was a null pointer.

E_NOINTERFACEThis return code states that the command did not support the interfacespecified in riid.

Chapter Four—An OLE DB Primer � 99

Page 119: Learn OLE DB Development With Visual C++ 6.0

E_OUTOFMEMORYThis return code indicates that the OLE DB provider did not have enoughmemory to create the command.

DB_E_NOAGGREGATIONThis return code shows *pUnkOuter was not a null pointer and the com-mand being created does not support aggregation, or that *pUnkOuter

was non-null and riid was not IID_Unknown.

DB_E_OBJECTOPENThis return code means that the OLE DB provider would have to open anew connection to support the operation and DBPROP_MULTIPLE-CONNECTIONS is set to VARIANT_FALSE.

ITableDefinition

The ITableDefinition interface exposes simple methods to create, drop, andalter tables on the data source.

ITableDefinition is optional for OLE DB providers that do not otherwise sup-port table creation; it is mandatory for OLE DB providers that support tablecreation through commands.

Method Description

AddColumn This method adds a new column to a base table.

CreateTable This method creates a new base table in the data source.

DropColumn This method drops a column from a base table.

DropTable 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 methodreturns any errors, the column is not created. The placement of the new col-umn, whether inserted or added after the last column, is OLE DBprovider-specific. It has the following syntax:

HRESULT AddColumn(DBID * pTableID,DBCOLUMNDESC * pColumnDesc,DBID ** ppColumnID);

100 � Chapter Four—An OLE DB Primer

Page 120: Learn OLE DB Development With Visual C++ 6.0

It has the following parameters:

pTableID [in]This parameter gives a pointer to the DBID of the table to which the col-umn is to be added.

pColumnDesc [in/out]This parameter provides a pointer to the DBCOLUMNDESC structure thatdescribes the new column.

ppColumnID [out]This parameter shows a pointer to memory in which to return thereturned DBID of newly created column. If this is a null pointer, no DBIDis returned. If *ppColumnID is non-null, the OLE DB provider allocatesmemory for the DBID and overwrites *ppColumnID with a pointer to thisnew DBID without regard for its current value.

It has the following return codes:

S_OKThis return code shows that the method succeeded.

DB_S_ERRORSOCCURREDThis return code means that the column was added, but one or moreproperties—for which the dwOptions element of the DBPROP structurewas DBPROPOPTIONS_OPTIONAL—were not set. The OLE DB consumerchecks dwStatus in the DBPROP structure to determine which propertieswere not set. The method can fail to set properties for a number of rea-sons, including:

� The property was not supported by the OLE DB provider.

� The property was not in the Column, Rowset, or Table property group.

� The property set was not supported by the OLE DB provider.

� It was not possible to set the property.

� colid in the DBPROP structure was not DB_NULLID.

� The data type in vValue in the DBPROP structure was not the data typeof the property or was not VT_EMPTY.

� The value in vValue in the DBPROP structure was invalid.

� The property’s value conflicted with an existing property.

E_FAILThis return code indicates that an OLE DB provider-specific erroroccurred.

E_INVALIDARGThis return code shows *pTableID or *pColumnDesc was a null pointer.

DB_E_BADCOLUMNIDThis return code means that dbcid in *pColumnDesc was an invalid col-umn ID.

Chapter Four—An OLE DB Primer � 101

Page 121: Learn OLE DB Development With Visual C++ 6.0

DB_E_BADPRECISIONThis return code indicates that the precision in *pColumnDesc wasinvalid.

DB_E_BADSCALEThis return code shows the scale in *pColumnDesc was invalid.

DB_E_BADTABLEIDThis return code provides that *pTableID was an invalid table ID.

DB_E_BADTYPEThis return code shows that one or more of the wType, pwszTypeName,and pTypeInfo elements in an rgColumnDescs element was invalid.

DB_E_DUPLICATECOLUMNIDThis return code states that dbcid in *pColumnDesc was the same as anexisting column ID.

DB_E_ERRORSOCCURREDThis return code indicates that the column was not added because one ormore properties—for which the dwOptions element of the DBPROP struc-ture was DBPROPOPTIONS_REQUIRED or an invalid value—could not beset. The OLE DB consumer checks dwStatus in the DBPROP structures todetermine which properties were not set. The method can fail to set prop-erties for any of the reasons specified in DB_S_ERRORSOCCURRED.

DB_E_NOTABLEThis return code shows that the specified table does not exist in the cur-rent data source.

DB_E_TABLEINUSEThis return code means the specified table was in use.

DB_SEC_E_PERMISSIONDENIEDThis return code shows that the OLE DB consumer did not have sufficientpermission to add a column.

ITableDefinition::CreateTable

This function creates a new base table in the data source. If *ppRowset is nota null pointer, an empty rowset is opened on the newly created table. IfCreateTable returns any errors, the table is not created, and *ppTableID is setto Null on output. It has the following syntax:

HRESULT CreateTable(IUnknown * pUnkOuter,DBID * pTableID,ULONG cColumnDescs,DBCOLUMNDESC rgColumnDescs[],REFIID riid,ULONG cPropertySets,DBPROPSET rgPropertySets[],

102 � Chapter Four—An OLE DB Primer

Page 122: Learn OLE DB Development With Visual C++ 6.0

DBID ** ppTableID,IUnknown ** ppRowset);

It has the following parameters:

pUnkOuter [in]This parameter indicates the controlling unknown if the rowset is to beaggregated; otherwise it is a null pointer.

pTableID [in]This parameter gives a pointer to the ID of the table to create. If this is anull pointer, the OLE DB provider must assign a unique ID to the table.

cColumnDescs [in]This parameter shows the number of DBCOLUMNDESC structures in thergColumnDescs array. This can be zero if the OLE DB provider allows thecreation of tables with no columns.

rgColumnDescs [in/out]This parameter gives an array of DBCOLUMNDESC structures thatdescribe 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 consumergenerally decides the values to use in the non-properties elements of thisstructure based on values from the PROVIDER_TYPES schema rowset.

Element Description

pwszTypeName This element gives the OLE DB provider-specific nameof the data type of the column. This name correspondsto a value in the TYPE_NAME column in thePROVIDER_TYPES schema rowset. In most cases,there is no reason for an OLE DB consumer to specify avalue for pwszTypeName that is different from the valueslisted in the PROVIDER_TYPES schema rowset.

Chapter Four—An OLE DB Primer � 103

Page 123: Learn OLE DB Development With Visual C++ 6.0

Element Description

pTypeInfo This element indicates that if *pTypeInfo is not a nullpointer, then the data type of the column is an abstractdata type (ADT) and values in this column are actuallyinstances of the type described by the Type Library.wType may be either DBTYPE_BYTES with a length ofat least 4, or it may be DBTYPE_IUNKNOWN. Theinstance values are required to be COM objects derivedfrom the IUnknown method.

rgPropertySets This element shows an array of DBPROPSET structurescontaining properties and values to be set. Theproperties specified in these structures must belong tothe Column property group. All properties specified in*rgPropertySets for this element of rgColumnDescs applyto the column specified by dbcid; the colid element inthe DBPROP structure for specified properties isignored. If the same property is specified more thanonce in *rgPropertySets, then it is OLE DB provider-specific which value is used. If cPropertySets is zero, thisargument is ignored.

pclsid If the column contains OLE objects, this elementspecifies a pointer to the class ID of those objects. Ifmore than one class of objects can reside in the column,*pclsid is set to IID_NULL.

cPropertySets This element provides the number of DBPROPSETstructures in *rgPropertySets. If this is zero, the OLE DB

provider ignores *rgPropertySets.

ulColumnSize This element shows that if wType is DBTYPE_STR orDBTYPE_WSTR, this is the maximum length incharacters for values in this column. If wType isDBTYPE_BYTES, this is the maximum length in bytesfor values in this column. For all other values of wType,this is ignored. OLE DB providers that do not require alength argument in the specification of the OLE DBprovider type (pwszTypeName) ignore this value.

dbcid This element shows the column ID of the column.

wType This element provides the type indicator for the datatype of the column. This name corresponds to a value inthe DATA_TYPE column in the PROVIDER_TYPESschema rowset. In most cases, there is no reason for anOLE DB consumer to specify a value for wType that isdifferent from the values listed in the PROVIDER_TYPES schema rowset.

bPrecision This element gives the maximum precision of datavalues in the column when wType is the indicator for anumeric type; it is ignored for all other data types. This

104 � Chapter Four—An OLE DB Primer

Page 124: Learn OLE DB Development With Visual C++ 6.0

Element Description

bPrecision (cont.) must be within the limits specified for the type in theCOLUMN_SIZE column in the PROVIDER_TYPESschema rowset.

bScale This element specifies the scale of data values in thecolumn when wType is DBTYPE_NUMERIC orDBTYPE_DECIMAL; it is ignored for all other datatypes. This must be within the limits specified for thetype in the MINIMUM_SCALE and MAXIMUM_SCALEcolumns in the PROVIDER_TYPES schema rowset.

riid [in]The IID of the interface to be returned for the resulting rowset; this isignored if *ppRowset is a null pointer.

cPropertySets [in]This parameter gives the number of DBPROPSET structures inrgPropertySets. If this is zero, the OLE DB provider ignores rgPropertySets.

rgPropertySets [in/out]This parameter shows an array of DBPROPSET structures containingproperties and values to be set. The properties specified in these struc-tures must belong to the Rowset property group (for properties that applyto the rowset returned in *ppRowset) or the Table property group (forproperties that apply to the table). If the same property is specified morethan once in rgPropertySets, then it is OLE DB provider-specific whichvalue is used. If cPropertySets is zero, this argument is ignored.

ppTableID [out]This parameter provides a pointer to memory in which to return the DBIDof the newly created table. If *ppTableID is a null pointer, no DBID isreturned.

ppRowset [out]This parameter gives a pointer to memory in which to return therequested interface pointer on an empty rowset opened on the newly cre-ated table. If *ppRowset is a null pointer, no rowset is created.

It has the following return codes:

S_OKThis return code indicates that the method succeeded and the table is cre-ated and opened. In all DBPROP structures passed to the method,dwStatus is set to DBPROPSTATUS_OK.

DB_S_ERRORSOCCURREDThis indicates the table was created and the rowset was opened, but oneor more properties—for which the dwOptions element of the DBPROPstructure was DBPROPOPTIONS_OPTIONAL—were not set. The OLE DBconsumer checks dwStatus in the DBPROP structures to determine which

Chapter Four—An OLE DB Primer � 105

Page 125: Learn OLE DB Development With Visual C++ 6.0

properties were not set. The method can fail to set properties for a num-ber of reasons, including:

� The property was not supported by the OLE DB provider.

� The property was not in the Column, Rowset, or Table property group.

� The property set was not supported by the OLE DB provider.

� It was not possible to set the property.

� colid in the DBPROP structure was not DB_NULLID.

� The data type in vValue in the DBPROP structure was not the data typeof the property or was not VT_EMPTY.

� The value in vValue in the DBPROP structure was invalid.

� The property’s value conflicted with an existing property.

E_FAILThis return code means that an OLE DB provider-specific error occurred.

E_INVALIDARGThis return code shows that *pTableID and *ppTableID were both nullpointers; or cColumnDesc was not zero; rgColumnDescs was a null pointer;cPropertySets was not zero and rgPropertySets was a null pointer; orcColumnDesc was zero and the OLE DB provider does not support creat-ing tables with no columns.

It can also mean that in an element of rgPropertySets, cProperties was notzero and rgProperties was a null pointer, or in an element of rgColumn-

Descs, cPropertySets was not zero and rgPropertySets was a null pointer.

DB_E_BADCOLUMNIDThis return code shows that dbcid in an element of rgColumnDescs was aninvalid column ID.

DB_E_BADTABLEIDThis return code indicates that *pTableID was an invalid table ID.

DB_E_BADPRECISIONThis return code means that the precision in an element of rgColumnDescswas invalid.

DB_E_BADSCALEThis return code shows that the scale in an element of rgColumnDescs wasinvalid.

DB_E_BADTYPEThis return code specifies that one or more of the wType, pwszTypeName,or pTypeInfo elements in an element of rgColumnDescs was invalid.

DB_E_DUPLICATECOLUMNIDThis return code provides that dbcid was the same in two or more ele-ments of rgColumnDescs.

106 � Chapter Four—An OLE DB Primer

Page 126: Learn OLE DB Development With Visual C++ 6.0

DB_E_DUPLICATETABLEIDThis return code indicates the specified table already exists in the currentdata source.

DB_E_ERRORSOCCURREDThis return code shows that the table was not created and no rowset wasreturned because one or more properties—for which the dwOptions ele-ment of the DBPROP structure was DBPROPOPTIONS_REQUIRED or aninvalid value—could not be set. The OLE DB consumer checks dwStatusin the DBPROP structures to determine which properties were not set.The method can fail to set properties for any of the reasons specified inDB_S_ERRORSOCCURRED.

DB_E_NOAGGREGATIONThis return code states that *pUnkOuter was not a null pointer and therowset being created does not support aggregation. *pUnkOuter wasnon-null and riid was not IID_Unknown.

DB_SEC_E_PERMISSIONDENIEDThis return code means the OLE DB consumer did not have sufficient per-mission to create the table.

DB_E_OBJECTOPENThis return code shows that the OLE DB provider would have to open anew connection to support the operation, and DBPROP_MULTIPLE-CONNECTIONS is set to VARIANT_FALSE.

ITableDefinition::DropColumn

This method drops a column from the base table. If the DropColumn methodreturns 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 todrop the column.

pColumnID [in]This parameter provides a pointer to the DBID of the column to drop.

It has the following return codes:

S_OKThis return code means that the method succeeded and the column wasdropped from the base table.

Chapter Four—An OLE DB Primer � 107

Page 127: Learn OLE DB Development With Visual C++ 6.0

E_FAILThis return code indicates an OLE DB provider-specific error occurred.

E_INVALIDARGThis return code shows that *pTableID or *pColumnID was a null pointer.

DB_E_BADCOLUMNIDThis return code states that the column specified in *pColumnID does notexist in the specified table.

DB_E_BADTABLEIDThis return code shows *pTableID was an invalid table ID.

DB_E_NOTABLEThis return code indicates the specified table does not exist in the currentdata source.

DB_E_TABLEINUSEThis return code means the specified table was in use.

DB_SEC_E_PERMISSIONDENIEDThis return code states the OLE DB consumer did not have sufficient per-mission to drop the column.

DB_E_BADTABLEIDThis 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 followingsyntax:

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_OKThis return code means that the method succeeded and the table isdropped.

E_FAILThis return code shows that an OLE DB provider-specific error occurred.

E_INVALIDARGThis return code indicates that *pTableID was a null pointer.

DB_E_NOTABLEThis return code means the specified table does not exist in the currentdata source.

108 � Chapter Four—An OLE DB Primer

Page 128: Learn OLE DB Development With Visual C++ 6.0

DB_E_TABLEINUSEThis return code shows that the specified table was in use and could notbe dropped.

DB_SEC_E_PERMISSIONDENIEDThis return code states the OLE DB consumer did not have sufficient per-mission to drop the table.

DB_E_BADTABLEIDThis return code indicates *pTableID was an invalid table ID.

ICommand

ICommand contains methods to execute commands. A command can be exe-cuted many times and the parameter values can vary. This interface ismandatory on commands.

A command object contains a single text command, which is specifiedthrough ICommandText.

Method Description

Cancel This method cancels the current command execution.

Execute This method executes the command.

GetDBSession This method returns an interface pointer to the session thatcreated the command.

ICommand::Cancel

This method cancels the current command execution. Cancel may be calledon a separate thread after Execute has been called and before the method hasreturned. If the executing command can be canceled, the method returnsS_OK, and Execute returns DB_E_CANCELED. If the executing command can-not be canceled, Cancel returns DB_E_CANTCANCEL and Execute continues.If the Execute method has not been called or has already returned, themethod 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 Exe-cute method may return with DB_S_ASYNCHRONOUS before the commandhas fully executed. Calling ICommand::Cancel after the Execute method hasreturned has no effect on any asynchronously executing commands. Once theExecute method has returned DB_S_ASYNCHRONOUS, the command canonly be canceled by calling IDBAsynchStatus::Abort on the returned object.It has the following syntax:

HRESULT Cancel();

Chapter Four—An OLE DB Primer � 109

Page 129: Learn OLE DB Development With Visual C++ 6.0

It has no parameters.

It has the following return codes:

S_OKThis return code means the method succeeded, or the command was notexecuting.

E_FAILThis return code shows that an OLE DB provider-specific error occurred.

DB_E_CANTCANCELThis return code indicates the executing command cannot be canceled.

ICommand::Execute

This method executes the command. If the command returns rows, such as aSQL SELECT statement, the result of this method is a rowset over the resultrows. If no rows match the command, the rowset is still created. The result-ing rowset is fully functional and can be used, for example, to insert newrows or determine column metadata.

If the command returns multiple results (rowsets or row counts), the OLE DBconsumer requests a multiple-results object by setting riid to IID_IMultiple-Results. The Execute method creates the multiple-results object and returnsan IMultipleResults interface pointer to it in *ppRowset. The OLE DB con-sumer repeatedly calls IMultipleResults::GetResult to retrieve the results inorder.

If any or all parameters fail and the OLE DB provider does not support errorswithin an array of parameters (that is, the command fails if any or all of theparameters fail), the OLE DB provider returns DB_E_ERRORSOCCURREDand returns any error information for the failed parameters in their statusbindings. If any or all parameters fail and the OLE DB provider supportserrors within an array of parameters, the OLE DB provider returnsDB_S_ERRORSOCCURRED, sets *pcRowsAffected to the number of successfulparameters, and returns any error information for the failed parameters intheir status bindings.

If Execute is called multiple times for a single command, with or withoutchanges to the command text, the outcome may reflect changes in the under-lying stored data, depending on the isolation level specified for thesurrounding transaction.

Execute can be called when a rowset is already open on the command only ifthe only change between the calls is a change in the value of existing parame-ters (calls to ICommandWithParameters::SetParameterInfo will fail). Methodsthat modify the command (ICommandPrepare::Prepare, ICommandPrepare::Unprepare, ICommandProperties::SetProperties, and ICommandText::SetCommandText) while a rowset is open will fail and return DB_E_OBJECT-

110 � Chapter Four—An OLE DB Primer

Page 130: Learn OLE DB Development With Visual C++ 6.0

OPEN. Each call to Execute creates a new rowset, which must be explicitlyreleased by IRowset::Release.

Execute does not affect the prepared state of a command. The OLE DB con-sumer determines whether the command supports parameters by callingQueryInterface for ICommandWithParameters. If this interface is exposed, thecommand supports parameters; if it is not exposed, the command does notsupport parameters. If the command does not support parameters, Executeignores *pParams. However, if the command text includes parameters, Exe-cute returns DB_E_ERRORSINCOMMAND.

If an input parameter value is not specified, Execute returns DB_E_PARAM-NOTOPTIONAL. If the OLE DB provider cannot describe parameters and theOLE DB consumer has not called SetParameterInfo for all parameters, thebehavior of Execute is undefined. For example, Execute might guess at theparameter 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 toreturn the properties that could not be set.

Execute does not alter the value of any properties. That is, ICommand-Properties::GetProperties returns the same value for a property regardless ofwhether it is called before or after Execute and whether Execute succeeded orfailed. However, if a property value is not required, IRowsetInfo::GetProperties can return a different value for that property thanICommandProperties::GetProperties.

If several threads concurrently request execution of a given command, thecorresponding executions are serialized, and each thread will block until itscorresponding execution concludes. Execute can fail even if ICommand-Prepare::Prepare has succeeded; this may be the case if, for example, theunderlying schema has changed between the Prepare and Execute calls andthe command text had therefore become illegal.

It has the following syntax:

HRESULT Execute (IUnknown * pUnkOuter,REFIID riid,DBPARAMS * pParams,LONG * pcRowsAffected,IUnknown ** ppRowset);

Chapter Four—An OLE DB Primer � 111

Page 131: Learn OLE DB Development With Visual C++ 6.0

It has the following parameters:

pUnkOuter [in]This parameter provides a pointer to the controlling IUnknown interfaceif 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 requiredinterfaces on the resulting rowset, and the method fails (E_NOINTER-FACE) 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. SpecifyingIID_NULL is useful in the case of text commands that do not generaterowsets, such as data definition commands, as a hint to the provider thatno rowset properties need to be verified.

If riid is IID_IMultipleResults, the OLE DB provider creates a multipleresults object and returns a pointer to it in *ppRowset; it does this even ifthe command generates a single result. If the OLE DB provider supportsmultiple results and the command generates multiple results but riid isnot IID_IMultipleResults, the OLE DB provider returns the first result anddiscards any remaining results. If riid is IID_IMultipleResults and the OLEDB provider does not support multiple results, Execute returnsE_NOINTERFACE.

pParams [in/out]This parameter gives a pointer to a DBPARAMS structure that specifiesthe values for one or more parameters. In text commands that use param-eters, if no value is specified for a parameter through *pParams, an erroroccurs.

struct DBPARAMS {void * pData;ULONG cParamSets;HACCESSOR hAccessor;

};

The elements of this structure are used as follows:

Element Description

pData This element shows a pointer to a buffer from which the OLEDB provider retrieves input parameter data and to which theOLE DB provider returns output parameter data, according tothe bindings specified by hAccessor. This pointer must be a validpointer to a contiguous block of OLE DB consumer-ownedmemory for the input and output parameter values. When

112 � Chapter Four—An OLE DB Primer

Page 132: Learn OLE DB Development With Visual C++ 6.0

Element Description

pData (cont.) output parameter data is available to the OLE DB consumerdepends on the DBPROP_OUTPUTPARAMETER-AVAILABILITY property.

cParamSets This element indicates the number of sets of parameters in*pData. If cParamSets is greater than 1, then the bindingsdescribed by hAccessor define the offsets within *pData foreach set of parameters, and cbRowSize (as specified inIAccessor::CreateAccessor) defines a single fixed offsetbetween each of those values and the corresponding values forthe next set of parameters. Sets of multiple parameters(cParamSets is greater than one) can be specified only ifDBPROP_MULTIPLEPARAMSETS is VARIANT_TRUE and thecommand does not return any rowsets.

hAccessor This element shows the handle of the OLE DB accessor to use.If hAccessor is the handle of a null accessor (cBindings inCreateAccessor was 0), then Execute does not retrieve orreturn any parameter values.

If the command text does not include parameters, the OLE DB providerignores this argument.

pcRowsAffected [out]This parameter gives a pointer to memory in which to return the count ofrows affected by a command that updates, deletes, or inserts rows. IfcParamSets is greater than one, *pcRowsAffected is the total number ofrows affected by all of the sets of parameters specified in the execution. Ifthe number of affected rows is not available, *pcRowsAffected is set toDB_COUNTUNAVAILABLE on output. If riid is IID_IMultipleResults, thevalue returned in *pcRowsAffected is either DB_COUNTUNAVAILABLE orthe total number of rows affected by the entire command; to retrieveindividual 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 nullpointer, no count of rows is returned.

*pcRowsAffected is undefined if ICommand::Execute returns DB_S_ASYNCHRONOUS. For asynchronously executed commands, the OLE DBconsumer should call IDBAsynchStatus::GetStatus to obtain the numberof rows affected in pulProgress.

ppRowset [in/out]This parameter shows a pointer to the memory in which to return therowset’s pointer. If *ppRowset is a null pointer, no rowset is created.

It has the following return codes:

S_OKThis return code means that the method succeeded. In all DBPROP struc-tures returned by the method, dwStatus is set to DBPROPSTATUS_OK, the

Chapter Four—An OLE DB Primer � 113

Page 133: Learn OLE DB Development With Visual C++ 6.0

status of all input parameters bound by the OLE DB accessor is set toDBSTATUS_S_OK or DBSTATUS_S_ISNULL, and the status of all outputparameters bound by the OLE DB accessor is set to DBSTATUS_S_OK,DBSTATUS_S_ISNULL, or DBSTATUS_S_TRUNCATED or is unknownbecause the parameter value has not been returned yet.

DB_S_ASYNCHRONOUSThis return code shows the method has initiated asynchronous creation ofthe rowset. The OLE DB consumer can call IDBAsynchStatus to poll forstatus or IConnectionPointContainer to obtain the IID_IDBAsynchNotifyConnection Point. Attempting to call any other interfaces may fail, andthe full set of interfaces may not be available on the object until asyn-chronous initialization of the rowset has completed.

DB_S_ERRORSOCCURREDThis return code can be returned for either of the following reasons:

� The command was executed but an error occurred while returningone or more output parameter values. To determine which outputparameters were not returned, the OLE DB consumer checks the statusvalues.

� The rowset was opened but one or more properties—for which thedwOptions element of the DBPROP structure was DBPROP-OPTIONS_OPTIONAL—were not set. The OLE DB consumer callsIRowsetInfo::GetProperties to determine which properties were set.

DB_S_STOPLIMITREACHEDThis return code means execution has been stopped because a resourcelimit 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 oneor more but not all of the parameters have been processed prior to thecommand being canceled by ICommand::Cancel or IDBAsynchStatus::Abort.

This return code takes precedence over DB_S_ERRORSOCCURRED. Thatis, 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 alsocheck for the conditions described in DB_S_ERRORSOCCURRED.

E_FAILThis return code shows that a provider-specific error occurred.

E_INVALIDARGThis return code indicates *pParams was not ignored, and thatcParamSets in the DBPARAMS structure pointed to by *pParams wasgreater than one, *ppRowset was not a null pointer, and the OLE DB pro-vider does not support multiple results.

114 � Chapter Four—An OLE DB Primer

Page 134: Learn OLE DB Development With Visual C++ 6.0

It can also mean that *pParams was not ignored and, in the DBPARAMSstructure pointed to by *pParams, *pData was a null pointer. It addition-ally can mean that *pParams was not ignored, was not a null pointer, andin the DBPARAMS structure pointed to by *pParams, cParamSets waszero. Finally, it can mean riid was not IID_NULL and *ppRowset was anull pointer.

E_NOINTERFACEThis return code means that the rowset did not support the interfacespecified in riid, or that riid was IID_IMultipleResults and the OLE DBprovider did not support multiple results objects.

DB_E_ABORTLIMITREACHEDThis return code specifies that execution has been aborted because aresource limit has been reached. For example, a query timed out. Noresults have been returned.

DB_E_BADACCESSORHANDLEThis return code shows that *pParams was not ignored and hAccessor inthe DBPARAMS structure pointed to by *pParams was invalid.

DB_E_BADACCESSORTYPEThis return code means that hAccessor in the DBPARAMS structurepointed to by *pParams was not the handle of a parameter accessor.

DB_E_CANCELEDThis return code provides that the command was canceled by a call toCancel on another thread. No records were affected.

DB_E_CANTCONVERTVALUEThis return code indicates that a literal value in the command text couldnot be converted to the type of the associated column for reasons otherthan data overflow.

DB_E_DATAOVERFLOWThis return code states that a literal value in the command text over-flowed the type specified by the associated column.

DB_E_ERRORSINCOMMANDThis return code shows the command text contained one or more errors.OLE DB providers should use OLE DB error objects to return details aboutthe errors.

DB_E_ERRORSOCCURREDThis return code indicates the method failed due to one or more invalidinput parameter values. To determine which input parameter values wereinvalid, the OLE DB consumer checks the status values. It can also indi-cate the command was not executed and no rowset was returned becauseone or more properties—for which the dwOptions element of the DBPROPstructure was DBPROPOPTIONS_REQUIRED—were not set.

Chapter Four—An OLE DB Primer � 115

Page 135: Learn OLE DB Development With Visual C++ 6.0

DB_E_INTEGRITYVIOLATIONThis return code indicates that a literal value in the command text vio-lated the integrity constraints for the column.

DB_E_NOAGGREGATIONThis return code shows *pUnkOuter was not a null pointer and the rowsetbeing created does not support aggregation, or that *pUnkOuter wasnon-null and riid was not IID_Unknown.

DB_E_NOCOMMANDThis return code means no command text was currently set on the com-mand object.

DB_E_NOTABLEThis return code indicates that the specific table or view does not exist inthe current data source.

DB_E_PARAMNOTOPTIONALThis return code states that a value was not supplied for a requiredparameter, or the command text used parameters and *pParams was anull pointer.

DB_SEC_E_PERMISSIONDENIEDThis return code shows that the OLE DB consumer did not have sufficientpermission to execute the command. For example, a rowset-returningcommand specified a column for which the OLE DB consumer does nothave read permission, or an update command specified a column forwhich the OLE DB consumer does not have write permission.

DB_E_OBJECTOPENThis return code means that the OLE DB provider would have to open anew connection to support the operation and DBPROP_MULTIPLE-CONNECTIONS is set to VARIANT_FALSE.

If this method performs deferred accessor validation and that validationtakes place before any data is transferred, it can also return any of thefollowing HRESULTs for the reasons listed in the correspondingDBBINDSTATUS values in IAccessor::CreateAccessor:

E_NOINTERFACEDB_E_BADBINDINFODB_E_BADORDINALDB_E_BADSTORAGEFLAGSDB_E_UNSUPPORTEDCONVERSION

ICommand::GetDBSession

This method returns an interface pointer to the session that created the com-mand. It has the following syntax:

116 � Chapter Four—An OLE DB Primer

Page 136: Learn OLE DB Development With Visual C++ 6.0

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 apointer.

ppSession [out]This parameter shows a pointer to memory in which to return the inter-face pointer. If the OLE DB provider does not have an object that createdthe command, it sets *ppSession to a null pointer. If GetDBSession fails, itmust attempt to set *ppSession to a null pointer.

It has the following return codes:

S_OKThis return code means the method succeeded.

S_FALSEThis return code shows that the OLE DB provider did not have an objectthat created the command. Therefore, it set *ppSession to a null pointer.

E_FAILThis return code indicates that an OLE DB provider-specific erroroccurred.

E_INVALIDARGThis return code states that *ppSession was a null pointer.

E_NOINTERFACEThis return code shows that the session did not support the interfacespecified in riid.

ICommandProperties

ICommandProperties specifies to the command the properties from theRowset property group that must be supported by the rowsets returned byICommand::Execute. A special case of these properties, and the ones mostcommonly requested, are the interfaces the rowset must support. In additionto interfaces, the OLE DB consumer can request properties that modify thebehavior of the rowset or interfaces.

All rowsets must support IRowset, IAccessor, IColumnsInfo, IRowsetInfo, andIConvertType. OLE DB providers may choose to return rowsets supportingother interfaces if doing so is possible and the support for the returned inter-faces does not affect OLE DB consumer code that is not expecting them. Theriid parameter of ICommand::Execute should be one of the interfacesreturned by IRowsetInfo::GetProperties.

Chapter Four—An OLE DB Primer � 117

Page 137: Learn OLE DB Development With Visual C++ 6.0

This interface is mandatory on commands.

Method Description

GetProperties This method returns the list of properties in the Rowsetproperty group that are currently requested for the rowset.

SetProperties This method sets properties in the Rowset property group.

ICommandProperties::GetProperties

This method returns the list of properties in the Rowset property group thatare currently requested for the rowset. GetProperties returns the current val-ues of properties that have been set by the OLE DB consumer withICommandProperties::SetProperties. For all values not set by the OLE DBconsumer, GetProperties returns the initial property value.

Even though IDBProperties::GetPropertyInfo lists a property as being sup-ported by the OLE DB provider, GetProperties will not return a value for it ifit does not apply to the current circumstances. For example, the OLE DB pro-vider’s ability to support the property might be affected by the currenttransaction or the current command text.

The property values returned by ICommandProperties::GetProperties are notaffected by executing the command. However, IRowsetInfo::GetPropertiesmight return a different value for a property than does ICommandProperties::GetProperties. For example, if an OLE DB consumer requests ordered book-marks if they are possible, it calls SetProperties to set the value of DBPROP_ORDEREDBOOKMARKS to VARIANT_TRUE and specifies a dwOptions valueof DBPROPOPTIONS_OPTIONAL. If the OLE DB provider cannot determineat this point in the definition of the command whether this is possible,ICommandProperties::GetProperties returns a value of VARIANT_TRUE and adwOptions of DBPROPOPTIONS_OPTIONAL for this property.

If the OLE DB provider determines during optimization or execution thatordered bookmarks are not possible, IRowsetInfo::GetProperties returns avalue of VARIANT_FALSE and a dwOptions of zero.

If ICommand::Execute returns DB_E_ERRORSOCCURRED, the OLE DB con-sumer can immediately call GetProperties with the DBPROPSET_PROPERTIESINERROR property set to return all the properties that were inerror.

It has the following syntax:

HRESULT GetProperties (const ULONG cPropertyIDSets,const DBPROPIDSET rgPropertyIDSets[],ULONG * pcPropertySets,DBPROPSET ** prgPropertySets);

118 � Chapter Four—An OLE DB Primer

Page 138: Learn OLE DB Development With Visual C++ 6.0

It has the following parameters:

cPropertyIDSets [in]This parameter shows the number of DBPROPIDSET structures inrgPropertyIDSets.

If cPropertySets is zero, the OLE DB provider ignores rgPropertyIDSets andreturns the values of all properties in the Rowset property group forwhich values have been set or defaults exist. It does not return the valuesof properties in the Rowset property group for which values have notbeen set and no defaults exist, nor does it return the values of propertiesfor which no value has been set or default exists, and for which a valuewill be set automatically because a value for another property in theRowset property group has been set.

If cPropertyIDSets is not zero, the OLE DB provider returns the values ofthe requested properties. If a property is not supported, the returnedvalue of dwStatus in the returned DBPROP structure for that property isDBPROPSTATUS_NOTSUPPORTED and the value of dwOptions isundefined.

rgPropertyIDSets [in]This parameter gives an array of cPropertyIDSets DBPROPIDSET struc-tures. The properties specified in these structures must belong to theRowset property group. The OLE DB provider returns the values of theproperties specified in these structures. If cPropertyIDSets is zero, thenthis parameter is ignored.

pcPropertySets [out]This parameter indicates a pointer to memory in which to return thenumber of DBPROPSET structures returned in *prgPropertySets. IfcPropertyIDSets is zero, *pcPropertySets is the total number of propertysets for which the OLE DB provider supports at least one property in theRowset property group. If cPropertyIDSets is greater than zero,*pcPropertySets is set to cPropertyIDSets. If an error other thanDB_E_ERRORSOCCURRED occurs, *pcPropertySets is set to zero.

prgPropertySets [out]This parameter provides a pointer to memory in which to return an arrayof DBPROPSET structures. If cPropertyIDSets is zero, then one structure isreturned for each property set that contains at least one property belong-ing to the Rowset property group. If cPropertyIDSets is not zero, then onestructure 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 DBPROPIDSETstructures in rgPropertyIDSets; that is, for corresponding elements of eacharray, the guidPropertySet elements are the same. If cPropertyIDs, in anelement of rgPropertyIDSets, is not zero, the DBPROP structures in thecorresponding element of *prgPropertySets are returned in the same order

Chapter Four—An OLE DB Primer � 119

Page 139: Learn OLE DB Development With Visual C++ 6.0

as the DBPROPID values in rgPropertyIDs. Thus, in the case where no col-umn properties are specified in rgPropertyIDSets, corresponding elementsof the input rgPropertyIDs and the returned rgProperties have the sameproperty ID. However, if a column property is requested inrgPropertyIDSets, multiple properties may be returned, one for each col-umn, in rgProperties. In this case, corresponding elements ofrgPropertyIDs and rgProperties will not have the same property ID, andrgProperties will contain more elements than rgPropertyIDs.

The OLE DB provider allocates memory for the structures and returns theaddress to this memory; the OLE DB consumer releases this memory withIMalloc::Free when it no longer needs the structures. Before callingIMalloc::Free for *prgPropertySets, the OLE DB consumer should callIMalloc::Free for the rgProperties element within each element of*prgPropertySets. If *pcPropertySets is zero on output or an error otherthan DB_E_ERRORSOCCURRED occurs, the OLE DB provider does notallocate any memory and ensures that *prgPropertySets is a null pointeron output.

It has the following return codes:

S_OKThis return code means the method succeeded. In all DBPROP structuresreturned by the method, dwStatus is set to DBPROPSTATUS_OK.

DB_S_ERRORSOCCURREDThis return code shows that no value was returned for one or more prop-erties. The OLE DB consumer checks dwStatus in the DBPROP structure todetermine the properties for which values were not returned. TheGetProperties method can fail to return properties for a number of rea-sons, including:

� The property was not supported by the OLE DB provider.

� The property was not in the Rowset property group.

� The property set was not supported by the OLE DB provider. IfcPropertyIDs in the DBPROPIDSET structure for the property set waszero, the OLE DB provider cannot set dwStatus in the DBPROP struc-ture because it does not know the IDs of any properties in theproperty set. Instead, it sets cProperties to zero in the DBPROPSETstructure returned for the property set.

E_FAILThis return code states that an OLE DB provider-specific error occurred.

E_INVALIDARGThis return code shows that cPropertyIDSets was not equal to zero andrgPropertyIDSets was a null pointer, or *pcPropertySets or*prgPropertySets was a null pointer. It can also mean in an element ofrgPropertyIDSets, cPropertyIDs was not zero and rgPropertyIDs was a null

120 � Chapter Four—An OLE DB Primer

Page 140: Learn OLE DB Development With Visual C++ 6.0

pointer, or guidPropertySet was DBPROPSET_PROPERTIESINERROR andcPropertyIDs was not zero, or rgPropertyIDs was not a null pointer. Finally,it can indicate cPropertyIDSets was greater than one, and in an element ofrgPropertyIDSets, guidPropertySet was DBPROPSET_PROPERTIESINERROR.

E_OUTOFMEMORYThis return code indicates that the OLE DB provider was unable to allo-cate sufficient memory in which to return the DBPROPSET or DBPROPstructures.

DB_E_ERRORSOCCURREDThis return code specifies that no values were returned for any proper-ties. The OLE DB provider allocates memory for *prgPropertySets and theOLE DB consumer checks dwStatus in the DBPROP structures to deter-mine why properties were not returned. The OLE DB consumer frees thismemory when it no longer needs the information.

ICommandProperties::SetProperties

This method sets properties in the Rowset property group. OLE DB consumersshould first set properties for interfaces and then set other properties thatmodify those interfaces. The combination of the values of an interface prop-erty and a noninterface property might result in another interface beingrequested. However, the value of a noninterface property, by itself, neverresults in another interface being requested.

Setting property values is a cumulative operation. That is, each call toSetProperties attempts to change the values of the specified properties. If anew value is illegal or conflicts with the value of another property, the valueof the property is not changed and SetProperties returns DBPROP-STATUS_BADVALUE or DBPROPSTATUS_CONFLICTING in the dwStatus ele-ment of the DBPROP structure for the property. Properties are processed fromthe beginning of the array to the end of the array. As they are processed,properties that conflict with previously set properties, including those set pre-viously on the same call, are marked with DBPROP STATUS_CONFLICTINGand processing continues through the array of properties.

SetProperties cannot always determine whether a property can be set to itsrequested value. For example, the OLE DB provider often cannot determinewhether the rowset is updatable until it creates the rowset. In this case,SetProperties appears to successfully set the property value and the OLE DBprovider delays the determination until ICommand::Execute is called.

If the value of dwOptions for a property is DBPROPOPTIONS_OPTIONAL, theOLE DB provider attempts to determine whether it can set the property to therequested value. If it can make this determination, it sets the property andreturns DBPROPSTATUS_OK or does not set the property and returnsDBPROPSTATUS_NOTSET. If it cannot make this determination, it can delay

Chapter Four—An OLE DB Primer � 121

Page 141: Learn OLE DB Development With Visual C++ 6.0

setting the property until the command is executed; in this case, the value ofdwOptions returned for the property by GetProperties is DBPROP-OPTIONS_OPTIONAL.

OLE DB consumers should not attempt to unset mandatory rowset interfacessuch as IRowset, IAccessor, IColumnsInfo, and IRowsetInfo. These interfacesare always supported, and SetProperties returns DBPROPSTATUS_NOTSETTABLE for them.

Even if IDBProperties::GetPropertyInfo lists a property as being supported bythe provider, SetProperties can return DBPROPSTATUS_NOTSUPPORTEDwhen the OLE DB consumer attempts to set the property value if the propertydoes not apply to the current circumstances. For example, the OLE DB pro-vider’s ability to support the property might be affected by the currenttransaction or the current command text.

If an error occurs when setting a particular property, SetProperties flags theerror in dwStatus in the DBPROP structure and continues processing.Although OLE DB providers allow changing properties at any time beforecommand execution, OLE DB consumers are encouraged to set all propertiesprior to preparing the command to avoid forcing the OLE DB provider toreprepare 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 inrgPropertySets. If this is zero, the OLE DB provider ignores rgPropertySetsand the method does not do anything.

rgPropertySets [in/out]This parameter provides an array of DBPROPSET structures containingproperties and values to be set. The properties specified in these struc-tures must belong to the Rowset property group. If the same property isspecified more than once in rgPropertySets, then which value is used isOLE DB provider-specific. If cPropertySets is zero, this parameter isignored.

It has the following return codes:

S_OKThis return code means the method succeeded. In all DBPROP structurespassed to the method, dwStatus is set to DBPROPSTATUS_OK.

122 � Chapter Four—An OLE DB Primer

Page 142: Learn OLE DB Development With Visual C++ 6.0

DB_S_ERRORSOCCURREDThis return code shows one or more properties were not set. Propertiesnot in error remain set. The OLE DB consumer checks dwStatus in theDBPROP structures to determine which properties were not set.SetProperties can fail to set properties for a number of reasons, including:

� The property was not supported by the OLE DB provider.

� The property was not in the Rowset property group.

� The property set was not supported by the OLE DB provider.

� The property is read-only and was not set to its default value.

� It was not possible to set the property

� The value of dwOptions in a DBPROP structure was invalid.

� colid in the DBPROP structure was not DB_NULLID and the propertycannot be set on a column.

� The data type in vValue in the DBPROP structure was not the data typeof the property or was not VT_EMPTY.

� The value in vValue in the DBPROP structure was invalid.

� The property’s value conflicted with an existing property.

E_FAILThis return code means an OLE DB provider-specific error occurred.

E_INVALIDARGThis return code specifies cPropertySets was not equal to zero andrgPropertySets was a null pointer, or in an element of rgPropertySets,cProperties was not zero and rgProperties was a null pointer.

DB_E_ERRORSOCCURREDThis return code states that all property values were invalid and no prop-erties were set. The OLE DB consumer checks dwStatus in the DBPROPstructures to determine why properties were not set. The method can failto set properties for any of the reasons specified in DB_S_ERRORS-OCCURRED, except the reason that states that it was not possible to setthe property.

DB_E_OBJECTOPENThis return code means properties cannot be set while there is an openrowset.

ICommandText

This interface is mandatory on commands.

A command object can have only one command text. When the commandtext is specified through SetCommandText, it replaces the existing commandtext.

Chapter Four—An OLE DB Primer � 123

Page 143: Learn OLE DB Development With Visual C++ 6.0

Method Description

GetCommandText Returns the command text set by the last call toSetCommandText.

SetCommandText Sets the command text, replacing the existing commandtext.

ICommandText::GetCommandText

This method returns the command text set by the last call to SetCommand-Text. The GUID returned in the GetCommandText method is the dialect thatwill be used by the OLE DB provider to interpret the statement. This is gener-ally the dialect specified in SetCommandText.

However, if DBGUID_DEFAULT is specified in SetCommandText, the OLE DBprovider should return the particular dialect that it will use to interpret thestatement. If the OLE DB provider chooses between multiple dialects at exe-cution time for a command that has been set with DBGUID_DEFAULT, or ifthe OLE DB provider is returning some OLE DB provider-specific syntax thatmay be different from the command set in SetCommandText, it returnsDBGUID_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 speci-fies 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 thestatement. This is generally the dialect specified in SetCommandText.However, if DBGUID_DEFAULT is specified in SetCommandText, the OLEDB provider returns the particular dialect that it will use to interpret thestatement. If the OLE DB provider determines between multiple dialectsat 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 inSetCommandText, it returns DBGUID_DEFAULT. OLE DB providers candefine 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 com-mand text. The Command object allocates memory for the command text

124 � Chapter Four—An OLE DB Primer

Page 144: Learn OLE DB Development With Visual C++ 6.0

and returns the address to this memory. The OLE DB consumer releasesthis memory with IMalloc::Free when it no longer needs the text. IfGetCommandText returns an error, *ppwszCommand is set to a nullpointer.

It has the following return codes:

S_OKThis return code means the method succeeded.

DB_S_DIALECTIGNOREDThis return code shows that the method succeeded, but the input value of*pguidDialect was ignored. The text is returned in the dialect specified inSetCommandText or the dialect that makes the most sense to the OLE DBprovider. The value returned in *pguidDialect represents that dialect.

E_FAILThis return code indicates an OLE DB provider-specific error occurred.

E_INVALIDARGThis return code specifies *ppwszCommand is a null pointer.

E_OUTOFMEMORYThis return code states that the OLE DB provider was unable to allocatesufficient memory in which to return the command text.

DB_E_NOCOMMANDThis return code means no command text was currently set on theCommand object.

ICommandText::SetCommandText

This method sets the command text, replacing the existing command text. ACommand object contains a single text command, usually a SQL statement.The new command text is copied into the Command object; thus, the OLE DBconsumer can delete the original text without affecting the Command object.All meaningful error checking, such as syntax checking and parsing, isdeferred until ICommandPrepare::Prepare or ICommand::Execute is called.SetCommandText only verifies that the command text can be copied into theCommand object’s space.

If the text of a prepared or unprepared command is overwritten with newcommand text, by calling SetCommandText, the Command object is left in anunprepared state.

SetCommandText does not alter the value of any properties. That is,ICommandProperties::GetProperties returns the same value for a propertyregardless of whether it is called before or after SetCommandText andwhether SetCommandText succeeded or failed. Furthermore, setting the com-mand text does not reset parameter information set through SetParameter-Info.

Chapter Four—An OLE DB Primer � 125

Page 145: Learn OLE DB Development With Visual C++ 6.0

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 rulesfor 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 nullpointer, the current command text is cleared and the command is put inan unprepared state. Any properties that have been set on the commandare unaffected; that is, they retain their current values. Methods thatrequire a command, such as ICommand::Execute, ICommandPrepare::Prepare, or IColumnsInfo::GetColumnsInfo, will return DB_E_NO-COMMAND until a new command text is set.

It has the following return codes:

S_OKThis return code means the method succeeded.

E_FAILThis return code shows that an OLE DB provider-specific error occurred.

DB_E_DIALECTNOTSUPPORTEDThis return code indicates that the provider did not support the dialectspecified in rguidDialect.

DB_E_OBJECTOPENThis return code specifies that a rowset was open on the command.

IColumnsRowset

This interface supplies complete information about columns in a rowset. Themethods in it can be called from a rowset or a command. IColumnsRowset isoptional on both commands and rowsets.

Advanced OLE DB providers implement this interface. OLE DB consumers usethe methods in IColumnsRowset for detailed and flexible information aboutthe columns of a rowset.

A service component can be used to synthesize IColumnsRowset from thesimpler IColumnsInfo (with defaults and Null values to round out theinformation).

126 � Chapter Four—An OLE DB Primer

Page 146: Learn OLE DB Development With Visual C++ 6.0

Note For commands that expose ICommandPrepare, the methods on thisinterface can be called only after the command is prepared or the rowset isinstantiated. If a command text is set but not prepared, any calls to methods onIColumnsInfo return DB_E_NOTPREPARED. For commands that do notexpose ICommandPrepare, the methods on this interface can be called onlyafter the command text has been set.

Method Description

GetAvailableColumns This method returns a list of optional metadatacolumns that can be supplied in a column’srowset.

GetColumnsRowset This method returns a rowset containingmetadata about each column in the currentrowset. The rowset is known as the columnmetadata rowset and is read-only.

IColumnsRowset::GetAvailableColumns

This method returns a list of optional metadata columns that can be suppliedin a column metadata rowset. The method makes no logical change to thestate of the object.

Calling GetAvailableColumns on a command before the command is executedmay 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 countof the elements in *prgOptColumns. If an error occurs, *pcOptColumns isset to zero.

prgOptColumns [out]This parameter gives a pointer to memory in which to return an array ofthe optional columns this OLE DB provider can supply. In addition to theoptional columns listed in GetColumnsRowset, the OLE DB provider canreturn OLE DB provider-specific columns. The rowset or command allo-cates memory for the structures and returns the address to this memory;the OLE DB consumer releases this memory with IMalloc::Free when it nolonger needs the list of columns. If *pcOptColumns is zero on output or an

Chapter Four—An OLE DB Primer � 127

Page 147: Learn OLE DB Development With Visual C++ 6.0

error occurs, the OLE DB provider does not allocate any memory andensures that *prgOptColumns is a null pointer on output.

It has the following return codes:

S_OKThis return code means the method succeeded.

E_FAILThis return code shows that an OLE DB provider-specific error occurred.

E_INVALIDARGThis return code states that *pcOptColumns or *prgOptColumns was a nullpointer.

E_OUTOFMEMORYThis return code indicates that the OLE DB provider was unable to allo-cate sufficient memory in which to return the column IDs.

E_UNEXPECTEDThis return code shows that ITransaction::Commit or ITransaction::Abortwas called and the object is in a zombie state. This error can be returnedonly when the method is called on a rowset.

DB_E_NOCOMMANDThis return code provides that no command text was set. This error canbe returned only when this method is called from the Command object.

DB_E_NOTPREPAREDThis return code means that the command exposed ICommandPrepareand the command text was set, but the command was not prepared. Thiserror can be returned only when this method is called from the Commandobject.

DB_E_NOTREENTRANTThis return code indicates that the OLE DB provider called a method fromIRowsetNotify in the OLE DB consumer and the method has not yetreturned.

DB_SEC_E_PERMISSIONDENIEDThis return code states that the OLE DB consumer did not have sufficientpermission to retrieve the available optional metadata columns.

IColumnsRowset::GetColumnsRowset

This method returns a rowset containing metadata about each column in thecurrent rowset. This rowset is known as the column metadata rowset and isread-only. The method makes no logical change to the state of the object.

The GetColumnsRowset method creates a rowset containing metadata abouta rowset. Unlike the IColumnsInfo::GetColumnInfo method, it provides all ofthe metadata, but it is more complex to implement and use.

128 � Chapter Four—An OLE DB Primer

Page 148: Learn OLE DB Development With Visual C++ 6.0

The rows in the column metadata rowset describe the columns in the under-lying rowset. The column metadata rowset contains one row for each columnin the rowset. This includes the columns of the base table and any pseudo-columns generated by the OLE DB provider or data source, such as book-marks 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 IColumns-Info. The order is usually predictable from the ordering of requested columnsin the command text; if the command text does not specify an order, such asSELECT * FROM MyTable, then the order is determined by the OLE DB pro-vider, such as when the command is prepared.

Each column in the column metadata rowset describes a single attribute, suchas the name or data type, of a column in the original rowset. The order of therequired columns is the same as the order in which they are listed below. Theorder of the optional columns is arbitrary, although they must be after therequired columns. That is, the optional columns in the column metadatarowset can occur in any order after the required columns.

The column metadata rowset always includes the required columns. It con-tains only those optional columns that are requested. GetColumnsRowset canbe called for rowsets created by GetColumnsRowset. Calling Get-ColumnsRowset on a command before the command is executed may be anexpensive 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 interfaceif the column metadata rowset is being created as part of an aggregate. Itis a null pointer if the rowset is not part of an aggregate.

cOptColumns [in]This parameter gives the number of elements in rgOptColumns. IfcOptColumns is zero, then rgOptColumns is ignored, and the OLE DB pro-vider returns all available columns in the columns rowset.

Chapter Four—An OLE DB Primer � 129

Page 149: Learn OLE DB Development With Visual C++ 6.0

rgOptColumns [in]This parameter shows an array that specifies the optional columns toreturn. In addition to the optional columns listed later in this section, theOLE DB consumer can request OLE DB provider-specific columns.

riid [in]This parameter indicates the IID of the requested rowset interface. Thisinterface is conceptually added to the list of required interfaces on theresulting rowset, and the method fails (E_NOINTERFACE) if that inter-face cannot be supported on the resulting rowset.

cPropertySets [in]This parameter specifies the number of DBPROPSET structures inrgPropertySets. If this is zero, the OLE DB provider ignores rgPropertySets.

rgPropertySets [in/out]This parameter provides an array of DBPROPSET structures containingproperties and values to be set. The properties specified in these struc-tures must belong to the Rowset property group. If the same property isspecified more than once in rgPropertySets, then it is OLE DB provider-specific which value is used. If cPropertySets is zero, this argument isignored.

ppColRowset [out]This parameter gives a pointer to memory in which to return therequested interface pointer on the column metadata rowset. If an erroroccurs, the returned pointer is null. If the GetColumnsRowset method iscalled on a command that does not return rows, then the columnmetadata rowset will be empty.

It has the following return codes:

S_OKThis return code means that the method succeeded. In all DBPROP struc-tures passed to the method, dwStatus is set to DBPROPSTATUS_OK.

DB_S_ASYNCHRONOUSThis return code shows that the method has initiated asynchronous cre-ation of the rowset. The OLE DB consumer can call IDBAsynchStatus topoll for status or IConnectionPointContainer to obtain the IID_IDBAsynch-Notify Connection Point. Attempting to call any other interfacesmay fail and the full set of interfaces may not be available on the objectuntil asynchronous initialization of the rowset has completed.

DB_S_ERRORSOCCURREDThis return code indicates that the rowset was opened but one or moreproperties—for which the dwOptions element of the DBPROP structurewas DBPROPOPTIONS_OPTIONAL—were not set. The OLE DB consumerchecks dwStatus in the DBPROP structures to determine which propertieswere not set. The method can fail to set properties for a number of rea-sons, including:

130 � Chapter Four—An OLE DB Primer

Page 150: Learn OLE DB Development With Visual C++ 6.0

� The property was not supported by the OLE DB provider.

� The property was not in the Rowset property group.

� The property set was not supported by the OLE DB provider.

� It was not possible to set the property.

� colid in the DBPROP structure was invalid.

� The data type in vValue in the DBPROP structure was not the data typeof the property or was not VT_EMPTY.

� The value in vValue in the DBPROP structure was invalid.

� The property’s value conflicted with an existing property.

� A property was specified to be applied to all columns, but could not beapplied to one or more columns.

E_FAILThis return code indicates that an OLE DB provider-specific erroroccurred.

E_INVALIDARGThis return code means *ppColRowset was a null pointer; *cPropertySets

was greater than zero and rgPropertySets was a null pointer; orcOptColumns was greater than zero and rgOptColumns was a null pointer.It can also mean in an element of rgPropertySets, cProperties was not zeroand rgProperties was a null pointer.

E_NOINTERFACEThis return code states that the column metadata rowset did not supportthe interface specified in riid.

E_UNEXPECTEDThis return code shows that ITransaction::Commit or ITransaction::Abortwas called and the object is in a zombie state. This error can be returnedonly when the method is called on a rowset.

DB_E_ABORTLIMITREACHEDThis return code specifies the method failed because a resource limit hasbeen reached. For example, a query used to implement the method timedout. No rowset is returned.

DB_E_BADCOLUMNIDThis return code indicates an element of rgOptColumns was an invalidDBID.

DB_E_ERRORSOCCURREDThis return code states that no rowset was returned because one or moreproperties—for which the dwOptions element of the DBPROP structurewas DBPROPOPTIONS_REQUIRED or an invalid value—were not set.The OLE DB consumer checks dwStatus in the DBPROP structures todetermine which properties were not set. None of the satisfiable proper-ties are remembered. The method can fail to set properties for any of the

Chapter Four—An OLE DB Primer � 131

Page 151: Learn OLE DB Development With Visual C++ 6.0

reasons specified in DB_S_ERRORSOCCURRED, except the reason thatstates that it was not possible to set the property.

DB_E_NOAGGREGATIONThis return code shows that *pUnkOuter was not a null pointer and thecolumn’s rowset does not support aggregation, or that *pUnkOuter wasnon-null and riid was not IID_Unknown.

DB_E_NOCOMMANDThis return code indicates that no command text was set. This error canbe returned only when this method is called from the Command object.

DB_E_NOTPREPAREDThis return code states that the command exposed ICommandPrepareand the command text was set, but the command was not prepared. Thiserror can be returned only when this method is called from the Commandobject.

DB_E_NOTREENTRANTThis return code shows that the OLE DB provider called a method fromIRowsetNotify in the OLE DB consumer and the method has not yetreturned.

DB_SEC_E_PERMISSIONDENIEDThis return code means that the OLE DB consumer did not have sufficientpermission to create the column metadata rowset.

Required Metadata Columns

The column metadata rowset always contains the following columns; thesecolumns return the same information as GetColumnInfo.

Column ID Type Indicator Description

DBCOLUMN_IDNAME DBTYPE_WSTR This column contains the column name.This column, together with the DB-COLUMN_GUID and DBCOLUMN_PROPID columns, form the ID of thecolumn. One or more of these columnswill be NULL depending on whichelements of the DBID structure the OLEDB provider uses.

The column ID of a base table should beinvariant under views.

DBCOLUMN_GUID DBTYPE_GUID This column contains the column GUID.

DBCOLUMN_PROPID DBTYPE_UI4 This column contains the column propertyID.DBCOLUMN_NAMEDBTYPE_WSTRThis column contains the name of thecolumn; it might not be unique. If thiscannot be determined, a NULL isreturned.

132 � Chapter Four—An OLE DB Primer

Page 152: Learn OLE DB Development With Visual C++ 6.0

Column ID Type Indicator Description

DBCOLUMN_PROPID(cont.)

The name can be different from the valuereturned in DBCOLUMN_IDNAME if thecolumn has been renamed by thecommand text. This name always reflectsthe most recent renaming of the columnin the current view or command text.If the GetColumnsRowset method iscalled for a column metadata rowset (therowset returned by GetColumnsRowset),the name of each column is the name ofthe column ID constant. For example, thename of the DBCOLUMN_SCALE columnis “DBCOLUMN_SCALE”.

DBCOLUMN_NUMBER DBTYPE_UI4 This column contains the ordinal of thecolumn. This is zero for the bookmarkcolumn of the row, if any. Other columnsare numbered starting with one. Thiscolumn cannot contain a NULL value.

DBCOLUMN_TYPE DBTYPE_UI2 This column contains the indicator of thecolumn’s data type. If the data type of thecolumn varies from row to row, this mustbe DBTYPE_VARIANT. The columncannot contain a NULL value.

DBCOLUMN_TYPEINFO

DBTYPE_IUNKNOWN

This column is reserved for future use.OLE DB providers should return a nullpointer in pTypeInfo.

DBCOLUMN_COLUMNSIZE

DBTYPE_UI4 This column contains the maximumpossible length of a value in the column.For columns that use a fixed-length datatype, this is the size of the data type. Forcolumns that use a variable-length datatype, this is one of the following:

� The maximum length of the column incharacters, for DBTYPE_STR andDBTYPE_WSTR, or bytes, forDBTYPE_BYTES, if one is defined. Forexample, a CHAR(5) column in a SQLtable has a maximum length of 5.

� The maximum length of the data typein characters, for DBTYPE_STR andDBTYPE_WSTR, or bytes, forDBTYPE_BYTES, if the column doesnot have a defined length.

� ~0 (bitwise, the value is not 0; that is,all bits are set to 1) if neither thecolumn nor the data type has a definedmaximum length.

Chapter Four—An OLE DB Primer � 133

Page 153: Learn OLE DB Development With Visual C++ 6.0

Column ID Type Indicator Description

DBCOLUMN_COLUMNSIZE (cont.)

� For data types that do not have alength, this is set to ~0 (bitwise, thevalue is not 0; that is, all bits are set to1).

DBCOLUMN_PRECISION

DBTYPE_UI2 This column shows that if DBCOLUMN_TYPE is a numeric data type, this is themaximum precision of the column. Theprecision of columns with a data type ofDBTYPE_DECIMAL or DBTYPE_NUMERIC depends on the definition ofthe column.If DBCOLUMN_TYPE is not a numericdata type, this is NULL.

DBCOLUMN_SCALE DBTYPE_I2 This column shows that if DBCOLUMN_TYPE is DBTYPE_DECIMAL or DBTYPE_NUMERIC, this is the number of digits tothe right of the decimal point. Otherwise,this is NULL.

DBCOLUMN_FLAGS DBTYPE_UI4 This column contains a bitmask thatdescribes column characteristics. TheDBCOLUMNFLAGS enumerated typespecifies the bits in the bitmask.. Thiscolumn cannot contain a NULL value.

Optional Metadata Columns

The following columns are optional; if the column metadata rowset does notcontain 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 theOLE DB provider does not support that information. It is also the value thecolumn metadata rowset returns when the OLE DB provider does have sup-port, but does not specify that information for a particular column. The OLEDB provider can also have optional, provider-specific columns.

Column ID Type Indicator Description

DBCOLUMN_BASECATALOGNAME

DBTYPE_WSTR This column contains the name of thecatalog in the data source that containsthe column. It is NULL if the base catalogname cannot be determined.The default is NULL.

DBCOLUMN_BASECOLUMNNAME

DBTYPE_WSTR This column contains the name of thecolumn in the data source. This might bedifferent from the column name returnedin the DBCOLUMN_NAME column if analias was used. It is NULL if the basecolumn name cannot be determined.The default is NULL.

134 � Chapter Four—An OLE DB Primer

Page 154: Learn OLE DB Development With Visual C++ 6.0

Column ID Type Indicator Description

DBCOLUMN_BASESCHEMANAME

DBTYPE_WSTR This column contains the name of theschema in the data source that containsthe column. It is NULL if the baseschema name cannot be determined.The default of this column is NULL.

DBCOLUMN_BASETABLENAME

DBTYPE_WSTR This column contains the name of thetable in the data source that contains thecolumn. It is NULL if the base table namecannot be determined.The default of this column is NULL.

DBCOLUMN_CLSID DBTYPE_GUID This column shows that if all objects inthe column have the same class ID, this isthat class ID. If the column may containobjects with different class IDs, or if thecolumn is not of DBTYPE_IUNKNOWN,this is set to NULL.The default is NULL.

DBCOLUMN_COLLATINGSEQUENCE

DBTYPE_I4 This column contains the locale ID(LCID) that defines the collatingsequence for the column. The default ofthis column is the code page installed onthe local machine.

DBCOLUMN_COMPUTEMODE

DBTYPE_I4 This column shows whether a column iscomputed. It can be one of the following:

� DBCOMPUTEMODE_COMPUTED:The column is computed, such asSalary/12.

� DBCOMPUTEMODE_DYNAMIC:The column is computed andIRowset::GetData returns the value ofthe column based on the currentvalues of its component columns,which might have been changed withIRowsetChange::SetData orIRowsetChange::InsertRow.

� DBCOMPUTEDMODE_NOTCOMPUTED: The column is not computed.This is the default.

DBCOLUMN_DATETIMEPRECISION

DBTYPE_UI4 This column contains the datetimeprecision—number of digits in thefractional seconds portion—if the columnis a datetime or interval type.The default of this column is derivedfrom the value in column DATETIME_PRECISION in the COLUMNS schemarowset.

Chapter Four—An OLE DB Primer � 135

Page 155: Learn OLE DB Development With Visual C++ 6.0

Column ID Type Indicator Description

DBCOLUMN_DEFAULTVALUE

DBTYPE_VARIANT

This column contains the column defaultvalue if declared statically. Dynamicinitialization is handled by notifications. Itis NULL if the default value cannot bedetermined. The default is NULL.

DBCOLUMN_DOMAINCATALOG

DBTYPE_WSTR This column contains the name of thecatalog containing the column’s domain.It is NULL if the domain catalog namecannot be determined. The default isNULL.

DBCOLUMN_DOMAINSCHEMA

DBTYPE_WSTR This column shows the name of theschema containing the column’s domain.It is NULL if the domain schema namecannot be determined. The default isNULL.

DBCOLUMN_DOMAINNAME

DBTYPE_WSTR This column contains the name of thedomain of which the column is amember. It is NULL if the domain namecannot be determined. The default isNULL.

DBCOLUMN_HASDEFAULT

DBTYPE_BOOL If this column contains VARIANT_TRUE,then the column has a default value. IfVARIANT_FALSE, the column does nothave a default value. If NULL, the OLEDB provider could not determine if thecolumn has a default value or if a defaultvalue does not make sense for thecolumn. For example, it is a computed,derived, or nonupdatable column.Thedefault is VARIANT_FALSE.

DBCOLUMN_ISAUTOINCREMENT

DBTYPE_BOOL If this column contains VARIANT_TRUE,the column assigns values to new rows infixed increments.If VARIANT_FALSE, the column does notassign values to new rows in fixedincrements. The default isVARIANT_FALSE.

DBCOLUMN_ISCASESENSITIVE

DBTYPE_BOOL This column shows VARIANT_TRUE ifthe order of the column is case sensitiveand if searches on the column are casesensitive. Otherwise, it is VARIANT_FALSE. The default is VARIANT_TRUE.

136 � Chapter Four—An OLE DB Primer

Page 156: Learn OLE DB Development With Visual C++ 6.0

Column ID Type Indicator Description

DBCOLUMN_ISSEARCHABLE

DBTYPE_UI4 This column contains an integerindicating the searchability of a column.The default of this column is derivedfrom the value of the SEARCHABLEcolumn in the PROVIDER_TYPESschema rowset.

DBCOLUMN_ISUNIQUE

DBTYPE_BOOL If this column contains VARIANT_TRUE,then no two rows in the base table—thetable returned in DBCOLUMN_BASE-TABLENAME—can have the same valuein this column. DBCOLUMN_ISUNIQUEis guaranteed to be VARIANT_TRUE ifthe column constitutes a key by itself, orif there is a constraint of type UNIQUEthat applies only to this column. IfVARIANT_FALSE, the column cancontain duplicate values in the base table.The default is VARIANT_FALSE.

DBCOLUMN_MAYSORT

DBTYPE_BOOL If this column contains VARIANT_TRUE,then the column can be sorted. IfVARIANT_FALSE, the column cannot besorted.

DBCOLUMN_OCTETLENGTH

DBTYPE_UI4 This column contains the maximumlength in octets (bytes) of the column, ifthe column is a character or binary type.A value of zero means the column has nomaximum length. It is NULL for all othertypes of columns. The default is derivedfrom the value of the CHARACTER_OCTET_LENGTH column in theCOLUMNS schema rowset.

DBCOLUMN_KEYCOLUMN

DBTYPE_BOOL If this column contains VARIANT_TRUE,the column is one of a set of columnsrequired in order to uniquely identify therow. If VARIANT_FALSE, the column isnot required to uniquely identify the row.If the DBPROP_UNIQUEROW propertyis set to VARIANT_TRUE, then the set ofcolumns with DBCOLUMN_KEYCOLUMN set to VARIANT_TRUEuniquely identifies a row in the rowset.

DBCOLUMN_BASETABLEVERSION

DBTYPE_UI8 This column contains the version numberof the table in the data source thatcontains the column. This number isassumed to change every time the tabledefinition is modified. The way in whichthis number is generated is providerspecific.

Chapter Four—An OLE DB Primer � 137

Page 157: Learn OLE DB Development With Visual C++ 6.0

ICommandPrepare

This optional interface encapsulates command optimization, a separation ofcompile time and run time, as found in traditional relational database sys-tems. 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 Description

Prepare This method validates and optimizes the current command.

Unprepare This method discards the current command execution plan.

ICommandPrepare::Prepare

This method validates and optimizes the current command. Although theyare not required to do so, OLE DB consumers should set any properties beforecalling Prepare, because these properties might be relevant to preparing thecommand.

If Prepare is called redundantly, the OLE DB provider determines whethercommand 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 prop-erty 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 thecommand execution plan, which is produced by Prepare, will be used;that is, how often the command is likely to be executed without renewedoptimization. This guides the optimizer in determining tradeoffs betweensearch effort and run-time processing effort. A value of zero indicates that

138 � Chapter Four—An OLE DB Primer

Page 158: Learn OLE DB Development With Visual C++ 6.0

the OLE DB consumer is unable to provide an estimate, and leaves it tothe optimizer to choose a default value.

It has the following return codes:

S_OKThis return code means the method succeeded.

DB_S_ERRORSOCCURREDThis return code shows that the command was prepared but one or moreproperties—for which the dwOptions element of the DBPROP structurewas DBPROPOPTIONS_OPTIONAL—were not set. The OLE DB consumercalls ICommandProperties::GetProperties to determine which propertieswere set.

E_FAILThis return code indicates that an OLE DB provider-specific erroroccurred.

E_OUTOFMEMORYThis return code specifies that the provider ran out of memory while pre-paring the command.

DB_E_ABORTLIMITREACHEDThis return code shows preparation has been aborted because a resourcelimit has been reached. For example, the preparation timed out.

DB_E_ERRORSINCOMMANDThis return code states that the command text contained one or moreerrors. OLE DB providers should use OLE DB error objects to returndetails about the errors.

DB_E_ERRORSOCCURREDThis return code indicates that the command was not prepared becauseone or more properties—for which the dwOptions element of the DBPROPstructure was DBPROPOPTIONS_REQUIRED—were not set. The OLE DBconsumer calls the ICommandProperties::GetProperties method to deter-mine which properties were not set.

DB_E_NOCOMMANDThis return code shows that no command text was currently set on theCommand object.

DB_E_OBJECTOPENThis return code means that a rowset was open on the command.

DB_SEC_E_PERMISSIONDENIEDThis return code states that the OLE DB consumer did not have sufficientpermission to prepare the command.

Chapter Four—An OLE DB Primer � 139

Page 159: Learn OLE DB Development With Visual C++ 6.0

ICommandPrepare::Unprepare

This method discards the current command execution plan. The method hasno effect if the command was not prepared; the OLE DB provider returnsS_OK.

It has the following syntax:

HRESULT Unprepare();

There are no parameters.

It has the following return codes:

S_OKThis return code means the method succeeded.

E_FAILThis return code shows that an OLE DB provider-specific error occurred.

DB_E_OBJECTOPENThis return code indicates a rowset was open on the command.

ICommandWithParameters

OLE DB providers that support parameters must support ICommandWith-Parameters. 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 sup-ported by many OLE DB providers in any scalar expression.

For scalar parameters of prepared commands, there is a presumption that dif-ferent parameter values do not require different plans. In other words, asingle preparation and its resulting plan are satisfactory for all possible valuesof scalar parameters.

Parameter values are set when a command is executed. Methods are includedhere to offer a means for setting and obtaining a list of parameters and theirtypes.

Method Description

GetParameterInfo This method gets a list of the command’s parameters,their names, and their types.

MapParameterNames This method returns an array of parameter ordinalswhen given named parameters.

SetParameterInfo This method specifies the native data type of eachparameter.

140 � Chapter Four—An OLE DB Primer

Page 160: Learn OLE DB Development With Visual C++ 6.0

ICommandWithParameters::GetParameterInfo

This method gets a list of the command’s parameters, their names, and theirtypes. The method makes no logical change to the state of the object.

If SetParameterInfo has not been called for any parameters or SetParameter-Info has been called with cParams equal to zero, GetParameterInfo returnsinformation about the parameters only if the OLE DB provider can deriveparameter information. If the OLE DB provider cannot derive parameterinformation, GetParameterInfo returns DB_E_PARAMUNAVAILABLE.

If SetParameterInfo has been called for at least one parameter, GetParameter-Info returns the parameter information only for those parameters for whichSetParameterInfo has been called. It does this even if the OLE DB providercan derive information about the parameters for which SetParameterInfo wasnot called. The OLE DB provider does not return a warning in this casebecause it often cannot determine the number of parameters and thereforecannot 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 numberof parameters in the command. If an error occurs, *pcParams is set tozero.

prgParamInfo [out]This parameter provides a pointer to memory in which to return an arrayof parameter information structures. The command allocates memory forthe array, as well as the strings, and returns the address to this memory.The OLE DB consumer releases the array memory with IMalloc::Freewhen it no longer needs the parameter information. If *pcParams is zeroon output or an error occurs, the OLE DB provider does not allocate anymemory and ensures that *prgParamInfo is a null pointer on output.Parameters are returned in ascending order according to the iOrdinal ele-ment of the DBPARAMINFO structure.

Here is the DBPARAMINFO structure:

typedef struct tagDBPARAMINFO {DBPARAMFLAGS dwFlags;ULONG iOrdinal;LPOLESTR pwszName;

Chapter Four—An OLE DB Primer � 141

Page 161: Learn OLE DB Development With Visual C++ 6.0

ITypeInfo * pTypeInfo;ULONG ulParamSize;DBTYPE wType;BYTE bPrecision;BYTE bScale;

} DBPARAMINFO;

The elements of this structure are used as follows:

Element Description

dwFlags This element provides a bitmask describing parameter characteristics;these values have the following meaning:

� DBPARAMFLAGS_ISINPUT—This flag indicates whether aparameter accepts values on input. It is not set if this is unknown.

� DBPARAMFLAGS_ISOUTPUT—This flag shows whether aparameter returns values on output. Not set if this is unknown. OLEDB providers support only those parameter types that make sensefor their data source.

� DBPARAMFLAGS_ISSIGNED—This flag indicates whether aparameter 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, suchas DBTYPE_BSTR. It is generally used in the SetParameterInfomethod so the OLE DB consumer can tell the OLE DB provider if aprovider-specific type name refers to a signed or unsigned type.

� DBPARAMFLAGS_ISNULLABLE—This flag specifies whether aparameter accepts NULLs. If nullability is unknown, the flag is set.

� DBPARAMFLAGS_ISLONG—This flag shows whether a parametercontains a BLOB that contains very long data. The definition of verylong data is OLE DB provider-specific. The flag setting correspondsto the value of the IS_LONG column in the PROVIDER_TYPESschema rowset for the data type.

When this flag is set, the BLOB is best manipulated through one of thestorage interfaces. Although such BLOBs can be sent in a single piecewith the ICommand::Execute method, there may be OLE DBprovider-specific problems in doing so. For example, the BLOB mightbe truncated due to machine limits on memory. Furthermore, whenthis flag is set, the OLE DB provider might not be able to accuratelyreturn the maximum length of the BLOB data in ulParamSize in theGetParameterInfo method.

When this flag is not set, the BLOB can be accessed either through theICommand::Execute method or through a storage interface.

iOrdinal This element provides the ordinal of the parameter. Parameters arenumbered from left to right as they appear in the command, with thefirst parameter in the command having an iOrdinal value of 1.

pwszName This element gives the name of the parameter; it is a null pointer ifthere is no name. Names are normal names. The colon prefix (whereused within SQL text) is stripped.

142 � Chapter Four—An OLE DB Primer

Page 162: Learn OLE DB Development With Visual C++ 6.0

Element Description

pTypeInfo This element, ITypeInfo, describes the type if *pTypeInfo is not a nullpointer.

ulParamSize This element gives the maximum possible length of a value in theparameter. For parameters that use a fixed-length data type, this is thesize of the data type. For parameters that use a variable-length datatype, this is one of the following:

� The maximum length of the parameters in characters, for DBTYPE_STR and DBTYPE_WSTR, or bytes, for DBTYPE_BYTES, if one isdefined. For example, a parameter for a CHAR(5) column in a SQLtable has a maximum length of 5.

� The maximum length of the data type in characters, for DBTYPE_STR and DBTYPE_WSTR, or bytes, for DBTYPE_BYTES, if theparameter does not have a defined length.

� ~0 (bitwise, the value is not 0; that is, all bits are set to 1) if neitherthe 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, thevalue is not 0; that is, all bits are set to 1).

wType This element is the indicator of the parameter’s data type.

bPrecision This element states that if wType is a numeric type, bPrecision is themaximum 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).

bScale 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 (ifbScale 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 stringvalues (names used within the *pwszName element of the DBPARAMINFOstructure) with a single, globally allocated buffer. Specifying a nullpointer for *ppNamesBuffer suspends the return of parameter names. Thecommand allocates memory for the buffer and returns the address to thismemory. The OLE DB consumer releases the memory with IMalloc::Freewhen it no longer needs the parameter information. If *pcParams is zeroon output or an error occurs, the OLE DB provider does not allocate anymemory and ensures that *ppNamesBuffer is a null pointer on output.Each of the individual string values stored in this buffer is terminated bya null termination character. Therefore, the buffer may contain one ormore strings, each with its own null termination character, and may con-tain embedded null termination characters.

It has the following return codes:

S_OKThis return code means the method succeeded.

Chapter Four—An OLE DB Primer � 143

Page 163: Learn OLE DB Development With Visual C++ 6.0

E_FAILThis return code shows that an OLE DB provider-specific error occurred.

E_INVALIDARGThis return code indicates that *pcParams or *prgParamInfo was a nullpointer.

E_OUTOFMEMORYThis return code states that the OLE DB provider was unable to allocatesufficient memory in which to return the parameter data array or param-eter names.

DB_E_NOCOMMANDThis return code specifies that the OLE DB provider can derive parameterinformation, but it does not support command preparation. However, nocommand text was currently set on the Command object and no parame-ter information had been specified with SetParameterInfo.

DB_E_NOTPREPAREDThis return code means the OLE DB provider can derive parameter infor-mation, and it supports command preparation. However, the commandwas in an unprepared state and no parameter information was specifiedwith SetParameterInfo.

DB_E_PARAMUNAVAILABLEThis return code indicates that the OLE DB provider cannot derive param-eter information from the command and SetParameterInfo has not beencalled.

ICommandWithParameters::MapParameterNames

This method returns an array of parameter ordinals when given namedparameters. It has the following syntax:

HRESULT MapParameterNames (ULONG cParamNames,const OLECHAR * rgParamNames[],LONG rgParamOrdinals[]);

It has the following parameters:

cParamNames [in]This parameter provides the number of parameter names to map. IfcParamNames is zero, MapParameterNames does nothing and returnsS_OK.

rgParamNames [in]This parameter gives an array of parameter names of which to determinethe parameter ordinals. If a parameter name is not found, the corre-sponding element of rgParamOrdinals is set to zero and the methodreturns DB_S_ERRORSOCCURRED.

144 � Chapter Four—An OLE DB Primer

Page 164: Learn OLE DB Development With Visual C++ 6.0

rgParamOrdinals [out]This parameter shows an array of cParamNames ordinals of the parame-ters identified by the elements of *rgParamNames. The OLE DB consumerallocates (but is not required to initialize) memory for this array andpasses the address of this memory to the OLE DB provider. The OLE DBprovider returns the parameter ordinals in the array.

It has the following return codes:

S_OKThis return code means the method succeeded. Each element ofrgParamOrdinals is set to a nonzero value.

DB_S_ERRORSOCCURREDThis return code shows that an element of *rgParamNames was invalid.The corresponding element of rgParamOrdinals is set to zero.

E_FAILThis return code indicates that an OLE DB provider-specific erroroccurred.

E_INVALIDARGThis return code states that cParamNames was not zero and*rgParamNames or rgParamOrdinals was a null pointer.

DB_E_ERRORSOCCURREDThis return code specifies that all elements of *rgParamNames wereinvalid. All elements of rgParamOrdinals are set to zero.

DB_E_NOCOMMANDThis return code means that no command text was currently set on theCommand object and no parameter information had been specified withSetParameterInfo.

DB_E_NOTPREPAREDThis return code indicates the OLE DB provider can derive parameterinformation and supports command preparation. However, the commandwas in an unprepared state and no parameter information had been spec-ified with SetParameterInfo.

ICommandWithParameters::SetParameterInfo

This method specifies the native data type of each parameter. OLE DB provid-ers generally derive parameter type information from the data source andreturn it to the OLE DB consumer through the GetParameterInfo method.OLE DB consumers then use this information to build parameter accessors foruse with ICommand::Execute.

Some OLE DB providers—notably many SQL database OLE DB providers—cannot derive parameter type information from the data source. For theseOLE DB providers, the OLE DB consumer must supply the native parameter

Chapter Four—An OLE DB Primer � 145

Page 165: Learn OLE DB Development With Visual C++ 6.0

type information through SetParameterInfo. The OLE DB provider uses thetype information specified by SetParameterInfo to determine how to convertparameter data from the type supplied by the OLE DB consumer (as indicatedby the wType value in the binding structure) to the native type used by thedata source. When the OLE DB consumer specifies a data type with knownprecision, scale, and size values, any information supplied by the OLE DBconsumer for precision, scale, or size should be ignored by the OLE DBprovider.

The information that the OLE DB consumer supplies must be correct andmust be supplied for all parameters. OLE DB providers that cannot deriveparameter type information cannot verify the supplied information againstthe parameter metadata, although they can determine that the specified val-ues are legal values for the OLE DB provider. Such OLE DB providerssometimes cannot even determine the number of parameters in thecommand.

The result of executing a command using incorrect parameter information orpassing parameter information for the wrong number of parameters is unde-fined. For example, if the parameter type is LONG and the OLE DB consumerspecifies a type indicator of DBTYPE_STR in SetParameterInfo, the OLE DBprovider 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 anerror.

When the OLE DB consumer calls SetParameterInfo, it specifies an OLE DBprovider-specific data type name (as derived from the PROVIDER_TYPESschema rowset) or a standard type name. If the OLE DB consumer passes astandard type name, the OLE DB provider maps it to an OLE DB provider-specific type name. For example, an OLE DB provider for a SQL DBMS mightmap “DBTYPE_I2” to “SMALLINT”.

The following is a list of standard type names and their associated type indi-cators. This list contains many commonly known types. Individual OLE DBproviders may allow other OLE DB provider-specific names as well.

Standard Type Name Type Indicator

"DBTYPE_I1" DBTYPE_I1

"DBTYPE_I2" DBTYPE_I2

"DBTYPE_I4" DBTYPE_I4

"DBTYPE_I8" DBTYPE_I8

"DBTYPE_UI1" DBTYPE_UI1

"DBTYPE_UI2" DBTYPE_UI2

"DBTYPE_UI4" DBTYPE_UI4

"DBTYPE_UI8" DBTYPE_UI8

"DBTYPE_R4" DBTYPE_R4

146 � Chapter Four—An OLE DB Primer

Page 166: Learn OLE DB Development With Visual C++ 6.0

Standard Type Name Type Indicator

"DBTYPE_R8" DBTYPE_R8

"DBTYPE_CY" DBTYPE_CY

"DBTYPE_DECIMAL" DBTYPE_DECIMAL

"DBTYPE_NUMERIC" DBTYPE_NUMERIC

"DBTYPE_BOOL" DBTYPE_BOOL

"DBTYPE_ERROR" DBTYPE_ERROR

"DBTYPE_UDT" DBTYPE_UDT

"DBTYPE_VARIANT" DBTYPE_VARIANT

"DBTYPE_IDISPATCH" DBTYPE_IDISPATCH

"DBTYPE_IUNKNOWN" DBTYPE_IUNKNOWN

"DBTYPE_GUID" DBTYPE_GUID

"DBTYPE_DATE" DBTYPE_DATE

"DBTYPE_DBDATE" DBTYPE_DBDATE

"DBTYPE_TIME" DBTYPE_TIME

"DBTYPE_TIMESTAMP" DBTYPE_TIMESTAMP

"DBTYPE_BSTR" DBTYPE_BSTR

"DBTYPE_CHAR" DBTYPE_STR

"DBTYPE_VARCHAR" DBTYPE_STR

"DBTYPE_LONGVARCHAR" DBTYPE_STR

"DBTYPE_WCHAR" DBTYPE_WSTR

"DBTYPE_WVARCHAR" DBTYPE_WSTR

"DBTYPE_WLONGVARCHAR" DBTYPE_WSTR

"DBTYPE_BINARY" DBTYPE_BYTES

"DBTYPE_VARBINARY" DBTYPE_BYTES

"DBTYPE_LONGVARBINARY" DBTYPE_BYTES

After the OLE DB consumer calls the SetParameterInfo method to specify theparameter type information, it can call the GetParameterInfo method toretrieve the type indicator for each parameter. These values—which arebased on the information specified in SetParameterInfo—represent the bestfit of OLE DB types to the native parameter types. The OLE DB provider guar-antees 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 parame-ter type.

If the OLE DB provider can derive parameter type information and the OLEDB consumer calls the SetParameterInfo method, SetParameterInfo uses thespecified type information and returns DB_S_TYPEINFOOVERRIDDEN.Because deriving type information can be an expensive operation, this mayresult in more efficient code.

Chapter Four—An OLE DB Primer � 147

Page 167: Learn OLE DB Development With Visual C++ 6.0

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 typeinformation. If cParams is zero, the type information for all parameters isdiscarded, and rgParamOrdinals and rgParamBindInfo are ignored.

rgParamOrdinals [in]This parameter provides an array of cParams ordinals. These are the ordi-nals of the parameters for which to set type information. Typeinformation for parameters whose ordinals are not specified is notaffected.

rgParamBindInfo [in]This parameter indicates an array of cParams DBPARAMBINDINFO struc-tures. If rgParamBindInfo is a null pointer, then the type information forthe 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 Description

pwszDataSourceType This element gives a pointer to the OLE DB provider-specific name of the parameter’s data type or a standarddata type name. This name is not returned by theGetParameterInfo method; instead, the OLE DB providermaps the data type specified by this name to an OLE DBtype indicator and returns that type indicator.

148 � Chapter Four—An OLE DB Primer

Page 168: Learn OLE DB Development With Visual C++ 6.0

Element Description

pwszName This element shows the name of the parameter. This is anull pointer if the parameter does not have a name.

The OLE DB consumer must specify a name for all ornone of the parameters set at any time. If the OLE DBprovider does not support named parameters, thisargument is ignored and the OLE DB provider is notrequired to verify that all or none of the parameters arenamed.

ulParamSize This element indicates the maximum possible length of avalue in the parameter. For parameters that use afixed-length data type, this is the size of the data type. Forparameters that use a variable-length data type, this isone of the following:

� The maximum length of the parameters in characters,for DBTYPE_STR and DBTYPE_WSTR, or bytes, forDBTYPE_BYTES, if one is defined. For example, aparameter for a CHAR(5) column in a SQL table has amaximum length of 5.

� The maximum length of the data type in characters, forDBTYPE_STR and DBTYPE_WSTR, or bytes, forDBTYPE_BYTES, if the parameter does not have adefined length.

� ~0 (bitwise, the value is not 0; that is, all bits are set to1) 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).

This argument is ignored if pwszDataSourceType is null.

dwFlags For information on this element, please see the dwFlagselement of the DBPARAMINFO structure.

bPrecision This element states that if pwszDataSourceType is anumeric type, bPrecision is the maximum number of digits,expressed in base 10. Otherwise, it is ignored.

This argument is ignored if pwszDataSourceType is null.

bScale This element provides that if pwszDataSourceType is anumeric type with a fixed scale, bScale is the number ofdigits to the right (if bScale is positive) or left (if bScale isnegative) of the decimal point. Otherwise, it is ignored.

This argument is ignored if pwszDataSourceType is null.

It has the following return codes:

S_OKThis return code means that the method succeeded.

Chapter Four—An OLE DB Primer � 149

Page 169: Learn OLE DB Development With Visual C++ 6.0

DB_S_TYPEINFOOVERRIDDENThis return code shows that the OLE DB provider was capable of derivingthe parameter type information and the SetParameterInfo method wascalled. The parameter type information specified in SetParameterInfo wasused. SetParameterInfo replaced parameter type information specified ina previous call to SetParameterInfo.

E_FAILThis return code states that an OLE DB provider-specific error occurred.

E_INVALIDARGThis return code specifies that cParams was not zero and rgParamOrdinals

was a null pointer, or an element of rgParamOrdinals was zero. It can alsomean that in an element of rgParamBindInfo, the pwszDataSourceType ele-ment was a null pointer and the OLE DB provider does not supportdefault parameter conversions, or the dwFlags element was invalid.

DB_E_BADPARAMETERNAMEThis return code indicates that in an element of rgParamBindInfo, thepwszName element specified an invalid parameter name. The OLE DBprovider does not check whether the name was correct for the specifiedparameter, just whether it was a valid parameter name.

It can also mean that in one or more elements of rgParamBindInfo, butnot all, pwszName was null. Additionally, it can mean that in one or moreelements of rgParamBindInfo, pwszName was null and one or moreparameters previously set and not overridden were specified with anon-null pwszName. Finally, it can mean that in one or more elements ofrgParamBindInfo, pwszName was non-null and one or more parameterspreviously set and not overridden were specified with a null pwszName.

DB_E_BADTYPENAMEThis return code states that in an element of rgParamBindInfo, thepwszDataSourceType element specified an invalid data type name. TheOLE DB provider does not check whether the data type was correct forthe specified parameter, just whether it was a data type name supportedby the OLE DB provider.

DB_E_OBJECTOPENThis return code means that a rowset was open on the command.

IRowset

IRowset is the base rowset interface. It provides methods for fetching rowssequentially, getting the data from those rows, and managing rows.

IRowset requires IAccessor and IRowsetInfo. IRowset is required for all OLEDB providers that support general OLE DB consumers. OLE DB consumers

150 � Chapter Four—An OLE DB Primer

Page 170: Learn OLE DB Development With Visual C++ 6.0

use the methods in IRowset for all basic rowset operations, including fetchingand releasing rows and getting column values.

When an OLE DB consumer first gets an interface pointer on a rowset, usu-ally its first step is to determine the rowset’s capabilities using IRowsetInfo::GetProperties. This returns information about the interfaces exposed by therowset as well as those capabilities of the rowset that do not show up as dis-tinct interfaces, such as the maximum number of active rows and how manyrows can have pending updates at the same time.

For OLE DB consumers, the next step is to determine the characteristics, ormetadata, of the columns in the rowset. For this they use either IColumnsInfoor 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 themetadata or on the basis of knowing the text command that generated therowset. It determines the ordinals of the needed columns from the orderingof the column information returned by IColumnsInfo or from ordinals in thecolumn metadata rowset returned by IColumnsRowset.

Some OLE DB consumers do not use a command or do not want to browsethe column information; they may know the name or property identifier forthe columns they want to use. They call IColumnsInfo::MapColumnIDs toretrieve the column ordinals. The ordinals are used to specify a binding to acolumn. A binding is a structure that associates an element of the OLE DBconsumer’s structure with a column. The binding can bind the column’s datavalue, length, and status value.

A set of bindings is gathered together in an OLE DB accessor, which is createdwith IAccessor::CreateAccessor. An OLE DB accessor can contain multiplebindings so that the data for multiple columns can be retrieved or set in a sin-gle call. The OLE DB consumer can create several OLE DB accessors to matchdifferent usage patterns in different parts of the application. It can create andrelease 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 suchas GetNextRows or IRowsetLocate::GetRowsAt. To create and initialize a newrow to be inserted into the data source, the OLE DB consumer calls IRowset-Change::InsertRow.

The methods that fetch rows do not actually return data to the OLE DB con-sumer. Instead, they return the handles to these rows, and a local copy of therows is stored in the rowset.

After the rows are returned, the OLE DB consumer can access the data in therows. The consumer calls GetData and passes it the handle to a row, the han-dle to an OLE DB accessor, and a pointer to an OLE DB consumer-allocatedbuffer. GetData converts the data (if it does not match the native OLE DB

Chapter Four—An OLE DB Primer � 151

Page 171: Learn OLE DB Development With Visual C++ 6.0

provider storage) and returns the columns as specified in the bindings usedto create the OLE DB accessor. The OLE DB consumer can call GetData morethan once for a row, using different OLE DB accessors and buffers; thus, theOLE DB consumer can have multiple copies of the same data. For example, ifa column contains a text document, the OLE DB consumer might call GetDatawith an OLE DB accessor that binds the first 50 bytes of the document. Whenthe user double-clicks on the displayed heading text, the OLE DB consumercould then call GetData with a different OLE DB accessor to retrieve theentire document.

Data from variable-length columns may be treated several ways. First, suchcolumns 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 ofthe buffer. The OLE DB consumer can determine that truncation has occurredby checking if the status is DBSTATUS_S_TRUNCATED. The returned length isalways the true length in bytes, so the OLE DB consumer also can determinehow 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 inthe column and returns this memory to the OLE DB consumer.

In both cases, it is likely that such large values may be best optimized asdeferred columns and accessed only when necessary. Performance varies withdifferent servers, but in general BLOB columns are stored separately fromother records and may be more costly to access than ordinary columns, sothey would not routinely be pulled in for browsing or scanning.

Another way to handle BLOB columns may be implemented on some provid-ers, 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 releasesthem with ReleaseRows. This releases resources from the rowset’s copy of therows and makes room for new rows. The OLE DB consumer can then repeatits 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::Releaseon all interfaces exposed by the rowset to release the rowset. When therowset is released, it forces the release of any remaining rows or OLE DBaccessors the OLE DB consumer may hold. Such handle objects are subordi-nate to the rowset. That is, they do not take reference counts upon the rowsetand cannot cause the rowset to linger beyond the point where all the inter-faces for the rowset have been released. The rowset must clean up all suchsubordinate objects.

152 � Chapter Four—An OLE DB Primer

Page 172: Learn OLE DB Development With Visual C++ 6.0

Note If the rowset was generated as a result of executing a command that con-tained output parameters and the OLE DB provider populates outputparameters when the rowset is released (that is, DBPROP_OUTPUT-PARAMETERAVAILABILITY is DBPROP_OA_ATROWRELEASE), the memoryfor the output parameters bound at execute time must be valid when therowset is released. Not doing so is considered a serious programming errorand likely will cause a crash.

Method Description

AddRefRows This method adds a reference count to an existing row handle.

GetData This method retrieves data from the rowset’s copy of the row.

GetNextRows This method fetches rows sequentially, remembering the

previous position.

ReleaseRows This method releases rows.

RestartPosition This method repositions the next fetch position to its initialposition; that is, its position when the rowset was firstcreated.

IRowset::AddRefRows

This method adds a reference count to an existing row handle. AddRefRowsmust be supported for implementing multiple references to the same roweven if the rowset does not support IRowsetIdentity. It is always possible foran OLE DB consumer to call AddRefRows while it is processing a method inIRowsetNotify. That is, the rowset must be reentrant through AddRefRowsduring notification.

If AddRefRows encounters an error while incrementing the reference count ofa row, it sets the corresponding element in rgRowStatus to the appropriateDBROWSTATUS value and continues processing. If a row handle is duplicatedin rghRows, the corresponding row will have its reference count incrementedby 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 ref-erence count.

Chapter Four—An OLE DB Primer � 153

Page 173: Learn OLE DB Development With Visual C++ 6.0

rghRows [in]This parameter gives an array of row handles for which to increment thereference count. The reference count of row handles is incremented byone for each time they appear in the array.

rgRefCounts [out]This parameter provides an array with cRows elements in which to returnthe new reference count for each row handle. The OLE DB consumer allo-cates memory for this array. If rgRefCounts is a null pointer, no referencecounts are returned.

rgRowStatus [out]This parameter indicates an array with cRows elements in which to returnvalues indicating the status of each row specified in rghRows. If no errorsoccur while incrementing the reference count of a row, the correspondingelement of rgRowStatus is set to DBROWSTATUS_S_OK. If an error occurswhile incrementing the reference count of a row, the corresponding ele-ment is set as specified in DB_S_ERRORSOCCURRED. The OLE DBconsumer allocates memory for this array. If rgRowStatus is a null pointer,no row statuses are returned.

It has the following return codes:

S_OKThis return code means the method succeeded. The reference count of allrows was successfully incremented, and the corresponding element ofprgRowStatus contains DBROWSTATUS_S_OK.

DB_S_ERRORSOCCURREDThis return code shows that an error occurred while incrementing the ref-erence count of a row, but the reference count of at least one row wasincremented. Successes can occur for the reason listed under S_OK. Thefollowing errors can occur:

� A row handle was invalid. The reference count of the row was notincremented and the corresponding element of rgRowStatus containsDBROWSTATUS_E_INVALID.

� A row handle referred to a row that had a reference count of zero. Thereference count of the row was not incremented and the correspond-ing element of rgRowStatus contains DBROWSTATUS_E_INVALID.

� A row handle referred to a row for which a deletion had been trans-mitted to the data source. The reference count of the row was notincremented and the corresponding element of rgRowStatus containsDBROWSTATUS_E_DELETED.

� The OLE DB consumer encountered a recoverable, OLE DB pro-vider-specific error, such as an RPC failure when transmitting thechange to a remote server. The corresponding element of rgRowStatuscontains DBROWSTATUS_E_FAIL.

154 � Chapter Four—An OLE DB Primer

Page 174: Learn OLE DB Development With Visual C++ 6.0

E_FAILThis return code means that an OLE DB provider-specific error occurred.

E_INVALIDARGThis return code shows that rghRows was a null pointer and cRows wasnot zero.

E_UNEXPECTEDThis return code means that ITransaction::Commit or ITransaction::Abortwas called and the object is in a zombie state.

DB_E_ERRORSOCCURREDThis return code indicates that errors occurred while incrementing thereference count of all of the rows. Errors can occur for the reasons listedunder DB_S_ERRORSOCCURRED.

IRowset::GetData

This method retrieves data from the rowset’s copy of the row. The methodmakes no logical change to the state of the object. An OLE DB consumer callsGetData to retrieve data from rows that have been fetched by prior calls tomethods such as GetNextRows.

An OLE DB consumer can call GetData any number of times. In each call, itcan 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 asit wants, and it can get data in different types if alternate conversions areavailable. GetData does not enforce any security restrictions. The OLE DBprovider must not create a rowset that includes columns for which the OLEDB consumer does not have read privileges, so GetData never encountersproblems accessing the data for a column. The rowset can contain columns towhich the OLE DB consumer does not have write permission ifDBPROP_COLUMNRESTRICT is VARIANT_TRUE.

The methods that fetch rows must not return the handles of rows for whichthe OLE DB consumer does not have read privileges, so GetData neverencounters problems accessing a row. Such rows might exist if theDBPROP_ROWRESTRICT property is VARIANT_TRUE.

If GetData fails, the memory to which *pData points is not freed but its con-tents are undefined. If, before GetData failed, the OLE DB provider allocatedany memory for return to the OLE DB consumer, the OLE DB provider freesthis memory and does not return it to the OLE DB consumer.

GetData must be reentrant during notifications. If the provider calls a methodfrom IRowsetNotify in the consumer, the consumer must be able to callGetData while processing the notification method.

It has the following syntax:

HRESULT GetData (

Chapter Four—An OLE DB Primer � 155

Page 175: Learn OLE DB Development With Visual C++ 6.0

HROW hRow,HACCESSOR hAccessor,void * 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 rowhandle; the OLE DB provider might not validate hRow before using it. Theresult of passing the handle of a deleted row is OLE DB provider-specific,although the OLE DB provider cannot terminate abnormally. For example, theOLE DB provider might return DB_E_BADROWHANDLE,DB_E_DELETEDROW, or it might get data from a different row. The result ofpassing an invalid row handle in hRow is undefined.

hAccessor [in]This parameter indicates the handle of the OLE DB accessor to use. IfhAccessor is the handle of a null accessor (cBindings in IAccessor::Create-Accessor was zero), then GetData does not get any data values.

Warning The OLE DB consumer must ensure that hAccessor contains a validOLE DB accessor handle; the OLE DB provider might not validate hAccessorbefore using it. The result of passing an invalid OLE DB accessor handle inhAccessor 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 mustbe a valid pointer to a contiguous block of OLE DB consumer-ownedmemory into which the data will be written.

It has the following return codes:

S_OKThis return code means the method succeeded. The status of all columnsbound by the accessor is set to DBSTATUS_S_OK, DBSTATUS_S_ISNULL,or DBSTATUS_S_TRUNCATED.

DB_S_ERRORSOCCURREDThis return code indicates that an error occurred while returning data forone or more columns, but data was successfully returned for at least onecolumn. To determine the columns for which data was returned, the OLEDB consumer checks the status values.

E_FAILThis return code shows that an OLE DB provider-specific error occurred.

156 � Chapter Four—An OLE DB Primer

Page 176: Learn OLE DB Development With Visual C++ 6.0

E_INVALIDARGThis return code specifies that *pData was a null pointer and the OLE DBaccessor was not a null accessor.

E_UNEXPECTEDThis return code states that ITransaction::Commit or ITransaction::Abortwas called and the object is in a zombie state.

DB_E_BADACCESSORHANDLEThis return code shows that hAccessor was invalid. OLE DB providers arenot required to check for this condition, because doing so might slow themethod significantly.

DB_E_BADACCESSORTYPEThis return code means the specified OLE DB accessor was not a rowaccessor.

DB_E_BADROWHANDLEThis return code shows that hRow was invalid. OLE DB providers are notrequired to check for this condition, because doing so might slow themethod significantly.

DB_E_DELETEDROWThis return code specifies hRow referred to a pending delete row or a rowfor which a deletion had been transmitted to the data source. OLE DBproviders are not required to check for this condition, because doing somight slow the method significantly.

DB_E_ERRORSOCCURREDThis return code indicates that errors occurred while returning data forall columns. To determine what errors occurred, the consumer checks thestatus values.

If this method performs deferred OLE DB accessor validation and thatvalidation takes place before any data is transferred, it can also returnany of the following return codes for the reasons listed in the correspond-ing DBBINDSTATUS values in IAccessor::CreateAccessor:

E_NOINTERFACEDB_E_BADBINDINFODB_E_BADORDINALDB_E_BADSTORAGEFLAGSDB_E_UNSUPPORTEDCONVERSION

IRowset::GetNextRows

This method fetches rows sequentially, remembering the previous position.GetNextRows fetches a sequence of rows. The OLE DB provider maintains anext fetch position that is used in subsequent calls to either GetNextRows orFindNextRow. The next fetch position is changed either by callingGetNextRows, or by calling FindNextRow with a null pBookmark value.

Chapter Four—An OLE DB Primer � 157

Page 177: Learn OLE DB Development With Visual C++ 6.0

Calling FindNextRow with a non-null pBookmark value has no effect on thenext 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 wasfetched in the previous direction; otherwise the next fetch position is the rowfollowing the last row fetched by GetNextRows, or by FindNextRow with anull pBookmark value.

For a newly created rowset, or any time the next fetch position is prior to thefirst row of the rowset, the next fetch position is computed as follows, whereN is the number of rows in the rowset. lRowsOffset can be less than zero onlyif DBPROP_CANSCROLLBACKWARDS is VARIANT_TRUE.

Value of lRowsOffset Next Fetch Position

lRowsOffset > 0 After lRowsOffset

lRowsOffset < 0 After N – abs(lRowsOffset)

lRowsOffset = 0 Before first row if cRows > 0, after last row if cRows < 0

None of the other methods that fetch rows, except for IRowsetFind::Find-NextRow with a null pBookmark value, has any effect on the next fetch posi-tion. However, IRowsetIndex::Seek sets the next fetch position to the rowspecified in the seek criteria, and RestartPosition resets the next fetch posi-tion to the same position as when the rowset is first created.

GetNextRows increments by one the reference count of each row for which itreturns a handle. Thus, if a handle is returned for a row that has alreadybeen 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 beenreturned.

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—GetNextRowsfetches the row normally, returns the row handle, and returns S_OK. How-ever, when the OLE DB consumer calls GetData for the row, the OLE DBprovider returns DBSTATUS_E_CANTCONVERTVALUE as the status for theoffending column.

GetNextRows must always check for the conditions that cause E_INVALID-ARG, E_UNEXPECTED, DB_E_CANTFETCHBACKWARDS, DB_E_CANT-SCROLLBACKWARDS, DB_E_NOTREENTRANT, and DB_E_ROWSNOT-RELEASED before changing the next fetch position. If it returns any othererror besides these, the next fetch position is unknown. For example, the OLEDB provider might have to perform actions that change the next fetch posi-tion in order to determine that the error DB_E_BADSTARTPOSITIONoccurred. When the next fetch position is unknown, the OLE DB consumergenerally calls RestartPosition to return it to a known position.

158 � Chapter Four—An OLE DB Primer

Page 178: Learn OLE DB Development With Visual C++ 6.0

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 fetchingrows. Deleted rows that the OLE DB provider has removed from therowset are not counted in the skip. If this value is zero and cRows contin-ues in the same direction as the previous call to either GetNextRows, orFindNextRow with a null pBookmark value, then the first row fetched willbe the next row after the last one fetched in the previous call. If this valueis zero and cRows reverses direction, then the first row fetched will be thelast one fetched in the previous call.

lRowsOffset can be a negative number only if the value of theDBPROP_CANSCROLLBACKWARDS property is VARIANT_TRUE.There is no guarantee that skipping rows is done efficiently on a sequen-tial rowset. If the data source resides on a remote server, there may beremote support for skipping without transferring the intervening recordsacross the network, but this is not guaranteed.

cRows [in]This parameter indicates the number of rows to fetch. A negative numbermeans to fetch backward. cRows can be a negative number only if thevalue of the DBPROP_CANFETCHBACKWARDS property is VARIANT_TRUE.

If cRows is zero, no rows are fetched; the fetch direction and the nextfetch position are unchanged, and the OLE DB provider performs no pro-cessing, 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 methodreturns S_OK; whether the provider checks for any other errors is OLE DBprovider-specific.

pcRowsObtained [out]This parameter provides a pointer to memory in which to return theactual number of fetched rows. If a warning condition occurs, this num-ber may be less than the number of rows available or requested, and is

Chapter Four—An OLE DB Primer � 159

Page 179: Learn OLE DB Development With Visual C++ 6.0

the number of rows actually fetched before the warning conditionoccurred. If the OLE DB consumer has insufficient permission to fetch allrows, GetNextRows fetches all rows for which the OLE DB consumer hassufficient 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 arrayof handles of the fetched rows. If *prghRows is not a null pointer oninput, it must be a pointer to memory large enough to return the handlesof the requested number of rows. If *prghRows is a null pointer on input,the rowset allocates memory for the row handles and returns the addressto this memory; the OLE DB consumer releases this memory withIMalloc::Free after it releases the row handles. If *prghRows is a nullpointer on input and *pcRowsObtained is zero on output or if the methodfails, 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_OKThis return code means the method succeeded.

DB_S_ENDOFROWSETThis return code indicates that GetNextRows reached the start or the endof the rowset or chapter or the start or end of the range on an indexrowset and could not fetch all requested rows because the countextended beyond the end. The next fetch position is before the start orafter the end of the rowset. The number of rows actually fetched isreturned in *pcRowsObtained; this will be less than cRows.

This can also indicate that the rowset is being populated asynchronouslyand no additional rows are available at this time. To determine whetheradditional rows may be available, the OLE DB consumer should callIDBAsynchStatus::GetStatus or listen for the IDBAsynchNotify::OnStopnotification.

Or, finally, lRowsOffset indicated a position either more than one rowbefore 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_ROWLIMITEXCEEDEDFetching the number of rows specified in cRows would have exceeded thetotal number of active rows supported by the rowset. The number of rowsthat were actually fetched is returned in *pcRowsObtained. This conditioncan occur only when there are more rows available than can be handledby the rowset. Thus, this condition never conflicts with those described inDB_S_ENDOFROWSET and DB_S_STOPLIMITREACHED, both of whichimply that no more rows were available.

160 � Chapter Four—An OLE DB Primer

Page 180: Learn OLE DB Development With Visual C++ 6.0

DB_S_STOPLIMITREACHEDThis return code states that fetching rows required further execution ofthe command, such as when the rowset uses a server-side cursor. Execu-tion has been stopped because a resource limit has been reached. Thenumber of rows that were actually fetched is returned in*pcRowsObtained.

E_FAILThis return code indicates that an OLE DB provider-specific erroroccurred.

E_INVALIDARGThis return code shows that *pcRowsObtained or *prghRows was a nullpointer.

E_OUTOFMEMORYThis return code specifies that the OLE DB provider was unable to allo-cate sufficient memory to complete the request.

E_UNEXPECTEDThis return code shows that ITransaction::Commit or ITransaction::Abortwas called and the object is in a zombie state.

DB_E_BADCHAPTERThis return code means the rowset was chaptered and hChapter wasinvalid, or the rowset was single-chaptered and the specified chapter wasnot the currently open chapter. The OLE DB consumer must use the cur-rently open chapter or release the currently open chapter beforespecifying a new chapter.

DB_E_BADSTARTPOSITIONThis return code states that lRowsOffset indicated a position either morethan one row before the first row of the rowset or more than one rowafter the last row, and the OLE DB provider was a 1.x OLE DB provider.

DB_E_CANCELEDThis return code indicates that fetching rows was canceled during notifi-cation. No rows were fetched.

DB_E_CANTFETCHBACKWARDSThis return code means that cRows was negative and the rowset cannotfetch backward.

DB_E_CANTSCROLLBACKWARDSThis return code shows lRowsOffset was negative and the rowset cannotscroll backward.

DB_E_NOTREENTRANTThis return code specifies that the consumer called the method while itwas processing a notification; it is an error to call this method while pro-cessing the specified DBREASON value.

Chapter Four—An OLE DB Primer � 161

Page 181: Learn OLE DB Development With Visual C++ 6.0

DB_E_ROWSNOTRELEASEDThis return code indicates that the OLE DB provider requires release ofexisting rows before new ones can be fetched.

DB_SEC_E_PERMISSIONDENIEDThis return code states that the OLE DB consumer did not have sufficientpermission to fetch any of the rows and no rows were fetched.

IRowset::ReleaseRows

This method releases rows. ReleaseRows decreases the reference count on thespecified 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 calledthree times. Furthermore, if a row handle is duplicated in rghRows, the corre-sponding row will have its reference count decremented by one for each timeit appears in the array.

If an OLE DB provider doesn’t support exact reference counts on rows, itshould return a reference count of 1 while the row is active. OLE DB consum-ers should be aware of this behavior and should use the returned referencecount for debugging purposes only.

OLE DB consumers should not rely on the returned reference count to indi-cate whether the row would survive another release. In this case, an OLE DBprovider may choose to return S_OK for any and all calls to ReleaseRowsuntil the rowset itself is released. If an OLE DB consumer calls ReleaseRowson a row with pending changes, the row remains valid and ReleaseRowsreturns DBROWSTATUS_S_PENDINGCHANGES in rgRowStatus.

When the reference count for a row decreases to zero, the row is released:

� Subject to the rules of the current transaction, the rowset is free to discardany resources used by a row that has a reference count of zero. For exam-ple, these might include memory, locks, and original values. When therowset actually discards these resources is OLE DB provider-specific.

� If the row has pending changes, the row still remains valid even though itsreference count is zero. OLE DB consumers should not use the handle of arow that has a reference count of zero, even though the handle might stillbe valid. If IRowsetUpdate::Update is called to transmit pending changesfor a row with a reference count of zero to the data source, it transmitsthe changes of the row and releases the row and its resources if theupdate succeeds. If IRowsetUpdate::Undo is called to undo the pendingchanges for a row with a reference count of zero, it releases the row andits resources.

� Pending changes are not lost when releasing a reference count obtainedfrom GetRowsAt. A provider may take its own reference count on the rowhandle or store the row internally until pending changes are committed ordismissed (using Update or Undo).

162 � Chapter Four—An OLE DB Primer

Page 182: Learn OLE DB Development With Visual C++ 6.0

After a row is released, methods called with the handle to that row returnDB_E_BADROWHANDLE if the row has pending changes. After the pendingchanges are transmitted to the data source, methods might continue to returnthis error. However, the OLE DB provider might have an implementation thatrecycles row handles and thereafter cannot detect the misuse. Because pro-vider behavior varies, OLE DB consumers should not use the handles ofreleased rows.

If ReleaseRows encounters an error while decrementing the reference countof a row or releasing the row, it sets the corresponding element in rgRow-

Status to the appropriate DBROWSTATUS value and continues processing.This method can be called while the rowset is in a zombie state to allow theOLE DB consumer to clean up after a transaction has been committed oraborted.

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. Therow handles need not form a logical cluster; they may have beenobtained at separate times and need not be for contiguous underlyingrows. They must belong to the current thread. Row handles are decre-mented by one reference count for each time they appear in the array.

rgRowOptions [in]This parameter provides an array of cRows elements containing bitmasksindicating additional options to be specified when releasing a row. Thisparameter 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 returnthe new reference count of each row. If rgRefCounts is a null pointer, nocounts are returned. The OLE DB consumer allocates but is not requiredto initialize memory for this array and passes the address of this memoryto the OLE DB provider. The OLE DB provider returns the referencecounts in the array.

Chapter Four—An OLE DB Primer � 163

Page 183: Learn OLE DB Development With Visual C++ 6.0

rgRowStatus [out]This parameter gives an array with cRows elements in which to returnvalues indicating the status of each row specified in rghRows. If no errorsor warnings occur while releasing a row, the corresponding element ofrgRowStatus is set to DBROWSTATUS_S_OK. If an error or warning occurswhile releasing a row, the corresponding element is set as specified inDB_S_ERRORSOCCURRED. The OLE DB consumer allocates memory forthis array. If rgRowStatus is a null pointer, no row statuses are returned.

It has the following return codes:

S_OKThis return code means the method succeeded. All rows were successfullyreleased. The following values can be returned in rgRowStatus:

� The row was successfully released. The corresponding element ofrgRowStatus contains DBROWSTATUS_S_OK.

� A row had a pending change. The row was released and the corre-sponding element of rgRowStatus contains DBROWSTATUS_S_PENDINGCHANGES.

DB_S_ERRORSOCCURREDThis return code shows that an error occurred while releasing a row, butat least one row was successfully released. Successes and warnings canoccur for the reasons listed under S_OK. The following errors can occur:

� A row handle was invalid. The row was not released and the corre-sponding element of rgRowStatus contains DBROWSTATUS_E_INVALID.

� The OLE DB consumer encountered a recoverable, OLE DB pro-vider-specific error, such as an RPC failure when transmitting thechange to a remote server. The corresponding element of rgRowStatuscontains DBROWSTATUS_E_FAIL.

E_FAILThis return code specifies that a provider-specific error occurred.

E_INVALIDARGThis return code indicates that rghRows was a null pointer and cRows wasnot equal to zero.

DB_E_ERRORSOCCURREDThis return code shows errors occurring while releasing all of the rows.Errors can occur for the reasons listed under DB_S_ERRORSOCCURRED.

DB_E_NOTREENTRANTThis return code means that the OLE DB consumer called this methodwhile it was processing a notification; it is an error to call this methodwhile processing the specified DBREASON value.

164 � Chapter Four—An OLE DB Primer

Page 184: Learn OLE DB Development With Visual C++ 6.0

IRowset::RestartPosition

This method repositions the next fetch position used by GetNextRows orFindNextRow to its initial position; that is, its position when the rowset wasfirst 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 isforward-only, the procedure may be reexecuted in order to satisfy the call toRestartPosition. This may cause other side effects. Additionally, if the storedprocedure has been changed, the rowset may have a different schema. If therowset is able to restart the next fetch position without reexecuting the pro-cedure, RestartPosition should not reexecute it.

How expensive RestartPosition is depends on the OLE DB provider, the rowsetcharacteristics, and the tables underlying the rowset. If the rowset supportsIRowsetLocate, then RestartPosition is always an inexpensive operation.

If the rowset is sequential, then RestartPosition might require reexecution ofthe 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 sin-gle table are not expensive to restart, but rowsets built by joining two ormore tables are expensive to restart. If the OLE DB provider reexecutes thecommand to restart the next fetch position, then the new rowset might returna different set of rows, differently ordered columns, and, in extreme cases, adifferent set of columns. Reexecution of a command by RestartPosition doesnot 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 success-fully processing a call to RestartPosition.

An OLE DB consumer can determine whether an OLE DB provider can quicklyrestart the next fetch position by attempting to set DBPROP_QUICKRESTARTto VARIANT_TRUE. Setting this property to VARIANT_TRUE does not guaran-tee that the rowset can be quickly restarted because the OLE DB provider isnot required to honor the property. This behavior is necessary because theOLE DB provider cannot evaluate the command at the time the property isset. For example, the OLE DB consumer can set DBPROP_QUICKRESTART toVARIANT_TRUE and then change the command text.

In implementations that require reexecution of a command to reposition thenext fetch position to its initial position, the OLE DB provider is responsiblefor caching all parameters required by the command.

If RestartPosition returns DB_S_COLUMNSCHANGED and the OLE DB con-sumer subsequently calls methods in IColumnsInfo or IColumnsRowset, thesemethods must reflect the new metadata. Existing rowset OLE DB accessorsare not updated to reflect the new metadata. That is, IAccessor::GetBindings

Chapter Four—An OLE DB Primer � 165

Page 185: Learn OLE DB Development With Visual C++ 6.0

returns exactly the same information it would have returned beforeRestartPosition was called. If such OLE DB accessors are subsequently used,such as in a call to GetData, the OLE DB provider must revalidate them. Ifnone of the columns bound by the OLE DB accessor have changed, the OLEDB accessor can be used successfully. If any of the columns have changed, theappropriate 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_OKThis return code means the method succeeded. The OLE DB provider didnot have to reexecute the command, either because the rowset supportspositioning on the first row without reexecuting the command or becausethe rowset is already positioned on the first row.

DB_S_COLUMNSCHANGEDThis return code states that the order of the columns was not specified inthe object that created the rowset. The OLE DB provider had to reexecutethe command to reposition the next fetch position to its initial position,and the order of the columns changed. The OLE DB provider had toreexecute the command to reposition the next fetch position to its initialposition, and columns were added or removed from the rowset. This isgenerally due to a change in the underlying schema and is extremelyuncommon.

This return code takes precedence over DB_S_COMMANDREEXECUTED.That is, if the conditions described here and in those described inDB_S_COMMANDREEXECUTED both occur, the OLE DB provider returnsthis code. A change to the columns generally implies that the commandwas reexecuted.

DB_S_COMMANDREEXECUTEDThis return code shows that the command associated with this rowsetwas reexecuted. If the properties DBPROP_OWNINSERT and DBPROP_OWNUPDATEDELETE are VARIANT_TRUE, then the OLE DB consumerwill see its own changes. If the properties DBPROP_OWNINSERT orDBPROP_OWNUPDATEDELETE are VARIANT_FALSE, then the rowsetmay see its changes. The order of the columns remains unchanged.

166 � Chapter Four—An OLE DB Primer

Page 186: Learn OLE DB Development With Visual C++ 6.0

E_FAILThis return code specifies that an OLE DB provider-specific erroroccurred.

E_UNEXPECTEDThis return code states that ITransaction::Commit or ITransaction::Abortwas called and the object is in a zombie state.

DB_E_BADCHAPTERThis return code indicates that the rowset was chaptered and hChapterwas invalid, or that the rowset was single-chaptered and the specifiedchapter was not the currently open chapter. The OLE DB consumer mustuse the currently open chapter or release the currently open chapterbefore specifying a new chapter.

DB_E_CANCELEDThis return code means that RestartPosition was canceled during notifica-tion. The next fetch position remains unmodified.

DB_E_CANNOTRESTARTThis 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_NOTREENTRANTThis return code specifies that the OLE DB provider called a method fromIRowsetNotify in the OLE DB consumer and the method has not yetreturned.

DB_E_ROWSNOTRELEASEDThis return code indicates that the provider requires release of existingrows before restarting because the rowset will be regenerated. This maybe required even if the OLE DB provider supports a value of VARIANT_TRUE for DBPROP_CANHOLDROWS.

DB_SEC_E_PERMISSIONDENIEDThis return code states that the consumer did not have sufficient permis-sion to reposition the next fetch position.

IRowsetInfo

IRowsetInfo provides information about a rowset. All rowsets must imple-ment IRowsetInfo.

When an OLE DB consumer gets an interface pointer on a rowset, its firststep usually is to determine the rowset’s capabilities using IUnknown::QueryInterface. It may call GetProperties to learn the properties of the rowsetthat do not show up as distinct interfaces, such as the maximum number ofactive rows and how many rows can have pending updates at the same time.

IRowsetInfo also provides methods for retrieving objects associated with therowset. GetSpecification gets the object (command or session) that created

Chapter Four—An OLE DB Primer � 167

Page 187: Learn OLE DB Development With Visual C++ 6.0

the rowset. GetReferencedRowset gets the rowset that is referenced by abookmark-valued column.

Method Description

GetProperties This method returns the current setting of all propertiessupported by the rowset.

GetReferencedRowset This method returns an interface pointer to the rowset towhich a bookmark applies.

GetSpecification 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 therowset. The method makes no logical change to the state of the object. Eventhough IDBProperties::GetPropertyInfo lists a property as being supported bythe OLE DB provider, GetProperties will not return a value for it if it does notapply to the current circumstances. For example, the OLE DB provider’s abil-ity to support the property might be affected by the current transaction or thecurrent command text.

IRowsetInfo::GetProperties might return a different value for a property thandoes ICommandProperties::GetProperties. For example, if an OLE DB con-sumer requests ordered bookmarks if they are possible, it calls ICommand-Properties::SetProperties to set the value of DBPROP_ORDERED-BOOKMARKS to VARIANT_TRUE and specifies a dwOptions value ofDBPROPOPTIONS_OPTIONAL. If the OLE DB provider cannot determinewhether this is possible, ICommandProperties::GetProperties returns a valueof VARIANT_TRUE and a dwOptions of DBPROPOPTIONS_OPTIONAL for thisproperty. If the OLE DB provider determines during execution that orderedbookmarks are not possible, IRowsetInfo::GetProperties returns a value ofVARIANT_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 inrgPropertyIDSets. If cPropertyIDSets is zero, the OLE DB provider ignoresrgPropertyIDSets and returns the values of all properties in the Rowsetproperty group for which values exist, including properties for which

168 � Chapter Four—An OLE DB Primer

Page 188: Learn OLE DB Development With Visual C++ 6.0

values were not set but for which defaults exist, and also including prop-erties for which values were set automatically because values were set forother properties.

If cPropertyIDSets is not zero, the OLE DB provider returns the values ofthe requested properties. If a property is not supported, the returnedvalue of dwStatus in the returned DBPROP structure for that property isDBPROPSTATUS_NOTSUPPORTED and the value of dwOptions isundefined.

rgPropertyIDSets [in]This parameter shows an array of cPropertyIDSets DBPROPIDSET struc-tures. The properties specified in these structures must belong to theRowset property group. The OLE DB provider returns the values of theproperties specified in these structures. If cPropertyIDSets is zero, thenthis parameter is ignored.

pcPropertySets[out]This parameter provides a pointer to memory in which to return the num-ber of DBPROPSET structures returned in *prgPropertySets. If cProperty-

IDSets is zero, *pcPropertySets is the total number of property sets forwhich the OLE DB provider supports at least one property in the Rowsetproperty group. If cPropertyIDSets is greater than zero, *pcPropertySets isset to cPropertyIDSets. If an error other than DB_E_ERRORSOCCURREDoccurs, *pcPropertySets is set to zero.

prgPropertySets [out]This parameter specifies a pointer to memory in which to return an arrayof DBPROPSET structures. If cPropertyIDSets is zero, then one structure isreturned for each property set that contains at least one property belong-ing to the Rowset property group. If cPropertyIDSets is not zero, then onestructure is returned for each property set specified in rgPropertyIDSets.If cPropertyIDSets is not zero, the DBPROPSET structures in *prgProperty-

Sets are returned in the same order as the DBPROPIDSET structures inrgPropertyIDSets; that is, for corresponding elements of each array, theguidPropertySet elements are the same. If cPropertyIDs, in an element ofrgPropertyIDSets, is not zero, the DBPROP structures in the correspondingelement of *prgPropertySets are returned in the same order as theDBPROPID values in rgPropertyIDs.

Thus, in the case where no column properties are specified in rgProperty-

IDSets, corresponding elements of the input rgPropertyIDs and thereturned rgProperties have the same property ID. However, if a columnproperty is requested in rgPropertyIDSets, multiple properties may bereturned, one for each column, in rgProperties. In this case, correspondingelements of rgPropertyIDs and rgProperties will not have the same prop-erty ID, and rgProperties will contain more elements than rgPropertyIDs.

Chapter Four—An OLE DB Primer � 169

Page 189: Learn OLE DB Development With Visual C++ 6.0

The OLE DB provider allocates memory for the structures and returns theaddress to this memory; the OLE DB consumer releases this memory withIMalloc::Free when it no longer needs the structures. Before callingIMalloc::Free for *prgPropertySets, the OLE DB consumer should callIMalloc::Free for the rgProperties element within each element of*prgPropertySets. If *pcPropertySets is zero on output or if an error otherthan DB_E_ERRORSOCCURRED occurs, the OLE DB provider does notallocate any memory and ensures that *prgPropertySets is a null pointeron output.

It has the following return codes:

S_OKThis return code means the method succeeded. In all DBPROP structuresreturned by the method, dwStatus is set to DBPROPSTATUS_OK.

DB_S_ERRORSOCCURREDThis return code provides that no value was returned for one or moreproperties. The OLE DB consumer checks dwStatus in the DBPROP struc-ture to determine the properties for which values were not returned.GetProperties can fail to return properties for a number of reasons,including:

� The property was not supported by the OLE DB provider.

� The property was not in the Rowset property group.

� The property set was not supported by the OLE DB provider. IfcPropertyIDs in the DBPROPIDSET structure for the property set waszero, the OLE DB provider cannot set dwStatus in the DBPROP struc-ture because it does not know the IDs of any properties in theproperty set. Instead, it sets cProperties to zero in the DBPROPSETstructure returned for the property set.

E_FAILThis return code states that an OLE DB provider-specific error occurred.

E_INVALIDARGThis return code shows that cPropertyIDSets was not equal to zero andrgPropertyIDSets was a null pointer. It alternatively can mean that*pcPropertySets or *prgPropertySets was a null pointer. Or this return codecan indicate that an element of rgPropertyIDSets, cPropertyIDs, was notzero and rgPropertyIDs was a null pointer.

E_OUTOFMEMORYThis return code states that the OLE DB provider was unable to allocatesufficient memory in which to return the DBPROPSET or DBPROPstructures.

E_UNEXPECTEDThis return code means that ITransaction::Commit or ITransaction::Abortwas called and the object is in a zombie state.

170 � Chapter Four—An OLE DB Primer

Page 190: Learn OLE DB Development With Visual C++ 6.0

DB_E_ERRORSOCCURREDThis return code specifies that values were not returned for any proper-ties. The OLE DB provider allocates memory for *prgPropertySets and theOLE DB consumer checks dwStatus in the DBPROP structures to deter-mine why properties were not returned. The OLE DB consumer frees thismemory when it no longer needs the information.

IRowsetInfo::GetReferencedRowset

This method returns an interface pointer to the rowset to which a bookmarkor chapter applies. The method makes no logical change to the state of thecurrent 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 geneal-ogy relations on a People table), or they can apply to a different rowset (anexample 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 getthe 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 ofrequired interfaces on the resulting rowset, and the method fails(E_NOINTERFACE) if that interface cannot be supported on the resultingrowset.

ppReferencedRowset [out]This parameter provides a pointer to memory in which to return anIUnknown interface pointer on the rowset that interprets values from thiscolumn. If this is not a reference column, *ppReferencedRowset is set to anull pointer.

It has the following return codes:

S_OKThis return code means the method succeeded.

E_FAILThis return code shows that an OLE DB provider-specific error occurred.

Chapter Four—An OLE DB Primer � 171

Page 191: Learn OLE DB Development With Visual C++ 6.0

E_INVALIDARGThis return code indicates that *ppReferencedRowset was a null pointer.

E_NOINTERFACEThis return code states that the interface specified in riid was not implic-itly or explicitly specified as a rowset property of the rowset. The currentrowset remains valid.

E_UNEXPECTEDThis return code specifies that ITransaction::Commit orITransaction::Abort was called and the object is in a zombie state.

DB_E_BADORDINALThis return code shows that the column specified by iOrdinal did notexist.

DB_E_NOTAREFERENCECOLUMNThis return code provides that the column specified by iOrdinal did notcontain bookmarks or chapter values.

DB_E_NOTREENTRANTThis return code means that the OLE DB provider called a method fromIRowsetNotify in the OLE DB consumer that had not yet returned, andthe OLE DB provider does not support reentrancy in this method.

DB_E_NOTSUPPORTEDThis return code indicates that the interface was exposed on an indexrowset.

IRowsetInfo::GetSpecification

This method returns an interface pointer on the object (Command orSession) that created this rowset. The method makes no logical change to thestate of the current rowset.

GetSpecification returns an interface pointer on the object that created therowset. If the rowset was created by ICommand::Execute, this object is acommand. If the rowset was created by IOpenRowset::OpenRowset, thisobject is a session.

If the object is not a command, then it must specify the contents of therowset. That is, it must expose interfaces that can be used to modify the con-tents of the rowset before the rowset is created, or be used to gain additionalinformation about the rowset. If the object cannot expose such interfaces,GetSpecification should return a null pointer in *ppSpecification. In a simpleOLE DB provider, such as an OLE DB provider that creates a rowset over afixed set of data, there might not be an object that created the rowset; in thiscase, GetSpecification returns a null pointer in *ppSpecification.

172 � Chapter Four—An OLE DB Primer

Page 192: Learn OLE DB Development With Visual C++ 6.0

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 inter-face pointer. If the OLE DB provider does not have an object that createdthe rowset, it sets *ppSpecification to a null pointer and returns S_FALSE.If GetSpecification fails, it must attempt to set *ppSpecification to a nullpointer.

It has the following return codes:

S_OKThis return code means the method succeeded.

S_FALSEThis return code shows that the OLE DB provider does not have an objectthat created the rowset.

E_FAILThis return code indicates that an OLE DB provider-specific erroroccurred.

E_INVALIDARGThis return code states that *ppSpecification was a null pointer.

E_NOINTERFACEThis return code provides that the object that created this rowset did notsupport the interface specified in riid.

E_UNEXPECTEDThis return code means that ITransaction::Commit or ITransaction::Abortwas called and the object is in a zombie state.

DB_E_NOTREENTRANTThis return code shows that the OLE DB provider called a method fromIRowsetNotify in the OLE DB consumer that had not yet returned, andOLE DB provider does not support reentrancy in this method.

IRowsetChange

The methods in IRowsetChange are used to update the values of columns inexisting rows, delete existing rows, and insert new rows. IRowsetChangerequires IAccessor and IRowset.

Chapter Four—An OLE DB Primer � 173

Page 193: Learn OLE DB Development With Visual C++ 6.0

Rowsets implement IRowsetChange if they support updating, deleting, orinserting rows. They are not required to support all three, but must supportat least one of these operations to support IRowsetChange. The rowsetreports which operations it supports through the DBPROP_UPDATABILITYproperty.

The OLE DB consumer calls methods in IRowsetChange to modify rows asfollows:

� SetData sets column data in an existing row.

� DeleteRows deletes existing rows.

� InsertRow creates a new row and sets initial values.

The SetData method and the InsertRow method require the use of an OLE DBaccessor. SetData and InsertRow can fail for a number of reasons. The mostcommon of these is that new data values do not meet the schema or integrityconstraints of the column. Furthermore, rowsets can have row-by-row andcolumn-by-column access permissions that override the general permissionsof the table or column.

If IRowsetUpdate is exposed on the rowset, then changes made throughIRowsetChange are buffered in the rowset and not transmitted to the datasource until IRowsetUpdate::Update is called; this is known as delayed update

mode. If IRowsetUpdate is not exposed on the rowset, then changes madethrough IRowsetChange are immediately transmitted to the data source; thisis known as immediate update mode.

Method Description

DeleteRows This method deletes rows.

InsertRow This method creates and initializes a new row.

SetData This method sets data in one or more columns in a row.

IRowsetChange::DeleteRows

This method deletes rows. In delayed update mode, DeleteRows marks rowsfor deletion, rather than actually deleting them. Rows with pending deletescannot be used in any methods except IRowsetRefresh::GetLastVisibleData,IRowsetUpdate::Undo, IRowsetUpdate::Update, IRowsetUpdate::GetOriginal-Data, and IRowset::ReleaseRows. The deletion is not transmitted to the datasource until Update is called.

In immediate update mode, DeleteRows transmits deletions to the datasource immediately. After a deletion has been transmitted to the data source,it cannot be undone. The row cannot be used with any method exceptReleaseRows. Neither DeleteRows nor Update releases rows after transmit-ting deletions to the data source. The OLE DB consumer must release the rowwith ReleaseRows.

174 � Chapter Four—An OLE DB Primer

Page 194: Learn OLE DB Development With Visual C++ 6.0

If DeleteRows is called for a row with a pending insert, the row is placed inthe same state as a row for which a deletion has been transmitted to the datasource. 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 methodexcept ReleaseRows, which must be called to release it.

If an error occurs while deleting a row, DeleteRows continues deleting theother rows in rghRows and returns DB_S_ERRORSOCCURRED or DB_E_ERRORSOCCURRED. It returns status information about each row inrgRowStatus. If the DBPROP_ROWRESTRICT property is VARIANT_TRUE, theOLE DB consumer may have permission to delete some rows but not otherrows.

It has the following syntax:

HRESULT DeleteRows (HCHAPTER hChapter,ULONG cRows,const HROW rghRows[],DBROWSTATUS 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 fol-lows. If the row handle is valid, it is OLE DB provider-specific whetherthe returned row status information for each row or a single instance ofthe row is set to DBROWSTATUS_S_OK. If the row handle is invalid, therow status information for each occurrence of the row contains theappropriate error.

rgRowStatus [out]This parameter provides an array with cRows elements in which to returnvalues indicating the status of each row specified in rghRows. If no errorsor warnings occur while deleting a row, the corresponding element ofrgRowStatus is set to DBROWSTATUS_S_OK. If a warning occurs whiledeleting a row, the corresponding element is set as specified in S_OK. Ifan error occurs while deleting a row, the corresponding element is set asspecified in DB_S_ERRORSOCCURRED. The OLE DB consumer allocatesmemory for this array. If rgRowStatus is a null pointer, no row statuses arereturned.

Chapter Four—An OLE DB Primer � 175

Page 195: Learn OLE DB Development With Visual C++ 6.0

It has the following return codes:

S_OKThis return code means the method succeeded, and all rows were suc-cessfully deleted. The following values can be returned in rgRowStatus:

� The row was successfully deleted and no warning conditions occurred.The corresponding element of rgRowStatus contains DBROWSTATUS_S_OK.

� The rowset was in immediate update mode, and deleting a single rowcaused more than one row to be deleted in the data source. The corre-sponding element of rgRowStatus contains DBROWSTATUS_S_MULTIPLECHANGES.

DB_S_ERRORSOCCURREDThis return code shows that an error occurred while deleting a row, but atleast one row was successfully deleted. Successes and warnings can occurfor the reasons listed under S_OK. The following errors can occur:

� An element of rghRows was invalid or was a row handle to which thecurrent thread does not have access rights. The corresponding elementof rgRowStatus contains DBROWSTATUS_E_INVALID.

� Deletion of a row was canceled during notification. The row was notdeleted and the corresponding element of rgRowStatus containsDBROWSTATUS_E_CANCELED.

� An element of rghRows referred to a row with a pending delete or forwhich a deletion had been transmitted to the data source. The corre-sponding element of rgRowStatus contains DBROWSTATUS_E_DELETED.

� Deleting a row referred to by an element of rghRows violated theintegrity constraints for the column or table. The corresponding ele-ment of rgRowStatus contains DBROWSTATUS_E_INTEGRITY-VIOLATION.

� The rowset was in immediate update mode and the row was notdeleted due to reaching a limit on the server, such as a query execu-tion timing out. The error in the corresponding element of rgRow-

Status contains DBROWSTATUS_E_LIMITREACHED.

� Deleting a row would exceed the limit for pending changes specifiedby the rowset property DBPROP_MAXPENDINGROWS. The corre-sponding element of rgRowStatus contains DBROWSTATUS_E_MAXPENDCHANGESEXCEEDED.

� DBPROP_CHANGEINSERTEDROWS was VARIANT_FALSE and an ele-ment of rghRows referred to a row for which the insertion has beentransmitted to the data source. The corresponding element ofrgRowStatus contains DBROWSTATUS_E_NEWLYINSERTED.

176 � Chapter Four—An OLE DB Primer

Page 196: Learn OLE DB Development With Visual C++ 6.0

� The OLE DB consumer did not have sufficient permission to delete arow. This error can be returned only if the value of the DBPROP_ROWRESTRICT property is VARIANT_TRUE. The corresponding ele-ment of rgRowStatus contains DBROWSTATUS_E_PERMISSION-DENIED. If the rowset is in delayed update mode, this error might notbe returned until IRowsetUpdate::Update is called.

� The OLE DB consumer encountered a recoverable OLE DB provider-specific error, such as an RPC failure when transmitting the change toa remote server. The corresponding element of rgRowStatus containsDBROWSTATUS_E_FAIL.

E_FAILThis return code shows that an OLE DB provider-specific error occurred.

E_INVALIDARGThis return code states that rghRows was a null pointer and cRows wasgreater than or equal to one.

E_UNEXPECTEDThis return code specifies that ITransaction::Commit or ITransaction::Abort was called and the object is in a zombie state.

DB_E_BADCHAPTERThis return code indicates that the rowset was chaptered and hChapterwas invalid, or the rowset was single-chaptered and the specified chapterwas not the currently open chapter. The OLE DB consumer must use thecurrently open chapter or release the currently open chapter before speci-fying a new chapter.

DB_E_ERRORSOCCURREDThis return code means that errors occurred while deleting all of therows. Errors can occur for the reasons listed under DB_S_ERRORS-OCCURRED.

DB_E_NOTREENTRANTThis return code shows that the OLE DB consumer called this methodwhile it was processing a notification, and it is an error to call thismethod while processing the specified DBREASON value.

DB_E_NOTSUPPORTEDThis 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 rowand initializes its columns. If phRow is not a null pointer, it then returns thehandle of this row to the OLE DB consumer and sets its reference count toone. In delayed update mode, the row is created locally to the rowset and istransmitted to the data source only when IRowsetUpdate::Update is called. In

Chapter Four—An OLE DB Primer � 177

Page 197: Learn OLE DB Development With Visual C++ 6.0

immediate update mode, the row is immediately transmitted to the datasource.

To the OLE DB consumer, newly inserted rows are almost indistinguishablefrom other rows. For example, they can be deleted with DeleteRows andupdated with SetData. However, methods that fetch rows might not be ableto return them. Furthermore, they might not contain the correct values forcomputed columns, including bookmark columns on some OLE DB providers.

The DBPROP_COLUMNRESTRICT and DBPROP_ROWRESTRICT propertiesaffect how security is enforced and how security errors are returned. IfDBPROP_COLUMNRESTRICT is VARIANT_TRUE, the OLE DB consumermight not have write permission on some columns. If the OLE DB consumerattempts to write to these columns, InsertRows returns a column status ofDBSTATUS_E_PERMISSIONDENIED and a return code of DB_S_ERRORS-OCCURRED. If the DBPROP_ROWRESTRICT property is VARIANT_TRUE, theOLE DB consumer might not have permission to insert some rows. If the OLEDB consumer attempts to insert one of these rows, InsertRows returns a codeof DB_SEC_E_PERMISSIONDENIED and no new row is created.

When a row is created, initialization proceeds in an orderly fashion:

� The OLE DB provider sets all columns to their default values. If there is nodefault value and the column is nullable, it sets the column to NULL. Ifthe column is non-nullable, it sets the column status to DBSTATUS_E_UNAVAILABLE. If the OLE DB provider is unable or unwilling to determinethe default value of a column or whether that column is nullable, it setsthe column status to DBSTATUS_E_UNAVAILABLE; the OLE DB providermight be unwilling to determine default values and nullability if doing sorequires a call to the data source.

If the column status is DBSTATUS_E_UNAVAILABLE, the OLE DB con-sumer can still send this value to the data source to use the default. In thiscase, the default is available after the insertion is transmitted to the datasource. To see the default, the OLE DB consumer must call GetLastVisible-Data or RefreshVisibleData. However, if there is no default for the columnand it is non-nullable, this will cause a schema violation.

� The OLE DB provider then calls IRowsetNotify::OnRowChange withDBREASON_ROW_INSERT if any OLE DB consumer of the rowset is usingnotifications. This serves as a hook allowing, among other things, morecomplex nondeclarative default values to be set in the row.

� InsertRow does not further modify the column values if the OLE DBaccessor 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 notifica-tions like it does when setting data in SetData. This prevents, for example,

178 � Chapter Four—An OLE DB Primer

Page 198: Learn OLE DB Development With Visual C++ 6.0

DBREASON_COLUMN_SET notifications from being generated for a rowthat is not yet properly constructed.

The OLE DB provider is not required to compute the value of computedcolumns. If the OLE DB provider does not compute the value of these col-umns but lets the data source do so, then the computed value is notavailable until after the change is transmitted to the data source—that is,after InsertRow is called in immediate update mode or after IRowset-Update::Update if InsertRow is called in delayed update mode. To retrievethe computed value, the OLE DB consumer calls RefreshVisibleData orGetLastVisibleData in IRowsetRefresh. Note that bookmark columns areoften computed, such as when the bookmark is the primary key or is aROWID assigned by the data source.

Domain and schema validation is enforced as it is with SetData. If InsertRowreturns an error, it does not create a new row. It is not recommended to usenull accessors because some OLE DB providers may not be able to determinedefault values and may not be able to identify a row inserted with all defaultvalues.

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. IfhAccessor is a null accessor (that is, an OLE DB accessor for whichcBindings in IAccessor::CreateAccessor was zero), then pData is ignoredand the rows are initialized as specified above. Thus, the role of a nullaccessor is to construct a default row; it is a convenient way for an OLEDB consumer to obtain a handle for a new row without having to set anyvalues in that row initially.

pData [in]This parameter provides a pointer to memory containing the new datavalues, 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 han-dle of the new row. If this is a null pointer, then no reference count is

Chapter Four—An OLE DB Primer � 179

Page 199: Learn OLE DB Development With Visual C++ 6.0

held on the row. OLE DB consumers should set this to null if they do notrequire the ability to make further changes to, or retrieve data from, thenewly inserted row. Whether or not default or computed values from theserver are available when calling GetData for this row handle depends onthe 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 returnedin *phRow, does not release the row until the change is transmitted to the datasource. If DBPROP_CANHOLDROWS is VARIANT_FALSE and the rowset isin deferred update mode, then in addition to freeing any reference counts onthe row, the OLE DB consumer must call IRowsetUpdate::Update in order totransmit the pending change to the data source before attempting to insert orretrieve any additional rows.

It has the following return codes:

S_OKThis return code means the method succeeded. The status of all columnsbound by the accessor is set to DBSTATUS_S_OK or DBSTATUS_S_ISNULL.

DB_S_ERRORSOCCURREDThis return code shows that an error occurred while setting data for oneor more columns, but data was successfully set for at least one column.To determine the columns for which values were invalid, the consumerchecks the status values.

E_FAILThis return code indicates that an OLE DB provider-specific erroroccurred.

E_INVALIDARGThis return code specifies that pData was a null pointer and hAccessor wasnot a null accessor.

E_OUTOFMEMORYThis return code states that the OLE DB provider was unable to allocatesufficient memory in which to instantiate the row.

E_UNEXPECTEDThis return code means ITransaction::Commit or ITransaction::Abort wascalled and the object is in a zombie state.

DB_E_ABORTLIMITREACHEDThis return code shows the rowset was in immediate update mode andthe row was not inserted due to reaching a limit on the server, such as aquery execution timing out.

180 � Chapter Four—An OLE DB Primer

Page 200: Learn OLE DB Development With Visual C++ 6.0

DB_E_BADACCESSORHANDLEThis return code indicates that hAccessor was invalid.

DB_E_BADACCESSORTYPEThis return code provides that the specified OLE DB accessor was not arow accessor or was a reference accessor.

DB_E_BADCHAPTERThis return code shows that the rowset was chaptered and hChapter wasinvalid, or the rowset was single-chaptered and the specified chapter wasnot the currently open chapter. The OLE DB consumer must use the cur-rently open chapter or release the currently open chapter beforespecifying a new chapter.

DB_E_CANCELEDThis return code specifies that the insertion was canceled during notifica-tion. The row was not inserted.

DB_E_CANTCONVERTVALUEThis return code means that the data value for one or more columnscouldn’t be converted for reasons other than sign mismatch or data over-flow, and the OLE DB provider was unable to determine which columnscouldn’t be converted. OLE DB providers that can detect which columnscould not be converted return DB_S_ERRORSOCCURRED and set the sta-tus flag for those columns to DBSTATUS_E_CANTCONVERTVALUE.

DB_E_DATAOVERFLOWThis return code shows that conversion failed because the data value forone or more columns overflowed the type used by the OLE DB providerand the OLE DB provider was unable to determine which columns causedthe overflow. OLE DB providers that can detect which columns caused theoverflow return DB_S_ERRORSOCCURRED and set the status flag for thecolumns in violation to DBSTATUS_E_DATAOVERFLOW.

DB_E_ERRORSOCCURREDThis return code indicates that an error occurred while setting data forone or more columns and data was not successfully set for any columns.To determine the columns for which values were invalid, the OLE DB con-sumer checks the status values.

DB_E_INTEGRITYVIOLATIONThis return code specifies that the data violated the integrity constraintsfor one or more columns of the rowset and the OLE DB provider wasunable to determine which columns violated the integrity constraints.OLE DB providers that can detect which columns violated the integrityconstraints return DB_S_ERRORSOCCURRED and set the status flag forthe columns in violation to DBSTATUS_E_INTEGRITYVIOLATION.

DB_E_MAXPENDCHANGESEXCEEDEDThis return code indicates that the number of rows that have pending

Chapter Four—An OLE DB Primer � 181

Page 201: Learn OLE DB Development With Visual C++ 6.0

changes has exceeded the limit specified by the DBPROP_MAXPENDING-ROWS property.

DB_E_NOTREENTRANTThis return code means the OLE DB provider called a method fromIRowsetNotify in the OLE DB consumer and the method has not yetreturned.

DB_E_NOTSUPPORTEDThis return code shows that the OLE DB provider does not support thismethod.

DB_E_ROWLIMITEXCEEDEDThis return code states that creating another row would have exceededthe total number of active rows supported by the rowset.

DB_E_ROWSNOTRELEASEDThis return code indicates that the OLE DB consumer attempted to inserta new row before releasing previously retrieved row handles or transmit-ting pending changes to the data source, and DBPROP_CANHOLDROWSis VARIANT_FALSE.

DB_SEC_E_PERMISSIONDENIEDThis return code provides that the OLE DB consumer did not have suffi-cient permission to insert a new row. This error can be returned only ifthe value of the DBPROP_ROWRESTRICT property is VARIANT_TRUE. Ifthe rowset is in delayed update mode, this error might not be returneduntil IRowsetUpdate::Update is called.

If this method performs deferred accessor validation and that validationtakes place before any data is transferred, it can also return any of thefollowing return codes for the applicable reasons listed in the correspond-ing DBBINDSTATUS values in IAccessor::CreateAccessor:

E_NOINTERFACEDB_E_BADBINDINFODB_E_BADORDINALDB_E_BADSTORAGEFLAGSDB_E_UNSUPPORTEDCONVERSION

IRowsetChange::SetData

This method sets data values in one or more columns in a row. SetData setsdata values in one or more columns in a row.

In delayed update mode, these changes are buffered locally in the rowset andare transmitted to the data source only when IRowsetUpdate::Update iscalled. In immediate update mode, the changes are immediately transmittedto the data source.

If a computed column depends on a column that is changed with SetData, theOLE DB provider is not required to compute the new value of the computed

182 � Chapter Four—An OLE DB Primer

Page 202: Learn OLE DB Development With Visual C++ 6.0

column. If the OLE DB provider computes the new value, it sends a notifica-tion to OLE DB consumer. If the OLE DB provider does not compute the newvalue but lets the data source do so, then the computed value is not availableuntil after the change is transmitted to the data source—that is, after SetDatais called in immediate update mode or after IRowsetUpdate::Update ifSetData is called in delayed update mode. To retrieve the computed value inthis case, the OLE DB consumer calls RefreshVisibleData or GetLastVisible-Data in IRowsetRefresh. Bookmark columns are often computed, such aswhen the bookmark is the primary key or is a ROWID assigned by the datasource.

If SetData changes a column that is used to order the rowset, the DBPROP_IMMOBILEROWS property describes whether the row is moved based on itsnew value. If this property is VARIANT_TRUE, the row is not moved. If thisproperty 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 forwhich DBPROP_OTHERUPDATEDELETE is VARIANT_TRUE but DBPROP_OTHERINSERT is VARIANT_FALSE), changing the value of the key column isgenerally equivalent to deleting the current row and inserting a new one.Thus, the row may appear to move or even disappear from the rowset (ifDBPROP_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 theSetData method, SetData replaces the data in the column with the data in thenew storage object. If the OLE DB consumer wants only to delete the data inthe column, it sets the column status to DBSTATUS_S_OK and passes a nullpointer instead of a pointer to a storage object.

If the rowset is in immediate update mode, storage object data is alwaystransmitted 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 constraintschema violations, it is not required to do so. Such validation can be delayeduntil the changes are transmitted to the data source with Update or the trans-action is committed with ITransaction::Commit. This delay is often necessarybecause of dependencies on values in other columns or tables.

SetData cannot be called for rows with pending or transmitted deletes. TheDBPROP_COLUMNRESTRICT and DBPROP_ROWRESTRICT properties affecthow security is enforced and how security errors are returned. If DBPROP_COLUMNRESTRICT is VARIANT_TRUE, the OLE DB consumer might nothave write permission on some columns. If the OLE DB consumer attempts towrite to these columns, SetData returns a column status of DBSTATUS_E_PERMISSIONDENIED and a return code of DB_S_ERRORSOCCURRED. If the

Chapter Four—An OLE DB Primer � 183

Page 203: Learn OLE DB Development With Visual C++ 6.0

DBPROP_ROWRESTRICT property is VARIANT_TRUE, the OLE DB consumermight not have permission to update some rows.

If the OLE DB consumer attempts to update one of these rows, SetDatareturns 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 pro-vider sends notifications. These notifications can be vetoed, in which case theOLE DB provider sends the DBEVENTPHASE_FAILEDTODO phase of the noti-fication. When the OLE DB consumer then calls Update, if the update is indelayed 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 sendsDBREASON_COLUMN_RECALCULATED notifications. In this case, the OLEDB provider must either be prepared to undo all pending changes for the row,and return DBREASON_ROW_UNDOCHANGE, or set the fCantDeny flag toTRUE.

The following sequence of notifications occurs for a rowset operating indelayed update mode:

� If the row is being changed for the first time since it was created orchanges were transmitted, the OLE DB provider sends the DBEVENT-PHASE_OKTODO and DBEVENTPHASE_ABOUTTODO phases of theDBREASON_ROW_FIRSTCHANGE notification.

The OLE DB provider sends the DBEVENTPHASE_OKTODO, DBEVENT-PHASE_ABOUTTODO, and DBEVENTPHASE_SYNCHAFTER phases of theDBREASON_COLUMN_SET notification, in that order, provided none ofthe listeners veto any of the phases. The notification covers all the col-umns defined by the accessor used in the call to SetData.

� The OLE DB provider sends the DBEVENTPHASE_SYNCHAFTER phase ofthe DBREASON_ROW_FIRSTCHANGE notification, assuming the OLE DBprovider sent the earlier phases of this notification.

� The OLE DB provider sends the DBEVENTPHASE_DIDEVENT phase of theDBREASON_COLUMN_SET notification.

� The OLE DB provider sends the DBEVENTPHASE_DIDEVENT phase of theROW_FIRSTCHANGE notification.

It has the following syntax:

HRESULT SetData (HROW hRow,HACCESSOR hAccessor,void * pData);

184 � Chapter Four—An OLE DB Primer

Page 204: Learn OLE DB Development With Visual C++ 6.0

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. IfhAccessor is the handle of a null accessor (cBindings in IAccessor::Create-Accessor was zero), then SetData does not set any data values.

pData [in]This parameter provides a pointer to memory containing the new datavalues, at offsets that correspond to the bindings in the OLE DB accessor.

It has the following return codes:

S_OKThis return code means the method succeeded. The status of all columnsbound by the OLE DB accessor is set to DBSTATUS_S_OK or DBSTATUS_S_ISNULL

DB_S_ERRORSOCCURREDThis return code indicates that an error occurred while setting data forone or more columns, but data was successfully set for at least one col-umn. To determine the columns for which data was returned, the OLE DBconsumer checks the status values.

DB_S_MULTIPLECHANGESThis return code shows that the rowset was in immediate update modeand updating the row caused more than one row to be updated in thedata source. This return code takes precedence over DB_S_ERRORS-OCCURRED. That is, if the conditions described here and in thosedescribed in DB_S_ERRORSOCCURRED both occur, the OLE DB providerreturns this code. When the OLE DB consumer receives this return code,it should also check for the conditions described in DB_S_ERRORS-OCCURRED.

E_FAILThis return code means that an OLE DB provider-specific error occurred.

E_INVALIDARGThis return code specifies that *pData was a null pointer and the OLE DBaccessor was not a null accessor.

E_UNEXPECTEDThis return code states that ITransaction::Commit or ITransaction::Abortwas called and the object is in a zombie state.

DB_E_ABORTLIMITREACHEDThis return code indicates that the rowset was in immediate update modeand the row was not updated due to reaching a limit on the server, suchas a query execution timing out.

Chapter Four—An OLE DB Primer � 185

Page 205: Learn OLE DB Development With Visual C++ 6.0

DB_E_BADACCESSORHANDLEThis return code indicates that hAccessor was invalid.

DB_E_BADACCESSORTYPEThis return code states that the specified OLE DB accessor was not a rowaccessor or was a reference accessor.

DB_E_BADROWHANDLEThis return code shows that hRow was invalid.

DB_E_CANCELEDThis return code specifies that the change was canceled during notifica-tion. No columns are changed.

DB_E_CANTCONVERTVALUEThis return code indicates that the data value for one or more columnscouldn’t be converted for reasons other than sign mismatch or data over-flow, and the OLE DB provider was unable to determine which columnscouldn’t be converted. OLE DB providers that can detect which columnscould not be converted return DB_S_ERRORSOCCURRED and set the sta-tus flag for the columns that couldn’t be converted to DBSTATUS_E_CANTCONVERTVALUE.

DB_E_CONCURRENCYVIOLATIONThis return code provides that the rowset was using optimistic concur-rency and the value of a column has been changed since the containingrow was last fetched or resynchronized. SetData returns this error onlywhen the rowset is in immediate update mode.

DB_E_DATAOVERFLOWThis return code states that conversion failed because the data value forone or more columns overflowed the type used by the OLE DB providerand the OLE DB provider was unable to determine which columns causedthe overflow. OLE DB providers that can detect which columns caused theoverflow return DB_S_ERRORSOCCURRED and set the status flag for thecolumns in violation to DBSTATUS_E_DATAOVERFLOW.

DB_E_DELETEDROWThis return code shows hRow referred to a row with a pending delete orfor which a deletion had been transmitted to the data source.

DB_E_ERRORSOCCURREDThis return code means that an error occurred while setting data for oneor more columns and data was not successfully set for any columns. Todetermine the columns for which values were invalid, the OLE DB con-sumer checks the status values.

DB_E_INTEGRITYVIOLATIONThis return code specifies that the data violated the integrity constraintsfor one or more columns of the rowset and the OLE DB provider wasunable to determine which columns violated the integrity constraints.OLE DB providers that can detect which columns violated the integrity

186 � Chapter Four—An OLE DB Primer

Page 206: Learn OLE DB Development With Visual C++ 6.0

constraints return DB_S_ERRORSOCCURRED and set the status flag forthe columns in violation to DBSTATUS_E_INTEGRITYVIOLATION.

DB_E_MAXPENDCHANGESEXCEEDEDThis return code states that the number of rows that have pendingchanges has exceeded the limit specified by the DBPROP_MAXPENDING-ROWS property.

DB_E_NEWLYINSERTEDThis return code indicates that DBPROP_CHANGEINSERTEDROWS wasVARIANT_FALSE and hRow referred to a row for which the insertion hasbeen transmitted to the data source.

DB_E_NOTREENTRANTThis return code means that the OLE DB consumer called this methodwhile it was processing a notification; it is an error to call this methodwhile processing the specified DBREASON value.

DB_E_NOTSUPPORTEDThis return code shows that the OLE DB provider does not support thismethod.

DB_SEC_E_PERMISSIONDENIEDThis return code indicates that the OLE DB consumer did not have suffi-cient permission to update the row. This error can be returned only if thevalue of the DBPROP_ROWRESTRICT property is VARIANT_TRUE. If therowset is in delayed update mode, this error might not be returned untilIRowsetUpdate::Update is called.

If this method performs deferred OLE DB accessor validation and thatvalidation takes place before any data is transferred, it can also returnany of the following return codes for the applicable reasons listed in thecorresponding DBBINDSTATUS values in IAccessor::CreateAccessor:

E_NOINTERFACEDB_E_BADBINDINFODB_E_BADORDINALDB_E_BADSTORAGEFLAGSDB_E_UNSUPPORTEDCONVERSION

IRowsetUpdate

IRowsetUpdate enables OLE DB consumers to delay the transmission ofchanges made with IRowsetChange to the data source. This interface alsoenables OLE DB consumers to undo changes before transmission.

IRowsetUpdate is optional.

Rowsets that want to allow delayed transmission of changes and to supportundo capabilities implement IRowsetUpdate. Generally, this requires the

Chapter Four—An OLE DB Primer � 187

Page 207: Learn OLE DB Development With Visual C++ 6.0

rowset to locally cache a copy of each row as it is changed withIRowsetChange::SetData.

If IRowsetUpdate is not requested as a property of the rowset, it must not beexposed by the rowset. If IRowsetUpdate is exposed on the rowset, thenchanges made through IRowsetChange are buffered in the rowset and nottransmitted to the data source until IRowsetUpdate::Update is called; this isknown as delayed update mode. If IRowsetUpdate is not exposed on therowset, then changes made through IRowsetChange are immediately trans-mitted 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 con-sumer 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 withUndo.

Method Description

GetOriginalData This method gets the data most recently fetched from ortransmitted to the data source; does not get values based onpending changes.

GetPendingRows This method transmits any changes made to a row since itwas last fetched or since Update was called for it.

GetRowStatus This method returns the status of rows.

Undo This method undoes any changes made to a row since it waslast fetched or since Update was called for it.

Update This method returns a list of rows with pending changes.

IRowsetUpdate::GetOriginalData

This method gets the data most recently fetched from or transmitted to thedata source; it does not get values based on pending changes. This methodmakes no logical change to the state of the object.

GetOriginalData retrieves the values the row contained when it was lastfetched or had changes transmitted to the data source. It does not retrieveany pending changes or changes made by other rowsets in the same transac-tion or other applications in other transactions. It also does not affect thecurrent values for the row.

To implement GetOriginalData, the OLE DB provider usually caches the origi-nal values just before making a change to a row and discards the cachedvalues when Undo or Update is called for the row.

If hRow refers to a pending insert row, GetOriginalData returns the columndefaults and, for columns without defaults or for which the OLE DB providerwas unable to determine the defaults, NULLs.

188 � Chapter Four—An OLE DB Primer

Page 208: Learn OLE DB Development With Visual C++ 6.0

Whether GetOriginalData can retrieve the original value of an OLE objectthat is stored in a column, or a storage object that is created over a BLOBafter changes have been made to that OLE object or BLOB, depends on thevalue of the DBPROP_DELAYSTORAGEOBJECTS rowset property.

There is a difference between calling GetOriginalData and calling GetLast-VisibleData. In a delayed update mode, if data is changed by another OLE DBconsumer, different OLE DB consumers may retrieve different data. For exam-ple, the value in Column 1 is X. In a delayed update mode, OLE DB consumerA changes the value in that column to Y but does not transmit this action tothe data source. OLE DB consumer B then changes the value in Column 1 toZ. If OLE DB consumer A calls GetOriginalData, it gets X. However, if it callsGetLastVisibleData, using a dirty read, it will retrieve Z.

GetOriginalData does not enforce any security restrictions. The OLE DB pro-vider must not create a rowset that includes columns for which the OLE DBconsumer does not have read privileges, so GetOriginalData never encountersproblems accessing the data for a column. The rowset can contain columns towhich the OLE DB consumer does not have write permission if DBPROP_COLUMNRESTRICT is VARIANT_TRUE. The methods that fetch rows mustnot return the handles of rows for which the OLE DB consumer does not haveread privileges, so GetOriginalData never encounters problems accessing arow. Such rows might exist if the DBPROP_ROWRESTRICT property isVARIANT_TRUE.

If GetOriginalData fails, the memory to which *pData points is not freed butits contents are undefined. If, before GetOriginalData failed, the OLE DB pro-vider allocated any memory for return to the OLE DB consumer, the OLE DBprovider frees this memory and does not return it to the OLE DB consumer.

It has the following syntax:

HRESULT GetOriginalData (HROW hRow,HACCESSOR hAccessor,void * pData);

It has the following parameters:

hRow [in]This parameter gives the handle of the row for which to get the originaldata. 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. IfhAccessor is the handle of a null accessor (cBindings in IAccessor::CreateAccessor was zero), then GetOriginalData does not get any datavalues.

Chapter Four—An OLE DB Primer � 189

Page 209: Learn OLE DB Development With Visual C++ 6.0

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_OKThis return code means the method succeeded. The status of all columnsbound by the OLE DB accessor is set to DBSTATUS_S_OK, DBSTATUS_S_ISNULL, or DBSTATUS_S_TRUNCATED.

DB_S_ERRORSOCCURREDThis return code shows that an error occurred while returning data forone or more columns, but data was successfully returned for at least onecolumn. To determine the columns for which data was returned, the con-sumer checks the status values.

E_FAILThis return code indicates that an OLE DB provider-specific erroroccurred.

E_INVALIDARGThis return code provides that *pData was a null pointer and hAccessorwas not a null accessor.

E_UNEXPECTEDThis return code specifies that ITransaction::Commit or ITransaction::Abort was called and the object is in a zombie state.

DB_E_BADACCESSORHANDLEThis return code means that hAccessor was invalid. It is possible for a ref-erence accessor or an OLE DB accessor that has a binding that uses OLEDB provider-owned memory to be invalid for use with this method, evenif the OLE DB accessor is valid for use with IRowset::GetData orIRowsetChange::SetData.

DB_E_BADACCESSORTYPEThis return code shows that the specified accessor was not a rowaccessor.

DB_E_BADROWHANDLEThis return code states that hRow was invalid.

DB_E_DELETEDROWThis return code provides that hRow referred to a row for which a dele-tion had been transmitted to the data source.

DB_E_ERRORSOCCURREDThis return code indicates that errors occurred while returning data forall columns. To determine what errors occurred, the OLE DB consumerchecks the status values.

190 � Chapter Four—An OLE DB Primer

Page 210: Learn OLE DB Development With Visual C++ 6.0

DB_E_NOTREENTRANTThis return code means the OLE DB provider called a method fromIRowsetNotify in the OLE DB consumer that had not yet returned, andthe OLE DB provider does not support reentrancy in this method. If thismethod performs deferred accessor validation and that validation takesplace before any data is transferred, it can also return any of the follow-ing return codes for the applicable reasons listed in the correspondingDBBINDSTATUS values in IAccessor::CreateAccessor:

E_NOINTERFACEDB_E_BADBINDINFODB_E_BADORDINALDB_E_BADSTORAGEFLAGSDB_E_UNSUPPORTEDCONVERSION

IRowsetUpdate::GetPendingRows

This method returns a list of rows with pending changes. The GetPending-Rows method increments the reference of each row handle it returns in*prgPendingRows. The OLE DB consumer must call ReleaseRows for theserows. If multiple changes are made to a single row, GetPendingRows returnsthe status as follows.

� If IRowsetChange::SetData is called for a pending insert row, the row isstill considered a pending insert row.

� If IRowsetChange::DeleteRows is called for a pending update row, the rowis considered a pending delete row.

� If IRowsetChange::DeleteRows is called for a pending insert row, the rowis considered a transmitted delete row; such rows are not returned byGetPendingRows.

� 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.

Chapter Four—An OLE DB Primer � 191

Page 211: Learn OLE DB Development With Visual C++ 6.0

dwRowStatus [in]This parameter indicates whether consumers want rows with pendingupdates, deletes, or inserts. The following DBPENDINGSTATUS values arevalid and can be combined:

DBPENDINGSTATUS_NEWDBPENDINGSTATUS_CHANGEDDBPENDINGSTATUS_DELETED

pcPendingRows [out]This parameter provides a pointer to memory in which to return the num-ber of rows with pending changes. If this is a null pointer, *prgPending-

Rows and *prgPendingStatus are ignored. This is useful when the OLE DBconsumer wants to check the returned return code to determine whetherthere are any pending changes. If an error occurs, *pcPendingRows is setto zero.

prgPendingRows [out]This parameter shows a pointer to memory in which to return an array ofhandles of rows with pending changes. If this is a null pointer, no rowhandles are returned. The rowset allocates memory for the row handlesand returns the address to this memory; the OLE DB consumer releasesthis 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 providerdoes not allocate any memory and ensures that *prgPendingRows is a nullpointer on output.

prgPendingStatus [out]This parameter gives a pointer to memory in which to return an array ofDBPENDINGSTATUS values. These values are in one-to-one correspon-dence with the row handles returned in *prgPendingRows and indicate thetype of pending change. If this is a null pointer, no status information isreturned.

The rowset allocates memory for the row statuses and returns the address tothis memory; the OLE DB consumer releases this memory with IMalloc::Freewhen it no longer needs the row statuses. This argument is ignored if*pcPendingRows is a null pointer. If *pcPendingRows is zero on output or anerror occurs, the OLE DB provider does not allocate any memory and ensuresthat *prgPendingStatus is a null pointer on output.

It has the following return codes:

S_OKThis return code means the method succeeded and changes werepending.

192 � Chapter Four—An OLE DB Primer

Page 212: Learn OLE DB Development With Visual C++ 6.0

S_FALSEThis return code shows that the method succeeded and no changes werepending.

E_FAILThis return code indicates that an OLE DB provider-specific erroroccurred.

E_INVALIDARGThisreturn code states that dwRowStatus was DBPENDINGSTATUS_INVALIDROW, DBPENDINGSTATUS_UNCHANGED, or any other invalidvalue.

E_OUTOFMEMORYThis return code means the OLE DB provider was unable to allocate suffi-cient memory in which to return the handles of rows with pendingchanges or the array of DBPENDINGSTATUS values.

E_UNEXPECTEDThis return code indicates that ITransaction::Commit or ITransaction::Abort was called and the object is in a zombie state.

DB_E_BADCHAPTERThis return code specifies that the rowset was chaptered and hChapterwas invalid, or the rowset was single-chaptered and the specified chapterwas not the currently open chapter. The OLE DB consumer must use thecurrently open chapter or release the currently open chapter before speci-fying a new chapter.

IRowsetUpdate::GetRowStatus

This method returns the status of rows. If multiple changes are made to a sin-gle row, GetRowStatus returns the status as described in GetPendingRows. IfIRowsetChange::DeleteRows is called for a pending insert row, a status ofDBPENDINGSTATUS_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-

Chapter Four—An OLE DB Primer � 193

Page 213: Learn OLE DB Development With Visual C++ 6.0

Status. If this value is zero, GetRowStatus ignores rghRows andrgPendingStatus and does not return any status.

rghRows [in]This parameter indicates an array of handles of rows for which to returnthe status. This array is allocated by the OLE DB consumer and must notbe freed by the OLE DB provider.

rgPendingStatus [out]This parameter shows an array of DBPENDINGSTATUS values. GetRow-Status returns the DBPENDINGSTATUS values for all rows specified in therghRows array. The DBPENDINGSTATUS_INVALIDROW value is used toindicate an invalid row handle.The rgPendingStatus array is allocated, butnot necessarily initialized, by the caller and must not be freed by the OLEDB provider.

It has the following return codes:

S_OKThis return code means the method succeeded. Status values were suc-cessfully retrieved for all rows and each element of rgPendingStatus is setto DBPENDINGSTATUS_NEW, DBPENDINGSTATUS_CHANGED,DBPENDINGSTATUS_DELETED, or DBPENDINGSTATUS_UNCHANGED.

DB_S_ERRORSOCCURREDThis return code shows an error occurred while getting the status of arow, but the status of at least one row was successfully retrieved. Suc-cesses can occur for the reasons listed under S_OK. An error can occur ifa row handle in rghRows was invalid. The corresponding element ofrgRowStatus contains DBPENDINGSTATUS_INVALIDROW.

E_FAILThis return code indicates that an OLE DB provider-specific erroroccurred.

E_INVALIDARGThis return code states that cRows was greater than zero and eitherrghRows or rgPendingStatus was a null pointer.

E_UNEXPECTEDThis return code provides that ITransaction::Commit or ITransaction::Abort was called and the object is in a zombie state.

DB_E_BADCHAPTERThis return code shows that the rowset was chaptered and hChapter wasinvalid, or the rowset was single-chaptered and the specified chapter wasnot the currently open chapter. The OLE DB consumer must use the cur-rently open chapter or release the currently open chapter beforespecifying a new chapter.

194 � Chapter Four—An OLE DB Primer

Page 214: Learn OLE DB Development With Visual C++ 6.0

DB_E_ERRORSOCCURREDThis return code means that errors occurred getting the status of all ofthe rows. Errors can occur for the reason listed under DB_S_ERRORS-OCCURRED.

IRowsetUpdate::Undo

This method undoes any changes made to a row since it was last fetched orUpdate was called for it. Undo backs any pending changes out of the speci-fied rows and clears their pending change status. That is, it undoes anychanges made to the row since it was last fetched or Update was called forthe row. If multiple changes were made to a row, Undo undoes all of thesechanges; the OLE DB provider does not remember intermediate steps. IfUpdate 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 hChapter,ULONG cRows,const HROW rghRows[],ULONG * pcRows,HROW ** prgRows,DBROWSTATUS ** 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. IfcRows is zero, Undo ignores rghRows and undoes all pending changes toall rows in the rowset.

rghRows [in]This parameter provides an array of handles of the rows to undo. Ele-ments of this array can refer to rows with pending deletes.

If rghRows includes a row that does not have any pending changes, Undodoes not return an error. Instead, the row remains unchanged from itsoriginal state—which is the intention of Undo—and its row status is setto DBROWSTATUS_S_OK.

If rghRows includes a duplicate row, Undo treats the occurrences as if therow were passed to the method two times sequentially. Thus, on the firstoccurrence, Undo undoes any pending changes. On the second

Chapter Four—An OLE DB Primer � 195

Page 215: Learn OLE DB Development With Visual C++ 6.0

occurrence, Undo treats the row as a row with no pending changes andleaves it in its current (now original) state.

pcRows [out]This parameter gives a pointer to memory in which to return the numberof rows Undo attempted to undo. If this is a null pointer, no count of rowsis returned. If the method fails with an error other than DB_E_ERRORS-OCCURRED, *pcRows is set to zero.

prgRows [out]This parameter shows a pointer to memory in which to return an arraycontaining the handles of all the rows Undo attempted to undo. IfrghRows is not a null pointer, then the elements of this array are inone-to-one correspondence with those in rghRows. For example, if a rowappears twice in rghRows, it appears twice in *prgRows. When rghRows isnot a null pointer, Undo does not add to the reference count of the rowsit returns in *prgRows; the reason is that the consumer already has theserow handles.

If rghRows is a null pointer, the elements of this array are the handles ofall the rows that had pending changes, regardless of whether Undo wassuccessful at undoing those changes. The OLE DB consumer checks*prgRowStatus to determine which rows were undone. When rghRows is anull 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 toalready have these row handles. A side effect of this is that rows with areference count of zero but with pending changes at the time Undo iscalled are brought back into existence; that is, their reference count isincreased to 1 and they must be rereleased.

The rowset allocates memory for the array of handles and returns theaddress to this memory; the OLE DB consumer releases this memory withIMalloc::Free when it no longer needs the handles. This argument isignored if *pcRows is a null pointer, and must not be a null pointer other-wise. If *pcRows is zero on output or the method fails with an error otherthan DB_E_ERRORSOCCURRED, the OLE DB provider does not allocateany 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 arrayof row status values. The elements of this array correspond one-to-onewith 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 arow, the corresponding element of *prgRowStatus is set to DBROW-STATUS_S_OK. If an error occurs while undoing a row, the correspondingelement is set as specified in DB_S_ERRORSOCCURRED. If*prgRowStatus is a null pointer, no row status values are returned.

196 � Chapter Four—An OLE DB Primer

Page 216: Learn OLE DB Development With Visual C++ 6.0

The rowset allocates memory for the row status values and returns theaddress to this memory; the OLE DB consumer releases this memory withIMalloc::Free when it no longer needs the row status values. This argu-ment is ignored if cRows is zero and *pcRows is a null pointer. If Undodoes not attempt to undo any rows or the method fails with an errorother than DB_E_ERRORSOCCURRED, the OLE DB provider does notallocate any memory and ensures that *prgRowStatus is a null pointer onoutput.

It has the following return codes:

S_OKThis return code means the method succeeded. The changes in all rowswere successfully undone. In this case, the corresponding element of*prgRowStatus contains DBROWSTATUS_S_OK.

DB_S_ERRORSOCCURREDThis return code shows that an error occurred while undoing the changesin a row, but the changes in at least one row were successfully undone.Successes can occur for the reasons listed under S_OK. The followingerrors can occur:

� Undoing the changes in a row was canceled during notification.Changes made to the row were not undone and the corresponding ele-ment of *prgRowStatus contains DBROWSTATUS_E_CANCELED.

� A row handle in rghRows referred to a row for which a delete hadbeen transmitted to the data source. The corresponding element of*prgRowStatus contains DBROWSTATUS_E_DELETED.

� A row handle in rghRows was invalid. The corresponding element of*prgRowStatus contains DBROWSTATUS_E_INVALID.

� A row handle in rghRows referred to a row on which a storage objector OLE object was open. The corresponding element of *prgRowStatuscontains DBROWSTATUS_E_OBJECTOPEN.

� The OLE DB consumer encountered a recoverable, provider-specificerror, such as an RPC failure when transmitting the change to aremote server. The corresponding element of *prgRowStatus containsDBROWSTATUS_E_FAIL.

E_FAILThis return code indicates an OLE DB provider-specific error occurred.

E_INVALIDARGThis return code states that cRows was not zero and rghRows was a nullpointer, or *pcRows was not a null pointer and *prgRows was a nullpointer.

Chapter Four—An OLE DB Primer � 197

Page 217: Learn OLE DB Development With Visual C++ 6.0

E_OUTOFMEMORYThis return code shows the OLE DB provider was unable to allocate suffi-cient memory in which to return either the handles of the rows Undoattempted to undo or the array of row status values.

E_UNEXPECTEDThis return code means that ITransaction::Commit or ITransaction::Abortwas called and the object is in a zombie state.

DB_E_BADCHAPTERThis return code specifies that the rowset was chaptered and hChapterwas invalid, or the rowset was single-chaptered and the specified chapterwas not the currently open chapter. The OLE DB consumer must use thecurrently open chapter or release the currently open chapter before speci-fying a new chapter.

DB_E_ERRORSOCCURREDThis return code shows that errors occurred while undoing all of therows. The OLE DB provider allocates memory for *prgRows and *prgRow-

Status and the OLE DB consumer checks the values in *prgRowStatus todetermine why the pending changes were not undone. The OLE DB con-sumer frees this memory when it no longer needs the information. Errorscan occur for the reasons listed under DB_S_ERRORSOCCURRED.

DB_E_NOTREENTRANTThis return code indicates that the consumer called this method while itwas processing a notification; it is an error to call this method while pro-cessing the specified DBREASON value.

To implement Undo, the OLE DB provider generally caches the original val-ues just before making a change to a row and discards the cached valueswhen Undo or Update is called for the row. This same cache can be used bythe 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 therowset. That is, calls to IRowset::GetData or SetData for the row fail withDB_E_DELETEDROW. The OLE DB consumer must still call IRowset::Release-Rows to release the row.

Whether Undo can undo changes made to an OLE object stored in a columnor to a storage object created over a BLOB depends on the value of theDBPROP_DELAYSTORAGEOBJECTS rowset property.

If Undo is called for a row that has a reference count of zero and exists onlybecause the row has a pending change, Undo releases the row and all itsresources. The only exception to this is when the handle to the row isreturned 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 Undoencounters an error, it continues processing rows until it has attempted toundo all specified rows, then returns the appropriate warning. Because Undo

198 � Chapter Four—An OLE DB Primer

Page 218: Learn OLE DB Development With Visual C++ 6.0

is generally implemented by copying data from a cache of original data, sucherrors should be extremely rare and generally represent an OLE DB consumerprogramming error, such as passing an invalid row handle.

If any OLE DB consumer of the rowset is using notifications, the OLE DB pro-vider sends notifications that pending changes for the specified rows arebeing undone.

IRowsetUpdate::Update

This method transmits any changes made to a row since it was last fetched orUpdate was called for it. Update transmits pending changes for the specifiedrows to the data source and clears their pending change status. That is, ittransmits any changes made to the row since it was last fetched or Updatewas called for the row.

The order in which Update processes rows is OLE DB provider-specific. IfUpdate encounters an error, it continues processing rows until it hasattempted to update all specified rows, then it returns the appropriate warn-ing. This might leave the rowset in a state where changes have been trans-mitted for some rows but not others. When the OLE DB consumer repairs thecause of the error, it can call Update again and the OLE DB provider guaran-tees it will not transmit data to the data source for those rows for which datawas already successfully transmitted.

If any OLE DB consumer of the rowset is using notifications, the OLE DB pro-vider sends notifications that pending changes for the specified rows arebeing transmitted to the data source.

If Update is called for a row that has a reference count of zero and exists onlybecause the row has a pending change, Update releases the row and all itsresources. The only exception to this is when the handle to the row isreturned in *prgRows, in which case the reference count is set to 1. If the OLEDB 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 hChapter,ULONG cRows,const HROW rghRows[],ULONG * pcRows,HROW ** prgRows,DBROWSTATUS** prgRowStatus);

Chapter Four—An OLE DB Primer � 199

Page 219: Learn OLE DB Development With Visual C++ 6.0

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. IfcRows is zero, Update ignores rghRows and updates all pending changesto all rows in the rowset.

rghRows [in]This parameter provides an array of handles of the rows to update. IfrghRows includes a row that does not have any pending changes, Updatedoes not return an error. Instead, the row remains unchanged and hencehas no pending changes after Update returns—which is the intention ofUpdate—and the row status value associated with that row is DBROW-STATUS_S_OK. Furthermore, Update guarantees not to transmit anyvalue for the row to the data source.

If rghRows includes a duplicate row, Update behaves as follows. If the rowhandle is valid, no errors occur and *prgRowStatus contains DBROW-STATUS_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 num-ber of rows Update attempted to update. If this is a null pointer, no countof 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 arraycontaining the handles of all the rows Update attempted to update. IfrghRows 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 rowappears twice in rghRows, it appears twice in *prgRows. When rghRows isnot a null pointer, Update does not add to the reference count of the rowsit returns in *prgRows; the reason is that the OLE DB consumer alreadyhas these row handles.

If rghRows is a null pointer, the elements of this array are handles of allthe rows that had pending changes, regardless of whether Update wassuccessful at transmitting those changes to the data source. The OLE DBconsumer checks *prgRowStatus to determine which rows were updated.When rghRows is a null pointer, Update adds to the reference count of therows it returns in *prgRows; the reason is that the OLE DB consumer isnot guaranteed to already have these row handles. A side effect of this isthat rows with a reference count of zero, but with pending changes at the

200 � Chapter Four—An OLE DB Primer

Page 220: Learn OLE DB Development With Visual C++ 6.0

time Update is called, are brought back into existence; that is, their refer-ence count is increased to 1 and they must be rereleased.

The rowset allocates memory for the array of handles and returns theaddress to this memory; the OLE DB consumer releases this memory withIMalloc:: Free when it no longer needs the handles. This argument isignored if *pcRows is a null pointer, and must not be a null pointer other-wise. If *pcRows is zero on output or the method fails with an error otherthan DB_E_ERRORSOCCURRED, the OLE DB provider does not allocateany 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 arrayof row status values. The elements of this array correspond one-to-onewith the elements of rghRows (if rghRows is not a null pointer) or *prg-

Rows (if rghRows is a null pointer). If no errors or warnings occur whileupdating a row, the corresponding element of *prgRowStatus is set toDBROWSTATUS_S_OK. If an error or warning occurs while updating arow, the corresponding element is set as specified in DB_S_ERRORS-OCCURRED. If *prgRowStatus is a null pointer, no row status values arereturned.

The rowset allocates memory for the row status values and returns theaddress to this memory; the OLE DB consumer releases this memory withIMalloc::Free when it no longer needs the row status values. This argu-ment is ignored if cRows is zero and *pcRows is a null pointer. If Updatedoes not attempt to update any rows or the method fails with an errorother than DB_E_ERRORSOCCURRED, the OLE DB provider does notallocate any memory and ensures that *prgRowStatus is a null pointer onoutput.

It has the following return codes:

S_OKThis return code means the method succeeded. The changes in all rowswere successfully updated. The following values can be returned in*prgRowStatus:

� The changes in the row were successfully updated. The correspondingelement of *prgRowStatus contains DBROWSTATUS_S_OK.

� Updating or deleting a single row caused more than one row to beupdated or deleted in the data source.. The corresponding element of*prgRowStatus contains DBROWSTATUS_S_MULTIPLECHANGES.

DB_S_ERRORSOCCURREDThis return code indicates that an error occurred while updating a row,but at least one row was successfully updated. Successes can occur forthe reasons listed under S_OK. The following errors can occur:

Chapter Four—An OLE DB Primer � 201

Page 221: Learn OLE DB Development With Visual C++ 6.0

� Updating a row was canceled during notification. The row was notupdated and the corresponding element of *prgRowStatus containsDBROWSTATUS_E_CANCELED.

� The rowset was using optimistic concurrency, a row was beingupdated or deleted, and the value of a column in that row has beenchanged since it was last fetched. The error in the corresponding ele-ment of *prgRowStatus contains DBROWSTATUS_E_CONCURRENCY-VIOLATION.

� An element of rghRows referred to a row for which a deletion hadbeen transmitted to the data source. The error in the correspondingelement of *prgRowStatus contains DBROWSTATUS_E_DELETED.

� A row was being inserted or updated and a value specified for thatrow violated the integrity constraints for the column or table. Theerror in the corresponding element of *prgRowStatus containsDBROWSTATUS_E_INTEGRITYVIOLATION.

� A row was being deleted, and doing so violated the integrity con-straints for the column or table. The error in the correspondingelement of *prgRowStatus contains DBROWSTATUS_E_INTEGRITY-VIOLATION.

� An element of rghRows was invalid. The error in the correspondingelement of *prgRowStatus contains DBROWSTATUS_E_INVALID.

� 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 ofthe DBPROP_ROWRESTRICT property is VARIANT_TRUE. The errorin the corresponding element of *prgRowStatus contains DBROW-STATUS_E_PERMISSIONDENIED.

� The update, delete, or insert failed due to reaching a limit on theserver, such as a query execution timing out. The error in the corre-sponding element of *prgRowStatus contains DBROWSTATUS_E_LIMITREACHED.

� A column value did not meet the schema requirements for the column.The error in the corresponding element of *prgRowStatus containsDBROWSTATUS_E_SCHEMAVIOLATION.

� The values for two or more columns did not meet the multiple-column schema requirements for those columns. The error in the cor-responding element of *prgRowStatus contains DBROWSTATUS_E_SCHEMAVIOLATION.

� A row was being inserted, no value was specified for a column, thecolumn does not have a default, and the column is non-nullable. Theerror in the corresponding element of *prgRowStatus containsDBROWSTATUS_E_SCHEMAVIOLATION.

202 � Chapter Four—An OLE DB Primer

Page 222: Learn OLE DB Development With Visual C++ 6.0

� The OLE DB consumer encountered a recoverable OLE DB provider-specific error, such as an RPC failure when transmitting the change toa remote server. The corresponding element of *prgRowStatus containsDBROWSTATUS_E_FAIL.

E_FAILThis return code specifies that a provider-specific error occurred.

E_INVALIDARGThis return code indicates that cRows was not zero and rghRows was anull pointer, or *pcRows was not a null pointer and prgRows was a nullpointer on input.

E_OUTOFMEMORYThis return code shows that the provider was unable to allocate sufficientmemory in which to return either the handles of the rows Updateattempted to update or the array of row status values.

E_UNEXPECTEDThis return code states that ITransaction::Commit or ITransaction::Abortwas called and the object is in a zombie state.

DB_E_BADCHAPTERThis return code means that the rowset was chaptered and hChapter wasinvalid, or the rowset was single-chaptered and the specified chapter wasnot the currently open chapter. The OLE DB consumer must use the cur-rently open chapter or release the currently open chapter beforespecifying a new chapter.

DB_E_ERRORSOCCURREDThis return code states that errors occurred while updating all of therows. The OLE DB provider allocates memory for *prgRows and *prgRow-

Status and the OLE DB consumer checks the values in *prgRowStatus todetermine why the pending changes were not updated. The OLE DB con-sumer frees this memory when it no longer needs the information. Errorscan occur for the reasons listed under DB_S_ERRORSOCCURRED.

DB_E_NOTREENTRANTThis return code indicates that the OLE DB provider called a method fromIRowsetNotify in the consumer and the method has not yet returned.

ITransaction

The ITransaction interface is used to commit, abort, and obtain status infor-mation about transactions.

Method Description

Abort This method aborts a transaction.

Commit This method commits a transaction.

Chapter Four—An OLE DB Primer � 203

Page 223: Learn OLE DB Development With Visual C++ 6.0

Method Description

GetTransactionInfo This method returns information about a transaction.

ITransaction::Abort

This method aborts a transaction. The following table shows how the valuesof fRetaining and DBPROP_ABORTPRESERVE affect the rowset state andtransaction mode.

DBPROP_ABORTPRESERVE fRetaining

Rowset StateAfter Abort

Resulting Transaction /Mode of Session

FALSE FALSE zombie implicit / autocommit

FALSE TRUE zombie explicit / manual

TRUE FALSE preserved implicit / autocommit

TRUE TRUE preserved 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 transac-tion 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 isperformed and the caller must use ITransactionOutcomeEvents to learnthe outcome of the transaction.

It has the following return codes:

S_OKThis return code means the transaction was successfully aborted.

XACT_S_ABORTINGThis return code shows that an abort operation was already in progress.This call was ignored.

XACT_S_ASYNCThis return code indicates that an asynchronous abort was specified. Theabort operation has begun but its outcome is not yet known. When the

204 � Chapter Four—An OLE DB Primer

Page 224: Learn OLE DB Development With Visual C++ 6.0

transaction is complete, notification will be sent by ITransactionOutcome-Events.

E_FAILThis return code shows that the transaction failed to abort for an unspeci-fied reason.

E_UNEXPECTEDThis return code states that an unexpected error occurred. The transac-tion status is unknown.

XACT_E_ALREADYINPROGRESSThis return code specifies that a commit operation was already in prog-ress. This call was ignored.

XACT_E_CANTRETAINThis return code provides that a retaining abort is not supported or a newunit of work could not be created. The abort succeeded and the session isin autocommit mode.

XACT_E_CONNECTION_DOWNThis return code shows that the connection to the transaction managerfailed. The transaction state is unknown.

XACT_E_INDOUBTThis return code means the transaction status is in doubt. A communica-tion failure occurred or a transaction manager or resource manager hasfailed.

XACT_E_NOTRANSACTIONThis return code indicates that the transaction cannot be aborted becauseit already had been implicitly or explicitly committed or aborted. This callwas ignored.

XACT_E_NOTSUPPORTEDThis return code states that fAsync was TRUE on input and asynchronousabort operations are not supported.

ITransaction::Commit

This method commits a transaction. The following table shows how values ofthe fRetaining parameter and DBPROP_COMMITPRESERVE affect the rowsetstate and transaction mode.

DBPROP_COMMIT PRESERVE fRetaining

Rowset StateAfter Commit

Resulting Transaction /Mode of Session

FALSE FALSE zombie implicit / autocommit

FALSE TRUE zombie explicit / manual

TRUE FALSE preserved implicit / autocommit

TRUE TRUE preserved explicit / manual

Chapter Four—An OLE DB Primer � 205

Page 225: Learn OLE DB Development With Visual C++ 6.0

If Commit fails for any reason that results in an aborted transaction, the ses-sion 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. Valuesthat may be specified in grfTC are as follows. These values are mutuallyexclusive.

Flag Description

XACTTC_ASYNC_PHASEONE

When this flag is specified, an asynchronous commit isperformed.

XACTTC_SYNC_PHASEONE

When this flag is specified, the call to Commit returns afterphase one of the two-phase commit protocol.

XACTTC_SYNC_PHASETWO

When this flag is specified, the call to Commit returns afterphase two of the two-phase commit protocol.

XACTTC_SYNC Synonym for XACTTC_SYNC_PHASETWO.

grfRM [in]This parameter must be zero.

It has the following return codes:

S_OKThis return code means the transaction was successfully committed.

XACT_S_ASYNCThis return code shows that an asynchronous commit was specified. Thecommit operation has begun but its outcome is not yet known. When thetransaction is complete, notification will be sent by ITransaction-OutcomeEvents.

E_FAILThis return code indicates that an OLE DB provider-specific erroroccurred. The transaction was aborted.

E_UNEXPECTEDThis return code states that an unexpected error occurred. The transac-tion status is unknown.

206 � Chapter Four—An OLE DB Primer

Page 226: Learn OLE DB Development With Visual C++ 6.0

XACT_E_ABORTEDThis return code specifies that the transaction was aborted before Com-mit was called.

XACT_E_ALREADYINPROGRESSThis return code means that a commit or abort operation was already inprogress. This call was ignored.

XACT_E_CANTRETAINThis return code provides that retaining commit is not supported or anew unit of work could not be created. The commit succeeded and thesession is in autocommit mode.

XACT_E_COMMITFAILEDThis return code means that the transaction failed to commit for anunknown reason. The transaction was aborted.

XACT_E_CONNECTION_DOWNThis return code indicates that the connection to the transaction managerfailed. The transaction was aborted.

XACT_E_INDOUBTThis return code shows that the transaction status is in doubt. A commu-nication failure occurred or a transaction manager or resource managerhas failed.

XACT_E_NOTRANSACTIONThis return code specifies the transaction is unable to commit because ithad already been implicitly or explicitly committed or aborted. This callwas ignored.

XACT_E_NOTSUPPORTEDThis return code shows an invalid combination of commit flags was speci-fied 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 XACTTRANSINFOstructure in which the method returns information about the transaction.*pInfo must not be a null pointer.

Chapter Four—An OLE DB Primer � 207

Page 227: Learn OLE DB Development With Visual C++ 6.0

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 Description

uow This element shows the unit of work associatedwith this transaction.

isoLevel This element gives the isolation level associatedwith this transaction. ISOLATIONLEVEL_UNSPECIFIED indicates that no isolation level wasspecified.

isoFlags This element will be zero.

grfTCSupported This bitmask indicates the XACTTC flags that thistransaction implementation supports.

grfRMSupported This element will be zero.

grfTCSupportedRetaining This element will be zero.

grfRMSupportedRetaining This element will be zero.

It has the following return codes:

S_OKThis return code means the method succeeded.

E_FAILThis return code shows that an OLE DB provider-specific error occurred.

E_INVALIDARGThis return code states that *pInfo was a null pointer.

E_UNEXPECTEDThis return code indicates that an unknown error occurred. No informa-tion is returned.

XACT_E_NOTRANSACTIONThis return code means the method was unable to retrieve informationfor the transaction because it was already completed. No information isreturned.

208 � Chapter Four—An OLE DB Primer

Page 228: Learn OLE DB Development With Visual C++ 6.0

ITransactionOptions

ITransactionOptions gets and sets a suite of options associated with atransaction.

Method Description

Get Options This method gets a suite of options associated with a transaction.

SetOptions 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 methodcan be called at any time. GetOptions does not make any logical changes tothe 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 toreturn the options for this transaction. The OLE DB consumer allocatesthis structure.

It has the following return codes:

S_OKThis return code means success.

E_FAILThis return code shows that an OLE DB provider-specific error occurred.

E_INVALIDARGThis return code indicates that *pOptions was a null pointer.

E_UNEXPECTEDThis return code specifies that an unknown error occurred; the methodfailed.

ITransactionOptions::SetOptions

This method sets a suite of options associated with a transaction.

It has the following syntax:

HRESULT SetOptions(XACTOPT * pOptions);

Chapter Four—An OLE DB Primer � 209

Page 229: Learn OLE DB Development With Visual C++ 6.0

It has the following parameter:

pOptions [in]This parameter gives a pointer to an XACTOPT structure containing theoptions 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 Description

ulTimeout This element gives the amount of real time in milliseconds before thetransaction is to be aborted automatically. Zero indicates an infinitetimeout. If no options have been previously set, ulTimeout is zero.

szDescription This element provides a pointer to a textual description associatedwith this transaction. This string is appropriate for display in variousend-user administration tools that might monitor or log thetransaction. If no options have been previously set, szDescription is anempty string.

It has the following return codes:

S_OKThis return code means the method succeeded.

E_FAILThis return code shows that an OLE DB provider-specific error occurred.

E_INVALIDARGThis return code states that *pOptions was a null pointer.

E_UNEXPECTEDThis return code indicates that an unknown error occurred; the methodfailed.

ITransactionObject

ITransactionObject enables OLE DB consumers to obtain the transactionobject associated with a particular transaction level.

Method Description

GetTransactionObject This method returns an interface pointer on thetransaction object.

210 � Chapter Four—An OLE DB Primer

Page 230: Learn OLE DB Development With Visual C++ 6.0

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 pointerto the returned transaction object.

It has the following return codes:

S_OKThis return code means the method succeeded.

E_FAILThis return code indicates that an OLE DB provider-specific erroroccurred.

E_INVALIDARGThis return code states that ulTransactionLevel was zero or*ppTransactionObject was a null pointer.

E_UNEXPECTEDThis return code shows that an unknown error occurred and the methodfailed.

ITransactionJoin

ITransactionJoin is exposed only by OLE DB providers that support distrib-uted transactions. The OLE DB consumer calls QueryInterface forITransactioJoin to determine whether the OLE DB provider supports coordi-nated transactions.

Method Description

GetOptionsObject This method returns an object that can be used to specifyconfiguration options for a subsequent call to theJoinTransaction method.

JoinTransaction This method requests that the session enlist in acoordinated transaction.

Chapter Four—An OLE DB Primer � 211

Page 231: Learn OLE DB Development With Visual C++ 6.0

ITransactionJoin::GetOptionsObject

This method returns an object that can be used to specify configurationoptions 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 tothe object that can be used to set extended transaction options.

It has the following return codes:

S_OKThis return code means the method succeeded.

E_FAILThis return code indicates that an unknown error occurred.

E_INVALIDARGThis return code shows that *ppOptions was a null pointer.

E_OUTOFMEMORYThis return code means the method was unable to allocate memory.

ITransactionJoin::JoinTransaction

This method requests that the session enlist in a coordinated transaction. TheOLE DB provider of the session will generally first register its ITransaction-OutcomeEvents notification sink with the transaction coordinator, so that itcan be notified of the outcome of the coordinated events, and will then enlistwith the transaction coordinator using the interfaces defined in OLEtransactions. It is important that the OLE DB provider first register with theoutcome events so that it can be advised of any failures which may occurbetween 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

212 � Chapter Four—An OLE DB Primer

Page 232: Learn OLE DB Development With Visual C++ 6.0

transaction coordinator, or NULL in order to unenlist from the coordi-nated transaction. If non-null, QueryInterface can be called forITransaction 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 GetOptions-Object called on this session.

It has the following return codes:

S_OKThis return code means the method succeeded.

E_FAILThis return code shows that an unknown error occurred.

E_INVALIDARGThis return code indicates that *punkTransactionCoord was a null pointerand the OLE DB provider doesn’t support unenlisting from a coordinatedtransaction.

E_UNEXPECTEDThis return code states that an unknown OLE DB provider-specific erroroccurred.

XACT_E_CONNECTION_DOWNThis return code specifies that the connection to the transaction managerfailed.

XACT_E_CONNECTION_REQUEST_DENIEDThis return code shows that the transaction manager did not accept aconnection request.

XACT_E_ISOLATIONLEVELThis return code states that neither the requested isolation level, nor astrengthening of it, can be supported by this transaction implementationor isoLevel was not valid.

XACT_E_LOGFULLThis return code means a new transaction is unable to begin because thelog file is full.

XACT_E_NOENLISTThis return code indicates a transaction coordinator was specified, butthe new transaction was unable to enlist therein.

XACT_E_NOISORETAINThis return code specifies that the requested semantics of retention of

Chapter Four—An OLE DB Primer � 213

Page 233: Learn OLE DB Development With Visual C++ 6.0

isolation across retaining commit and abort boundaries cannot be sup-ported by this transaction implementation or isoFlags was not equal tozero.

XACT_E_NOTIMEOUTThis return code shows that a time-out was specified, but time-outs arenot supported.

XACT_E_TMNOTAVAILABLEThis return code means unable to connect to the transaction manager orthe transaction manager is unavailable.

XACT_E_XTIONEXISTSThis return code states that the enlistment request failed for one of thefollowing reasons:

� This session can handle only one extant transaction at a time, andthere is presently such a transaction.

� *punkTransactionCoord was NULL, and the session is enlisted in acoordinated transaction with uncommitted work.

IRowsetIndex

IRowsetIndex is the primary interface for exposing index functionality in OLEDB. The IRowsetIndex interface is implemented by OLE DB providers toexpose the functionality of a file access method such as a B+-tree, or linearhash. For integrated indexes, IRowsetIndex is exposed on the base tablerowset; 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 arange of index entries to be read, to position at an index entry within therange, 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 Comments

Open an index This operation serves to get a handle to an IRowsetIndexobject. The OLE DB consumer calls IOpenRowset::Open-Rowset, passing it the DBID of the index. The method returnsa pointer to an index rowset.

Close an index This operation ensures an index is closed by releasing allreferences to the index rowset.

Insert an index entry This operation inserts new key entries into an index by usingIRowsetChange.

Delete an index entry This operation deletes key entries from an index by usingIRowsetChange.

214 � Chapter Four—An OLE DB Primer

Page 234: Learn OLE DB Development With Visual C++ 6.0

Operation Comments

Update an index entry This operation updates key entries in an index by first deletingthe old index entry and then inserting a new index entry.

Traverse the index This operation states that to traverse an index, a user callsmethods on IRowset.

Method Description

GetIndexInfo This method returns information about the index rowsetcapabilities.

Seek This method allows direct positioning at a key value withinthe current range.

SetRange This method restricts the set of row entries visible throughcalls 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 numberof key columns in the index.

prgIndexColumnDesc [out]This parameter shows a pointer to memory in which to return an array ofDBINDEXCOLUMNDESC structures in key column order. The size of thearray is equal to *pcKeyColumns. If an error occurs, *prgIndexColumnDescis set to a null pointer.

pcIndexProperties [out]This parameter indicates a pointer to memory in which to return thenumber of DBPROPSET structures returned in *prgIndexProperties.*pcIndexProperties is the total number of property sets for which the OLEDB provider supports at least one property in the Index property group. Ifan error occurs, *pcIndexProperties is set to zero.

prgIndexProperties [out]This parameter specifies a pointer to memory in which to return an arrayof DBPROPSET structures. One structure is returned for each property setthat contains at least one property belonging to the Index property group.

Chapter Four—An OLE DB Primer � 215

Page 235: Learn OLE DB Development With Visual C++ 6.0

The OLE DB provider allocates memory for the structures and returns theaddress to this memory; the OLE DB consumer releases this memory withIMalloc::Free when it no longer needs the structures. Before callingIMalloc::Free for *prgPropertySets, the OLE DB consumer should callIMalloc::Free for the rgProperties element within each element of*prgPropertySets. If *pcIndexProperties is zero on output, or if an erroroccurs, the OLE DB provider does not allocate any memory and ensuresthat *prgIndexProperties is a null pointer on output.

It has the following return codes:

S_OKThis return code means the method succeeded.

E_FAILThis return code shows that an OLE DB provider-specific error occurred.

E_INVALIDARGThis return code indicates that *pcKeyColumns, *prgIndexColumnDesc,

*pcIndexProperties, or *prgIndexProperties was a null pointer.

E_OUTOFMEMORYThis return code states that the OLE DB provider was unable to allocatesufficient memory in which to return the column description structures orproperties of the index.

DB_E_NOINDEXThis return code specifies that the rowset uses integrated indexes andthere is no current index.

IRowsetIndex::Seek

This method allows direct positioning at a key value within the current rangeestablished by the IRowsetIndex::SetRange method. The Seek method pro-vides the caller more control over the traversal of an index. Consider arelational query processor component implementing a merge join over inputsR1 and R2. R1, the outer input, is a rowset ordered by the joining columnR1.x. R2, the inner input, is an indexed rowset on column R2.x. Suppose thatR1.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 from20 to 100 knowing the values of the input R1.x. In some cases, this strategycould be cost effective.

It has the following syntax:

HRESULT Seek (HACCESSOR hAccessor,ULONG cKeyValues,void * pData,DBSEEK dwSeekOptions);

216 � Chapter Four—An OLE DB Primer

Page 236: Learn OLE DB Development With Visual C++ 6.0

It has the following parameters:

hAccessor [in]This parameter gives the handle of the OLE DB accessor to use. This OLEDB accessor must meet the following criteria, which are illustrated with akey that consists of columns A, B, and C, where A is the most significantcolumn and C is the least significant column:

� For each key column this OLE DB accessor binds, it must also bind allmore significant key columns. For example, the OLE DB accessor canbind column A, columns A and B, or columns A, B, and C.

� Key columns must be bound in order from most significant key col-umn to least significant key column. For example, if the OLE DBaccessor binds columns A and B, then the first binding must bind col-umn A and the second binding must bind column B.

� If the OLE DB accessor binds any non-key columns, key columns mustbe bound first. For example, if the OLE DB accessor binds columns A,B, and the bookmark column, then the first binding must bind columnA, the second binding must bind column B, and the third binding mustbind the bookmark column.

If the OLE DB accessor does not meet these criteria, the method returnsDB_E_BADBINDINFO or a status of DBSTATUS_E_BADACCESSOR for theoffending column.

If hAccessor is the handle of a null accessor (cBindings in IAccessor::CreateAccessor was zero), then Seek does not change the next fetchposition.

cKeyValues [in]This parameter shows the number of bindings in hAccessor for which*pData contains valid data. SetRange retrieves data from the firstcKeyValues key columns from *pData. For example, suppose the OLE DBaccessor binds columns A, B, and C of the key in the previous exampleand 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 toseek, at offsets that correspond to the bindings in the OLE DB accessor.

dwSeekOptions [in]This parameter gives a bitmask describing the options for the Seekmethod. The values in DBSEEKENUM have the following meanings:

Value Description

DBSEEK_FIRSTEQ First key with values equal to the values in *pData.

DBSEEK_LASTEQ Last key with values equal to the values in *pData.

DBSEEK_GE First key with values greater than or equal to the values in*pData.

Chapter Four—An OLE DB Primer � 217

Page 237: Learn OLE DB Development With Visual C++ 6.0

Value Description

DBSEEK_GT First key with values greater than the values in *pData.

DBSEEK_LE First key with values less than or equal to the values in*pData.

DBSEEK_LT First key with values less than the values in *pData.

It has the following return codes:

S_OKThis return code means the method succeeded.

E_FAILThis return code shows that an OLE DB provider-specific error occurred.

E_INVALIDARGThis return code indicates that dwSeekOptions was invalid, hAccessor wasthe handle of a null accessor, cKeyValues was zero, or *pData was a nullpointer.

DB_E_BADACCESSORHANDLEThis return code means that hAccessor was invalid.

DB_E_BADACCESSORTYPEThis return code provides that the specified accessor was not a rowaccessor.

DB_E_ERRORSOCCURREDThis return code specifies that an error occurred while transferring datafor one or more key columns. To determine the columns for which valueswere invalid, the OLE DB consumer checks the status values.

DB_E_NOINDEXThis return code shows that the rowset uses integrated indexes and thereis no current index.

DB_E_NOTFOUNDThis return code indicates that no key value matching the described char-acteristics could be found within the current range.

DB_E_NOTREENTRANTThis return code shows that the OLE DB consumer called this methodwhile it was processing a notification, and it is an error to call thismethod while processing the specified DBREASON value.

If this method performs deferred accessor validation and that validation takesplace before any data is transferred, it can also return any of the followingreturn codes for the reasons listed in the corresponding DBBINDSTATUS val-ues in IAccessor::CreateAccessor:

E_NOINTERFACEDB_E_BADBINDINFODB_E_BADORDINAL

218 � Chapter Four—An OLE DB Primer

Page 238: Learn OLE DB Development With Visual C++ 6.0

DB_E_BADSTORAGEFLAGSDB_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 indexcontaining a contiguous set of key values. The *pStartData and *pEndData

values always specify the starting and ending positions in the range, respec-tively. Thus, for an ascending index, *pStartData contains the smaller valueand *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 thecurrent 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 follow-ing 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 signifi-cant column:

� For each key column this OLE DB accessor binds, it must also bind allmore significant key columns. For example, the OLE DB accessor canbind column A, columns A and B, or columns A, B, and C.

� Key columns must be bound in order from most significant key col-umn to least significant key column. For example, if the OLE DBaccessor binds columns A and B, then the first binding must bind col-umn A and the second binding must bind column B.

� If the OLE DB accessor binds any nonkey columns, key columns mustbe bound first. For example, if the OLE DB accessor binds columns A,B, and the bookmark column, then the first binding must bind columnA, the second binding must bind column B, and the third binding mustbind the bookmark column.

Chapter Four—An OLE DB Primer � 219

Page 239: Learn OLE DB Development With Visual C++ 6.0

If the OLE DB accessor does not meet these criteria, the method returnsDB_E_BADBINDINFO or a status of DBSTATUS_E_BADACCESSOR for theoffending column. If hAccessor is the handle of a null accessor (cBindingsin IAccessor::CreateAccessor was zero), then SetRange does not set arange.

cStartKeyColumns [in]This parameter gives the number of bindings in hAccessor for which*pStartData contains valid data. SetRange retrieves data from the firstcStartKeyValues key columns from *pStartData. For example, suppose theOLE DB accessor binds columns A, B, and C of the key in the previousexample and cStartKeyValues is 2. SetRange retrieves data for columns Aand B.

pStartData [in]This parameter provides a pointer to a buffer containing the starting keyvalues of the range, at offsets that correspond to the bindings in the OLEDB accessor.

cEndKeyColumns [in]This parameter indicates the number of bindings in hAccessor for which*pEndData contains valid data. The SetRange method retrieves data fromthe first cEndKeyValues key columns from *pEndData. For example, sup-pose the OLE DB accessor binds columns A, B, and C of the key in theprevious example and cEndKeyValues is 2. SetRange retrieves data for col-umns A and B.

pEndData [in]This parameter shows a pointer to a buffer containing the ending key val-ues of the range, at offsets that correspond to the bindings in the OLE DBaccessor.

dwRangeOptions [in]This parameter gives a bitmask describing the options of the range. Thevalues in DBRANGEENUM have the following meanings:

Value Description

DBRANGE_INCLUSIVESTART

This value shows that the start boundary is inclusive (thedefault).

DBRANGE_EXCLUSIVESTART

This value states that the start boundary is exclusive.

DBRANGE_INCLUSIVEEND

This value indicates that the end boundary is inclusive (thedefault).

DBRANGE_EXCLUSIVEEND

This value means the end boundary is exclusive.

DBRANGE_EXCLUDENULLS

This value excludes NULLs from the range.

220 � Chapter Four—An OLE DB Primer

Page 240: Learn OLE DB Development With Visual C++ 6.0

Value Description

DBRANGE_PREFIX This value specifies using *pStartData as a prefix. *pEndDatamust be a null pointer. Prefix matching can be specifiedentirely using the inclusive and exclusive flags. However,because prefix matching is an important common case, thisflag enables the OLE DB consumer to specify only the*pStartData values, and enables the OLE DB provider tointerpret this request quickly.

DBRANGE_MATCH This value indicates setting the range to all keys that match*pStartData. *pStartData must specify a full key. *pEndDatamust be a null pointer. Used for fast equality match.

DBRANGE_MATCH_N_MASK

This value is equal to 0xFF000000.

DBRANGE_MATCH_N_SHIFT

This value is equal to 24 to indicate the number of bits to shiftto get the number N.

It has the following return codes:

S_OKThis return code means the method succeeded.

E_FAILThis return code shows that an OLE DB provider-specific error occurred.

E_INVALIDARGThis return code states that dwRangeOptions was invalid; cStartKeyValues

was not zero and pStartData was a null pointer; cEndKeyValues was notzero and pEndData was a null pointer; or hAccessor was the handle of anull accessor.

DB_E_BADACCESSORHANDLEThis return code indicates that hAccessor was invalid.

DB_E_BADACCESSORTYPEThis return code means the specified OLE DB accessor was not a rowaccessor.

DB_E_ERRORSOCCURREDThis return code means that an error occurred while transferring data forone or more key columns. To determine the columns for which valueswere invalid, the OLE DB consumer checks the status values.

DB_E_NOINDEXThis return code shows that the rowset uses integrated indexes and thereis no current index. If this method performs deferred accessor validationand that validation takes place before any data is transferred, it can alsoreturn any of the following return codes for the reasons listed in the cor-responding DBBINDSTATUS values in IAccessor::CreateAccessor:

E_NOINTERFACEDB_E_BADBINDINFO

Chapter Four—An OLE DB Primer � 221

Page 241: Learn OLE DB Development With Visual C++ 6.0

DB_E_BADORDINALDB_E_BADSTORAGEFLAGSDB_E_UNSUPPORTEDCONVERSION

IErrorRecords

IErrorRecords is defined by OLE DB. It is used to add and retrieve records inan OLE DB Error object. Information is passed to and from OLE DB errorobjects in an ERRORINFO structure.

IErrorRecords is implemented by code in the OLE DB SDK. OLE DB consum-ers use this interface to retrieve information stored in the records of an OLEDB Error object. They call QueryInterface to get a pointer to this interfaceafter retrieving an OLE DB Error object with GetErrorInfo in the AutomationDLL.

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 Automa-tion DLL, they call QueryInterface on that object to get a pointer to thisinterface. If they create a new OLE DB Error object through a class factory orwith CoCreateInstance, they request that a pointer to this interface bereturned.

Method Description

AddErrorRecord This method adds a record to an OLE DB Error object.

GetBasicErrorInfo This method returns basic information about the error,such as the return code and provider-specific errornumber.

GetCustomErrorObject This method returns a pointer to an interface on thecustom error object.GetErrorInfoThis method returns anIErrorInfo interface pointer on the specified record.

GetErrorParameters This method returns the error parameters.

GetrRecordCount This method returns the count of records in the OLE DBerror object.

IErrorRecords::AddErrorRecord

This method adds a record to an OLE DB Error object. The method should beused only by OLE DB providers; there are no reasons for OLE DB consumersto use it.

Records are added to the top of the list. That is, the number of the newlyadded record is record 0 and the number of all other records is increased by1. AddErrorRecord adds a reference count on the custom error object. Afteradding a custom error object to a record in an OLE DB error object, the OLEDB provider must call Release on all interface pointers it holds on that cus-tom error object. This transfers ownership of the custom error object to the

222 � Chapter Four—An OLE DB Primer

Page 242: Learn OLE DB Development With Visual C++ 6.0

OLE DB error object. When it is released, the OLE DB error object will releaseall 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 containinginformation about the error. This structure is allocated and freed by theOLE DB consumer.

dwLookupID [in]This parameter shows the value used by the OLE DB provider’s errorlookup service in conjunction with the return code to identify the errordescription, Help file, and context ID for an error. This can be an OLE DBprovider-specific value, such as the dwMinor element of *pErrorInfo. Itcan also be a special value, IDENTIFIER_SDK_ERROR, that tells theimplementation of IErrorInfo that is shipped with the OLE DB SDK toignore the OLE DB provider’s lookup service and use the description sup-plied in the OLE DB SDK error resource DLL.

pdispparams [in]This parameter provides a pointer to the parameters for the error. This isa null pointer if there are no error parameters. The error parameters areinserted into the error text by the error lookup service. This structure isallocated and freed by the OLE DB consumer.

punkCustomError [in]This parameter gives an interface pointer to the custom error object. Thisis a null pointer if there is no custom object for the error.

dwDynamicErrorID[in]This parameter indicates that if the error lookup service uses staticerrors—that is, error information that is hard-coded in the lookup ser-vice—dwDynamicErrorID is zero.

If the error lookup service uses dynamic errors—that is, error informationthat 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 objectis released. Although it is not required, it is more efficient for all errorrecords in a single OLE DB error object to have the same dynamic error ID.

Chapter Four—An OLE DB Primer � 223

Page 243: Learn OLE DB Development With Visual C++ 6.0

It has the following return codes:

S_OKThis return code means the method succeeded.

E_INVALIDARGThis return code shows that pErrorInfo was a null pointer.

E_OUTOFMEMORYThis return code states that the OLE DB error object was unable to allo-cate sufficient memory with which to add a new record.

IErrorRecords::GetBasicErrorInfo

This method returns basic information about the error, such as the returncode and OLE DB provider-specific error number. The method should be usedonly by OLE DB consumers; there are no reasons for OLE DB providers to useit.

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 toreturn information.

pErrorInfo [out]This parameter shows a pointer to an ERRORINFO structure in which toreturn basic error information. This structure is allocated and freed by theOLE DB consumer.

It has the following return codes:

S_OKThis return code means the method succeeded.

E_INVALIDARGThis return code states that pErrorInfo was a null pointer.

DB_E_BADRECORDNUMThis return code shows that ulRecordNum, which is zero-based, wasgreater than or equal to the count, which is one-based, of recordsreturned by GetRecordCount.

224 � Chapter Four—An OLE DB Primer

Page 244: Learn OLE DB Development With Visual C++ 6.0

IErrorRecords::GetCustomErrorObject

This method returns a pointer to an interface on the custom error object. Themethod should be used only by OLE DB consumers; there are no reasons forOLE DB providers to use it.

It has the following syntax:

HRESULT GetCustomErrorObject (ULONG ulRecordNum,REFIID riid,IUnknown ** ppObject);

It has the following parameters:

ulRecordNum [in]This parameter gives the zero-based number of the record for which toreturn 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 inter-face 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_OKThis return code means the method succeeded.

E_INVALIDARGThis return code shows that *ppObject was a null pointer.

E_NOINTERFACEThis return code indicates that the custom error object did not supportthe interface specified in riid.

DB_E_BADRECORDNUMThis return code means that ulRecordNum, which is zero-based, wasgreater than or equal to the count, which is one-based, of recordsreturned 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 reasonsfor OLE DB providers to use it.

Chapter Four—An OLE DB Primer � 225

Page 245: Learn OLE DB Development With Visual C++ 6.0

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 toreturn an IErrorInfo interface pointer.

lcid [in]This parameter provides the locale ID for which to return error informa-tion. The parameter is checked when it is passed to methods inIErrorInfo.

ppErrorInfo [out]This parameter shows a pointer to memory in which to return a pointerto an IErrorInfo interface on the specified record. This IErrorInfo inter-face pointer is different from the IErrorInfo interface pointer exposed onthe OLE DB error object with Queryinterface.

It has the following return codes:

S_OKThis return code means the method succeeded.

E_INVALIDARGThis return code shows that *ppErrorInfo was a null pointer.

DB_E_BADRECORDNUMThis return code indicates that ulRecordNum, which is zero-based, wasgreater than or equal to the count, which is one-based, of recordsreturned by GetRecordCount.

IErrorRecords::GetErrorParameters

This method returns the error parameters. The method is used by OLE DBconsumers only when the meaning of the error parameters is known to theOLE DB consumer; error parameters are generally passed to the error lookupservice and incorporated into error messages by the OLE DB provider throughthat lookup service. There is no reason for OLE DB providers to use thismethod.

It has the following syntax:

HRESULT GetErrorParameters (ULONG ulRecordNum,DISPPARAMS * pdispparams);

226 � Chapter Four—An OLE DB Primer

Page 246: Learn OLE DB Development With Visual C++ 6.0

It has the following parameters:

ulRecordNum[in]This parameter gives the zero-based number of the record for which toreturn parameters.

pdispparams [out]This parameter shows a pointer to a DISPPARAMS structure in which toreturn the error parameters. The OLE DB consumer allocates the memoryfor the DISPPARAMS structure itself, but the OLE DB provider allocatesthe memory for any arrays pointed to by elements of the DISPPARAMSstructure.

It has the following return codes:

S_OKThis return code means the method succeeded.

E_INVALIDARGThis return code indicates that *pdispparams was a null pointer.

E_OUTOFMEMORYThis return code shows that the provider was unable to allocate sufficientmemory in which to return the data pointed to by elements of*pdispparams.

DB_E_BADRECORDNUMThis return code means that ulRecordNum, which is zero-based, wasgreater than or equal to the count, which is one-based, of recordsreturned by GetRecordCount.

IErrorRecords::GetRecordCount

This method returns the count of records in the OLE DB Error object. Themethod should be used only by OLE DB consumers; there are no reasons forOLE 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 oferror records. This is a one-based count, although error records arezero-based. If *pcRecords is zero, there are no records in the OLE DB errorobject and calls to GetBasicErrorInfo, GetErrorInfo, GetErrorParameters,and GetCustomErrorObject will return DB_E_BADRECORDNUM.

Chapter Four—An OLE DB Primer � 227

Page 247: Learn OLE DB Development With Visual C++ 6.0

It has the following return codes:

S_OKThis return code means the method succeeded.

E_INVALIDARGThis return code shows that *pcRecords was a null pointer.

IErrorLookup

IErrorLookup is used by OLE DB error objects to determine the values of theerror message, source, Help file path, and context ID based on the returncode and a provider-specific error number.

IErrorLookup is exposed by an OLE DB provider-specific error lookup servicethat is mandatory for all OLE DB providers that return OLE DB error objects.All OLE DB providers that return OLE DB error objects must implementIErrorLookup in a separate lookup service.

IErrorLookup is called by the code shipped in the OLE DB SDK that imple-ments OLE DB error objects. It should not be called by general OLE DBconsumers. When an error occurs, the following sequence of events takesplace:

1. The OLE DB provider creates an OLE DB error object and adds a record tothat object.

2. The OLE DB consumer retrieves the error object and gets an IErrorInfointerface on a particular record in that object. It then calls a method inIErrorInfo.

3. Except for the IErrorInfo::GetGUID method, the OLE DB error objectcode, which is shipped with the OLE DB SDK, loads the error lookup ser-vice based on the class ID stored in the error record.

4. The OLE DB error object code returns the hrError element of theERRORINFO structure, the lookup ID, and the error parameters from theerror record. It also returns the locale ID requested inIErrorRecords::GetErrorInfo. It passes all of this information to the appro-priate IErrorLookup method.

5. The IErrorLookup method returns the requested information, based onthe 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 theOLE DB consumer.

7. The OLE DB consumer releases the OLE DB error object.

8. The OLE DB error object code calls IErrorLookup::ReleaseErrors for allerror records with a nonzero dynamic error ID to release the error infor-mation associated with that record.

228 � Chapter Four—An OLE DB Primer

Page 248: Learn OLE DB Development With Visual C++ 6.0

Method Description

GetErrorDescription This method returns the error message and source, basedon the return code and the OLE DB provider-specificerror number.

GetHelpInfo This method returns the path of the Help file and thecontext ID of the topic that explains the error.

ReleaseErrors This method releases any dynamic error informationassociated with a dynamic error ID.

IErrorLookup::GetErrorDescription

This method returns the error message and source, based on the return codeand 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 theerror.

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 descriptionand source.

pbstrSource [out]This parameter provides a pointer to memory in which to return a pointerto 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 allo-cated by the OLE DB provider and must be freed by the OLE DBconsumer with a call to SysFreeString.

pbstrDescription [out]This parameter gives a pointer to memory in which to return a pointer toa string that describes the error. If *pdispparams was not a null pointer,

Chapter Four—An OLE DB Primer � 229

Page 249: Learn OLE DB Development With Visual C++ 6.0

then the error parameters are integrated into this description. If there isno error description or an error occurs, the returned value(*pbstrDescription) is a null pointer. The memory for this string is allo-cated by the OLE DB provider and must be freed by the consumer with acall to SysFreeString.

It has the following return codes:

S_OKThis return code means the method succeeded.

E_FAILThis return code shows that an OLE DB provider-specific error occurred.

E_INVALIDARGThis return code indicates that *pbstrSource or *pbstrDescription was anull pointer.

E_OUTOFMEMORYThis return code states that the OLE DB provider was unable to allocatesufficient memory in which to return the error source or description.

DB_E_BADHRESULTThis return code means that hrError was invalid.

DB_E_BADLOOKUPIDThis return code shows that dwLookupID was invalid.

DB_E_NOLOCALEThis return code specifies that the locale ID specified in lcid was not sup-ported by the OLE DB provider.

IErrorLookup::GetHelpInfo

This method returns the path of the Help file and the context ID of the topicthat 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 theerror.

230 � Chapter Four—An OLE DB Primer

Page 250: Learn OLE DB Development With Visual C++ 6.0

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 filepath and context ID.

pbstrHelpFile [out]This parameter specifies a pointer to memory in which to return a pointerto a string containing the fully qualified path of the Help file. If there isno Help file or an error occurs, the returned value (*pbstrHelpFile) is anull pointer. The memory for this string is allocated by the OLE DB pro-vider and must be freed by the OLE DB consumer with a call toSysFreeString.

pdwHelpContext [out]This parameter gives a pointer to memory in which to return the Helpcontext ID for the error. If there is no Help file (*pbstrHelpFile is a nullpointer), then the returned value has no meaning.

It has the following return codes:

S_OKThis return code means the method succeeded.

E_FAILThis return code shows that an OLE DB provider-specific error occurred.

E_INVALIDARGThis return code states that *pbstrHelpFile or *pdwHelpContext was a nullpointer.

E_OUTOFMEMORYThis return code indicates that the OLE DB provider was unable to allo-cate sufficient memory in which to return the Help file path.

DB_E_BADHRESULTThis return code shows that hrError was invalid.

DB_E_BADLOOKUPIDThis return code means thatr dwLookupID was invalid.

DB_E_NOLOCALEThis return code specifies that the locale ID specified in lcid was not sup-ported by the OLE DB provider.

IErrorLookup::ReleaseErrors

This method releases any dynamic error information associated with adynamic error ID. Dynamic error information is created at run time. It isreleased when the OLE DB error object calls ReleaseErrors with the ID of theerror information to release. Although it is not required, it is more efficientfor OLE DB providers to use the same error ID for all records in a single error

Chapter Four—An OLE DB Primer � 231

Page 251: Learn OLE DB Development With Visual C++ 6.0

object. This allows ReleaseErrors to release all of this information in a singlecall.

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_OKThis return code means the method succeeded.

E_FAILThis return code indicates that an OLE DB provider-specific erroroccurred.

DB_E_BADDYNAMICERRORIDThis return code shows that dwDynamicErrorID was invalid.

ISQLErrorInfo

ISQLErrorInfo is used to return the SQLSTATE and native error code. OLE DBproviders for SQL databases can implement ISQLErrorInfo on custom errorobjects returned with the OLE DB error object.

OLE DB consumers call the methods on ISQLErrorInfo to retrieve theSQLSTATE and native error code. To retrieve a custom error object, an OLEDB consumer calls IErrorRecords::GetCustomErrorObject.

Method Description

GetSQLInfo This method returns the SQLSTATE and native error codeassociated with an error.

ISQLErrorInfo::GetSQLInfo

This method returns the SQLSTATE and native error code associated with anerror.

It has the following syntax:

HRESULT GetSQLInfo (BSTR * pbstrSQLState,LONG * plNativeError);

232 � Chapter Four—An OLE DB Primer

Page 252: Learn OLE DB Development With Visual C++ 6.0

It has the following parameters:

pbstrSQLState [out]This parameter gives a pointer to memory in which to return a pointer toa string that contains the SQLSTATE. An SQLSTATE is a five-characterstring defined by the ANSI SQL standard. The memory for this string isallocated by the OLE DB provider and must be freed by the OLE DB con-sumer with a call to SysFreeString. If an error occurs, *pbstrSQLState isset to a null pointer.

plNativeError [out]This parameter specifies a pointer to memory in which to return an OLEDB provider-specific, native error code. *plNativeError is not necessarilythe same as the dwMinor element in the ERRORINFO structure returnedby IErrorRecords::GetErrorInfo. The combination of the hrError anddwMinor elements of the ERRORINFO structure is used to identify anerror to the error lookup service, whereas *plNativeError has no suchrestrictions.

It has the following return codes:

S_OKThis return code means the method succeeded.

E_FAILThis return code indicates that an OLE DB provider-specific erroroccurred.

E_INVALIDARGThis return code shows that pbstrSQLState or plNativeError was a nullpointer.

E_OUTOFMEMORYThis return code states that the OLE DB provider was unable to allocatesufficient memory in which to return the SQLSTATE.

Where We Go From HereWhere We Go From Here

OLE DB is supported by the most powerful database technology yet to comefrom Microsoft: ActiveX Data Objects, or ADO. The next chapter gives you acomplete primer on using these essential elements of the OLE DB picture.

Chapter Four—An OLE DB Primer � 233

Page 253: Learn OLE DB Development With Visual C++ 6.0
Page 254: Learn OLE DB Development With Visual C++ 6.0

Chapter Five

An ADO PrimerAn ADO Primer

Microsoft has two high-level database technologies: OLE DB (which isn’t anacronym) and MTS (which is, and stands for Microsoft Transaction Server).Both of these powerful database systems rely on a single technology to con-nect with end users: ActiveX Data Objects (ADO). ADO is a set of COMobjects (i.e., interfaces) designed to connect with database systems via theOLE DB technology, but users don’t need to know anything about OLE DB touse ADO. Instead, they simply create appropriate ADO objects and makemethod calls on them to connect with databases and obtain and set informa-tion. For OLE DB developers, ADO is the vehicle their users need to connectwith the OLE DB providers and consumers they create. For MTS program-mers, ADO is the system they use to interact with databases under theumbrella of MTS. (ADO itself has several advanced capabilities, includingRDS (Remote Data Service) and MD (Multi-Dimensional) database interac-tions, which we won’t go into here.)

Either way, database programmers using Microsoft technology need adetailed reference about ADO. This chapter provides that reference, with acomplete writeup on all the collections, objects, methods, and events of theADO system. Dive in, and your database development will never be the same!

ADO CollectionsADO Collections

Collections are groups of things that share at least one similar feature, like acoin collection. In ADO, collections are objects (i.e., interfaces) used to storeand provide access to groups of other ADO objects (i.e., interface pointers)that contain specific information or capabilities important to their owningADO element (like the Errors collection for the Connection object). Each suchcollection allows clients to access single elements from groups of IPs usingeither a compile-time name, a run-time string, or a run-time integer. Thisallows the ADO system to provide general information to its clients whilestaying inside the COM metaphor. The collections supported in ADO 2.0 areErrors, Fields, Parameters, and Properties. Each has its own section below. Inaddition, each collection object supports a single property, as explained

235

Page 255: Learn OLE DB Development With Visual C++ 6.0

below. Each specific collection will implement its own specialized methods aswell, with some overlap to the methods of other collections. Except for theErrors collection, each collection is fixed in number of elements and contentwhen 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 isthe number of objects (i.e., interface pointers) in the current collection. A valueof 0 indicates no interface pointers in the collection. Although Count is1-based, the Item method used to retrieve collection objects is 0-based, so loopsusing Count should subtract 1 from it (i.e., (forx=0;x++;x<MyCollection.Count)).

The Errors Collection

Any operation involving ADO objects can generate one or more errors. Aseach error occurs, one or more Error objects are placed in the Errors collec-tion of the current Connection object. When another ADO operationgenerates an error, the Errors collection is cleared, and the new set of Errorobjects is placed in the Errors collection. Each Error object represents a spe-cific OLE DB error, not an ADO run-time error (ADO errors are exposed to therun-time exception-handling mechanism). ADO operations that don’t gener-ate an error have no effect on the Errors collection. Developers can use theClear method to manually clear an Errors collection.The set of Error objectsin the Errors collection describes all errors that occurred in response to a sin-gle SQL statement. Enumerating the specific errors in the Errors collectionenables an application’s error-handling routines to more precisely determinethe cause and origin of an error, and take appropriate steps to recover. Someproperties and methods return warnings that appear as Error objects in theErrors collection but do not halt an application’s execution. Before an applica-tion calls the Resync, UpdateBatch, or CancelBatch methods on a Recordsetobject or the Open method on a Connection object, or sets the Filter propertyon a Recordset object, it should call the Clear method on the Errors collectionso that it can check the Count property of the Errors collection to test forreturned 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 applicationshould use the Clear method on the Errors collection to remove all existingError objects from the collection. When an error occurs, ADO automaticallyclears the Errors collection and fills it with Error objects based on the newerror.

236 � Chapter Five—An ADO Primer

Page 256: Learn OLE DB Development With Visual C++ 6.0

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 ordinalnumber. An application should use the Item method to return a specificobject in a collection. If the method cannot find an object in the collectioncorresponding 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:

IndexThis parameter is a Variant that evaluates either to the name or to theordinal 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 Fieldobject corresponds to a column in the Recordset. An application should popu-late the Fields collection before opening the Recordset by calling the Refreshmethod on the Fields collection.

The Fields collection supports four methods: Append, Delete, Item, andRefresh.

Append Method (Fields Collection)

This method appends a new Field object to the collection, which may be cre-ated before it is appended. An application must set the CursorLocationproperty to adUseClient before calling the Fields.Append method. Calling theFields.Append method for an open Recordset, or a Recordset where theActiveConnection property has been set, will cause an ADO error.

The method has the following syntax:

pfields->Append(Name, Type, DefinedSize, Attrib);

Chapter Five—An ADO Primer � 237

Page 257: Learn OLE DB Development With Visual C++ 6.0

The method has the following parameters:

NameThis 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.

TypeThis parameter is a DataTypeEnum, whose default value is adEmpty; itholds the data type of the new field.

DefinedSizeThis optional parameter is a Long and is the defined size in characters orbytes of the new field. The default value for this parameter is derivedfrom Type. (The default Type is adEmpty, and therefore the defaultDefinedSize is unspecified.)

AttribThis optional parameter is a FieldAttributeEnum that specifies attributesfor the new field. The default value for this parameter is adFldDefault. Ifthis value is not specified, the field will contain attributes derived fromType.

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:

FieldThis parameter is a Variant designating the Field object to delete, whichmust be the name of the Field object; it cannot be an ordinal position orthe Field object itself.

Item Method (Fields Collection)

This method returns a specific member of a collection by name or ordinalnumber. An application should use the Item method to return a specificobject in a collection. If the method cannot find an object in the collectioncorresponding 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.

238 � Chapter Five—An ADO Primer

Page 258: Learn OLE DB Development With Visual C++ 6.0

The method has the following parameter:

IndexThis parameter is a Variant that evaluates either to the name or to theordinal 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 objectsavailable 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 visi-ble effect. To retrieve changes from the underlying database structure, anapplication must use either the Requery method or, if the Recordset objectdoes 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’sParameters collection retrieves parameter information for the stored proce-dure or parameterized query specified in the Command object. Someproviders do not support stored procedure calls or parameterized queries;calling the Refresh method on the Parameters collection when using such aprovider will return an error. If an application has not defined its own Param-eter objects and it accesses the Parameters collection before calling theRefresh method, ADO will automatically call the method and populate thecollection for it. For this reason, an application can minimize calls to the pro-vider to improve performance if it knows the properties of the parametersassociated with the stored procedure or parameterized query it wishes to call.An application can use the CreateParameter method to create Parameterobjects with the appropriate property settings and use the Append method toadd them to the Parameters collection. This lets the application set and returnparameter values without having to call the provider for the parameter infor-mation. If an application is writing to a provider that does not supply

Chapter Five—An ADO Primer � 239

Page 259: Learn OLE DB Development With Visual C++ 6.0

parameter information, it must manually populate the Parameters collectionusing the CreateParameter method to be able to use parameters at all. Anapplication should use the Delete method to remove Parameter objects fromthe Parameters collection if necessary.

The Parameters collection supports four methods: Append, Delete, Item, andRefresh.

Append Method (Parameters Collection)

Use the Append method to add a new Parameter object to the Parameters col-lection of a Command object. The application must set the Type property ofthe Parameter object before appending it to the Parameters collection. If anapplication selects a variable-length data type, it must also set the Size prop-erty to a value greater than zero.

The method has the following syntax:

pParameters->Append(object);

The method has the following parameter:

objectThis parameter is the Parameter interface pointer to be appended.

Delete Method (Parameters Collection)

This method deletes an object from the Parameters collection. The applica-tion must use the Parameter object’s Name property or its collection indexwhen 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:

IndexThis parameter is a String representing the name of the Parameter objectthe application wants to delete, or the object’s ordinal position (index) inthe collection.

Item Method (Parameters Collection)

This method returns a specific member of a collection by name or ordinalnumber. An application should use the Item method to return a specificobject in a collection. If the method cannot find an object in the collectioncorresponding to the Index argument, an ADO error occurs.

The method has the following syntax:

pParameter = pParameterscollection->Item(Index);

240 � Chapter Five—An ADO Primer

Page 260: Learn OLE DB Development With Visual C++ 6.0

The method returns an interface pointer to an ADO Parameter object.

The method has the following parameter:

IndexThis parameter is a Variant that evaluates either to the name or to theordinal 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 collectionretrieves provider-side parameter information for the stored procedure orparameterized query specified in the Command object. The collection will beempty for providers that do not support stored procedure calls orparameterized queries.

An application should set the ActiveConnection property of the Commandobject to a valid Connection object, the CommandText property to a validcommand, and the CommandType property to adCmdStoredProc before call-ing the Refresh method. If an application accesses the Parameters collectionbefore calling the Refresh method, ADO will automatically call the methodand 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 informa-tion from the provider and it returns one or more variable-length data typeParameter objects, ADO may allocate memory for the parameters based ontheir maximum potential size, which will cause an error when executed. Anapplication should explicitly set the Size property for these parameters beforecalling the Execute method to prevent errors.

The Properties Collection

Some ADO objects (interface pointers) have a Properties collection made upof Property objects. Each Property object corresponds to a characteristic ofthe ADO object specific to the provider currently in use.

The Properties collection supports two methods: Item and Refresh.

Chapter Five—An ADO Primer � 241

Page 261: Learn OLE DB Development With Visual C++ 6.0

Item Method (Properties Collection)

This method returns a specific member of a collection by name or ordinalnumber. An application should use the Item method to return a specificobject in a collection. If the method cannot find an object in the collectioncorresponding 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:

IndexThis parameter is a Variant that evaluates either to the name or to theordinal 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 popu-lates the collection with the dynamic properties the provider exposes in theform of Property interface pointers (objects). These properties provide infor-mation about functionality specific to the provider beyond the built-inproperties ADO supports; consult the specific OLE DB provider documenta-tion 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 (anOLE DB provider). In the case of a client/server database system, it may bean actual network connection to the server. To execute a query without usinga Command object, an application can pass a query string to the Executemethod of a Connection object. (A Command object is still required when anapplication wants to persist the command text and re-execute it, or use queryparameters.) Applications can create Connection objects independently ofany other previously defined object.

242 � Chapter Five—An ADO Primer

Page 262: Learn OLE DB Development With Visual C++ 6.0

One of the more interesting uses of Connection objects is that they permit anapplication to execute commands or stored procedures as if they were nativemethods on the Connection object. To execute such a command, an applica-tion must give the command a name using the Command object Nameproperty, then set the Command object’s ActiveConnection property to theconnection. Next, the application must include a program statement wherethe 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 arereturned. The application can also set the recordset properties to customizethe resulting recordset. To execute a stored procedure, an application simplymust contain a program statement where the stored procedure name is usedas 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, anapplication can do any or all of the following:

� Configure the connection before opening it with the ConnectionString,ConnectionTimeout, and Mode properties.

� Set the CursorLocation property to invoke the client cursor provider,which supports batch updates.

� Set the default database for the connection with the DefaultDatabaseproperty.

� Set the level of isolation for the transactions opened on the connectionwith the IsolationLevel property.

� Specify an OLE DB provider with the Provider property.

� Establish, and later break, the physical connection to the data source withthe Open and Close methods.

� Execute a command on the connection with the Execute method and con-figure the execution with the CommandTimeout property.

� Manage transactions on the open connection, including nested transac-tions if the provider supports them, with the BeginTrans, CommitTrans,and RollbackTrans methods and the Attributes property.

� Examine errors returned from the data source with the Errors collection.

� Read the version from the ADO implementation in use with the Versionproperty.

� Obtain schema information about its database with the OpenSchemamethod.

Warning Depending on the functionality supported by the OLE DB providerin use, some collections, methods, or properties of a Connection object maynot be available.

Chapter Five—An ADO Primer � 243

Page 263: Learn OLE DB Development With Visual C++ 6.0

Each of the properties and methods of the Connection object is discussedbelow.

Attributes Property (Connection Object)

This property is read/write. It sets or returns a Long value, which is the sumof any one or more of the XactAttributeEnum values in Table 5-1 (default iszero).

Table 5-1 Connection object XactAttributeEnum values

Constant Description

adXactCommitRetaining This value indicates that calling CommitTrans automaticallystarts a new transaction. Not all providers support thisfeature.

adXactAbortRetaining This value indicates that calling RollbackTrans automaticallystarts a new transaction. Not all providers support thisfeature.

CommandTimeout Property (Connection Object)

This property indicates how long the ADO run-time system should wait whileexecuting a command before terminating the attempt and generating anerror (presumably due to a network connection failure). The property isread/write (even after a connection is opened), with a default value of 30seconds. If the interval set in the CommandTimeout property elapses beforethe command completes execution, an error occurs and ADO cancels thecommand. If an application sets the property to zero, ADO will wait indefi-nitely until the execution is complete.

Note The CommandTimeout setting on a Connection object has no effect onthe CommandTimeout setting on a Command object on the same Connection;that is, the Command object’s CommandTimeout property does not inheritthe value of the Connection object’s CommandTimeout value.

Warning If an application uses CommandTimeout functionality without sup-port from the underlying OLE DB provider, results are unpredictable.

ConnectionString Property (Connection Object)

This property contains the information used to establish a connection to adata source (i.e., an OLE DB provider). The property is a String, and isread/write until a connection is opened and read-only thereafter. Applica-tions use the ConnectionString property to specify a data source by passing adetailed series of argument = value statements separated by semicolons, asexplained below. After an application has set the ConnectionString property

244 � Chapter Five—An ADO Primer

Page 264: Learn OLE DB Development With Visual C++ 6.0

and opened the Connection object, the provider may alter the contents of theproperty by mapping the ADO-defined argument names to their providerequivalents. The ConnectionString property automatically inherits the valueused for the ConnectionString argument of the Open method, so an applica-tion can override the current ConnectionString property during the Openmethod call. ADO supports four arguments for the ConnectionString property,which are listed in Table 5-2; any other arguments pass directly to the pro-vider without any processing by ADO.

Table 5-2 ConnectionString arguments processed by ADO

Argument Description

Provider= This argument specifies the name of an OLE DB provider to usefor the connection.

File Name= This argument specifies the name of a provider-specific filecontaining preset connection information.

Remote Provider= This argument specifies the name of a provider to use whenopening a client-side connection. This argument is used by theRemote Data Service only.

Remote Server= This argument specifies the path name of the server to use whenopening a client-side connection. This argument is used by theRemote Data Service only.

Warning Because the File Name= argument causes ADO to load the associ-ated 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 estab-lishing a connection before terminating the attempt and generating an error(usually due to a failed network transmission). The property is a Long valuewith a default of 15 seconds; the property is read/write until a connection isopened 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 sup-port 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 byADO, based on various cursor libraries accessible to the OLE DB provider inuse. The property is a Long value that is read/write regardless of whether aconnection is open or not, but the setting affects connections established onlyafter the property has been set; changing this property has no effect on

Chapter Five—An ADO Primer � 245

Page 265: Learn OLE DB Development With Visual C++ 6.0

existing connections. The cursor obtained via an Execute call will automati-cally inherit the setting of this property. Table 5-3 gives the possible values ofthis property:

Table 5-3 Connection object CursorLocation property values

Constant Description

adUseNone This value indicates that no cursor services are used; it is obsolete andshould be used only for the sake of backward compatibility with legacycode.

adUseClient This value indicates that the connection uses client-side cursorssupplied by a local cursor library. Since local cursor engines often willallow features that driver-supplied cursors may not, using this settingmay enable extra features. (adUseClientBatch is an alternative valuefor this constant.)

adUseServer This value is the default; it indicates that the connection uses OLE DBprovider- or driver-supplied cursors. Some features of the MicrosoftClient Cursor Provider (such as disassociated recordsets) cannot besimulated with server-side cursors, but other provider-specificfeatures may be supported instead.

DefaultDatabase Property (Connection Object)

This property indicates the default database for a Connection object. Thisproperty is a String that evaluates to the name of a database available fromthe current OLE DB provider. If there is a default database, SQL strings sentas commands may use an unqualified syntax to access objects in that data-base. To access objects in a database other than the one specified in theDefaultDatabase property, SQL strings must qualify object names with thedesired database name. Upon connection, the provider will write defaultdatabase information to the DefaultDatabase property if it uses the property.

Warning Some OLE DB providers allow only one database per connection, inwhich case an application cannot change the DefaultDatabase property. Also,some OLE DB providers may not support this feature, and will return an erroror an empty string.

IsolationLevel Property (Connection Object)

This property indicates the level of isolation for a Connection object. Theproperty contains one or more of the IsolationLevelEnum values shown inTable 5-4 below. The setting does not take effect until the next time an appli-cation calls the BeginTrans method.

246 � Chapter Five—An ADO Primer

Page 266: Learn OLE DB Development With Visual C++ 6.0

Warning If the level of isolation an application requests is unavailable, the OLEDB provider may return the next greater level of isolation.

Table 5-4 Connection object IsolationLevelEnum values

Constant Description

adXactUnspecified This value indicates that the provider is using a differentisolation level than specified, but that the level cannot bedetermined.

adXactChaos This value is the default; it indicates that an applicationcannot overwrite pending changes from more highlyisolated transactions.

adXactBrowse This value indicates that from one transaction anapplication can view uncommitted changes in othertransactions.

adXactReadUncommitted This value is the same as adXactBrowse.

adXactCursorStability This value indicates that from one transaction anapplication can view changes in other transactions onlyafter they’ve been committed.

adXactReadCommitted This value is the same as adXactCursorStability.

adXactRepeatableRead This value indicates that from one transaction anapplication cannot see changes made in othertransactions, but that requerying can bring newrecordsets.

adXactIsolated This value indicates that transactions are conducted inisolation of other transactions.

adXactSerializable This value is the same as adXactIsolated.

Mode Property (Connection Object)

This property indicates the available permissions for modifying data in a con-nection. The property is one of the ConnectModeEnum values listed in Table5-5 below; it is read/write when the connection is not open and read-onlywhen a connection is open.

Table 5-5 Connection object ConnectModeEnum and ConnectOptionEnum values

Constant Description

adModeUnknown This value is the default, and indicates that thepermissions have not yet been set or cannot bedetermined.

adModeRead This value indicates read-only permissions.

adModeWrite This value indicates write-only permissions.

adModeReadWrite This value indicates read/write permissions.

Chapter Five—An ADO Primer � 247

Page 267: Learn OLE DB Development With Visual C++ 6.0

Table 5-5 Connection object ConnectModeEnum and ConnectOptionEnum values (cont.)

Constant Description

adModeShareDenyRead This value indicates that ADO prevents others fromopening the connection with read permissions.

adModeShareDenyWrite This value indicates that ADO prevents others fromopening the connection with write permissions.

adModeShareExclusive This value indicates that ADO prevents others fromopening the connection.

adModeShareDenyNone This value indicates that ADO prevents others fromopening the connection with any permissions.

Provider Property (Connection Object)

This property indicates the name of the OLE DB provider for a Connectionobject. The property is a String value that is read/write when a connection isclosed and read-only when it is open. If no provider is specified, the propertywill default to MSDASQL (Microsoft OLE DB provider for ODBC). The settingdoes not take effect until an application either opens the Connection object oraccesses the Properties collection of the Connection object.

Warning This property can also be set by the contents of the Connection-String property or the ConnectionString argument of the Open method; how-ever, specifying a provider in more than one place while calling the Openmethod can have unpredictable results. If an invalid setting is placed in theproperty, an ADO error occurs.

State Property (Connection Object)

This is a read-only Long property that indicates whether the Connectionobject is currently open (interacting with an OLE DB provider) or closed (notinteracting with an OLE DB provider). The possible values are listed in Table5-6.

Table 5-6 Connection object State property values

Constant Description

adStateClosed This value is the default; it indicates that the Connectionobject is closed.

adStateOpen 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 appli-cation should use it to determine which ADO implementation is currentlyavailable on a given client or server computer, and degrade gracefully if alesser version than expected is found.

248 � Chapter Five—An ADO Primer

Page 268: Learn OLE DB Development With Visual C++ 6.0

Note The version information of a given OLE DB provider will usually be avail-able as a dynamic Property object in the Properties collection.

BeginTrans Method (Connection Object)

An application should use the BeginTrans, CommitTrans, and RollbackTransmethods with a Connection object when it wants to save or cancel a series ofchanges made to the source data as a single unit. Once an application callsthe BeginTrans method, the OLE DB provider will no longer instantaneouslycommit any changes the application makes until it calls CommitTrans orRollbackTrans to end the transaction. For OLE DB providers that supportnested transactions, calling the BeginTrans method within an open transac-tion starts a new, nested transaction. The return value indicates the level ofnesting: A return value of 1 indicates the application has opened a top-leveltransaction (that is, the transaction is not nested within another transaction),2 indicates that the application has opened a second-level transaction (atransaction nested within a top-level transaction), and so forth. CallingCommitTrans or RollbackTrans affects only the most recently opened transac-tion; the application must close or roll back the current transaction before itcan resolve any higher-level transactions. (Note that rolling back a nestedtransaction does not automatically force a rollback of a higher-level one.)Calling the CommitTrans method saves changes made within an open trans-action on the connection and ends the transaction. Calling the RollbackTransmethod reverses any changes made within an open transaction and ends thetransaction. Calling either method when there is no open transaction gener-ates an ADO error.

Note Depending on the Connection object’s Attributes property, calling eitherthe CommitTrans or RollbackTrans methods may automatically start a newtransaction. If the Attributes property is set to adXactCommitRetaining, theOLE DB provider automatically starts a new transaction after a CommitTranscall. If the Attributes property is set to adXactAbortRetaining, the OLE DBprovider automatically starts a new transaction after a RollbackTrans call.

Warning Not all OLE DB providers support transactions. To determine if agiven OLE DB provider does support transactions, check that the provider-defined Transaction DDL property appears in the Connection object’s Prop-erties collection. If an application calls any of the transaction-orientedConnection object methods for an OLE DB provider that does not supporttransactions, an ADO error will result.

Chapter Five—An ADO Primer � 249

Page 269: Learn OLE DB Development With Visual C++ 6.0

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 indicatingthe nesting level of the transaction.

The method has no parameters.

Cancel Method (Connection Object)

Applications should use the Cancel method to terminate execution of anasynchronous Execute or Open method call (that is, the method was invokedwith the adConnectAsync, adExecuteAsync, or adFetchAsync option). Cancelwill return an ADO error if adRunAsync was not used in the terminatedmethod.

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 anyassociated system resources needed while it is active. Closing a Connectionobject does not remove it from memory; the application can change its prop-erty settings and open it again later. Using the Close method to close aConnection object also closes any active Recordset objects associated with theconnection. A Command object associated with the Connection object theapplication is closing will persist, but it will no longer be associated with aConnection object; that is, its ActiveConnection property will be set to Noth-ing. Also, the Command object’s Parameters collection will be cleared of anyprovider-defined parameters. An application can later call the Open methodto re-establish the connection to the same or another data source. While theConnection object is closed, calling any methods that require an open connec-tion to the data source will generate an ADO error. Closing a Connectionobject while there are open Recordset objects on the connection rolls backany pending changes in all of the Recordset objects. Explicitly closing a Con-nection object (calling the Close method) while a transaction is in progressgenerates an ADO error. If a Connection object falls out of scope while atransaction is in progress, ADO automatically rolls back the transaction.

The method has the following syntax:

pConnection->Close();

250 � Chapter Five—An ADO Primer

Page 270: Learn OLE DB Development With Visual C++ 6.0

The method has no parameters.

CommitTrans Method (Connection Object)

An application should use the BeginTrans, CommitTrans, and RollbackTransmethods with a Connection object when it wants to save or cancel a series ofchanges made to the source data as a single unit. Once an application callsthe BeginTrans method, the OLE DB provider will no longer instantaneouslycommit any changes the application makes until it calls CommitTrans orRollbackTrans to end the transaction. For OLE DB providers that supportnested transactions, calling the BeginTrans method within an open transac-tion starts a new, nested transaction. The return value indicates the level ofnesting: A return value of 1 indicates the application has opened a top-leveltransaction (that is, the transaction is not nested within another transaction),2 indicates that the application has opened a second-level transaction (atransaction nested within a top-level transaction), and so forth. CallingCommitTrans or RollbackTrans affects only the most recently opened transac-tion; the application must close or roll back the current transaction before itcan resolve any higher-level transactions. (Note that rolling back a nestedtransaction does not automatically force a rollback of a higher-level one.)Calling the CommitTrans method saves changes made within an open trans-action on the connection and ends the transaction. Calling the RollbackTransmethod reverses any changes made within an open transaction and ends thetransaction. Calling either method when there is no open transaction gener-ates an ADO error.

Note Depending on the Connection object’s Attributes property, calling eitherthe CommitTrans or RollbackTrans methods may automatically start a newtransaction. If the Attributes property is set to adXactCommitRetaining, theOLE DB provider automatically starts a new transaction after a CommitTranscall. If the Attributes property is set to adXactAbortRetaining, the OLE DBprovider automatically starts a new transaction after a RollbackTrans call.

Warning Not all OLE DB providers support transactions. To determine if agiven OLE DB provider does support transactions, check that the provider-defined Transaction DDL property appears in the Connection object’s Prop-erties collection. If an application calls any of the transaction-orientedConnection object methods for an OLE DB provider that does not supporttransactions, an ADO error will result.

The method has the following syntax:

pConnection->CommitTrans();

Chapter Five—An ADO Primer � 251

Page 271: Learn OLE DB Development With Visual C++ 6.0

The method has no parameters.

Execute Method (Connection Object)

An application should use the Execute method on a Connection object to exe-cute whatever query the application passes to the method in the Command-Text argument on the specified connection. If the CommandText argumentspecifies a row-returning query, any results the execution generates arestored in a new Recordset object. If the command is not a row-returningquery, the provider returns a closed Recordset object. The returned Recordsetobject is always a read-only, forward-only cursor. If an application needs aRecordset object with more functionality, it should first create a Recordsetobject with the desired property settings, then use the Recordset object’sOpen method to execute the query and return the desired cursor type. Thecontents of the CommandText argument are specific to the provider and canbe standard SQL syntax or any special command format that the providersupports. An ExecuteComplete event will be issued when this operationconcludes.

The method has the following syntax for a non–row-returning commandstring:

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 secondsyntax.

The method has the following parameters for both syntax options:

CommandTextThis parameter contains a String with the SQL statement, table name,stored procedure, or provider-specific text to execute.

RecordsAffectedThis optional parameter contains a Long variable through which the pro-vider returns the number of records that the operation affected.

OptionsThis optional parameter contains a Long value that indicates how theprovider should evaluate the CommandText argument. It can be one ofthe values in Table 5-7:

252 � Chapter Five—An ADO Primer

Page 272: Learn OLE DB Development With Visual C++ 6.0

Table 5-7 Connection object Execute method Options parameter values

Constant Description

adCmdText This value indicates that the OLE DB provider should evaluateCommandText as a textual definition of a command.

adCmdTable This value indicates that ADO should generate a SQL query toreturn all rows from the table named in CommandText.

adCmdTableDirect This value indicates that the OLE DB provider should return allrows from the table named in CommandText.

adCmdTable This value indicates that the OLE DB provider should evaluateCommandText as a table name.

adCmdStoredProc This value indicates that the OLE DB provider should evaluateCommandText as a stored procedure.

adCmdUnknown This value indicates that the type of command in theCommandText argument is not known.

adExecuteAsync This value indicates that the command should executeasynchronously.

adFetchAsync This value indicates that the remaining rows after the initialquantity specified in the CacheSize property should be fetchedasynchronously.

Open Method (Connection Object)

Applications should use the Open method on a Connection object to establishthe physical connection to a data source (usually an OLE DB provider). Afterthis method successfully completes, the connection is live and the applicationcan issue commands against it and process the results. The application canuse the optional ConnectionString argument to specify a string containing aseries of argument = value statements separated by semicolons. TheConnectionString property automatically inherits the value used for theConnectionString argument. Therefore, the application can either set theConnectionString property of the Connection object before opening it, or usethe ConnectionString argument to set or override the current connectionparameters during the Open method call. If an application chooses to passuser and password information both in the ConnectionString argument and inthe optional UserID and Password arguments, the UserID and Password argu-ments will override the values specified in ConnectionString. When anapplication has concluded its operations over an open connection, it shoulduse the Close method to free any associated system resources, as notedabove.

The method has the following syntax:

pConnection->Open(ConnectionString, UserID, Password, OpenOptions);

Chapter Five—An ADO Primer � 253

Page 273: Learn OLE DB Development With Visual C++ 6.0

The method has the following parameters:

ConnectionStringOptional. A String containing connection information. See theConnectionString property for details on valid settings.

UserIDThis optional parameter contains a String with a user name to use whenestablishing the connection.

PasswordThis optional parameter contains a String with a password to use whenestablishing the connection.

OpenOptionsThis optional parameter contains a ConnectOptionEnum value. If set toadConnectAsync, the connection will be opened asynchronously. AConnectComplete event will be issued when the connection is available inthis case.

OpenSchema Method (Connection Object)

Applications should use this method to obtain database schema informationfrom the OLE DB provider. The OpenSchema method returns informationabout the data source, such as information about the tables on the server andthe columns in the tables. The Criteria argument is an array of values thatcan be used to limit the results of a schema query. Each schema query has adifferent set of parameters that it supports. The actual schemas are definedby the OLE DB specification under the IDBSchemaRowset interface. The con-stant adSchemaProviderSpecific is used for the QueryType argument if theOLE DB provider defines its own nonstandard schema queries. When thisconstant is used, the SchemaID argument is required to pass the GUID of theschema query to execute. If QueryType is set to adSchemaProviderSpecific butSchemaID is not provided, an ADO error will result. OLE DB providers are notrequired to support all of the OLE DB standard schema queries. Specifically,only adSchemaTables, adSchemaColumns, and adSchemaProviderTypes arerequired 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:

QueryTypeThis parameter contains the type of schema query to run; it can be any ofthe constants listed in Table 5-8.

254 � Chapter Five—An ADO Primer

Page 274: Learn OLE DB Development With Visual C++ 6.0

CriteriaThis parameter is optional; if included it is an array of query constraintsfor each QueryType option shown in Table 5-8.

SchemaIDThis parameter contains the GUID for a provider-schema schema querynot defined by the OLE DB specification. This parameter is required ifQueryType 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_CATALOGCONSTRAINT_SCHEMACONSTRAINT_NAME

adSchemaCatalogs CATALOG_NAME

adSchemaCharacterSets CHARACTER_SET_CATALOGCHARACTER_SET_SCHEMACHARACTER_SET_NAME

adSchemaCheckConstraints CONSTRAINT_CATALOGCONSTRAINT_SCHEMACONSTRAINT_NAME

adSchemaCollations COLLATION_CATALOGCOLLATION_SCHEMACOLLATION_NAME

adSchemaColumnDomainUsage DOMAIN_CATALOGDOMAIN_SCHEMADOMAIN_NAMECOLUMN_NAME

adSchemaColumnPrivileges TABLE_CATALOGTABLE_SCHEMATABLE_NAMECOLUMN_NAMEGRANTORGRANTEE

adSchemaColumns TABLE_CATALOGTABLE_SCHEMATABLE_NAMECOLUMN_NAME

adSchemaConstraintColumnUsage TABLE_CATALOGTABLE_SCHEMATABLE_NAMECOLUMN_NAME

Chapter Five—An ADO Primer � 255

Page 275: Learn OLE DB Development With Visual C++ 6.0

Table 5-8

Connection object OpenSchema method QueryType and Criteria parameter values (cont.)

Constant Description

adSchemaConstraintTableUsage TABLE_CATALOGTABLE_SCHEMATABLE_NAME

adSchemaForeignKeys PK_TABLE_CATALOGPK_TABLE_SCHEMAPK_TABLE_NAMEFK_TABLE_CATALOGFK_TABLE_SCHEMAFK_TABLE_NAME

adSchemaIndexes TABLE_CATALOGTABLE_SCHEMAINDEX_NAMETYPETABLE_NAME

adSchemaKeyColumnUsage CONSTRAINT_CATALOGCONSTRAINT_SCHEMACONSTRAINT_NAMETABLE_CATALOGTABLE_SCHEMATABLE_NAMECOLUMN_NAME

adSchemaPrimaryKeys PK_TABLE_CATALOGPK_TABLE_SCHEMAPK_TABLE_NAME

adSchemaProcedureColumns PROCEDURE_CATALOGPROCEDURE_SCHEMAPROCEDURE_NAMECOLUMN_NAME

adSchemaProcedureParameters PROCEDURE_CATALOGPROCEDURE_SCHEMAPROCEDURE_NAMEPARAMETER_NAME

adSchemaProcedures PROCEDURE_CATALOGPROCEDURE_SCHEMAPROCEDURE_NAMEPARAMETER_TYPE

adSchemaProviderSpecific <see above>

adSchemaProviderTypes DATA_TYPEBEST_MATCH

256 � Chapter Five—An ADO Primer

Page 276: Learn OLE DB Development With Visual C++ 6.0

Table 5-8

Connection object OpenSchema method QueryType and Criteria parameter values (cont.)

Constant Description

adSchemaReferentialConstraints CONSTRAINT_CATALOGCONSTRAINT_SCHEMACONSTRAINT_NAME

adSchemaSchemata CATALOG_NAMESCHEMA_NAMESCHEMA_OWNER

adSchemaSQLLanguages <none>

adSchemaStatistics TABLE_CATALOGTABLE_SCHEMATABLE_NAME

adSchemaTableConstraints CONSTRAINT_CATALOGCONSTRAINT_SCHEMACONSTRAINT_NAMETABLE_CATALOGTABLE_SCHEMATABLE_NAMECONSTRAINT_TYPE

adSchemaTablePrivileges TABLE_CATALOGTABLE_SCHEMATABLE_NAMEGRANTORGRANTEE

adSchemaTables TABLE_CATALOGTABLE_SCHEMATABLE_NAMETABLE_TYPE

adSchemaTranslations TRANSLATION_CATALOGTRANSLATION_SCHEMATRANSLATION_NAME

adSchemaUsagePrivileges OBJECT_CATALOGOBJECT_SCHEMAOBJECT_NAMEOBJECT_TYPEGRANTORGRANTEE

adSchemaViewColumnUsage VIEW_CATALOGVIEW_SCHEMAVIEW_NAME

Chapter Five—An ADO Primer � 257

Page 277: Learn OLE DB Development With Visual C++ 6.0

Table 5-8

Connection object OpenSchema method QueryType and Criteria parameter values (cont.)

Constant Description

adSchemaViewTableUsage VIEW_CATALOGVIEW_SCHEMAVIEW_NAME

adSchemaViews TABLE_CATALOGTABLE_SCHEMATABLE_NAME

RollbackTrans Method (Connection Object)

An application should use the BeginTrans, CommitTrans, and RollbackTransmethods with a Connection object when it wants to save or cancel a series ofchanges made to the source data as a single unit. Once an application callsthe BeginTrans method, the OLE DB provider will no longer instantaneouslycommit any changes the application makes until it calls CommitTrans orRollbackTrans to end the transaction. For OLE DB providers that supportnested transactions, calling the BeginTrans method within an open transac-tion starts a new, nested transaction. The return value indicates the level ofnesting: A return value of 1 indicates the application has opened a top-leveltransaction (that is, the transaction is not nested within another transaction),2 indicates that the application has opened a second-level transaction (atransaction nested within a top-level transaction), and so forth. CallingCommitTrans or RollbackTrans affects only the most recently opened transac-tion; the application must close or roll back the current transaction before itcan resolve any higher-level transactions. (Note that rolling back a nestedtransaction does not automatically force a rollback of a higher-level one.)Calling the CommitTrans method saves changes made within an open trans-action on the connection and ends the transaction. Calling the RollbackTransmethod reverses any changes made within an open transaction and ends thetransaction. Calling either method when there is no open transaction gener-ates an ADO error.

Note Depending on the Connection object’s Attributes property, calling eitherthe CommitTrans or RollbackTrans methods may automatically start a newtransaction. If the Attributes property is set to adXactCommitRetaining, theOLE DB provider automatically starts a new transaction after a CommitTranscall. If the Attributes property is set to adXactAbortRetaining, the OLE DBprovider automatically starts a new transaction after a RollbackTrans call.

258 � Chapter Five—An ADO Primer

Page 278: Learn OLE DB Development With Visual C++ 6.0

Warning Not all OLE DB providers support transactions. To determine if agiven OLE DB provider does support transactions, check that the provider-defined Transaction DDL property appears in the Connection object’s Prop-erties collection. If an application calls any of the transaction-orientedConnection object methods for an OLE DB provider that does not supporttransactions, 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 andreturn records in a Recordset object, to execute a bulk operation, and tomanipulate the structure of a database. A Command object is required whenan application wants to persist command text and re-execute it, or use queryparameters. To create a Command object independently of a previouslydefined Connection object, an application should first set its Active-Connection property to a valid connection string. ADO will still create a Con-nection object, but it doesn’t assign that object to an object variable. Toassociate multiple Command objects with the same connection, an applica-tion should explicitly create and open a Connection object; this assigns theConnection object to an object variable. If an application does not set theCommand objects’ ActiveConnection property to the Connection object vari-able, ADO creates a new Connection object for each Command object, even ifan application uses the same connection string. To execute a command, sim-ply call it by its Name property on the associated Connection object. Thecommand must have its ActiveConnection property set to the Connectionobject. If the command has parameters, pass values for them as arguments tothe method. With the collections, methods, and properties of a Commandobject, an application can do the following:

� Define the executable text of the command (for example, a SQL state-ment) with the CommandText property.

� Define parameterized queries or stored procedure arguments with Param-eter objects and the Parameters collection.

� Execute a command and return a Recordset object if appropriate with theExecute method.

� Specify the type of command with the CommandType property prior toexecution to optimize performance.

� Control whether or not the provider saves a prepared (or compiled) ver-sion of the command prior to execution with the Prepared property.

Chapter Five—An ADO Primer � 259

Page 279: Learn OLE DB Development With Visual C++ 6.0

� Set the number of seconds a provider will wait for a command to executewith the CommandTimeout property.

� Associate an open connection with a Command object by setting itsActiveConnection property.

� Set the Name property to identify the Command object as a method onthe associated Connection object.

� Pass a Command object to the Source property of a Recordset object inorder to obtain data.

CommandText Property (Command Object)

This property contains the text of a command that an application wants toissue against an OLE DB provider. The property is a String value containingan OLE DB provider command, such as a SQL statement, a table name, or astored procedure call; its default value is the empty string. Usually, the valueof CommandText will be a SQL statement, but it can also be any other type ofcommand statement recognized by the OLE DB provider, such as a storedprocedure call. A SQL statement must be of the particular dialect or versionsupported by the OLE DB provider’s query processor. If the Prepared propertyof the Command object is set to True and the Command object is bound to anopen connection when an application sets the CommandText property, ADOprepares the query (that is, a compiled form of the query is stored by the OLEDB provider) when an application calls the Execute or Open methods.Depending on the CommandType property setting, ADO may alter theCommandText property. An application can read the CommandText propertyat 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 execut-ing a command before terminating the attempt and generating an error(usually due to a failed network transmission). The property is a Long valuewith a default of 30 seconds; the property is read/write regardless of whethera 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 sup-port from the underlying OLE DB provider, results are unpredictable.

CommandType Property (Command Object)

Applications should use the CommandType property to optimize evaluationof the CommandText property. If the CommandType property value equalsadCmdUnknown (the default value), an application may experience dimin-ished performance because ADO must make calls to the provider todetermine if the CommandText property is a SQL statement, a stored

260 � Chapter Five—An ADO Primer

Page 280: Learn OLE DB Development With Visual C++ 6.0

procedure, or a table name. If an application knows what type of command itis using, setting the CommandType property instructs ADO to go directly tothe relevant code. If the CommandType property does not match the type ofcommand in the CommandText property, an ADO error occurs when you callthe Execute method. CommandType can be one or more of theCommandTypeEnum values in Table 5-9.

Note The adExecuteNoRecords constant improves performance by minimizinginternal processing. This constant never stands alone; it is always combinedwith adCmdText or adCmdStoredProc. An ADO error results if adExecute-NoRecords is used with the Recordset.Open method or a Command objectused by that method.

Table 5-9 Command object CommandTypeEnum values

Constant Description

adCmdText This value indicates that ADO should evaluate CommandTextas a textual definition of a command or stored procedure call.

adCmdTable This value indicates that ADO should evaluate CommandTextas a table name whose columns are all returned by an internallygenerated SQL query.

adCmdTableDirect This value indicates that ADO should evaluate CommandTextas a table name whose columns are all returned.

adCmdStoredProc This value indicates that ADO should evaluate CommandTextas a stored procedure name.

adCmdUnknown This value is the default; it indicates that the type of commandin the CommandText property is not known and that ADOcannot perform any internal processing prior to sending thecommand to the OLE DB provider.

adCmdFile This value indicates that ADO should evaluate CommandTextas the filename of a persisted Recordset.

adExecuteNoRecords This value indicates that CommandText is a command orstored procedure that does not return rows; if any rows areretrieved, they are discarded and not returned. This constant isalways combined with adCmdText or adCmdStoredProc.

Prepared Property (Command Object)

This property indicates whether or not an OLE DB provider should save acompiled version of a command before execution.The property is aread/write Boolean value. Setting it to True may slow a command’s first exe-cution, but once the OLE DB provider compiles a command, the provider willuse the compiled version of the command for any subsequent executions,which will result in improved performance.

Chapter Five—An ADO Primer � 261

Page 281: Learn OLE DB Development With Visual C++ 6.0

Warning If the OLE DB provider does not support command preparation, itmay return an error as soon as this property is set to True. If it does not returnan error, the OLE DB provider simply ignores the request to prepare the com-mand and sets the Prepared property to False.

State Property (Command Object)

This is a read-only Long property which indicates whether the Commandobject is currently open (interacting with an OLE DB provider) or closed (notinteracting with an OLE DB provider). The possible values are listed in Table5-10.

Table 5-10 Command object State property values

Constant Description

adStateClosed This value is the default; it indicates that the Command object isclosed.

adStateOpen This value indicates that the Command object is open.

Cancel Method (Command Object)

An application should use the Cancel method to terminate execution of anasynchronous Execute or Open method call (that is, the method was invokedwith the adConnectAsync, adExecuteAsync, or adFetchAsync option). Cancelwill return a run-time error if adRunAsync was not used in the method it istrying 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 newParameter object with the specified name, type, direction, size, and value.Any values an application passes in the arguments are written to the corre-sponding Parameter properties. This method does not automatically appendthe Parameter object to the Parameters collection of a Command object; theapplication must explicitly do so using the Append method described earlierin this chapter. This lets the application set additional properties whose val-ues 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);

262 � Chapter Five—An ADO Primer

Page 282: Learn OLE DB Development With Visual C++ 6.0

The method returns a Parameter object (interface pointer).

The method has the following parameters:

NameThis optional parameter is a String representing the name of the Parame-ter object.

TypeThis optional parameter is a Long value specifying the data type of theParameter object, with values as noted for the Type property.

DirectionThis optional parameter is a Long value specifying the type of Parameterobject, with values as noted for the Direction property.

SizeThis optional parameter is a Long value specifying the maximum lengthfor the parameter value in characters or bytes.

ValueThis optional parameter is a Variant specifying the Value property for theParameter object.

Execute Method (Command Object)

An application should use the Execute method on a Command object to exe-cute the query specified in the CommandText property against the currentOLE DB provider. If the CommandText property specifies a row-returningquery, any results the execution generates are stored in a new Recordsetobject. If the command is not a row-returning query, the OLE DB providerreturns a closed Recordset object. Some application languages allow an appli-cation to ignore this return value if no Recordset is desired. If the query hasparameters, the current values for the Command object’s parameters are usedunless the application chooses to override these with parameter values passedwith the Execute call. An application can override a subset of the parametersby omitting new values for some of the parameters when calling the Executemethod. The order in which the application should specify the parameters isthe same order in which the method passes them. An ExecuteComplete eventwill be issued when this operation concludes.

Warning Output parameters will not return correct values when passed in theParameters parameter.

The method has the following syntax for a row-returning Command object:

pRecordset = pCommand.Execute(RecordsAffected, Parameters, Options);

Chapter Five—An ADO Primer � 263

Page 283: Learn OLE DB Development With Visual C++ 6.0

The method has the following syntax for a non–row-returning Commandobject:

pCommand.Execute(RecordsAffected, Parameters, Options);

The method returns a Recordset object reference.

The method has the following parameters:

RecordsAffectedThis optional parameter is a Long variable from which the providerreturns the number of records that the operation affected.

ParametersThis optional parameter is a Variant array of parameter values passedwith a SQL statement. (It is important to remember that output parame-ters will not return correct values when passed in this argument.)

OptionsThis optional parameter is a Long value that indicates how the providershould evaluate the CommandText property of the Command object. Itcan contain one or more of the constants in Table 5-11.

Table 5-11 Command object Execute method Options values

Constant Description

adCmdText Indicates that the provider should evaluate CommandText as atextual definition of a command, such as a SQL statement.

adCmdTable Indicates that ADO should generate a SQL query to return allrows from the table named in CommandText.

adCmdTableDirect Indicates that the provider should return all rows from thetable named in CommandText.

adCmdStoredProc Indicates that the provider should evaluate CommandText as astored procedure.

adCmdUnknown Indicates that the type of command in CommandText is notknown.

adExecuteAsync Indicates that the command should execute asynchronously.

adFetchAsync Indicates that the remaining rows after the initial quantityspecified in the CacheSize property should be fetchedasynchronously.

The ADO Recordset Object

An application should use Recordset objects to manipulate data from an OLEDB provider. All Recordset objects are constructed using records (rows) andfields (columns). The most important property for a Recordset is CursorType;it must be set prior to opening the Recordset either directly in the applica-tion’s program statements, or via passing a CursorType argument with theOpen method. Some OLE DB providers don’t support all cursor types; anapplication should check the documentation for the specific OLE DB provider.

264 � Chapter Five—An ADO Primer

Page 284: Learn OLE DB Development With Visual C++ 6.0

If an application doesn’t specify a cursor type, ADO opens a forward-only cur-sor by default. There are four different Recordset cursor types defined inADO:

� Dynamic cursor This cursor allows an application to view additions,changes, and deletions by other users, and allows all types of movementthrough the Recordset that don’t rely on bookmarks. It also allows book-marks if the OLE DB provider supports them.

� Keyset cursor This cursor behaves like a dynamic cursor except that itprevents an application from seeing records that other users add and pre-vents access to records that other users delete. Data changes by otherusers will still be visible. It always supports bookmarks and thereforeallows all types of movement through the Recordset.

� Static cursor This cursor provides a static copy of a set of records foran application to use to find data or generate reports. It always allowsbookmarks and therefore allows all types of movement through theRecordset. Additions, changes, or deletions by other users will not be visi-ble. This is the only type of cursor allowed when an application opens aclient-side (ADOR) Recordset object.

� Forward-only cursor This cursor behaves identically to a dynamiccursor except that it allows an application to scroll only forward throughrecords. This improves performance in situations where an applicationneeds to make only a single pass through a Recordset.

When used with some providers (such as the Microsoft ODBC Provider forOLE DB in conjunction with Microsoft® SQL Server™), an application can cre-ate Recordset objects independently of a previously defined Connectionobject by passing a connection string with the Open method. ADO still createsa Connection object, but it doesn’t assign that object to an object variable.However, if an application is opening multiple Recordset objects over thesame connection, it should explicitly create and open a Connection object;this assigns the Connection object to an object variable. If an application doesnot use this object variable when opening its Recordset objects, ADO createsa new Connection object for each new Recordset, even if the applicationpasses the same connection string. An application can create as manyRecordset objects as needed, subject to memory limitations.

When an application opens a Recordset, the current record is positioned tothe first record (if any) and the BOF and EOF properties are set to False. Ifthere are no records, the BOF and EOF property settings are True. An applica-tion can use the MoveFirst, MoveLast, MoveNext, and MovePreviousmethods, as well as the Move method, and the AbsolutePosition, Absolute-Page, and Filter properties to reposition the current record, assuming the pro-vider supports the relevant functionality. Forward-only Recordset objectssupport only the MoveNext method. When an application uses the MoveXXXmethods to visit each record (or enumerate the Recordset), it can use the

Chapter Five—An ADO Primer � 265

Page 285: Learn OLE DB Development With Visual C++ 6.0

BOF and EOF properties to see if it has moved beyond the beginning or endof the Recordset.

Recordset objects can support two types of updating: immediate and batched.In immediate updating, all changes to data are written immediately to theunderlying data source once an application calls the Update method. Anapplication can also pass arrays of values as parameters with the AddNewand Update methods and simultaneously update several fields in a record. Ifan OLE DB provider supports batch updating, an application can have theprovider cache changes to more than one record and then transmit them in asingle call to the database with the UpdateBatch method. This applies tochanges made with the AddNew, Update, and Delete methods. After an appli-cation calls the UpdateBatch method, it can use the Status property to checkfor any data conflicts in order to resolve them.

AbsolutePage Property (Recordset Object)

An application should use the AbsolutePage property to identify the pagenumber on which the current record is located. First, the application shoulduse the PageSize property to logically divide the Recordset object into a seriesof pages, each of which has the number of records equal to PageSize (exceptfor the last page, which may have fewer records). The OLE DB provider mustsupport the appropriate functionality for this property to be available. Likethe AbsolutePosition property, AbsolutePage is 1-based and equals 1 when thecurrent record is the first record in the Recordset. Aside from a read-onlyLong 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 Description

adPosUnknown This value indicates that the Recordset is empty, the currentposition is unknown, or the provider does not support theAbsolutePage property.

adPosBOF This value indicates that the current record pointer is at BOF(that is, the BOF property is True).

adPosEOF 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 recordbased on its ordinal position in the Recordset object or to determine the ordi-nal position of the current record. The OLE DB provider must support theappropriate functionality for this property to be available. Like theAbsolutePage property, AbsolutePosition is 1-based and equals 1 when thecurrent record is the first record in the Recordset. An application can obtainthe total number of records in the Recordset object from the RecordCount

266 � Chapter Five—An ADO Primer

Page 286: Learn OLE DB Development With Visual C++ 6.0

property. When an application sets the AbsolutePosition property, even if it isto a record in the current cache, ADO reloads the cache with a new group ofrecords starting with the record it specified. The CacheSize property deter-mines the size of this group. Aside from a read/write Long numeric recordvalue, AbsolutePosition can also have one of the (negative) constant values inTable 5-13:

Table 5-13 Recordset object AbsolutePosition property values

Constant Description

adPosUnknown This value indicates that the Recordset is empty, the currentposition is unknown, or the provider does not support theAbsolutePosition property.

adPosBOF This value indicates that the current record pointer is at BOF(that is, the BOF property is True).

adPosEOF 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 sur-rogate record number. The position of a given record changes when anapplication deletes a preceding record. There is also no assurance that a givenrecord will have the same AbsolutePosition if the Recordset object is requeriedor reopened. Bookmarks are still the recommended way of retaining andreturning to a given position and are the only way of positioning across alltypes of Recordset objects.

ActiveConnection Property (Recordset Object)

This property indicates to which Connection object the specified Command orRecordset object currently belongs. The read/write value is a String contain-ing the definition for a connection or a Connection object; the default value isa Null reference.

BOF/EOF Property (Recordset Object)

An application should use the BOF and EOF properties to determine whethera Recordset object contains records or whether it has gone beyond the limitsof a Recordset object when it moves from record to record. BOF indicates thatthe current record position is before the first record in a Recordset object; theBOF property returns True (–1) if the current record position is before thefirst record and False (0) if the current record position is on or after the firstrecord. The EOF property returns True if the current record position is afterthe last record and False if the current record position is on or before the lastrecord. 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 andEOF properties are set to True, and the Recordset object’s RecordCount prop-erty setting is zero. When an application opens a Recordset object that

Chapter Five—An ADO Primer � 267

Page 287: Learn OLE DB Development With Visual C++ 6.0

contains at least one record, the first record is the current record and the BOFand EOF properties are False. If an application deletes the last remainingrecord in the Recordset object, the BOF and EOF properties may remain Falseuntil an application attempts to reposition the current record. Table 5-14shows which MoveXXX methods are allowed with different combinations ofthe BOF and EOF properties.

Table 5-14 MoveXXX method calls permitted by EOF/BOF state combinations

MoveFirst,MoveLast

MovePrevious,Move < 0 Move 0

MoveNext,Move > 0

BOF=True,EOF=False

Allowed Error Error Allowed

BOF=False,EOF=True

Allowed Allowed Error Error

Both True Error Error Error Error

Both False Allowed Allowed Allowed Allowed

Allowing a MoveXXX method doesn’t guarantee that the method will success-fully locate a record; it only means that calling the specified MoveXXXmethod won’t generate an ADO error.

Table 5-15 shows what happens to the BOF and EOF property settings whenan application calls various MoveXXX methods that are unable to successfullylocate 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

BOF EOF

MoveFirst, MoveLast Set to True Set to True

Move 0 No change No change

MovePrevious, Move < 0 Set to True No change

MoveNext, Move > 0 No change Set to True

Bookmark Property (Recordset Object)

An application should use the Bookmark property to save the position of thecurrent record and return to that record at any time. Bookmarks are availableonly in Recordset objects that support bookmark functionality via the currentOLE DB provider. When an application opens a Recordset object, each of itsrecords has a unique bookmark. To save the bookmark for the current record,an application should assign the value of the Bookmark property to a vari-able. Then to quickly return to that record at any time after moving to adifferent record, the application should set the Recordset object’s Bookmarkproperty to the value of that variable. If an application uses the Clone methodto create a copy of a Recordset object, the Bookmark property settings for theoriginal and the duplicate Recordset objects are identical and an applicationcan use them interchangeably. However, an application cannot use book-marks from different Recordset objects interchangeably, even if they were

268 � Chapter Five—An ADO Primer

Page 288: Learn OLE DB Development With Visual C++ 6.0

created from the same source or command. The property is read/write anduses a Variant expression that evaluates to a valid bookmark on the currentOLE DB provider.

Warning The application may not be able to view the value of a bookmarksince it may be represented in a non-displayable way by the OLE DB providerin use. Also, applications should not expect bookmarks to be directly compara-ble—two bookmarks that refer to the same record may have different valuesbecause of the OLE DB provider’s implementation.

CacheSize Property (Recordset Object)

Applications should use the CacheSize property to control how many recordsthe OLE DB provider keeps in its buffer and how many to retrieve at one timeinto 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 providerretrieves the first CacheSize records into local memory. As an applicationmoves through the Recordset object, the OLE DB provider returns the datafrom the local memory buffer. As soon as the application moves past the lastrecord in the cache, the provider retrieves the next CacheSize records fromthe data source into the cache. The value of this property can be adjustedduring the life of the Recordset object, but changing this value only affectsthe number of records in the cache after subsequent retrievals from the datasource. Changing the property value alone will not change the current con-tents of the cache. If there are fewer records to retrieve than CacheSizespecifies, the provider returns the remaining records; no error occurs.Records retrieved from the cache do not reflect concurrent changes that otherusers 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 byADO, based on various cursor libraries accessible to the OLE DB provider inuse. The property is a Long value which is read/write regardless of whether aconnection is open or not, but the setting affects connections established onlyafter the property has been set; changing this property has no effect on exist-ing connections. The cursor obtained via an Execute call will automaticallyinherit the setting of this property. Table 5-16 gives the possible values of thisproperty:

Chapter Five—An ADO Primer � 269

Page 289: Learn OLE DB Development With Visual C++ 6.0

Table 5-16 Recordset object CursorLocation property values

Constant Description

adUseNone This value indicates that no cursor services are used; it isobsolete and should be used only for the sake of backwardcompatibility with legacy code.

adUseClient This value indicates that the connection uses client-side cursorssupplied by a local cursor library. Since local cursor enginesoften will allow features that driver-supplied cursors may not,using this setting may enable extra features. (adUseClientBatchis an alternative value for this constant.)

adUseServer This value is the default; it indicates that the connection usesOLE DB provider- or driver-supplied cursors. Some features ofthe Microsoft Client Cursor Provider (such as disassociatedrecordsets) cannot be simulated with server-side cursors, butother provider-specific features may be supported instead.

CursorType Property (Recordset Object)

Applications should use the CursorType property to specify the type of cursorthat should be used when opening the Recordset object. Only a setting ofadOpenStatic is supported if the CursorLocation property is set toadUseClient. If an unsupported value is set, then no error will result; the clos-est supported CursorType will be used instead. If an OLE DB provider doesnot support the requested cursor type, the provider may return another cur-sor type. The CursorType property will change to match the actual cursortype in use when the Recordset object is open. To verify specific functionalityof the returned cursor, use the Supports method. Although Sup-ports(adUpdateBatch) may be true for dynamic and forward-only cursors, forbatch 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 Cli-ent Cursor Engine, which is required for batch updates.

After an application closes the Recordset, the CursorType property reverts toits original setting. The CursorType property is read/write when theRecordset is closed and read-only when it is open. The property is composedof only one of the CursorTypeEnum values in Table 5-17.

Table 5-17 Recordset object CursorTypeEnum values

Constant Description

adOpenForwardOnly This value is the default and is a forward-only cursor. This isidentical to a static cursor except that an application can onlyscroll forward through records.

270 � Chapter Five—An ADO Primer

Page 290: Learn OLE DB Development With Visual C++ 6.0

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 usersadd, although records that other users delete are inaccessiblefrom the Recordset.

adOpenDynamic This value is for a dynamic cursor. Under it, all additions,changes, and deletions by other users are visible, and all typesof movement through the Recordset are allowed, except forbookmarks 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 ofrecords that an application can use to find data or generatereports.

EditMode Property (Recordset Object)

An application should use the EditMode property to determine the editingstatus of the current record. The application can test for pending changes ifan editing process has been interrupted and determine whether it needs touse the Update or CancelUpdate method. ADO maintains an editing bufferassociated with the current record. This property indicates whether changeshave 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 Description

adEditNone This value indicates that no editing operation is in progress.

adEditInProgress This value indicates that data in the current record has beenmodified but not yet saved.

adEditAdd This value indicates that the AddNew method has beeninvoked, and the current record in the copy buffer is a newrecord that hasn’t been saved in the database.

adEditDelete This value indicates that the current record has beendeleted.

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 proper-ties such as AbsolutePosition, AbsolutePage, RecordCount, and PageCountthat return values based on the current cursor, because setting the Filterproperty to a specific value will move the current record to the first recordthat satisfies the new value. The criteria string is made up of clauses in theform FieldName Operator Value (for example, “Genus = ‘Pinniped’ ”). Anapplication can create compound clauses by concatenating individual clauseswith AND (for example, “Species = Genus AND Habitat = ‘Arctic’ ”) or OR(for example, “Genus = ‘Pinniped’ OR Genus = ‘Cetacean’ ”). FieldName must

Chapter Five—An ADO Primer � 271

Page 291: Learn OLE DB Development With Visual C++ 6.0

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 beone of the following: <, >, <=, >=, <>, =, or LIKE. Value is the value withwhich the OLE DB provider will compare the field values (for example,‘Pinniped’, #7/27/52#, 158.158 or $158.00). An application should use sin-gle quotes with strings and pound signs (#) with dates. For numbers, anapplication can use decimal points, dollar signs, and scientific notation. IfOperator is LIKE, Value can use wild cards. Only the asterisk (*) and percentsign (%) wild cards are allowed, and they must be the last character in thestring. Value cannot be Null. There is no precedence between AND and OR.Clauses can be grouped within parentheses. However, an application cannotgroup clauses joined by an OR and then join the group to another clause withan AND.

The filter constants make it easier to resolve individual record conflicts dur-ing batch update mode by allowing an application to view only those recordsthat were affected during the last UpdateBatch method call. Setting the Filterproperty itself may fail because of a conflict with the underlying data (forexample, 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 pro-gram execution. An ADO error occurs only if there are conflicts on all therequested records. An application can use the Status property to locaterecords with conflicts. Setting the Filter property to an empty string (“”) hasthe same effect as using the adFilterNone constant. Whenever the Filter prop-erty is set, the current record position moves to the first record in the filteredsubset of records in the Recordset. Similarly, when the Filter property iscleared, 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 follow-ing types of data:

� Criteria string—a string made up of one or more individual clauses con-catenated with AND or OR operators.

� Array of bookmarks—an array of unique bookmark values that point torecords in the Recordset object.

� One of the FilterGroupEnum values in Table 5-19:

Table 5-19 Recordset object FilterGroupEnum values

Constant Description

adFilterAffectedRecords This value allows an application to view only recordsaffected by the last Delete, Resync, UpdateBatch, orCancelBatch call.

adFilterConflictingRecords This value allows an application to view the recordsthat failed the last batch update attempt.

272 � Chapter Five—An ADO Primer

Page 292: Learn OLE DB Development With Visual C++ 6.0

Table 5-19 Recordset object FilterGroupEnum values (cont.)

Constant Description

adFilterFetchedRecords This value allows an application to view records in thecurrent cache, that is, the results of the last call toretrieve records from the database.

adFilterNone This value removes the current filter and restores allrecords to view.

adFilterPendingRecords This value allows an application to view only recordsthat have changed but have not yet been sent to theserver. It is applicable only for batch update mode.

LockType Property (Recordset Object)

An application should set the LockType property before opening a Recordsetto specify what type of locking the OLE DB provider should use when open-ing it. An application should read the property to return the type of locking inuse on an open Recordset object it did not create, or if it is unsure whetherthe current OLE DB provider supports a given LockType setting. TheLockType property is read/write when the Recordset is closed and read-onlywhen it is open. Not all OLE DB providers support all lock types. If an OLEDB provider cannot support the requested LockType setting, it will substituteanother type of locking rather than raising an ADO error. To determine theactual locking functionality available in a Recordset object, an applicationshould use the Supports method with adUpdate and adUpdateBatch. Theproperty contains only one of the LockTypeEnum values in Table 5-20.

Table 5-20 Recordset object LockTypeEnum values

Constant Description

adLockReadOnly This value is the default. It indicates read-only access; theapplication cannot alter the data.

adLockPessimistic This value indicates pessimistic locking, record by record. Inthis case, the OLE DB provider does what is necessary toensure successful editing of the records, usually by lockingrecords at the data source immediately upon editing.

adLockOptimistic This value indicates optimistic locking, record by record. Inthis case, the OLE DB provider uses optimistic locking,locking records only when an application calls the Updatemethod.

adLockBatchOptimistic This value indicates optimistic batch updates. It is requiredfor batch update mode as opposed to immediate updatemode.

Chapter Five—An ADO Primer � 273

Page 293: Learn OLE DB Development With Visual C++ 6.0

Warning The adLockPessimistic setting is not supported if the CursorLocationproperty 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 modifiedon the client are written back to the middle tier or web server through a tech-nique called marshaling, the process of packaging and sending interfacemethod parameters across thread or process boundaries. Setting theMarshalOptions property can improve performance when modified remotedata is marshaled for updating back to the middle tier or web server. Theproperty contains a Long value that can only be one of the constants in Table5-21.

Table 5-21 Recordset object MarshalOptions property values

Constant Description

adMarshalAll This value is the default; it indicates that all rows arereturned to the server.

adMarshalModifiedOnly This value indicates that only modified rows arereturned to the server.

MaxRecords Property (Recordset Object)

An application should use the MaxRecords property to limit the number ofrecords the OLE DB provider returns from the data source. A value of 0means the OLE DB provider returns all requested records. The property isread/write when the Recordset is closed and read-only when it is open. It is aLong value with a default of 0 (no limit).

PageCount Property (Recordset Object)

An application should use the PageCount property to determine how manypages of data are in the Recordset object. Pages are groups of records whosesize 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 addi-tional page in the PageCount value. If the Recordset object does not supportthis property, the value will be –1 to indicate that the PageCount is indeter-minable. The property is read-only and is a Long value.

PageSize Property (Recordset Object)

An application should use the PageSize property to determine how manyrecords make up a logical page of data. Establishing a page size allows anapplication to use the AbsolutePage property to move to the first record of aparticular page. This is useful in web server scenarios when an applicationwants to allow the user to page through data, viewing a certain number of

274 � Chapter Five—An ADO Primer

Page 294: Learn OLE DB Development With Visual C++ 6.0

records at a time. This property can be set at any time, and its value will beused for calculating the location of the first record of a particular page. Theproperty is a Long value with a default of 10.

RecordCount Property (Recordset Object)

An application should use the RecordCount property to find out how manyrecords are in a Recordset object. The property returns –1 when ADO cannotdetermine the number of records. If the Recordset object supports approxi-mate positioning or bookmarks, this value will be the exact number ofrecords in the Recordset regardless of whether it has been fully populated. Ifthe Recordset object does not support approximate positioning, this propertymay be a significant drain on resources because all records will have to beretrieved and counted to return an accurate RecordCount value. The propertyis a read-only Long value.

Warning Reading the RecordCount property on a closed Recordset causes anerror.

Sort Property (Recordset Object)

An application should use this property to set one or more field names onwhich the Recordset is sorted and specify whether each field is sorted inascending or descending order. The data is not physically rearranged, but issimply accessed in the sorted order. A temporary index will be created foreach field specified in the Sort property if the CursorLocation property is setto adUseClient and an index does not already exist. Setting the Sort propertyto an empty string will reset the rows to their original order and delete tem-porary indexes; existing indexes will not be deleted. The property is alwaysread/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 fol-lowed by a blank and the keyword ASCENDING or DESCENDING, whichspecifies the field sort order. If no keyword is specified, the default isdescending order.

Source Property (Recordset Object)

An application should use the Source property to specify a data source for aRecordset object using a Command object variable, a SQL statement, a storedprocedure, or a table name. If an application sets the Source property to aCommand object, the ActiveConnection property of the Recordset object willinherit the value of the ActiveConnection property for the specified Com-mand object. Reading the Source property does not return a Commandobject; instead, it returns the CommandText property of the Command objectto which the application set the Source property. If the Source property is aSQL statement, a stored procedure, or a table name, an application can

Chapter Five—An ADO Primer � 275

Page 295: Learn OLE DB Development With Visual C++ 6.0

optimize performance by passing the appropriate Options argument with theOpen method call. The Source property is read/write for closed Recordsetobjects and read-only for open Recordset objects. The property is set to aString value or Command object reference, but can only return a Stringvalue.

State Property (Recordset Object)

This property describes for a Recordset object executing an asynchronousmethod whether the current state of the object is connecting, executing, orfetching; otherwise, it simply reports whether the Recordset is open (commu-nicating with the OLE DB provider) or closed (not communicating with theOLE DB provider). The property can have a combination of values; if a state-ment is executing, this property will have a combined value of adStateOpenand adStateExecuting. This property is read-only and returns a Long valuethat can be one or more of the constants in Table 5-22.

Table 5-22 Recordset object State property values

Constant Description

adStateClosed This value indicates that the Recordset is closed.

adStateOpen This value indicates that the Recordset is open.

adStateConnecting This value indicates that the Recordset object isconnecting.

adStateExecuting This value indicates that the Recordset object is

executing a command.

adStateFetching This value indicates that the rows of the Recordsetobject are being fetched.

Status Property (Recordset Object)

Applications should use the Status property to see what changes are pendingfor records modified during batch updating. Applications can also use theStatus property to view the status of records that fail during bulk operations,such as when they call the Resync, UpdateBatch, or CancelBatch methods ona Recordset object, or set the Filter property on a Recordset object to an arrayof bookmarks. The property is read-only and contains a combination of oneor more of the RecordStatusEnum values in Table 5-23.

Table 5-23 Recordset object RecordStatusEnum values

Constant Description

adRecOK This value indicates that the record was successfullyupdated.

adRecNew This value indicates that the record is new.

adRecModified This value indicates that the record was modified.

adRecDeleted This value indicates that the record was deleted.

adRecUnmodified This value indicates that the record was not modified.

276 � Chapter Five—An ADO Primer

Page 296: Learn OLE DB Development With Visual C++ 6.0

Table 5-23 Recordset object RecordStatusEnum values (cont.)

Constant Description

adRecInvalid This value indicates that the record was not savedbecause its bookmark is invalid.

adRecMultipleChanges This value indicates that the record was not savedbecause it would have affected multiple records.

adRecPendingChanges This value indicates that the record was not savedbecause it refers to a pending insert.

adRecCanceled This value indicates that the record was not savedbecause the operation was canceled.

adRecCantRelease This value indicates that the new record was not savedbecause of existing record locks.

adRecConcurrencyViolation This value indicates that the record was not savedbecause optimistic concurrency was in use.

adRecIntegrityViolation This value indicates that the record was not savedbecause the user violated integrity constraints.

adRecMaxChangesExceeded This value indicates that the record was not savedbecause there were too many pending changes.

adRecObjectOpen This value indicates that the record was not savedbecause of a conflict with an open storage object.

adRecOutOfMemory This value indicates that the record was not savedbecause the computer has run out of memory.

adRecPermissionDenied This value indicates that the record was not savedbecause the user has insufficient permissions.

adRecSchemaViolation This value indicates that the record was not savedbecause it violates the structure of the underlyingdatabase.

adRecDBDeleted This value indicates that the record has already beendeleted from the data source.

AddNew Method (Recordset Object)

An application should use the AddNew method to create and initialize a newrecord. First, however, it should use the Supports method with adAddNew toverify whether it can add records to the current Recordset object. After itmakes the call to AddNew, the new record becomes the current record andremains current after the call to the Update method. If the Recordset objectdoes not support bookmarks, an application may not be able to access thenew 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 recordaccessible. If an application calls AddNew while editing the current record orwhile adding a new record, ADO calls the Update method to save anychanges and then creates the new record. The behavior of the AddNewmethod depends on the updating mode of the Recordset object and whetheror not an application chooses to pass the FieldList and Values arguments. Inimmediate update mode (the provider writes changes to the underlying data

Chapter Five—An ADO Primer � 277

Page 297: Learn OLE DB Development With Visual C++ 6.0

source once the application calls the Update method), calling the AddNewmethod without arguments sets the EditMode property to adEditAdd. TheOLE DB provider caches any field value changes locally. Calling the Updatemethod posts the new record to the database and resets the EditMode prop-erty to adEditNone. If an application chooses to pass the FieldList and Valuesarguments, ADO immediately posts the new record to the database (noUpdate call is necessary); the EditMode property value does not change(adEditNone). In batch update mode (the OLE DB provider caches multiplechanges and writes them to the underlying data source only when you callthe UpdateBatch method), calling the AddNew method without argumentssets the EditMode property to adEditAdd. The OLE DB provider caches anyfield value changes locally. Calling the Update method adds the new record tothe current Recordset and resets the EditMode property to adEditNone, butthe OLE DB provider does not post the changes to the underlying databaseuntil the application calls the UpdateBatch method. If an application choosesto pass the FieldList and Values arguments, ADO sends the new record to theprovider for storage in a cache; the application then needs to call theUpdateBatch 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:

FieldListThis optional parameter is a single name or an array of names or ordinalpositions of the fields in the new record.

ValuesThis optional parameter is a single value or an array of values for thefields in the new record. If FieldList is an array, Values must also be anarray with the same number of members; otherwise, an ADO erroroccurs. The order of field names must match the order of field values ineach array.

Cancel Method (Recordset Object)

An application should use the Cancel method to terminate execution of anasynchronous Execute or Open method call (that is, the method was invokedwith the adConnectAsync, adExecuteAsync, or adFetchAsync option). Cancelwill return an ADO error if adRunAsync was not used in the method it is try-ing to terminate.

The method has the following syntax:

pRecordset->Cancel();

The method has no parameters.

278 � Chapter Five—An ADO Primer

Page 298: Learn OLE DB Development With Visual C++ 6.0

CancelBatch Method (Recordset Object)

An application should use the CancelBatch method to cancel any pendingupdates in a recordset in batch update mode. If the Recordset is in immediateupdate mode, calling CancelBatch without adAffectCurrent generates an ADOerror. If the application is editing the current record or is adding a new recordwhen it calls CancelBatch, ADO first calls the CancelUpdate method to cancelany cached changes; after that, all pending changes in the recordset are can-celed. It is possible that the current record will be indeterminable after aCancelBatch call, especially if the application was in the process of adding anew record. For this reason, it is prudent to set the current record position toa known location in the Recordset after the CancelBatch call.

The method has the following syntax:

pRecordset->CancelBatch(AffectRecords);

The method has the following parameter:

AffectRecordsThis optional parameter is an AffectEnum value that determines howmany records the CancelBatch method will affect. The value is one of theconstants in Table 5-24:

Table 5-24 Recordset object CancelBatch method AffectEnum values

Constant Description

adAffectCurrent This value indicates that ADO should cancel pending updates onlyfor the current record.

adAffectGroup This value indicates that ADO should cancel pending updates forrecords that satisfy the current Filter property setting. You mustset the Filter property to one of the valid predefined constants inorder to use this option.

adAffectAll This default value indicates that ADO should cancel pendingupdates for all the records in the Recordset object, including anyhidden by the current Filter property setting.

CancelUpdate Method (Recordset Object)

An application should use the CancelUpdate method to cancel any changesmade to the current record or to discard a newly added record. An applica-tion cannot undo changes to the current record or to a new record after itcalls the Update method unless the changes are either part of a transactionthat the application can roll back with the RollbackTrans method or part of abatch update that it can cancel with the CancelBatch method. If the applica-tion is adding a new record when it calls the CancelUpdate method, therecord that was current prior to the AddNew call becomes the current recordagain. If an application has not changed the current record or added a newrecord, calling the CancelUpdate method generates an ADO error.

Chapter Five—An ADO Primer � 279

Page 299: Learn OLE DB Development With Visual C++ 6.0

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, duplicateRecordset objects, particularly if it wants to be able to maintain more thanone current record in a given set of records. Using the Clone method is moreefficient than creating and opening a new Recordset object with the samedefinition as the original. The current record of a newly created clone is setto the first record. Changes the application may make to one Recordset objectare visible in all of its clones regardless of cursor type. However, once anapplication executes Requery on the original Recordset, the clones will nolonger be synchronized to the original. Closing the original Recordset doesnot close its copies; closing a copy does not close the original or any of theother copies. An application can only clone a Recordset object that supportsbookmarks. Bookmark values are interchangeable; that is, a bookmark refer-ence 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:

LockTypeThis optional parameter is a LockTypeEnum value (given in Table 5-25)that specifies either the lock type of the original Recordset or a read-onlyRecordset.

Table 5-25 Recordset object LockTypeEnum values

Constant Description

adLockReadOnly This default value indicates that the clone is created asread-only.

adLockUnspecified This default value indicates that the clone is created with thesame lock type as the original.

Delete Method (Recordset Object)

An application should use the Delete method to mark the current record or agroup of records in a Recordset object for deletion. If the Recordset objectdoesn’t allow record deletion, an ADO error occurs. If the application is inimmediate update mode, deletions occur in the database immediately. Other-wise, the records are marked for deletion from the cache and the actual

280 � Chapter Five—An ADO Primer

Page 300: Learn OLE DB Development With Visual C++ 6.0

deletion happens when the application calls the UpdateBatch method. (Appli-cations can use the Filter property to view the deleted records.) Retrievingfield values from the deleted record generates an ADO error. After deletingthe current record, the deleted record remains current until an applicationchooses to move to a different record. Once the application chooses to moveaway from the deleted record, that deleted record is no longer accessible. Ifan application should nest deletions in a transaction, however, it can recoverdeleted records with the RollbackTrans method. If an application is in batchupdate mode, it can cancel a pending deletion or group of pending deletionswith the CancelBatch method. If the attempt to delete records fails because ofa conflict with the underlying data (for example, a record has already beendeleted by another user), the provider returns warnings to the Errors collec-tion but does not halt program execution with an ADO error. However, anADO 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:

AffectRecordsThis parameter is an AffectEnum value that determines how manyrecords the Delete method will affect. The AffectEnum constants aregiven in Table 5-26.

Table 5-26 Recordset object AffectEnum values

Constant Description

adAffectCurrent This default value indicates that ADO should delete only thecurrent record.

adAffectGroup This value indicates that ADO should delete the records thatsatisfy the current Filter property setting. An application mustset the Filter property to one of the valid predefined constantsin order to use this option.

Move Method (Recordset Object)

An application should use this method to move the position of the currentrecord in a Recordset object. The Move method is supported on all Recordsetobjects. If the NumRecords argument is greater than zero, the current recordposition moves forward (toward the end of the recordset). If NumRecords isless than zero, the current record position moves backward (toward thebeginning of the recordset). If the Move call would move the current recordposition to a point before the first record, ADO sets the current record to theposition before the first record in the recordset (BOF is True). An attempt tomove backward when the BOF property is already True generates an ADOerror. If the Move call would move the current record position to a point afterthe last record, ADO sets the current record to the position after the last

Chapter Five—An ADO Primer � 281

Page 301: Learn OLE DB Development With Visual C++ 6.0

record in the recordset (EOF is True). An attempt to move forward when theEOF property is already True generates an ADO error. Calling the Movemethod from an empty Recordset object generates an error. If an applicationchooses to pass the Start argument, the move is relative to the record withthis bookmark, assuming the Recordset object supports bookmarks. If thisparameter is not specified, the move is relative to the current record. If anapplication is using the CacheSize property to locally cache records from theprovider, passing a NumRecords argument that moves the current record posi-tion outside the current group of cached records forces ADO to retrieve a newgroup of records starting from the destination record. The CacheSize propertydetermines the size of the newly retrieved group, and the destination recordis the first record retrieved. If the Recordset object is forward-only, an appli-cation can still pass a NumRecords argument less than zero as long as thedestination is within the current set of cached records. If the Move call wouldmove 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 recordcache that supports full scrolling over a provider that supports only forwardscrolling. Because cached records are loaded into memory, however, an appli-cation should avoid caching more records than is necessary. Even if aforward-only Recordset object supports backward moves using this techniquefrom the vantage point of the end user, calling the MovePrevious method onany forward-only Recordset interface pointer in program statements still gen-erates an ADO error.

The method has the following syntax:

pRecordset->Move(NumRecords, Start);

The method has the following parameters:

NumRecordsThis parameter is a signed Long expression specifying the number ofrecords the current record position moves.

StartThis optional parameter is a String or Variant that evaluates to a book-mark. An application can also use one of the BookmarkEnum values inTable 5-27:

Table 5-27 Recordset object BookmarkEnum values

Constant Description

adBookmarkCurrent This default value indicates that ADO should start at thecurrent record.

adBookmarkFirst This value indicates that ADO should start at the firstrecord.

Table 5-27 Recordset object BookmarkEnum values (cont.)

Constant Description

282 � Chapter Five—An ADO Primer

Page 302: Learn OLE DB Development With Visual C++ 6.0

adBookmarkLast 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 recordposition to the first record in the Recordset. The Recordset object must sup-port bookmarks or backward cursor movement; otherwise, the method callwill generate an ADO error. If the Recordset is forward-only and an applica-tion wants to support both forward and backward scrolling, it can use theCacheSize property to create a record cache that will support backward cur-sor movement through the Move method. Because cached records are loadedinto memory, an application should avoid caching more records than is neces-sary. An application can call the MoveFirst method in a forward-onlyRecordset object; doing so may cause the OLE DB provider to re-execute thecommand 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 recordposition to the last record in the Recordset. The Recordset object must supportbookmarks or backward cursor movement; otherwise, the method call will gen-erate an ADO error. If the Recordset is forward-only and an application wantsto support both forward and backward scrolling, it can use the CacheSize prop-erty to create a record cache that will support backward cursor movementthrough the Move method. Because cached records are loaded into memory, anapplication 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 recordposition one record forward (toward the bottom of the Recordset). If the lastrecord is the current record and an application calls the MoveNext method,ADO sets the current record to the position after the last record in theRecordset (EOF is True). An attempt to move forward when the EOF propertyis already True generates an ADO error. If the Recordset is forward-only andan application wants to support both forward and backward scrolling, it canuse the CacheSize property to create a record cache that will support

Chapter Five—An ADO Primer � 283

Page 303: Learn OLE DB Development With Visual C++ 6.0

backward cursor movement through the Move method. Because cachedrecords are loaded into memory, an application should avoid caching morerecords 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 currentrecord position one record backward (toward the top of the recordset). TheRecordset object must support bookmarks or backward cursor movement; oth-erwise, the method call will generate an ADO error. If the first record is thecurrent record and an application calls the MovePrevious method, ADO sets thecurrent record to the position before the first record in the recordset (BOF isTrue). An attempt to move backward when the BOF property is already Truegenerates an ADO error. If the Recordset object does not support either book-marks or backward cursor movement, the MovePrevious method will generatean ADO error. If the Recordset is forward-only and an application wants to sup-port both forward and backward scrolling, it can use the CacheSize property tocreate a record cache that will support backward cursor movement through theMove method. Because cached records are loaded into memory, an applicationshould 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 ofthe next command in a compound command statement or of a stored proce-dure that returns multiple results. If you open a Recordset object based on acompound command statement using the Execute method on a Command orthe Open method on a Recordset, ADO executes only the first command andreturns the results to the Recordset. To access the results of subsequent com-mands in the statement, call the NextRecordset method. As long as there areadditional results, the NextRecordset method will continue to returnRecordset objects. If a row-returning command returns no records, thereturned Recordset object will be empty; test for this case by verifying thatthe BOF and EOF properties are both True. If a non–row-returning commandexecutes successfully, the returned Recordset object will be closed, which anapplication can verify by testing the State property on the Recordset. When

284 � Chapter Five—An ADO Primer

Page 304: Learn OLE DB Development With Visual C++ 6.0

there are no more results, the Recordset will be set to Null. If an edit is inprogress while in immediate update mode, calling the NextRecordset methodgenerates an ADO error; an application should call the Update orCancelUpdate method first. If an application needs to pass parameters formore than one command in the compound statement by filling the Parame-ters 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 theirrespective commands in the command series. An application must finishreading all the results before reading output parameter values. When anapplication calls the NextRecordset method, ADO executes only the next com-mand in the statement. If an application explicitly closes the Recordset objectbefore stepping through the entire command statement, ADO never executesthe remaining commands.

The method has the following syntax:

pRecordset2 = pRecordset1->NextRecordset(RecordsAffected);

The method returns a Recordset object. In the syntax model, pRecordset1 andpRecordset2 can be the same Recordset object, or the application can use sep-arate objects.

The method has the following parameter:

RecordsAffectedThis optional parameter is a Long variable to which the OLE DB providerreturns 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 acursor that represents records from a base table, the results of a query, or apreviously saved Recordset. The application should use the optional Sourceargument to specify a data source using one of the following: a Commandobject variable, a SQL statement, a stored procedure, a table name, or a com-plete file path name. The ActiveConnection argument corresponds to theActiveConnection property and specifies in which connection to open theRecordset object. If an application chooses to pass a connection definition forthis argument, ADO opens a new connection using the specified parameters.An application can change the value of this property after opening theRecordset to send updates to another provider. Or, an application can set thisproperty to Null to disconnect the Recordset from any provider (after callingRelease; see Chapter 1). For the other arguments that correspond directly toproperties of a Recordset object (Source, CursorType, and LockType), the rela-tionship of the arguments to the properties is as follows:

� The property is read/write before the Recordset object is opened.

� The property settings are used unless an application passes the corre-sponding arguments when executing the Open method. If an application

Chapter Five—An ADO Primer � 285

Page 305: Learn OLE DB Development With Visual C++ 6.0

chooses to pass an argument, it overrides the corresponding property set-ting, and the property setting is updated with the argument value.

� After an application opens the Recordset object, these properties becomeread-only.

For Recordset objects whose Source property is set to a valid Commandobject, the ActiveConnection property is read-only, even if the Recordsetobject isn’t open. If an application passes a Command object in the Source

argument and also passes an ActiveConnection argument, an ADO erroroccurs. The ActiveConnection property of the Command object must alreadybe set to a valid Connection object or connection string. If an applicationpasses something other than a Command object in the Source argument, anADO error occurs. An application can use the Options argument to optimizeevaluation of the Source argument. If the Options argument is not defined, anapplication may experience diminished performance because ADO must makecalls to the OLE DB provider to determine if the argument is a SQL state-ment, a stored procedure, or a table name. If an application knows whatsource type it is using, setting the Options argument instructs ADO to jumpdirectly to the relevant code. If the Options argument does not match thesource type, an ADO error occurs. The default for the Options argument isadCmdFile if no connection is associated with the recordset. This will typi-cally be the case for persisted Recordset objects. If the data source returns norecords, the provider sets both the BOF and EOF properties to True, and thecurrent record position is undefined. An application can still add new data tothis empty Recordset object if the cursor type allows it. When an applicationhas concluded its operations over an open Recordset object, it should use theClose method to free any associated system resources. Closing an object doesnot remove it from memory; an application can change its property settingsand use the Open method to open it again later. To completely eliminate anobject from memory, set the object variable to Null after calling Release (seeChapter 1). An application can call Open with no operands, and before theActiveConnection property is set, to create an instance of a Recordset createdby 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:

SourceThis optional parameter is a Variant that evaluates to a valid Commandobject variable name, a SQL statement, a table name, a stored procedurecall, or the filename of a persisted Recordset.

ActiveConnectionThis optional parameter is either a Variant that evaluates to a valid

286 � Chapter Five—An ADO Primer

Page 306: Learn OLE DB Development With Visual C++ 6.0

Connection object variable name or a String containing ConnectionStringparameters.

CursorTypeThis optional parameter is a CursorTypeEnum value (listed in Table 5-28)that determines the type of cursor the provider should use when openingthe Recordset.

Table 5-28 Recordset object CursorTypeEnum values

Constant Description

adOpenForwardOnly This default value opens a forward-only–type cursor.

adOpenKeyset This value opens a keyset-type cursor.

adOpenDynamic This value opens a dynamic-type cursor.

adOpenStatic 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 shoulduse when opening the Recordset.

Table 5-29 Recordset object LockTypeEnum values

Constant Description

adLockReadOnly This value is the default and indicates read-only access; theapplication cannot alter the data.

adLockPessimistic This value indicates pessimistic locking, record by record. Inthis case, the OLE DB provider does what is necessary toensure successful editing of the records, usually by lockingrecords at the data source immediately upon editing.

adLockOptimistic This value indicates optimistic locking, record by record. Inthis case, the OLE DB provider uses optimistic locking,locking records only when an application calls the Updatemethod.

adLockBatchOptimistic This value indicates optimistic batch updates. It is requiredfor batch update mode as opposed to immediate updatemode.

Options

This optional parameter is a Long value that indicates how the OLE DBprovider should evaluate the Source argument if it represents somethingother than a Command object, or that the Recordset should be restoredfrom a file where it was previously saved. This parameter is one of theconstants in Table 5-30:

Chapter Five—An ADO Primer � 287

Page 307: Learn OLE DB Development With Visual C++ 6.0

Table 5-30 Recordset object CommandTypeEnum values

Constant Description

adCmdText This value indicates that the OLE DB provider shouldevaluate CommandText as a textual definition of a command.

adCmdTable This value indicates that ADO should generate a SQL queryto return all rows from the table named in CommandText.

adCmdTableDirect This value indicates that the OLE DB provider should returnall rows from the table named in CommandText.

adCmdTable This value indicates that the OLE DB provider shouldevaluate CommandText as a table name.

adCmdStoredProc This value indicates that the OLE DB provider shouldevaluate CommandText as a stored procedure.

adCmdUnknown This value indicates that the type of command in theCommandText argument is not known.

adExecuteAsync This value indicates that the command should executeasynchronously.

adFetchAsync This value indicates that the remaining rows after the initialquantity specified in the CacheSize property should befetched asynchronously.

Requery Method (Recordset Object)

An application should use the Requery method to refresh the entire contentsof a Recordset object from the data source by reissuing the original commandand retrieving the data a second time. Calling this method is equivalent tocalling the Close and Open methods in succession. If you are editing the cur-rent record or adding a new record, an error occurs. While the Recordsetobject is open, the properties that define the nature of the cursor(CursorType, LockType, MaxRecords, etc.) are read-only. Thus, the Requerymethod can only refresh the current cursor. To change any of the cursor prop-erties and view the results, an application must use the Close method so thatthe properties become read/write again. An application can then change theproperty 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:

OptionsThis optional parameter is a bitmask indicating options affecting thisoperation. If this parameter is set to adExecuteAsync, this operation willexecute asynchronously and a RecordsetChangeComplete event will beissued when it concludes.

288 � Chapter Five—An ADO Primer

Page 308: Learn OLE DB Development With Visual C++ 6.0

Resync Method (Recordset Object)

An application should use the Resync method to resynchronize records in thecurrent Recordset with the underlying database. This is useful if an applicationis using either a static or forward-only cursor but also wants to see any changesin the underlying database. Unlike the Requery method, the Resync methoddoes not re-execute the Recordset object’s underlying command; new recordsin the underlying database will not be visible. If the attempt to resynchronizefails because of a conflict with the underlying data, the provider returns warn-ings to the Errors collection and an ADO error occurs. In this case, anapplication should use the Filter property (adFilterConflictingRecords) and theStatus property to locate records with conflicts.

The method has the following syntax:

pRecordset->Resync(AffectRecords, ResyncValues);

The method has the following parameter:

AffectRecordsThis optional parameter is an AffectEnum value (listed in Table 5-31) thatdetermines how many records the Resync method will affect.

Table 5-31 Recordset object AffectEnum values

Constant Description

adAffectCurrent This value indicates that ADO should refresh only thecurrent record.

adAffectGroup This value indicates that ADO should refresh therecords that satisfy the current Filter property setting.An application must set the Filter property to one of thevalid predefined constants in order to use this option.

adAffectAll This default value indicates that ADO should refresh allthe records in the Recordset object, including anyhidden by the current Filter property setting.

ResyncValues

This optional parameter is a ResyncEnum value (given in Table 5-32) thatspecifies whether underlying values are overwritten.

Table 5-32 Recordset object ResyncEnum values

Constant Description

adResyncAllValues This default value indicates that data is overwritten, andpending updates are canceled.

adResyncUnderlyingValues This value indicates that data is not overwritten, andpending updates are not canceled.

Chapter Five—An ADO Primer � 289

Page 309: Learn OLE DB Development With Visual C++ 6.0

Save Method (Recordset Object)

An application should use this method to save (persist) the Recordset in afile. The Save method can only be invoked on an open Recordset. Use theOpen method to later restore the Recordset from FileName. If the Filter prop-erty is in effect for the Recordset, then only the rows accessible under thefilter are saved. If the Recordset is hierarchical, then the current childrecordset and its children are saved, but not the parent recordset. The firsttime an application saves the Recordset, it must specify the FileName parame-ter. If you subsequently invoke Save, omit FileName or else a run-time errorwill occur. If it subsequently invokes Save with a new FileName, the Recordsetis saved to the new file. However, the new file and the original file will bothbe open, since Save does not close Recordset or FileName, so an applicationcan continue to work with the Recordset and save its most recent changes.FileName remains open until the Recordset is closed, during which time otherapplications can read but not write to FileName. If the Save method is calledwhile an asynchronous Recordset fetch, execute, or update operation is inprogress, then Save waits until the asynchronous operation is complete.When the Save method is done, the current row position will be the first rowof the Recordset.

The method has the following syntax:

pRecordset->Save(FileName, PersistFormat);

The method has the following parameters:

FileNameThis optional parameter is the complete path name of the file where theRecordset is to be saved.

PersistFormatThis optional parameter is the format in which the Recordset is to besaved. In ADO 2.0 the only valid value is adPersistADTG.

Warning For reasons of security, the Save method cannot be used from ascript executed by Microsoft Internet Explorer.

Supports Method (Recordset Object)

An application should use this method to determine whether a specifiedRecordset 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 featuresidentified by the CursorOptions argument are supported by the provider.

290 � Chapter Five—An ADO Primer

Page 310: Learn OLE DB Development With Visual C++ 6.0

The method has the following parameter:

CursorOptionsThis parameter is a Long expression that consists of one or more of theCursorOptionEnum values given in Table 5-33.

Table 5-33 Recordset object CursorOptionEnum values

Constant Description

adAddNew This value indicates that the application can use the AddNewmethod to add new records.

adApproxPosition This value indicates that the application can read and set theAbsolutePosition and AbsolutePage properties.

adBookmark This value indicates that the application can use the Bookmarkproperty to gain access to specific records.

adDelete This value indicates that the application can use the Deletemethod to delete records.

adHoldRecords This value indicates that the application can retrieve morerecords or change the next retrieve position withoutcommitting all pending changes.

adMovePrevious This value indicates that the application can use the MoveFirstand MovePrevious methods, and Move or GetRows methodsto move the current record position backward withoutrequiring bookmarks.

adResync This value indicates that the application can update the cursorwith the data visible in the underlying database, using theResync method.

adUpdate This value indicates that the application can use the Updatemethod to modify existing data.

adUpdateBatch This value indicates that the application can use batch updating(UpdateBatch and CancelBatch methods) to transmit changesto the provider in groups.

Update Method (Recordset Object)

An application can use this method to set field values, by doing one of thefollowing:

� Assign values to a Field object’s Value property and calling the Updatemethod.

� Passing a field name and a value as arguments with the Update call.

� Passing an array of field names and an array of values with the Updatecall.

When an application chooses to use arrays of fields and values, there must bean equal number of elements in both arrays. Also, the order of field namesmust match the order of field values. If the number and order of fields andvalues do not match, an ADO error occurs. If the Recordset object supportsbatch updating, then an application can cache multiple changes to one or

Chapter Five—An ADO Primer � 291

Page 311: Learn OLE DB Development With Visual C++ 6.0

more records locally until it chooses to call the UpdateBatch method. If anapplication is editing the current record or adding a new record when it callsthe UpdateBatch method, ADO will automatically call the Update method tosave any pending changes to the current record before transmitting thebatched changes to the provider. If an application chooses to move from therecord it is adding or editing before calling the Update method, ADO willautomatically call Update to save the changes. An application must call theCancelUpdate method if it wants to cancel any changes made to the currentrecord or to discard a newly added record. The current record remains cur-rent after the application calls the Update method.

The method has the following syntax:

pRecordset->Update(Fields, Values);

The method has the following parameters:

FieldsThis optional parameter is a Variant representing a single name or a Vari-ant array representing names or ordinal positions of the field or fields anapplication may wish to modify.

ValuesThis optional parameter is a Variant representing a single value or a Vari-ant 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 aRecordset object in batch update mode to transmit all changes made in aRecordset object to the underlying database. If the Recordset object supportsbatch updating, then an application can cache multiple changes to one ormore records locally until it calls the UpdateBatch method. If an applicationis editing the current record or adding a new record when it calls theUpdateBatch method, ADO will automatically call the Update method to saveany pending changes to the current record before transmitting the batchedchanges to the OLE DB provider. An application should use batch updatingonly with either a keyset or static cursor. If the attempt to transmit changesfails because of a conflict with the underlying data, the OLE DB providerreturns warnings to the Errors collection but does not halt program executionwith an ADO error unless there are conflicts on all the requested records. Inthis case, an application should use the Filter property (adFilterAffected-Records) and the Status property to locate records with conflicts. To cancelall pending batch updates, an application should use the CancelBatchmethod.

The method has the following syntax:

pRecordset->UpdateBatch(AffectRecords);

292 � Chapter Five—An ADO Primer

Page 312: Learn OLE DB Development With Visual C++ 6.0

The method has the following parameter:

AffectRecordsThis optional parameter is an AffectEnum value (given in Table 5-34) thatdetermines how many records the UpdateBatch method will affect.

Table 5-34 Recordset object AffectEnum values

Constant Description

adAffectCurrent This value indicates that ADO should refresh only the currentrecord.

adAffectGroup This value indicates that ADO should refresh the records thatsatisfy the current Filter property setting. An application mustset the Filter property to one of the valid predefined constantsin order to use this option.

adAffectAll This default value indicates that ADO should refresh all therecords in the Recordset object, including any hidden by thecurrent Filter property setting.

The ADO Field Object

A Recordset object has a Fields collection made up of Field objects. Each Fieldobject corresponds to a column in the Recordset. Applications should use theValue property of Field objects to set or return data for the current record.With the collections, methods, and properties of a Field object, an applicationcan:

� Return the name of a field with the Name property.

� View or change the data in the field with the Value property.

� Return the basic characteristics of a field with the Type, Precision, andNumericScale properties.

� Return the declared size of a field with the DefinedSize property.

� Return the actual size of the data in a given field with the ActualSizeproperty.

� Determine what types of functionality are supported for a given field withthe Attributes property and Properties collection.

� Manipulate the values of fields containing long binary or long characterdata with the AppendChunk and GetChunk methods.

� If the provider supports batch updates, resolve discrepancies in field val-ues during batch updating with the OriginalValue and UnderlyingValueproperties.

All of the metadata properties (Name, Type, DefinedSize, Precision, andNumericScale) are available before opening the Field object’s Recordset; thisis useful for dynamically constructing forms.

Chapter Five—An ADO Primer � 293

Page 313: Learn OLE DB Development With Visual C++ 6.0

ActualSize Property (Field Object)

An application should use the ActualSize property to return the actual lengthof a Field object’s value. For all fields, the ActualSize property is read-only. IfADO cannot determine the length of the Field object’s value, the ActualSizeproperty returns adUnknown.

Attributes Property (Field Object)

For a Field object, the Attributes property is read-only; its value can be thesum of any one or more of the FieldAttributeEnum values in Table 5-35.

Table 5-35 Field object FieldAttributeEnum values

Constant Description

adFldMayDefer This value indicates that the field is deferred—that is, thefield values are not retrieved from the data source with thewhole record, but only when an application explicitlyaccesses them.

adFldUpdatable This value indicates that an application can write to the field.

adFldUnknownUpdatable This value indicates that the OLE DB provider cannotdetermine if you can write to the field.

adFldFixed This value indicates that the field contains fixed-length data.

adFldIsNullable This value indicates that the field accepts Null values.

adFldMayBeNull This value indicates that an application can read Null valuesfrom the field.

adFldLong This value indicates that the field is a Long binary field. Alsoindicates that an application can use the AppendChunk andGetChunk methods.

adFldRowID This value indicates that the field contains a persistent rowidentifier that cannot be written to and has no meaningfulvalue except to identify the row (such as a record number,unique identifier, and so forth).

adFldRowVersion This value indicates that the field contains some kind of timeor date stamp used to track updates.

adFldCacheDeferred This value indicates that the OLE DB provider caches fieldvalues and that subsequent reads are done from the cache.

DefinedSize Property (Field Object)

Applications should use the DefinedSize property to determine the datacapacity of a Field object. The DefinedSize and ActualSize properties aredifferent.

Name Property (Field Object)

An application should use the Name property to assign a name to or retrievethe name of a Command, Field, Parameter, or Property object. For Parameter

294 � Chapter Five—An ADO Primer

Page 314: Learn OLE DB Development With Visual C++ 6.0

objects not yet appended to the Parameters collection, the Name property isread/write. For appended Parameter objects and all other objects, the Nameproperty is read-only. Names do not have to be unique within a collection. Anapplication can retrieve the Name property of an object by an ordinal refer-ence, 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 manydigits to the right of the decimal point will be used to represent values for anumeric Parameter or Field object. The NumericScale property is read-only.

OriginalValue Property (Field Object)

An application should use the OriginalValue property to return the originalfield value for a field from the current record. In immediate update mode(the OLE DB provider writes changes to the underlying data source once anapplication calls the Update method), the OriginalValue property returns thefield value that existed prior to any changes (that is, since the last Updatemethod call). This is the same value that the CancelUpdate method uses toreplace the Value property. In batch update mode (the OLE DB providercaches multiple changes and writes them to the underlying data source onlywhen an application calls the UpdateBatch method), the OriginalValue prop-erty returns the field value that existed prior to any changes (that is, sincethe last UpdateBatch method call). This is the same value that theCancelBatch method uses to replace the Value property. When an applicationuses this property with the UnderlyingValue property, it can resolve conflictsthat arise from batch updates.

Precision Property (Field Object)

An application should use the Precision property to determine the maximumnumber of digits used to represent values for a numeric Parameter or Fieldobject. The property is a Byte value, indicating the maximum total number ofdigits 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 Fieldobject. It contains one of the DataTypeEnum values in Table 5-36. The corre-sponding OLE DB type indicator is shown in parentheses.

Table 5-36 Field object DataTypeEnum values

Constant Description

adArray This value indicates that the type is joined in a logical ORtogether with another type to indicate that the data is asafe-array of that type (DBTYPE_ARRAY).

Chapter Five—An ADO Primer � 295

Page 315: Learn OLE DB Development With Visual C++ 6.0

Table 5-36 Field object DataTypeEnum values (cont.)

Constant Description

adBigInt 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 ORtogether with another type to indicate that the data is apointer to data of the other type (DBTYPE_BYREF).

adBSTR This value indicates that the type is a null-terminatedcharacter 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 fourdigits to the right of the decimal point. It is stored in an8-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 wholepart of which is the number of days since December 30,1899, and the fractional part of which is the fraction of aday.

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 valuewith a fixed precision and scale (DBTYPE_DECIMAL).

adDouble This value indicates that the type is a double-precisionfloating-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 uniqueidentifier (GUID) (DBTYPE_GUID).

adIDispatch This value indicates that the type is a pointer to anIDispatch interface on an OLE object(DBTYPE_IDISPATCH).

adInteger This value indicates that the type is a 4-byte signed integer(DBTYPE_I4).

296 � Chapter Five—An ADO Primer

Page 316: Learn OLE DB Development With Visual C++ 6.0

Table 5-36 Field object DataTypeEnum values (cont.)

Constant Description

adIUnknown This value indicates that the type is a pointer to anIUnknown interface on an OLE object(DBTYPE_IUNKNOWN).

adNumeric This value indicates that the type is an exact numeric valuewith a fixed precision and scale (DBTYPE_NUMERIC).

adSingle This value indicates that the type is a single-precisionfloating-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 unsignedinteger (DBTYPE_UI8).

adUnsignedInt This value indicates that the type is a 4-byte unsignedinteger (DBTYPE_UI4).

adUnsignedSmallInt This value indicates that the type is a 2-byte unsignedinteger (DBTYPE_UI2).

adUnsignedTinyInt This value indicates that the type is a 1-byte unsignedinteger (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 ORtogether with another type to indicate that the data is aDBVECTOR structure, as defined by OLE DB, thatcontains a count of elements and a pointer to data of theother type (DBTYPE_VECTOR).

adWChar This value indicates that the type is a null-terminatedUnicode character string (DBTYPE_WSTR).

UnderlyingValue Property (Field Object)

An application should use the UnderlyingValue property to return the currentfield value from the database. The field value in the UnderlyingValue prop-erty is the value that is visible to an application’s transaction and may be theresult of a recent update by another transaction. This may differ from theOriginalValue property, which reflects the value that was originally returnedto the Recordset. This is similar to using the Resync method, but theUnderlyingValue property returns only the value for a specific field from thecurrent record. This is the same value that the Resync method uses to replacethe Value property.

Chapter Five—An ADO Primer � 297

Page 317: Learn OLE DB Development With Visual C++ 6.0

Value Property (Field Object)

An application should use the Value property to set or return data from Fieldobjects. ADO allows setting and returning Long amounts of binary data withthe Value property. Otherwise, the rules for manipulating Value depend onthe data type (see Table 5-36).

AppendChunk Method (Field Object)

An application should use the AppendChunk method on a Field object to fillit with Long binary or character data. In situations where system memory islimited, an application can use the AppendChunk method to manipulate Longvalues in portions rather than in their entirety. If the adFldLong bit in theAttributes property of a Field object is set to True, an application can use theAppendChunk method for that field. The first AppendChunk call on a Fieldobject writes data to the field, overwriting any existing data. SubsequentAppendChunk calls add to existing data. If an application is appending datato one field and then chooses to set or read the value of another field in thecurrent record, ADO assumes that the application is done appending data tothe first field. If an application calls the AppendChunk method on the firstfield again, ADO interprets the call as a new AppendChunk operation andoverwrites the existing data. Accessing fields in other Recordset objects thatare not clones of the first Recordset object will not disrupt AppendChunkoperations. If there is no current record when an application callsAppendChunk on a Field object, an ADO error occurs.

The method has the following syntax:

pField->AppendChunk(Data);

The method has the following parameter:

DataThis parameter is a Variant containing the data the application wants toappend to the Field object.

GetChunk Method (Field Object)

An application should use the GetChunk method on a Field object to retrievepart or all of its Long binary or character data. In situations where systemmemory is limited, an application can use the GetChunk method to manipu-late Long values in portions rather than in their entirety. The data that aGetChunk call returns is assigned to pMyVariant. If Size is greater than theremaining data, the GetChunk method returns only the remaining data with-out padding pMyVariant with empty spaces. If the field is empty, theGetChunk method returns Null. Each subsequent GetChunk call retrieves datastarting from where the previous GetChunk call left off. However, if an appli-cation is retrieving data from one field and then it chooses to set or read thevalue of another field in the current record, ADO assumes the application is

298 � Chapter Five—An ADO Primer

Page 318: Learn OLE DB Development With Visual C++ 6.0

done retrieving data from the first field. If an application calls the GetChunkmethod on the first field again, ADO interprets the call as a new GetChunkoperation and starts reading from the beginning of the data. Accessing fieldsin other Recordset objects that are not clones of the first Recordset object willnot disrupt GetChunk operations. If the adFldLong bit in the Attributes prop-erty of a Field object is set to True, an application can use the GetChunkmethod for that field. If there is no current record when an application usesthe 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:

SizeThis parameter is a Long expression equal to the number of bytes or char-acters an application wants to retrieve.

The ADO Error Object

Any operation involving ADO objects can generate one or more OLE DB pro-vider errors. As each error occurs, one or more Error objects are placed in theErrors collection of the Connection object. When another ADO operation gen-erates an error, the Errors collection is cleared, and the new set of Errorobjects is placed in the Errors collection. Each Error object represents a spe-cific provider error, not an ADO error. An application can read an Errorobject’s properties to obtain specific details about each error, including thefollowing:

� The Description property, which contains the text of the error.

� The Number property, which contains the Long integer value of the errorconstant.

� The Source property, which identifies the object that raised the error. Thisis particularly useful when an application has several Error objects in theErrors collection following a request to a data source.

� The SQLState and NativeError properties, which provide information fromSQL data sources.

When an OLE DB provider error occurs, it is placed in the Errors collection ofthe Connection object. ADO supports the return of multiple errors by a singleADO operation to allow for error information specific to the provider. TheErrors collection on the Connection object is cleared and populated onlywhen the OLE DB provider generates a new error, or when the Clear methodis called. Some properties and methods return warnings that appear as Errorobjects in the Errors collection but do not halt a program’s execution with an

Chapter Five—An ADO Primer � 299

Page 319: Learn OLE DB Development With Visual C++ 6.0

ADO error. Before an application calls the Resync, UpdateBatch, orCancelBatch methods on a Recordset object or the Open method on a Con-nection object, or sets the Filter property on a Recordset object, it should firstcall the Clear method on the Errors collection so that it can read the Countproperty 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 descrip-tion of the error. It should display this property to alert the user to an errorthat it cannot or does not want to handle. The string will come from eitherADO or an OLE DB provider. OLE DB providers are responsible for passingspecific error text to ADO. ADO adds an Error object to the Errors collectionfor each OLE DB provider error or warning it receives. Applications shouldenumerate the Errors collection to trace the errors that the OLE DB providerpasses. The property holds a String value.

NativeError Property (Error Object)

Applications should use the NativeError property to retrieve the data-base-specific error information for a particular Error object. When using theMicrosoft ODBC Provider for OLE DB with a Microsoft SQL Server database,native error codes that originate from SQL Server pass through ODBC andthe ODBC provider to the ADO NativeError property. The property holds aLong value.

Number Property (Error Object)

Applications should use the Number property to determine which erroroccurred. The value of the property is a unique number that corresponds tothe error condition (defined by the OLE DB provider). The property holds aLong value.

Source Property (Error Object)

Applications should use the Source property on an Error object to determinethe name of the object or application that originally generated an error. Thiscould be the object’s class name or programmatic ID. For errors in ADODB,the property value will be ADODB.ObjectName, where ObjectName is thename 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-charactererror code that the provider returns when an error occurs during the

300 � Chapter Five—An ADO Primer

Page 320: Learn OLE DB Development With Visual C++ 6.0

processing of a SQL statement. When using the Microsoft OLE DB Providerfor ODBC with a Microsoft SQL Server database, SQL state error codes origi-nate from ODBC based either on errors specific to ODBC or on errors thatoriginate from Microsoft SQL Server and are then mapped to ODBC errors.These error codes are documented in the ANSI SQL standard, but may beimplemented differently by different data sources. The property holds aString value.

The ADO Property Object

ADO objects have two types of properties: built-in and dynamic. Built-inproperties are those properties implemented in ADO and immediately avail-able to any new object. They do not appear as Property objects in an object’sProperties collection, so although an application can change their values, itcannot modify their characteristics. Dynamic properties are defined by theunderlying OLE DB provider, and appear in the Properties collection for theappropriate ADO object. For example, a property specific to the provider mayindicate if a Recordset object supports transactions or updating. These addi-tional properties will appear as Property objects in that Recordset object’sProperties collection. Dynamic properties can be referenced only through thecollection. An application must consult the documentation for each OLE DBprovider 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:

� The Attributes property is a Long value that indicates characteristics of theproperty specific to the OLE DB provider.

� The Name property is a String that identifies the property.

� The Type property is an Integer that specifies the property data type.

� 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 thesum of any one or more of the PropertyAttributesEnum values in Table 5-37:

Table 5-37 Property object PropertyAttributesEnum values

Constant Description

adPropNotSupported This value indicates that the property is not supported bythe provider.

adPropRequired This value indicates that the user must specify a value forthis property before the data source is initialized.

adPropOptional This value indicates that the user does not need to specify avalue for this property before the data source is initialized.

adPropRead This value indicates that the user can read the property.

adPropWrite This value indicates that the user can set the property.

Chapter Five—An ADO Primer � 301

Page 321: Learn OLE DB Development With Visual C++ 6.0

Name Property (Property Object)

Applications should use the Name property to retrieve the name of a Propertyobject. The Name property is read-only. Names do not have to be uniquewithin a collection. An application can retrieve the Name property of a Prop-erty object by an ordinal reference, after which it can refer to the objectdirectly by name. For example, if MyConnection.Properties(15).Name yieldsTransactionState, an application can subsequently refer to this property asMyConnection.Properties(“TransactionState”).

Type Property (Property Object)

This read-only property indicates the operational type or data type of a Prop-erty object. It contains one of the DataTypeEnum values in Table 5-38. Thecorresponding OLE DB type indicator is shown in parentheses.

Table 5-38 Property object DataTypeEnum values

Constant Description

adArray This value indicates that the type is joined in a logical ORtogether with another type to indicate that the data is asafe-array of that type (DBTYPE_ARRAY).

adBigInt This value indicates that the type is an 8-byte signed integer(DBTYPE_18).

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 ORtogether with another type to indicate that the data is apointer to data of the other type (DBTYPE_BYREF).

adBSTR This value indicates that the type is a null-terminatedcharacter 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 fourdigits to the right of the decimal point. It is stored in an8-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 wholepart of which is the number of days since December 30,1899, and the fractional part of which is the fraction of aday.

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).

302 � Chapter Five—An ADO Primer

Page 322: Learn OLE DB Development With Visual C++ 6.0

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 valuewith a fixed precision and scale (DBTYPE_DECIMAL).

adDouble This value indicates that the type is a double-precisionfloating-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 uniqueidentifier (GUID) (DBTYPE_GUID).

adIDispatch This value indicates that the type is a pointer to anIDispatch 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 anIUnknown interface on an OLE object(DBTYPE_IUNKNOWN).

adNumeric This value indicates that the type is an exact numeric valuewith a fixed precision and scale (DBTYPE_NUMERIC).

adSingle This value indicates that the type is a single-precisionfloating-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 unsignedinteger (DBTYPE_UI8).

adUnsignedInt This value indicates that the type is a 4-byte unsignedinteger (DBTYPE_UI4).

adUnsignedSmallInt This value indicates that the type is a 2-byte unsignedinteger (DBTYPE_UI2).

adUnsignedTinyInt This value indicates that the type is a 1-byte unsignedinteger (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).

Chapter Five—An ADO Primer � 303

Page 323: Learn OLE DB Development With Visual C++ 6.0

Table 5-38 Property object DataTypeEnum values (cont.)

Constant Description

adVector This value indicates that the type is joined in a logical ORtogether with another type to indicate that the data is aDBVECTOR structure, as defined by OLE DB, that containsa count of elements and a pointer to data of the other type(DBTYPE_VECTOR).

adWChar This value indicates that the type is a null-terminatedUnicode character string (DBTYPE_WSTR).

Value Property (Property Object)

Applications should use the Value property to set or return property settingswith Property objects; the property is a Variant and can contain any appropri-ate data as indicated in Table 5-38. ADO allows setting and returning Longbinary data with the Value property.

The ADO Parameter Object

Many providers support parameterized commands. These are commands inwhich the desired action is defined once, but variables (or parameters) areused to alter some details of the command. For example, a SQL SELECT state-ment could use a parameter to define the matching criteria of a WHEREclause, and another to define the column name for a SORT BY clause. Param-eter objects represent parameters associated with parameterized queries, orthe in/out arguments and the return values of stored procedures. If an appli-cation knows the names and properties of the parameters associated with thestored procedure or parameterized query it wishes to call, it can use theCreateParameter method to create Parameter objects with the appropriateproperty settings and use the Append method to add them to the Parameterscollection. This lets an application set and return parameter values withouthaving to call the Refresh method on the Parameters collection to retrieve theparameter information from the OLE DB provider, a lengthy andresource-intensive operation. With the collections, methods, and properties ofa Parameter object, an application can:

� Set or return the name of a parameter with the Name property.

� Set or return the value of a parameter with the Value property.

� 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 theParameter object; its value can be the sum of any one or more of theParameterAttributesEnum values in Table 5-39. The property is read-only.

304 � Chapter Five—An ADO Primer

Page 324: Learn OLE DB Development With Visual C++ 6.0

Table 5-39 Parameter object ParameterAttributesEnum values

Constant Description

adParamSigned This default value indicates that the parameter accepts signedvalues.

adParamNullable This value indicates that the parameter accepts Null values.

adParamLong 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 retrievethe name of a Parameter object. For Parameter objects not yet appended tothe Parameters collection, the Name property is read/write. For appendedParameter objects, the Name property is read-only. Names do not have to beunique within a collection. An application can retrieve the Name property ofan object by an ordinal reference, after which it can refer to the objectdirectly by name. For example, if MyConnection.Parameters(13).Name yieldsBinaryParameter, an application can subsequently refer to this parameter asMyConnection.Parameters(“BinaryParameter”).

NumericScale Property (Parameter Object)

An application should use the NumericScale property to determine how manydigits to the right of the decimal point will be used to represent values for anumeric 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 maximumnumber 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 forvalues written to or read from the Value property of a Parameter object. TheSize property is read/write. If an application chooses to specify a variable-length data type for a Parameter object (for example, any String type, such asadVarChar), it must set the object’s Size property before appending it to theParameters collection; otherwise an ADO error occurs. If an application hasalready appended the Parameter object to the Parameters collection of aCommand object and it chooses to change its type to a variable-length datatype, it must set the Parameter object’s Size property before executing theCommand object; otherwise an ADO error occurs. If an application uses theRefresh method to obtain parameter information from the OLE DB providerand it returns one or more variable-length data type Parameter objects, ADO

Chapter Five—An ADO Primer � 305

Page 325: Learn OLE DB Development With Visual C++ 6.0

may allocate memory for the parameters based on their maximum potentialsize, which could cause an error during execution. To prevent an error, anapplication should explicitly set the Size property for all parameters beforeexecuting the command.

Type Property (Parameter Object)

This read-write property indicates the operational type or data type of aParameter 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 Description

adArray This value indicates that the type is joined in a logical OR togetherwith another type to indicate that the data is a safe-array of thattype (DBTYPE_ARRAY).

adBigInt 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 togetherwith another type to indicate that the data is a pointer to data of theother type (DBTYPE_BYREF).

adBSTR This value indicates that the type is a null-terminated characterstring (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 thedecimal point. It is stored in an 8-byte signed integer scaled by10,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 numberof days since December 30, 1899, and the fractional part of which isthe 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 afixed precision and scale (DBTYPE_DECIMAL).

adDouble This value indicates that the type is a double-precision floating-pointvalue (DBTYPE_R8).

306 � Chapter Five—An ADO Primer

Page 326: Learn OLE DB Development With Visual C++ 6.0

Table 5-40 Parameter object DataTypeEnum values (cont.)

Constant Description

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 IDispatchinterface 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 IUnknowninterface 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 stringvalue.

adNumeric This value indicates that the type is an exact numeric value with afixed precision and scale (DBTYPE_NUMERIC).

adSingle This value indicates that the type is a single-precision floating-pointvalue (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 togetherwith another type to indicate that the data is a DBVECTORstructure, as defined by OLE DB, that contains a count of elementsand a pointer to data of the other type (DBTYPE_VECTOR).

Chapter Five—An ADO Primer � 307

Page 327: Learn OLE DB Development With Visual C++ 6.0

Table 5-40 Parameter object DataTypeEnum values (cont.)

Constant Description

adVarWChar This value indicates that the type is a null-terminated Unicodecharacter string.

adWChar This value indicates that the type is a null-terminated Unicodecharacter string (DBTYPE_WSTR).

Value Property (Parameter Object)

Applications should use the Value property to set or return parameter valueswith Parameter objects. The property holds a Variant which can assume anyof the allowed data types in Table 5-40. ADO allows setting and returningLong binary data with the Value property.

ADO EventsADO Events

Events are notifications between applications, or between the operating sys-tem and applications, that some state of the application or system haschanged. 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 ofwhich has a section below that explains when it happens, what information itprovides, and what ADO-using applications should do about the event.

BeginTransComplete Event

This event handler is called after the associated operation on the Connectionobject finishes executing; BeginTransComplete is called after the BeginTransoperation. In Visual C++ multiple Connection objects can share the sameevent handling method. The method implementation should use the returnedConnection object to determine which object (interface pointer) caused theevent. If the Attributes property is set to adXactCommitRetaining oradXactAbortRetaining, a new transaction is started after committing or roll-ing back a transaction. A BeginTransComplete event handler routine shouldignore 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 theBeginTrans operation that caused this event.

pError

This parameter is an Error object that describes the error that occurred if

308 � Chapter Five—An ADO Primer

Page 328: Learn OLE DB Development With Visual C++ 6.0

the value of EventStatusEnum is adStatusErrorsOccurred; otherwise it isnot set.

adStatus

This parameter is an EventStatusEnum status value which, when any ofthese methods is called, is set to adStatusOK if the operation that causedthe event was successful, or adStatusErrorsOccurred if the operationfailed. The event handler can prevent subsequent notifications by settingthis 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 Connectionobject finishes executing; CommitTransComplete is called after theCommitTrans operation. In Visual C++ multiple Connection objects canshare the same event handling method. The method implementation shoulduse the returned Connection object to determine which object (interfacepointer) caused the event. If the Attributes property is set to adXactCommit-Retaining or adXactAbortRetaining, a new transaction is started after commit-ting 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 ifthe value of EventStatusEnum is adStatusErrorsOccurred; otherwise it isnot set.

adStatus

This parameter is an EventStatusEnum status value that is set toadStatusOK if the operation that caused the event was successful, oradStatusErrorsOccurred if the operation failed. In RollbackTrans-Complete, an application can prevent subsequent notifications by settingthis 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 iscalled after a connection ends.

Chapter Five—An ADO Primer � 309

Page 329: Learn OLE DB Development With Visual C++ 6.0

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 ifthe value of adStatus is adStatusErrorsOccurred; otherwise it is not set.

adStatus

This parameter is an EventStatusEnum status value that is set toadStatusOK if the operation that caused the event was successful, oradStatusErrorsOccurred if the operation failed. When ConnectComplete iscalled, this parameter is set to adStatusCancel if a WillConnect methodhas requested cancellation of the pending connection. Before Connect-Complete returns, an application can set this parameter toadStatusUnwantedEvent 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 ConnectCompleteevent 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 ifthe value of adStatus is adStatusErrorsOccurred; otherwise it is not set.

adStatus

This parameter is an EventStatusEnum status value that is set toadStatusOK if the operation that caused the event was successful, oradStatusErrorsOccurred if the operation failed. Before Disconnectreturns, an application can set this parameter to adStatusUnwantedEventto 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 pastthe end of the Recordset. An EndOfRecordset event may also occur if the

310 � Chapter Five—An ADO Primer

Page 330: Learn OLE DB Development With Visual C++ 6.0

Recordset.MoveNext operation fails for other reasons. This event handler iscalled when the application attempts to move past the end of the records inthe pRecordset parameter, perhaps as a result of calling MoveNext. However,while in this event handler the application could retrieve more records from adatabase and append them to the end of pRecordset. In that case, the eventhandler implementation would set fMoreData to VARIANT_TRUE, and returnfrom EndOfRecordset. Then the application could safely call MoveNext againto 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 toappend new records to pRecordset while processing this event. BeforeEndOfRecordset returns, the implementation should add the new data,then set this parameter to True to indicate that there is a new end to theRecordset.

adStatus

This parameter is an EventStatusEnum status value that is set toadStatusOK if the operation that caused the event was successful. It is setto adStatusCantDeny if this method cannot request cancellation of theoperation that caused this event. Before EndOfRecordset returns, anapplication can set this parameter to adStatusUnwantedEvent to preventsubsequent 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. AnExecuteComplete event can occur due to Connection.Execute, Command.Exe-cute, 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 bythe command.

Chapter Five—An ADO Primer � 311

Page 331: Learn OLE DB Development With Visual C++ 6.0

pError

This parameter is an Error object that describes the error that occurred ifthe value of adStatus is adStatusErrorsOccurred; otherwise it is not set.

adStatus

This parameter is an EventStatusEnum status value that is set toadStatusOK if the operation that caused the event was successful, oradStatusErrorsOccurred if the operation failed. Before this methodreturns, an application can set this parameter to adStatusUnwantedEventto prevent subsequent notifications.

pCommand

This parameter is the Command object that was executed. If the execu-tion was not via a Command object, then this parameter is empty.

pRecordset

This parameter is a Recordset object containing the result of the execu-tion, which may be empty.

pConnection

This parameter is a Connection object on which the command wasexecuted.

FetchComplete Event

This event handler is called after all the records in a lengthy asynchronousoperation have been retrieved (fetched) into the Recordset. It is normallyused 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 ifthe value of adStatus is adStatusErrorsOccurred; otherwise it is not set.

adStatus

This parameter is an EventStatusEnum status value that is set toadStatusOK if the operation that caused the event was successful, oradStatusErrorsOccurred if the operation failed. Before this methodreturns, an application can set this parameter to adStatusUnwantedEventto prevent subsequent notifications.

pRecordset

This parameter is a Recordset object containing the result of the fetchoperation, which may be empty.

312 � Chapter Five—An ADO Primer

Page 332: Learn OLE DB Development With Visual C++ 6.0

FetchProgress Event

This event handler is called periodically during a lengthy asynchronous oper-ation to report how many rows have currently been retrieved (fetched) intothe current Recordset. Its normal use is to update a progress bar or othervisual display to maintain a user comfort level during the lengthy operationto 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 cur-rently been retrieved.

MaxProgress

This parameter is a Long containing the maximum number of recordsexpected to be retrieved.

pRecordset

This parameter is a Recordset object for which the records are beingretrieved.

FieldChangeComplete Event

This event handler is called after the value of one or more Field objects haschanged. A FieldChangeComplete event can occur due to the followingRecordset operations: calls to Value and Update with field and value arrayparameters.

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 withpending changes.

pError

This parameter is an Error object that describes the error that occurred ifthe value of adStatus is adStatusErrorsOccurred; otherwise it is not set.

adStatus

This parameter is an EventStatusEnum status value that is set toadStatusOK if the operation that caused the event was successful, or

Chapter Five—An ADO Primer � 313

Page 333: Learn OLE DB Development With Visual C++ 6.0

adStatusErrorsOccurred if the operation failed. Before FieldChange-Complete returns, an application can set this parameter toadStatusUnwantedEvent 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 com-pletes successfully and additional information is returned by an OLE DBprovider.

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 thatoccurred if the value of adStatus is adStatusErrorsOccurred; otherwise itis not set. Multiple OLE DB provider warnings can be returned, which canbe found by enumerating the Errors collection.

adStatus

This parameter is an EventStatusEnum status value that is set toadStatusOK if the operation that caused the event was successful, oradStatusErrorsOccurred if the operation failed. Before this methodreturns, an application can set this parameter to adStatusUnwantedEventto prevent subsequent notifications.

pConnection

This parameter is a Connection object on which the command wasexecuted.

MoveComplete Event

The MoveComplete method is called after the current position in theRecordset changes. A MoveComplete event can occur due to the followingRecordset method calls (depending on whether they cause the position tochange): Open, Move, MoveFirst, MoveLast, MoveNext, MovePrevious, Book-mark, AddNew, Delete, Requery, and Resync.

The event handler function has the following syntax:

MoveComplete(adReason, pError, adStatus, pRecordset);

314 � Chapter Five—An ADO Primer

Page 334: Learn OLE DB Development With Visual C++ 6.0

The event handler function has the following parameters:

adReason

This parameter is an EventReasonEnum value that specifies the reason forthis event. Its value can be adRsnMoveFirst, adRsnMoveLast,adRsnMoveNext, adRsnMovePrevious, adRsnMove, or adRsnRequery.Each such enumeration value corresponds to the name of the methodthat caused the event to fire (i.e., a value of adRsnMoveLast means that acall was made to MoveLast, resulting in a position change that fired theevent).

pError

This parameter is an Error object that describes the error that occurred ifthe value of adStatus is adStatusErrorsOccurred; otherwise it is not set.

adStatus

This parameter is an EventStatusEnum status value that is set toadStatusOK if the operation that caused the event was successful, oradStatusErrorsOccurred if the operation failed. Before MoveCompletereturns, an application can set this parameter to adStatusUnwantedEventto 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 opera-tion (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) ofthe 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 thatcaused the error.

CancelDisplay

This parameter is a Boolean that if set to True prevents the error frombeing displayed in a dialog box (useful for remote server applicationswhere there is no one to see and dismiss the dialog box).

Chapter Five—An ADO Primer � 315

Page 335: Learn OLE DB Development With Visual C++ 6.0

OnReadyStateChange Event

This event handler is called whenever the value of the ReadyState propertychanges. The ReadyState property reflects the progress of an RDS.DataControl object as it asynchronously fetches data into its Recordset object. Usethe onReadyStateChange event handler to monitor changes in the ReadyStateproperty whenever they occur, rather than periodically checking the prop-erty’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 Record-ChangeComplete 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 forthis event. Its value can be adRsnAddNew, adRsnDelete, adRsn- Update,adRsnUndoUpdate, adRsnUndoAddNew, adRsnUndoDelete, oradRsnFirstChange, with the value of the enumeration giving the name ofthe method that fired the event (i.e., adRsnDelete means that a Deletemethod 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 ifthe value of adStatus is adStatusErrorsOccurred; otherwise it is not set.

adStatus

This parameter is an EventStatusEnum status value that is set toadStatusOK if the operation that caused the event was successful, oradStatusErrorsOccurred if the operation failed. Before RecordChange-Complete returns, an application can set this parameter toadStatusUnwantedEvent to prevent subsequent notifications.

316 � Chapter Five—An ADO Primer

Page 336: Learn OLE DB Development With Visual C++ 6.0

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 Recordset-ChangeComplete event can occur due to the following Recordset operations:Requery, Resync, Close, Open, and Filter (depending on whether they changethe 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 forthis event. Its value can be adRsnReQuery, adRsnReSynch, adRsnClose, oradRsnOpen, with the name of the enumeration giving the name of themethod that fired the event (i.e., adRsnClose means that the Closemethod call fired the current event).

pError

This parameter is an Error object that describes the error that occurred ifthe value of adStatus is adStatusErrorsOccurred; otherwise it is not set.

adStatus

This parameter is an EventStatusEnum status value that is set toadStatusOK if the operation that caused the event was successful,adStatusErrorsOccurred if the operation failed, or adStatusCancel if theoperation associated with the previously accepted WillChangeRecordsetevent has been canceled. Before RecordsetChangeComplete returns, anapplication can set this parameter to adStatusUnwantedEvent to preventsubsequent 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 Con-nection object finishes executing. RollbackTransComplete is called after theRollbackTrans operation. In Visual C++ multiple connections can share thesame event handling method. The event handler uses the returned Connec-tion object to determine which object caused the event. If the Attributesproperty is set to adXactCommitRetaining or adXactAbortRetaining, a newtransaction is started after committing or rolling back a transaction.

Chapter Five—An ADO Primer � 317

Page 337: Learn OLE DB Development With Visual C++ 6.0

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 ifthe value of EventStatusEnum is adStatusErrorsOccurred; otherwise it isnot set.

adStatus

This parameter is an EventStatusEnum status value that is set toadStatusOK if the operation that caused the event was successful, oradStatusErrorsOccurred if the operation failed. In RollbackTrans-Complete, an application can prevent subsequent notifications by settingthis 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 ofone or more Field objects in the Recordset. A WillChangeField event canoccur due to the following Recordset operations: calls to Value and Updatewith 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 withpending changes.

adStatus

This parameter is an EventStatusEnum status value that is set toadStatusOK if the operation that caused the event was successful. It is setto adStatusCantDeny if this method cannot request cancellation of thepending operation. Before WillChangeField returns, an application canset this parameter to adStatusCancel to request cancellation of the pend-ing operation.

pRecordset

This parameter is a Recordset object for which this event occurred.

318 � Chapter Five—An ADO Primer

Page 338: Learn OLE DB Development With Visual C++ 6.0

WillChangeRecord Event

This event handler is called before one or more records (rows) in theRecordset change. A WillChangeRecord event can occur due to the followingRecordset operations: Update, Delete, CancelUpdate, AddNew, UpdateBatch,and CancelBatch (depending on whether they actually change one or morerecords). During the WillChangeRecord event, the Recordset Filter property isset to adFilterAffectedRecords. It is illegal to change this property while pro-cessing 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 forthis event. Its value can be adRsnAddNew, adRsnDelete, adRsnUpdate,adRsnUndoUpdate, adRsnUndoAddNew, adRsnUndo- Delete, oradRsnFirstChange, with the value of the enumeration giving the name ofthe method that fired the event (i.e., adRsnDelete means that a Deletemethod 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 toadStatusOK if the operation that caused the event was successful. It is setto adStatusCantDeny if this method cannot request cancellation of thepending operation. Before WillChangeRecord returns, an application canset this parameter to adStatusCancel to request cancellation of the opera-tion 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 theRecordset. A WillChangeRecordset event can occur due to the followingRecordset operations: Requery, Resync, Close, Open, and Filter (dependingon whether they change the Recordset).

The event handler function has the following syntax:

WillChangeRecordset(adReason, adStatus, pRecordset);

Chapter Five—An ADO Primer � 319

Page 339: Learn OLE DB Development With Visual C++ 6.0

The event handler function has the following parameters:

adReason

This parameter is an EventReasonEnum value that specifies the reason forthis event. Its value can be adRsnReQuery, adRsnReSynch, adRsnClose, oradRsnOpen, with the name of the enumeration giving the name of themethod that fired the event (i.e., adRsnClose means that the Closemethod call fired the current event).

adStatus

This parameter is an EventStatusEnum status value that is set toadStatusOK if the operation that caused the event was successful. It is setto adStatusCantDeny if this method cannot request cancellation of thepending operation. Before WillChangeRecordset returns, an applicationcan set this parameter to adStatusCancel to request cancellation of thepending operation or to adStatusUnwantedEvent to prevent subsequentnotifications.

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 beused in the pending connection are supplied as input parameters and can bechanged before the method returns. This event handler may return a requestthat the pending connection be canceled. When this method is called, theConnectionString, UserID, Password, and Options parameters are set to the val-ues established by the operation that caused this event. When this method iscanceled, ConnectComplete will be called with its adStatus parameter set toadStatusErrorsOccurred.

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 thepending connection.

UserID

This parameter is a String containing a user name for the pendingconnection.

Password

This parameter is a String containing a password for the pendingconnection.

320 � Chapter Five—An ADO Primer

Page 340: Learn OLE DB Development With Visual C++ 6.0

Options

This parameter is a Long value that indicates how the provider shouldevaluate ConnectionString, using the CommandTypeEnum values.

adStatus

This parameter is an EventStatusEnum status value. When this event han-dler is called, this parameter is set to adStatusOK if the operation thatcaused the event was successful. This parameter is set to adStatusCant-Deny if this method cannot request cancellation of the pending operation.Before this method returns, an application can set this parameter toadStatusUnwantedEvent to prevent subsequent notifications or toadStatusCancel to request the connection operation that caused cancella-tion of this notification.

pConnection

This parameter is the Connection object for which this event notificationapplies.

WillExecute Event

This event handler is called just before a pending command executes on thecurrent Connection object. The event gives the application an opportunity toexamine and modify the pending execution parameters. This method mayreturn a request that the pending command be canceled. A WillExecute eventmay occur due to Connection.Execute, Command.Execute, orRecordset.Open. The corresponding pConnection, pCommand, or pRecordsetparameter will be set to the object causing the event and the remaining twowill 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 pro-cedure name.

CursorType

This parameter is a CursorTypeEnum that contains the type of cursor forthe recordset that will be opened. This parameter cannot be changed if itis set to adOpenUnspecified when this method is called.

LockType

This parameter is a LockTypeEnum that contains the lock type for therecordset that will be opened. This parameter cannot be changed if it isset to adLockUnspecified when this method is called.

Chapter Five—An ADO Primer � 321

Page 341: Learn OLE DB Development With Visual C++ 6.0

Options

This parameter is a Long value that contains settings that can be used toexecute the command or open the recordset.

adStatus

This parameter is an EventStatusEnum status value that may beadStatusCantDeny or adStatusOK when this method is called. If it isadStatusCantDeny, this method may not request cancellation of the pend-ing operation. Before this method returns, it can set this parameter toadStatusUnwantedEvent to prevent subsequent notifications or toadStatusCancel to request cancellation of the operation that caused thisevent.

pCommand

This parameter is the Command object for which this event notificationapplies.

pRecordset

This parameter is the Recordset object for which this event notificationapplies.

pConnection

This parameter is the Connection object for which this event notificationapplies.

WillMove Event

The WillMove method is called before a pending operation changes the cur-rent position in the Recordset. A WillMove event can occur due to thefollowing Recordset method calls (depending on whether they cause the posi-tion 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 forthis event. Its value can be adRsnMoveFirst, adRsnMoveLast, adRsn-MoveNext, adRsnMovePrevious, adRsnMove, or adRsnRequery. Each suchenumeration value corresponds to the name of the method that causedthe event to fire (i.e., a value of adRsnMoveLast means that a call wasmade to MoveLast, resulting in a position change that fired the event).

adStatus

This parameter is an EventStatusEnum status value that is set toadStatusOK if the operation that caused the event was successful. It is setto adStatusCantDeny if this method cannot request cancellation of the

322 � Chapter Five—An ADO Primer

Page 342: Learn OLE DB Development With Visual C++ 6.0

pending operation. Before WillMove returns, an application can set thisparameter to adStatusCancel to request cancellation of the pendingoperation.

pRecordset

This parameter is a Recordset object for which this event occurred.

Where We Go From HereWhere We Go From Here

At this point, you’re up to speed on COM, ATL, MFC, MTS, and ADO. With thisalphabet soup under your belt, it is time to fire up Visual C++ and start doingsome real programming! The next three chapters take you through MTS devel-opment in ATL and MFC, for both components and clients. After that come twomore chapters that show you how to install and administer MTS componentsand clients, and how to deploy them on the Internet and WWW.

Chapter Five—An ADO Primer � 323

Page 343: Learn OLE DB Development With Visual C++ 6.0
Page 344: Learn OLE DB Development With Visual C++ 6.0

Chapter Six

Creating and Using Simple OLECreating and Using Simple OLE

DB Providers and Consumers withDB Providers and Consumers with

ATL and MFCATL 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 PCworld has ever seen. Now it is time to do some actual programming and putall this information into practice. This chapter will provide you with a start inusing the OLE DB templates for ATL and MFC, showing you how to create asimple (but really very powerful) OLE DB provider in ATL, and then a match-ing consumer in MFC. Chapter 7 will move on to a more powerful ATLprovider that implements a three-tier client/server architecture via COM.Chapter 8 demonstrates creating a sophisticated OLE DB consumer via ATLtemplates that manipulates the Microsoft Agent HCI system via an Accessdatabase. Chapter 9 shows the steps needed to use MFC with OLE DB toimplement a powerful OLE DB provider schema viewer with a graphical userinterface. Chapter 10 finishes up our book with a detailed examination ofusing COM over the Internet so that your OLE DB components can be safelydeployed and used inside the modern Active browser technologies.

OLE DB Template Changes are Required in ATLOLE DB Template Changes are Required in ATL

Unlike normal ATL programming, using the OLE DB templates requires a lotof rewriting of the wizard-generated code. What portions you changedepends on what you want your provider or consumer to do: All must changethe default implementations for their data mappings, and most will want toadd support for updating and bookmarks as well. Making these changes is anintricate job, and for this reason this book departs a bit from my usualformat.

325

Page 345: Learn OLE DB Development With Visual C++ 6.0

Normally, I use a two-step process in guiding readers through development ofprojects: a step-by-step section that details interactions with the user inter-face, and a line-by-line section that gives the code entries and an explanationof what they mean. This system is based on the assumption that little or noneof the wizard-generated code is changed. For OLE DB programming, thisapproach won’t work, so in this and the subsequent project chapters I use amodified system called Before and After and the Wizard’s Wand.

The Before section gives a listing of the wizard-generated code, and then pro-vides 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 explanationof what the code (both changed and unchanged) does.

The Wizard’s Wand section gives a listing of wizard-generated code anddetails what it does in the line-by-line format.

Creating an OLE DB Provider COM DLL with ATLCreating an OLE DB Provider COM DLL with ATL

One of the most common tasks performed in personal computing is readingdata from text files; with OLE DB, this chore can be promoted into the power-ful realm of database programming. The following sections take you throughthe user interface portions of creating an OLE DB provider project that readsa text file of e-mail addresses and URLs and provides them as an OLE DBrowset. It also supports bookmarks, including dynamic column information.As noted in the previous section, the project is presented in my well-receivedstep-by-step system, then shows you the code you need to write and an analy-sis using the Before and After and Wizard’s Wand techniques.

Warning You must have Visual C++ 6.0 to create this and the other projectsin the book; Visual C++ 5.0 does not support the OLE DB system in the sameway.

Step-By-Step

Start Visual C++ 6.0. From the File menu, select New. The New dialog boxopens; click on the ATL COM AppWizard entry. Next, select an appropriatedirectory, then enter a project name of URLProv. Figure 6-1 shows how theNew dialog should appear when you are done. Press OK to open the ATLCOM AppWizard.

326 � Chapter Six—Creating and Using Simple OLE DB Providers and Consumers

Page 346: Learn OLE DB Development With Visual C++ 6.0

In the ATL AppWizard, select the DLL project, and make sure that the SupportMFC, Merge Proxy/Stub Code, and Support MTS check boxes are uncheckedsince we don’t need those options; Figure 6-2 illustrates how the dialogshould appear when you are done. Press Finish.

Chapter Six—Creating and Using Simple OLE DB Providers and Consumers � 327

Figure 6-2

Setting theOLE DB COMserver projectproperties

Figure 6-1

The New dia-log box creat-ing theURLProv ATLproject

Page 347: Learn OLE DB Development With Visual C++ 6.0

A project creation confirmationdialog box appears as shown inFigure 6-3; press OK to createthe ATL project.

From the Insert menu, select New ATL Object. The ATL Object Wizard dialogappears, as shown in Figure 6-4. Select the Data Access entry in the left-hand

list box and the Provider entryin the right-hand list box. PressNext to move to the OLE DBsettings page of the wizard.

There is only one tab in the OLE DB Provider settings wizard dialog. Asshown in Figure 6-5, enter URLProvider as the name of the implementation

class; the other boxes will fillin automatically. Accept thedefault values and press OKto add the OLE DB ProviderComponent object to yourATL project. Save the projectto protect your work.

328 � Chapter Six—Creating and Using Simple OLE DB Providers and Consumers

Figure 6-5

Enteringname infor-mation in theATL ObjectWizard dialog

Figure 6-4

Selecting anOLE DB Pro-vider Objectin the ATLObjectWizard

Figure 6-3

The confirma-tion dialog

Page 348: Learn OLE DB Development With Visual C++ 6.0

Finally, before we start examining and adding code, we need to provide forthe addition of a new header file to the project to store a custom implementa-tion we are going to write. To do this, select Project|Add To Project|Newfrom 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 toadd the blank file to the project in the proper location. Save the project toprotect your work.

What the Wizard Wrought

Since this is the first time we’ve created an OLE DB project in ATL, let’s spendsome time reviewing what the wizard created for us before we begin chang-ing it. This will serve as a reference for later projects in the remainingchapters in case you’re not sure what a given bit of code does. The ATL OLEDB Object Wizard creates five source code files (plus some support files wewon’t go into) with automatically generated code to provide basic OLE DBprovider functionality.

Listing 6-1 gives the source code for the URLPROV.CPP file, which is the coreimplementation of the COM DLL itself.

1. // URLProv.cpp : Implementation of DLL Exports.2.3.4. // Note: Proxy/Stub Information5. // To build a separate proxy/stub DLL,6. // run nmake -f URLProvps.mk in the project directory.7.

Chapter Six—Creating and Using Simple OLE DB Providers and Consumers � 329

Figure 6-6

Adding a newC++ headerfile to theproject

Page 349: Learn OLE DB Development With Visual C++ 6.0

8. #include “stdafx.h”9. #include “resource.h”10. #include <initguid.h>11. #include “URLProv.h”12.13. #include “URLProv_i.c”14. #include “URLProviderSess.h”15. #include “URLProviderDS.h”16.17.18. CComModule _Module;19.20. BEGIN_OBJECT_MAP(ObjectMap)21. OBJECT_ENTRY(CLSID_URLProvider, CURLProviderSource)22. END_OBJECT_MAP()23.24. /////////////////////////////////////////////////////////////////////////////25. // DLL Entry Point26.27. extern “C”28. BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID/*lpReserved*/)29. {30. if (dwReason == DLL_PROCESS_ATTACH)31. {32. _Module.Init(ObjectMap, hInstance, &LIBID_URLPROVLib);33. DisableThreadLibraryCalls(hInstance);34. }35. else if (dwReason == DLL_PROCESS_DETACH)36. _Module.Term();37. return TRUE; // ok38. }39.40. /////////////////////////////////////////////////////////////////////////////41. // Used to determine whether the DLL can be unloaded by OLE42.43. STDAPI DllCanUnloadNow(void)44. {45. return (_Module.GetLockCount()==0) ? S_OK : S_FALSE;46. }47.48. /////////////////////////////////////////////////////////////////////////////49. // Returns a class factory to create an object of the requested type50.51. STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)52. {53. return _Module.GetClassObject(rclsid, riid, ppv);54. }

330 � Chapter Six—Creating and Using Simple OLE DB Providers and Consumers

Page 350: Learn OLE DB Development With Visual C++ 6.0

55.56. /////////////////////////////////////////////////////////////////////////////57. // DllRegisterServer - Adds entries to the system registry58.59. STDAPI DllRegisterServer(void)60. {61. // registers object, typelib and all interfaces in typelib62. return _Module.RegisterServer(TRUE);63. }64.65. /////////////////////////////////////////////////////////////////////////////66. // DllUnregisterServer - Removes entries from the system registry67.68. STDAPI DllUnregisterServer(void)69. {70. return _Module.UnregisterServer(TRUE);71. }72.73.

Listing 6-1 URLPROV.CPP source code before modifications

The list below gives the important code blocks of the listing and explainswhat they do; particular attention is paid to macros and template definitionsas appropriate:

� Lines 14-15 This code contains the #include directives for the headerfiles implementing the additional functionality of the provider. OLE DBdevelopers who add additional functionality in new files must be sure toinclude the header files they create here.

� Lines 20-22 This code contains the vital OBJECT_MAP macros. Thesemacros are essential for the COM aspects of the provider to work; theydetermine what COM interfaces the server will support. OLE DB develop-ers who add new interfaces (a common task for advanced providers) mustcarefully modify these macros to add additional OBJECT_ENTRYelements.

� Lines 24-38 This code is the entry point for the DLL application; it alsohandles initialization for the server as well as shutdown via calls to the_Module implementation object. Notice that it calls theDisableThreadLibraryCalls function; this is an optimization feature whichsupports a high level of thread usage by the DLL. In some cases, OLE DBdevelopers may need to add additional initialization code to this function.

� Lines 40-46 This code handles determining whether the DLL can beremoved from memory by COM because it has no outstanding interfacepointers (see Chapter 1 for an explanation of COM reference counting andmemory management), via a call to the _Module implementation object.

Chapter Six—Creating and Using Simple OLE DB Providers and Consumers � 331

Page 351: Learn OLE DB Development With Visual C++ 6.0

This is a COM requirement and won’t be changed by OLE DBdevelopment.

� Lines 48-54 This code handles creating an instance of the underlyingobject for the COM interface supported by the DLL, via the _Moduleimplementation object. This is a COM requirement and won’t be changedby OLE DB development.

� Lines 56-63 This code handles registering the server (adding its entriesto the Registry for use by COM) when requested, via the call to the _Mod-ule implementation object. This is a COM requirement and won’t bechanged by OLE DB development.

� Lines 65-73 This code handles unregistering the server (removing itsentries from the Registry) when requested via the call to the _Moduleimplementation object. This is a COM requirement and won’t be changedby OLE DB development.

Listing 6-2 gives the source code for the URLPROVIDERRS.CPP file, which isthe implementation of the rowset functionality of the provider. (Rememberfrom Chapter 4 that all OLE DB data is returned in the form of rowsets.) Notethat unlike the DataSource implementation, the rowset implementation has aseparate header file with its definitions.

1. // Implementation of the CURLProviderCommand2. #include “stdafx.h”3. #include “URLProv.h”4. #include “URLProviderRS.h”5. /////////////////////////////////////////////////////////////////////////////6. // CURLProviderCommand7. HRESULT CURLProviderCommand::Execute(IUnknown * pUnkOuter, REFIID riid,8. DBPARAMS * pParams, LONG * pcRowsAffected,9. IUnknown ** ppRowset)10. {11. CURLProviderRowset* pRowset;12. return CreateRowset(pUnkOuter, riid, pParams, pcRowsAffected,ppRowset,pRowset);13. }

Listing 6-2 URLPROVIDERRS.CPP source code before modifications

The list below gives the important code blocks of the listing and explainswhat they do; particular attention is paid to macros and template definitionsas appropriate:

� Lines 5-13 This code is the default implementation of theIRowset::Execute function. It calls an internal implementation whichreturns a default rowset defined in the header file described below. Noticethat it moves its data as parameters and returns an HRESULT return code(see Chapter 1 for COM HRESULT information). OLE DB developers who

332 � Chapter Six—Creating and Using Simple OLE DB Providers and Consumers

Page 352: Learn OLE DB Development With Visual C++ 6.0

want to change what rowsets are returned don’t modify this code butrather the implementation in the header file.

Listing 6-3 gives the source code for the URLPROVIDERDS.H file, which isthe definition of the data source functionality of the provider. (Rememberfrom Chapter 4 that the data source implementation is where all clients con-nect to the OLE DB provider.) Note that it does not have a CPP file and socontains both its definitions and implementations.

1. // URLProviderDS.h : Declaration of the CURLProviderSource2. #ifndef __CURLProviderSource_H_3. #define __CURLProviderSource_H_4. #include “resource.h” // main symbols5. #include “URLProviderRS.h”6. /////////////////////////////////////////////////////////////////////////////7. // CDataSource8. class ATL_NO_VTABLE CURLProviderSource :9. public CComObjectRootEx<CComSingleThreadModel>,10. public CComCoClass<CURLProviderSource, &CLSID_URLProvider>,11. public IDBCreateSessionImpl<CURLProviderSource, CURLProviderSession>,12. public IDBInitializeImpl<CURLProviderSource>,13. public IDBPropertiesImpl<CURLProviderSource>,14. public IPersistImpl<CURLProviderSource>,15. public IInternalConnectionImpl<CURLProviderSource>16. {17. public:18. HRESULT FinalConstruct()19. {20. return FInit();21. }22. DECLARE_REGISTRY_RESOURCEID(IDR_URLPROVIDER)23. BEGIN_PROPSET_MAP(CURLProviderSource)24. BEGIN_PROPERTY_SET(DBPROPSET_DATASOURCEINFO)25. PROPERTY_INFO_ENTRY(ACTIVESESSIONS)26. PROPERTY_INFO_ENTRY(DATASOURCEREADONLY)27. PROPERTY_INFO_ENTRY(BYREFACCESSORS)28. PROPERTY_INFO_ENTRY(OUTPUTPARAMETERAVAILABILITY)29. PROPERTY_INFO_ENTRY(PROVIDEROLE DBVER)30. PROPERTY_INFO_ENTRY(DSOTHREADMODEL)31. PROPERTY_INFO_ENTRY(SUPPORTEDTXNISOLEVELS)32. PROPERTY_INFO_ENTRY(USERNAME)33. END_PROPERTY_SET(DBPROPSET_DATASOURCEINFO)34. BEGIN_PROPERTY_SET(DBPROPSET_DBINIT)35. PROPERTY_INFO_ENTRY(AUTH_PASSWORD)36. PROPERTY_INFO_ENTRY(AUTH_PERSIST_SENSITIVE_AUTHINFO)37. PROPERTY_INFO_ENTRY(AUTH_USERID)38. PROPERTY_INFO_ENTRY(INIT_DATASOURCE)39. PROPERTY_INFO_ENTRY(INIT_HWND)

Chapter Six—Creating and Using Simple OLE DB Providers and Consumers � 333

Page 353: Learn OLE DB Development With Visual C++ 6.0

40. PROPERTY_INFO_ENTRY(INIT_LCID)41. PROPERTY_INFO_ENTRY(INIT_LOCATION)42. PROPERTY_INFO_ENTRY(INIT_MODE)43. PROPERTY_INFO_ENTRY(INIT_PROMPT)44. PROPERTY_INFO_ENTRY(INIT_PROVIDERSTRING)45. PROPERTY_INFO_ENTRY(INIT_TIMEOUT)46. END_PROPERTY_SET(DBPROPSET_DBINIT)47. CHAIN_PROPERTY_SET(CURLProviderCommand)48. END_PROPSET_MAP()49. BEGIN_COM_MAP(CURLProviderSource)50. COM_INTERFACE_ENTRY(IDBCreateSession)51. COM_INTERFACE_ENTRY(IDBInitialize)52. COM_INTERFACE_ENTRY(IDBProperties)53. COM_INTERFACE_ENTRY(IPersist)54. COM_INTERFACE_ENTRY(IInternalConnection)55. END_COM_MAP()56. public:57. };58. #endif //__CURLProviderSource_H_

Listing 6-3 URLPROVIDERDS.H source code before modifications

The list below gives the important code blocks of the listing and explainswhat they do; particular attention is paid to macros and template definitionsas appropriate:

� Lines 6-15 This code contains the template inheritance hierarchy forthe data source implementation. If an OLE DB developer wants to addfunctionality to the default implementation, he must alter these lines toinherit from replacement and/or additional template classes, either sup-plied with OLE DB or custom ones he created or obtained from third-partysources.

� Lines 23-48 This code contains the vital PROPSET_MAP, PROPERTY_SET, and PROPERTY_INFO_ENTRY macros for the provider’s data sourceimplementation. These macros define the OLE DB properties supported bythe provider’s DataSource object. While most of these are automaticallysupported by the templates, OLE DB developers may want to remove oradd property sets and individual entries via adding or removing the mac-ros in this section. (It is important to remember that each OLE DBimplementation object has its own set of supported OLE DB properties.)

� Lines 49-55 This code contains the other critical code for the COMinterfaces supported by the data source implementation via COM_MAPand COM_INTERFACE_ENTRY macros. If OLE DB developers want toenhance the data source functionality of the provider, they must be sure toadd appropriate interface entries to these macros.

334 � Chapter Six—Creating and Using Simple OLE DB Providers and Consumers

Page 354: Learn OLE DB Development With Visual C++ 6.0

Listing 6-4 gives the source code for the URLPROVIDERRS.H file, which is thedefinition file for the recordSet functionality of the provider. Note that it hasan associated CPP file and so does not contain implementation code.

1. // URLProviderRS.h : Declaration of the CURLProviderRowset2. #ifndef __CURLProviderRowset_H_3. #define __CURLProviderRowset_H_4. #include “resource.h” // main symbols5. class CURLProviderWindowsFile:6. public WIN32_FIND_DATA7. {8. public:9. BEGIN_PROVIDER_COLUMN_MAP(CURLProviderWindowsFile)10. PROVIDER_COLUMN_ENTRY(“FileAttributes”, 1, dwFileAttributes)11. PROVIDER_COLUMN_ENTRY(“FileSizeHigh”, 2, nFileSizeHigh)12. PROVIDER_COLUMN_ENTRY(“FileSizeLow”, 3, nFileSizeLow)13. PROVIDER_COLUMN_ENTRY(“FileName”, 4, cFileName)14. PROVIDER_COLUMN_ENTRY(“AltFileName”, 5, cAlternateFileName)15. END_PROVIDER_COLUMN_MAP()16. };17. // CURLProviderCommand18. class ATL_NO_VTABLE CURLProviderCommand :19. public CComObjectRootEx<CComSingleThreadModel>,20. public IAccessorImpl<CURLProviderCommand>,21. public ICommandTextImpl<CURLProviderCommand>,22. public ICommandPropertiesImpl<CURLProviderCommand>,23. public IObjectWithSiteImpl<CURLProviderCommand>,24. public IConvertTypeImpl<CURLProviderCommand>,25. public IColumnsInfoImpl<CURLProviderCommand>26. {27. public:28. BEGIN_COM_MAP(CURLProviderCommand)29. COM_INTERFACE_ENTRY(ICommand)30. COM_INTERFACE_ENTRY(IObjectWithSite)31. COM_INTERFACE_ENTRY(IAccessor)32. COM_INTERFACE_ENTRY(ICommandProperties)33. COM_INTERFACE_ENTRY2(ICommandText, ICommand)34. COM_INTERFACE_ENTRY(IColumnsInfo)35. COM_INTERFACE_ENTRY(IConvertType)36. END_COM_MAP()37. // ICommand38. public:39. HRESULT FinalConstruct()40. {41. HRESULT hr = CConvertHelper::FinalConstruct();42. if (FAILED (hr))43. return hr;44. hr = IAccessorImpl<CURLProviderCommand>::FinalConstruct();

Chapter Six—Creating and Using Simple OLE DB Providers and Consumers � 335

Page 355: Learn OLE DB Development With Visual C++ 6.0

45. if (FAILED(hr))46. return hr;47. return CUtlProps<CURLProviderCommand>::FInit();48. }49. void FinalRelease()50. {51. IAccessorImpl<CURLProviderCommand>::FinalRelease();52. }53. HRESULT WINAPI Execute(IUnknown * pUnkOuter, REFIID riid, DBPARAMS * pParams,54. LONG * pcRowsAffected, IUnknown ** ppRowset);55. static ATLCOLUMNINFO* GetColumnInfo(CURLProviderCommand* pv, ULONG* pcInfo)56. {57. return CURLProviderWindowsFile::GetColumnInfo(pv,pcInfo);58. }59. BEGIN_PROPSET_MAP(CURLProviderCommand)60. BEGIN_PROPERTY_SET(DBPROPSET_ROWSET)61. PROPERTY_INFO_ENTRY(IAccessor)62. PROPERTY_INFO_ENTRY(IColumnsInfo)63. PROPERTY_INFO_ENTRY(IConvertType)64. PROPERTY_INFO_ENTRY(IRowset)65. PROPERTY_INFO_ENTRY(IRowsetIdentity)66. PROPERTY_INFO_ENTRY(IRowsetInfo)67. PROPERTY_INFO_ENTRY(IRowsetLocate)68. PROPERTY_INFO_ENTRY(BOOKMARKS)69. PROPERTY_INFO_ENTRY(BOOKMARKSKIPPED)70. PROPERTY_INFO_ENTRY(BOOKMARKTYPE)71. PROPERTY_INFO_ENTRY(CANFETCHBACKWARDS)72. PROPERTY_INFO_ENTRY(CANHOLDROWS)73. PROPERTY_INFO_ENTRY(CANSCROLLBACKWARDS)74. PROPERTY_INFO_ENTRY(LITERALBOOKMARKS)75. PROPERTY_INFO_ENTRY(ORDEREDBOOKMARKS)76. END_PROPERTY_SET(DBPROPSET_ROWSET)77. END_PROPSET_MAP()78. };79. class CURLProviderRowset : public CRowsetImpl< CURLProviderRowset,80. CURLProviderWindowsFile, CURLProviderCommand>81. {82. public:83. HRESULT Execute(DBPARAMS * pParams, LONG* pcRowsAffected)84. {85. USES_CONVERSION;86. BOOL bFound = FALSE;87. HANDLE hFile;88. LPTSTR szDir =89. (m_strCommandText == _T(“”)) ? _T(“*.*”) : OLE2T(m_strCommandText);90. CURLProviderWindowsFile wf;91. hFile = FindFirstFile(szDir, &wf);

336 � Chapter Six—Creating and Using Simple OLE DB Providers and Consumers

Page 356: Learn OLE DB Development With Visual C++ 6.0

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 explainswhat they do; particular attention is paid to macros and template definitionsas appropriate:

� Lines 5-16 This code provides the definition of the data storage andretrieval component of the implementation, contained in theCURLProviderWindowsFile class. The PROVIDER_COLUMN_MAP andPROVIDER_COLUMN_ENTRY macros here must be changed by OLE DBdevelopers to accurately represent the structure of their underlying data.

� Lines 17-27 This code defines the template inheritance tree for theimplementation of the Command object for the provider. OLE DB develop-ers who want to change the functionality of the Command object mustadd or delete entries here.

� Lines 28-36 This code contains the COM_MAP and COM_INTERFACE_ENTRY macros that define which interfaces are supported for the Com-mand object in the provider. Developers who want to add or removesupported interfaces must add or delete entries here.

� Lines 37-58 This code provides default implementations for the sup-ported Command object functionality. OLE DB developers who want tooverride or enhance this functionality must replace this code or alter it asneeded.

� Lines 59-78 This code contains the OLE DB property macros for sup-ported properties of the Command object. OLE DB developers who wantto alter the properties of the Command object must add or delete appro-priate macros in this section.

Chapter Six—Creating and Using Simple OLE DB Providers and Consumers � 337

Page 357: Learn OLE DB Development With Visual C++ 6.0

� Lines 79-108 This code contains a default implementation of rowsetgeneration when the Execute method is called. It. provides a directory list-ing as a rowset, which matches the column macro entries earlier in thefile. OLE DB developers must override this code to provide their own Exe-cute implementation.

Listing 6-5 gives the source code for the URLPROVIDERSESS.H file, which isthe definition of the session functionality of the provider.

1. // Session.h : Declaration of the CURLProviderSession2. #ifndef __CURLProviderSession_H_3. #define __CURLProviderSession_H_4. #include “resource.h” // main symbols5. #include “URLProviderRS.h”6. class CURLProviderSessionTRSchemaRowset;7. class CURLProviderSessionColSchemaRowset;8. class CURLProviderSessionPTSchemaRowset;9. /////////////////////////////////////////////////////////////////////////////10. // CURLProviderSession11. class ATL_NO_VTABLE CURLProviderSession :12. public CComObjectRootEx<CComSingleThreadModel>,13. public IGetDataSourceImpl<CURLProviderSession>,14. public IOpenRowsetImpl<CURLProviderSession>,15. public ISessionPropertiesImpl<CURLProviderSession>,16. public IObjectWithSiteSessionImpl<CURLProviderSession>,17. public IDBSchemaRowsetImpl<CURLProviderSession>,18. public IDBCreateCommandImpl<CURLProviderSession, CURLProviderCommand>19. {20. public:21. CURLProviderSession()22. {23. }24. HRESULT FinalConstruct()25. {26. return FInit();27. }28. STDMETHOD(OpenRowset)(IUnknown *pUnk, DBID *pTID, DBID *pInID, REFIID riid,29. ULONG cSets, DBPROPSET rgSets[], IUnknown **ppRowset)30. {31. CURLProviderRowset* pRowset;32. return CreateRowset(pUnk, pTID, pInID, riid, cSets, rgSets, ppRowset,33. pRowset);34. }35. BEGIN_PROPSET_MAP(CURLProviderSession)36. BEGIN_PROPERTY_SET(DBPROPSET_SESSION)37. PROPERTY_INFO_ENTRY(SESS_AUTOCOMMITISOLEVELS)38. END_PROPERTY_SET(DBPROPSET_SESSION)

338 � Chapter Six—Creating and Using Simple OLE DB Providers and Consumers

Page 358: Learn OLE DB Development With Visual C++ 6.0

39. END_PROPSET_MAP()40. BEGIN_COM_MAP(CURLProviderSession)41. COM_INTERFACE_ENTRY(IGetDataSource)42. COM_INTERFACE_ENTRY(IOpenRowset)43. COM_INTERFACE_ENTRY(ISessionProperties)44. COM_INTERFACE_ENTRY(IObjectWithSite)45. COM_INTERFACE_ENTRY(IDBCreateCommand)46. COM_INTERFACE_ENTRY(IDBSchemaRowset)47. END_COM_MAP()48. BEGIN_SCHEMA_MAP(CURLProviderSession)49. SCHEMA_ENTRY(DBSCHEMA_TABLES, CURLProviderSessionTRSchemaRowset)50. SCHEMA_ENTRY(DBSCHEMA_COLUMNS, CURLProviderSessionColSchema-Rowset)51. SCHEMA_ENTRY(DBSCHEMA_PROVIDER_TYPES, CURLProviderSessionPT-SchemaRowset)52. END_SCHEMA_MAP()53. };54. class CURLProviderSessionTRSchemaRowset :55. public CRowsetImpl< CURLProviderSessionTRSchemaRowset, CTABLESRow,56. CURLProviderSession>57. {58. public:59. HRESULT Execute(LONG* pcRowsAffected, ULONG, const VARIANT*)60. {61. USES_CONVERSION;62. CURLProviderWindowsFile wf;63. CTABLESRow trData;64. lstrcpyW(trData.m_szType, OLESTR(“TABLE”));65. lstrcpyW(trData.m_szDesc, OLESTR(“The Directory Table”));66. HANDLE hFile = INVALID_HANDLE_VALUE;67. TCHAR szDir[MAX_PATH + 1];68. DWORD cbCurDir = GetCurrentDirectory(MAX_PATH, szDir);69. lstrcat(szDir, _T(“\\*.*”));70. hFile = FindFirstFile(szDir, &wf);71. if (hFile == INVALID_HANDLE_VALUE)72. return E_FAIL; // User doesn’t have a c:\ drive73. FindClose(hFile);74. lstrcpynW(trData.m_szTable, T2OLE(szDir),75. SIZEOF_MEMBER(CTABLESRow, m_szTable));76. if (!m_rgRowData.Add(trData))77. return E_OUTOFMEMORY;78. *pcRowsAffected = 1;79. return S_OK;80. }81. };82. class CURLProviderSessionColSchemaRowset :83. public CRowsetImpl< CURLProviderSessionColSchemaRowset, CCOLUMNSRow,84. CURLProviderSession>85. {

Chapter Six—Creating and Using Simple OLE DB Providers and Consumers � 339

Page 359: Learn OLE DB Development With Visual C++ 6.0

86. public:87. HRESULT Execute(LONG* pcRowsAffected, ULONG, const VARIANT*)88. {89. USES_CONVERSION;90. CURLProviderWindowsFile wf;91. HANDLE hFile = INVALID_HANDLE_VALUE;92. TCHAR szDir[MAX_PATH + 1];93. DWORD cbCurDir = GetCurrentDirectory(MAX_PATH, szDir);94. lstrcat(szDir, _T(“\\*.*”));95. hFile = FindFirstFile(szDir, &wf);96. if (hFile == INVALID_HANDLE_VALUE)97. return E_FAIL; // User doesn’t have a c:\ drive98. FindClose(hFile);// szDir has got the tablename99. DBID dbid;100. memset(&dbid, 0, sizeof(DBID));101. dbid.uName.pwszName = T2OLE(szDir);102. dbid.eKind = DBKIND_NAME;103. return InitFromRowset < _RowsetArrayType >104. (m_rgRowData, &dbid, NULL, m_spUnkSite, pcRowsAffected);105. }106. };107. class CURLProviderSessionPTSchemaRowset :108. public CRowsetImpl< CURLProviderSessionPTSchemaRowset, CPROVIDER_TYPERow,109. CURLProviderSession>110. {111. public:112. HRESULT Execute(LONG* pcRowsAffected, ULONG, const VARIANT*)113. {114. return S_OK;115. }116. };117. #endif //__CURLProviderSession_H_

Listing 6-5 URLPROVIDERSESS.H source code before modifications

The list below gives the important code blocks of the listing and explainswhat they do; particular attention is paid to macros and template definitionsas appropriate:

� Lines 9-18 This code defines the template inheritance tree for theimplementation of the Session object for the provider. OLE DB developerswho want to change the functionality of the Session object must add ordelete entries here.

� Lines 20-34 This code provides default implementations for the sup-ported Session object functionality. OLE DB developers who want tooverride or enhance this functionality must replace this code or alter it asneeded.

340 � Chapter Six—Creating and Using Simple OLE DB Providers and Consumers

Page 360: Learn OLE DB Development With Visual C++ 6.0

� Lines 35-39 This code contains the OLE DB property macros for sup-ported properties of the Session object. OLE DB developers who want toalter the properties of the Session object must add or delete appropriatemacros in this section.

� Lines 40-47 This code contains the COM_MAP and COM_INTERFACE_ENTRY macros that define which interfaces are supported forthe Session object in the provider. Developers who want to add or removesupported interfaces must add or delete entries here.

� Lines 48-52 This code contains the SCHEMA_MAP andSCHEMA_ENTRY macros that define which schema implementationobjects are supported for the Session object in the provider. Developerswho want to add or remove supported schemas must add or delete entrieshere.

� Lines 54-81 This code contains a default implementation of rowsetgeneration when the Execute method is called for a table schema. It pro-vides a directory listing as a rowset, which matches the column macroentries earlier in the file. OLE DB developers must override this code toprovide their own Execute implementation for table schemas if they wantto use them.

� Lines 82-106 This code contains a default implementation of rowsetgeneration when the Execute method is called for a column schema. Itprovides a directory listing as a rowset, which matches the column macroentries earlier in the file. OLE DB developers must override this code toprovide their own Execute implementation for column schemas if theywant to use them.

� Lines 107-116 This code contains a default implementation of rowsetgeneration when the Execute method is called for a provider-implementedschema. It provides no implementation, only a success return code. OLEDB developers must override this code to provide their own Executeimplementation for provider-implemented schemas if they want to usethem.

Gentlemen, Start Your Keyboards!

Now that you’ve got some familiarity with the wizard-generated code for abasic OLE DB provider, it is time to change it to meet the needs of our specificprovider. The sections below give you before and after code samples, fol-lowed 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 theseguidelines as a template for your own specific changes.

Chapter Six—Creating and Using Simple OLE DB Providers and Consumers � 341

Page 361: Learn OLE DB Development With Visual C++ 6.0

Returning a Custom Rowset

Listing 6-6 gives the source code from the URLPROVIDERRS.H file thatimplements the Execute functionality for rowsets.

1. class CURLProviderRowset : public CRowsetImpl< CURLProviderRowset,2. CURLProviderWindowsFile, CURLProviderCommand>3. {4. public:5. HRESULT Execute(DBPARAMS * pParams, LONG* pcRowsAffected)6. {7. USES_CONVERSION;8. BOOL bFound = FALSE;9. HANDLE hFile;10. LPTSTR szDir = (m_strCommandText == _T(“”)) ? _T(“*.*”) :11. OLE2T(m_strCommandText);12. CURLProviderWindowsFile wf;13. hFile = FindFirstFile(szDir, &wf);14. if (hFile == INVALID_HANDLE_VALUE)15. return DB_E_ERRORSINCOMMAND;16. LONG cFiles = 1;17. BOOL bMoreFiles = TRUE;18. while (bMoreFiles)19. {20. if (!m_rgRowData.Add(wf))21. return E_OUTOFMEMORY;22. bMoreFiles = FindNextFile(hFile, &wf);23. cFiles++;24. }25. FindClose(hFile);26. if (pcRowsAffected != NULL)27. *pcRowsAffected = cFiles;28. return S_OK;29. }30. };

Listing 6-6 URLPROVIDERRS.H source code before modifications

The directions below tell you what to remove and what to add to modify thiscode to meet our needs:

� Lines 7-25 Delete these lines. Replace them with the grayed code inListing 6-7.

Listing 6-7 gives the source code from the URLPROVIDERRS.H file thatimplements your changes.

342 � Chapter Six—Creating and Using Simple OLE DB Providers and Consumers

Page 362: Learn OLE DB Development With Visual C++ 6.0

1. class CURLProviderRowset : public CRowsetImpl< CURLProviderRowset,2. CURLProviderWindowsFile, CURLProviderCommand>3. {4. public:5. HRESULT Execute(DBPARAMS * pParams, LONG* pcRowsAffected)6. {7. //@@@ macros @@@8. USES_CONVERSION;9. //@@@ file handle @@@10. FILE* pFile;11. //@@@ string holder @@@12. TCHAR szString[256];13. //@@@ filename holder @@@14. TCHAR szFile[MAX_PATH];15. //@@@ length holder @@@16. size_t nLength;17. //@@@ if there is no command text @@@18. if (m_strCommandText == (BSTR)NULL)19. {20. //@@@ signal error @@@21. return E_FAIL;22. }23. //@@@ put the filename and path into the holder @@@24. _tcscpy(szFile, OLE2T(m_strCommandText));25. //@@@ if bad filename or can’t open file @@@26. if (szFile[0] == _T(‘\0’) || ((pFile = fopen(&szFile[0], “r”)) == NULL))27. {28. //@@@ signal error per OLE DB spec @@@29. return DB_E_NOTABLE;30. }31. //@@@ number of records holder @@@32. LONG cFiles = 0;33. //@@@ get first string of pair until run out @@@34. while (fgets(szString, 256, pFile) != NULL)35. {36. //@@@ get the length of the input string @@@37. nLength = strlen(szString);38. //@@@ make it null terminated @@@39. szString[nLength-1] = ‘\0’;40. //@@@ define record holder @@@41. CURLProviderWindowsFile ur;42. //@@@ put this into string @@@43. _tcscpy(ur.szCommand, szString );44. //@@@ put it into other string @@@45. _tcscpy(ur.szCommand2, szString );46. //@@@ get second string if present @@@47. if (fgets(szString,256,pFile) != NULL)

Chapter Six—Creating and Using Simple OLE DB Providers and Consumers � 343

Page 363: Learn OLE DB Development With Visual C++ 6.0

48. {49. //@@@ get length of input string @@@50. nLength = strlen(szString);51. //@@@ make it null terminated @@@52. szString[nLength-1] = ‘\0’;53. //@@@ put into other string holder @@@54. _tcscpy(ur.szText, szString );55. //@@@ and second string holder @@@56. _tcscpy(ur.szText2, szString );57. }58. //@@@ update the bookmark @@@59. ur.dwBookmark = ++cFiles;60. //@@@ try to put it into the recordset @@@61. if (!m_rgRowData.Add(ur))62. {63. //@@@ otherwise abort with error @@@64. return E_FAIL:65. }66. }67. //@@@ send back record count and return good HRESULT @@@68. if (pcRowsAffected !=NULL)69. *pcRowsAffected = cFiles:70. return S_OK: }71. };72. #endif //__CURLProviderRowset_H_

Listing 6-7 URLPROVIDERRS.H source code after modifications

The following entries explain what the new code lines do:

� Lines 7-26 This code uses the m_strCommandText member variable tofind out whether a filename has been supplied as the “command” to theprovider. If so, it puts it into a holder and opens the specified text file forreading.

� Lines 27-30 If the provider is not able to locate or open the file, ratherthan returning E_FAIL, the routine returns DB_E_NOTABLE, to maintaincompliance with the OLE DB spec.

� Lines 31-34 This code begins the process of reading in strings from theopened file; it is designed to loop until there are no more strings to beread.

� Lines 35-47 This code creates a local copy of a CURL-ProviderWindowsFile user record. (It will be defined in the next sectionfrom its OLE DB wizard default.) It then places the string just obtainedinto two different members of the user record.

344 � Chapter Six—Creating and Using Simple OLE DB Providers and Consumers

Page 364: Learn OLE DB Development With Visual C++ 6.0

� Lines 48-57 This code just repeats the process of getting a string andputting it into the already-created user record. Note that it does not breakthe loop if it fails; rather the loop ends with the next read attempt.

� 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 itsname).

� Lines 60-65 This code uses the member variable m_rgRowData (aCSimpleArray object) and its Add method to put the user record intointernal storage. Note that it returns E_FAIL if this bombs (due to memorylack, usually).

� Lines 68-69 This code sets the number of string pairs read into theimported parameter pcRowsAffected to maintain compliance with the OLEDB spec.

� Line 70 This code returns the HRESULT S_OK to indicate a valid rowsetwas obtained.

Defining a Custom User Record

Next, we need to alter the user record to contain our custom data fields andalso support for dynamic column returns to accommodate bookmarks. Listing6-8 gives the source code from the URLPROVIDERRS.H file that implementsthe CURLProviderWindowsFile user record.

1. #include “resource.h” // main symbols2. class CURLProviderWindowsFile:3. public WIN32_FIND_DATA4. {5. public:6. BEGIN_PROVIDER_COLUMN_MAP(CURLProviderWindowsFile)7. PROVIDER_COLUMN_ENTRY(“FileAttributes”, 1, dwFileAttributes)8. PROVIDER_COLUMN_ENTRY(“FileSizeHigh”, 2, nFileSizeHigh)9. PROVIDER_COLUMN_ENTRY(“FileSizeLow”, 3, nFileSizeLow)10. PROVIDER_COLUMN_ENTRY(“FileName”, 4, cFileName)11. PROVIDER_COLUMN_ENTRY(“AltFileName”, 5, cAlternateFileName)12. END_PROVIDER_COLUMN_MAP()13. };

Listing 6-8 URLPROVIDERRS.H source code before modifications

The directions below tell you what to remove and what to add to modify thiscode to meet our needs:

� Lines 1-2 Insert lines 2 through 10 of Listing 6-9 between lines 1 and 2here.

� Lines 3-13 Delete these lines and add lines 12 through 58 from Listing6-9 in place of them.

Chapter Six—Creating and Using Simple OLE DB Providers and Consumers � 345

Page 365: Learn OLE DB Development With Visual C++ 6.0

Listing 6-9 gives the source code from the URLPROVIDERRS.H file thatimplements the CURLProviderWindowsFile user record after your changes.

1. #include “resource.h” //main symbols2. #include <stdio.h>3. //@@@ ole chars library @@@4. #include <tchar.h>5. //@@@ bring in custom bookmark implementation @@@6. #include “urlrowloc.h”7. //@@@ forward definitions for compiler @@@8. class CURLProviderRowset;9. class CURLProviderCommand;10. //@@@ this was already here @@@11. class CURLProviderWindowsFile {12. public:13. //@@@ hold the bookmark @@@14. DWORD dwBookmark;15. //@@@ command string @@@16. TCHAR szCommand[256];17. //@@@ text string @@@18. TCHAR szText[256];19. //@@@ command string 2 @@@20. TCHAR szCommand2[256];21. //@@@ text 2 @@@22. TCHAR szText2[256];23. //@@@ override getcolumninfo for rowsets @@@24. static ATLCOLUMNINFO* GetColumnInfo(CURLProviderRowset* pThis, ULONG*pcCols);25. //@@@ override getcolumninfo for commands @@@26. static ATLCOLUMNINFO* GetColumnInfo(CURLProviderCommand* pThis, ULONG*pcCols);27.28. };29.30. //@@@ define a new macro to allow us to create a COLUMN entry on the fly @@@31. #define ADD_COLUMN_ENTRY(ulCols, name, ordinal, colSize, type, precision, scale,

guid, \32. dataClass, member) \33. _rgColumns[ulCols].pwszName = (LPOLESTR)name; \34. _rgColumns[ulCols].pTypeInfo = (ITypeInfo*)NULL; \35. _rgColumns[ulCols].iOrdinal = (ULONG)ordinal; \36. _rgColumns[ulCols].dwFlags = 0; \37. _rgColumns[ulCols].ulColumnSize = (ULONG)colSize; \38. _rgColumns[ulCols].wType = (DBTYPE)type; \39. _rgColumns[ulCols].bPrecision = (BYTE)precision; \40. _rgColumns[ulCols].bScale = (BYTE)scale; \41. _rgColumns[ulCols].cbOffset = offsetof(dataClass, member); \42. memset(&(_rgColumns[ulCols].columnid), 0, sizeof(DBID));\43. _rgColumns[ulCols].columnid.uName.pwszName =(LPOLESTR)name;44.

346 � Chapter Six—Creating and Using Simple OLE DB Providers and Consumers

Page 366: Learn OLE DB Development With Visual C++ 6.0

45. //@@@ define a new macro to let us create another COLUMN entry on the fly @@@46. #define ADD_COLUMN_ENTRY_EX(ulCols, name, ordinal, colSize, type, precision,

scale, \47. guid, dataClass, member, flags) \48. _rgColumns[ulCols].pwszName = (LPOLESTR)name; \49. _rgColumns[ulCols].pTypeInfo = (ITypeInfo*)NULL; \50. _rgColumns[ulCols].iOrdinal = (ULONG)ordinal; \51. _rgColumns[ulCols].dwFlags = flags; \52. _rgColumns[ulCols].ulColumnSize = (ULONG)colSize; \53. _rgColumns[ulCols].wType = (DBTYPE)type; \54. _rgColumns[ulCols].bPrecision = (BYTE)precision; \55. _rgColumns[ulCols].bScale = (BYTE)scale; \56. _rgColumns[ulCols].cbOffset = offsetof(dataClass, member); \57. memset(&(_rgColumns[ulCols].columnid), 0, sizeof(DBID)); \58. _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:

� Lines 2-9 This code brings in the standard io library, the ole charslibrary, and an IRowsetLocator header file we’ll create shortly. It also pro-vides essential forward definitions for our two custom classes that areused in the template (the compiler will not accept them without thesedefinitions!).

� Lines 13-21 This code defines the variables for the user record whichwill store data for returned rowsets. Notice that previously there weren’tany such definitions.

� Lines 23-26 This code overrides the GetColumnInfo functions for bothrowsets and commands to allow dynamic column information to bereturned when bookmarks are used.

� Lines 30-58 This code demonstrates a grungy aspect of OLE DB codingat present; these are reproductions of the basic macros used to define col-umn entries. By defining them here, they can be used later inside othercode.

Redefining the Rowset Implementation Template to SupportBookmarks

Next, we need to alter the code by inserting some template code for later useby our implementation of the IRowsetLocate interface. Listing 6-10 gives thesource code from the URLPROVIDERRS.H file where the insertion will bemade.

Chapter Six—Creating and Using Simple OLE DB Providers and Consumers � 347

Page 367: Learn OLE DB Development With Visual C++ 6.0

1. BEGIN_PROPSET_MAP(CURLProviderCommand)2. BEGIN_PROPERTY_SET(DBPROPSET_ROWSET)3. PROPERTY_INFO_ENTRY(IAccessor)4. PROPERTY_INFO_ENTRY(IColumnsInfo)5. PROPERTY_INFO_ENTRY(IConvertType)6. PROPERTY_INFO_ENTRY(IRowset)7. PROPERTY_INFO_ENTRY(IRowsetIdentity)8. PROPERTY_INFO_ENTRY(IRowsetInfo)9. PROPERTY_INFO_ENTRY(IRowsetLocate)10. PROPERTY_INFO_ENTRY(BOOKMARKS)11. PROPERTY_INFO_ENTRY(BOOKMARKSKIPPED)12. PROPERTY_INFO_ENTRY(BOOKMARKTYPE)13. PROPERTY_INFO_ENTRY(CANFETCHBACKWARDS)14. PROPERTY_INFO_ENTRY(CANHOLDROWS)15. PROPERTY_INFO_ENTRY(CANSCROLLBACKWARDS)16. PROPERTY_INFO_ENTRY(LITERALBOOKMARKS)17. PROPERTY_INFO_ENTRY(ORDEREDBOOKMARKS)18. END_PROPERTY_SET(DBPROPSET_ROWSET)19. END_PROPSET_MAP()20. };21. class CURLProviderRowset : public CRowsetImpl< CURLProviderRowset,22. CURLProviderWindowsFile, CURLProviderCommand>23. {

Listing 6-10 URLPROVIDERRS.H source code before modifications

The directions below tell you what to remove and what to add to modify thiscode to meet our needs:

� Lines 21-23 Replace these lines with the grayed code in lines 21-52 inListing 6-11.

Listing 6-11 gives the source code from the URLPROVIDERRS.H file thatimplements the template class after your changes.

1. BEGIN_PROPSET_MAP(CURLProviderCommand)2. BEGIN_PROPERTY_SET(DBPROPSET_ROWSET3. PROPERTY_INFO_ENTRY(IAccessor)4. PROPERTY_INFO_ENTRY(IColumnsInfo)5. PROPERTY_INFO_ENTRY(IConvertType)6. PROPERTY_INFO_ENTRY(IRowset)7. PROPERTY_INFO_ENTRY(IRowsetIdentity)8. PROPERTY_INFO_ENTRY(IRowsetInfo)9. PROPERTY_INFO_ENTRY(IRowsetLocate)10. PROPERTY_INFO_ENTRY(BOOKMARKS)11. PROPERTY_INFO_ENTRY(BOOKMARKSKIPPED)12. PROPERTY_INFO_ENTRY(BOOKMARKTYPE)13. PROPERTY_INFO_ENTRY(CANFETCHBACKWARDS)14. PROPERTY_INFO_ENTRY(CANHOLDROWS)

348 � Creating and Using Simple OLE DB Providers and Consumers

Page 368: Learn OLE DB Development With Visual C++ 6.0

15. PROPERTY_INFO_ENTRY(CANSCROLLBACKWARDS)16. PROPERTY_INFO_ENTRY(LITERALBOOKMARKS)17. PROPERTY_INFO_ENTRY(ORDEREDBOOKMARKS)18. END_PROPERTY_SET(DBPROPSET_ROWSET)19. END_PROPSET_MAP()20. };21. //@@@ define our new template and implementation @@@22. template <class T, class Storage, class CreatorClass, class ArrayType =23. CSimpleArray<Storage> >24. class CURLProviderRowsetImpl:25. public CRowsetImpl<T, Storage, CreatorClass, ArrayType, CSimpleRow,26. IRowsetLocateImpl< T > >27. {28. //@@@ COM MAP for our new class (note chaining) @@@29. BEGIN_COM_MAP(CURLProviderRowsetImpl)30. COM_INTERFACE_ENTRY(IRowsetLocate)31. COM_INTERFACE_ENTRY_CHAIN(_RowsetBaseClass)32. END_COM_MAP()33.34. //@@@ Implementation of the class method @@@35. HRESULT ValidateCommandID(DBID* pTableID, DBID* pIndexID)36. { //@@@ call ancestor method @@@37. HRESULT hr = _RowsetBaseClass::ValidateCommandID(pTableID,pIndexID);38. if (hr != S_OK)39. return hr;40. //@@@ if bad HR return @@@41. if (pIndexID != NULL)42. return DB_E_NOINDEX;43. //@@@ abort since we don’t do indexes @@@44. return S_OK;45. //@@@ otherwise OK @@@46. }47.48. };49. //@@@ note change in base template class @@@50. class CURLProviderRowset : public CURLProviderRowsetImpl< CURLProviderRowset,51. CURLProviderWindowsFile, CURLProviderCommand>52. {53. public:

Listing 6-11 URLPROVIDERRS.H source code after modifications

The following entries explain what the new code lines do:

� Lines 21-27 These lines provide the template definition and usage forthe CURLProviderRowsetImpl class which will replace the standardCRowsetImpl provided with the OLE DB templates. We do this so we can

Chapter Six—Creating and Using Simple OLE DB Providers and Consumers � 349

Page 369: Learn OLE DB Development With Visual C++ 6.0

implement bookmarks; note the support for the IRowsetLocateImpl tem-plate which we will write shortly.

� Lines 28-32 These lines provide the COM functionality for the inter-faces supported by our new implementation; note the use of the CHAINmacro to tie in to previous COM_MAP definitions to save rewriting them.

� Lines 34-46 This code makes sure that we got a valid command IDusing an ancestor class.

� Lines 49-52 This code replaces the previous implementation ofCURLProviderRowset with our new template class.

Implementing Bookmarks in the Custom Rowset Implementation

Next, we need to alter the implementation code for the CURLProviderRowsetclass to add support for bookmarks. Listing 6-12 gives the source code fromthe URLPROVIDERRS.H file that implements the class.

1. class CURLProviderRowset : public CRowsetImpl< CURLProviderRowset,2. CURLProviderWindowsFile, CURLProviderCommand>3. {4. public:5. HRESULT Execute(DBPARAMS * pParams, LONG* pcRowsAffected)6. {7. //@@@ macros @@@8. USES_CONVERSION;9. //@@@ file handle @@@10. FILE* pFile;11. //@@@ string holder @@@12. TCHAR sString(256);13. //@@@ filename holder @@@14. TCHAR szFile(MAX_PATH);15. //@@@ length holder @@@16. size_t nLength;17. //@@@ if there is no command text @@@18. if (!m_szCommandText)19. {20. //@@@ signal error @@@21. return E_FAIL;22. }23. //@@@ put the filename and path into the holder @@@24. _tcscpy(szFile, m_szCommandText);25. //@@@ if bad filename or can’t open file @@@26. if (szFile[0] == _T(‘\0’) || ((pFile = fopen(&szFile[0], “r”)) == NULL))27. {28. //@@@ signal error per OLE DB spec @@@29. return DB_E_NOTABLE;30. }31. //@@@ number of records holder @@@

350 � Chapter Six—Creating and Using Simple OLE DB Providers and Consumers

Page 370: Learn OLE DB Development With Visual C++ 6.0

32. LONG cFiles = 0;33. //@@@ get first string of pair until run out @@@34. while (fgets(szString, 256, pFile) != NULL)35. {36. //@@@ get the length of the input string @@@37. nLength = strlen(szString);38. //@@@ make it null terminated @@@39. szString[nLength-1] = ‘\0’;40. //@@@ define record holder @@@41. CURLProviderWindowsFile ur;42. //@@@ put this into string @@@43. _tcscpy(ur.szCommand, szString );44. //@@@ put it into other string @@@45. _tcscpy(ur.szCommand2, szString );46. //@@@ get second string if present @@@47. if (fgets(szString,256,pFile) != NULL)48. {49. //@@@ get length of input string @@@50. nLength = strlen(szString);51. //@@@ make it null terminated @@@52. szString[nLength-1] = ‘\0’;53. //@@@ put into other string holder @@@54. _tcscpy(ur.szText, szString );55. //@@@ and second string holder @@@56. _tcscpy(ur.szText2, szString );57. }58. //@@@ update the bookmark @@@59. ur.dwBookmark = ++cFiles;60. //@@@ try to put it into the recordset @@@61. if (!m_rgRowData.Add(ur))62. {63. //@@@ otherwise abort with error @@@64. return E_FAIL;65. }66. }67. //@@@ send back record count and return good HRESULT @@@68. if (pcRowsAffected != NULL)69. *pcRowsAffected = cFiles;70. return S_OK;71. }72. };73. #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 thiscode to meet our needs:

Chapter Six—Creating and Using Simple OLE DB Providers and Consumers � 351

Page 371: Learn OLE DB Development With Visual C++ 6.0

� Lines 4-5 Insert the grayed code in lines 5-11 of Listing 6-13 here.Don’t delete or change anything otherwise.

� Lines 71-72 Insert the grayed code in lines 79-113 of Listing 6-13here. Don’t delete or change anything otherwise.

Listing 6-13 gives the source code from the URLPROVIDERRS.H file thatimplements the CURLProviderRowset class after your changes.

1. class CURLProviderRowset : public CRowsetImpl< CURLProviderRowset,2. CURLProviderWindowsFile, CURLProviderCommand>3. {4. public:5.6. //@@@ We must implement this because we’re using bookmarks @@@7. virtual DBSTATUS GetDBStatus(CSimpleRow* , HACCESSOR)8. { //@@@ we don’t have a status for the database @@@9. return DBSTATUS_S_ISNULL;10. }11.12. HRESULT Execute(DBPARAMS * pParams, LONG* pcRowsAffected)13. {14. //@@@ macros @@@15. USES_CONVERSION;16. //@@@ file handle @@@17. FILE* pFile;18. //@@@ string holder @@@19. TCHAR sString(256);20. //@@@ filename holder @@@21. TCHAR szFile(MAX_PATH);22. //@@@ length holder @@@23. size_t nLength;24. //@@@ if there is no command text @@@25. if (!m_szCommandText)26. {27. //@@@ signal error @@@28. return E_FAIL;29. }30. //@@@ put the filename and path into the holder @@@31. _tcscpy(szFile, m_szCommandText);32. //@@@ if bad filename or can’t open file @@@33. if (szFile[0] == _T(‘\0’) || ((pFile = fopen(&szFile[0], “r”)) == NULL))34. {35. //@@@ signal error per OLE DB spec @@@36. return DB_E_NOTABLE;37. }38. //@@@ number of records holder @@@39. LONG cFiles = 0;40. //@@@ get first string of pair until run out @@@

352 � Chapter Six—Creating and Using Simple OLE DB Providers and Consumers

Page 372: Learn OLE DB Development With Visual C++ 6.0

41. while (fgets(szString, 256, pFile) != NULL)42. {43. //@@@ get the length of the input string @@@44. nLength = strlen(szString);45. //@@@ make it null terminated @@@46. szString[nLength-1] = ‘\0’;47. //@@@ define record holder @@@48. CURLProviderWindowsFile ur;49. //@@@ put this into string @@@50. _tcscpy(ur.szCommand, szString );51. //@@@ put it into other string @@@52. _tcscpy(ur.szCommand2, szString );53. //@@@ get second string if present @@@54. if (fgets(szString,256,pFile) != NULL)55. {56. //@@@ get length of input string @@@57. nLength = strlen(szString);58. //@@@ make it null terminated @@@59. szString[nLength-1] = ‘\0’;60. //@@@ put into other string holder @@@61. _tcscpy(ur.szText, szString );62. //@@@ and second string holder @@@63. _tcscpy(ur.szText2, szString );64. }65. //@@@ update the bookmark @@@66. ur.dwBookmark = ++cFiles;67. //@@@ try to put it into the recordset @@@68. if (!m_rgRowData.Add(ur))69. {70. //@@@ otherwise abort with error @@@71. return E_FAIL;72. }73. }74. //@@@ send back record count and return good HRESULT @@@75. if (pcRowsAffected != NULL)76. *pcRowsAffected = cFiles;77. return S_OK;78. }79.80. //@@@ we have to implement this because we do bookmarks @@@81. HRESULT OnPropertyChanged(ULONG iCurSet, DBPROP* pDBProp)82. { //@@@ abort if we have a null pointer @@@83. ATLASSERT( pDBProp != NULL );84. //@@@ get a working copy to play with @@@85. DWORD dwPropertyID = pDBProp->dwPropertyID;86. //@@@ if we’ve changed anything relating to bookmarks handle it @@@87. if (dwPropertyID == DBPROP_IRowsetLocate ||

Chapter Six—Creating and Using Simple OLE DB Providers and Consumers � 353

Page 373: Learn OLE DB Development With Visual C++ 6.0

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:

� Lines 6-10 This code implements the required function for databasestatus when bookmarks are supported. Since our text file doesn’t have astatus, we always return none.

� Lines 80-112 This code is required because since we are supportingbookmarks, we have to implement bookmark property changes by clients.The code essentially uses internal implementations to handle this, andonly accepts turning on bookmarks since this is a technologydemonstrator.

354 � Chapter Six—Creating and Using Simple OLE DB Providers and Consumers

Page 374: Learn OLE DB Development With Visual C++ 6.0

Implementing Bookmarks in the Custom IRowsetLocateImplementation

Next, we need to add code to the blank header file we created earlier. Bringup the URLROWLOC.H file in the editor and enter all the grayed code in List-ing 6-14 (since the file is blank you don’t need to worry about modifyinganything). Save the project to protect your work.

1. #include “stdafx.h”2.3. //@@@ define our template @@@4. template <class T>5. class ATL_NO_VTABLE IRowsetLocateImpl : public IRowsetImpl<T, IRowsetLocate>6. {7. public:8. //@@@ Implement a comparison of our bookmarks @@@9. STDMETHOD (Compare)(HCHAPTER hReserved, ULONG cbBookmark1,10. const BYTE * pBookmark1, ULONG cbBookmark2, const BYTE * pBookmark2,11. DBCOMPARE * pComparison)12. {13. //@@@ call our validate bookmark to be sure they’re real ones @@@14. HRESULT hr = ValidateBookmark(cbBookmark1, pBookmark1);15. //@@@ abort if not ok @@@16. if (hr != S_OK)17. return hr;18. //@@@ call our validate bookmark to be sure they’re real ones @@@19. hr = ValidateBookmark(cbBookmark2, pBookmark2);20. //@@@ abort if not ok @@@21. if (hr != S_OK)22. return hr;23. //@@@ Return based on simple number comparisons @@@24. if (*pBookmark1 == *pBookmark2)25. *pComparison = DBCOMPARE_EQ;26. if (*pBookmark1 < *pBookmark2)27. *pComparison = DBCOMPARE_LT;28. if (*pBookmark1 > *pBookmark2)29. *pComparison = DBCOMPARE_GT;30. //@@@ tell COM we’re cool @@@31. return S_OK;32. }33. //@@@ This is the biggie; it returns a Rowset based on the bookmark + offset @@@34. STDMETHOD (GetRowsAt)(HWATCHREGION hReserved1, HCHAPTER hReserved2,35. ULONG cbBookmark, const BYTE * pBookmark, LONG lRowsOffset,36. LONG cRows, ULONG * pcRowsObtained, HROW ** prghRows)37. {38. //@@@ we need this to be thread safe @@@39. T* pT = (T*)this;

Chapter Six—Creating and Using Simple OLE DB Providers and Consumers � 355

Page 375: Learn OLE DB Development With Visual C++ 6.0

40. //@@@ Check bookmark via our method @@@41. HRESULT hr = ValidateBookmark(cbBookmark, pBookmark);42. //@@@ abort if not good @@@43. if (hr != S_OK)44. return hr;45. //@@@ Make sure the other pointers are valid @@@46. if (pcRowsObtained == NULL || prghRows == NULL)47. return E_INVALIDARG;48. //@@@ enter critical section for free threading @@@49. pT->Lock();50. //@@@ if our offset is negative and we can’t scroll back then error out @@@51. if (lRowsOffset < 0 && !pT->m_bCanScrollBack)52. return DB_E_CANTSCROLLBACKWARDS;53. //@@@ save the current rowset in case we need it @@@54. LONG iRowsetTemp = pT->m_iRowset;55. //@@@ get our previous position @@@56. pT->m_iRowset = *pBookmark;57. //@@@ if we are at the beginning then make sure it’s 1 @@@58. if ((cbBookmark == 1) && (*pBookmark == DBBMK_FIRST))59. pT->m_iRowset = 1;60. //@@@ if we are at the end make sure it’s the last record number @@@61. if ((cbBookmark == 1) && (*pBookmark == DBBMK_LAST))62. pT->m_iRowset = pT->m_rgRowData.GetSize() + 1;63. //@@@ Set the start position to m_iRowset + lRowsOffset once normalized @@@64. pT->m_iRowset += lRowsOffset;65. //@@@ do fancy position calculation if greater or less than zero @@@66. if (lRowsOffset >= 0)67. (cRows >= 0) ? pT->m_iRowset -= 1 : pT->m_iRowset +=0;68. else69. (cRows >= 0) ? pT->m_iRowset -= 1 : pT->m_iRowset +=0;70. //@@@ If we’re too far off abort with OLE DB error message @@@71. if (pT->m_iRowset < 0 || pT->m_iRowset >(DWORD)pT->m_rgRowData.GetSize())72. {73. pT->m_iRowset = iRowsetTemp;74. return DB_E_BADSTARTPOSITION;75. }76. //@@@ Call IRowsetImpl::GetNextRows to actually get the rows @@@.77. hr = pT->GetNextRows(hReserved2, 0, cRows, pcRowsObtained, prghRows);78. //@@@ put them into the output variable @@@79. pT->m_iRowset = iRowsetTemp;80. //@@@ exit the critical section @@@81. pT->Unlock();82. //@@@ return the value from the actual data acquisition call @@@83. return hr;84. }85. //@@@ This is the other biggie; it gets a Rowset based on a bookmark @@@86. STDMETHOD (GetRowsByBookmark)(HCHAPTER hReserved, ULONG cRows,

356 � Chapter Six—Creating and Using Simple OLE DB Providers and Consumers

Page 376: Learn OLE DB Development With Visual C++ 6.0

87. const ULONG rgcbBookmarks[], const BYTE * rgpBookmarks[],88. HROW rghRows[], DBROWSTATUS rgRowStatus[])89. {90. //@@@ define our HRESULT holder @@@91. HRESULT hr = S_OK;92. //@@@ do this for thread safety @@@93. T* pT = (T*)this;94. //@@@ abort if any parameter is invalid @@@95. if (rgcbBookmarks == NULL || rgpBookmarks == NULL || rghRows == NULL)96. return E_INVALIDARG;97. //@@@ boundary case where no rows are requested @@@98. if (cRows == 0)99. return S_OK;100. //@@@ set up error holder @@@101. bool bErrors = false;102. //@@@ lock for free threading safety @@@103. pT->Lock();104. //@@@ get some rows to return @@@105. for (ULONG l=0; l<cRows; l++)106. {107. //@@@ call validation routine for each bookmark @@@108. hr = ValidateBookmark(rgcbBookmarks[l], rgpBookmarks[l]);109. //@@@ error if not good bookmark @@@110. if (hr != S_OK)111. {112. //@@@ set our error flag @@@113. bErrors = TRUE;114. //@@@ have we gotten some rows? @@@115. if (rgRowStatus != NULL)116. {117. //@@@ if so kill em @@@118. rgRowStatus[l] = DBROWSTATUS_E_INVALID;119. //@@@ onward to glory @@@120. continue;121. }122. }123. //@@@ define rows obtained counter @@@.124. ULONG ulRowsObtained = 0;125. //@@@ get the row via internal implementation @@@126. if (pT->CreateRow((long)*rgpBookmarks[l],127. ulRowsObtained, &rghRows[l]) != S_OK)128. {129. //@@@ blew it @@@130. bErrors = TRUE;131. }132. else133. {

Chapter Six—Creating and Using Simple OLE DB Providers and Consumers � 357

Page 377: Learn OLE DB Development With Visual C++ 6.0

134. //@@@ if we didn’t have internal failure then we’re cool @@@135. if (rgRowStatus != NULL)136. rgRowStatus[l] = DBROWSTATUS_S_OK;137. }138. }139. //@@@ exit critical section @@@140. pT->Unlock();141. //@@@ handle errors or return last call to CreateRow’s HRESULT @@@142. if (bErrors)143. return DB_S_ERRORSOCCURRED;144. else145. return hr;146. }147. //@@@ we have to implement this but don’t do anything @@@148. STDMETHOD (Hash)(HCHAPTER hReserved, ULONG cBookmarks,149. const ULONG rgcbBookmarks[], const BYTE * rgpBookmarks[],150. DWORD rgHashedValues[], DBROWSTATUS rgBookmarkStatus[])151. {152. return E_NOTIMPL; //@@@ you would implement this for fancy bookmarks @@@153. }154.155. //@@@ this is hidden @@@156. protected:157. //@@@ we need this to check for valid numbers @@@158. HRESULT ValidateBookmark(ULONG cbBookmark, const BYTE* pBookmark)159. {160. //@@@ use this to get size of our data @@@161. T* pT = (T*)this;162. //@@@ we don’t accept zero or NULL bookmarks @@@163. if (cbBookmark == 0 || pBookmark == NULL)164. return E_INVALIDARG;165. //@@@ All of our bookmarks are DWORDs, if they are anything other than @@@166. //@@@ sizeof(DWORD) then we have an invalid bookmark @@@167. if ((cbBookmark != sizeof(DWORD)) && (cbBookmark != 1))168. {169. //@@@ note OLE DB error code @@@170. return DB_E_BADBOOKMARK;171. }172. //@@@ If the contents of our bookmarks are less than 0 or greater than @@@173. //@@@ rowcount, then they are invalid @@@174. UINT nRows = pT->m_rgRowData.GetSize();175. if ((*pBookmark <= 0 || *pBookmark > nRows)176. && *pBookmark != DBBMK_FIRST && *pBookmark != DBBMK_LAST)177. {178. //@@@ note OLE DB error code @@@179. return DB_E_BADBOOKMARK;180. }

358 � Chapter Six—Creating and Using Simple OLE DB Providers and Consumers

Page 378: Learn OLE DB Development With Visual C++ 6.0

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:

� Lines 3-7 This code defines the template class and its implementationfor the IRowsetLocate interface.

� Lines 8-32 This code handles the comparison of two bookmarks todetermine which is smaller or larger, or if they are the same. Notice that itfirst does a validation check using a custom routine written later in thelisting. Also note the return codes are HRESULTS defined via OLE DB.

� Lines 39, 49, 81 These three lines are essential to most OLE DB imple-mentations; they define and perform “critical section” behavior, which iscode that cannot be interrupted by threading. This makes the code“thread safe” and therefore available for the most powerful type of COMthreading called free threading. You’ll see this code several more times inthe listing; it normally should be used in the portions of any implementa-tion that modifies the database or related pointers like bookmarks.

� Lines 45-47 This set of code is an example of OLE DB parameterchecking. Notice that it returns the E_INVALIDARG COM HRESULT errorcode rather than just E_FAIL.

� Lines 50-52 This code handles a situation where a negative offset isrequested but the provider cannot support it by moving backwards in thedata set; it returns an OLE DB HRESULT error code signaling this.

� Lines 57-62 This code is the first major bookmark manipulation seg-ment; notice that it uses the DBBMK_FIRST and DBBMK_LAST constants(which are not real bookmark values). These are always valid bookmarksas will be shown in the next routine. This segment converts the bookmarkinto a real number pointer into our list of strings.

� Lines 63-75 This is the position calculation segment of the bookmarkmanipulation code; it makes sure the bookmark plus offset still points to avalid record number, or returns an OLE DB HRESULT error code indicat-ing a bad bookmark position.

� Lines 76-79 Finally, this code gets the actual rows via a call to an inter-nal implementation method that will eventually call our own Executefunction. This returns the actual rowset to the user.

� Lines 85-146 The previous function took a single valid bookmark andreturned 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 asingle row for each of them, but without an offset. This requires validating

Chapter Six—Creating and Using Simple OLE DB Providers and Consumers � 359

Page 379: Learn OLE DB Development With Visual C++ 6.0

each bookmark (lines 107-122), then requesting one row at a time perbookmark (lines 123-140). Its complexity comes in working with arrays ofrows, thus requiring a separate status code for each one (lines 117-119and 134-137). Note the OLE DB HRESULT return code if an error happensalong the way.

� Lines 147-153 Some provider implementations may require complexbookmarks that are computationally expensive to process. In this case, atechnique called hashing (determining a general start position for thebookmark using precomputed values) is often used. This code provides anopportunity for implementation of bookmark hash calculations; our sim-ple integer bookmarks don’t need it and so we have no implementationcode.

� Lines 157-183 This code implements the needed bookmark validationfunction; 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 orzero (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 willnot give an error because they are explicitly checked for.

Implementing Variable COLUMNIFO in the Custom RowsetImplementation

Finally, we need to enter some additional code to the implementation of theRowset object. Listing 6-15 gives the source code from theURLPROVIDERRS.CPP file that implements the Rowset object.

1. // Implementation of the CURLProviderCommand2. #include “stdafx.h”3. #include “URLProv.h”4. #include “URLProviderRS.h”5. /////////////////////////////////////////////////////////////////////////////6. // CURLProviderCommand7. HRESULT CURLProviderCommand::Execute(IUnknown * pUnkOuter, REFIID riid,8. DBPARAMS * pParams,9. LONG * pcRowsAffected, IUnknown ** ppRowset)10. {11. CURLProviderRowset* pRowset;12. return CreateRowset(pUnkOuter, riid, pParams, pcRowsAffected, ppRowset,

pRowset);13. }14.

Listing 6-15 URLPROVIDERRS.CPP source code before modifications

360 � Chapter Six—Creating and Using Simple OLE DB Providers and Consumers

Page 380: Learn OLE DB Development With Visual C++ 6.0

The directions below tell you what to remove and what to add to modify thiscode to meet our needs:

� Line 14 Insert the code in lines 14-101 of Listing 6-16 at line 14. Don’tdelete or change anything else.

Listing 6-16 gives the source code from the URLPROVIDERRS.CPP file thatimplements the Rowset object after your changes.

1. // Implementation of the CURLProviderCommand2. #include “stdafx.h”3. #include “URLProv.h”4. #include “URLProviderRS.h”5. /////////////////////////////////////////////////////////////////////////////6. // CURLProviderCommand7. HRESULT CURLProviderCommand::Execute(IUnknown * pUnkOuter, REFIID riid,8. DBPARAMS * pParams,9. LONG * pcRowsAffected, IUnknown ** ppRowset)10. {11. CURLProviderRowset* pRowset;12. return CreateRowset(pUnkOuter, riid, pParams, pcRowsAffected, ppRowset,

pRowset);13. }14. //@@@ define our template @@@15. template <class TInterface>16. ATLCOLUMNINFO* CommonGetColInfo(IUnknown* pPropsUnk, ULONG*pcCols)17. {18. //@@@ define structure @@@19. static ATLCOLUMNINFO _rgColumns[5];20. //@@@ set working variable @@@21. ULONG ulCols = 0;22. //@@@ put the IUnknown pointer into a CComQIPtr @@@23. CComQIPtr<TInterface> spProps = pPropsUnk;24. //@@@ create a working set @@@25. CDBPropIDSet set(DBPROPSET_ROWSET);26. //@@@ put in bookmarks property @@@27. set.AddPropertyID(DBPROP_BOOKMARKS);28. //@@@ get a property set variable @@@29. DBPROPSET* pPropSet = NULL;30. //@@@ set another variable @@@31. ULONG ulPropSet = 0;32. //@@@ define HRESULT holder @@@33. HRESULT hr;34. //@@@ if we have a valid interface pointer get its properties @@@35. if (spProps)36. hr = spProps->GetProperties(1, &set, &ulPropSet, &pPropSet);37. //@@@ if we have properties @@@38. if (pPropSet)39. {

Chapter Six—Creating and Using Simple OLE DB Providers � 361

Page 381: Learn OLE DB Development With Visual C++ 6.0

40. //@@@ get the value of the properties @@@41. CComVariant var = pPropSet->rgProperties[0].vValue;42. //@@@ free memory @@@43. CoTaskMemFree(pPropSet->rgProperties);44. CoTaskMemFree(pPropSet);45. //@@@ if we got properties and our property is TRUE @@@46. if ((SUCCEEDED(hr) && (var.boolVal == VARIANT_TRUE)))47. {48. //@@@ put in bookmark property @@@49. ADD_COLUMN_ENTRY_EX(ulCols, OLESTR(“Bookmark”), 0, sizeof(DWORD),50. DBTYPE_BYTES,51. 0, 0, GUID_NULL, CURLProviderWindowsFile, dwBookmark,52. DBCOLUMNFLAGS_ISBOOKMARK)53. //@@@ increment total columns @@@54. ulCols++;55. }56.57. }58. //@@@ use our macro to put in the column info @@@59. ADD_COLUMN_ENTRY(ulCols, OLESTR(“Command”), 1, 256,DBTYPE_STR, 0xFF, 0xFF,60. GUID_NULL, CURLProviderWindowsFile, szCommand)61. //@@@ update the total columns @@@62. ulCols++;63. //@@@ use our macro to put in the column info @@@64. ADD_COLUMN_ENTRY(ulCols, OLESTR(“Text”), 2, 256,DBTYPE_STR,0xFF, 0xFF,65. GUID_NULL, CURLProviderWindowsFile, szText)66. //@@@ update the total columns @@@67. ulCols++;68. //@@@ use our macro to put in the column info @@@69. ADD_COLUMN_ENTRY(ulCols, OLESTR(“Command2"), 3, 256, DBTYPE_STR, 0xFF, 0xFF,70. GUID_NULL, CURLProviderWindowsFile, szCommand2)71. //@@@ update the total columns @@@72. ulCols++;73. //@@@ use our macro to put in the column info @@@74. ADD_COLUMN_ENTRY(ulCols, OLESTR(“Text2"), 4, 256, DBTYPE_STR, 0xFF, 0xFF,75. GUID_NULL, CURLProviderWindowsFile, szText2)76. //@@@ update the total columns @@@77. ulCols++;78. //@@@ return our columns @@@79. if (pcCols != NULL)80. *pcCols = ulCols;81. //@@@ send back the columnsinfo structure @@@82. return _rgColumns;83. }84.85.86. //@@@ implement command-based column info @@@

362 � Chapter Six—Creating and Using Simple OLE DB Providers and Consumers

Page 382: Learn OLE DB Development With Visual C++ 6.0

87. ATLCOLUMNINFO*CURLProviderWindowsFile::GetColumnInfo (CURLProviderCommand*88. pThis, ULONG* pcCols)89. {90. //@@@ use internal implementation @@@91. return CommonGetColInfo<ICommandProperties>(pThis->GetUnknown(), pcCols);92. }93.94. //@@@ implement rowset-based column info @@@95. ATLCOLUMNINFO* CURLProviderWindowsFile::GetColumnInfo(CURLProviderRowset*96. pThis, ULONG* pcCols)97. {98. //@@@ use internal implementation @@@99. return CommonGetColInfo<IRowsetInfo>(pThis->GetUnknown(), pcCols);100.101. }

Listing 6-16 URLPROVIDERRS.CPP source code after modifications

The following entries explain what the new code lines do:

� Lines 14-17 This code defines and implements the template for theCOLUMNINFO override class.

� Lines 18-19 This segment defines a static structure which will returneither four or five elements depending on whether we are getting book-mark-enabled COLUMNINFO (the purpose of defining this function).

� Lines 20-21 Here we define our holder for the number of columnsreturned. We start it at zero so we can increment it.

� Lines 22-23 This transfers the input IUnknown interface pointer into aCComQIPtr template class so we can manipulate it more easily.

� Lines 24-25 Here we create a working property set variable to deter-mine whether or not we support bookmarks.

� Lines 26-27 We add bookmark support to the working set here.

� Lines 28-29 This defines our other property set variable.

� Lines 30-31 We create a holder for the total properties returned to thesecond property set.

� Lines 32-33 Defines an HRESULT holder.

� Lines 34-36 Now, if we got a valid interface pointer, we determinewhat properties it actually supports via a call to its OLE DB templateimplementation for property support.

� Lines 37-44 This is where our sleight of hand begins. Remember thatwe are doing this to handle in one routine both COLUMNINFO with book-mark support and COLUMNINFO without bookmark support. Therefore,the only thing that can change is whether bookmarks are currently sup-ported. This code gets the boolean VARIANT value from the property set

Chapter Six—Creating and Using Simple OLE DB Providers and Consumers � 363

Page 383: Learn OLE DB Development With Visual C++ 6.0

returned from the implementation code, and then disposes of the remain-ing results because they are fixed.

� Lines 45-57 If we got a valid function return HRESULT and if the sup-port value is True, then call our previously defined (Listing 6-9, lines45-58) ADD_COLUMN_ENTRY_EX macro to put in the bookmark propertysupport at the first position and increment our counter.

� Lines 58-77 This code puts in the fixed columninfo data via theADD_COLUMN_ENTRY macros defined in Listing 6-9 above. Notice thatdue to the use of the counter, the entries are put in the right place regard-less of whether bookmarks are supported.

� Lines 79-82 If we got a valid pointer to hold the returned columnsnumber, we put our counter into it. Either way, we send back theCOLUMNSINFO structure.

� Lines 87-101 Here we use our common implementation to get the col-umns information for both Command and Rowset objects.

Disabling Schema Support

Finally, we need to alter the Session object implementation to remove schemasupport, since rewriting the code to support our schema is a lot of work andwill be covered in more detail in later chapters. Listing 6-17 gives the sourcecode from the URLPROVIDERSESS.H file that implements the Session object.

1. // Session.h : Declaration of the CURLProviderSession2. #ifndef __CURLProviderSession_H_3. #define __CURLProviderSession_H_4. #include “resource.h” // main symbols5. #include “URLProviderRS.h”6. class CURLProviderSessionTRSchemaRowset;7. class CURLProviderSessionColSchemaRowset;8. class CURLProviderSessionPTSchemaRowset;9. /////////////////////////////////////////////////////////////////////////////10. // CURLProviderSession11. class ATL_NO_VTABLE CURLProviderSession :12. public CComObjectRootEx<CComSingleThreadModel>,13. public IGetDataSourceImpl<CURLProviderSession>,14. public IOpenRowsetImpl<CURLProviderSession>,15. public ISessionPropertiesImpl<CURLProviderSession>,16. public IObjectWithSiteSessionImpl<CURLProviderSession>,17. public IDBSchemaRowsetImpl<CURLProviderSession>,18. public IDBCreateCommandImpl<CURLProviderSession, CURLProviderCommand>19. {20. public:21. CURLProviderSession()22. {23. }

364 � Chapter Six—Creating and Using Simple OLE DB Providers and Consumers

Page 384: Learn OLE DB Development With Visual C++ 6.0

24. HRESULT FinalConstruct()25. {26. return FInit();27. }28. STDMETHOD(OpenRowset)(IUnknown *pUnk, DBID *pTID, DBID *pInID, REFIID riid,29. ULONG cSets, DBPROPSET rgSets[],30. IUnknown **ppRowset)31. {32. CURLProviderRowset* pRowset;33. return CreateRowset(pUnk, pTID, pInID, riid, cSets, rgSets, ppRowset,34. pRowset);35. }36. BEGIN_PROPSET_MAP(CURLProviderSession)37. BEGIN_PROPERTY_SET(DBPROPSET_SESSION)38. PROPERTY_INFO_ENTRY(SESS_AUTOCOMMITISOLEVELS)39. END_PROPERTY_SET(DBPROPSET_SESSION)40. END_PROPSET_MAP()41. BEGIN_COM_MAP(CURLProviderSession)42. COM_INTERFACE_ENTRY(IGetDataSource)43. COM_INTERFACE_ENTRY(IOpenRowset)44. COM_INTERFACE_ENTRY(ISessionProperties)45. COM_INTERFACE_ENTRY(IObjectWithSite)46. COM_INTERFACE_ENTRY(IDBCreateCommand)47. COM_INTERFACE_ENTRY(IDBSchemaRowset)48. END_COM_MAP()49. BEGIN_SCHEMA_MAP(CURLProviderSession)50. SCHEMA_ENTRY(DBSCHEMA_TABLES, CURLProviderSessionTRSchemaRowset)51. SCHEMA_ENTRY(DBSCHEMA_COLUMNS, CURLProviderSessionColSchemaRowset)52. SCHEMA_ENTRY(DBSCHEMA_PROVIDER_TYPES, CURLProviderSessionPTSchemaRowset)53. END_SCHEMA_MAP()54. };55. class CURLProviderSessionTRSchemaRowset :56. public CRowsetImpl< CURLProviderSessionTRSchemaRowset, CTABLESRow,57. CURLProviderSession>58. {59. public:60. HRESULT Execute(LONG* pcRowsAffected, ULONG, const VARIANT*)61. {62. USES_CONVERSION;63. CURLProviderWindowsFile wf;64. CTABLESRow trData;65. lstrcpyW(trData.m_szType, OLESTR(“TABLE”));66. lstrcpyW(trData.m_szDesc, OLESTR(“The Directory Table”));67. HANDLE hFile = INVALID_HANDLE_VALUE;68. TCHAR szDir[MAX_PATH + 1];69. DWORD cbCurDir = GetCurrentDirectory(MAX_PATH, szDir);70. lstrcat(szDir, _T(“\\*.*”));

Chapter Six—Creating and Using Simple OLE DB Providers and Consumers � 365

Page 385: Learn OLE DB Development With Visual C++ 6.0

71. hFile = FindFirstFile(szDir, &wf);72. if (hFile == INVALID_HANDLE_VALUE)73. return E_FAIL; // User doesn’t have a c:\ drive74. FindClose(hFile);75. lstrcpynW(trData.m_szTable, T2OLE(szDir), SIZEOF_MEMBER(CTABLESRow,76. m_szTable));77. if (!m_rgRowData.Add(trData))78. return E_OUTOFMEMORY;79. *pcRowsAffected = 1;80. return S_OK;81. }82. };83. class CURLProviderSessionColSchemaRowset :84. public CRowsetImpl< CURLProviderSessionColSchemaRowset, CCOLUMNSRow,85. CURLProviderSession>86. {87. public:88. HRESULT Execute(LONG* pcRowsAffected, ULONG, const VARIANT*)89. {90. USES_CONVERSION;91. CURLProviderWindowsFile wf;92. HANDLE hFile = INVALID_HANDLE_VALUE;93. TCHAR szDir[MAX_PATH + 1];94. DWORD cbCurDir = GetCurrentDirectory(MAX_PATH, szDir);95. lstrcat(szDir, _T(“\\*.*”));96. hFile = FindFirstFile(szDir, &wf);97. if (hFile == INVALID_HANDLE_VALUE)98. return E_FAIL; // User doesn’t have a c:\ drive99. FindClose(hFile);// szDir has got the tablename100. DBID dbid;101. memset(&dbid, 0, sizeof(DBID));102. dbid.uName.pwszName = T2OLE(szDir);103. dbid.eKind = DBKIND_NAME;104. return InitFromRowset < _RowsetArrayType > (m_rgRowData, &dbid, NULL,105. m_spUnkSite, pcRowsAffected);106. }107. };108. class CURLProviderSessionPTSchemaRowset :109. public CRowsetImpl< CURLProviderSessionPTSchemaRowset, CPROVIDER_TYPERow,110. CURLProviderSession>111. {112. public:113. HRESULT Execute(LONG* pcRowsAffected, ULONG, const VARIANT*)114. {115. return S_OK;116. }117. };

366 � Chapter Six—Creating and Using Simple OLE DB Providers and Consumers

Page 386: Learn OLE DB Development With Visual C++ 6.0

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 codein Listing 6-18.

Listing 6-18 gives the source code from the URLPROVIDERSESS.H file thatimplements the Session object after your changes.

1. // Session.h : Declaration of the CURLProviderSession2. #ifndef __CURLProviderSession_H_3. #define __CURLProviderSession_H_4. #include “resource.h” // main symbols5. #include “URLProviderRS.h”6. /////////////////////////////////////////////////////////////////////////////7. // CURLProviderSession8. class ATL_NO_VTABLE CURLProviderSession :9. public CComObjectRootEx<CComSingleThreadModel>,10. public IGetDataSourceImpl<CURLProviderSession>,11. public IOpenRowsetImpl<CURLProviderSession>,12. public ISessionPropertiesImpl<CURLProviderSession>,13. public IObjectWithSiteSessionImpl<CURLProviderSession>,14. public IDBCreateCommandImpl<CURLProviderSession, CURLProviderCommand>15. {16. public:17. CURLProviderSession()18. {19. }20. HRESULT FinalConstruct()21. {22. return FInit();23. }24. STDMETHOD(OpenRowset)(IUnknown *pUnk, DBID *pTID, DBID*pInID, REFIID riid,25. ULONG cSets, DBPROPSET rgSets[],26. IUnknown **ppRowset)27. {28. CURLProviderRowset* pRowset;29. return CreateRowset(pUnk, pTID, pInID, riid, cSets, rgSets, ppRowset,30. pRowset);31. }32. BEGIN_PROPSET_MAP(CURLProviderSession)33. BEGIN_PROPERTY_SET(DBPROPSET_SESSION)34. PROPERTY_INFO_ENTRY(SESS_AUTOCOMMITISOLEVELS)35. END_PROPERTY_SET(DBPROPSET_SESSION)36. END_PROPSET_MAP()37. BEGIN_COM_MAP(CURLProviderSession)38. COM_INTERFACE_ENTRY(IGetDataSource)

Chapter Six—Creating and Using Simple OLE DB Providers and Consumers � 367

Page 387: Learn OLE DB Development With Visual C++ 6.0

39. COM_INTERFACE_ENTRY(IOpenRowset)40. COM_INTERFACE_ENTRY(ISessionProperties)41. COM_INTERFACE_ENTRY(IObjectWithSite)42. COM_INTERFACE_ENTRY(IDBCreateCommand)43. END_COM_MAP()44. };45. #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 schemasupport are gone, but everything else is the same. This is necessary becausethe column information this schema depends on has been changed to fit ourdatabase, and so won’t compile.

Building the OLE DB Provider DLL

Once you are satisfied you’ve entered the code from the above listings cor-rectly, build the OLE DB COM server from the Build menu. If you encounterany errors, please check the code in the listing to find your mistake (it hasbeen exhaustively debugged and is correct). Once you have compiled thecomponent successfully it is automatically registered on the local machine; ifyou 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 totest the provider.

Creating an OLE DB Consumer Application with MFCCreating an OLE DB Consumer Application with MFC

MFC does not directly support OLE DB (although it does have one MFC classthat does support basic recordset functionality). However, with Visual C++6.0 this isn’t much of a problem, since we can import ATL templates directlyfrom their header files and use them. The following application will allowyou to learn how to create a basic MFC OLE DB consumer and how to con-nect with an OLE DB provider and obtain information from it. While thisapplication is quite simple, it can easily be scaled up to do very sophisticatedthings!

Step-By-Step

Start Visual C++ 6.0. From the File menu, select New. The New dialog boxopens; click on the MFC AppWizard (exe) entry. Next, select an appropriatedirectory, then enter a project name of TestURLProv. Figure 6-7 shows howthe New dialog should appear when you are done. Click OK to open the MFCAppWizard.

368 � Chapter Six—Creating and Using Simple OLE DB Providers and Consumers

Page 388: Learn OLE DB Development With Visual C++ 6.0

In the first page of the MFC AppWizard dialog, make sure the Dialog basedradio button is selected, as indicated in Figure 6-8. Click on the Next buttonto move to the next page of the wizard.

On the second page of the MFC AppWizard, you have a number of optionsthat control the behavior of the application; leave everything at the defaultexcept to make sure that Automation support is checked. Figure 6-9 illus-trates how the dialog should appear when you are done. Click on the Nextbutton to move to the next page of the AppWizard.

Chapter Six—Creating and Using Simple OLE DB Providers and Consumers � 369

Figure 6-8

Setting theMFC applica-tion projectbasicproperties

Figure 6-7

The Newdialog boxcreating theTestURLProvMFC project

Page 389: Learn OLE DB Development With Visual C++ 6.0

On the third page of the MFC AppWizard, select the only available option forthe user interface (MFC Standard), and enable source file comments and useof MFC as a shared DLL (to save executable size). Figure 6-10 illustrates howthe dialog should appear when you are done. Click on the Next button tomove to the next page of the AppWizard.

On the fourth page of the MFC AppWizard, you see the results of the choicesyou made in the previous dialogs. Figure 6-11 illustrates how the dialogshould appear when you are done. Click on the Finish button to create theMFC application.

370 � Chapter Six—Creating and Using Simple OLE DB Providers and Consumers

Figure 6-10

Setting theMFC applica-tion projectuser interfaceproperties

Figure 6-9

Setting theMFC applica-tion projectCOMproperties

Page 390: Learn OLE DB Development With Visual C++ 6.0

A confirmation dialog box appears as shown in Figure 6-12; press OK to cre-ate the MFC project.

Back in the Visual C++ IDE, bring up the workspace viewer, and select theResourceView tab. Expand the Dialogs entry and double-click on its only ele-ment, which is the main dialog resource for the application. Remove thepre-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, acceptingthe default ID values generated by Visual C++. Save the project to protectyour work.

Chapter Six—Creating and Using Simple OLE DB Providers and Consumers � 371

Figure 6-12

The confirma-tion dialog

Figure 6-11

Confirmingthe MFCapplicationproject userinterfaceproperties

Page 391: Learn OLE DB Development With Visual C++ 6.0

Next, select the list box and right-click to bring up its context menu; selectthe Properties entry. Move to the Styles tab and uncheck the Sort check box,

as illustrated in Figure6-14. Click on thepushpin button tokeep the Propertiesdialog visible whileyou make the othertwo changes needed.

Next, select the buttoncontrol; its propertiesappear in the Prop-erties dialog. Move tothe General tab andenter a caption of GetOLE DB ProviderData, as illustrated inFigure 6-15.

372 � Chapter Six—Creating and Using Simple OLE DB Providers and Consumers

Figure 6-13

The main dia-log display ofthe MFCapplication inthe ResourceEditor

Figure 6-14

Setting the listbox Sort prop-erty to off inthe MFCResourceEditor

Figure 6-15

Setting thebutton cap-tion in theMFC ResourceEditor

Page 392: Learn OLE DB Development With Visual C++ 6.0

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 cap-tion of OLE DB Provider Test as illustrated in Figure 6-16. (This will

become the dialog boxtitle.) Click on thepushpin button againto free the Propertiesdialog, and save theproject to protect yourwork.

As the next-to-last user interface step, you need to set up a member variableto facilitate interaction with the list box control. Hold down the Ctrl key and

double-click on the list box control.The Add Member Variable dialogappears as shown in Figure 6-17.Enter m_ctlstrings as the name ofthe member variable and set its cat-egory to Control (which willautomatically set its type toCListBox), and click on the OK but-ton. This will connect the userinterface of the list box with anMFC control, which is much easierto interact with!

Finally, you need to create an event handler for dealing with user clicks onthe button control. To save time, simply hold down the Ctrl key and double-click on the button control. MFC will automatically create a default eventhandler for you and place you in the text editor ready to enter code. Save theproject at this point.

Line-By-Line

Now you are ready to enter the small amount of source code needed to con-nect 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 andenter or change any grayed line of source code in Listing 6-19. Save the pro-ject. The section after the code explains what these changes do.

Chapter Six—Creating and Using Simple OLE DB Providers and Consumers � 373

Figure 6-17

Adding amember vari-able controlfor the list boxin MFC

Figure 6-16

Setting thedialog boxtitle in theMFC ResourceEditor

Page 393: Learn OLE DB Development With Visual C++ 6.0

1. // TestURLProvDlg.h : header file2. //3.4. #if !defined(AFX_TESTURLPROVDLG_H__C04E85BB_F97F_11D2_B7EE_00E02916C424__

INCLUDED_)5. #define AFX_TESTURLPROVDLG_H__C04E85BB_F97F_11D2_B7EE_00E02916C424__INCLUDED_6.7. #if _MSC_VER > 10008. #pragma once9. #endif // _MSC_VER > 100010. //@@@ ESSENTIAL!!! Otherwise MFC will expand the macros incorrectly!!! @@@11. #include <atldbcli.h>12.13. //@@@ define our handler class @@@14. class CProvider15. {16. public:17. //@@@ put in the bookmark field @@@18. CBookmark<4> bookmark;19. //@@@ put in the four text fields @@@20. TCHAR szCommand[256];21. TCHAR szText[256];22. TCHAR szCommand2[256];23. TCHAR szText2[256];24. //@@@ put in the database map @@@25. BEGIN_COLUMN_MAP(CProvider)26. //@@@ this is a bookmark @@@27. BOOKMARK_ENTRY( bookmark );28. //@@@ these are string fields @@@29. COLUMN_ENTRY(1, szCommand );30. COLUMN_ENTRY(2, szText );31. COLUMN_ENTRY(3, szCommand2 );32. COLUMN_ENTRY(4, szText2 );33. END_COLUMN_MAP()34. };35.36. class CTestURLProvDlgAutoProxy;37.38. /////////////////////////////////////////////////////////////////////////////39. // CTestURLProvDlg dialog40.41. class CTestURLProvDlg : public CDialog42. {43. DECLARE_DYNAMIC(CTestURLProvDlg);44. friend class CTestURLProvDlgAutoProxy;45.46. // Construction

374 � Chapter Six—Creating and Using Simple OLE DB Providers and Consumers

Page 394: Learn OLE DB Development With Visual C++ 6.0

47. public:48. CTestURLProvDlg(CWnd* pParent = NULL);// standard constructor49. virtual ~CTestURLProvDlg();50.51. // Dialog Data52. //{{AFX_DATA(CTestURLProvDlg)53. enum { IDD = IDD_TESTURLPROV_DIALOG };54. CListBox m_ctlstrings;55. //}}AFX_DATA56.57. // ClassWizard generated virtual function overrides58. //{{AFX_VIRTUAL(CTestURLProvDlg)59. protected:60. virtual void DoDataExchange(CDataExchange* pDX);// DDX/DDV support61. //}}AFX_VIRTUAL62.63. // Implementation64. protected:65. CTestURLProvDlgAutoProxy* m_pAutoProxy;66. HICON m_hIcon;67.68. BOOL CanExit();69.70. // Generated message map functions71. //{{AFX_MSG(CTestURLProvDlg)72. virtual BOOL OnInitDialog();73. afx_msg void OnSysCommand(UINT nID, LPARAM lParam);74. afx_msg void OnPaint();75. afx_msg HCURSOR OnQueryDragIcon();76. afx_msg void OnClose();77. virtual void OnOK();78. virtual void OnCancel();79. afx_msg void OnButton1();80. //}}AFX_MSG81. DECLARE_MESSAGE_MAP()82. };83.84. //{{AFX_INSERT_LOCATION}}85. // Microsoft Visual C++ will insert additional declarations immediately86. // before the previous line.87.88. #endif89. // !defined(AFX_TESTURLPROVDLG_H__C04E85BB_F97F_11D2_B7EE_00E02916C424__

INCLUDED_)

Listing 6-19 TESTURLPROVDLG.H source code after modifications

Chapter Six—Creating and Using Simple OLE DB Providers and Consumers � 375

Page 395: Learn OLE DB Development With Visual C++ 6.0

� Lines 10-11 This code makes sure that the ATL OLE DB templates areimported from their header file. Without this include file, MFC will inter-pret the macros using its definitions, which won’t work!

� Lines 14-23 This code defines the custom class we need to send toother OLE DB templates to interact with our OLE DB provider; it is a“stub” definition of the provider’s user record functionality. The codedefines the actual variables that will hold the information from the OLEDB provider.

� Lines 24-34 These macros are used to actually create the databasefunctionality for our stub provider. Notice that they are somewhat differ-ent from the macros we used in the provider.

With the database record defined, you next need to actually connect to thedatabase and get information from it into the list box. You do this in theevent handler for the button control’s BN_CLICKED message. Bring up theTESTURLPROVDLG.CPP file in the text editor since this is where MFC put thecode for the event handler when it created it. Scan through the listing andenter or change any grayed line of source code in Listing 6-20. Save the pro-ject. The section after the code explains what these changes do.

1. // TestURLProvDlg.cpp : implementation file2. //3.4. #include “stdafx.h”5. #include “TestURLProv.h”6. #include “TestURLProvDlg.h”7. #include “DlgProxy.h”8. //@@@ put this in to be sure @@@9. #include <atldbcli.h>10.11. #ifdef _DEBUG12. #define new DEBUG_NEW13. #undef THIS_FILE14. static char THIS_FILE[] = __FILE__;15. #endif16.17. /////////////////////////////////////////////////////////////////////////////18. // CAboutDlg dialog used for App About19.20. class CAboutDlg : public CDialog21. {22. public:23. CAboutDlg();24.25. // Dialog Data26. //{{AFX_DATA(CAboutDlg)27. enum { IDD = IDD_ABOUTBOX };

376 � Chapter Six—Creating and Using Simple OLE DB Providers and Consumers

Page 396: Learn OLE DB Development With Visual C++ 6.0

28. //}}AFX_DATA29.30. // ClassWizard generated virtual function overrides31. //{{AFX_VIRTUAL(CAboutDlg)32. protected:33. virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support34. //}}AFX_VIRTUAL35.36. // Implementation37. protected:38. //{{AFX_MSG(CAboutDlg)39. //}}AFX_MSG40. DECLARE_MESSAGE_MAP()41. };42.43. CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)44. {45. //{{AFX_DATA_INIT(CAboutDlg)46. //}}AFX_DATA_INIT47. }48.49. void CAboutDlg::DoDataExchange(CDataExchange* pDX)50. {51. CDialog::DoDataExchange(pDX);52. //{{AFX_DATA_MAP(CAboutDlg)53. //}}AFX_DATA_MAP54. }55.56. BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)57. //{{AFX_MSG_MAP(CAboutDlg)58. // No message handlers59. //}}AFX_MSG_MAP60. END_MESSAGE_MAP()61.62. /////////////////////////////////////////////////////////////////////////////63. // CTestURLProvDlg dialog64.65. IMPLEMENT_DYNAMIC(CTestURLProvDlg, CDialog);66.67. CTestURLProvDlg::CTestURLProvDlg(CWnd* pParent /*=NULL*/)68. : CDialog(CTestURLProvDlg::IDD, pParent)69. {70. //{{AFX_DATA_INIT(CTestURLProvDlg)71. // NOTE: the ClassWizard will add member initialization here72. //}}AFX_DATA_INIT73. // Note that LoadIcon does not require a subsequent DestroyIcon in Win3274. m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);

Chapter Six—Creating and Using Simple OLE DB Providers and Consumers � 377

Page 397: Learn OLE DB Development With Visual C++ 6.0

75. m_pAutoProxy = NULL;76. }77.78. CTestURLProvDlg::~CTestURLProvDlg()79. {80. // If there is an automation proxy for this dialog, set81. // its back pointer to this dialog to NULL, so it knows82. // the dialog has been deleted.83. if (m_pAutoProxy != NULL)84. m_pAutoProxy->m_pDialog = NULL;85. }86.87. void CTestURLProvDlg::DoDataExchange(CDataExchange* pDX)88. {89. CDialog::DoDataExchange(pDX);90. //{{AFX_DATA_MAP(CTestURLProvDlg)91. DDX_Control(pDX, IDC_LIST1, m_ctlstrings);92. //}}AFX_DATA_MAP93. }94.95. BEGIN_MESSAGE_MAP(CTestURLProvDlg, CDialog)96. //{{AFX_MSG_MAP(CTestURLProvDlg)97. ON_WM_SYSCOMMAND()98. ON_WM_PAINT()99. ON_WM_QUERYDRAGICON()100. ON_WM_CLOSE()101. ON_BN_CLICKED(IDC_BUTTON1, OnButton1)102. //}}AFX_MSG_MAP103. END_MESSAGE_MAP()104.105. /////////////////////////////////////////////////////////////////////////////106. // CTestURLProvDlg message handlers107.108. BOOL CTestURLProvDlg::OnInitDialog()109. {110. CDialog::OnInitDialog();111.112. // Add “About...” menu item to system menu.113.114. // IDM_ABOUTBOX must be in the system command range.115. ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);116. ASSERT(IDM_ABOUTBOX < 0xF000);117.118. CMenu* pSysMenu = GetSystemMenu(FALSE);119. if (pSysMenu != NULL)120. {121. CString strAboutMenu;

378 � Chapter Six—Creating and Using Simple OLE DB Providers and Consumers

Page 398: Learn OLE DB Development With Visual C++ 6.0

122. strAboutMenu.LoadString(IDS_ABOUTBOX);123. if (!strAboutMenu.IsEmpty())124. {125. pSysMenu->AppendMenu(MF_SEPARATOR);126. pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);127. }128. }129.130. // Set the icon for this dialog. The framework does this automatically131. // when the application’s main window is not a dialog132. SetIcon(m_hIcon, TRUE); // Set big icon133. SetIcon(m_hIcon, FALSE); // Set small icon134.135. // TODO: Add extra initialization here136.137. return TRUE; // return TRUE unless you set the focus to a control138. }139.140. void CTestURLProvDlg::OnSysCommand(UINT nID, LPARAM lParam)141. {142. if ((nID & 0xFFF0) == IDM_ABOUTBOX)143. {144. CAboutDlg dlgAbout;145. dlgAbout.DoModal();146. }147. else148. {149. CDialog::OnSysCommand(nID, lParam);150. }151. }152.153. // If you add a minimize button to your dialog, you will need the code below154. // to draw the icon. For MFC applications using the document/view model,155. // this is automatically done for you by the framework.156.157. void CTestURLProvDlg::OnPaint()158. {159. if (IsIconic())160. {161. CPaintDC dc(this); // device context for painting162.163. SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);164.165. // Center icon in client rectangle166. int cxIcon = GetSystemMetrics(SM_CXICON);167. int cyIcon = GetSystemMetrics(SM_CYICON);168. CRect rect;

Chapter Six—Creating and Using Simple OLE DB Providers and Consumers � 379

Page 399: Learn OLE DB Development With Visual C++ 6.0

169. GetClientRect(&rect);170. int x = (rect.Width() - cxIcon + 1) / 2;171. int y = (rect.Height() - cyIcon + 1) / 2;172.173. // Draw the icon174. dc.DrawIcon(x, y, m_hIcon);175. }176. else177. {178. CDialog::OnPaint();179. }180. }181.182. // The system calls this to obtain the cursor to display while the user drags183. // the minimized window.184. HCURSOR CTestURLProvDlg::OnQueryDragIcon()185. {186. return (HCURSOR) m_hIcon;187. }188.189. // Automation servers should not exit when a user closes the UI190. // if a controller still holds on to one of its objects. These191. // message handlers make sure that if the proxy is still in use,192. // then the UI is hidden but the dialog remains around if it193. // is dismissed.194.195. void CTestURLProvDlg::OnClose()196. {197. if (CanExit())198. CDialog::OnClose();199. }200.201. void CTestURLProvDlg::OnOK()202. {203. if (CanExit())204. CDialog::OnOK();205. }206.207. void CTestURLProvDlg::OnCancel()208. {209. if (CanExit())210. CDialog::OnCancel();211. }212.213. BOOL CTestURLProvDlg::CanExit()214. {215. // If the proxy object is still around, then the automation

380 � Chapter Six—Creating and Using Simple OLE DB Providers and Consumers

Page 400: Learn OLE DB Development With Visual C++ 6.0

216. // controller is still holding on to this application. Leave217. // 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 here230. //@@@ put in template code to send a command-based string to the provider @@@231. CCommand<CAccessor<CProvider> > 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;

Chapter Six—Creating and Using Simple OLE DB Providers and Consumers � 381

Page 401: Learn OLE DB Development With Visual C++ 6.0

263. }264. //@@@ create a bookmark holder @@@265. CBookmark<4> tempBookmark;266. //@@@ create a position counter just to show off @@@267. ULONG ulCount=0;268. //@@@ loop through all the strings in the database @@@269. while (table.MoveNext() == S_OK)270. {271. //@@@ if we have just done second record make it bookmark @@@272. if (ulCount == 2 ) tempBookmark = table.bookmark;273. //@@@ put first string into list box @@@274. m_ctlstrings.AddString(table.szCommand);275. //@@@ put second string into list box @@@276. m_ctlstrings.AddString(table.szText);277. //@@@ increment our count @@@278. ulCount++;279. }280. //@@@ show off bookmark functionality @@@281. table.MoveToBookmark(tempBookmark);282. //@@@ get second entry again into list box @@@283. m_ctlstrings.AddString(table.szCommand);284. //@@@ ditto @@@285. m_ctlstrings.AddString(table.szText);286.287. }

Listing 6-20 TESTURLPROVDLG.CPP source code after modifications

� Lines 8-9 This makes sure that MFC will use the ATL macros for ourOLE DB templates.

� Lines 230-235 This segment defines a template-created Commandobject and a DataSource and Session object. As noted in Chapter 4, allinteraction with OLE DB providers must be via a DataSource object, whichis then used to create a Session object, which finally executes commandsor gets rowsets.

� Lines 236-243 This code gets our basic DataSource object. It does sousing the COM ProgID value for the OLE DB provider (which is in the RGSfile of that project if you forget it). We test for a COM success, and if itdoesn’t happen we signal the user with an informative error message sothey can fix the problem. All this does, however, is make contact with theCOM server that contains our OLE DB provider; we haven’t done anydatabase work yet!

� Lines 244-251 Next we have to get a Session object, and this codedoes that chore. Notice that it takes our source variable as a parameter (soit has the right object to connect with), and returns a COM HRESULT

382 � Chapter Six—Creating and Using Simple OLE DB Providers and Consumers

Page 402: Learn OLE DB Development With Visual C++ 6.0

Chapter Six—Creating and Using Simple OLE DB Providers and Consumers � 383

error code, which we can check and then return a user-friendly error mes-sage if needed.

� Lines 253-263 This is the most vital code, because it actually sends acommand to the OLE DB provider. Up to this point we were using“canned” OLE DB functionality from our ATL templates. This is where ourcode gets tested! First, an OLE DB property object is created that informsthe provider that we support bookmarks (lines 253-255; note that theIRowsetLocate interface is what turns on bookmarks). Then in line 257the big call is made: It sends a command string to the provider via thetable object’s Open method. This call contains as parameters the Sessionobject, a string with a path to a text file containing names and URL valuesthat we’ll create later and place on the root in C: drive, and our OLE DBProperty object that indicates bookmark support. This will be sent to thevarious routines we wrote or modified so that a rowset is returned with allthe 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 messagesent to the user.

� Lines 264-267 This segment simply defines a bookmark holder objectand a counter variable. We’ll use them together to illustrate a successfulbookmark implementation.

� Lines 268-279 This is the loop that gets all the records in our databaseand puts them into the list box. It is quite straightforward, using theMoveNext function of our Table object to increment along the returnedrowset. Pay a bit of attention to our arbitrary setting of the second recordfor the bookmark; we could have used any record value or one of theDBBMK_XXX constants. As each set of strings is acquired we put them intothe 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 ignoreotherwise.

� Lines 280-285 This code illustrates using a bookmark; it takes thearbitrary bookmark value we acquired in the earlier loop and obtains therecord 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 cor-rectly, build the OLE DB consumer test application from the Build menu. Ifyou encounter any errors, please check the code in the listing to find yourmistake (it has been exhaustively debugged and is correct). Now you areready to create the text file “database” and test the consumer and provider.

Page 403: Learn OLE DB Development With Visual C++ 6.0

Creating the Text File Database and Testing the OLE DBCreating the Text File Database and Testing the OLE DB

Provider with the MFC Consumer ApplicationProvider with the MFC Consumer Application

As the final part of this chapter’s work, you need to create a simple text filewith at least 20 strings in it. Although they could be anything, for this illus-tration I’ve used paired descriptions and URLs. Once you’ve created this“database” and saved it into the filename and directory you hard-coded intothe application, you can then run the application and test the OLE DBprovider you wrote earlier.

Warning Be sure you have an even number of strings in the file if you chooseto alter what is shown in the listing.

Step-By-Step

Open Notepad or any other text file editor you like (Visual C++, for exam-ple). Enter the text in Listing 6-21 and save it as URLS.TXT in the rootdirectory of your C: drive (or wherever you hard-coded it in Listing 6-20above).

1. Wordware Publishing2. http://www.wordware.com/3. Microsoft

4. http://www.microsoft.com/

5. COM and DCOM

6. http://www.microsoft.com/com/

7. OLE DB

8. http://www.microsoft.com/oledb/

9. MTS10. http://www.microsoft.com/com/mts/11. COM+12. http://www.microsoft.com/com/com+/13. Scripting14. http://www.microsoft.com/scripting/15. Internet Explorer16. http://www.microsoft.com/ie/17. Windows 200018. http://www.microsoft.com/w2k/19. Visual Studio20. http://www.microsoft.com/vstudio/

21. FrontPage Express22. http://www.microsoft.com/fpe/23. Office 200024. http://www.microsoft.com/msoffice/

384 � Chapter Six—Creating and Using Simple OLE DB Providers and Consumers

Page 404: Learn OLE DB Development With Visual C++ 6.0

25. MSDN26. http://msdn.microsoft.com/

Listing 6-21 The URLS.TXT database file

Now start the TESTURLPROV application from inside Visual C++ or from theExplorer. When the dialog appears, click on the button. After a moment (ifyou’ve successfully completed all the above steps), the list of URLs and

descriptions shouldappear in the list boxas shown in Figure6-18. If you get oneof the error messagesor the text is garbled,check over your codeand make sure youhave compiled andregistered the OLEDB provider COMserver. (As a lastresort, the applica-tion and provider areon the CD as VisualC++ 6.0 projects.)

Where We Go From HereWhere We Go From Here

You’ve completed the first OLE DB provider and consumer projects for ourbook now, and should have a good handle on the basics of OLE DB program-ming with both ATL templates and MFC classes. The next chapter ratchetsthings up a significant amount, providing directions for creating a much morepowerful ATL OLE DB provider that is designed to work in a three-tier client/server environment.

Chapter Six—Creating and Using Simple OLE DB Providers and Consumers � 385

Figure 6-18

The MFC OLEDB consumerapplicationdisplays thefile databasefrom the OLEDB provider

Page 405: Learn OLE DB Development With Visual C++ 6.0
Page 406: Learn OLE DB Development With Visual C++ 6.0

Chapter Seven

Creating OLE DB ServiceCreating OLE DB Service

Providers with ATLProviders with ATL

Now that you’ve implemented your first OLE DB simple provider, you’reready to move to a higher level in OLE DB: service providers. These OLE DBelements are both providers and consumers (although not always using OLEDB 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 Accessdatabases as dual COM servers, one of which as a consumer acquires a data-base record, then sends this information as COM properties to another COMserver which provides it as an OLE DB provider record. An MFC applicationthat accesses the OLE DB provider is also shown.

Service Providers Have the Best of Both WorldsService Providers Have the Best of Both Worlds

in OLE DBin OLE DB

Although the provider-consumer model is a good one and encompasses mostof the jobs OLE DB will be called upon to do, there are some that don’t fit. Anexample is the need to obtain results from a database without incurring theoverhead of having to write database access code. Say a company needs toallow its traveling representatives to find out their remaining travel allow-ance so they can determine whether an increase is needed. Creating softwareto connect with a database over the Internet is not trivial, particularly if thereis a need to maintain security. The standard OLE DB provider-consumermodel just doesn’t fit this situation.

A better approach would be to put a single COM server on the databasemachine which would contain the database code, and have the remote appli-cations contact it via DCOM. This requires the database server to contain OLEDB Consumer code (to contact Access) and the database consumer on the cli-ent machine to contain OLE DB provider code (to interact with the remoteapplications). This is the concept behind an OLE DB service provider.

387

Page 407: Learn OLE DB Development With Visual C++ 6.0

Creating an OLE DB Service Provider COM DLLCreating an OLE DB Service Provider COM DLL

Consumer with ATLConsumer with ATL

The first step in our three-tier application is to create the OLE DB serviceprovider using ATL. This COM DLL will contain code to obtain data from anAccess database, and encode it into a set of COM BSTR and long properties.

Note Before you perform this step, however, you need to copy theACCOUNTS. MDB file from the Ch07 folder of the companion CD to thedevelopment computer, and then create a user DSN for it in the ODBC 32-bitcontrol panel applet named CheckingAccount. (See Chapter 8 if you need helpwith Access and ODBC.)

Warning You must have Visual C++ 6.0. to create this and the other projectsin the book; Visual C++ 5.0 does not support the OLE DB system in the sameway.

Step-By-Step

Start Visual C++ 6.0. From the File menu, select New. The New dialog boxopens; click on ATL COM AppWizard. Next select an appropriate directory,then enter a project name of atl3c. Figure 7-1 shows how the New dialogshould appear when you are done. Click OK to open the ATL COMAppWizard.

388 � Chapter Seven—Creating OLE DB Service Providers with ATL

Figure 7-1

The New dia-log box creat-ing the atl3cATL project

Page 408: Learn OLE DB Development With Visual C++ 6.0

In the ATL COMAppWizard, select theDLL project, and makesure that the SupportMFC and MergeProxy/Stub Code checkboxes are checked, andthat the Support MTScheck box is uncheckedsince we don’t need thatoption. Figure 7-2 illus-trates how the dialogshould appear when youare done. Click Finish.

A confirmation dialog boxappears as shown in Figure7-3; press OK to create theATL project.

From the Insert menu, selectNew ATL Object. The ATLObject Wizard dialog appears,as shown in Figure 7-4. Selectthe Objects entry in theleft-hand list box, and theSimple Object entry in theright-hand list box. Press Nextto move to the Names tab ofthe settings page of thewizard.

Figure 7-4

Selecting Sim-ple Object inthe ATLObject Wizard

Chapter Seven—Creating OLE DB Service Providers with ATL � 389

Figure 7-3

The confirma-tion dialog

Figure 7-2

Setting theOLE DB COMserver projectproperties

Page 409: Learn OLE DB Development With Visual C++ 6.0

As shown in Figure 7-5, enter AccountInfo as the short name of the imple-mentation class; the other boxes will fill in automatically. Accept the defaultvalues and move to the Attributes tab. As shown in Figure 7-6, be sure thatAggregation is on and that Support ISupportErrorInfo is checked. Press OK toadd the simple Server object to your ATL project. Save the project to protectyour work.

Next, activate the ClassView pane of the workspace window, and expand thenodes until the IAccountInfo interface entry is seen. Right-click on it to bringup its context menu, and select Add Method. In the Add Method Wizard

dialog, set a method name ofOpenAccounts. Figure 7-7shows how the dialog shouldlook when you are done. Clickon OK to add this method tothe OLE DB service providerATL project.

390 � Chapter Seven—Creating OLE DB Service Providers with ATL

Figure 7-7

Setting theOpenAccountsmethod in theMethod Wiz-ard dialog

Figure 7-5 Entering name infor-mation in the ATL Object Wizard

Figure 7-6

Entering attrib-ute informationin the ATL ObjectWizard dialog

Page 410: Learn OLE DB Development With Visual C++ 6.0

Repeat the above process to addanother method, namedCloseAccounts. Figure 7-8shows how the Add Method Wiz-ard should look when you aredone. Click on OK to add thismethod, and save the project toprotect your work.

Next, right-click on theIAccountInfo interface inClassWizard to bring up its contextmenu, and select Add Property. Inthe Add Property Wizard dialog,set a property name ofAccountName, a property typeof BSTR, and uncheck the PutFunction to make the propertyread-only. Figure 7-9 shows howthe dialog should look when youare done. Click on OK to add thisproperty to the OLE DB ServiceProvider ATL project.

Repeat the above process to addanother property, namedBackupAccount, also of typeBSTR and read-only. Figure 7-10shows how the Add Property Wiz-ard should look when you aredone. Click on OK to add thisproperty, and save the project toprotect your work.

Chapter Seven—Creating OLE DB Service Providers with ATL � 391

Figure 7-9

Setting theAccountNameproperty inthe PropertyWizard dialog

Figure 7-10

Setting theBackup-Account prop-erty in theProperty Wiz-ard dialog

Figure 7-8

Setting theCloseAccountsmethod in theMethod Wiz-ard dialog

Page 411: Learn OLE DB Development With Visual C++ 6.0

Repeat the above process to add another property, named AccountNumber,of type long and read-only. Figure 7-11 shows how the Add Property Wizardshould look when you are done. Click on OK to add this property, and savethe project.

Repeat the above process to add another property, named AccountBalance,of type long and read-only. Figure 7-12 shows how the Add Property Wizardshould look when you are done. Click on OK to add this property, and savethe project.

392 � Chapter Seven—Creating OLE DB Service Providers with ATL

Figure 7-11

Setting theAccount-Number prop-erty in theProperty Wiz-ard dialog

Figure 7-12

Setting theAccount-Balance prop-erty in theProperty Wiz-ard dialog

Page 412: Learn OLE DB Development With Visual C++ 6.0

Repeat the above process to addanother property, namedLookupKey, of type long andread-write (i.e., both Get and Putmethods enabled). Figure 7-13shows how the Add Property Wiz-ard should look when you aredone. Click on OK to add thisproperty, and save the project toprotect your work.

Gentlemen, Start Your Keyboards!

Now that you have created one OLE DB project, you should be more easilyable to work with new ones. For this project, you’ll create a hand-coded OLEDB consumer using the listing below. In Chapter 8, you’ll learn how to do thisfrom the OLE DB templates. For now, create a new blank C++ header filenamed CHECKINGACCOUNT.H. Copy the text in Listing 7-1 into it and saveit.

1. // CheckingAccount.H : Declaration of the CCheckingAccount class2.3. #ifndef __CHECKINGACCOUNT_H_4. #define __CHECKINGACCOUNT_H_5.6. class CCheckingAccountAccessor7. {8. public:9. TCHAR m_Account[14];10. SHORT m_OwnerID;11. double m_Balance;12. TCHAR m_BackupAccount[14];13.14. BEGIN_COLUMN_MAP(CCheckingAccountAccessor)15. COLUMN_ENTRY(1, m_Account)16. COLUMN_ENTRY(2, m_OwnerID)17. COLUMN_ENTRY(3, m_Balance)18. COLUMN_ENTRY(4, m_BackupAccount)19. END_COLUMN_MAP()20.21. DEFINE_COMMAND(CCheckingAccountAccessor, _T(“ \

Chapter Seven—Creating OLE DB Service Providers with ATL � 393

Figure 7-13

Setting theLookup keyproperty inthe PropertyWizard dialog

Page 413: Learn OLE DB Development With Visual C++ 6.0

22. SELECT \23. Account, \24. Owner_ID, \25. Balance, \26. Backup_Account \27. FROM Checking_Account"))28.29. // You may wish to call this function if you wish to30. // initialize all the fields31. void ClearRecord()32. {33. memset(this, 0, sizeof(*this));34. }35. };36.37. class CCheckingAccount : public38. CCommand<CAccessor<CCheckingAccountAccessor> >39. {40. public:41. HRESULT Open()42. {43. HRESULT hr;44.45. hr = OpenDataSource();46. if (FAILED(hr))47. return hr;48.49. return OpenRowset();50. }51. HRESULT OpenDataSource()52. {53. HRESULT hr;54. CDataSource db;55. CDBPropSet dbinit(DBPROPSET_DBINIT);56.57. dbinit.AddProperty(58. DBPROP_AUTH_PERSIST_SENSITIVE_AUTHINFO, false);59. dbinit.AddProperty(DBPROP_AUTH_USERID, OLESTR(“Admin”));60. dbinit.AddProperty(DBPROP_INIT_DATASOURCE,61. OLESTR(“CheckingAccount”));62. dbinit.AddProperty(DBPROP_INIT_MODE, (long)1);63. dbinit.AddProperty(DBPROP_INIT_PROMPT, (short)4);64. dbinit.AddProperty(DBPROP_INIT_LCID, (long)1033);65. hr = db.Open(_T(“MSDASQL”), &dbinit);66. if (FAILED(hr))67. return hr;68.

394 � Chapter Seven—Creating OLE DB Service Providers with ATL

Page 414: Learn OLE DB Development With Visual C++ 6.0

69. return m_session.Open(db);70. }71. HRESULT OpenRowset()72. {73. return CCommand<CAccessor<74. CCheckingAccountAccessor> >::Open(m_session);75. }76. CSession m_session;77. };78.79. #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 informationfrom the Access database.

Lines 14-19 This code calls the OLE DB macros to create support code forthe database record fields.

Lines 21-27 This code defines the default command to get the entirerecordset in one pass.

Lines 37-76 This code defines the basic access methods for the class; itbasically creates and opens a data source, and then opens a default rowset onit with all records.

Next, we need to alter the header file for our server to create our OLE DBConsumer 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. // AccountInfo.h : Declaration of the CAccountInfo2.3. #ifndef __ACCOUNTINFO_H_4. #define __ACCOUNTINFO_H_5.6. #include “resource.h” // main symbols7. //@@@ bring in OLE DB Consumer Class @@@8. #include “CheckingAccount.H”9. //////////////////////////////////////////////////////////10. // CAccountInfo11. class ATL_NO_VTABLE CAccountInfo :12. public CComObjectRootEx<CComSingleThreadModel>,13. public CComCoClass<CAccountInfo, &CLSID_AccountInfo>,14. public ISupportErrorInfo,15. public IDispatchImpl<IAccountInfo,16. &IID_IAccountInfo, &LIBID_ATL3CLib>17. {

Chapter Seven—Creating OLE DB Service Providers with ATL � 395

Page 415: Learn OLE DB Development With Visual C++ 6.0

18. public:19. CAccountInfo()20. {21. }22.23. DECLARE_REGISTRY_RESOURCEID(IDR_ACCOUNTINFO)24.25. DECLARE_PROTECT_FINAL_CONSTRUCT()26.27. BEGIN_COM_MAP(CAccountInfo)28. COM_INTERFACE_ENTRY(IAccountInfo)29. COM_INTERFACE_ENTRY(IDispatch)30. COM_INTERFACE_ENTRY(ISupportErrorInfo)31. END_COM_MAP()32.33. // ISupportsErrorInfo34. STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid);35. //@@@ put in private data section for our member vars @@@36. private:37. //@@@ hold the OLE DB Consumer class @@@38. CCheckingAccount* m_pCheckingAccount;39. //@@@ hold the account name @@@40. CComBSTR m_ccbstrAccountName;41. //@@@ hold the backup account @@@42. CComBSTR m_ccbstrBackupAccount;43. //@@@ hold the account number @@@44. long m_lAccountNumber;45. //@@@ hold the account balance @@@46. long m_lAccountBalance;47. //@@@ hold the lookup key @@@48. long m_lLookupKey;49. // IAccountInfo50. public:51. STDMETHOD(get_LookupKey)(/*[out, retval]*/ long *pVal);52. STDMETHOD(put_LookupKey)(/*[in]*/ long newVal);53. STDMETHOD(get_AccountBalance)(/*[out, retval]*/ long *pVal);54. STDMETHOD(get_AccountNumber)(/*[out, retval]*/ long *pVal);55. STDMETHOD(get_BackupAccount)(/*[out, retval]*/ BSTR *pVal);56. STDMETHOD(get_AccountName)(/*[out, retval]*/ BSTR *pVal);57. STDMETHOD(CloseAccounts)();58. STDMETHOD(OpenAccounts)();59. };60.61. #endif //__ACCOUNTINFO_H_

Listing 7-2 ACCOUNTINFO.H source code after modifications

396 � Chapter Seven—Creating OLE DB Service Providers with ATL

Page 416: Learn OLE DB Development With Visual C++ 6.0

The following entries explain what the new code lines do:

� Lines 7-8 This code brings in the CHECKINGACCOUNT.H header file sowe can use its class.

� Lines 35-48 This code defines our member variables for the class tohold property data and the Consumer class.

Finally, we need to add in the implementations of the properties and meth-ods. Locate the ACCOUNTINFO.CPP file in the text editor. Enter the grayedlines in Listing 7-3 and save the file.

1. // AccountInfo.cpp : Implementation of CAccountInfo2. #include “stdafx.h”3. #include “Atl3c.h”4. #include “AccountInfo.h”5.6. ///////////////////////////////////////////////////////////7. // CAccountInfo8.9. STDMETHODIMP CAccountInfo::InterfaceSupportsErrorInfo(REFIID riid)10. {11. static const IID* arr[] =12. {13. &IID_IAccountInfo14. };15. for (int i=0; i < sizeof(arr) / sizeof(arr[0]); i++)16. {17. if (InlineIsEqualGUID(*arr[i],riid))18. return S_OK;19. }20. return S_FALSE;21. }22.23. STDMETHODIMP CAccountInfo::OpenAccounts()24. {25. AFX_MANAGE_STATE(AfxGetStaticModuleState())26.27. // TODO: Add your implementation code here28. //@@@ create OLE DB Consumer class @@@29. m_pCheckingAccount = new CCheckingAccount;30. //@@@ open it to get recordset @@@31. m_pCheckingAccount->Open();32. //@@@ look along for record with matching key @@@33. while (m_pCheckingAccount->m_OwnerID != m_lLookupKey)34. m_pCheckingAccount->MoveNext();35. //@@@ for this demo we always assume we find it @@@36. m_ccbstrAccountName = m_pCheckingAccount->m_Account;37. //@@@ get the data from the Access db into our member vars @@@

Chapter Seven—Creating OLE DB Service Providers with ATL � 397

Page 417: Learn OLE DB Development With Visual C++ 6.0

38. m_ccbstrBackupAccount = m_pCheckingAccount->m_BackupAccount;39. m_lAccountBalance = m_pCheckingAccount->m_Balance;40. m_lAccountNumber = m_pCheckingAccount->m_OwnerID;41. //@@@ say we’re good to go @@@42. return S_OK;43. }44.45. STDMETHODIMP CAccountInfo::CloseAccounts()46. {47. AFX_MANAGE_STATE(AfxGetStaticModuleState())48.49. // TODO: Add your implementation code here50. //@@@ close the accounts db @@@51. m_pCheckingAccount->Close();52. //@@@ remove the class from memory @@53. delete m_pCheckingAccount;54. //@@@ signal OK @@@55. return S_OK;56. }57.58. STDMETHODIMP CAccountInfo::get_AccountName(BSTR *pVal)59. {60. AFX_MANAGE_STATE(AfxGetStaticModuleState())61.62. // TODO: Add your implementation code here63. //@@@ return the member variable data @@@64. *pVal = m_ccbstrAccountName.Copy();65. return S_OK;66. }67.68. STDMETHODIMP CAccountInfo::get_BackupAccount(BSTR *pVal)69. {70. AFX_MANAGE_STATE(AfxGetStaticModuleState())71.72. // TODO: Add your implementation code here73. //@@@ return the member variable data @@@74. *pVal = m_ccbstrBackupAccount.Copy();75. return S_OK;76. }77.78. STDMETHODIMP CAccountInfo::get_AccountNumber(long *pVal)79. {80. AFX_MANAGE_STATE(AfxGetStaticModuleState())81.82. // TODO: Add your implementation code here83. //@@@ return the member variable data @@@84. *pVal = m_lAccountNumber;

398 � Chapter Seven—Creating OLE DB Service Providers with ATL

Page 418: Learn OLE DB Development With Visual C++ 6.0

85. return S_OK;86. }87.88. STDMETHODIMP CAccountInfo::get_AccountBalance(long *pVal)89. {90. AFX_MANAGE_STATE(AfxGetStaticModuleState())91.92. // TODO: Add your implementation code here93. //@@@ return the member variable data @@@94. *pVal = m_lAccountBalance;95. return S_OK;96. }97.98. STDMETHODIMP CAccountInfo::get_LookupKey(long *pVal)99. {100. AFX_MANAGE_STATE(AfxGetStaticModuleState())101.102. // TODO: Add your implementation code here103. //@@@ return the member variable data @@@104. *pVal = m_lLookupKey;105. return S_OK;106. }107.108. STDMETHODIMP CAccountInfo::put_LookupKey(long newVal)109. {110. AFX_MANAGE_STATE(AfxGetStaticModuleState())111.112. // TODO: Add your implementation code here113. //@@@ set the member variable data @@@114. m_lLookupKey = newVal;115. return S_OK;116. }

Listing 7-3 ACCOUNTINFO.CPP source code after modifications

The following entries explain what the new code lines do:

� 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 aCOM server, just a C++ class). Lines 30 and 31 open it and obtain a com-plete recordset (see CHECKINGACCOUNT.H for the implementation).Lines 32-34 cover searching for the input LookupKey property (note thatwe assume it is always set before this method is called) in the Accountsdatabase in the Owner_ID field; it is assumed that the match is alwaysmade (but a robust server would implement error handling here). Lines35-41 set all our COM properties to the values of that particular record.

Chapter Seven—Creating OLE DB Service Providers with ATL � 399

Page 419: Learn OLE DB Development With Visual C++ 6.0

� Lines 50-54 These lines provide for closing the OLE DB consumer classand makes sure the next call to the database will work properly.

� Lines 63-64 This code sets the AccountName property to the membervariable when called. It assumes the database has been opened and theproperty member variables set already.

� Lines 73-74 This code sets the BackupAccount property to the membervariable when called. It assumes the database has been opened and theproperty member variables set already.

� Lines 83-84 This code sets the AccountNumber property to the mem-ber variable when called. It assumes the database has been opened andthe property member variables set already.

� Lines 93-94 This code sets the AccountBalance property to the mem-ber variable when called. It assumes the database has been opened andthe property member variables set already.

� Lines 103-104 This code sets the LookupKey property to the membervariable when called.

� Lines 113-114 This code sets the LookupKey member variable whencalled. It is required prior to opening the database or inconsistent behav-ior will result.

Building the OLE DB Service Provider Consumer DLL

Once you are satisfied you’ve entered the code from the above listings cor-rectly, build the OLE DB COM server from the Build menu. If you encounterany errors, please check the code in the listing to find your mistake (it hasand destroying its instance in memory. This both prevents a memory leakbeen exhaustively debugged and is correct). Once you have compiled thecomponent successfully it is automatically registered on the local machine; ifyou 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 themiddle tier of the application.

Creating an OLE DB Service Provider COM DLL ProviderCreating an OLE DB Service Provider COM DLL Provider

with ATLwith ATL

At this point you may be thinking, “Wait a minute, he’s got it backwards!” Butno, OLE DB service providers don’t follow the normal rules. This particularone reverses the normal arrangement to simulate sending data over a net-work via DCOM so that the receiver can use a remote database. It will obtainthe data from the OLE DB consumer you just created and provide it in theform of a standard OLE DB provider record (each recordset will have onlyone record for this provider).

400 � Chapter Seven—Creating OLE DB Service Providers with ATL

Page 420: Learn OLE DB Development With Visual C++ 6.0

Warning You must have Visual C++ 6.0. to create this and the other projectsin the book; Visual C++ 5.0 does not support the OLE DB system in the sameway.

Gentlemen, Start Your Keyboards!

Rather than go through all the grunt work of re-creating another OLE DBprovider, let’s reuse the URLProv sample from Chapter 6. (Copy it from thecompanion 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. public:2. //@@@ hold the bookmark @@@3. DWORD dwBookmark;4. //@@@ command string @@@5. TCHAR szAccountName[256];6. //@@@ text string @@@7. TCHAR szBackupAccount[256];8. //@@@ command string 2 @@@9. TCHAR szAccountNumber[256];10. //@@@ text 2 @@@11. TCHAR szAccountBalance[256];12. //@@@ override getcolumninfo for rowsets @@@13. static ATLCOLUMNINFO* GetColumnInfo(CURLProviderRowset* pThis, ULONG* pcCols);14. //@@@ override getcolumninfo for commands @@@15. static ATLCOLUMNINFO* GetColumnInfo(CURLProviderCommand* pThis, ULONG* pcCols);16. //@@@ note change in base template class @@@17. class CURLProviderRowset : public18. CURLProviderRowsetImpl<CURLProviderRowset,19. CURLProviderWindowsFile, CURLProviderCommand>20. {21. public:22.23. //@@@ We must implement this because we’re using bookmarks @@@24. virtual DBSTATUS GetDBStatus(CSimpleRow* , HACCESSOR)25. { //@@@ we don’t have a status for the database @@@26. return DBSTATUS_S_ISNULL;27. }28.29. HRESULT Execute(DBPARAMS * pParams, LONG* pcRowsAffected)30. {31. //@@@ macros @@@32. USES_CONVERSION;33. //@@@ critical section @@@

Chapter Seven—Creating OLE DB Service Providers with ATL � 401

Page 421: Learn OLE DB Development With Visual C++ 6.0

34. ObjectLock lock(this);35. //@@@ if there is no command text @@@36. if (m_strCommandText == (BSTR)NULL)37. {38. //@@@ signal error @@@39. return E_FAIL;40. }41. //@@@ create our server using standard COM techniques @@@42. IAccountInfo* pAccountInfo = NULL;43. //@@@ get the IP via CCI @@@44. HRESULT hr = CoCreateInstance(CLSID_AccountInfo,45. NULL,CLSCTX_ALL, IID_IUnknown,46. (void**)&pAccountInfo);47. //@@@ if no good blow up @@@48. if (!SUCCEEDED(hr)) return E_FAIL;49. //@@@ now we need the command string as a long @@@50. long lholder = atol(OLE2T(m_strCommandText));51. //@@@ ESSENTIAL!!!! if we don’t do this first blows up!!!@@@52. pAccountInfo->put_LookupKey( lholder );53. //@@@ open the accounts database and get info via server @@@54. pAccountInfo->OpenAccounts();55. //@@@ define our string holder @@@56. TCHAR szString[256];57. //@@@ define our BSTR holder @@@58. CComBSTR ccbstrHolder;59. //@@@ our database record @@@60. CURLProviderWindowsFile ur;61. //@@@ get the BSTR from the server @@@62. pAccountInfo->get_AccountName( &ccbstrHolder.m_str );63. //@@@ put this into our record via macro @@@64. _tcscpy(ur.szAccountName, OLE2T( ccbstrHolder.Copy()));65. //@@@ release the pointer to avoid memory leak @@@66. ccbstrHolder.Empty();67. //@@@ get other string from server68. pAccountInfo->get_BackupAccount( &ccbstrHolder.m_str );69. //@@@ put this into record via macro @@@70. _tcscpy(ur.szBackupAccount, OLE2T( ccbstrHolder.Copy()));71. //@@@ get first number via long holder @@72. pAccountInfo->get_AccountNumber( &lholder );73. //@@@ copy it into our string via fn @@@74. wsprintf(szString,"%ld",lholder);75. //@@@ then copy into record @@@76. _tcscpy(ur.szAccountNumber, szString);77. //@@@ get other number @@@78. pAccountInfo->get_AccountBalance( &lholder );79. //@@@ put into our string @@@80. wsprintf(szString,"%ld",lholder);

402 � Chapter Seven—Creating OLE DB Service Providers with ATL

Page 422: Learn OLE DB Development With Visual C++ 6.0

81. //@@@ then put into database record @@@82. _tcscpy(ur.szAccountBalance, szString);83. //@@@ try to add our only record @@@84. if (!m_rgRowData.Add(ur))85. {86. //@@@ otherwise abort with error @@@87. return E_FAIL;88. }89. //@@@ we always have only one record @@@90. if (pcRowsAffected != NULL)91. *pcRowsAffected = 1;92. //@@@ do this to close the DB for next time @@@93. pAccountInfo->CloseAccounts();94. //@@@ release pointer for memory reclamation95. pAccountInfo->Release();96. //@@@ say we are good to go @@@97. return S_OK;98. }

Listing 7-4 URLPROVIDERRS.H source code after modifications

The following entries explain what the new code lines do:

� Lines 4-11 This code redefines the four data elements to match thosefrom the COM server and Accounts Access database.

� Lines 31-49 This code brings in the third-tier component; we’ll justinclude it and call the local server, but a real application would include itvia DCOM.

� Lines 50-54 This code sets up the data from the input string and callsthe third-tier server to open its database and set its properties.

� 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 COMserver. The intricacy of the code is due in large part to converting fromOLE 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 cor-rectly, build the OLE DB COM server from the Build menu. If you encounterany errors, please check the code in the listing to find your mistake (it hasbeen exhaustively debugged and is correct). Once you have compiled thecomponent successfully it is automatically registered on the local machine; ifyou 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 toptier of the application.

Chapter Seven—Creating OLE DB Service Providers with ATL � 403

Page 423: Learn OLE DB Development With Visual C++ 6.0

Creating an OLE DB Service Provider ConsumerCreating an OLE DB Service Provider Consumer

Application with MFCApplication 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 providerwe created in the previous step. This will allow us to get to the meat of thesystem without worrying about unnecessary complexity!

Step-By-Step

Start Visual C++ 6.0. From the File menu, select New. The New dialog boxopens; click on the MFC AppWizard (exe), select an appropriate directory,then enter a project name of mfc1e. Figure 7-14 shows how the New dialogshould appear when you are done.

In the first page of the MFC AppWizard dialog, make sure the Dialog basedradio button is selected, as indicated in Figure 7-15. Click on the Next buttonto move to the next page of the wizard.

404 � Chapter Seven—Creating OLE DB Service Providers with ATL

Figure 7-14

The New dia-log box creat-ing the mfc1MFC project

Page 424: Learn OLE DB Development With Visual C++ 6.0

On the second page of the MFC AppWizard, there are a number of optionsthat control the behavior of the application; leave everything at the defaultexcept to make sure that Automation support is checked. Figure 7-16 illus-trates how the dialog should appear when you are done. Click on the Nextbutton to move to the next page.

On the third page of the MFC AppWizard, select the only available option forthe user interface (MFC Standard), and enable source file comments and useof MFC as a shared DLL (to save executable size). Figure 7-17 illustrates howthe dialog should appear when you are done. Click on the Next button tomove to the next page of the AppWizard.

Chapter Seven—Creating OLE DB Service Providers with ATL � 405

Figure 7-16

Setting theMFC applica-tion projectCOMproperties

Figure 7-15

Setting theMFC applica-tion projectbasicproperties

Page 425: Learn OLE DB Development With Visual C++ 6.0

On the fourth page of the MFC AppWizard, you see the results of the choicesyou made in the previous dialogs. Figure 7-18 illustrates how the dialogshould appear when you are done. Click on the Finish button to create theMFC application.

A confirmation dialog box appears as shown in Figure 7-19; press OK to cre-ate the MFC project.

406 � Chapter Seven—Creating OLE DB Service Providers with ATL

Figure 7-18

Confirmingthe MFCapplicationproject userinterfaceproperties

Figure 7-17

Setting theMFC applica-tion projectuser interfaceproperties

Page 426: Learn OLE DB Development With Visual C++ 6.0

Back in the Visual C++ IDE, bring up the workspace viewer, and select theResourceView tab. Expand the Dialogs entry and double-click on its only ele-ment, which is the main dialog resource for the application. Remove thepre-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 thefigure, accepting the default ID values generated by Visual C++. Save theproject to protect your work.

Chapter Seven—Creating OLE DB Service Providers with ATL � 407

Figure 7-20

The maindialog displayof the MFCapplication inthe ResourceEditor

Figure 7-19

The confirma-tion dialog

Page 427: Learn OLE DB Development With Visual C++ 6.0

As the next user interface step you need to set up a member variable to facili-tate interaction with the top edit control. Hold down the Ctrl key and

double-click on the edit controllabeled Lookup Key. The Add Mem-ber Variable Wizard dialog appearsas shown in Figure 7-21. Enterm_LookupKey as the name of themember variable, set its category toValue and its type to long, and clickon the OK button. This will connectthe user interface of the edit controlwith a simple member variable,which is much easier to interactwith.

As the next user interface step you need to set up a member variable to facili-tate interaction with the next edit control. Hold down the Ctrl key and

double-click on the edit controllabeled Account Name. The AddMember Variable Wizard dialogappears as shown in Figure 7-22.Enter m_AccountName as thename of the member variable, set itscategory to Value and its type toCString, and click on the OK button.This will connect the user interfaceof the edit control with a simplemember variable, which is mucheasier to interact with.

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 AccountBalance. The Add Member Variable Wizard dialog appears. Enterm_AccountBalance as the name of the member variable, set its category toValue and its type to long, and click on the OK button.

Set up a member variable to facilitate interaction with the next edit controlby holding down the Ctrl key and double-clicking on the edit control labeledAcount Number. The Add Member Variable Wizard dialog appears. Enterm_AccountNumber as the name of the member variable, set its categoryto Value and its type to long, and click on the OK button.

Set up the final member variable to facilitate interaction with the bottom editcontrol by holding down the Ctrl key and double-clicking on the edit controllabeled Backup Account. The Add Member Variable Wizard dialog appears.

408 � Chapter Seven—Creating OLE DB Service Providers with ATL

Figure 7-22

Adding amember vari-able controlfor an editcontrol inMFC

Figure 7-21

Adding amember vari-able controlfor an editcontrol inMFC

Page 428: Learn OLE DB Development With Visual C++ 6.0

Enter m_BackupAccount as the name of the member variable, set its cate-gory 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 onthe button control. To save time, simply hold down the Ctrl key and dou-ble-click on the button control. MFC will automatically create a default eventhandler for you and place you in the text editor ready to enter code. Save theproject at this point.

Line-By-Line

Now you are ready to enter the small amount of source code needed to con-nect 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 orchange any grayed line of source code in Listing 7-5. Save the project. Thesection after the code explains what these changes do.

1. // mfc1eDlg.h : header file2. //3.4. #if !defined(5. AFX_MFC1EDLG_H__60517009_3F5B_11D3_96F0_DE8523A96746__INCLUDED_)6. #define7. AFX_MFC1EDLG_H__60517009_3F5B_11D3_96F0_DE8523A96746__INCLUDED_8.9. #if _MSC_VER > 100010. #pragma once11. #endif // _MSC_VER > 100012. //@@@ ESSENTIAL!!! MFC will expand the macros incorrectly!!! @@@13. #include <atldbcli.h>14. //@@@ define our handler class @@@15. class CProvider16. {17. public:18. //@@@ put in the bookmark field @@@19. CBookmark<4> bookmark;20. //@@@ put in the four text fields @@@21. TCHAR szAccountName[256];22. TCHAR szBackupAccount[256];23. TCHAR szAccountNumber[256];24. TCHAR szAccountBalance[256];25. //@@@ put in the database map @@@26. BEGIN_COLUMN_MAP(CProvider)27. //@@@ this is a bookmark @@@28. BOOKMARK_ENTRY( bookmark );29. //@@@ these are string fields @@@30. COLUMN_ENTRY(1, szAccountName );31. COLUMN_ENTRY(2, szBackupAccount );

Chapter Seven—Creating OLE DB Service Providers with ATL � 409

Page 429: Learn OLE DB Development With Visual C++ 6.0

32. COLUMN_ENTRY(3, szAccountNumber );33. COLUMN_ENTRY(4, szAccountBalance );34. END_COLUMN_MAP()35. };36.37. class CMfc1eDlgAutoProxy;38.39. //////////////////////////////////////////////////////////////40. // CMfc1eDlg dialog41.42. class CMfc1eDlg : public CDialog43. {44. DECLARE_DYNAMIC(CMfc1eDlg);45. friend class CMfc1eDlgAutoProxy;46.47. // Construction48. public:49. CMfc1eDlg(CWnd* pParent = NULL); // standard constructor50. virtual ~CMfc1eDlg();51.52. // Dialog Data53. //{{AFX_DATA(CMfc1eDlg)54. enum { IDD = IDD_MFC1E_DIALOG };55. long m_LookupKey;56. CString m_AccountName;57. long m_AccountBalance;58. long m_AccountNumber;59. CString m_BackupAccount;60. //}}AFX_DATA61.62. // ClassWizard generated virtual function overrides63. //{{AFX_VIRTUAL(CMfc1eDlg)64. protected:65. virtual void DoDataExchange(CDataExchange* pDX);66. // DDX/DDV support67. //}}AFX_VIRTUAL68.69. // Implementation70. protected:71. CMfc1eDlgAutoProxy* m_pAutoProxy;72. HICON m_hIcon;73.74. BOOL CanExit();75.76. // Generated message map functions77. //{{AFX_MSG(CMfc1eDlg)78. virtual BOOL OnInitDialog();

410 � Chapter Seven—Creating OLE DB Service Providers with ATL

Page 430: Learn OLE DB Development With Visual C++ 6.0

79. afx_msg void OnPaint();80. afx_msg HCURSOR OnQueryDragIcon();81. afx_msg void OnClose();82. virtual void OnOK();83. virtual void OnCancel();84. afx_msg void OnButton1();85. //}}AFX_MSG86. DECLARE_MESSAGE_MAP()87. };88.89. //{{AFX_INSERT_LOCATION}}90. // Microsoft Visual C++ will insert additional declarations91.92. #endif // !defined93. (AFX_MFC1EDLG_H__60517009_3F5B_11D3_96F0_DE8523A96746__INCLUDED_)

Listing 7-5 MFC1EDLG.H source code after modifications

� Lines 12-13 This code makes sure that the ATL OLE DB templates areimported from their header file. Without this include file, MFC will inter-pret the macros using its definitions, which won’t work!

� Lines 14-24 This code defines the custom class we need to send toother OLE DB templates to interact with our OLE DB provider; it is a“stub” definition of the provider’s user record functionality. The codedefines the actual variables that will hold the information from the OLEDB provider.

� Lines 25-34 These macros are used to actually create the databasefunctionality for our stub provider. Notice that they are somewhat differ-ent from the macros we used in the provider.

With the database record defined, you now need to actually connect to thedatabase and get information from it into the edit controls. You do this in theevent handler for the button control’s BN_CLICKED message. Bring up theMFC1EDLG.CPP file in the text editor, since this is where MFC put the codefor the event handler when it created it. Scan through the listing and enter orchange any grayed line of source code in Listing 7-6. Save the project. Thesection after the code explains what these changes do.

1. // mfc1eDlg.cpp : implementation file2. //3.4. #include “stdafx.h”5. #include “mfc1e.h”6. #include “mfc1eDlg.h”7. #include “DlgProxy.h”8. //@@@ put this in to be sure @@@9. #include <atldbcli.h>

Chapter Seven—Creating OLE DB Service Providers with ATL � 411

Page 431: Learn OLE DB Development With Visual C++ 6.0

10. #ifdef _DEBUG11. #define new DEBUG_NEW12. #undef THIS_FILE13. static char THIS_FILE[] = __FILE__;14. #endif15.16. ///////////////////////////////////////////////////////////////17. // CMfc1eDlg dialog18.19. IMPLEMENT_DYNAMIC(CMfc1eDlg, CDialog);20.21. CMfc1eDlg::CMfc1eDlg(CWnd* pParent /*=NULL*/)22. : CDialog(CMfc1eDlg::IDD, pParent)23. {24. //{{AFX_DATA_INIT(CMfc1eDlg)25. m_LookupKey = 0;26. m_AccountName = _T(“”);27. m_AccountBalance = 0;28. m_AccountNumber = 0;29. m_BackupAccount = _T(“”);30. //}}AFX_DATA_INIT31. // Note that LoadIcon does not require a DestroyIcon in Win3232. m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);33. m_pAutoProxy = NULL;34. }35.36. CMfc1eDlg::~CMfc1eDlg()37. {38. // If there is an automation proxy for this dialog, set39. // its back pointer to this dialog to NULL, so it knows40. // the dialog has been deleted.41. if (m_pAutoProxy != NULL)42. m_pAutoProxy->m_pDialog = NULL;43. }44.45. void CMfc1eDlg::DoDataExchange(CDataExchange* pDX)46. {47. CDialog::DoDataExchange(pDX);48. //{{AFX_DATA_MAP(CMfc1eDlg)49. DDX_Text(pDX, IDC_EDIT1, m_LookupKey);50. DDX_Text(pDX, IDC_EDIT2, m_AccountName);51. DDX_Text(pDX, IDC_EDIT4, m_AccountBalance);52. DDX_Text(pDX, IDC_EDIT3, m_AccountNumber);53. DDX_Text(pDX, IDC_EDIT5, m_BackupAccount);54. //}}AFX_DATA_MAP55. }56.

412 � Chapter Seven—Creating OLE DB Service Providers with ATL

Page 432: Learn OLE DB Development With Visual C++ 6.0

57. BEGIN_MESSAGE_MAP(CMfc1eDlg, CDialog)58. //{{AFX_MSG_MAP(CMfc1eDlg)59. ON_WM_PAINT()60. ON_WM_QUERYDRAGICON()61. ON_WM_CLOSE()62. ON_BN_CLICKED(IDC_BUTTON1, OnButton1)63. //}}AFX_MSG_MAP64. END_MESSAGE_MAP()65.66. ////////////////////////////////////////////////////////////67. // CMfc1eDlg message handlers68.69. BOOL CMfc1eDlg::OnInitDialog()70. {71. CDialog::OnInitDialog();72.73. // Set the icon. The framework does this automatically74. // when the application’s main window is not a dialog75. SetIcon(m_hIcon, TRUE); // Set big icon76. SetIcon(m_hIcon, FALSE); // Set small icon77.78. // TODO: Add extra initialization here79. return TRUE;80. // return TRUE unless you set the focus to a control81. }82.83. // If you add a minimize button, you will need the code below84. // to draw the icon.For MFC applications using the doc/view model,85. // this is automatically done for you by the framework.86.87. void CMfc1eDlg::OnPaint()88. {89. if (IsIconic())90. {91. CPaintDC dc(this); // device context for painting92.93. SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);94.95. // Center icon in client rectangle96. int cxIcon = GetSystemMetrics(SM_CXICON);97. int cyIcon = GetSystemMetrics(SM_CYICON);98. CRect rect;99. GetClientRect(&rect);100. int x = (rect.Width() - cxIcon + 1) / 2;101. int y = (rect.Height() - cyIcon + 1) / 2;102.103. // Draw the icon

Chapter Seven—Creating OLE DB Service Providers with ATL � 413

Page 433: Learn OLE DB Development With Visual C++ 6.0

104. dc.DrawIcon(x, y, m_hIcon);105. }106. else107. {108. CDialog::OnPaint();109. }110. }111.112. // The system calls this to obtain the cursor while the user drags113. // the minimized window.114. HCURSOR CMfc1eDlg::OnQueryDragIcon()115. {116. return (HCURSOR) m_hIcon;117. }118.119. // Automation servers should not exit when a user closes the UI120. // if a controller still holds on to one of its objects. These121. // message handlers make sure that if the proxy is still in use,122. // then the UI is hidden but the dialog remains around if it123. // is dismissed.124.125. void CMfc1eDlg::OnClose()126. {127. if (CanExit())128. CDialog::OnClose();129. }130.131. void CMfc1eDlg::OnOK()132. {133. if (CanExit())134. CDialog::OnOK();135. }136.137. void CMfc1eDlg::OnCancel()138. {139. if (CanExit())140. CDialog::OnCancel();141. }142.143. BOOL CMfc1eDlg::CanExit()144. {145. // If the proxy object is still around, then the automation146. // controller is still holding on to this application. Leave147. // the dialog around, but hide its UI.148. if (m_pAutoProxy != NULL)149. {150. ShowWindow(SW_HIDE);

414 � Chapter Seven—Creating OLE DB Service Providers with ATL

Page 434: Learn OLE DB Development With Visual C++ 6.0

151. return FALSE;152. }153.154. return TRUE;155. }156.157. void CMfc1eDlg::OnButton1()158. {159. // TODO: Add your control notification handler code here160. //@@@ 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<CAccessor<CProvider> > 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. {

Chapter Seven—Creating OLE DB Service Providers with ATL � 415

Page 435: Learn OLE DB Development With Visual C++ 6.0

198. //@@@ signal error to user @@@199. AfxMessageBox( “Can’t open database!” );200. //@@@ leave @@@201. return;202. }203. //@@ must do this to get to first record or get garbage! @@@204. table.MoveNext();205. //@@@ get the four fields and put them into the member vars @@@206. m_AccountName = table.szAccountName;207. m_BackupAccount = table.szBackupAccount;208. m_AccountNumber = atol( table.szAccountNumber );209. m_AccountBalance = atol( table.szAccountBalance );210. //@@@ put the member vars into the ui @@@211. UpdateData(false);212. }

Listing 7-6 MFC1EDLG.CPP source code after modifications

� Lines 8-9 This makes sure that MFC will use the ATL macros for ourOLE DB templates.

� Lines 160-161 This brings the value from the edit control into themember variable for the lookup key value so we can use it.

� Lines 162-167 This segment defines a template-created Commandobject and a DataSource and Session object. As noted in Chapter 4, allinteraction with OLE DB providers must be via a DataSource object, whichis then used to create a Session object, which finally executes commandsor get rowsets.

� Lines 168-176 This code gets our basic DataSource object. It does sousing the COM ProgID value for the OLE DB provider (which is in the RGSfile of that project if you forget it). We test for a COM success, and if itdoesn’t happen we signal the user with an informative error message sothey can fix the problem. All this does, however, is make contact with theCOM server that contains our OLE DB provider; we haven’t done anydatabase work yet!

� Lines 177-184 Next we have to get a Session object, and this codedoes 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 HRESULTerror code, which we can check and then return a user-friendly error mes-sage if needed.

� Lines 185-202 This is the most vital code, because it actually sends acommand to the OLE DB provider. Up to this point we were using“canned” OLE DB functionality from our ATL templates. This is where ourcode gets tested! First an OLE DB Property object is created that informsthe provider that we support bookmarks (lines 185-188; note that theIRowsetLocate interface is what turns on bookmarks). In lines 189-195 we

416 � Chapter Seven—Creating OLE DB Service Providers with ATL

Page 436: Learn OLE DB Development With Visual C++ 6.0

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 theprovider along via the Table object’s Open method. This call contains asparameters the Session object, a string with a path to a text file containingnames 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 aRowset is returned with all the strings stored in the text file in recordsconsisting of two strings each. If a problem occurs, the COM error code ischecked and an error message sent to the user.

� Lines 203-211 This is the code that gets all the records in our data-base and puts them into the edit controls via member variables. It is quitestraightforward, using the MoveNext function of our Table object to moveto the only record of the returned rowset. Conversion is then done to putthe data into member variables, and the UpdateData function called toshow 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 cor-rectly, build the OLE DB consumer test application from the Build menu. Ifyou encounter any errors, please check the code in the listing to find yourmistake (it has been exhaustively debugged and is correct). Now you areready to test the consumer and service provider.

Testing the OLE DB Service Provider with the MFC ConsumerApplication

Now you’re ready to test the whole shebang! Make sure the Accounts data-base 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 from1 to 7 in the Lookup Key edit control and click on the button. After a

moment, as shown inFigure 7-23, a set ofvalues will appearfrom the AccountsAccess database, sentalong via the COMserver OLE DB con-sumer and the OLEDB service provider.Try entering any ofthe other numbersand the appropriatevalues will promptlyshow up.

Chapter Seven—Creating OLE DB Service Providers with ATL � 417

Figure 7-23

The MFC OLEDB consumerapplicationdisplays theAccess data-base from theOLE DB ser-vice provider

Page 437: Learn OLE DB Development With Visual C++ 6.0

Where We Go From HereWhere We Go From Here

You’ve completed two OLE DB project suites now; you are ready to move upto the more sophisticated and powerful OLE DB consumer template system,which is the topic of the next chapter.

418 � Chapter Seven—Creating OLE DB Service Providers with ATL

Page 438: Learn OLE DB Development With Visual C++ 6.0

Chapter Eight

Creating and Using Simple OLECreating and Using Simple OLE

DB Consumers in ATLDB 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 sup-ported by an MFC client. In this chapter, you’ll learn how to take advantageof the template functionality of an ATL OLE DB consumer to create a simplebut very powerful COM server that can function in a web page to connect toan Access database and drive the Microsoft Agent ActiveX control. Whenyou’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 ActionOLE DB Consumer Templates in Action

Unlike OLE DB provider templates, OLE DB consumer templates are designedto automate much of the process of writing the application. Their user inter-face (via the ATL Object Wizard) locates the OLE DB provider you wish to useand interrogates it for the information needed to create the code to instan-tiate an OLE DB consumer for it. Boilerplate methods are provided that con-nect to the OLE DB provider and obtain a default rowset, storing it in amember variable.

This approach has two drawbacks: First, you may not want to obtain a rowsetas soon as you connect, or you may want to send a command string whenyou do (as is the case in our application in this chapter); in either case, you’llneed to modify the automatically generated code to change the behavior. Sec-ond, an OLE DB consumer is not a COM interface, oddly enough! Instead, itis a group of C++ classes only; if you want to expose its functionality viaCOM, you must add an interface-based object (like a Simple Object) andexpose methods and/or properties on it to let your clients interact with theOLE DB consumer.

419

Page 439: Learn OLE DB Development With Visual C++ 6.0

In the project that follows, you’ll use both of these techniques to modify thedefault OLE DB consumer the wizard provides you. In the process, you’llobserve many of the basics needed to create a working, usable OLE DBconsumer application that does a real job. You can then take the code andmodify it as needed for your task, or use the same process to create your owncustom component with OLE DB consumer functionality.

Creating an OLE DB Consumer COM Server with ATLCreating an OLE DB Consumer COM Server with ATL

You create an OLE DB consumer much in the same way you created OLE DBproviders in the previous two chapters. The exceptions are that you need aworking OLE DB provider with a database and registered ODBC connectionalready in place before you can create the consumer. The following directionswill take you through the process of creating an Access database and register-ing it with the ODBC system as well as showing you how to create the actualOLE 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 ODBC-compliant databases and which has an ODBC driver installed on both thedevelopment and target machines. For the step-by-step section below, I usedMicrosoft Access, but any similar database application will work also.

Note You can skip this step if you wish by copying over the project files forChapter 8 from the companion CD; they contain the completed Access data-base 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 Figure8-1 appears. Select the Blank Databaseradio button and click on the OKbutton.

Figure 8-1

The new data-base dialog forAccess forcreating a blankdatabase

420 � Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL

Page 440: Learn OLE DB Development With Visual C++ 6.0

As shown in Figure 8-2, a filebrowser dialog appears. Use it tolocate the directory where youwill create the project files forthe simple consumer (use theicon button for a new folder ifneeded). Enter a filename ofcommands.mdb, and click onthe Create button.

After a moment, a blank database child window will appear inside the AccessIDE. Maximize it and select the Tables tab as shown in Figure 8-3. Then clickon the New button to start creating a table for the database.

The New Table dialog appears as shown in Figure 8-4. Select the Design Viewitem from the right-hand list box and click on the OK button to start layingout the table.

Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL � 421

Figure 8-2

Browsing tothe directoryand namingthe databasefile in Access95

Figure 8-3

The Tablestab of theAccess 95database IDE

Figure 8-4

Creating anew table inAccess 95

Page 441: Learn OLE DB Development With Visual C++ 6.0

The Access Table Editor child window appears, similar to that shown in Fig-ure 8-5. Enter a name of ID for the first column in the Field Name cell, thengive it an entry of AutoNumber in the Data Type cell. Select the ID cell

again and click on thesmall key icon in thetoolbar; this makes theselected field the primarykey for the database. Insimilar fashion, enter avalue of Command inthe second Field Namecell, with a default DataType cell value of Text,and a value of Text in thethird Field Name cell, alsowith a default Data Typecell value of Text. Theeditor should look verysimilar to Figure 8-5 whenyou are finished.

Now click on the Save icon or chooseFile|Save from the menu. The Save Asdialog will appear as shown in Figure8-6. Enter Instructions as the tablename and click on the OK button tosave the table and the database.

From the menu, nowselect View|DatabaseView. The IDE changes toshow the columns you justdesigned, currently withno data. Notice that sinceyou set it to a data type ofAutoNumber, you cannotenter data in the ID cell;rather the application setsthis value as you enterdata in the other cells.Enter the values in Table8-1 into the Commandand Text cells in the ordershown to create the datafor the beginning

422 � Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL

Figure 8-7

The DatabaseEditor inAccess 95showing 10entries

Figure 8-5

Creating threecolumns inAccess 95’sTable Editor

Figure 8-6

Naming thedatabasetable prior tosaving

Page 442: Learn OLE DB Development With Visual C++ 6.0

database. When you are done, the editor should look very similar to the dis-play in Figure 8-7. Save your work and close Access.

Table 8-1 Entries for the Instructions table of the COMMAND.MDB Access database

ID Command Text

1 Show

2 Speak Welcome to OLE DB

3 MoveTo 300,300

4 Speak I am being controlled from an OLE DB consumer

5 Play Acknowledge

6 MoveTo 300,600

7 GestureAt 300,300

8 Play Wave

9 Speak Now it is your turn to command me

10 Hide

Creating an ODBC Connection with Control Panel

Now that you have created the database for our project, you must create anODBC connection to it via the Control Panel. This will allow our OLE DBconsumer 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 inFigure 8-8, double-click on the ODBC Data Sources (32bit) icon to start theODBC configuration applet.

Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL � 423

Figure 8-8

The Control Panelshowing theODBC DataSources icon

Page 443: Learn OLE DB Development With Visual C++ 6.0

The ODBC Data SourceAdministrator dialog appears.Select the User DSN tab asshown in Figure 8-9. Click onthe Add button to start add-ing the database for theproject.

The Create New Data Source dia-log appears next; select theMicrosoft Access Driver (*.mdb)entry (or the driver for the typeof database file you created in theprevious step if you didn’t useAccess). Figure 8-10 shows howthe dialog should appear at thispoint; click on Finish to movefrom the ODBC Administrator tothe driver’s user interface.

The dialog that appears nextdepends entirely on thedriver you selected for yourdatabase file. Figure 8-11illustrates the one forMicrosoft Access; if you useda different database, yourdialog may be different.Regardless of the exact con-figuration, however, you willneed to perform some versionof the following steps. ForAccess, enter a Data SourceName of Command, and aDescription of test data-base for OLE DB

424 � Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL

Figure 8-10

Selecting adriver for thedatabase file

Figure 8-9

ODBC DataSource Admin-istrator UserDSN tab

Figure 8-11

Naming theAccess datasource

Page 444: Learn OLE DB Development With Visual C++ 6.0

consumer. Leave the System Database None radio button selected since thisis a user database. Then, click on the Select button.

In the Access driver, a filebrowser dialog appears next.Use it to locate the com-mands.mdb file you created inthe previous section. Select it asshown in Figure 8-12 and clickon OK.

Back in the Access setup dia-log, you should see thefilename you selected dis-played inside the Databasegroup box, as shown in Fig-ure 8-13. Next, click on theAdvanced button.

The Access Set Advanced Optionsdialog appears; enter a Login Nameof Admin as shown in Figure 8-14.Click on OK to set this advancedoption and return to the main setupdialog. Click on OK again to com-plete adding the new data source.

Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL � 425

Figure 8-13

The ODBCdatabasesetup dialogshowing theselected data-base file

Figure 8-12

Selecting theactual data-base file forthe AccessData Source

Figure 8-14

Setting thelogin name forthe Accessdatabase

Page 445: Learn OLE DB Development With Visual C++ 6.0

After a moment the ODBC DataSource Administrator dialog reap-pears. As illustrated by Figure 8-15,it should now display the Commanddata source with the driver you pre-viously selected. Click on OK tocomplete adding the new ODBC datasource for the OLE DB consumerproject.

Creating an OLE DB Provider COM DLL with ATLCreating an OLE DB Provider COM DLL with ATL

One of the most common tasks performed in personal computing is readingdata from text files; with OLE DB, this chore can be promoted into the power-ful realm of database programming. The following sections take you throughthe user interface portions of creating an OLE DB provider project that readsa text file of e-mail addresses and URLs and provides them as an OLE DBrowset. It also supports bookmarks, including dynamic column information.As noted in Chapter 6, the project is presented in my well-received step-by-step system, then shows you the code you need to write and an analysis usingthe Before and After and Wizard’s Wand techniques.

Warning You must have Visual C++ 6.0 to create this and the other projectsin the book; Visual C++ 5.0 does not support the OLE DB system in the sameway.

Step-By-Step

Start Visual C++ 6.0. From the File menu, select New. The New dialog boxopens; click on ATL COM AppWizard and select an appropriate directory.Then enter a project name of ATLSimpleC. Figure 8-16 shows how the Newdialog should appear when you are done.

426 � Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL

Figure 8-15

The ODBCData SourceAdministratorshowing thenew DataSource

Page 446: Learn OLE DB Development With Visual C++ 6.0

In the ATL COM AppWizard, select the DLL project, and make sure that theSupport MFC, Merge Proxy/Stub Code, and Support MTS check boxes areunchecked since we don’t need those options; Figure 8-17 illustrates how thedialog should appear when you are done.

Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL � 427

Figure 8-16

The Newdialog boxcreating theATLSimpleCATL project

Figure 8-17

Setting theOLE DB COMserver projectproperties

Page 447: Learn OLE DB Development With Visual C++ 6.0

A confirmation dialog boxappears as shown in Figure8-18; press OK to create the ATLproject.

From the Insert menu, selectNew ATL Object. The ATLObject Wizard dialog appears,as shown in Figure 8-19. Selectthe Data Access entry in theleft-hand list box, and the Con-sumer entry in the right-handlist box. Press Next to move tothe OLE DB settings page of thewizard.

As illustrated by Figure8-20, the initial Propertiesdialog has most of its userinterface disabled, includingthe ability to complete it.This is because it requiresselecting an OLE DB pro-vider via the SelectDatasource button shown inthe figure. First make surethe Command radio buttonis selected in the Type groupbox, then click on the SelectDatasource button.

428 � Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL

Figure 8-19

Selecting anOLE DB Con-sumer Objectin the ATLObjectWizard

Figure 8-18

The confirma-tion dialog

Figure 8-20

The initialPropertiesdialog

Page 448: Learn OLE DB Development With Visual C++ 6.0

The Data Link Properties dialogappears; select the Provider tab asshown in Figure 8-21. Since we haveconfigured an ODBC database, selectMicrosoft OLE DB Provider for ODBCDrivers as illustrated in the figure.Then click on the Next button, orselect the Connection tab.

Either action brings you to the Con-nection tab of the Data Link Propertiesdialog box. Select the Use data sourcename radio button, and select Com-mand from the drop- down list. Entera User name of Admin, and check theBlank password check box. From thebottom drop-down list, select the onlyentry since our database only has onecatalog. Figure 8-22 shows how thedialog should look at this point. Nowclick on the Test Connection button.

If you have configured the ODBC data source correctly in the previous sectionand entered its name and login correctly in the previous step, you should seethe success dialog shown in Figure 8-23. If not, please recheck the previouswork until you find the problem, and repeat the steps in this section. Once

you see the success dialog, move to the Advancedtab of the dialog and check the read/write checkbox to allow updates, deletions, and insertions tothe database. Then click on OK to dismiss the dia-log and provide the information to the ATL ObjectWizard.

Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL � 429

Figure 8-23

The successdialog

Figure 8-22

The Data LinkProperties dia-log Connec-tion tab

Figure 8-21

The Data LinkProperties dia-log Providertab

Page 449: Learn OLE DB Development With Visual C++ 6.0

At this point you have exited theData Link Properties dialog andare back in the OLE DB ATLObject Wizard. The next dialogyou’ll see is the one shown in Fig-ure 8-24, which should displaythe Instructions table from theCommand database we created inthe first section of this chapter.Select it as shown in the figureand click on the OK button.

This returns you to the ATL Object Wizard Properties dialog. Now the userinterface is active; enter Instructions as the Short Name; the other name

edit controls fill in automati-cally. Select all three of theChange, Insert, and Deletecheck boxes to enable thatfunctionality. When you aredone, the dialog should lookvery similar to Figure 8-25.Click on OK to add the OLEDB consumer object to theproject. Save the project toprotect your work.

Remember that earlier wementioned one of the draw-backs to the OLE DBconsumer in ATL is that it isnot a COM object. To over-come this, we need to add aCOM interface to the pro-ject. Start the process byselecting Insert|New ATLObject from the menu. TheATL Object Wizard appearsas shown in Figure 8-26.Select the Simple Objecticon of the Objects line ofthe wizard as illustrated,and click on the Nextbutton.

430 � Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL

Figure 8-25

The ATLObject Wiz-ard Propertiesdialog for theOLE DBconsumer

Figure 8-24

The SelectDatabaseTable dialogfor the OLEDB consumerATL object

Figure 8-26

The ATLObjectWizard

Page 450: Learn OLE DB Development With Visual C++ 6.0

Select the Names tab of theProperties dialog thatappears next. As illustratedby Figure 8-27, enterAgentController in the ShortName edit control; the otheredit controls will fill in auto-matically. Now click on theAttributes tab of the dialog.

On the Attributes tab, leaveeverything in its default posi-tion except for checking theSupport ISupportErrorInfocheck box as shown in Fig-ure 8-28. Click on OK todismiss the wizard and addthis COM interface to theOLE DB consumer project.Save the project to protectyour work.

To make the new COM interface useful, you need to add method functions toit. To do this, make the workspace visible if it isn’t already, and select theClassView tab. Expand the classes tree until the IAgent- Controller entry

becomes visible. Right-click on itto bring up its context menu, andselect Add Method. The AddMethod to Interface Wizard dia-log appears as shown in Figure8-29. Enter a Method Name ofExecuteCommands. Leave allthe other settings unchanged andclick on the OK button to add themethod to the COM interface.

Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL � 431

Figure 8-28

The Attributestab of the ATLObjectWizard

Figure 8-27

The Namestab of the ATLObjectWizard

Figure 8-29

Adding amethod to theCOM inter-face via theMethodWizard

Page 451: Learn OLE DB Development With Visual C++ 6.0

Repeat the previous step, giving the new method a Method Name ofAddCommand. Again repeat the process to add a new method, with aMethod Name of DeleteCommand. Add a final method to the interfacewith a Method Name of ChangeCommand.

You may have noticed that none ofthese methods have parameters.Instead, we’ll send them their infor-mation via COM interface properties.Right-click on the IAgentControllerinterface again in the workspaceClassView tab, and select the AddProperty menu entry. The Add Prop-erty to Interface Wizard dialogappears as illustrated in Figure 8-30.Select short from the Property Typecombo box, then enter a PropertyName of CommandID. Leave theother elements unchanged as shownin the figure, and click on OK to addthe COM property to theIAgentController interface.

Repeat the above step to add another property to the COM interface, thistime giving it a Property Type of BSTR and a Property Name ofCommandName.

Again repeat the process to add a final property to the COM interface; give ita Property Type of BSTR and a Property Name of CommandText. Save theproject.

As a final userinterface step forthis project, youneed to makesure that theMicrosoft AgentActiveX controlis installed onyour develop-ment computerand on the clientcomputer for theproject (if differ-ent). You canuse theOLE/COMObject Viewer

432 � Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL

Figure 8-30

Adding aproperty tothe COMinterface viathe PropertyWizard

Figure 8-31

Using theOLE/COMObject Viewerto determineif theMicrosoftAgent ActiveXcontrol isinstalled

Page 452: Learn OLE DB Development With Visual C++ 6.0

from the Tools menu to determine this; bring up the application, then expandthe Controls item in the left-hand tree control. When it finishes opening,scroll down to the Microsoft controls group and check to see if MicrosoftAgent 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 theAgentsvr.exe file (usually found in the C:\WINDOWS\ MSAGENT\directory).

Make a note as to its exactpath; you’ll need it later dur-ing code entry. Otherwise,navigate to the followingURL: http:// msdn.microsoft.com/ work-shop/media/agent/agentdl.asp, asshown in Figure 8-32. Usethe link shown to downloadthe core Agent files andinstall them by running thedownloaded executable onthe development and, ifappropriate, targetmachine(s). Once this isdone, locate Agentsvr.exe onthe development computerand note its path asexplained 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 webegin changing it. This will serve as a reference for later projects you mayundertake in case you’re not sure what a given bit of code does. The ATL OLEDB Object Wizard creates only one source code file (plus some support fileswe won’t go into including the core COM DLL file, which is identical for prac-tical purposes with the one discussed in Chapter 7) with automaticallygenerated code to provide basic OLE DB consumer functionality.

Listing 8-1 gives the source code for the INSTRUCTIONS.H file, which is theOLE DB consumer implementation based on the Instructions table of ourCommands database. Other projects, of course, will give this file a differentname based on the table name for their particular database.

Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL � 433

Figure 8-32

Downloadingthe MicrosoftAgent Controlfrom theMicrosoft website

Page 453: Learn OLE DB Development With Visual C++ 6.0

1. // Instructions.H : Declaration of the CInstructions class2.3. #ifndef __INSTRUCTIONS_H_4. #define __INSTRUCTIONS_H_5.6. class CInstructionsAccessor7. {8. public:9. LONG m_ID;10. TCHAR m_Command[51];11. TCHAR m_Text[51];12.13. BEGIN_COLUMN_MAP(CInstructionsAccessor)14. COLUMN_ENTRY(1, m_ID)15. COLUMN_ENTRY(2, m_Command)16. COLUMN_ENTRY(3, m_Text)17. END_COLUMN_MAP()18.19. DEFINE_COMMAND(CInstructionsAccessor, _T(“ \20. SELECT \21. ID, \22. Command, \23. Text \24. FROM Instructions"))25.26. // You may wish to call this function if you are inserting a record and wish to27. // initialize all the fields, if you are not going to explicitly set all of

them.28. void ClearRecord()29. {30. memset(this, 0, sizeof(*this));31. }32. };33.34. class CInstructions : public CCommand<CAccessor <CInstructionsAccessor> >35. {36. public:37. HRESULT Open()38. {39. HRESULT hr;40.41. hr = OpenDataSource();42. if (FAILED(hr))43. return hr;44.45. return OpenRowset();46. }

434 � Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL

Page 454: Learn OLE DB Development With Visual C++ 6.0

47. HRESULT OpenDataSource()48. {49. HRESULT hr;50. CDataSource db;51. CDBPropSet dbinit(DBPROPSET_DBINIT);52.53. dbinit.AddProperty(DBPROP_AUTH_PASSWORD, OLESTR(“”));54. dbinit.AddProperty(DBPROP_AUTH_PERSIST_SENSITIVE_AUTHINFO, false);55. dbinit.AddProperty(DBPROP_AUTH_USERID, OLESTR(“Admin”));56. dbinit.AddProperty(DBPROP_INIT_DATASOURCE, OLESTR(“Command”));57. dbinit.AddProperty(DBPROP_INIT_PROMPT, (short)4);58. dbinit.AddProperty(DBPROP_INIT_LCID, (long)1033);59. dbinit.AddProperty(DBPROP_INIT_CATALOG, OLESTR(60. “J:\\wordware\\Learn OLE DB Programming\\chap08\\projects\\ATLSimpleC\\commands”61. ));62. hr = db.Open(_T(“MSDASQL”), &dbinit);63. if (FAILED(hr))64. return hr;65.66. return m_session.Open(db);67. }68. HRESULT OpenRowset()69. {70. // Set properties for open71. CDBPropSet propset(DBPROPSET_ROWSET);72. propset.AddProperty(DBPROP_IRowsetChange, true);73. propset.AddProperty(DBPROP_UPDATABILITY,74. DBPROPVAL_UP_CHANGE | DBPROPVAL_UP_INSERT | DBPROPVAL_UP_DELETE);75.76. return CCommand<CAccessor<CInstructionsAccessor>>::Open(m_session,77. NULL, &propset);78. }79. CSession m_session;80. };81.82. #endif // __INSTRUCTIONS_H_

Listing 8-1 INSTRUCTIONS.H source code before modifications

The list below gives the important code blocks of the listing and explainswhat they do; particular attention is paid to macros and template definitionsas appropriate:

� Lines 9-11 This code contains the three member variables that willhold the data from each record in the database; their names are automati-cally assigned and typed based on the schema for the database.

Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL � 435

Page 455: Learn OLE DB Development With Visual C++ 6.0

� Lines 13-17 This code contains the COLUMN_MAP macro entries thatdefine the three fields for the database record for the OLE DB consumertemplate.

� Lines 19-24 This code contains the macro to define the SQL commandused to obtain each record of the database.

� Lines 26-32 This code contains a function to clear a record prior tomanipulating it for insertion, which can be used instead of writing yourown.

� Lines 37-46 This code contains the default implementation of theOpen method of the CInstructions class used to interact with the OLE DBprovider. Notice that it automatically opens a DataSource object via a callto the OpenDataSource method, and if the call succeeds it then opens adefault rowset via the m_session member variable and a call to theOpenRowset method. This behavior may need to be changed (and in factwe will change it) by many consumer applications.

� Lines 47-67 This code contains the implementation of theOpenDataSource method of the CInstructions class. It creates localCDataSource and CDBPropSet variables and initializes them with informa-tion from the ODBC data source we entered in the wizard dialog. It thenopens the DataSource object, and if that succeeds, initializes the m_ses-sion variable via its Open method to complete the connection with thedatabase. Normally this code won’t be changed for applications.

� Lines 68-77 This code contains the default implementation of theOpenRowset 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 inlines 76 and 77 is the most likely to be changed depending on what ele-ments a default rowset needs for a given application.

� Line 79 This code contains the definition of the Session object for theclass.

Customizing the OLE DB Consumer

Now that you’ve got some familiarity with the wizard-generated code for abasic OLE DB consumer, it is time to change it to meet the needs of our spe-cific 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 theseguidelines as a template for your own specific changes.

436 � Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL

Page 456: Learn OLE DB Development With Visual C++ 6.0

Disabling Rowset Return on Database Open

Listing 8-2 gives the source code from the INSTRUCTIONS.H file that imple-ments the Open functionality for the CInstructions class.

1. class CInstructions : public CCommand<CAccessor <CInstructionsAccessor> >2. {3. public:4. HRESULT Open()5. {6. HRESULT hr;7.8. hr = OpenDataSource();9. if (FAILED(hr))10. return hr;11.12. return OpenRowset();13. }14.

Listing 8-2 INSTRUCTIONS.H source code before modifications

The directions below tell you what to remove and what to add to modify thiscode to meet our needs:

� 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 imple-ments the Open method after your changes.

1. class CInstructions : public CCommand<CAccessor <CInstructionsAccessor> >2. {3. public:4. HRESULT Open()5. {6. HRESULT hr;7.8. hr = OpenDataSource();9. if (FAILED(hr))10. return hr;11.12. return hr;//@@@ return OpenRowset(); disable open and return hr13. }14.

Listing 8-3 INSTRUCTIONS.H source code after modifications

Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL � 437

Page 457: Learn OLE DB Development With Visual C++ 6.0

The following entries explain what the new code lines do:

� Line 12 This code returns the HRESULT from OpenDataSource and dis-ables the default opening of a rowset. This allows access to variousRowset objects based on more sophisticated interactions from the userinterface.

Adding OLE DB Consumer and Microsoft Agent Support

The remaining actions you need to take to complete the OLE DB consumerserver are standard line-by-line modifications. Start by bringing up theAGENTCONTROLLER.H file in the text editor and entering the grayed code inListing 8-4. Save the project. The grayed entries are explained in the sectionbelow.

1. // Agentcontroller.h : Declaration of the CAgentcontroller2.3. #ifndef __AGENTCONTROLLER_H_4. #define __AGENTCONTROLLER_H_5.6. #include “resource.h” // main symbols7. //@@@ include CInstructions OLE DB Consumer @@@8. #include “Instructions.h”9. //@@@ change these to match your MSAgent location @@@10. #import “C:\Windows\MSAgent\agentsvr.exe” no_namespace11. #define AGENT_CHARACTER “C:\\Windows\\MSAgent\\Chars\\merlin.acs”12.13. /////////////////////////////////////////////////////////////////////////////14. // CAgentcontroller15. class ATL_NO_VTABLE CAgentcontroller :16. public CComObjectRootEx<CComSingleThreadModel>,17. public CComCoClass<CAgentcontroller, &CLSID_Agentcontroller>,18. public ISupportErrorInfo,19. public IDispatchImpl<IAgentcontroller, &IID_IAgentcontroller,20. &LIBID_ATLSIMPLECLib>21. {22. public:23. CAgentcontroller()24. {25. }26. HRESULT FinalConstruct();27. void FinalRelease();28. DECLARE_REGISTRY_RESOURCEID(IDR_AGENTCONTROLLER)29.30. DECLARE_PROTECT_FINAL_CONSTRUCT()31.32. BEGIN_COM_MAP(CAgentcontroller)33. COM_INTERFACE_ENTRY(IAgentcontroller)

438 � Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL

Page 458: Learn OLE DB Development With Visual C++ 6.0

34. COM_INTERFACE_ENTRY(IDispatch)35. COM_INTERFACE_ENTRY(ISupportErrorInfo)36. END_COM_MAP()37.38. // ISupportsErrorInfo39. STDMETHOD(interfaceSupportsErrorInfo)(REFIID riid);40.41. // IAgentcontroller42. public:43. STDMETHOD(GetAllCommands)(/*[out]*/ BSTR* bstrCommandsString);44. STDMETHOD(get_CommandText)(/*[out, retval]*/ BSTR *pVal);45. STDMETHOD(put_CommandText)(/*[in]*/ BSTR newVal);46. STDMETHOD(get_CommandName)(/*[out, retval]*/ BSTR *pVal);47. STDMETHOD(put_CommandName)(/*[in]*/ BSTR newVal);48. STDMETHOD(get_CommandID)(/*[out, retval]*/ short *pVal);49. STDMETHOD(put_CommandID)(/*[in]*/ short newVal);50. STDMETHOD(ChangeCommand)();51. STDMETHOD(DeleteCommand)();52. STDMETHOD(AddCommand)();53. STDMETHOD(ExecuteCommands)();54. //@@@ hold the OLE DB Consumer @@@55. CInstructions* instructions;56. //@@@ hold the id value @@@57. short sID;58. //@@@ hold the command @@@59. CComBSTR ccbstrCommand;60. //@@@ hold the text @@@61. CComBSTR ccbstrText;62. //@@@ get agent pointer @@@63. IAgentPtr m_pAgent;64. //@@@ get agent character pointer @@@65. IAgentCharacterPtr m_pCharacter;66. //@@@ Store Agent ID @@@67. long m_lRequestID;68. };69.70. #endif //__AGENTCONTROLLER_H_

Listing 8-4 AGENTCONTROLLER.H source code after modifications

The following entries explain what the new code lines do:

� Lines 7-11 This code brings in the header file so we can declare a vari-able of the OLE DB consumer’s specific class, as well as the two MicrosoftAgent files. Note that the Agent files may be in different places for yoursystem; if so, you must modify the code as noted in the comment lines.

Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL � 439

Page 459: Learn OLE DB Development With Visual C++ 6.0

� Lines 26-27 This code declares the two overridden methods forFinalConstruct and FinalRelease, so that we can use them to create theOLE DB Consumer class and Agent control in memory and later releasethem.

� Lines 54-67 This code declares all the member variables we need forthe 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 enterthe grayed code in Listing 8-5. Save the project. The grayed entries areexplained in the section below.

1. // Agentcontroller.cpp : Implementation of CAgentcontroller2. #include “stdafx.h”3. #include “ATLSimpleC.h”4. #include “Agentcontroller.h”5.6. /////////////////////////////////////////////////////////////////////////////7. // CAgentcontroller8.9. HRESULT CAgentcontroller::FinalConstruct()10. {11. //@@@ Define HRESULT holder @@@12. HRESULT hr;13. //@@@ define a character id holder @@@14. long lCharID;15. //@@@ define an IDispatch IP holder @@@16. CComPtr<IDispatch> pdCharacter;17. //@@@ get the MSAgent ActiveX control @@@18. hr = m_pAgent.CreateInstance(__uuidof(Agentserver));19. //@@@ inform user if fail @@@20. if (!SUCCEEDED(hr))21. {22. return Error(_T(“Unable To Contact Microsoft Agent control!”));23. }24. //@@@ get the defined character’s ID values @@@25. hr = m_pAgent->Load(AGENT_CHARACTER,&lCharID,&m_lRequestID);26. //@@@ inform user if fail @@@27. if (!SUCCEEDED(hr))28. {29. return Error(_T(“Unable To Contact Microsoft Agent control!”));30. }31. //@@@ get the designated character @@@32. hr = m_pAgent->GetCharacter(lCharID,&pdCharacter);33. //@@@ inform user if fail @@@

440 � Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL

Page 460: Learn OLE DB Development With Visual C++ 6.0

34. if (!SUCCEEDED(hr))35. {36. return Error(_T(“Unable To Contact Microsoft Agent control!”));37. }38. //@@@ put IDispatch IP into our member variable @@@39. m_pCharacter = pdCharacter;40. //@@@ create the instructions object @@@41. instructions = new CInstructions;42. //@@@ open the database @@@43. return instructions->Open();44. }45.46. void CAgentcontroller::FinalRelease()47. {48. //@@@ delete the database if not null @@@49. if (instructions) delete instructions;50. //@@@ release the two IP’s @@@51. if (m_pCharacter) m_pCharacter.Release();52. if (m_pAgent) m_pAgent.Release();53. }54.55. STDMETHODIMP CAgentcontroller::interfaceSupportsErrorInfo(REFIID riid)56. {57. static const IID* arr[] =58. {59. &IID_IAgentcontroller60. };61. for (int i=0; i < sizeof(arr) / sizeof(arr[0]); i++)62. {63. if (InlineIsEqualGUID(*arr[i],riid))64. return S_OK;65. }66. return S_FALSE;67. }68.69. STDMETHODIMP CAgentcontroller::ExecuteCommands()70. {71. // TODO: Add your implementation code here72. //@@@ bring in conversion macros @@@73. USES_CONVERSION;74. //@@@ define HRESULT holder @@@75. HRESULT hr;76. //@@@ open the current rowset @@@77. hr = instructions->OpenRowset();78. //@@@ if fail return IDispatch Error @@@79. if (!SUCCEEDED(hr))80. {

Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL � 441

Page 461: Learn OLE DB Development With Visual C++ 6.0

81. return Error(_T(“Unable to Open Rowset for Excuting Commands!”));82. }83. //@@@ play instructions one at a time @@@84. while (instructions->MoveNext() == S_OK)85. {86. //@@@ do if else cascade to check for supported commands @@@87. if (lstrcmpi(instructions->m_Command,_T(“Speak”)) == 0)88. { //@@@ speak command @@@89. m_pCharacter->Show(FALSE,&m_lRequestID);90. m_pCharacter->Speak( (BSTR)CComBSTR (instructions->m_Text), “”,91. &m_lRequestID);92. }93. //@@@ play command @@@94. else if (lstrcmpi(instructions->m_Command, _T(“Play”)) == 0)95. m_pCharacter->Play( (BSTR)CComBSTR (instructions->m_Text),96. &m_lRequestID);97. //@@@ hide command @@@98. else if (lstrcmpi(instructions->m_Command, _T(“Hide”)) == 0)99. m_pCharacter->Hide(FALSE, &m_lRequestID);100. //@@@ show command @@@101. else if (lstrcmpi(instructions->m_Command, _T(“Show”)) == 0)102. m_pCharacter->Show(FALSE, &m_lRequestID);103. //@@@ moveto command @@@104. else if (lstrcmpi(instructions->m_Command, _T(“MoveTo”)) == 0)105. { //@@@ define position variables @@@106. short x, y;107. #ifdef _UNICODE //@@@ unicode scanf @@@108. wscanf(instructions->m_Text, _T(“%d, %d”), &x, &y);109. #else //@@@ ansi scanf @@@110. sscanf(instructions->m_Text, _T(“%d, %d”), &x, &y);111. #endif112. m_pCharacter->MoveTo(x, y, 50 , &m_lRequestID);113. }114. //@@@ gestureat command @@@115. else if (lstrcmpi(instructions->m_Command, _T(“GestureAt”)) == 0)116. { //@@@ position variables @@@117. short x, y;118. #ifdef _UNICODE //@@@ unicode scanf @@@119. wscanf(instructions->m_Text, _T(“%d, %d”), &x, &y);120. #else //@@@ ansi scanf @@@121. sscanf(instructions->m_Text, _T(“%d, %d”), &x, &y);122. #endif123. m_pCharacter->GestureAt(x, y, &m_lRequestID);124. }125.126. }127. //@@@ close the rowset @@@

442 � Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL

Page 462: Learn OLE DB Development With Visual C++ 6.0

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 here136. //@@@ 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 @@@

Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL � 443

Page 463: Learn OLE DB Development With Visual C++ 6.0

175. instructions->Close();176. //@@@ signal ok @@@177. return S_OK;178. }179.180. STDMETHODIMP CAgentcontroller::DeleteCommand()181. {182. // TODO: Add your implementation code here183. //@@@ 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 false195. 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. else205. {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. }

444 � Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL

Page 464: Learn OLE DB Development With Visual C++ 6.0

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 here238. //@@@ 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 false250. 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. else260. {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. }

Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL � 445

Page 465: Learn OLE DB Development With Visual C++ 6.0

269. }270. //@@@ set the command ID @@@271. instructions->m_ID = sID;272. //@@@ set the command text @@@273. wsprintf( &instructions->m_Command[0], OLE2T( ccbstrCommand.Copy()) );274. //@@@ set the text @@@275. wsprintf( &instructions->m_Text[0], OLE2T( ccbstrText.Copy()) );276. //@@@ update the database @@@277. hr = instructions->Update();278. //@@@ signal error @@@279. if (!SUCCEEDED(hr))280. {281. return Error(_T(“Unable to Update Rowset after Changing Command!”));282. }283. //@@@ close the rowset @@@284. instructions->Close();285. //@@@ signal ok @@@286. return S_OK;287. }288.289. STDMETHODIMP CAgentcontroller::get_CommandID(short *pVal)290. {291. // TODO: Add your implementation code here292. //@@@ send back our member variable @@@293. *pVal = sID;294. return S_OK;295. }296.297. STDMETHODIMP CAgentcontroller::put_CommandID(short newVal)298. {299. // TODO: Add your implementation code here300. //@@@ set our member variable @@@301. sID = newVal;302. return S_OK;303. }304.305. STDMETHODIMP CAgentcontroller::get_CommandName(BSTR *pVal)306. {307. // TODO: Add your implementation code here308. //@@@ send back our member variable @@@309. *pVal = ccbstrCommand.Copy();310. return S_OK;311. }312.313. STDMETHODIMP CAgentcontroller::put_CommandName(BSTR newVal)314. {315. // TODO: Add your implementation code here

446 � Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL

Page 466: Learn OLE DB Development With Visual C++ 6.0

316. //@@@ set our member variable @@@317. ccbstrCommand = newVal;318. return S_OK;319. }320.321. STDMETHODIMP CAgentcontroller::get_CommandText(BSTR *pVal)322. {323. // TODO: Add your implementation code here324. //@@@ send back our member variable @@@325. *pVal = ccbstrText.Copy();326. return S_OK;327. }328.329. STDMETHODIMP CAgentcontroller::put_CommandText(BSTR newVal)330. {331. // TODO: Add your implementation code here332. //@@@ set our member variable @@@333. ccbstrText = newVal;334. return S_OK;335. }336.337. STDMETHODIMP CAgentcontroller::GetAllCommands(BSTR*bstrCommandsString)338. {339. // TODO: Add your implementation code here340. //@@@ bring in conversion macros @@@341. USES_CONVERSION;342. //@@@ define working buffer @@@343. CComBSTR ccbstrHolder(_T(“”));344. //@@@ number holder @@@345. TCHAR buf[51];346. //@@@ define HRESULT holder @@@347. HRESULT hr;348. //@@@ open the current rowset @@@349. hr = instructions->OpenRowset();350. //@@@ if fail return IDispatch Error @@@351. if (!SUCCEEDED(hr))352. {353. return Error(_T(“Unable to Open Rowset for Getting All Commands!”));354. }355. //@@@ read instructions one at a time @@@356. wsprintf( instructions->m_Text,"");357. while (instructions->MoveNext() == S_OK)358. {359. //@@@ put number into string buffer @@@360. itoa( instructions->m_ID, &buf[0] , 10);361. //@@@ put it into the holding string @@@362. ccbstrHolder += T2OLE( buf );

Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL � 447

Page 467: Learn OLE DB Development With Visual C++ 6.0

363. //@@@ add padding for display @@@364. ccbstrHolder += _T(“ ”);365. //@@@ add command string @@@366. ccbstrHolder += (BSTR)CComBSTR(instructions->m_Command);367. //@@@ add padding for display @@@368. ccbstrHolder += _T(“ ”);369. //@@@ put in text string @@@370. ccbstrHolder += (BSTR)CComBSTR(instructions->m_Text);371. //@@@ put in terminator @@@372. ccbstrHolder += _T(“*”);373. wsprintf( instructions->m_Text,"");374. }375. //@@@ kill the rowset @@@376. instructions->Close();377. //@@@ send back copy of working string @@@378. *bstrCommandsString = ccbstrHolder.Copy();379. //@@@ if we get here we’re okay @@@380. return S_OK;381. }

Listing 8-5 AGENTCONTROLLER.CP source code after modifications

The following entries explain what the new code lines do:

� Lines 9-44 This code handles the chores of creating the CInstructionsclass, and of obtaining the working interface pointer to the MicrosoftAgent control. Notice that it uses COM error handling if it encountersproblems.

� Lines 46-53 This code disposes of the three memory-using objects cre-ated in the FinalConstruct method if they contain valid pointers.

� Lines 72-129 This code illustrates using the OLE DB consumer to readdata from the database. First the Recordset is opened, then a loop is set upto read records until no more are found, generating a failure HRESULT(lines 83-84). Each successful read puts new data into the Recordset inthe instructions variable; its member variables are checked in a nestedif..else loop (lines 85-127). Each recognized directive is then sent to theAgent control, possibly with parsing for additional parameters (lines105-113). Once all commands are read, the Recordset is closed to avoiderrors later.

� Lines 136-176 This code handles inserting a new command. First itopens a Rowset (lines 137-146), then it calls the Insert method (lines147-153). Next it moves to the last record (lines 154-160), and then setsthe 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 simi-lar pattern, except for the specific operation performed.

448 � Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL

Page 468: Learn OLE DB Development With Visual C++ 6.0

� Lines 183-231 This code handles deleting a command. It uses thesame techniques described above.

� Lines 238-285 This code handles changing the data in a command. Ituses the same techniques described above.

� Lines 292-293, 308-309, 324-325 This code sets our member vari-ables to the passed-in data.

� Lines 300-301, 316-317, 332-333 This code sets the passed-inparameter to member var data.

� Lines 340-379 This code handles the important chore of getting all thedata 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 bedecoded later. Also notice in line 376 that the rowset is closed when weare done with it; if this is not done, ATLASSERT errors will happen in ourclient!

Building the OLE DB Consumer DLL

Once you are satisfied you’ve entered the code from the above listings cor-rectly, build the OLE DB COM server from the Build menu. If you encounterany errors, please check the code in the listing to find your mistake (it hasbeen exhaustively debugged and is correct). Once you have compiled thecomponent successfully it is automatically registered on the local machine; ifyou 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 theconsumer.

Creating a Client Application for an OLE DB ConsumerCreating a Client Application for an OLE DB Consumer

with MFCwith MFC

In previous chapters we implemented OLE DB consumers directly in MFCusing imported ATL classes. For this project, we will examine how to use anATL OLE DB consumer with an MFC client application. This involves treatingthe consumer as simply another COM server, connecting it with our userinterface and calling its methods. This scenario is a very common one forATL-based OLE DB consumer DLLs.

Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL � 449

Page 469: Learn OLE DB Development With Visual C++ 6.0

Step-By-Step

Start Visual C++ 6.0. From the File menu, select New. The New dialog boxopens; click on the MFC AppWizard (exe) entry. Next, select an appropriatedirectory, then enter a project name of AgentPlayer. Figure 8-33 shows howthe New dialog should appear when you are done.

In the first page of the MFC AppWizard dialog, make sure the Dialog basedradio button is selected, as indicated in Figure 8-34. Click on the Next buttonto move to the next page of the wizard.

450 � Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL

Figure 8-33

The New dia-log box creat-ing theAgentPlayerMFC project

Figure 8-34

Setting theMFC applica-tion projectbasicproperties

Page 470: Learn OLE DB Development With Visual C++ 6.0

On the second page of the MFC AppWizard, there are a number of optionsthat control the behavior of the application; leave everything at the defaultexcept to make sure that Automation support is checked. Figure 8-35 illus-trates how the dialog should appear when you are done. Click on the Nextbutton to move to the next page of the AppWizard.

On the third page of the MFC AppWizard, select the only available option forthe user interface (MFC Standard), and enable source file comments and useof MFC as a shared DLL (to save executable size). Figure 8-36 illustrates howthe dialog should appear when you are done. Click on the Next button tomove to the next page of the AppWizard.

Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL � 451

Figure 8-35

Setting theMFC applica-tion projectCOMproperties

Figure 8-36

Setting theMFC applica-tion projectuser interfaceproperties

Page 471: Learn OLE DB Development With Visual C++ 6.0

On the fourth page of the MFC AppWizard, you see the results of the choicesyou made in the previous dialogs. Figure 8-37 illustrates how the dialogshould appear when you are done. Click on the Finish button to create theMFC application.

A confirmation dialog box appears as shown in Figure 8-38; press OK to cre-ate the MFC project.

452 � Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL

Figure 8-38

The confirma-tion dialog

Figure 8-37

Confirmingthe MFCapplicationproject userinterfaceproperties

Page 472: Learn OLE DB Development With Visual C++ 6.0

Back in the Visual C++ IDE, bring up the workspace viewer, and select theResourceView tab. Expand the Dialogs entry and double-click on its only ele-ment, which is the main dialog resource for the application. Remove thepre-existing static text and button controls. Using Figure 8-39 as your guide,

lay out thegroup box,edit, list box,and buttoncontrols asshown on thefigure,accepting thedefault IDvalues gener-ated byVisual C++.Save the pro-ject toprotect yourwork.

Next, select the list box and right-click to bring up its context menu; selectthe 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 theotherchangesneeded.Using Figure8-40 as yourguide, enterthe variousCaption prop-erties shownfor the but-tons, groupboxes, andmain dialog.Unclick thepushpin andsave yourwork.

Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL � 453

Figure 8-40

Setting thevarious prop-erties of con-trols in theMFC ResourceEditor

Figure 8-39

The maindialog displayof the MFCapplication inthe ResourceEditor

Page 473: Learn OLE DB Development With Visual C++ 6.0

Now you need to set up a member variable to facilitate interaction with thelist box control. Hold down the Ctrl key and double-click on the list box con-

trol. The Add Member VariableWizard dialog appears as shown inFigure 8-41. Enterm_commandslb as the name ofthe member variable and set its cat-egory to Control (which willautomatically set its type toCListBox), and click on the OK but-ton. This will connect the userinterface of the list box with anMFC control, which is much easierto interact with.

To set up a member variable to facilitate interaction with the uppermost editcontrol, hold down the Ctrl key and double-click on the edit control. The AddMember Variable Wizard dialog appears. Enter m_idnumber as the name ofthe member variable and set its category to Value (which will automaticallyset its type to CString), and click on the OK button. This will connect the userinterface of the edit control with an MFC CString class.

To set up a member variable to facilitate interaction with the middle edit con-trol, hold down the Ctrl key and double-click on the edit control. The AddMember Variable Wizard dialog appears. Enter m_commandstring as thename of the member variable and set its category to Value (which will auto-matically set its type to CString), and click on the OK button. This willconnect the user interface of the edit control with an MFC CString class.

To set up a member variable to facilitate interaction with the bottommost editcontrol, hold down the Ctrl key and double-click on the edit control. The AddMember Variable Wizard dialog appears. Enter m_commandtext as thename of the member variable and set its category to Value (which will auto-matically set its type to CString), and click on the OK button. This willconnect 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 listbox control (which should transfer information to the edit controls). To dothis, hold down the Shift key and double-click on the list box control. TheMFC ClassWizard dialog appears; select the Message Maps tab, then the

IDC_LIST1 entry and theLBN_DBLCLICK entry, respec-tively, and then click on the AddFunction button. A dialog similarto that shown in Figure 8-42appears; accept the default

454 � Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL

Figure 8-42

The MFC AddMemberFunctiondialog

Figure 8-41

Adding amember vari-able controlfor the list boxin MFC

Page 474: Learn OLE DB Development With Visual C++ 6.0

name by clicking on the OKbutton. The ClassWizard dia-log should then look verysimilar to the one shown inFigure 8-43 (except it has adifferent list box ID value).Click on OK to accept the newevent handler and close theClassWizard dialog.

Now repeat the process foreach of the button controls:Select their BN_CLICKED mes-sage in the Message Maps tabof the ClassWizard dialog andclick on the Add Function but-ton; accept the default namesby clicking on the OK buttoneach time. When you aredone, the ClassWizard dialog’sMessage Maps tab should lookquite similar to that shown inFigure 8-44. Click on OK toaccept the changes. Save theproject.

Next you need to add a new member functionto the dialog box class. Display the workspaceif it is not already visible. Then select theClassView tab, and expand the tree controluntil the CAgentPlayerDlg entry is shown.Right-click on this entry as shown in Figure8-45, and select the Add Member Functionmenu option as indicated in the figure.

Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL � 455

Figure 8-44

The MFCClassWizardshowingadded eventhandlers forthe buttoncontrols

Figure 8-43

The MFCClassWizardshowing anadded eventhandler forthe list box

Figure 8-45

The MFCworkspacecontext menufor theCAgentPlayerDlg class

Page 475: Learn OLE DB Development With Visual C++ 6.0

The Add Member Function Wizard dia-log appears as illustrated in Figure8-46 Enter a Function Type of void anda Function Declaration of Update-Listbox Display() (be sure to typeexactly what is given, since the wizardsimply inserts the text directly into thesource code!). Click on the OK buttonto accept the remaining default set-tings and add the member function tothe dialog class.

As the final user interface step for this project, you need to add the OLE DBconsumer DLL you created in the previous section. To do this, selectView|ClassWizard from the menu bar. In ClassWizard, select the Automationtab, then click on the New Class button, and select From A Type Library in

the pop-up menu thatappears. The Import FromType Library dialog appears asshown in Figure 8-47. Navi-gate to the directory whereyou placed the ATLSimpleCproject files, and locate its TLBfile as shown in the figure.Click on Open to start addingthe new Automation classfrom the 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; thisclass will be added in the files shown at the bottom of the dialog to the cur-

rent project. Click on OK to add theOLE DB consumer support. Save theproject.

456 � Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL

Figure 8-47

Adding a newAutomationclass from aType Library

Figure 8-48

The IAgent-ControllerCOM inter-face classadded to theMFC project

Figure 8-46

Adding a newmember func-tion to theCAgentPlayer-Dlg class

Page 476: Learn OLE DB Development With Visual C++ 6.0

Line-By-Line

Now you are ready to enter the small amount of source code needed to con-nect 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 andenter or change any grayed line of source code in Listing 8-6. Save the pro-ject. The section after the code explains what these changes do.

1. // AgentPlayerDlg.h : header file2. //3.4. #if !defined(AFX_AGENTPLAYERDLG_H__367C4E0A_000C_11D3_B7EE_00E02916C424__

INCLUDED_)5. #define AFX_AGENTPLAYERDLG_H__367C4E0A_000C_11D3_B7EE_00E02916C424__INCLUDED_6.7. #if _MSC_VER > 10008. #pragma once9. #endif // _MSC_VER > 100010. //@@@ bring in the imported OLE DB Consumer DLL @@@11. #include “atlsimplec.h”12.13. class CAgentPlayerDlgAutoProxy;14.15. /////////////////////////////////////////////////////////////////////////////16. // CAgentPlayerDlg dialog17.18. class CAgentPlayerDlg : public CDialog19. {20. DECLARE_DYNAMIC(CAgentPlayerDlg);21. friend class CAgentPlayerDlgAutoProxy;22.23. // Construction24. public:25. void UpdateListboxDisplay();26. CAgentPlayerDlg(CWnd* pParent = NULL); // standard constructor27. virtual ~CAgentPlayerDlg();28. //@@@ member variable for the COM interface of the OLE DB Consumer @@@29. IAgentcontroller pAgentcontroller;30. // Dialog Data31. //{{AFX_DATA(CAgentPlayerDlg)32. enum { IDD = IDD_AGENTPLAYER_DIALOG };33. CListBox m_commandslb;34. CString m_idnumber;35. CString m_commandstring;36. CString m_commandtext;37. //}}AFX_DATA38.39. // ClassWizard generated virtual function overrides

Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL � 457

Page 477: Learn OLE DB Development With Visual C++ 6.0

40. //{{AFX_VIRTUAL(CAgentPlayerDlg)41. protected:42. virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support43. //}}AFX_VIRTUAL44.45. // Implementation46. protected:47. CAgentPlayerDlgAutoProxy* m_pAutoProxy;48. HICON m_hIcon;49.50. BOOL CanExit();51.52. // Generated message map functions53. //{{AFX_MSG(CAgentPlayerDlg)54. virtual BOOL OnInitDialog();55. afx_msg void OnSysCommand(UINT nID, LPARAM lParam);56. afx_msg void OnPaint();57. afx_msg HCURSOR OnQueryDragIcon();58. afx_msg void OnClose();59. virtual void OnOK();60. virtual void OnCancel();61. afx_msg void OnDblclkList2();62. afx_msg void OnButton1();63. afx_msg void OnButton2();64. afx_msg void OnButton3();65. afx_msg void OnButton4();66. afx_msg void OnButton5();67. afx_msg void OnButton6();68. //}}AFX_MSG69. DECLARE_MESSAGE_MAP()70. };71.72. //{{AFX_INSERT_LOCATION}}73. // Microsoft Visual C++ will insert additional declarations immediately before

the74. previous line.75.76. #endif //77. !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:

� Lines 10-11 This code makes the ATL OLE DB consumer available viathe class file created by the ClassWizard in the previous steps.

� Lines 28-29 This code declares a member variable to hold the OLE DBConsumer class for use by the MFC application.

458 � Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL

Page 478: Learn OLE DB Development With Visual C++ 6.0

Next bring up the AGENTPLAYERDLG.CPP file in the text editor. Scanthrough 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. // AgentPlayerDlg.cpp : implementation file2. //3.4. #include “stdafx.h”5. #include “AgentPlayer.h”6. #include “AgentPlayerDlg.h”7. #include “DlgProxy.h”8. #ifdef _DEBUG9. #define new DEBUG_NEW10. #undef THIS_FILE11. static char THIS_FILE[] = __FILE__;12. #endif13.14. /////////////////////////////////////////////////////////////////////////////15. // CAboutDlg dialog used for App About16.17. class CAboutDlg : public CDialog18. {19. public:20. CAboutDlg();21.22. // Dialog Data23. //{{AFX_DATA(CAboutDlg)24. enum { IDD = IDD_ABOUTBOX };25. //}}AFX_DATA26.27. // ClassWizard generated virtual function overrides28. //{{AFX_VIRTUAL(CAboutDlg)29. protected:30. virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support31. //}}AFX_VIRTUAL32.33. // Implementation34. protected:35. //{{AFX_MSG(CAboutDlg)36. //}}AFX_MSG37. DECLARE_MESSAGE_MAP()38. };39.40. CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)41. {42. //{{AFX_DATA_INIT(CAboutDlg)

Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL � 459

Page 479: Learn OLE DB Development With Visual C++ 6.0

43. //}}AFX_DATA_INIT44. }45.46. void CAboutDlg::DoDataExchange(CDataExchange* pDX)47. {48. CDialog::DoDataExchange(pDX);49. //{{AFX_DATA_MAP(CAboutDlg)50. //}}AFX_DATA_MAP51. }52.53. BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)54. //{{AFX_MSG_MAP(CAboutDlg)55. // No message handlers56. //}}AFX_MSG_MAP57. END_MESSAGE_MAP()58.59. /////////////////////////////////////////////////////////////////////////////60. // CAgentPlayerDlg dialog61.62. IMPLEMENT_DYNAMIC(CAgentPlayerDlg, CDialog);63.64. CAgentPlayerDlg::CAgentPlayerDlg(CWnd* pParent /*=NULL*/)65. : CDialog(CAgentPlayerDlg::IDD, pParent)66. {67. //{{AFX_DATA_INIT(CAgentPlayerDlg)68. m_idnumber = _T(“”);69. m_commandstring = _T(“”);70. m_commandtext = _T(“”);71. //}}AFX_DATA_INIT72. // Note that LoadIcon does not require a subsequent DestroyIcon in Win3273. m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);74. m_pAutoProxy = NULL;75. }76.77. CAgentPlayerDlg::~CAgentPlayerDlg()78. {79. // If there is an automation proxy for this dialog, set80. // its back pointer to this dialog to NULL, so it knows81. // the dialog has been deleted.82. if (m_pAutoProxy != NULL)83. m_pAutoProxy->m_pDialog = NULL;84. }85.86. void CAgentPlayerDlg::DoDataExchange(CDataExchange* pDX)87. {88. CDialog::DoDataExchange(pDX);89. //{{AFX_DATA_MAP(CAgentPlayerDlg)

460 � Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL

Page 480: Learn OLE DB Development With Visual C++ 6.0

90. DDX_control(pDX, IDC_LIST2, m_commandslb);91. DDX_Text(pDX, IDC_EDIT1, m_idnumber);92. DDX_Text(pDX, IDC_EDIT2, m_commandstring);93. DDX_Text(pDX, IDC_EDIT3, m_commandtext);94. //}}AFX_DATA_MAP95. }96.97. BEGIN_MESSAGE_MAP(CAgentPlayerDlg, CDialog)98. //{{AFX_MSG_MAP(CAgentPlayerDlg)99. ON_WM_SYSCOMMAND()100. ON_WM_PAINT()101. ON_WM_QUERYDRAGICON()102. ON_WM_CLOSE()103. ON_LBN_DBLCLK(IDC_LIST2, OnDblclkList2)104. ON_BN_CLICKED(IDC_BUTTON1, OnButton1)105. ON_BN_CLICKED(IDC_BUTTON2, OnButton2)106. ON_BN_CLICKED(IDC_BUTTON3, OnButton3)107. ON_BN_CLICKED(IDC_BUTTON4, OnButton4)108. ON_BN_CLICKED(IDC_BUTTON5, OnButton5)109. ON_BN_CLICKED(IDC_BUTTON6, OnButton6)110. //}}AFX_MSG_MAP111. END_MESSAGE_MAP()112.113. /////////////////////////////////////////////////////////////////////////////114. // CAgentPlayerDlg message handlers115.116. BOOL CAgentPlayerDlg::OnInitDialog()117. {118. CDialog::OnInitDialog();119.120. // Add “About...” menu item to system menu.121.122. // IDM_ABOUTBOX must be in the system command range.123. ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);124. ASSERT(IDM_ABOUTBOX < 0xF000);125.126. CMenu* pSysMenu = GetSystemMenu(FALSE);127. if (pSysMenu != NULL)128. {129. CString strAboutMenu;130. strAboutMenu.LoadString(IDS_ABOUTBOX);131. if (!strAboutMenu.IsEmpty())132. {133. pSysMenu->AppendMenu(MF_SEPARATOR);134. pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);135. }136. }

Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL � 461

Page 481: Learn OLE DB Development With Visual C++ 6.0

137.138. // Set the icon for this dialog. The framework does this automatically139. // when the application’s main window is not a dialog140. SetIcon(m_hIcon, TRUE); // Set big icon141. SetIcon(m_hIcon, FALSE); // Set small icon142.143. // TODO: Add extra initialization here144. //@@@ get the COM interface for the OLE DB Consumer via its ProgID @@@145. pAgentcontroller.CreateDispatch(“ATLSimpleC.Agentcontroller.1");146. //@@@ handle error @@@147. if (pAgentcontroller.m_lpDispatch == NULL)148. {149. AfxMessageBox(“Unable To Connect With Agentcontroller OLEDB server”);150. return FALSE;151. }152. return TRUE; // return TRUE unless you set the focus to a control153. }154.155. void CAgentPlayerDlg::OnSysCommand(UINT nID, LPARAM lParam)156. {157. if ((nID & 0xFFF0) == IDM_ABOUTBOX)158. {159. CAboutDlg dlgAbout;160. dlgAbout.DoModal();161. }162. else163. {164. CDialog::OnSysCommand(nID, lParam);165. }166. }167.168. // If you add a minimize button to your dialog, you will need the code below169. // to draw the icon. For MFC applications using the document/view model,170. // this is automatically done for you by the framework.171.172. void CAgentPlayerDlg::OnPaint()173. {174. if (IsIconic())175. {176. CPaintDC dc(this); // device context for painting177.178. SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);179.180. // Center icon in client rectangle181. int cxIcon = GetSystemMetrics(SM_CXICON);182. int cyIcon = GetSystemMetrics(SM_CYICON);183. CRect rect;

462 � Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL

Page 482: Learn OLE DB Development With Visual C++ 6.0

184. GetclientRect(&rect);185. int x = (rect.Width() - cxIcon + 1) / 2;186. int y = (rect.Height() - cyIcon + 1) / 2;187.188. // Draw the icon189. dc.DrawIcon(x, y, m_hIcon);190. }191. else192. {193. CDialog::OnPaint();194. }195. }196.197. // The system calls this to obtain the cursor to display while the user drags198. // the minimized window.199. HCURSOR CAgentPlayerDlg::OnQueryDragIcon()200. {201. return (HCURSOR) m_hIcon;202. }203.204. // Automation servers should not exit when a user closes the UI205. // if a controller still holds on to one of its objects. These206. // message handlers make sure that if the proxy is still in use,207. // then the UI is hidden but the dialog remains around if it208. // is dismissed.209.210. void CAgentPlayerDlg::OnClose()211. {212. pAgentcontroller.ReleaseDispatch();//@@@ shut down correctly @@@213. PostQuitMessage(0);214. DestroyWindow();215. }216.217. void CAgentPlayerDlg::OnOK()218. {219. if (CanExit())220. CDialog::OnOK();221. }222.223. void CAgentPlayerDlg::OnCancel()224. {225. if (CanExit())226. CDialog::OnCancel();227. }228.229. BOOL CAgentPlayerDlg::CanExit()230. {

Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL � 463

Page 483: Learn OLE DB Development With Visual C++ 6.0

231. // If the proxy object is still around, then the automation232. // controller is still holding on to this application. Leave233. // 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 here246. //@@@ 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.

464 � Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL

Page 484: Learn OLE DB Development With Visual C++ 6.0

278. }279.280. void CAgentPlayerDlg::OnButton1()281. {282. // TODO: Add your control notification handler code here283. //@@@ do our internal update call (it handles errors so we don’t) @@@284. UpdateListboxDisplay();285. }286.287. void CAgentPlayerDlg::OnButton2()288. {289. // TODO: Add your control notification handler code here290. //@@@ error handling block @@@291. try292. {293. //@@@ invoke execute handler of contained server @@@294. pAgentcontroller.ExecuteCommands();295. }296. catch (COleDispatchException e) //@@@ handle OLE exception @@@297. {298. //@@@ display OLE error message @@@299. AfxMessageBox( e.m_strDescription );300. }301. }302.303. void CAgentPlayerDlg::OnButton3()304. {305. // TODO: Add your control notification handler code here306. //@@@ get the data from the controls @@@307. UpdateData(TRUE);308. //@@@ error handling block @@@309. try310. {311. //@@@ set the contained server properties from the member vars @@@312. pAgentcontroller.SetCommandID( atoi( m_idnumber ) );313. pAgentcontroller.SetCommandName( m_commandstring );314. pAgentcontroller.SetCommandText( m_commandtext );315. //@@@ invoke the add function on the contained server @@@316. pAgentcontroller.AddCommand();317. //@@@ redo display @@@318. UpdateListboxDisplay();319. }320. catch (COleDispatchException e) //@@@ handle OLE exception @@@321. {322. //@@@ display OLE error message @@@323. AfxMessageBox( e.m_strDescription );324. }

Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL � 465

Page 485: Learn OLE DB Development With Visual C++ 6.0

325. }326.327. void CAgentPlayerDlg::OnButton4()328. {329. // TODO: Add your control notification handler code here330. //@@@ get the data from the controls @@@331. UpdateData(TRUE);332. //@@@ error handling block @@@333. try334. {335. //@@@ set the contained server properties from the member vars @@@336. pAgentcontroller.SetCommandID( atoi( m_idnumber ) );337. pAgentcontroller.SetCommandName( m_commandstring );338. pAgentcontroller.SetCommandText( m_commandtext );339. //@@@ invoke the delete function on the contained server @@@340. pAgentcontroller.DeleteCommand();341. //@@@ redo display @@@342. UpdateListboxDisplay();343. }344. catch (COleDispatchException e) //@@@ handle OLE exception @@@345. {346. //@@@ display OLE error message @@@347. AfxMessageBox( e.m_strDescription );348. }349. }350.351. void CAgentPlayerDlg::OnButton5()352. {353. // TODO: Add your control notification handler code here354. //@@@ get the data from the controls @@@355. UpdateData(TRUE);356. //@@@ error handling block @@@357. try358. {359. //@@@ set the contained server properties from the member vars @@@360. pAgentcontroller.SetCommandID( atoi( m_idnumber ) );361. pAgentcontroller.SetCommandName( m_commandstring );362. pAgentcontroller.SetCommandText( m_commandtext );363. //@@@ invoke the change function on the contained server @@@364. pAgentcontroller.ChangeCommand();365. //@@@ redo display @@@366. UpdateListboxDisplay();367. }368. catch (COleDispatchException e) //@@@ handle OLE exception @@@369. {370. //@@@ display OLE error message @@@371. AfxMessageBox( e.m_strDescription );

466 � Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL

Page 486: Learn OLE DB Development With Visual C++ 6.0

372. }373. }374.375. void CAgentPlayerDlg::OnButton6()376. {377. // TODO: Add your control notification handler code here378. pAgentcontroller.ReleaseDispatch();//@@@ shut down correctly @@@379. PostQuitMessage(0);380. DestroyWindow();381.382. }383.384. void CAgentPlayerDlg::UpdateListboxDisplay()385. {386. //@@@ define strings for display manipulation @@@387. CString strHolder = _T(“ ”);388. CString strWorking;389. //@@@ define position holder @@@390. int iPos;391. //@@@ define BSTR to get from server @@@392. BSTR bstrHolder = strHolder.AllocSysString();393. //@@@ clear the list box if needed @@@394. while (m_commandslb.GetCount() > 0) m_commandslb.DeleteString ( 0 );395. //@@@ error handler @@@396. try397. {398. //@@@ get the long string with all commands in it as text separated by * @@@399. pAgentcontroller.GetAllCommands( &bstrHolder );400. //@@@ move the BSTR into the CString @@@401. strHolder = bstrHolder;402. //@@@ define boolean holder @@@403. BOOL bFlag;404. //@@@ set it to false @@@405. bFlag = FALSE;406. //@@@ loop till we die @@@407. while (!bFlag)408. {409. //@@@ get next instance of *410. iPos = strHolder.Find(“*”,0);411. //@@@ if out of * then done since last entry has one @@@412. if (iPos < 1 ) return;413. //@@@ pull out the entry to the left @@@414. strWorking = strHolder.Left(iPos );415. //@@@ move working string to the right @@@416. strHolder = strHolder.Mid( iPos + 1 );417. //@@@ add it to the list box @@@418. m_commandslb.AddString( strWorking );

Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL � 467

Page 487: Learn OLE DB Development With Visual C++ 6.0

419. }420. }421. catch (COleDispatchException e) //@@@ handle OLE Exception @@@422. {423. //@@@ show user OLE error string @@@424. AfxMessageBox( e.m_strDescription );425. }426. }

Listing 8-7 AGENTPLAYERDLG.CPP source code after modifications

The following entries explain what the new code lines do:

� Lines 144-151 This code handles obtaining an interface pointer to theOLE DB consumer COM server. Notice that if it fails it aborts creation ofthe main dialog box.

� Lines 213-214 This code closes the application properly by telling theMFC code to quit and destroying the displayed dialog.

� Lines 246-276 This code handles the tricky but essential job of updat-ing the three edit controls from the list box data. First, the string from theselected line of the list box is obtained (lines 246-256), then it is scannedfor spaces (lines 257-260) and the first data segment pulled out and putinto the member variable for the edit control (lines 261-264). The processis repeated two more times to get the remaining data. Finally, MFC is toldto update the display controls (line 276).

� Lines 283-284 This code just calls our internal update method for thelist box.

� Lines 290-300 This code handles the basic task of calling the OLE DBconsumer with a request to play what is in the database through theAgent control. It handles any problem by simply displaying the error mes-sage from the OLE exception thrown by the OLE DB consumer (since weset it to use COM error handling).

� Lines 306-324 This code is one of three virtually identical blocks thattrigger database updates via the OLE DB consumer (this one adds a newcommand). First, the data is moved from the edit controls into the mem-ber variables (lines 306-307). Then the variables’ data is placed into theproperties of the OLE DB consumer member variable interface pointer(lines 311-314). Finally, the appropriate method is called to manipulatethe database (lines 315-316). The list box display is then automaticallyupdated (lines 317-318). If any problem occurs, an OLE exception isthrown by the OLE DB consumer via its COM error handler, and takencare of by simply reporting the error message to the user (lines 320-324).

� Lines 330-348 This code repeats the database manipulation operationdescribed above to delete a command.

468 � Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL

Page 488: Learn OLE DB Development With Visual C++ 6.0

� Lines 354-372 This code repeats the database manipulation operationdescribed above to change the contents of a command.

� Lines 378-380 This code deals with exiting via the button controlrather than the close button; it works identically to the previous closurecode.

� Lines 386-425 This code does the really rather difficult operation ofclearing the list box control and updating it with the latest set of com-mands from the database via the OLE DB consumer. First, a manualclearing of the list box is done (since MFC, rather strangely, doesn’t havethis method supported) (lines 393-394). Then the OLE DB consumer iscalled to obtain the BSTR containing the complete commands list (lines398-399). This string is then taken apart in a loop which terminates whenno 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 listbox (lines 417-418). If an error occurs, the user is informed (lines422-424).

Building the MFC OLE DB Consumer Application

Once you are satisfied you’ve entered the code from the above listings cor-rectly, build the OLE DB consumer test application from the Build menu. Ifyou encounter any errors, please check the code in the listing to find yourmistake (it has been exhaustively debugged and is correct). Now you areready 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. Thiswill show you an amusing visual display, but also demonstrate how OLE DBconsumers work.

Step-By-Step

Start the AGENTPLAYER application from inside Visual C++ or from theExplorer. When the dialog appears, click on the Update Listbox From Data-base button; the list box should fill with the list of commands you placed inthe database earlier in this chapter. Now click on the Execute CommandsWith Agent button. As shown in Figure 8-49, the Merlin Agent character willappear, moving around and speaking several statements about OLE DB.

Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL � 469

Page 489: Learn OLE DB Development With Visual C++ 6.0

Experiment with deleting, adding, and changing commands (only use theones shown because our consumer doesn’t support any others!). You’ll findyour changes persist even between sessions because they are stored in thedatabase by the OLE DB consumer.

Where We Go from HereWhere We Go from Here

You now have successfully created two OLE DB provider applications and anOLE DB consumer as well, using ATL templates along with various hand-builtATL and MFC programs to support them. There is one major arena whereOLE DB will be used that still needs exploration: MFC MDI (Multiple Docu-ment Interface) applications. Chapter 9 provides you with a completelyworked out MFC MDI application to use OLE DB consumer templates tolocate and examine OLE DB provider schema information.

470 � Chapter Eight—Creating and Using Simple OLE DB Consumers in ATL

Figure 8-49

The MFCapplicationdisplays thedatabase viaMicrosoftAgent fromthe OLE DBconsumer

Page 490: Learn OLE DB Development With Visual C++ 6.0

Chapter Nine

Creating and Using ComplexCreating and Using Complex

OLE DB Consumers in MFCOLE 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 sup-ported by an MFC client. In Chapter 8, you learned how to take advantage ofthe template functionality of an ATL OLE DB consumer to create a simple butvery powerful COM server that can function in an application to connect toan Access database and drive the Microsoft Agent ActiveX control. Whenyou’ve finished this chapter, you’ll have created a powerful MDI (MultipleDocument Interface) OLE DB consumer written in MFC.

OLE DB Consumer Templates in MFCOLE DB Consumer Templates in MFC

OLE DB support in Visual C++ is currently entirely within ATL. Fortunately, itis quite easy to include ATL capabilities in an MFC application! As the follow-ing code will demonstrate, you can simply include the appropriate headerfile, 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 withCreating an OLE DB Consumer MDI Application with

MFC

This chapter’s work entails a different approach to OLE DB consumer pro-gramming: namely, merging it with the MFC MDI system. This requiresessentially three steps: designing the MDI application itself using theAppWizard, adding the OLE DB code using ATL templates, and connectingthe data from the database with the MFC user interface. Except for the designprocess, all the action takes place in added code.

471

Page 491: Learn OLE DB Development With Visual C++ 6.0

Step-By-Step

Start Visual C++ 6.0. From the File menu, select New. The New dialog boxopens; click on the MFC AppWizard (exe) entry. Next, select an appropriatedirectory, then enter a project name of SchemaDB. Figure 9-1 shows howthe New dialog should appear when you are done.

In the first page of the MFC AppWizard dialog, make sure the Multiple Docu-ments radio button is selected, and that Document/View Architecture isenabled, as indicated in Figure 9-2. Click on the Next button to move to thenext page of the wizard.

472 � Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC

Figure 9-1

The Newdialog boxcreating theSchemaDBMFC project

Figure 9-2

Setting theMFC applica-tion projectbasicproperties

Page 492: Learn OLE DB Development With Visual C++ 6.0

On the second page of theMFC AppWizard, you get toset the MFC database sup-port for the application;since OLE DB is not yet sup-ported by MFC, leave theNone radio button selectedas indicated in Figure 9-3.Click on the Next button tomove to the next page of theAppWizard.

On the third page of theMFC AppWizard, you get toselect how your MFC appli-cation interacts with theOLE compound documentsystem; since we aren’timplementing a compounddocument, make sure theNone radio button isselected. Since we will beusing Automation for ourOLE DB consumer, makesure the Automation checkbox is checked at the bottomof the wizard dialog. Figure9-4 illustrates how the dia-log should appear when youare done. Click on the Nextbutton to move to the nextpage of the AppWizard.

On the fourth page of theMFC AppWizard, you selectthe various user interfacefeatures you want for theMFC application. Leave allthe default settingsunchanged; Figure 9-5shows how the dialogshould look. Click on theAdvanced button next.

Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC � 473

Figure 9-5

Setting theMFC applica-tion projectuser interfaceproperties

Figure 9-4

Setting theMFC applica-tion projectcompounddocumentproperties

Figure 9-3

Setting theMFC applica-tion projectMFC databaseproperties

Page 493: Learn OLE DB Development With Visual C++ 6.0

The Advanced Options dialog appears;enter a File Extension of dbs. Leave allthe other settings unchanged as shown inFigure 9-6. Click on Close to return to themain AppWizard dialog. Then click on theNext button to move to the fifth page ofthe wizard.

On the fifth page of the MFCAppWizard, you get to selectfrom the various UI choicesfor the project; make surethe MFC Standard radio but-ton is selected and leave theother radio buttons at theirdefault settings. Figure 9-7illustrates how the dialogshould appear when you aredone. Click on the Next but-ton to move to the next pageof the AppWizard.

On the sixth page of theMFC AppWizard, you see theresults of the choices youmade in the previousdialogs. Figure 9-8 illus-trates how the dialog shouldappear when you are done.Click on the Finish button tocreate the MFC application.

474 � Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC

Figure 9-7

Setting theMFC applica-tion projectuser interfaceproperties

Figure 9-8

The MFCapplicationproject sum-mary dialog

Figure 9-6

Setting theMFC applica-tion projectadvancedproperties

Page 494: Learn OLE DB Development With Visual C++ 6.0

A confirmation dialog boxappears as shown in Figure 9-9;press OK to create the MFCproject.

Back in the VisualC++ IDE, bring up theworkspace viewer, andselect theResourceView tab.Expand the Dialogsentry and right-clickon it to bring up itscontext menu. Selectthe Insert Dialog menuoption as shown inFigure 9-10. Repeatthe process a secondtime to add anotherdialog to the project.Save the project toprotect your work.

Bring up the second dialog youadded in the Resource Editor. Holddown the Ctrl key and double-clickon the dialog box; the ClassWizarddialog appears, and then a modaldialog shown in Figure 9-11. ThisAdding a Class dialog allows you toeither create a new class for the

Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC � 475

Figure 9-11

UsingClassWizardto add a classfor the dialogboxes

Figure 9-10

Adding dialogboxes to theMFC applica-tion via theResourceEditor

Figure 9-9

The confirma-tion dialog

Page 495: Learn OLE DB Development With Visual C++ 6.0

dialog or use an existing one. Select the Create a new class radio button asshown in the figure and click on OK.

The New Class Wizard dialogappears as shown in Figure 9-12.Enter a Name of colpage and leavethe remaining settings unchanged.Click on OK to add the new class forthe dialog box.

The ClassWizard dialog nowappears again, this time on theClass Info tab showing theadded class. Click on OK to addthe new class. Repeat the previ-ous set of steps for the firstdialog you added, giving itsclass the name of tablepag.Save the project.

Back in the Visual C++ IDE, bring upthe workspace viewer, and select theResourceView tab. Expand the Dialogsentry and double-click on the first dia-log you added. Remove thepre-existing button controls. UsingFigure 9-14 as your guide, lay out thethree check box controls as shown inthe figure, accepting the default IDvalues generated by Visual C++. Givethem and the dialog itself the captionsindicated in the figure using the Prop-erties dialog. Save the project toprotect your work.

476 � Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC

Figure 9-13

ClassWizardshowing thenew dialogclass

Figure 9-14

The Tablesdialog displayof the MFCapplication inthe ResourceEditor

Figure 9-12

Using theNew ClassWizard to adda class for thedialog boxes

Page 496: Learn OLE DB Development With Visual C++ 6.0

In the Dialogs entry of the ResourceView of the workspace, double-clickon the second dialog you added.Remove the pre-existing button con-trols. Using Figure 9-15 as yourguide, lay out the three check boxcontrols as shown in the figure,accepting the default ID values gener-ated by Visual C++. Give them andthe dialog itself the captions indi-cated in the figure using theProperties dialog. Save the project toprotect your work.

Now you need to set up a member variable to facilitate interaction with theuppermost check box control of the Column Data dialog. Hold down the Ctrl

key and double-click on the top checkbox control. The Add Member VariableWizard dialog appears as shown inFigure 9-16. Enter m_bLength as thename of the member variable and setits category to Value; this will set theVariable type to BOOL automatically.Click on the OK button. This will con-nect the user interface of the checkbox with an MFC member variable,which is much easier to interact with!

Now you need to set up a member variable to facilitate interaction with themiddle check box control of the Column Data dialog. Hold down the Ctrl keyand double-click on the middle check box control. The Add Member VariableWizard dialog appears. Enter m_bPrecision as the name of the membervariable and set its category to Value; this will set the Variable type to BOOLautomatically. Click on the OK button. This will connect the user interface ofthe check box with an MFC member variable.

Set up a member variable to facilitate interaction with the bottommost checkbox control of the Column Data dialog by holding down the Ctrl key and dou-ble-clicking on the bottom check box control. The Add Member VariableWizard dialog appears. Enter m_bNullability as the name of the membervariable and set its category to Value; this will set the Variable type to BOOLautomatically. Click on the OK button. This will connect the user interface ofthe check box with an MFC member variable.

Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC � 477

Figure 9-16

Adding amember vari-able for acheck box inMFC

Figure 9-15

The ColumnData dialogdisplay of theMFC applica-tion in theResourceEditor

Page 497: Learn OLE DB Development With Visual C++ 6.0

Set up a member variable to facilitate interaction with the uppermost checkbox control of the Tables dialog, hold down the Ctrl key and double-click onthe top check box control. The Add Member Variable Wizard dialog appears.Enter m_bSystemTables as the name of the member variable and set itscategory to Value; this will set the Variable type to BOOL automatically. Clickon the OK button.

To set up a member variable to facilitate interaction with the middle checkbox control of the tables dialog, hold down the Ctrl key and double-click onthe middle check box control. The Add Member Variable Wizard dialogappears. Enter m_bViews as the name of the member variable and set itscategory to Value; this will set the Variable type to BOOL automatically. Clickon the OK button.

To set up a member variable to facilitate interaction with the bottommostcheck box control of the tables dialog, hold down the Ctrl key and dou-ble-click on the bottom check box control. The Add Member Variable Wizarddialog appears. Enter m_bSynonyms as the name of the member variableand set its category to Value; this will set the Variable type to BOOL automati-cally. Click on the OK button.

Next, you need to manipulate the default menu items MFC’s AppWizard cre-ated. To do this, activate the ResourceView tab of the workspace and openthe Menu entry. Select the IDR_MAINFRAME entry, and click on the Filemenu item. Remove all elements except the Open and Exit commands. Next,

478 � Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC

Figure 9-17

The MFCMenu Editorshowing thenew Viewmenu

Page 498: Learn OLE DB Development With Visual C++ 6.0

select the View menu item. Add and delete items from the menu until it is thesame as the one illustrated in Figure 9-17. Save the project.

Next, you need to add event handlers for the menu items. To do this, displaythe ClassWizard dialog and select the identifier for the File|Open menu com-

mand, then select its COMMANDevent. Click on the Add Function but-ton, and you’ll see the dialog shownin Figure 9-18. Click on OK to addthis event handler to the project.

As your lastuser interfacestep, repeat theprocess for theother menucommands youadded. Figure9-19 showshow the dialogshould lookwhen you aredone. Click onOK to add thenew event han-dlers. Save theproject.

Line-By-Line

Now you are ready to enter the source code needed to connect the user inter-face with the OLE DB consumer templates and databases. Bring up theSCHEMADBDOC.H file in the text editor. Scan through it and enter or changeany grayed line of source code in Listing 9-1. Save the project. The sectionafter the code explains what these changes do.

1. // SchemaDBDoc.h : interface of the CSchemaDBDoc class2. //3. /////////////////////////////////////////////////////////////////////////////4.5. #if !defined(AFX_SCHEMADBDOC_H__D9C40DD1_0339_11D3_B7EE_00E02916C424__INCLUDED_)6. #define AFX_SCHEMADBDOC_H__D9C40DD1_0339_11D3_B7EE_00E02916C424__INCLUDED_7.8. #if _MSC_VER > 1000

Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC � 479

Figure 9-19

The MFCClassWizardshowing addedmenu itemevent handlers

Figure 9-18

The MFCevent handlerdialog

Page 499: Learn OLE DB Development With Visual C++ 6.0

9. #pragma once10. #endif // _MSC_VER > 100011.12.13. class CSchemaDBDoc : public CDocument14. {15. protected: // create from serialization only16. CSchemaDBDoc();17. DECLARE_DYNCREATE(CSchemaDBDoc)18.19. // Attributes20. public:21. CString m_strConnect;22. // Database Connection23. CDataSource m_source;24. CSession m_session;25.26. // Table Information27. CTables* m_pTableset;28. BOOL m_bSystemTables;29. BOOL m_bViews;30. BOOL m_bSynonyms;31.32. // Column Information33. CColumns* m_pColumnset;34. BOOL m_bLength;35. BOOL m_bPrecision;36. BOOL m_bNullability;37.38. // Level Information39. enum Level40. {41. levelNone,42. levelTable,43. levelColumn44. };45.46. Level m_nLevel;47. CString m_strTableName;48.49. // Operations50. public:51. void SetLevel(Level nLevel);52. CString GetDSN();53. void FetchColumnInfo(LPCSTR lpszName);54. BOOL FetchTableInfo();55.

480 � Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC

Page 500: Learn OLE DB Development With Visual C++ 6.0

56. // Overrides57. // ClassWizard generated virtual function overrides58. //{{AFX_VIRTUAL(CSchemaDBDoc)59. public:60. virtual BOOL OnNewDocument();61. virtual void Serialize(CArchive& ar);62. virtual BOOL OnOpenDocument();63. virtual void OnCloseDocument();64. //}}AFX_VIRTUAL65.66. // Implementation67. public:68. virtual ~CSchemaDBDoc();69. #ifdef _DEBUG70. virtual void AssertValid() const;71. virtual void Dump(CDumpContext& dc) const;72. #endif73.74. protected:75.76. // Generated message map functions77. protected:78. //{{AFX_MSG(CSchemaDBDoc)79. // NOTE - the ClassWizard will add and remove member functions here.80. // DO NOT EDIT what you see in these blocks of generated code !81. afx_msg void OnSettings();82. //}}AFX_MSG83. DECLARE_MESSAGE_MAP()84.85. // Generated OLE dispatch map functions86. //{{AFX_DISPATCH(CSchemaDBDoc)87. // NOTE - the ClassWizard will add and remove member functions here.88. // DO NOT EDIT what you see in these blocks of generated code !89. //}}AFX_DISPATCH90. DECLARE_DISPATCH_MAP()91. DECLARE_INTERFACE_MAP()92. };93.94. /////////////////////////////////////////////////////////////////////////////95.96. //{{AFX_INSERT_LOCATION}}97. // Microsoft Visual C++ will insert additional declarations immediately before

the98. previous line.99.

Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC � 481

Page 501: Learn OLE DB Development With Visual C++ 6.0

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:

� Lines 21-47 This code declares the various member variables neededfor the OLE DB consumer and its data.

� Lines 51-54 This code declares the helper functions we’ll use to inter-act with the OLE DB consumer.

Next bring up the SCHEMADBDOC.CPP file in the text editor. Scan through itand enter or change any grayed line of source code in Listing 9-2. Save theproject. The section after the code explains what these changes do.

1. // SchemaDBDoc.cpp : implementation of the CSchemaDBDoc class2. //3.4. #include “stdafx.h”5. #include “SchemaDB.h”6.7. #include “SchemaDBDoc.h”8. #include “tablepag.h”9. #include “colpage.h”10.11. #ifdef _DEBUG12. #define new DEBUG_NEW13. #undef THIS_FILE14. static char THIS_FILE[] = __FILE__;15. #endif16.17. /////////////////////////////////////////////////////////////////////////////18. // CSchemaDBDoc19.20. IMPLEMENT_DYNCREATE(CSchemaDBDoc, CDocument)21.22. BEGIN_MESSAGE_MAP(CSchemaDBDoc, CDocument)23. //{{AFX_MSG_MAP(CSchemaDBDoc)24. // NOTE - the ClassWizard will add and remove mapping macros here.25. // DO NOT EDIT what you see in these blocks of generated code!26. ON_COMMAND(IDM_SETTINGS, OnSettings)27. //}}AFX_MSG_MAP28. END_MESSAGE_MAP()29.30. BEGIN_DISPATCH_MAP(CSchemaDBDoc, CDocument)31. //{{AFX_DISPATCH_MAP(CSchemaDBDoc)

482 � Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC

Page 502: Learn OLE DB Development With Visual C++ 6.0

32. // NOTE - the ClassWizard will add and remove mapping macros here.33. // DO NOT EDIT what you see in these blocks of generated code!34. //}}AFX_DISPATCH_MAP35. END_DISPATCH_MAP()36.37. // Note: we add support for IID_ISchemaDB to support typesafe binding38. // from VBA. This IID must match the GUID that is attached to the39. // dispinterface in the .ODL file.40.41. // {D9C40DC5-0339-11D3-B7EE-00E02916C424}42. static const IID IID_ISchemaDB =43. { 0xd9c40dc5, 0x339, 0x11d3, { 0xb7, 0xee, 0x0, 0xe0, 0x29, 0x16, 0xc4, 0x24 } };44.45. BEGIN_INTERFACE_MAP(CSchemaDBDoc, CDocument)46. INTERFACE_PART(CSchemaDBDoc, IID_ISchemaDB, Dispatch)47. END_INTERFACE_MAP()48.49. /////////////////////////////////////////////////////////////////////////////50. // CSchemaDBDoc construction/destruction51.52. CSchemaDBDoc::CSchemaDBDoc()53. {54. // TODO: add one-time construction code here55. m_pTableset = NULL;56. m_pColumnset = NULL;57. m_strTableName = _T(“”);58.59. EnableAutomation();60.61. AfxOleLockApp();62. }63.64. CSchemaDBDoc::~CSchemaDBDoc()65. {66. AfxOleUnlockApp();67. }68.69. BOOL CSchemaDBDoc::OnNewDocument()70. {71. if (!CDocument::OnNewDocument())72. return FALSE;73.74. // TODO: add reinitialization code here75. // (SDI documents will reuse this document)76. // initialize current view level77. m_nLevel = levelNone;78.

Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC � 483

Page 503: Learn OLE DB Development With Visual C++ 6.0

79. // initialize table settings80. m_bSystemTables = GetProfileValue(_T(“TableSettings”), _T(“SystemTables”));81. m_bViews = GetProfileValue(_T(“TableSettings”), _T(“Views”));82. m_bSynonyms = GetProfileValue(_T(“TableSettings”), _T(“SystemTables”));83.84. // initialize column info settings85. m_bLength = rofileVal86. m_bPrecision = GetProfileValue(_T(“ColumnInfoSettings”),_T(“Precision”));87. m_bNullability = GetProfileValue(_T(“ColumnInfoSettings”),_T(“Nullability”));88.89. return TRUE;90. }91.92.93.94. /////////////////////////////////////////////////////////////////////////////95. // CSchemaDBDoc serialization96.97. void CSchemaDBDoc::Serialize(CArchive& ar)98. {99. if (ar.IsStoring())100. {101. // TODO: add storing code here102. }103. else104. {105. // TODO: add loading code here106. }107. }108.109. /////////////////////////////////////////////////////////////////////////////110. // CSchemaDBDoc diagnostics111.112. #ifdef _DEBUG113. void CSchemaDBDoc::AssertValid() const114. {115. CDocument::AssertValid();116. }117.118. void CSchemaDBDoc::Dump(CDumpContext& dc) const119. {120. CDocument::Dump(dc);121. }122. #endif //_DEBUG123.124. /////////////////////////////////////////////////////////////////////////////125. // CSchemaDBDoc commands

484 � Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC

Page 504: Learn OLE DB Development With Visual C++ 6.0

126. void CSchemaDBDoc::SetLevel(Level nLevel)127. {128. m_nLevel = nLevel;129. UpdateAllViews(NULL);130. }131.132. CString CSchemaDBDoc::GetDSN()133. {134. // Check to see if the database is open135. if (!m_pTableset)136. return _T(“[No Data Source Selected]”);137.138. // pull DSN from database connect string139. return m_strConnect;140. }141.142. void CSchemaDBDoc::FetchColumnInfo(LPCSTR lpszName)143. {144. if (m_pColumnset)145. {146. delete m_pColumnset;147. m_pColumnset = NULL;148. }149.150. m_pColumnset = new CColumns;151. HRESULT hr = m_pColumnset->Open(m_session, NULL, NULL, lpszName);152. if (FAILED(hr))153. {154. AfxMessageBox(_T(“Couldn’t open column rowset”));155. delete m_pColumnset;156. m_pColumnset = NULL;157. }158. }159.160. BOOL CSchemaDBDoc::FetchTableInfo()161. {162. if (m_pTableset != NULL)163. {164. delete m_pTableset;165. m_pTableset = NULL;166. }167. m_pTableset = new CTables;168.169. // Must use char array for ODBC interface170. // (can simply hard code max size)171. char lpszType[64];172.

Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC � 485

Page 505: Learn OLE DB Development With Visual C++ 6.0

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 recordsets196. 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 database212. if (!m_strConnect.IsEmpty())213. m_strConnect = “”;214.215. // open the database216. if (m_source.Open(AfxGetMainWnd()->GetSafeHwnd()) != S_OK)217. {218. AfxMessageBox(_T(“Couldn’t connect to data source”));219. m_strConnect = _T(“”);

486 � Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC

Page 506: Learn OLE DB Development With Visual C++ 6.0

220. return FALSE;221. }222. else223. {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. else239. 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 page249. 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 page255. 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 settings261. if (sheet.DoModal() == IDOK)262. {263. BOOL bTableModified = FALSE;264. BOOL bColumnModified = FALSE;265.266. if (m_bSystemTables != pageTable.m_bSystemTables)

Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC � 487

Page 507: Learn OLE DB Development With Visual C++ 6.0

267. {268. m_bSystemTables = pageTable.m_bSystemTables;269. AfxGetApp()->WriteProfileInt(_T(“TableSettings”),270. _T(“SystemTables”),m_bSystemTables);271. bTableModified = TRUE;272. }273.274. if (m_bViews != pageTable.m_bViews)275. {276. m_bViews = pageTable.m_bViews;277. AfxGetApp()->WriteProfileInt(_T(“TableSettings”),278. _T(“Views”),m_bViews);279. bTableModified = TRUE;280. }281.282. if (m_bSynonyms != pageTable.m_bSynonyms)283. {284. m_bSynonyms = pageTable.m_bSynonyms;285. AfxGetApp()->WriteProfileInt(_T(“TableSettings”),286. _T(“Synonyms”),m_bSynonyms);287. bTableModified = TRUE;288. }289.290. if (m_bLength != pageColumn.m_bLength)291. {292. m_bLength = pageColumn.m_bLength;293. AfxGetApp()->WriteProfile93Int (_T(“ColumnInfoSettings”),294. _T(“Length”),m_bLength);295. bColumnModified = TRUE;296. }297.298. if (m_bPrecision != pageColumn.m_bPrecision)299. {300. m_bPrecision = pageColumn.m_bPrecision;301. AfxGetApp()->WriteProfileInt(_T(“ColumnInfoSettings”),302. _T(“Precision”),m_bPrecision);303. bColumnModified = TRUE;304. }305.306. if (m_bNullability != pageColumn.m_bNullability)307. {308. m_bNullability = pageColumn.m_bNullability;309. AfxGetApp()->WriteProfileInt(_T(“ColumnInfoSettings”),310. _T(“Nullability”),m_bNullability);311. bColumnModified = TRUE;312. }313.

488 � Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC

Page 508: Learn OLE DB Development With Visual C++ 6.0

314. // check for table modification first315. if (bTableModified && (m_nLevel == CSchemaDBDoc::levelTable))316. {317. // close and delete any open recordsets318. if (m_pTableset)319. {320. delete m_pTableset;321. m_pTableset = 0;322. }323.324. if (m_pColumnset)325. {326. delete m_pColumnset;327. m_pColumnset = 0;328. }329.330. // refresh table data331. FetchTableInfo();332. UpdateAllViews(NULL);333. }334.335. // if table settings not modified, check column info336. else if (bColumnModified && (m_nLevel == CSchemaDBDoc::levelColumn))337. {338. FetchColumnInfo(m_strTableName);339. UpdateAllViews(NULL);340. }341. }342. }343.344. int CSchemaDBDoc::GetProfileValue(LPCTSTR lpszSection,LPCTSTR lpszItem)345. {346. int nValue = AfxGetApp()->GetProfileInt(lpszSection, lpszItem,-1);347. if (nValue == -1)348. {349. nValue = 0;350. AfxGetApp()->WriteProfileInt(lpszSection,lpszItem,nValue);351. }352. return nValue;353. }354.355. void CSchemaDBDoc::OnCloseDocument()356. {357. if (m_pTableset)358. {359. delete m_pTableset;360. m_pTableset = 0;

Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC � 489

Page 509: Learn OLE DB Development With Visual C++ 6.0

361. }362.363. if (m_pColumnset)364. {365. delete m_pColumnset;366. m_pColumnset = 0;367. }368.369. CDocument::OnCloseDocument();370. }

Listing 9-2 SCHEMADBDOC.CPP source code after modifications

The following entries explain what the new code lines do:

� Lines 8-9 This code brings in the property page dialogs.

� Lines 55-57 This code sets default values for the UI.

� Lines 76-82 This code updates the table settings from the schemainformation.

� Lines 85-87 This code updates the column settings from the schemainformation.

� Lines 126-130 This code simply flips the display level and updates theactual UI.

� Lines 132-140 This code obtains the DSN string as needed.

� Lines 142-158 This code actually opens an OLE DB Recordset and pro-vides its column schema information via the internal member variables.

� Lines 160-189 This code actually opens an OLE DB Recordset and pro-vides its table schema information via the internal member variables.

� Lines 193-239 This code opens the database specified in the Open dia-log. It takes care of closing open Recordsets and deleting existing OLE DBconsumers first. Notice that it always starts in table display mode.

� Lines 244-340 This code handles interaction with the property sheetdialog for the settings for display of column and table information. It’spretty straightforward MFC code; you’ll write it many times if you do MFCOLE DB development.

� Lines 357-369 This code makes sure that the two data elements usedfor the table and column data are deleted before the application closes.

Finally, bring up the SCHEMADBVIEW.CPP file in the text editor. Scanthrough 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.

490 � Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC

Page 510: Learn OLE DB Development With Visual C++ 6.0

1. // SchemaDBView.cpp : implementation of the CSchemaDBView class2. //3.4. #include “stdafx.h”5. #include “SchemaDB.h”6.7. #include “SchemaDBDoc.h”8. #include “SchemaDBView.h”9.10. #ifdef _DEBUG11. #define new DEBUG_NEW12. #undef THIS_FILE13. static char THIS_FILE[] = __FILE__;14. #endif15.16. /////////////////////////////////////////////////////////////////////////////17. // CSchemaDBView18.19. IMPLEMENT_DYNCREATE(CSchemaDBView, CView)20.21. BEGIN_MESSAGE_MAP(CSchemaDBView, CView)22. //{{AFX_MSG_MAP(CSchemaDBView)23. // NOTE - the ClassWizard will add and remove mapping macros here.24. // DO NOT EDIT what you see in these blocks of generated code!25. //}}AFX_MSG_MAP26. // Standard printing commands27. ON_COMMAND(ID_FILE_OPEN, OnFileOpen)28. ON_COMMAND(IDM_COLINFO, OnColinfo)29. ON_COMMAND(IDM_TABLES, OnTables)30. ON_UPDATE_COMMAND_UI(IDM_COLINFO, OnUpdateColinfo)31. ON_UPDATE_COMMAND_UI(IDM_TABLES, OnUpdateTables)32. ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)33. ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)34. ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)35. END_MESSAGE_MAP()36.37. /////////////////////////////////////////////////////////////////////////////38. // CSchemaDBView construction/destruction39.40. CSchemaDBView::CSchemaDBView()41. {42. // TODO: add construction code here43.44. }45.46. CSchemaDBView::~CSchemaDBView()47. {

Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC � 491

Page 511: Learn OLE DB Development With Visual C++ 6.0

48. }49.50. BOOL CSchemaDBView::PreCreateWindow(CREATESTRUCT& cs)51. {52. // TODO: Modify the Window class or styles here by modifying53. // the CREATESTRUCT cs54.55. return CView::PreCreateWindow(cs);56. }57.58. /////////////////////////////////////////////////////////////////////////////59. // CSchemaDBView drawing60.61. void CSchemaDBView::OnDraw(CDC* pDC)62. {63. CSchemaDBDoc* pDoc = GetDocument();64. ASSERT_VALID(pDoc);65. // TODO: add draw code for native data here66. }67.68. /////////////////////////////////////////////////////////////////////////////69. // CSchemaDBView printing70.71. BOOL CSchemaDBView::OnPreparePrinting(CPrintInfo* pInfo)72. {73. // default preparation74. return DoPreparePrinting(pInfo);75. }76.77. void CSchemaDBView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo*/*pInfo*/)78. {79. // TODO: add extra initialization before printing80. }81.82. void CSchemaDBView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo*/*pInfo*/)83. {84. // TODO: add cleanup after printing85. }86.87. /////////////////////////////////////////////////////////////////////////////88. // CSchemaDBView diagnostics89.90. #ifdef _DEBUG91. void CSchemaDBView::AssertValid() const92. {93. CView::AssertValid();94. }

492 � Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC

Page 512: Learn OLE DB Development With Visual C++ 6.0

95.96. void CSchemaDBView::Dump(CDumpContext& dc) const97. {98. CView::Dump(dc);99. }100.101. CSchemaDBDoc* CSchemaDBView::GetDocument() // non-debug version is inline102. {103. ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CSchemaDBDoc)));104. return (CSchemaDBDoc*)m_pDocument;105. }106. #endif //_DEBUG107.108. /////////////////////////////////////////////////////////////////////////////109. // CSchemaDBView message handlers110. void CSchemaDBView::OnFileOpen()111. {112. // TODO: Add your command handler code here113. CSchemaDBDoc* pDoc = GetDocument();114. ASSERT_VALID(pDoc);115.116. if (pDoc->OnOpenDocument())117. pDoc->SetLevel(CSchemaDBDoc::levelTable);118. else119. pDoc->SetLevel(CSchemaDBDoc::levelNone);120.121. }122.123. void CSchemaDBView::OnColinfo()124. {125. // TODO: Add your command handler code here126. CSchemaDBDoc* pDoc = GetDocument();127. ASSERT_VALID(pDoc);128.129. // determine list control selection130. CListCtrl& control = GetListCtrl();131. int nCount = control.GetItemCount();132. for (int i = 0; i < nCount; i++)133. {134. if (control.GetItemState(i,LVIS_SELECTED))135. break;136. }137. if (i < nCount)138. {139. // pull table name to send to document140. pDoc->m_strTableName = control.GetItemText(i,0);141.

Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC � 493

Page 513: Learn OLE DB Development With Visual C++ 6.0

142. #ifndef _UNICODE143. LPCSTR lpszName;144. lpszName = pDoc->m_strTableName;145. #else146. LPSTR lpszName;147. char rgchTableName[257];148. lpszName = rgchTableName;149. int nSize;150. nSize = ::WideCharToMultiByte(CP_ACP,0,pDoc->m_strTableName,151. -1, lpszName, 257, NULL, NULL);152. // Notify on failure153. ASSERT(nSize);154. #endif // _UNICODE155.156. pDoc->FetchColumnInfo(lpszName);157. pDoc->SetLevel(CSchemaDBDoc::levelColumn);158. }159.160. }161.162. void CSchemaDBView::OnTables()163. {164. // TODO: Add your command handler code here165. CSchemaDBDoc* pDoc = GetDocument();166. ASSERT_VALID(pDoc);167.168. pDoc->m_strTableName.Empty();169. pDoc->FetchTableInfo();170. pDoc->SetLevel(CSchemaDBDoc::levelTable);171.172. }173.174.175. void CSchemaDBView::OnUpdateColinfo(CCmdUI* pCmdUI)176. {177. // TODO: Add your command update UI handler code here178. CSchemaDBDoc* pDoc = GetDocument();179. ASSERT_VALID(pDoc);180.181. if (pDoc->m_nLevel == CSchemaDBDoc::levelTable &&182. GetListCtrl().GetSelectedCount())183. {184. pCmdUI->Enable();185. }186. else187. pCmdUI->Enable(FALSE);188.

494 � Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC

Page 514: Learn OLE DB Development With Visual C++ 6.0

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 columns198. CListCtrl& control = GetListCtrl();199. control.DeleteAllItems();200. while (control.DeleteColumn(0));201.202. // set up view based on the document’s level203. switch (pDoc->m_nLevel)204. {205. case CSchemaDBDoc::levelNone:206.207. // set the document title208. pDoc->SetTitle(pDoc->GetDSN());209. break;210.211. case CSchemaDBDoc::levelTable:212. {213. // set the document title214. CString strDataSource = pDoc->GetDSN();215. strDataSource += _T(“ [Tables]”);216. pDoc->SetTitle(strDataSource);217.218. // add columns to display219. 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 recordset226. // displaying the table information227. 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);

Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC � 495

Page 515: Learn OLE DB Development With Visual C++ 6.0

236. control.SetItem(item,3,LVIF_TEXT,237. pDoc->m_pTableset->m_szSchema,0,0,0,0);238. control.SetItem(item,4,LVIF_TEXT,239. pDoc->m_pTableset->m_szDescription,0,0,0,0);240. item++;241. }242.243. break;244. }245.246. case CSchemaDBDoc::levelColumn:247. {248. int column;249.250. // set the document title251. CString strDataSource = pDoc->GetDSN();252. strDataSource += _T(“ - ”);253. strDataSource += pDoc->m_strTableName;254. strDataSource += _T(“ [Column Info]”);255. pDoc->SetTitle(strDataSource);256.257. // add columns to display258. // respect the column info settings values259. column = 0;260. control.InsertColumn(column++,_ T(“Name”),LVCFMT_LEFT,100,-1);261. control.InsertColumn(column,_ T(“Type”),LVCFMT_LEFT,100,column++);262. if (pDoc->m_bLength)263. control.InsertColumn(column,_ T(“Length”),LVCFMT_LEFT,80,column++);264. if (pDoc->m_bPrecision)265. {266. control.InsertColumn(column,_ T(“Precision”),LVCFMT_LEFT,80,column++);267. control.InsertColumn(column,_ T(“Scale”),LVCFMT_LEFT,50,column++);268. }269. if (pDoc->m_bNullability)270. control.InsertColumn(column,_ T(“Nullable”),LVCFMT_LEFT,50,column++);271.272. // traverse the column info recordset273. // respect the column info settings values274. int item = 0;275.276. // If the column rowset couldn’t be opened, don’t attempt to fetch277. // any data.278. if (pDoc->m_pColumnset == NULL)279. break;280.281. while (pDoc->m_pColumnset->MoveNext() == S_OK)282. {

496 � Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC

Page 516: Learn OLE DB Development With Visual C++ 6.0

283. CString strValue;284.285. // always insert the column name286. control.InsertItem(item, pDoc->m_pColumnset-287. >m_szColumnName);288.289. // always insert the column type290. column = 1;291. CString strType;292. strType.Format(“%d”, pDoc->m_pColumnset->m_nDataType);293. control.SetItem(item,column++,LVIF_TEXT, strType,0,0,0,0);294.295. // only show type if requested296. if (pDoc->m_bLength)297. {298. strValue.Format(_T(“%ld”),pDoc->m_pColumnset-299. >m_nMaxLength);300. control.SetItem(item,column++,LVIF_TEXT,strValue,0,0,0,0);301. }302.303. // only show precision,scale,radix if requested304. if (pDoc->m_bPrecision)305. {306. // precision307. strValue.Format(_T(“%d”),pDoc->m_pColumnset-308. >m_nNumericPrecision);309. control.SetItem(item,column++,LVIF_TEXT,strValue,0,0,0,0);310.311. // scale312. int nOrdinal = pDoc->m_pColumnset-313. >m_nOrdinalPosition;314. strValue.Format(_T(“%d”),pDoc->m_pColumnset-315. >m_nNumericScale);316. control.SetItem(item,column++,LVIF_TEXT,strValue,0,0,0,0);317. }318.319. // only show nullability if requested320. if (pDoc->m_bNullability)321. {322. if (pDoc->m_pColumnset->m_bIsNullable == FALSE)323. control.SetItem(item,column++,LVIF_TEXT,_T(“No”),0,0,0,0);324. else325. control.SetItem(item,column++,LVIF_TEXT,_T(“Yes”),0,0,0,0);326. }327.328. item++;329. }

Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC � 497

Page 517: Learn OLE DB Development With Visual C++ 6.0

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:

� Lines 113-120 This code gets the initial database information fordisplay.

� Lines 126-158 This code connects to the database and gets its infor-mation appropriately so the display logic can show it.

� Lines 165-170 This code updates whether the tables display is shown.

� Lines 178-187 This code updates whether the columns display isshown.

� Lines 193-332 This code is the core display logic. It is pretty well com-mented, and basically just outputs the information from the OLE DBconsumer into whichever view is currently enabled. Most of it is orientedtowards the specific requirements of MFC display behaviors.

Building the MFC OLE DB Consumer Application

Once you are satisfied you’ve entered the code from the above listings cor-rectly, build the OLE DB consumer test application from the Build menu. Ifyou encounter any errors, please check the code in the listing to find yourmistake (it has been exhaustively debugged and is correct). Now you areready 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 tobrowse any OLE DB provider on your system and examine its table and col-umn schemas.

Step-By-Step

Start the SCHEMADB application from inside Visual C++ or from theExplorer. When the dialog appears, select the File|Open menu item. Usingthe dialog, navigate to a database for an OLE DB provider and open it. Asshown in Figure 9-20, the database will open and display its tables. Select atable and then click on the Columns Info menu item; this will change the dis-play to show the column names and characteristics of the selected table.

498 � Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC

Page 518: Learn OLE DB Development With Visual C++ 6.0

Experiment with other databases that are OLE DB providers; you’ll find theapplication happily shows you all their schema information as well.

Where We Go From HereWhere We Go From Here

You have conquered all the basics and some of the advanced aspects of OLEDB as of this point. But OLE DB doesn’t live in its own world anymore; mod-ern developers must understand how to deploy and interact with OLE DBproviders and consumers in a world increasingly dominated by the Internetand World Wide Web. Chapter 10 brings you up to speed on how to work inthis incredible environment!

Chapter Nine—Creating and Using Complex OLE DB Consumers in MFC � 499

Figure 9-20

The MFCapplicationdisplays thedatabaseschema forOLE DBproviders

Page 519: Learn OLE DB Development With Visual C++ 6.0
Page 520: Learn OLE DB Development With Visual C++ 6.0

Chapter Ten

Putting OLE DB on the InternetPutting OLE DB on the Internet

At this point, you’re fully up to speed on all that Visual C++ 6.0 has to offerin terms of OLE DB; you can create and use OLE DB components and applica-tions with the best of them. You understand the issues involved in creatingOLE DB Automation servers and ActiveX controls and clients in both ATL andMFC, and you’re an ace with both the Visual C++ IDE and the underlyingconcepts behind OLE DB. So what’s left? The Internet, that’s what! More andmore database developers are tasked by their clients to put DBMS (and thusOLE DB) functionality onto web pages, either on a local intranet or the big,bad Internet/World Wide Web itself. Doing this requires an understanding ofarcane issues totally unrelated to OLE DB or COM, and so the following sec-tions will fill that essential gap. This chapter will add mastery of theLPK_TOOL application for creating HTML license keys, understanding of theActiveX Control Pad and the ActiveX SDK’s code-signing tools, and familiaritywith the two major tools of Internet and World Wide Web functionality, FTPand Internet Explorer, to your OLE DB skills inventory. Let’s dig in! (Beforewe start, however, it is important to remember that in most places where yousee “ActiveX” below, you can also substitute “COM,” as in our OLE DB COMAutomation components.)

Using the LPK_TOOL Application to Create HTMLUsing the LPK_TOOL Application to Create HTML

Run-time License LPK FilesRun-time License LPK Files

Some components are sufficiently expensive that developers will want to con-trol even their usage on WWW pages; the mechanism COM provides for thisis called an LPK file. Visual C++ 6.0 includes the LPK_TOOL.EXE applicationto 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 “Down-loading the ActiveX SDK” section below for an alternative location of theprogram.

501

Page 521: Learn OLE DB Development With Visual C++ 6.0

An LPK file is a text file that con-tains two very important pieces ofinformation relative to Internetusage of COM components. Thefirst element is a copyright noticethat tells users not to download thefile without permission; this hardlyprevents it from happening, butprovides legal protection if a copy-right infringement suit must bebrought against unlicensed users ofthe software. The second is aMIME-encoded text string that con-tains the actual LPK licenseinformation.

Figure 10-2 shows the dialog that is the interface for LPK_TOOL.EXE. It con-sists of two list boxes and several command buttons. The left-hand list boxshows all the COM elements currently registered on your computer; you canrestrict it to only those controls that support licensing by checking the lowerleft-hand check box. To move a component to the right-hand list box, select itin the left-hand list box, and press the Add -> button. To remove a selectedcomponent from the right-hand list box, select it and press <-Remove.

The way that LPK files are used is vital in creating them! LPK files arerequired for each combination of licensed components on a given web page.In other words, if you have three instances of licensed controls on one pageand 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 sepa-rate pages, however!), and every licensed control used on that page musthave 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 givenHTML page when creating that page’s LPK file.

502 � Chapter Ten—Putting OLE DB on the Internet

Figure 10-1

The LPK_TOOL.EXE fileon the VisualC++ 5.0 dis-tribution CD

Figure 10-2

The LPK_TOOL.EXEfile userinterface

Page 522: Learn OLE DB Development With Visual C++ 6.0

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 thecomponents to be uploaded to theweb site for your OCX file and itsHTML page(s); assign it a uniquename based on the page where thecontrol will appear. Then pressSave to actually create the LPK file.

Figure 10-4 shows the final dialog the LPK_TOOL application will displayafter successfully creating the LPK file for the selected component group. List-ing 10-1 shows the contents of a sample LPK file. The garbled characters atthe end of the file contain the MIME-encoded license key.

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

Chapter Ten—Putting OLE DB on the Internet � 503

Figure 10-3

The LPK_TOOL Save Asdialog for cre-ating the LPKfile

Figure 10-4

The LPK_TOOL successmessage finaldialog

Page 523: Learn OLE DB Development With Visual C++ 6.0

Downloading the ActiveX SDKDownloading the ActiveX SDK

Figure 10-5 showsInternet Explorer atthe download pagefor the ActiveX SDKfrom the Microsoftweb site. If you wishto digitally sign yourCOM components,you will need toobtain a copy of theSDK from this website. The URL is:http://www.microsoft.com/intdev/sdk/.

Click on the Download link and follow the directions; a self-extracting com-pressed file containing the SDK will be downloaded by your browser andplaced on your hard drive. Once you have downloaded the file (and itsoptional documentation), run the self-installing executable; it will place acopy of the SDK on your hard drive.

Figure 10-6 shows the BIN subdirectory of the ActiveX SDK once it has beeninstalled on a local computer. Notice it has two very important applications:

LPK_TOOL.EXE (this iswhere to find it if youdon’t have the VisualC++ 6.0 CD) andSIGNCODE.EXE. Thesecond file will be ofvital importance in thenext section on codesigning. The rest of theSDK consists of varioushelp files, documenta-tion, and other tools toassist in creatingInternet componentsfor Microsoft Windows.

504 � Chapter Ten—Putting OLE DB on the Internet

Figure 10-5

The downloadpage atMicrosoft’sweb site forthe ActiveXSDK

Figure 10-6

The ActiveXSDK BIN sub-directoryshowingLPK_TOOLand SIGN-CODE appli-cations

Page 524: Learn OLE DB Development With Visual C++ 6.0

Code Signing using Authenticode™—Obtaining an SPCCode Signing using Authenticode™—Obtaining an SPC

Authenticode™ is Microsoft’s proprietary alternative to the Java sandboxsecurity system. Java’s sandbox is a term for the severe restrictions placed onJava applets; they cannot write to the local hard or floppy drives, cannot usemost OS API calls, and otherwise can do very little beside show pictures andaccept keyboard input. COM goes beyond this to allow much of the function-ality of true application programs in its COM system, but at a price. The COMcode element must be digitally “signed” for Internet Explorer or anotherAuthenticode-based system to allow it to operate on the local machine. (Youcan alter the security settings to permit this, but it certainly is notrecommended!)

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 becomeacquainted with in the previous section, namely downloading the ActiveXSDK 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 acquirea digital signature. ItsURL is: http://digitalid.verisign.com/. The followingsteps direct youthrough the process ofobtaining your ownpersonal SPC fromVeriSign. (If youalready have one ordon’t wish to obtainone, skip this sectionand the following oneon actual codesigning.)

Step-By-Step

Click on the Request a Digital ID image button. You should see a display simi-lar to that shown in Figure 10-8 after the browser has navigated to the newpage. These are the various kinds of SPCs available from VeriSign.

Chapter Ten—Putting OLE DB on the Internet � 505

Figure 10-7

The VeriSignhome page

Page 525: Learn OLE DB Development With Visual C++ 6.0

Click on the Software Publisher link. After the navigation is complete, youshould see a display similar to Figure 10-9. There are two types of SoftwarePublisher SPCs currently available, both of which are explained here. (Notethat to use either service, a valid credit card with at least $20 credit isrequired. Also, Class 2 SPCs are only available at this time to United Statesresidents.)

If you are an individual or a small business, click the Class 2 link or image. Ifyou are a large business, click the Class 3 link or image. The following stepsare for the Class 2 SPC, but the Class 3 steps are very similar. You will see adisplay similar to Figure 10-10, which is the initial data entry screen for theSPC.

506 � Chapter Ten—Putting OLE DB on the Internet

Figure 10-8

The VeriSignSPC ServicesPage

Figure 10-9

The VeriSignSoftware Pub-lisher SPCclasses page

Page 526: Learn OLE DB Development With Visual C++ 6.0

Fill out the data entryform very carefully.Obtaining the Class 2SPC requires that yourinformation be com-pletely accurate and upto date! When you arefinished, press the sub-mit image at thebottom of the page.You will then see a dis-play similar to Figure10-11, the subscriberagreement required toobtain the SPC.

The subscriber agree-ment is an absoluterequirement for obtain-ing the SPC; it legallybinds you to behaveresponsibly with what-ever software isencoded using yourSPC. After reading andagreeing with it, pressthe Accept image at thebottom of the page.You will then see a dis-play similar to Figure

Chapter Ten—Putting OLE DB on the Internet � 507

Figure 10-11

SPC Sub-scriber Agree-ment page

Figure 10-12

SPC submis-sion startscreen

Figure 10-10

Initial dataentry screenfor Class 2VeriSign SPC

Page 527: Learn OLE DB Development With Visual C++ 6.0

10-12, which is the display page to begin the actual process of obtaining yourSPC.

Make sure there is a removabledisk with about 500 K of space inwhatever device you previously setup to hold your license files. Thenpress the Submit button. Thebrowser will begin displaying aseries of wizard dialog boxes, thefirst of which is shown in Figure10-13. These wizards guide youthrough the automated process ofacquiring your private portion ofthe SPC.

Press the Next button on the firstwizard dialog box, and you will seea data entry dialog similar to thatshown in Figure 10-14. It is used toenter the location of the removablemedia device you will use to storeyour Private Key File (PVK) for usein digitally signing files.

Use the Browse button to navigate to the drive and directory where you planto store your PVK and SPC files. Then press Finish. The PVK file will be cre-

ated and stored, andyou will see a displaysimilar to Figure10-15, giving you theURL to return andobtain your SPC file.

508 � Chapter Ten—Putting OLE DB on the Internet

Figure 10-14

Path entrydialog for thePVK PrivateKey File

Figure 10-13

Initial PVKWizard dis-play dialog

Figure 10-15

URL displayfor retrieval ofSPC file

Page 528: Learn OLE DB Development With Visual C++ 6.0

At this point there will be adelay; if you are obtaining aClass 2 SPC, it will be only afew moments, but if you areobtaining a Class 3 SPC, itmay be several days. Even-tually you will receive ane-mail message similar to thatshown in Figure 10-16, indi-cating you have beenawarded the SPC yourequested or explaining whyit was not awarded and giv-ing you the steps to follow tocorrect the problem.

At this point youshould reactivate yourcopy of InternetExplorer and navigateto the URL: http://digitalid.verisign.com/msgetiscs.htm.You should see a dis-play similar to thatshown in Figure 10-17,which is a form toallow entry of your PINto retrieve the SPC file.

The PIN is in the e-mailmessage from step 9.Enter it in the text boxand press the Submitbutton. There will be apause of up to severalminutes while securedinformation isexchanged betweenVeriSign and your copyof Internet Explorer.Eventually you will seea display similar to Fig-ure 10-18, unless a

Chapter Ten—Putting OLE DB on the Internet � 509

Figure 10-16

E-mail clientdisplayingVeriSign con-firmationmessage

Figure 10-17

VeriSign SPCPIN entryform

Figure 10-18

SPC installa-tion prepara-tion screen

Page 529: Learn OLE DB Development With Visual C++ 6.0

problem is encountered. In the latter case, you will be given a phone numberto call and will be able to clear up the matter and resubmit your PIN. The dis-play indicates the SPC file is ready to be placed on your removable mediadevice.

Pressing the Install button on the display will bring up the Credentials Enroll-ment Wizard dialog box shown in Figure 10-19. Make sure the removable

disk with your PVK file is availableto the system and enter the samepath for the SPC file. The IE appli-cation will, under control of theVeriSign page, write out your SPCfile. You will see a success dialogand should then remove the mediawith the software key, restoring itto the drive only when using it toactually sign code.

Code Signing using Authenticode™—Digitally SigningCode Signing using Authenticode™—Digitally Signing

Code

Now that you have obtained your SPC, here are the steps to actually signcode with it.

Step-By-Step

First, bring up an MS-DOS window in the directory INETSDK\BIN of theActiveX SDK. Enter SIGNCODE and press Enter. Although you are running a

DOS mode program, a set of wiz-ard dialog boxes will appear toguide you through the code-signingprocess, with the initial dialogshown in Figure 10-20.(SIGNCODE.EXE can also be usedfrom the DOS command line; seethe ActiveX SDK documentation fordetails.)

510 � Chapter Ten—Putting OLE DB on the Internet

Figure 10-19

SPC Enroll-ment Wizarddialog box

Figure 10-20

Code SigningWizard initialdialog

Page 530: Learn OLE DB Development With Visual C++ 6.0

Press Next on the initial dialog box.A new wizard dialog appears asshown in Figure 10-21. Fill in thetop text entry control with the pathand name of the CAB file you wishto sign. Give it a descriptive namein the middle text control. Finally,enter a URL for your web site or aREADME filename on the localdrive.

Make sure the removable media with your SPC and PVK files on it is availableto the system. Press Next; the wizard dialog shown in Figure 10-22 willappear. It has two text controls which should already be filled in with the

name and paths to the PVK andSPC files created in the “Obtainingan SPC” section. If they are not, itis probably because you have notinserted the removable disk in thedrive containing them; do so anduse the Browse buttons to obtainthe paths and filenames for yourPVK and SPC files.

Pressing Next after completing the PVK and SPC file entry will bring up thedialog shown in Figure 10-23. It is a confirmation dialog that allows you togo back if needed and change parts of the code-signing data.

Chapter Ten—Putting OLE DB on the Internet � 511

Figure 10-21

Code SigningWizard initialdata acquisi-tion dialog

Figure 10-22

Code SigningWizardPVK/SPC pathentry dialog

Figure 10-23

Code SigningWizard dataconfirmationdialog

Page 531: Learn OLE DB Development With Visual C++ 6.0

Pressing Next moves you to thedialog shown in Figure 10-24. It isthe final gateway prior to activat-ing the code-signing process. If forsome reason you do not wish tocontinue the process, press Cancel;if you remember a needed datachange, press Back. Otherwisepress Sign to actually sign yourcode.

Once the signing process is complete, you will see a success dialog similar tothat shown in Figure 10-25. It indicates the file has been digitally signed andis ready for secure Internet, intranet, or World Wide Web usage. If for any

reason you recompile or re-createthe file, however, you must repeatthe entire signing process! If thecode-signing process fails for anyreason, an explanatory dialog boxwill appear; consult the ActiveXSDK documentation files for direc-tions as to fixing the problem. (It isusually either an invalid SPC orPVK file, or a problem with locat-ing the file to be signed or the PVKor SPC files.)

Once a file has been digitally signed,Microsoft has provided a way to checkthe file prior to using it over the net-works. This utility is called CHKTRUST,and it is another MS-DOS utility in thesame directory as SIGNCODE. Activateanother MS-DOS window and move tothe \INETSDK\BIN\ directory. EnterCHKTRUST -C and the path to thesigned CAB file. After a moment, youshould see a certificate display similarto that shown in Figure 10-26 exceptusing your name and the distinctivename you gave the control. If this dia-log does not appear, make sure youhave the added -C and the correct file-name and path on the command line

512 � Chapter Ten—Putting OLE DB on the Internet

Figure 10-24

Code SigningWizard finalconfirmationdialog

Figure 10-25

Code-signingsuccess dialog

Figure 10-26

Confirmationdialog showingcode-signingcertificate

Page 532: Learn OLE DB Development With Visual C++ 6.0

for CHKTRUST. You now have a signed, guaranteed-safe CAB file with a COMcomponent ready to be distributed over the World Wide Web!

Obtaining and Using ActiveX Control Pad from MicrosoftObtaining and Using ActiveX Control Pad from Microsoft

Microsoft has kindlyprovided a very pow-erful utility for addingCOM components toHTML pages, calledActiveX Control Pad.The URL to obtain itfrom Microsoft ishttp://www.microsoft.com/work-shop/author/cpad.Figure 10-27 showsthe download locationat Microsoft’s web sitefor obtaining it. Clickon the Download linkand follow the direc-

tions to place the self-extracting install program on your hard drive. Thenexecute 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 offunctionality that the ActiveXControl Pad provides, over andabove its Notepad-like text edi-tor (which has no special HTMLfeatures, unfortunately): auto-matic insertion of <OBJECT>tags for COM components,including visual editing of thecomponent desired, creation ofthe powerful new HTML Layoutcontrol layout files (called ALXbut still HTML-based), visualediting and insertion of HTMLLayouts into HTML pages, andActiveX scripting automationfor both HTML pages and

HTML Layouts. Each of these areas is vital to effective use of COM compo-nents over the Internet, and is explained below.

Chapter Ten—Putting OLE DB on the Internet � 513

Figure 10-27

Downloadpage forActiveX Con-trol Pad fromMicrosoft

Figure 10-28

ActiveX Con-trol Pad’s ini-tial userinterface

Page 533: Learn OLE DB Development With Visual C++ 6.0

Automatic COM <OBJECT> Tags/Visual Editing

Figure 10-29 shows the menu used to insert an ActiveX control into an HTMLpage. Selecting it causes the Control Pad to search the Registry for the com-puter and produce the dialog box shown in Figure 10-30, which lists all theavailable COM components on the host machine. Since this is a design activ-ity, those controls that require license keys will not work unless the key isinstalled on the host computer.

Once you select the component to use (you can choose only one at a timesince that’s the way the <OBJECT> tag works), be prepared for a significantdelay while the Control Pad convinces OLE to let it have the control; the lessRAM and the more ActiveX controls you have on your system, the longer thiswill take (on my machine it’s about five minutes!).

Figure 10-31 shows the various parts of the ActiveX Control Pad Editor inter-face. The window marked “A” is the main visual editor pane. It allows you tosize the control and observe its visual characteristics; however, it does notkeep position information (unlike the HTML Layout control discussed below),so moving the control away from the top of the editor window has no neteffect. 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 avalue for them via typing into the upper text control (next to the Apply but-ton) or supplies a drop-down list of values. Sometimes a custom editor

514 � Chapter Ten—Putting OLE DB on the Internet

Figure 10-29

The Edit|Insert ActiveXcontrol menuitem

Figure 10-30

A dialog boxlisting allavailableCOMcomponents

Page 534: Learn OLE DB Development With Visual C++ 6.0

button will appear to the rightof the upper text control; press-ing it will bring up a customproperty editor for that particu-lar property. The windowmarked “C” is not available forall controls, only those thatsupport Property Pages. It’saccessible from the right-clickshortcut menu of the objectinside the visual editing win-dow, and is always the lowest“properties” entry there. It sup-plies user-written visual editingof property values for the con-trol, as shown in the figure.

Once you have made all the changes you like to the visual and informationalstate of the ActiveX control in the editors, click the close button of the mainvisual editor. This will close all the ActiveX control editor windows and causethe system to write out an <OBJECT> tag into the HTML file at the positionof the cursor when the Edit menu option was invoked. This is shown in Fig-ure 10-32, where I have accidentally placed the <OBJECT> tag in an invalid

position, inside the <HEAD>block. (I moved it after discov-ering this...:)). Notice the tagswritten after the main<OBJECT> tag; these arewhere the custom values set inthe ActiveX Editor are stored sothe browser or other applica-tion can re-create them. Somedefective controls don’t cor-rectly store their visual editingresults this way, which willresult in those settings beinglost when the control isre-created in the targetenvironment.

Creation of HTML Layout Controls

The big question is: What the heck is an HTML Layout control, and what hasit got to do with Visual C++ ActiveX control creation and usage? The answerlies in the usage part; HTML Layout controls are the hottest thing to comealong since frames, and maybe even since HTML itself! Right now they are a

Chapter Ten—Putting OLE DB on the Internet � 515

Figure 10-31

ActiveX Con-trol Pad’s AXCeditorinterface

Figure 10-32

An<OBJECT>tag insertedInto theHTML byActiveX Con-trol Pad

Page 535: Learn OLE DB Development With Visual C++ 6.0

supported extra requiring a download from Microsoft when a layout is firstencountered. However, their functionality is already built into InternetExplorer 5.0 and Windows 2000, and so learning about them now will onlyput 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 HTMLpage, overlapping of controls along with transparency, and three-dimensionalordering of control display (also called z-ordering). With these capabilities,an HTML Layout allows a group of ActiveX controls on a web page to behaveexactly like a Windows dialog: They always come up in the same positionswith the same arrangement, no matter what HTML environment they areplaced within. By clever use of z-ordering and transparency, sophisticatedeffects equal to those found in commercial games and multimedia presenta-tions 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 isitself an ActiveX control and must be directly inserted into the HTML to func-tion), supplied with a nonstandard ALX file extension since although they areHTML based, their tags are not supported by other browsers at the presenttime (such as Netscape’s products). The ALX file(s) must be placed on theweb site along with the HTM file that references them; Internet Explorer willautomatically download both the HTM and ALX files to reconstruct the pageas needed.

Using HTML Layouts requires configuring their principal visual component,called the Toolbox. To start the process, select the File|New HTML Layoutmenu option. After a relatively lengthy delay (about the same as for addingan ActiveX control, and heavily dependent on the individual state of yourcomputer), the Layout Editor will appear, along with the Toolbox dialog. The

Toolbox will initially have two tabs onits display. Click on the one markedAdditional to move it into view;notice that it has room for additionalcontrols. Right-click on the Toolboxdialog’s Additional page to bring upits shortcut menu, and select Addi-tional Controls. You will see a displaysimilar to Figure 10-33, as anotherdialog appears showing all the avail-able controls on your computer.Unlike the ActiveX Editor, however,you can select multiple controls with

this dialog box. When you select OK, all the controls you have checked willhave a small image placed on the current palette of the Toolbox. You willthen be able to visually click on them and drag their size on the Layout toposition a new copy, just like in the Forms Editor Window of Visual C++!

516 � Chapter Ten—Putting OLE DB on the Internet

Figure 10-33

Choosing toadd availablecontrols tothe HTMLLayout EditorToolbox

Page 536: Learn OLE DB Development With Visual C++ 6.0

Select an interesting control or two, press OK, and observe as the systemadds 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, asshown in Figure 10-34. This menu permits a number of important operations

on the pages, including adding, deleting, orrenaming a page, and changing its tooltiptext. Also, pages can be saved into files formoving between copies of the Control Pad,either over the Internet or on different com-puters at the same location. Pages can alsobe moved from one position to another inthe sequence of display. Once more pagesare available than fit on the current size ofthe display, two small scroll buttons appearto the right of the tabs, as shown in thefigure.

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 controlwill maintain this appearance regardless of what HTML page it is displayed

within! The controls shown inthis page come from the Stan-dard tab of the Toolbox, whichincludes Internet-ready versionsof most of the controls pro-vided with Visual C++ 6.0.Shown in the figure are aTextBox, a CommandButton,and an Image control. Alsoshown is a custom control cre-ated in another project; it wasadded using the functionality inthe previous section and worksjust like the other controls.

To place a control on the Lay-out, simply click on it in the

Toolbox, then click and drag on the Layout Editor pane to the desired size ofthe control. It will draw itself and add itself to the Layout as soon as yourelease the mouse. If you want to move it, simply click on it and drag its cen-ter; 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++; theyalways appear on the center of the Layout when pasted. Clicking outside the

Chapter Ten—Putting OLE DB on the Internet � 517

Figure 10-34

Adding andmodifyingpages of theToolbox dialog

Figure 10-35

An HTML Lay-out underconstruction

Page 537: Learn OLE DB Development With Visual C++ 6.0

area of a control and dragging around multiple controls selects them as agroup; there are special group menu options that are mainly used forOptionButton controls.

Controls can overlap, simply by placing them in such a way as to cause theeffect. To determine which control is drawn “on top of” another control, usethe four buttons showing overlapping controls on the toolbar, or use the For-mat menu options. You can make a given control go all the way in front ofother controls, go all the way behind them, or incrementally move it forwardor backward. This is called “z-ordering,” and is very powerful because, amongother things, it can be controlled at run time with ActiveX scripting (dis-cussed below).

Controls have properties; these are accessible via the Properties dialog justlike in Visual C++ and the other ActiveX Editor. There is a feature which isunique to the Layout, however: BackStyle. This property can be set to eitheropaque (the default) or transparent. If set to transparent, the backgroundbrush for the control is set to bsClear, meaning that when it draws itself, itsbackground color won’t show up, allowing things “behind it” (i.e., drawn pre-viously in z-order) to be visible. Obviously, the control must not explicitlydraw itself with an opaque brush style, or the effect won’t take place; sincemany controls let Windows draw their background, however, the effect canbe quite easily obtained and is very striking!

The other properties of the control are also accessible via the Properties dia-log, 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 includesbringing up their Property Pages, showing the Script Wizard (explainedbelow), and saving the HTML Layout and showing it in text form in Notepador other default text editor. In addition, the shortcut menu supports the fourz-order positioning choices.

Once a Layout is constructed as desired, it must be saved using the Saveoptions 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 desiredin the HTML code. A dialog will appear, prompting for the choice of Layoutfile; an <OBJECT> tag will then be written into the HTML to show the Lay-out at that point in the HTML stream. Unfortunately, there is no way atpresent to position two Layout controls effectively on either the same HTMLpage or a single Layout. In the case of multiple Layout controls on the sameHTML page, they simply get treated like any other object and drawn in thesequence found in the HTML. The Layout control itself will not permitanother copy of itself to be placed on it, to prevent stack explosions and otherfun recursive catastrophes.

518 � Chapter Ten—Putting OLE DB on the Internet

Page 538: Learn OLE DB Development With Visual C++ 6.0

ActiveX Script Wizard for HTML and ALX

But the real power of the ActiveX Control Pad, and in may ways of COMitself, is the feature called ActiveX scripting. It is a way for small programfragments, called scripts, written directly inside the HTML or ALX page itself,to directly manipulate the browser and other ActiveX controls, includingHTML Layout controls and their embedded controls!

Books can and have been written on the subject of ActiveX scripting (includ-ing one by the author), so the subject is much too large to be covered here inany real detail. But to give you enough information to get started, here is thescenario: Once you place an ActiveX control or other COM element on anHTML page, you cannot manipulate it using the editor tools used to createthe page itself. Running in the browser, it can only be changed via the facilityof scripting. By including small program elements written in VCScript lan-guage (a subset of Visual C++ itself) or in JScript (Microsoft’s proprietaryversion of Netscape’s JavaScript), you can achieve at run time in the browserwhat was previously only possible in the development environment! You canchange properties, call methods, and respond to events, just as if the controlwas running in Visual C++ or the ActiveX Control Pad.

The key to including this incredi-ble capability is the ScriptWizard. It is shown in Figure10-36, invoked from the HTMLLayout Editor; it can also becalled up from a regular HTMLpage in the main editor. TheScript Wizard consists of twolarge treeview controls; theleft-hand one is called the EventsPane and the right hand one isthe Actions Pane. At the bottom isthe Code Pane, which is shown inList View mode in the figure.

The procedure to utilize the Script Wizard is quite simple: You select theevent to which you wish to add behavior in the Events Pane (in the figure it isthe Click event of the Command Button control on the Layout), and thenselect either the method you wish to invoke or the property you wish tochange in the Actions Pane. In the figure, the custom control’s BackColorproperty is being changed. In List View mode, dialog boxes will appear toprompt you for values to add, and the calls to methods will simply be placedinto the list of statements. This method is very useful for simple scripts anddoes not require a great deal of understanding of scripting, since the environ-ment handles most of the work.

Chapter Ten—Putting OLE DB on the Internet � 519

Figure 10-36

The ScriptWizard in theHTML LayoutEditor

Page 539: Learn OLE DB Development With Visual C++ 6.0

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 onmethods and properties merely adds a line of code referencing that elementwithout placing any other components like equals signs and without prompt-ing you for new values.

The advantage of Code View mode, shown in Figure 10-37, is that you canenter most scripting statements by hand, such as variable declarations,changes to the parameters of procedures and functions, and new procedures

and functions. The shortcut menufor the Action Pane also providesthis capability, somewhatrestrictedly. (It cannot declarelocal variables, for example.)Also, to call methods with param-eters, you must use Code Viewmode, since those methods willnot even be shown in List Viewmode.

When you are finished addingyour script, press OK and theappropriate tags will be writteninto the HTML or ALX file asappropriate.

Figure 10-38 shows the results of creating an HTML page with an ActiveXcontrol, an HTML Layout control, and a script. Each of these code blocks hasa small glyph in the left-hand gutter; click on it to bring up that element in

the appropriate editor(ActiveX, HTML Lay-out, or ScriptWizard). Saving theHTML file will allowall this useful func-tionality to come tolife in InternetExplorer.

520 � Chapter Ten—Putting OLE DB on the Internet

Figure 10-37

The ScriptWizard inCode Viewmode

Figure 10-38

A very func-tional HTMLpage

Page 540: Learn OLE DB Development With Visual C++ 6.0

Obtaining and Using File Transfer Protocol (FTP) SoftwareObtaining and Using File Transfer Protocol (FTP) Software

to Place COM Elements on the Internetto Place COM Elements on the Internet

Now that you’ve gone to all the trouble of creating COM elements just for useon the Internet and World Wide Web, it seems appropriate to put them wherethey 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 com-mand line in Windows 95 and Windows NT, it seems more reasonable to findone that is Windows-based and nicely laid out. Fortunately, there is a share-ware 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 thecompany that now markets WS_FTP, with the download location for a freeevaluation copy. If you prefer freeware, you can also obtain older freeware

versions of the programfrom many FTP sites usingInfoSeek or Yahoo’s searchfacilities. Also, manyInternet service providers(ISPs) distribute thefreeware version ofWS_FTP to their custom-ers, so check yourcomputer for that file-name, because it is verylikely you already have itinstalled.

At this point, you may be wondering why all this bother! Well, the answer liesin the way the Internet and World Wide Web work. Browsers are designed toretrieve files from web and FTP server computers, not send them. The onlyway to move files around the Internet is FTP; it was one of the original proto-cols and is still the most widely used, since most download requests bybrowser software actually use the FTP protocol. In order to transfer yourhard-written COM components and HTML/ALX files up to the web site youwill be using, you need FTP!

Figure 10-40 shows the WS_FTP client’s startup screen. It is fairly typical ofFTP software, so if you are using another graphical package, it should havethe same functionality, just arranged a little differently.

Chapter Ten—Putting OLE DB on the Internet � 521

Figure 10-39

DownloadingWS_FTP FTPclient

Page 541: Learn OLE DB Development With Visual C++ 6.0

Most of the problems encountered with FTP happen at login, so this sectionwill attempt to anticipate most of them so you won’t have to. First, notice theProfile Name box. It is a very good idea to enter a unique informational namefor the web server you’ll be using and click the Auto Save Config check box sothat your information only has to be entered once. If you choose not to savethe configuration automatically, you can still save it manually from the Filemenu before you close the connection.

Next, note the Host Name box. This is where you enter the Internet addressof the web server you will use. For this example, it is my web provider’saddress, www.tde.com; you would enter your own address, of course. Youcan also enter the “dotted decimal” version, like “158.158.12.140”, but makesure you have it exactly right, or no end of problems will ensue! Do not enterpath information to your own web pages here, or you’ll hopelessly confusethe poor thing.

Below Host Name is Host Type, a drop-down combo box. The UNIX option isstandard, but you can also choose to autodetect the operating system of theweb server, or use one of the other choices. (If your provider is supportingMicrosoft’s Internet Information Server, the operating system may not appearin older versions and must be autodetected.) This is a vital choice; makingthe wrong one will either crash the program or cause it to send bad data toyour server.

The User ID field is your user name for the web server. If the server is foryour personal web pages, it will be your ISP account user name; if it is foryour business, it will be the company account’s name. The Password field isthe 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 inordinary text that anyone can examine if they get access to your computer.

522 � Chapter Ten—Putting OLE DB on the Internet

Figure 10-40

The WS_FTPclient startupscreen

Page 542: Learn OLE DB Development With Visual C++ 6.0

The Account field is optional; it is used for larger servers that have accountnames besides their user name.

The Initial Directories fields are used to store the information about startupdirectories the program will change to, both on the local machine and theremote machine. If either path is invalid, an error message will display butthe connection will not be lost.

The Comment field can be used to store short text descriptions of the locationto make it easier to distinguish between sites with similar names and profiles.

Aside from the Auto Save Config option mentioned earlier, there are twoother possible settings for the login dialog. Anonymous Login is used for FTPsites that allow such connections; it won’t be used for web server interac-tions. The Save Password option allows saving the password in an encryptedtext block inside the normal text of the record; it is relatively safe and nor-mally poses no security risk (not to mention making it a lot easier to use thesystem!).

Once you have filled out all the fields to your satisfaction, press OK and youwill be connected to the server, if possible. There are several situations whereconnecting to the server may be impossible. One is that the server itself isdown, making contact out of the question. Another common problem is traf-fic overload on larger and more popular servers, making getting a connectiondifficult and requiring multiple connection attempts over time. Finally, thenetwork itself sometimes is either impossibly slow or down in some seg-ments; 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 youget a good connection.

Figure 10-41 shows theWS_FTP client performingone of the most vitalchores of maintaining aweb site: adding newdirectories. The procedureis quite simple; navigate inthe right-hand list boxuntil you find the directoryon your web server whereyou need the new direc-tory created. Then pressthe MkDir button, and thedialog box shown in thefigure will appear. Enter avalid name (rememberoperating system

Chapter Ten—Putting OLE DB on the Internet � 523

Figure 10-41

The WS_FTPclient adding aremotedirectory

Page 543: Learn OLE DB Development With Visual C++ 6.0

peculiarities!) and press OK, and the client will attempt to create the direc-tory on the remote computer. If it succeeds, the display will flip to show thenew directory in the list box; otherwise, an error message dialog will appear.Once the directory is created, it can be navigated into, have subdirectoriescreated under it, and so forth, just like any other directory on the remotemachine. Other options available both on the remote and the local computerare explicitly changing the directory with ChgDir and removing a selecteddirectory with RmDir. (Directories cannot be renamed; they have to bedeleted and re-created.) Files can be viewed (downloaded into a viewer, bestwith text files only), executed either locally or remotely (where possible,often not on web servers), deleted, and renamed. In addition, the display canbe refreshed and the actual text of the FTP directory output viewed with theDirInfo command.

There are two other very important functions shown in the figure: the typeoption buttons and the Log Window. The type options are either ASCII orBinary, with an Auto check box as well. The default mode is Binary, which isan absolute requirement for all ZIP, CAB, EXE, DLL, OCX, and image files;they are all binary images and must be transmitted exactly as found. Theother mode, ASCII, is used only for text files, but has an advantage overbinary: If the source computer is a Windows machine but the server is a UNIXcomputer, text files are handled differently between the two computers,which can result in inconsistencies in the display and manipulation of thetext. Choosing ASCII mode will allow the system to determine which formatis needed on each end, and add or remove control characters as needed. TheAuto check box allows the program to attempt to determine for itself whichmode is needed, by checking file extensions and other clues. The best way touse this feature is to keep it at Binary except when sending HTML and ALXfiles.

The Log Window displays the actual text conversation going on between thelocal computer and the web server. It is only of real use to techno-weenieslike the author, but if you start experiencing odd problems with files going upto your web server, this is the first place to look for error or warning mes-sages or unusual events of any sort. Usually your web server administratorwill ask for a copy of this data; you can use the LogWnd button at the top ofthe 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 connec-tion, making a new connection, and setting options for the program, most ofwhich 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 taketo perform this task: First, navigate to the directory on the local computerwhere the files to be stored are located; you can use the tree control or theexplicit ChgDir dialog box. Once there, use Ctrl and Shift to select the filesyou want to upload; then make sure you have the Binary/ASCII type setting

524 � Chapter Ten—Putting OLE DB on the Internet

Page 544: Learn OLE DB Development With Visual C++ 6.0

correct (only use ASCII forHTML, TXT, and ALXfiles). Then press the ->button in the center of thetwo treeview controls.This will cause the uploadto begin.

As each file is uploaded, asmall dialog box willappear as shown in thefigure; it will give the sizeof the file, the total bytesuploaded so far, and avisual gauge of the per-centage of completion.The dialog is destroyed

and re-created for each file, which makes things rather annoying with a lot ofsmall files, because it unfortunately grabs both the mouse and keyboard focuseach time it does this. Therefore, don’t plan on doing anything else while youupload or download with WS_FTP.

Once the upload is complete, the new files will appear in the right-handtreeview control. Note that overwriting is normally silent; make sure you arewhere you want to be before sending files up. Also, be aware that UNIX iscase-sensitive on filenames; MyFile and MYFILE are totally different filesunder UNIX, unlike DOS or Windows. (Windows NT is case-sensitive,however!)

A final caveat regarding pathnames for web servers. Your account on the webserver computer will have a somewhat different directory path than that usedon the Internet or World Wide Web. The reason is something called“aliasing.” It is a trick UNIX and Windows NT administrators use to allowthem to restructure their hard disks at will without breaking thousands ofweb pages every time they do so. Most paths that are appended to a webserver Internet address begin with the tilde character “~”. (For example,http://www.tde.com/~ciupkc/index.html.) The tilde character is thesyntax 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 yourusers think that your web files live in a “ciupkc” directory when they actuallylive 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. Fortu-nately, most FTP servers for web servers look up the account of the userlogging in (since they normally don’t have access to other directories, forobvious reasons.) and place the user in the directory that corresponds to their

Chapter Ten—Putting OLE DB on the Internet � 525

Figure 10-42

The WS_FTPclient upload-ing a file tothe webserver

Page 545: Learn OLE DB Development With Visual C++ 6.0

“tilde” address. Some don’t, however, and in other cases you may need toaccess a different alias, such as with large corporate accounts. The key factorto remember in all of this is that the directory path you observe in the FTPclient is, in most cases, not the one the user will see on the Internet. Consultyour web server administrator for serious difficulties in this area; only theyknow for sure.

Using Internet Explorer to Access COM Over the InternetUsing Internet Explorer to Access COM Over the Internet

and World Wide Weband World Wide Web

At last, you are ready to look at the spiffy COM content you have laboriouslyauthored, compressed, digitally signed, and painstakingly uploaded to yourweb server. Let’s do it!

How?

Internet Explorer!

Figure 10-43 shows InternetExplorer displaying a custom COMcontrol created with Visual C++.Notice that all that is required is totype in the correct URL (UniformResource Locator) for the web page;IE does the rest, downloading theCOM components referenced in theHTML, creating the components inmemory and obtaining any requiredsupport files from the web server orother location. All this is done auto-matically; all the user sees is theneato-keen COM functionality. Andthis is why COM will rule theInternet and WWW! Rather thanhaving to mess around with locating

and downloading endless new plug-ins (many of which require payment andare often buggy), the browser silently and quickly locates the needed COMelements, brings them onto the local hard drive, and runs them. Like HAL inthe movie 2001, Internet Explorer is almost too good a servant!

Fortunately, Microsoft has included several very important configurationoptions in Internet Explorer relating to Active content. To access them, selectView|Options from the main menu bar. Figure 10-44 shows the Security tabof the Options dialog, which is where most of the Active Content featureslive.

526 � Chapter Ten—Putting OLE DB on the Internet

Figure 10-43

Viewing aCOM HTMLpage withInternetExplorer

Page 546: Learn OLE DB Development With Visual C++ 6.0

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 keepyounger children from encountering web sites with inappropriate content. IEuses an industry standard rating system, which most commercial content pro-viders and many individual pages support. The details on it are in the onlinehelp for IE.

The next section is an enhancement to the digital signature system you wereshown how to use earlier in this chapter. Rather than having to download acertificate each time a signed control is encountered, this section allows set-ting a default acceptance for your own signature or that of other individuals(Personal), that of entire web sites (Sites), and those from publishers (Pub-lishers). The increasing level of acceptance does not compromise securitysince all the accepted items must still have a valid digital signature; instead,it removes the time penalty to display repeated signature certificates from thesame person or business when their content is regularly used.

The third section contains the most important settings in regard to Activecontent. This group of four check box controls determines whether your copyof IE will permit downloading of COM elements not already on your localmachine, 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 theremight be reasons for any one of them to be turned off briefly in a securitythreat environment.

The fourth element is contained behind the Safety Level button. Pressing itbrings up the dialog box displayed in Figure 10-45.

Chapter Ten—Putting OLE DB on the Internet � 527

Figure 10-44

IE’s ActiveContentOptionssettings

Page 547: Learn OLE DB Development With Visual C++ 6.0

The default setting is High, which refuses all ActiveX content that does nothave a digital signature. The Medium setting is best used by developers suchas ourselves, and only when we are developing our own work. This settingwill allow non-signed ActiveX content to be downloaded and run, but willwarn first and provide option levels for safety. The Low setting is only appro-priate for completely isolated intranet environments, or those behindextremely tight firewall security. It performs no signature checking and doesnot warn when any content is downloaded and executed.

You have probably heard quite a bit about the “security holes” in InternetExplorer. All of these “holes” require using the browser on Low or Mediumsecurity. None of them will happen on High security! So if in doubt, stay onHigh. (Also be sure you have the latest copy of IE. Microsoft is still giving it

away for free as of this writing, soall it will cost you is a little down-load time and a few megabytes onyour hard drive.)

Figure 10-46 shows the final figurefor this chapter: the Favorites dialogof IE. This dialog is activated when-ever you cruise to a useful web siteand select Add to Favorites from themenu. It allows you to create newFavorites folders, navigate amongexisting ones, and save the page inthe one of your choice, with a modi-fied title if desired. At first glance,this is pretty spiffy, right?

528 � Chapter Ten—Putting OLE DB on the Internet

Figure 10-45

IE’s ActiveXsafety settings

Figure 10-46

IE’s Favoritesdialog

Page 548: Learn OLE DB Development With Visual C++ 6.0

Well, it gets better! It turns out that the Favorites system of IE is nothingmore than a set of directories under WINDOWS\ FAVORITES\ on the harddrive! The actual favorites entries are stored as Internet shortcuts inside thedirectories, just like the shortcuts used for the Start menu or other programgroups. What that means is that you can use your copy of Explorer to manip-ulate the Favorites system yourself! You can add directories, move themaround, and even add and remove items all with Explorer, without ever usingthe Favorites system of IE itself. And if you do choose the Organize Favoritesmenu item, don’t be surprised at how similar the dialog used for this option isto Explorer; it’s actually a junior copy of it.

Why bring up Favorites here? Well, in the process of testing the deployment ofeven 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 thenusing the Favorite shortcut to go back to it can save huge amounts of typing.

Mastering COM Over the InternetMastering COM Over the Internet

Even though there isn’t a thing about Visual C++ or OLE DB programming inthis chapter, I think you’ll agree it is one of the hardest! You’ve been on awhirlwind tour of the Internet aspects of the ActiveX SDK and code signing,ActiveX Control Pad, WS_FTP, and Internet Explorer. You’ve learned how todigitally sign your COM elements and how to create sophisticated HTML andALX pages with ActiveX controls, HTML Layouts, and ActiveX scripting. Youunderstand how to upload files onto your web server with WS_FTP, and howto download them with Internet Explorer, as well as how to determine whattypes of Active content IE will permit under security considerations.

Where We Go From HereWhere We Go From Here

And so we’ve come to the end of our OLE DB journey in this book. Hopefullyyou’ve learned how to start unlocking the power of OLE DB for your applica-tions in both ATL and MFC. You have a thorough reference to OLE DB andADO, which you can refer back to when needed, and a set of clear directionsfor using the user interfaces in ATL and MFC to create OLE DB provider andconsumer projects. You also have four very effective projects that you can tin-ker with to start your OLE DB applications.

But there is much more to OLE DB than the basics we’ve covered here. If Ihad time and space, I’d love to show you all the extra power under the OLEDB hood. You have to go beyond the simple provider and consumer templatesinto 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 forOutlook Express data files for e-mail and newsgroup postings; keep an eye onmy web site (www.ciupkc.com) for details and possible source code.Happy OLE DB coding!

Chapter Ten—Putting OLE DB on the Internet � 529

Page 549: Learn OLE DB Development With Visual C++ 6.0
Page 550: Learn OLE DB Development With Visual C++ 6.0

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

Page 551: Learn OLE DB Development With Visual C++ 6.0

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

532 � Index

Page 552: Learn OLE DB Development With Visual C++ 6.0

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

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

Index � 533

Page 553: Learn OLE DB Development With Visual C++ 6.0

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

<OBJECT> 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

534 � Index

Page 554: Learn OLE DB Development With Visual C++ 6.0

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

Index � 535

Page 555: Learn OLE DB Development With Visual C++ 6.0

On the CDOn the CD

The companion CD-ROM contains complete project source code for all thematerial 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 companionCD-ROM. Change to the lodbpcppweb directory. (You must do this toavoid copying all the utilities to your hard drive.)

Type XCOPY /S*.* followed by the drive and directory you want thefiles copied into, then press Enter. DOS will copy all the files (includingthe web files) to your hard drive. To avoid copying the web portion of theCD, 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 thebook as zip files. The zip files have been created using directory names;to unzip them and create the directory structure for the projects, simplyuse WinZip or PKUnzip with the same “use folder names” or “-d”options.

The CD also includes several third-party applications and utilities for creatingHTML, CDF, OSD, and ASP files; and trial versions of Borland tools.

Warning: Opening the CD package makes this book nonreturnable.