Teach Yourself Database Programming
with Visual C++ 6 in 21 days


Day 17
      Accessing a Data Source with OLE DB



As you learned yesterday, OLE DB is based on the Component Object Model (COM) architecture. Today begins the process of integrating OLE DB into applications. You will explore the relationship between COM and OLE DB and learn how COM technology influences the OLE DB programming model.

NOTE
Much of today's material deals with COM and will help you understand and integrate COM components, including OLE DB, into your applications.

Today you will

Data Consumers and Providers

As you learned yesterday, OLE DB applications in their simplest form are composed of a data provider and a data consumer. A data provider is a COM component that provides an OLE DB-compliant interface. A data consumer is an application or component that uses an OLE DB interface to access a data source. Figure 17.1 shows how OLE DB applications are structured in a give-and-take manner:

Figure 17.1 : The give-and-take nature of OLE DB applications.

OLE DB extends the key concept of the COM architecture. That is, it provides a well-defined and structured mechanism for application components to "talk" with each other in order to access data. An OLE DB data provider is not required to support the complete OLE DB interface; for example, a data provider does not have to support commands. Lowering this barrier makes it easier to write OLE DB data providers.

The interface layer of the COM architecture can tell you which aspects of the OLE DB specification a given data provider supports. By querying a data provider's interfaces, you can determine which portions of the OLE DB specification the data provider supports and how to write an application to take full advantage of that provider.

Interfaces

The idea behind interfaces is best described through a real-world example. A standard electrical wall outlet is a good example of an interface. The top two holes in the outlet provide the power to an electrical device, and the bottom hole provides grounding.

Because an electrical outlet is a standard interface, it can support the power needs of any device, current or future, that conforms to its standards, that is, has a compatible plug (one that fits in the outlet's holes) and can use the standard 120 volts of power. A standard electrical outlet can supply power to a diverse set of devices, from a device as simple as a toaster to something as complex as a personal computer.

Another key aspect of an electrical outlet is the grounding receptacle. As you probably know, not every electrical device uses the ground provided by the outlet, but that doesn't prevent the device from being able to use the outlet. An electrical outlet provides a set of features, just like a well-designed software component. A device doesn't have to use all the features of the interface in order to use the interface.

You learned about COM interfaces in Day 9, "Understanding COM." Interfaces are the key aspect of the COM architecture, as you can see in Figure 17.2. Interfaces describe the functionality provided by the component and also supply the structured mechanism by which the components talk with each other. You can think of a component as a collection of code that supports the functionality described by the interface. The code is separate from the interface, and the details are hidden from the user of the component.

Figure 17.2 : The role of the interface in an application that uses COM components.

If you have any previous experience with object-oriented programming, the preceding description of a COM component should remind you of an object. In fact, a COM component is an object that uses the rules in the COM specification to provide access to the component's interface.

Interface Factoring

One of the most important aspects of COM interfaces is that they don't change. After a COM component interface is published, it remains static. When a new version of a component is released, a new interface is created; however, the old version of the interface is still supported. Like the electrical outlet that has two interfaces, one for two-pronged devices and another for three-pronged devices, a COM component can support multiple interfaces.

A COM component can support any number of interfaces. Multiple interfaces enable older applications to continue to use a COM component that has been upgraded with additional functionality, even though the older applications were developed before the component was upgraded and have no knowledge of the additional features. Figure 17.3 shows how a set of COM components and their interfaces can define an application. Each set of interfaces defines a set of methods specific to it.

Figure 17.3 : The partitioned nature of a COM application.

You can determine the interfaces that a COM component supports by calling the QueryInterface method. When an application determines whether a component supports a specific interface, it is guaranteed the functionality of that interface. Because an interface defines a complete set of functions, interfaces are separated, or factored, by the functionality they support.

OLE DB uses interface factoring extensively. The OLE DB specification requires certain interfaces, but OLE DB objects can support additional functionality. OLE DB consumers use the QueryInterface method to determine the level of functionality an OLE DB object supports.

If you have previous experience with object-oriented programming, you might recognize the term polymorphism. Polymorphism is the use of the same methods when you are accessing different objects. The capability of COM components to support multiple interfaces helps to facilitate polymorphism. Figure 17.4 shows that OLE DB applications designed to support the base level of functionality can also be used with different OLE DB data providers. Various OLE DB providers can be plugged into the same application, like the electrical outlet described earlier, and still be guaranteed to work.

Figure 17.4 : The plug-in orientation of COM and the OLE DB architecture.

Interface Negotiations

How does an application determine which interfaces an object supports? The QueryInterface method of the COM IUnknown interface checks whether an object supports an interface. The QueryInterface method returns a pointer to the interface (in an out parameter) if the object supports it; otherwise, the QueryInterface method places NULL in the out parameter. An application uses this mechanism to determine the interface functionality that a component supports. At a minimum, every COM component must support the IUnknown interface. (Ideally, a component also supports other interfaces, or the component won't have any functionality!) All interfaces are inherited from the IUnknown interface.

The IUnknown Object

The IUnknown interface is declared in the UNKNWN.H header file, which is part of the Win32 SDK. The IUnknown interface is essentially defined as this:

interface IUnknown
{
     virtual HRESULT stdcall QueryInterface(const IID &riid,
                                      void **ppvObject) = 0;
     virtual ULONG stdcall AddRef() = 0;
     virtual ULONG stdcall Release() = 0;
};

The actual declaration looks more complex, but this the essence of it.

NOTE
The stdcall option in the Iunknown definition tells the C++ compiler to use the standard Pascal calling convention. Most Win32 API functions use the Pascal calling convention. Most COM functions use this method, also. With the Pascal calling convention, the function called is responsible for removing calling arguments from the stack before it returns. If the function takes a variable number of arguments, it uses the normal C/C++ calling convention cdecl, and the caller of the function is responsible for removing arguments from the stack.

Because every interface that a COM component supports is inherited from the IUnknown interface, any interface can be used to get to any other interface that a component supports. The QueryInterface method determines whether a component supports an interface, and the AddRef method adds to the reference count of an object (see the following note). The Release method subtracts from the object reference count; when the reference count is 0, the resources used by the component can be released by the system.

NOTE
Reference counting ensures that a COM object does not release its resources (for example, the memory it is using) until the COM object is no longer being used. Every time an object is obtained, it should be sandwiched between an AddRef call to lock the component's resources and a Release call to free up the resource when it's available. Failure to lock a component's resources could cause them to be freed before your application has finished using them. Failure to release a component when your application is finished with it can cause resources to remain locked and associated memory to be unreleased, even after you have finished with the component.

Table 17.1 describes the parameters used by the QueryInterface method:

virtual HRESULT __stdcall QueryInterface(const IID &riid,
     void **ppvObject) = 0;

Table 17.1  The QueryInterface Method Parameters
Parameter
Description
riid_The globally unique identifier (GUID) of the interface being queried.
ppvObject_An address that, on return, will contain a pointer to the object whose interface is queried.

You will see the GUIDs of various interfaces throughout the week. These interface identifiers are generally constants. In the case of OLE DB, these constants are defined in the OLE DB library. If you look back at Listing 16.2, you will see where you used an interface definition. Generally, all interface definitions begin with IID. Listing 16.2 references the IID_IDBInitialize interface identifier. I will explain the OLE DB interfaces in detail as you examine the objects that use them.

The QueryInterface method returns an HRESULT, which can be S_OK if an interface is supported or E_NOINTERFACE if not. Because status codes returned in an HRESULT can vary (that is, multiple status codes for success and failure), you can use the SUCCEEDED and FAILED macros to determine whether the QueryInterface method succeeded or failed.

Listing 17.1 defines a function called CheckInterface. You could use the function in Listing 17.1 in OLE DB consumer applications to discover which interfaces an OLE DB data provider supports. CheckInterface takes a single parameter that defines the interface ID and returns 1 if the interface is supported or 0 if not.


Listing 17.1  The CHECKINTERFACE Function

 1:  int CheckInterface(IUnknown* pInterface, REFIID pIID)
 2:  {
 3:    // Define a pointer to hold the interface being queried
 4:     IUnknown* pChkInterface;
 5:
 6:    // Query for the interface
 7:    HRESULT hr = pInterface->QueryInterface(pIID, pChkInterface);
 8:
 9:  if(SUCCEEDED(hr))
10:  {
11:      pCheckInterface->Release();
12:      return TRUE;
13:  }
14:  else
15:      return FALSE;

You can see from line 1 that the CheckInterface function takes an IUnknown pointer and an interface ID as parameters. The pointer defined in line 4 is used only as a temporary variable in which to store the pointer to the queried interface. Line 7 calls QueryInterface to get and stores the HRESULT in a variable named hr. Line 11 causes the function to return the result of passing hr to the SUCCEEDED macro.

OLE DB Application Flow

Figure 17.5 illustrates the typical flow of an OLE DB application. An Enumerator object determines which OLE DB data source providers are available. Using the data source name returned by the ISourcesRowset and IParseDisplayName interfaces, you can create a DataSource object. The DataSource object defines access to the data source and also defines a security context. Using the IDBCreateSession interface of the DataSource object, the CreateSession method can create a Session object. The Session object provides a logical scope for transactions and permits access to data source row sets and to data source schema information.

Figure 17.5 : The typical flow of an OLE DB application.

The CreateCommand method can use the IDBCreateCommand interface of the Session object to create a Command object that performs provider-specific commands. For example, if the provider is a database that can interpret SQL statements, these commands can be SQL commands. Using the ICommand interface of the Command object, the Execute method can be used to create a Rowset object. A Rowset object permits access to data source data in a tabular form.

Enumerators

The Enumerator object is a good place for you to start exploring the OLE DB classes. The Enumerator object retrieves information regarding the OLE DB providers available on this system. Much of the information about OLE DB providers is stored in the registry. Using the Enumerator object is better than directly accessing the registry, because in the future this information might be stored elsewhere. The Enumerator object abstracts the source of the data provider information from an application, enabling it to work even if a new enumeration method is created.

NOTE
Enumerators and providers are defined in the registry, using the OLE DB enumerator or provider subkey, and under the class ID of the enumerator or provider. Under the HKEY_CLASS_ROOT key in the registry, an enumerator or provider requires the following subkeys to be defined:
EnumOrProvID=DisplayName
EnumOrProvIDID\CLSID=EnumOrProvCLSID

Under the HKEY_CLASS_ROOT\CLSID subkey in the registry, the following subkeys are also required:
EnumOrProvCLSID=DisplayName EnumOrProvCLSID\PROGID=EnumOrProvProgID
EnumOrProvCLSID\VersionIndependentProgID=VerIndProgID
EnumOrProvCLSID\InProcServer32=EnumOrProvDLL
EnumOrProvCLSID\InProcServer32\ThreadingModel=Apartment|Free|
Both
EnumOrProvCLSID\OLEDBEnumerator=Description

The TEnumerator object is defined as supporting the following interfaces:

TEnumerator {
     interface IParseDisplayName;          // Required Interface
     interface ISourcesRowset;             // Required Interface
     interface IDBInitialize;
     interface IDBProperties;
     interface ISupportErrorInfo;
};

TEnumerator is an OLE DB CoType. CoTypes are used to define groups of COM objects that have similar characteristics and that implement certain mandatory interfaces.

An OLE DB Enumerator object is required to define the IParseDisplayName and ISourcesRowset interfaces. The other interfaces are optional. The OLE DB SDK provides a root enumerator, which searches the registry for other data providers and enumerators. The CLASS_ID of this root enumerator is CLSID_OLEDB_ENUMERATOR. Before you look at the implementation details of the Enumerator object, a review of the purpose and methods of the interfaces defined by the Enumerator object is in order.

NOTE
The following presentation of individual OLE DB objects also considers the methods that each object's interfaces provide, as well as the parameters required by each method. For a more complete description of each inter-face's methods and parameters, refer to the OLE DB documentation.

The IParseDisplayName Interface

The IParseDisplayName interface converts a displayable name string to a moniker. A moniker contains the information that uniquely identifies a COM object. In a sample application later today, you will see exactly how the display name and moniker are related. The IParseDisplayName interface defines the standard IUnknown interface methods QueryInterface, AddRef, and Release. The interface provides one additional method, ParseDisplayName, which is defined as follows:

HRESULT ParseDisplayName(LPBC pbc, LPOLECHAR lpszDisplayName,
                         unsigned long *pchUsed, LPMONIKER *ppMoniker);

When a display name is converted to a moniker, the BindMoniker method can access the object described by the moniker. Figure 17.6 illustrates how a display name is converted to a moniker and how a moniker is bound to an object and an interface.

NOTE
When an interface method returns an HRESULT, that value can be used to check the success or failure of the method. The SUCCEEDED or FAILED macros should be used to check whether the interface method was successful.

Figure 17.6 : The process of converting a display name to a moniter and binding it to an object and an interface.

The ISourcesRowset Interface

The ISourcesRowset interface accesses row set data from data sources and enumerators. (See Day 18, "Querying a Data Source," and Day 19, "Navigating the Result of a Query," for more on row sets.) For now you will learn enough about the ISourcesRowset interface to use the Enumerator class. The ISourcesRowset interface defines the standard IUnknown interface methods QueryInterface, AddRef, and Release. The interface provides one additional method, GetSourcesRowset, which is defined as follows:

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

From an Enumerator object, the GetSourcesRowset method returns tabular information about the available data sources. Table 17.2 displays the field names, types, and descriptions of the row set returned by the GetSourcesRowset method. The columns returned in the row set are read-only; they cannot be changed.

Table 17.2  The Row Set Returned by the GetSourcesRowset Method
Column
Type
Description
SOURCES_NAMEDBTYPE_WSTR The name of the data source.
SOURCES_PARSENAMEDBTYPE_WSTR The parse name of the data source, used by the ParseDisplayName method to create a moniker.
SOURCES_DESCRIPTIONDBTYPE_WSTR The data source description.
SOURCES_TYPEDBTYPE_UI2 A flag describing the type of the source. If the value is DBSOURCETYPE_DATASOURCE, it describes a data source. If the value is DBSOURCETYPE_ENUMERATOR, it describes an enumerator.
SOURCES_ISPARENTDBTYPE_BOOL If the source is an enumerator and the value is TRUE, the enumerator is a parent enumerator. Multiple enumerators can be defined, and a parent enumerator can also be enumerated.

The IDBInitialize Interface

The IDBInitialize interface initializes an enumerator. It is an optional interface for enumerators. The root enumerator provided by the OLE DB SDK doesn't support the IDBInitialize interface. You should use the QueryInterface method to determine whether the Enumerator object you are using supports this interface. The IDBInitialize interface defines the standard IUnknown interface methods QueryInterface, AdddRef, and Release. The interface provides two additional methods: Initialize and Uninitialize. Neither method takes a parameter. Obviously, the Initialize method initializes the enumerator. The Uninitialize method returns the enumerator to an uninitialized state.

The IDBProperties Interface

The IDBProperties interface gets and sets the properties of an Enumerator object. Properties define values that determine the state of an object. Like the IDBInitialize interface, the IDBProperties interface is optional for Enumerator objects. The OLE DB SDK root enumerator doesn't support the IDBProperties interface. Later today in the section on DataSource objects, the IDBProperties interface is discussed briefly and then explained in more detail on Day 20, "Properties, Transactions, and Indexes."

The IDBProperties interface defines the standard IUnknown interface methods QueryInterface, AddRef, and Release. The interface provides three additional methods: GetProperties, GetPropertyInfo, and SetProperties. The GetProperties method retrieves the value of a property. The GetPropertyInfo method returns information about all the properties supported by the enumerator. The SetProperties method sets the value of a property. These methods are defined as follows:

HRESULT GetProperties(ULONG cPropIDSets, const DBPROPIDSET rgPropSets[],
                      ULONG *pcPropSets, DBPROPSET **prgPropSets);
HRESULT GetPropertyInfo(ULONG cPropIDSets, const DBPROPIDSET rgPropSets[],
                        ULONG *pcPropInfoSets,
                           DBPROPINFOSET **prgPropInfoSets,
                        OLECHAR **ppDescription);
HRESULT SetProperties(ULONG cPropNum, DBPROPSET rgPropSets[]);

The ISupportErrorInfo Interface

The ISupportErrorInfo interface determines whether an interface supports Automation error objects. OLE DB Error objects are returned using the same interface as the Automation error objects. Error handling in OLE DB is discussed in more detail on Day 21, "OLE DB Error Handling." The ISupportErrorInfo interface defines the standard IUnknown interface methods QueryInterface, AddRef, and Release. This interface provides one additional method: InterfaceSupportsErrorInfo. The InterfaceSupportsErrorInfo method is defined as follows:

HRESULT InterfaceSupportsErrorInfo(REFIID riid);

If the interface supports error information, S_OK is returned; otherwise, S_FALSE is returned. Remember to use the SUCCEEDED or FAILED macros to check the return value.

Using an Enumerator: A Simple Example

Now that you have a general idea of the interfaces that the Enumerator object supports, you need to know how to obtain and use an Enumerator object to display the data sources it supports. Listing 17.2 defines the EnumTest application. This application accesses an Enumerator object and then uses the ISourcesRowset interface to loop through the data providers available. The data providers and enumerators are accessed, and the display and parse names are printed to cout.

This example also demonstrates the relationship between the display name and parse name (GUID) of a data source provider. The details of how to access a row set are given tomorrow (Day 18). For now, try to get a sense of how the Enumerator object and its associated interfaces are accessed.

To build the application, run Visual Studio and select the File New menu choice. Click the Projects tab and specify a Win32 Console Application. Call the application EnumTest. Click the OK button and then specify that you want to create an empty project; then click the Finish button. After AppWizard runs, create a new C++ source file as part of the project. You can call it whatever you think is appropriate, for example, main.cpp. Enter the code in Listing 17.2 into the source file. Note that the code in lines 19 and 20 should actually be on one line. In other words, line 20 should be appended onto line 19 in your source file. When you build the project, it should compile and link without errors or warnings.


Listing 17.2  The Enumeration Test Application

  1:  #define UNICODE
  2:  #define _UNICODE
  3:
  4:  // Standard Application Includes
  5:  #include <windows.h>
  6:  #include <stdio.h>
  7:  #include <iostream.h>
  8:  #include <tchar.h>
  9:  #include <stddef.h>
 10:  // OLE DB Header Files
 11:  #include <oledb.h>
 12:  #include <oledberr.h>
 13:
 14:  // OLE DB - ODBC Provider Header Files
 15:  #include <msdaguid.h>
 16:  #include <msdasql.h>
 17:
 18:  #define NUMELEM(p1) (sizeof(p1) / sizeof(p1[0]))
 19:  #define ROUND_TO_NEXT_BYTE( Size, Amount )
 20:      (((DWORD)(Size) + ((Amount) - 1)) & ~((Amount) - 1))
 21:  #define COLUMN_ALIGNVAL 8
 22:
 23:  void EnumerateProviders();
 24:
 25:  int main() {
 26:    // Initialize The Component Object Module Library
 27:    CoInitialize(NULL);
 28:
 29:    EnumerateProviders();
 30:
 31:    // Release The Component Object Module Library
 32:    CoUninitialize();
 33:
 34:    return(0);
 35:  };
 36:
 37:  void EnumerateProviders()
 38:  {
 39:       ULONG            i,                       // Counter
 40:       cRows = 0;               // Number of rows returned
 41:       ISourcesRowset*  pISourceRowset = NULL;   // Source Rowset
                                                     // Interface
 42:       IRowset*         pIRowset = NULL;         // Rowset Interface
 43:       IAccessor*       pIAccessor = NULL;       // Accessor Interface
 44:       BYTE*            pData = NULL;            // Data buffer
 45:       DWORD            dwOffset;                // Offset counter
 46:       HACCESSOR        hAccessor = NULL;        // Handle to accesspr
                                                     // interface
 47:       DBBINDING        rgBind[3];               // Data bindings
                                                     // buffer
 48:       HROW             rghRows[256];            // Row handle array
 49:       HROW*            pRows = &rghRows[0];     // Pointer to Row
                                                     // handle array
 50:       CHAR             string[256];
 51:
 52:       // Define A Structure That Represents The
 53:       // Data Elements We Wish To Retrieve
 54:      struct COLUMNDATA {
 55:         DBSTATUS wStatus;     // Column Status
 56:         DWORD dwLength;       // Column Length
 57:         BYTE bData[1];        // Column Data
 58:       };
 59:_
 60:       // Define An Enumerator Type That Defines Each Of The
 61:       // Data Columns Returned By The Source Rowset Interface
 62:       enum enumSOURCES_COLUMNS {
 63:         eid_SOURCES_NAME = 1,
 64:         eid_SOURCES_PARSENAME,
 65:         eid_SOURCES_DESCRIPTION,
 66:         eid_SOURCES_TYPE,
 67:         eid_SOURCES_ISPARENT,
 68:         eid_SOURCES_CLSID,
 69:       };
 70:
 71:       // Define A Tagged Structure That Identifies The
 72:       static struct tagSOURCES
 73:       {
 74:            ULONG     iOrdinal;
 75:            DBTYPE     wType;
 76:            ULONG     cbMaxLen;
 77:       } s_rgSources[3];
 78:
 79:       // Initialize the Source Columns to retrieve
 80:       s_rgSources[0].iOrdinal = eid_SOURCES_NAME;
 81:       s_rgSources[0].wType = DBTYPE_STR;
 82:       s_rgSources[0].cbMaxLen = 64;
 83:       s_rgSources[1].iOrdinal = eid_SOURCES_PARSENAME;
 84:       s_rgSources[1].wType = DBTYPE_WSTR;
 85:       s_rgSources[1].cbMaxLen = 64 * sizeof(WCHAR);
 86:       s_rgSources[2].iOrdinal = eid_SOURCES_TYPE;
 87:       s_rgSources[2].wType = DBTYPE_UI4;
 88:       s_rgSources[2].cbMaxLen = sizeof(ULONG);
 89:
 90:       cout << "Enumerate The Providers\n" << "-------------------\n";
 91:
 92:       // Allocate the Rows Buffer
 93:       memset(rghRows, 0, sizeof(rghRows));
 94:
 95:       // Initialize the OLE DB Root Enumerator
 96:       CoCreateInstance(CLSID_OLEDB_ENUMERATOR, NULL,
 97:            CLSCTX_INPROC_SERVER, IID_ISourcesRowset,
                    (LPVOID*)&pISourceRowset);
 98:
 99:       // Retrieve the SourceRowset
100:       pISourceRowset->GetSourcesRowset(NULL, IID_IRowset, 0, NULL,
101:                                      (IUnknown**)&pIRowset);
102:
103:       // Allocate space for row bindings
104:       memset(rgBind, 0, sizeof(rgBind));
105:
106:       // Obtain access to the Accessor Interface
107:       pIRowset->QueryInterface(IID_IAccessor,
                                       (LPVOID*)&pIAccessor);
108:
109:       // Initialize the column bindings, from the Source Column array
110:       dwOffset = 0;
111:       for(i=0; i< NUMELEM(s_rgSources); i++)
112:       {
113:            // Bind The Value, Length, And Status
114:            rgBind[i].dwPart = DBPART_VALUE | DBPART_LENGTH |
                                     DBPART_STATUS;
115:            // Reminder, This is not a parameter!
116:            rgBind[i].eParamIO = DBPARAMIO_NOTPARAM;
117:            // The ordinal location of the column to retrieve
118:            rgBind[i].iOrdinal = s_rgSources[i].iOrdinal;
119:            // Set the column type
120:            rgBind[i].wType = s_rgSources[i].wType;
121:            // Set the offset length of the column for the value
                // in the buffer
122:            rgBind[i].obValue = dwOffset + offsetof(COLUMNDATA,bData);
123:            // Set the offset length of the column for the length
                // in the buffer
124:            rgBind[i].obLength = dwOffset + offsetof
                     (COLUMNDATA,dwLength);
125:            // Set the offset length of the column for the status
                // in the buffer
126:            rgBind[i].obStatus = dwOffset + offsetof
                     (COLUMNDATA,wStatus);
127:            // Set the maximum column length
128:            rgBind[i].cbMaxLen = s_rgSources[i].cbMaxLen;
129:            // Set the source for the data buffer allocation,
                // in this case the
130:            // Enumerator client
131:            rgBind[i].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
132:            // Set to the next column
133:            dwOffset += rgBind[i].cbMaxLen + offsetof
                     ( COLUMNDATA, bData );
134:            // Round The Offset to the next byte
135:            dwOffset = ROUND_TO_NEXT_BYTE( dwOffset, COLUMN_ALIGNVAL );
136:       }
137:
138:       // Create The RowSet accessor
139:       pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA,
                                        NUMELEM(s_rgSources),
140:            rgBind, dwOffset, &hAccessor, NULL);
141:
142:       // Retrieve the providers
143:       if( SUCCEEDED(pIRowset->GetNextRows(NULL, 0, 256, &cRows,
                                                 &pRows)) )
144:       {
145:          // Allocate block of memory to retrieve the row data into.
146:          pData = new BYTE[dwOffset];
147:
148:          // Loop over the rows of data, collecting providers 
149:          // and discarding enumerators..
150:          for(i=0; (i<cRows) && (i<256); i++)
151:          {
152:             // Allocate the data buffer
153:             memset(pData, 0, dwOffset);
154:
155:             // Get the row set data
156:             pIRowset->GetData(rghRows[i], hAccessor, pData);
157:             // Is it a data source? Not an Enumerator!
158:             if( *((ULONG*)(pData + rgBind[2].obValue)) ==
159:                     DBSOURCETYPE_DATASOURCE )
160:             {
161:                // Convert the Parsename from Unicode to
                    // a standard string
162:                WideCharToMultiByte(CP_ACP, 0,
163:                                    (WCHAR *)(pData + rgBind[1].
                                                     obValue),
164:                                    wcslen((WCHAR *)(pData +   rgBind[1].
                                                           obValue)),
165:                                    string, 256, NULL, FALSE);
166:                string[wcslen((WCHAR *)(pData + rgBind[1].obValue))]
                       = '\0';
167:                cout << "Provider # " << i << "\n\tName: " <<
168:                  (CHAR*)(pData + rgBind[0].obValue) <<
                        "\n\tParse Name: " <<
169:                  string << "\n";
170:                }
171:            }
172:       };
173:
174:       // Free the Data buffer
175:       if( pData )
176:            delete[] pData;
177:
178:       // Free the Accessor Interface
179:       if( pIAccessor )
180:            pIAccessor->Release();
181:
182:       // Free the Rowset Interface
183:       if( pIRowset )
184:            pIRowset->Release();
185:
186:       // Free the SourceRowset Interface
187:       if( pISourceRowset )
188:            pISourceRowset->Release();
189:  };

Lines 1 and 2 of Listing 17.2 define this as a Unicode application. Lines 5-16 include the various required headers. Lines 25-35 constitute the main function for the application. Note the calls to CoInitialize and CoUninitialize in the main function. Lines 37-189 constitute the EnumerateProviders function. Lines 39-88 define variables and structures the function will use. Lines 95-101 create an instance of the OLE DB root enumerator and call its GetSourcesRowset function. Lines 103-140 create an Accessor and bind it to variables in the function. Lines 142-172 call the Rowset GetNextRows function to get the providers and display them. Lines 174-188 do the appropriate cleanup.

Note that the error checking in Listing 17.2 is minimized for brevity. The value of the HRESULTs needs to be checked when you are querying for an interface.

Listing 17.3 shows the output of the EnumTest application. The output will vary, based on which OLE DB providers are installed on the system.


Listing 17.3  Output from ENUMTEST

 1:  Enumerate The Providers
 2:  -----------------------
 3:  Provider # 0
 4:      Name: SampProv
 5:      Parse Name: {E8CCCB79-7C36-101B-AC3A-00AA0044773D}
 6:  Provider # 1
 7:      Name: ADsDSOObject
 8:      Parse Name: {549365d0-ec26-11cf-8310-00aa00b505db}
 9:  Provider # 2
10:      Name: MSDataShape
11:      Parse Name: {3449A1C8-C56C-11D0-AD72-00C04FC29863}
12:  Provider # 3
13:      Name: MSPersist
14:      Parse Name: {7C07E0D0-4418-11D2-9212-00C04FBBBFB3}
15:  Provider # 4
16:      Name: Microsoft.Jet.OLEDB.3.51
17:      Parse Name: {dee35060-506b-11cf-b1aa-00aa00b8de95}
18:  Provider # 5
19:      Name: MSDAOSP
20:      Parse Name: {dfc8bdc0-e378-11d0-9b30-0080c7e9fe95}
21:  Provider # 6
22:      Name: MSDAORA
23:      Parse Name: {e8cc4cbe-fdff-11d0-b865-00a0c9081c1d}
24:  Provider # 7
25:      Name: SQLOLEDB
26:      Parse Name: {0C7FF16C-38E3-11d0-97AB-00C04FC2AD98}
27:  Provider # 8
28:      Name: MSDASQL
29:      Parse Name: {c8b522cb-5cf3-11ce-ade5-00aa0044773d}

The DataSource Object

The discussion of OLE DB objects continues with a review of the DataSource object. You create the DataSource object by binding a moniker returned from the Enumerator class or directly calling the CoCreateInstance, using the appropriate CLSID. The DataSource object abstracts the actual data source you want to access. As shown in Figure 17.5, the DataSource object creates a session, which can then create commands and row sets. When a DataSource object is created, if the data provider supports the appropriate interface, it can be persisted (that's a fancy object-oriented way of saying that the object is going to be saved) to a file. The OLE DB CoType for the DataSource object is TDataSource. The TDataSource object is defined as supporting the following interfaces:

TDataSource {
     interface IDBCreateSession;     // Required Interface
     interface IDBInitialize;        // Required Interface
     interface IDBProperties;        // Required Interface
     interface IPersist;             // Required Interface
     interface IDBDataSourceAdmin;
     interface IDBInfo;
     interface IPersistFile;
     interface ISupportErrorInfo;
};

An OLE DB DataSource object is required to define the IDBCreateSession, IDBInitialize, IDBProperties, and IPersist interfaces. The other interfaces are optionally supported. The OLE DB SDK supplies an ODBC OLE DB provider. If you look at the data providers enumerated in the previous example, you can see this data provider listed as MSDASQL; the class ID constant for the MSDASQL data provider is CLSID_MSDASQL.

This section starts with a survey of interfaces that the DataSource object supports and then shows you how to use the DataSource object to connect to an ODBC data source, including how to specify the ODBC data source name and security context. Earlier today, I discussed the IDBInitialize, IDBProperties, and ISupportErrorInfo interfaces in conjunction with the Enumerator object. (Unlike the Enumerator object though, the DataSource object requires the IDBInitialize and IDBProperties interfaces.)

The IDBCreateSession Interface

The IDBCreateSession interface creates a new session with a data source. A session creates a scope for transactions and provides a mechanism for creating commands and row sets. The IDBCreateSession interface defines the standard IUnknown interface methods QueryInterface, AddRef, and Release. The interface provides one additional method, CreateSession, which is defined as follows:

HRESULT CreateSession(IUnknown pAggInterface, REFIID riid,
                      IUnknown **ppDBSession);

The CreateSession method creates a Session object, which I cover in more depth tomorrow. An OLE DB DataSource object is required to support the IDBCreateSession interface.

NOTE
The IDBDataSourceAdmin interface manipulates the data sources themselves (that is, where the data is actually stored), not the DataSource objects.

The IDBDataSourceAdmin Interface

The optional IDBDataSourceAdmin interface creates, modifies, and deletes data sources.

The IDBDataSourceAdmin interface defines the standard IUnknown interface methods QueryInterface, AddRef, and Release. The interface provides four additional methods: CreateDataSource, DestroyDataSource, GetCreationProperties, and ModifyDataSource. These interfaces are defined as follows:

HRESULT CreateDataSource(ULONG cPropertySets, DBPROPSET rgPropertySets[],
                       IUnknown* pUnkOuter, REFIID riid,
                       IUnknown** ppSession);
HRESULT DestroyDataSource();
HRESULT GetCreateProperties ( ULONG cPropertyIDSets,
                            const DBPROPIDSET rgPropertyIDSets[],
                              ULONG* pcPropertyInfoSets,
                            DBPROPINFOSET** prgPropertyInfoSets,
                              OLECHAR** ppDescBuffer);
HRESULT ModifyDataSource(ULONG cPropSets, DBPROPSET rgPropSets[]);

The CreateDataSource method creates a new data source, and the DataSource object is initialized to access the new data source. The DestroyDataSource method deletes the current data source. When a data source is deleted, the DataSource object is returned to an uninitialized state. The GetCreationProperties accesses the properties that describe the state of the DataSource object. The ModifyDataSource method modifies the current data source according to the set of new properties specified.

The IDBInfo Interface

The optional IDBInfo interface determines the keywords and literals that the data source uses to create data source commands. The IDBInfo interface defines the standard IUnknown interface methods QueryInterface, AddRef, and Release. The interface provides two additional methods: GetKeyWords and GetLiteralInfo. These methods are defined as follows:

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

The GetKeywords method _returns a string of comma-separated keywords that the data source supports. Table 17.3 lists the keywords recognized by OLE DB. The GetLiteralInfo method determines the additional information about literals supported in command strings, such as a % used to match zero, or more, characters in a SQL LIKE clause. Tomorrow, in more depth, I will explain the SQL query language and the process of creating commands.

Table 17.3  The Keywords Recognized by OLE DB
ABSOLUTE
ACTION
ADD
ALLALLOCATEALTER
ANDANYARE
ASASCASSERTION
ATAUTHORIZATIONAVG
BEGINBETWEENBIT
BIT_LENGTHBOTHBY
CASCADECASCADEDCASE
CASTCATALOGCHAR
CHARACTERCHAR_LENGTH CHARACTER_LENGTH
CHECKCLOSECOALESCE
COLLATECOLLATIONCOLUMN
COMMITCONNECTCONNECTION
CONSTRAINTCONSTRAINTS CONTINUE
CONVERTCORRESPONDING COUNT
CREATECROSSCURRENT
CURRENT_DATECURRENT_TIME CURRENT_TIMESTAMP
CURRENT_USERCURSORDATE
DAYDEALLOCATEDEC
DECIMALDECLAREDEFAULT
DEFERRABLEDEFERREDDELETE
DESCDESCRIBEDESCRIPTOR
DIAGNOSTICSDISCONNECT DISTINCT
DISTINCTROWDOMAINDOUBLE
DROPELSEEND
END-EXECESCAPEEXCEPT
EXCEPTIONEXECEXECUTE
EXISTSEXTERNALEXTRACT
FALSEFETCHFIRST
FLOATFORFOREIGN
FOUNDFROMFULL
GETGLOBALGO
GOTOGRANTGROUP
HAVINGHOURIDENTITY
IMMEDIATEININDICATOR
INITIALLYINNERINPUT
INSENSITIVEINSERTINT
INTEGERINTERSECTINTERVAL
INTOISISOLATION
JOINKEYLANGUAGE
LASTLEADINGLEFT
LEVELLIKELOCAL
LOWERMATCHMAX
MINMINUTEMODULE
MONTHNAMESNATIONAL
NATURALNCHARNEXT
NONOTNULL
NULLIFNUMERICOCTET_LENGTH
OFONONLY
OPENOPTIONOR
ORDEROUTEROUTPUT
OVERLAPSPARTIALPOSITION
PRECISIONPREPAREPRESERVE
PRIMARYPRIORPRIVILEGES
PROCEDUREPUBLICREAD
REALREFERENCESRELATIVE
RESTRICTREVOKERIGHT
ROLLBACKROWSSCHEMA
SCROLLSECONDSECTION
SELECTSESSIONSESSION_USER
SETSIZESMALLINT
SOMESQLSQLCODE
SQLERRORSQLSTATESUBSTRING
SUMSYSTEM_USERTABLE
TEMPORARYTHENTIME
TIMESTAMPTIMEZONE_HOUR TIMEZONE_MINUTE
TOTRAILINGTRANSACTION
TRANSLATETRANSLATION TRIGGER
TRIMTRUEUNION
UNIQUEUNKNOWNUPDATE
UPPERUSAGEUSER
USINGVALUEVALUES
VARCHARVARYINGVIEW
WHENWHENEVERWHERE
WITH  
YEARZONE 

The IPersist Interface

The IPersist interface is the base interface for other persist-type interfaces. OLE objects can typically support IPersistStorage, IPersistStream, and IPersistFile interfaces. The IPersist interface is like a base class in that it's not usually used directly. The DataSource object can optionally support the IPersistFile interface described next.

The IPersistFile Interface

The IPersistFile interface saves and retrieves a DataSource object to and from a file. Although the IPersist interface is required, the IPersistFile interface is optional.
The IPersistFile interface defines the standard IUnknown interface methods QueryInterface, AddRef, and Release. The interface provides five additional methods: IsDirty, Load, Save, SaveCompleted, and GetCurFile. These methods are defined as follows:

HRESULT GetCurFile(LPOLESTR *ppszFileName);
HRESULT IsDirty();
HRESULT Load(LPCOLESTR pszFilename, DWORD dwFileMode);
HRESULT Save(LPCOLESTR pszFileName, BOOL fCurrentFile);
HRESULT SaveCompleted(LPCOLESTR pszFileName);

The GetCurFile method returns the path of the last file used to Save or Load. If no current working file exists, the GetCurFile method returns the default save filename for the data source. The IsDirty method returns S_OK if the current DataSource object has changed since the last time it was saved to a file; otherwise, IsDirty returns S_FALSE. Both S_FALSE and S_OK will resolve to TRUE when you use the SUCCEEDED macro. You will need to use SUCCEEDED and then explicitly check for S_OK. If IsDirty returns anything other than S_OK, you should save the object.

The Load method retrieves the DataSource object from a file. The first parameter specifies the full pathname to the file, and the second parameter specifies the mode to use to read the file. Table 17.4 summarizes and describes the mode value constants.

The Save method saves the DataSource object to a file. The first parameter of the Save method specifies the full pathname to the file (if the filename is NULL, the current working file is used), and the second Boolean parameter signals whether the filename specified should now be considered the current working file. The SaveCompleted method determines whether the last Save command has been completed. The SaveCompleted method is not usually used.

Table 17.4  A Summary of Mode Value Constants
Flag
Description
 Save Type Flag Group
STGM_DIRECTChanges are saved as they occur.
STGM_TRANSACTEDChanges are saved and only written when a commit operation is specified.
STGM_SIMPLEProvides faster saves when used with compound flags.
 Read/Write Flag Group
STGM_READRead-only flag.
STGM_WRITEWrite-enabled flag.
STGM_READWRITERead-enabled and write-enabled flag.
 Sharing Flag Group
STGM_SHARE_DENY_NONEIf the file is already opened by another user, access will not be denied.
STGM_SHARE_DENY_READDenies other users the ability to read the file while it's opened.
STGM_SHARE_DENY_WRITEDenies others the ability to write the file if it's opened by another user.
STGM_SHARE_EXCLUSIVEA combination of the STGM_SHARE_DENY_READ and STGM_SHARE_DENY_WRITE flags.
 Priority Flag Group
STGM_PRIORITYOpen the file for priority access; no one else can commit changes while the file is opened in this mode.
 Delete Flag Group
STGM_DELETEONRELEASEUsed primarily for temporary files. The file is deleted when the associated object is deleted.
FlagDescription
 Creation Flag Group
STGM_CREATEIf there is an existing file, delete it before any changes are saved.
STGM_CONVERTCreate a new data object and preserve any existing data.
STGM_FAILIFTHEREDon't create the file if an existing file has the same name.
STGM_NOSCRATCHDon't buffer the file in scratch space (applicable only under Windows 95).

NOTE
File mode constants can be combined, but only one mode constant from each group can be used.

Connecting to a DataSource Object

The last activity for today is to tie together the new information with the concepts you learned at the end of the day yesterday. You will create another basic OLE DB application, which builds upon the code you wrote in Listing 16.2.

Yesterday's example simply created and released a DataSource object. Although you are not yet ready to do anything useful, such as access the data contained in a data source (see Days 18 and 19), you can build on the earlier example. Today's application uses the OLE DB ODBC data provider to access a data source. This time, you use the OLE DB ODBC data source provider object's properties to specify the appropriate connection information: the data source name, username, and password.

Before looking at the code, you need to understand the steps necessary for building an OLE DB-enabled application. The first step, mentioned yesterday, is to initialize the necessary COM-related DLLs. The CoInitialize and CoUninitialize methods load and release the appropriate COM-related DLLs. You must call these methods at the start and end of any application that uses COM components.

After you initialize the COM environment, the next step is to create access to the DataSource object's IDBInitialize interface from the OLE DB ODBC data provider. You could use the Enumerator object, discussed earlier, to find the OLE DB ODBC data source provider, but if you know which provider you want to access, you can directly access the interface by calling the CoCreateInstance method. The definition of the CoCreateInstance method is

STDAPI CoCreatInstance(REFCLSID rclsid, LPUNKNOWN pAggInterface,
                       DWORD dwClsContext, REFIID riid,
                         LPVOID *ppInterface);
NOTE
When you are specifying an interface reference ID, you specify the constants by using the prefix IID before the interface name.

The rclsid parameter specifies the class identifier of the COM object you want to use. To connect to the OLE DB ODBC data provider, you can use the class ID CLSID_MSDASQL. The pAggInterface parameter specifies whether this object is part of an aggregate. For this example, the object is not part of an aggregate, so you can specify NULL. The dwClsContext specifies the context for running the COM object. This application uses the CLSCTX_INPROC_SERVER, which specifies that the COM object runs in the same process space as the caller. The riid specifies the interface reference ID. You want to access the IDBInitialize interface, so you should use the IID_IDBInitialize constant. The ppInterface parameter specifies the variable that retrieves the interface.

The OLE DB ODBC Provider

As I mentioned earlier, the OLE DB ODBC provides access to an ODBC data source. For your reference, Table 17.5 lists the interfaces that the OLE DB ODBC data source provider supports. Only the OLE DB objects that can use these interfaces (either because they are mandatory or optional) support them.

Table 17.5  Interfaces Supported by the OLE DB ODBC Data Source Provider
Interface
IaccessorIColumnsInfo
IcolumnsRowsetICommand
IcommandPrepareICommandProperties
IcommandTextICommandWithParameters
IconnectionPointIConnectionPointContainer
IconvertTypeIDBCreateCommand
IDBCreateSessionIDBInfoIDBInitialize
IDBPropertiesIDBSchemaRowset
IerrorLookupIGetDataSource
ImultipleResultsIOpenRowset
IpersistFileIRowset
IrowsetChangeIRowsetIdentity
IRowsetInfoIRowsetLocate
IRowsetResynchIRowsetUpdate
ISessionPropertiesISourcesRowset
ISupportErrorInfoITransaction
ItransactionLocalITransactionOptions_

NOTE
The IPersistFile interface is supported only for the DataSource object.

Initialization Properties Used

The DataSource object of the OLE DB ODBC data source provider must be initialized before it can be used. Table 17.6 lists the properties that the IDBInitialize interface uses to specify the prompt mode, data source name, username, and password needed to access the ODBC data source. The SetProperties method of the IDBProperties interface sets the properties necessary to access the ODBC data source.

Table 17.6  The Initialization Properties Used by the OLE DB ODBC Data Provider
Property
Description
DBPROP_AUTH_PASSWORDThe password required to access the ODBC data source
DBPROP_AUTH_USERIDThe user ID required to access the ODBC data source
DBPROP_INIT_DATASOURCEThe name of the ODBC data source
DBPROP_INIT_HWNDThe HWND argument used by SQLDriverConnect
DBPROP_INIT_LOCATIONThe name of the server used by SQLDriverConnect
DBPROP_INIT_MODEThe connection mode, either DB_MODE_READ or DB_MODE_READWRITE
DBPROP_INIT_PROMPTSpecifies the prompting mode used when connecting to the data source
DBPROP_INIT_PROVIDERSTRINGThe complete ODBC connection string
DBPROP_INIT_TIMEOUTSpecifies the time-out value for the login

Example: Connecting to an OLE DB ODBC Data Source

Listing 17.4 shows the ODBCTEST.CPP source code. This example extends the example in Listing 16.2 by setting the properties required to access the ODBC data source.

To build the application, run Visual Studio and select the File New menu choice. Click the Projects tab and specify a Win32 Console Application. Call the application ODBCTEST. Click the OK button and then specify that you want to create an empty project; click the Finish button. After AppWizard runs, create a new C++ source file as part of the project. You can call it whatever you think is appropriate, ODBCTEST.CPP, for example. Enter the code shown in Listing 17.4 into the source file.

TIP
You can modify this example to set the properties that specify the connection context for the username, password, and data source name to values that are appropriate for other ODBC data sources set up on your system.

You need to change the input libraries for the linker to

oledbd.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib
advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib

You do this on the Link tab under the Project Settings menu. When you build the project, it should compile and link without errors or warnings._


Listing 17.4  A Sample Application That Connects to an ODBC Data Source

 1:  #define DBINITCONSTANTS
 2:
 3:  // Standard Application Includes
 4:  #include <windows.h>
 5:  #include <stdio.h>
 6:  #include <tchar.h>
 7:  #include <stddef.h>
 8:  #include <iostream.h>
 9:
10:  // OLE DB Header Files
11:  #include <oledb.h>
12:  #include <oledberr.h>
13:
14:  // OLE DB - ODBC Provider Header File
15:  #include <msdasql.h>
16:
17:  void main() {
18:    IDBInitialize *pIDBInitialize = NULL;
19:    IDBProperties *pIDBProperties;
20:    DBPROP         InitProperties[4];
21:    DBPROPSET      rgInitPropSet[1];
22:    int            i;
23:
24:    // Initialize The Component Object Module Library
25:    CoInitialize(NULL);
26:
27:    // Obtain Access To The OLE DB - ODBC Provider
28:    CoCreateInstance(CLSID_MSDASQL, NULL, CLSCTX_INPROC_SERVER,
29:                     IID_IDBInitialize, (void **) &pIDBInitialize);
30:
31:    // Initialize the property values that are the same for each
32:    // property
33:    for (i = 0; i < 4; i++ ) {
34:         VariantInit(&InitProperties[i].vValue);
35:         InitProperties[i].dwOptions = DBPROPOPTIONS_REQUIRED;
36:         InitProperties[i].colid = DB_NULLID;
37:    }
38:    
39:    // level of prompting that will be done
       // to complete the connection process
40:    InitProperties[0].dwPropertyID = DBPROP_INIT_PROMPT;
41:    InitProperties[0].vValue.vt = VT_I2;
42:    InitProperties[0].vValue.iVal = DBPROMPT_NOPROMPT;
43:
44:    // Specify the User Name
45:    InitProperties[1].dwPropertyID = DBPROP_AUTH_USERID;
46:    InitProperties[1].vValue.vt = VT_BSTR;
47:    // Note: The L cast directive casts the string into a UNICODE 
// string
48:    InitProperties[1].vValue.bstrVal = SysAllocString((LPOLESTR)L"");
49:
50:    // Specify the appropriate Password
51:    InitProperties[2].dwPropertyID = DBPROP_AUTH_PASSWORD;
52:    InitProperties[2].vValue.vt = VT_BSTR;
53:    InitProperties[2].vValue.bstrVal = SysAllocString((LPOLESTR)L"");
54:
55:    // Specify the Data Source name
56:    InitProperties[3].dwPropertyID = DBPROP_INIT_DATASOURCE;    
57:    InitProperties[3].vValue.vt = VT_BSTR;
58:    InitProperties[3].vValue.bstrVal = SysAllocString((LPOLESTR)
                                                           L"OrdersDb");
59:
60:    rgInitPropSet[0].guidPropertySet = DBPROPSET_DBINIT;
61:    rgInitPropSet[0].cProperties = 4;
62:    rgInitPropSet[0].rgProperties = InitProperties;
63:
64:    // set initialization properties
65:    pIDBInitialize->QueryInterface(IID_IDBProperties,
                                        (void **)&pIDBProperties);
66:    pIDBProperties->SetProperties(1,rgInitPropSet);
67:    pIDBProperties->Release();
68:
69:    // Call the Initialize method to establish the connection to
70:    // the ODBC data source specified above
71:    HRESULT hr = pIDBInitialize->Initialize();
72:
73:    if SUCCEEDED(hr)
74:    {
75:      ::MessageBeep(MB_OK);
76:    }
77:
78:    // This Is Where You Would Utilize OLE DB to execute commands and
79:    // access row sets
80:
81:    // Free Up Allocated Memory
82:    pIDBInitialize->Uninitialize();
83:    pIDBInitialize->Release();
84:
85:    // Release The Component Object Module Library
86:    CoUninitialize();
87:  };_

Lines 20 and 21 in Listing 17.4 define two variables of the type DBPROPSET and DBPROP. The DBPROPSET structure holds the set of properties, and the DBPROP structure holds an individual property. The values of these variables are set in lines 31-62. After the properties are set to the appropriate values, they are used by the IDBProperties interface in lines 64-67. The SetProperties method specifies the appropriate security context for access to the ODBC data source. After the properties are set, the Initialize method of the IDBInitialize interface is called in line 71, and a connection is made to the ODBC data source. Line 75 calls MessageBeep if the connection was made successfully.

Listing 17.4 minimizes the error checking for code brevity. In your code, you should check the return values from CoCreateInstance and QueryInterface to be sure that the interface pointers have been successfully returned.

Tomorrow you learn how to create a session, create commands, and access data source row sets.

Summary

The major components of OLE DB are data consumers and data providers. OLE DB uses COM extensively. You learned about the COM architecture in more detail, the importance of interfaces in the COM architecture, how OLE DB uses these interfaces, that a COM object can support multiple interfaces at different functional levels, and the role of interface factoring in OLE DB. You also learned how the COM architecture uses the QueryInterface method to determine whether an object supports an interface.

You worked on two sample applications: The first uses the Enumerator object to list the available data providers, and the second uses the OLE DB ODBC data provider to connect to an ODBC data source.

The discussion of OLE DB objects continues tomorrow with a look at the Session and Command objects. In addition, Day 18 begins the discussion of how to use OLE DB objects to obtain access to the data contained in a data source.

Q&A

This section answers some common questions related to today's topics.
Q
How does the COM architecture handle different versions of components?
A
The COM architecture is well suited to versioning. As you might realize, when an interface is published using COM, it cannot be changed. Therefore, if a COM component needs to support some added functionality, the current interfaces supported by the COM component are not changed to support this functionality. Instead, a new interface (with a new interface identifier) is added to support these new features. The QueryInterface mechanism determines the exact level of support provided by a COM component. A new interface should be created under the following conditions:
  • The number, type, or meanings of a method in an interface change.
  • The number, order, return values, or meaning of functions change.
Q
What is the best way to access an OLE DB data provider-through enumerators or directly?
A
You can use the Enumerator object to determine the data providers supported on the computer on which the application is running. This method is good to use in an application in which the end user can dynamically select the data source. If you know the provider your application is required to use, the CoCreateInstance method is a better method to use. It requires less overhead than the enumeration methodology requires. Remember to check the HRESULT of the CoCreateInstance method to ensure that the computer where the application is currently running actually supports the provider of an interface you have chosen.
Q
I know that some of the interfaces provided by OLE DB objects are optional. How do I determine whether the data provider I am using supports a particular interface for a specific object? Can I get this information at runtime?
A
The QueryInterface mechanism determines whether a COM object supports a particular interface. If the QueryInterface method doesn't return S_OK, the interface is not supported. The QueryInterface method is used during runtime to establish a "connection" between an application and a particular interface. An application should call the QueryInterface method before it attempts to rely on any methods supported by that interface.

Workshop

The Workshop quiz questions test your understanding of today's material. (The answers appear in Appendix F, "Answers.") The exercises encourage you to apply the information you learned today to real-life situations.

Quiz

  1. What is the role of a data provider and a data consumer in the OLE DB
    architecture?
  2. What is an interface? How does the COM architecture use an interface?
  3. What is interface factoring?
  4. What method is used to determine whether a COM object supports a particular interface?
  5. Describe the basic flow of information in an OLE DB application.
  6. What is an Enumerator object, and how is it used?
  7. What interfaces are supported by an Enumerator object?
  8. What is a DataSource object, and how is it used?
  9. What interfaces does a DataSource object support?
  10. What methods initialize and release the DLLs required by a COM application?

Exercises

  1. Review the Visual C++ books online documentation (provided with Visual C++) for more information regarding the specifics of COM programming.
  2. The applications developed yesterday and today do not really consider error handling. How would you integrate error handling into the application in Listing 17.4? (Hint: Most of the COM related functions return an HRESULT type value.)

© Copyright, Sams Publishing. All rights reserved.