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


Day 10
      Database Client Technologies and the
      Secrets of ADO



Database client technologies for the Windows platform have rapidly evolved during the past few years. Technologies for doing database client development include ODBC, the MFC ODBC classes, DAO, RDO, OLE DB, and ADO. Each of these technologies can be a useful tool for developing database client applications.

The question of when and where to use each client technology can be very confusing-unless you understand each technology and how it relates to the others. Today, the murky waters of database client technologies will become clear.

After examining the context of database client technologies and the relationships between them, you will spend some time learning more about ADO.

Today you will learn

You won't be writing new code today. Rather, you will concentrate on gaining a deeper understanding of the ADO code you wrote in Day 4, "Retrieving SQL Data Through a C++ API," Day 5, "Adding, Modifying, and Deleting Data," and Day 6, "Harnessing the Power of Relational Database Servers."

An Overview of Database Client Technologies

Database client technologies provide abstractions. That is their purpose. A database is a very complex piece of software. Writing programs to communicate with a database through its native interface can be very complicated. Database client technologies simplify this process.

Database client technologies provide an interface that is less complex than the underlying database. Database client interfaces provide leverage for you, the developer. They enable you to write relatively simple programs that leverage an enormous amount of code (code that resides in the database) to perform very complex tasks.

A good database interface is like a magnifying glass for your code, as shown in Figure 10.1.

Figure 10.1 : A database interface as a code magnifier.

Writing programs to communicate with a database through its native interface not only can be complex but also can result in limited and inflexible applications. An application written to use a particular database's native interface is limited, of course, to using only that particular database. The process of enabling such an application to use another database can be very difficult and time-consuming, if not impossible.

Database client technologies provide a uniform interface for communicating with different and disparate database systems. With modern database client interfaces, you can write a single program that performs complex operations using multiple types of data-bases, as shown in Figure 10.2.

Figure 10.2 : A uniform interface to disparate database systems.

A good database interface magnifies your code and provides a uniform interface to different database systems. In the recent past, several database interfaces have been developed. These database interfaces differ from each other in the things they accomplish and the way they go about them.

The popular database interfaces on the Windows platforms include

You will learn database client technologies in more depth in Days 14-21. However, here is a brief explanation of each technology to give you an understanding of the context of each.

ODBC

ODBC was created in the late '80s and early '90s to provide a uniform interface for writing client software for relational databases. ODBC provides a single API for client applications to work with different databases. Applications that use the ODBC API can communicate with any relational database for which there is an ODBC driver.

Compared to other database interfaces, the ODBC API could be classified as a low-level database interface. The ODBC API enables client applications to configure and control the database at a relatively low level.

Figure 10.3 illustrates the architecture of ODBC.

Figure 10.3 : The ODBC architecture.

ODBC was designed to provide an interface to relational databases. ODBC has become quite popular and is generally accepted as a standard for interfacing with relational database systems.

ODBC is limited to relational databases. Because of the relational nature of ODBC, it's difficult to use ODBC to communicate with non-relational data sources, such as object databases, network directory services, email stores, and so on.

ODBC provides the ODBC Driver Manager (ODBC32.DLL), an import library (ODBC32.LIB), and ODBC header files for the ODBC API. Client applications link with the import library to use the functions exposed by the ODBC Driver Manager. At runtime, the ODBC Driver Manager calls functions in the ODBC drivers (which are also DLLs) to perform operations on the databases, as shown in Figure 10.3.

ODBC does not provide an embedded SQL interface. With embedded SQL, the SQL code is embedded in the application program source code. A precompiler transforms the SQL code at build time into native function calls that call the database's runtime library.

ODBC provides a call-level interface (CLI). A CLI is a special kind of database API. A CLI, like a typical API, provides functions for client applications to call. However, in a CLI, the SQL code in the client application is not precompiled. Rather, the API provides functions that enable the application to send the SQL code to the database at runtime. The SQL code is interpreted at runtime.

ODBC is a nontrivial topic. You will explore the architecture of ODBC (and write some ODBC code) in Day 14, "Legacy Database APIs."

MFC ODBC Classes

ODBC was created to provide a uniform interface to relational databases. However, the ODBC API isn't necessarily simple.

In Visual C++, MFC provides classes that simplify the ODBC API. The MFC ODBC classes make ODBC programming much less complex. You used the MFC ODBC classes in Day 1, "Choosing the Right Database Technology," in Listing 1.4.

The MFC ODBC classes are easier to use than the ODBC API but do not give you the low-level control that the ODBC API offers. Therefore, the MFC ODBC classes could be classified as a high-level database interface. You will learn more about using the MFC ODBC classes in Day 15, "The ODBC API and the MFC ODBC Classes."

DAO

DAO stands for Data Access Objects. Data Access Objects is a set of (COM) Automation interfaces for the Microsoft Access/Jet database engine. DAO talks directly to Access/Jet databases. DAO can also communicate with other databases through the Jet engine, as shown in Figure 10.4.

The COM-based Automation interface of DAO provides more than a function-based API. DAO provides an object model for database programming.

The DAO object model is better suited to object-oriented development than a straight API. Integrating a set of disparate API functions into an object-oriented application typically means that the developer must write her own set of classes to encapsulate the API functions.

Figure 10.4 : DAO architecture.

Rather than provide merely a bunch of functions, DAO provides a set of objects for connecting to a database and performing operations on the data. These DAO objects are easy to integrate into the source code of an object-oriented application.

In addition to including classes for connecting to a database and manipulating data, the DAO object model also encapsulates the structural pieces of an Access database, such as tables, queries, indexes, and so on. This means that DAO also enables you to directly modify the structure, or schema, of Access databases without having to use SQL DDL statements.

DAO provides a useful object model for database programming, but as you can see from Figure 10.4, several layers of software are involved. Note that if you are using DAO to talk to a database server such as Oracle or SQL Server, all the calls into the database and all the data coming out of the database must pass through the Access/Jet engine. This can be a significant bottleneck for applications that use a database server.

DAO is easier to use than the ODBC API but doesn't provide the degree of low-level control afforded by the ODBC API. Therefore, DAO could be classified as a high-level database interface.

There is a set of MFC classes that further simplify the DAO Automation interfaces. The MFC DAO classes are prefixed with CDAO. You can find information on these MFC classes in the Visual C++ documentation. On Day 14, you will learn more about DAO and the MFC DAO classes.

RDO

RDO stands for Remote Data Objects. RDO was originally developed as an abstraction of the ODBC API for Visual Basic programmers. Therefore, RDO is closely tied to ODBC and Visual Basic.

RDO is easier to use than the ODBC API but doesn't offer the low-level control provided by the ODBC API. Therefore, RDO could be classified as a high-level database interface.

Because RDO calls the ODBC API directly (rather than through Jet, like DAO), it can provide good performance for applications that use relational database servers.

RDO can be used with Visual C++ applications by inserting the RemoteData control in the application. The RemoteData control is an OLE Control that can be bound to controls in the application's UI. You can call RDO functions through the RemoteData control's methods. You will learn more about RDO in Day 14.

OLE DB

As I mentioned earlier, OLE DB stands for object-linking and embedding database. The OLE DB name makes more sense as an acronym. You will understand why in a moment.

OLE DB expands on ODBC in two important ways. First, OLE DB provides an OLE-actually, a COM-interface for database programming. Second, OLE DB provides an interface to both relational and nonrelational data sources.

OLE DB provides an OLE (COM) interface. OLE was the original name for COM. When OLE DB was being created, OLE was still used as the name for COM. Since that time, COM has become the name for the foundation of Microsoft's component technology (which you explored yesterday), and OLE has come to be associated with UI components such as OLE Controls (OCX Controls).

Used as an acronym, the OLE DB name invokes an image of OLE/COM and databases, which is accurate. However, the expanded object-linking and embedding database name makes no sense. This is why I say the OLE DB name makes more sense as an acronym.

I think the technology might best be named COM DB instead of OLE DB because OLE DB has little to do with UI components such as OLE Controls. Unfortunately, Microsoft has yet to seek my approval of the OLE DB name and seems committed to using the OLE DB term.

OLE DB's provision of a COM interface for database programming is important because a COM interface can be much more robust and flexible than a traditional call-level interface, such as the ODBC interface. This flexibility can result in better performance and more robust error handling and can enable interfacing with nonrelational data sources.

Like ODBC, OLE DB could be classified as a low-level database API. OLE DB incorporates the functionality of ODBC for relational databases and expands on it by providing access to nonrelational data sources.

There are two kinds of OLE DB software: OLE DB consumers and OLE DB providers. Figure 10.5 illustrates the relationship between OLE DB consumers and OLE DB providers.

Figure 10.5 : OLE DB consumers and providers.

An OLE DB consumer is any application that uses or consumes OLE DB interfaces. For example, any application that you write in C++ and that uses OLE DB to connect to a database server would be an OLE DB consumer.

OLE DB providers are DLLs that implement the OLE DB interfaces and do the actual communication with the data source. OLE DB providers are similar in function to ODBC drivers, except that OLE DB providers implement COM interfaces instead of API functions.

OLE DB furnishes access to any data source for which there is an OLE DB provider. These data sources include email stores, object databases, network directories, and other nonrelational data stores.

As you can see in Figure 10.5, there is an OLE DB provider called MSDASQL.DLL that can talk to ODBC data sources. This is handy for those data sources that have an ODBC driver but don't yet have an OLE DB provider.

OLE DB exposes a set of COM interfaces that can be called from C++ programs. OLE DB doesn't offer an Automation interface.

OLE DB is the future of database client development on Windows. Microsoft's own development efforts are focused on OLE DB. It's unlikely that we will not see any further updates of ODBC. ODBC will stick around in its present form, and all the new database client technology from Microsoft will be applied to OLE DB. OLE DB is the focus of Days 16-21.

ADO

ADO stands for ActiveX Data Objects. ADO is built on top of OLE DB. ADO is an OLE DB consumer. Applications that use ADO use the OLE DB interfaces indirectly.

ADO provides an object model for database programming that's similar to, but more flexible than, DAO's object model. For instance, you can create Recordset objects in ADO without first creating a Connection object (which is something you can't do in DAO).

ADO simplifies OLE DB. OLE DB is large and complex; a program that uses OLE DB must use some complex COM interfaces. ADO is much simpler to use than OLE DB and can be classified as a high-level database interface.

Also, ADO can be used with more programming languages than OLE DB. ADO provides an Automation interface. This enables ADO to be used from scripting languages, such as VBScript and JavaScript. (OLE DB can't be used from scripting languages because scripting languages don't have pointers and therefore can't use COM interfaces.)

You've already used ADO objects in Days 4, 5, and 6 to connect to a database, issue queries, retrieve resultsets, and execute stored procedures. You will use ADO objects more today.

Database Client Technology Summary

The database client technologies and how they relate to each other are shown in Figure 10.6. As you can see, several technologies are available to you for database client development.

Table 10.1 presents the relative strengths and weaknesses of the various database client technologies.

Table 10.1  Comparison of the Database Client Technologies
 

ODBC
MFC
ODBC

DAO

RDO
OLE
DB

ADO
Object model
-
+
+
+
+
++
Nonrelational data sources
-
-
-
-
+
+
Low-level control
+
 
-
-
+
 
Performance
+
 
-
 
++
 
Code-to-functionality ratio
-
 
+
 
-
+

Figure 10.6 : Database client technologies.

In Table 10.1, a plus sign (+) indicates a strength, two plus signs (++) indicate a special strength, a minus sign (-) indicates a weakness, and a blank indicates no particular strength or weakness.

Of all these technologies, OLE DB and ADO have the most promising future. These two technologies are where Microsoft is doing its development work. The other technologies are not being discontinued, per se, but will not be further updated by Microsoft.

OLE DB offers unparalleled power and flexibility for client database programming. However, as you can see from Table 10.1, OLE DB is a low-level interface and requires more code than a high-level interface such as ADO.

ADO offers a flexible yet simple object model with decent performance. This makes ADO the best way to start doing database client development. Next, you will explore ADO and learn how to discover details about its functions.

The Secrets of ADO

The things you are about to learn are not really secrets. They are important pieces of information about ADO that are not very clearly documented. The knowledge you are about to gain here, combined with the existing ADO documentation, should give you what you need to perform any ADO programming task.

Rather than describe all the ADO functions (as in traditional API documentation), I will show you where to find that information and how to use it. The knowledge you gain here will apply not only to ADO but also to all other dual-interface COM servers.

ADO's History

Compared to the other database client technologies, ADO is relatively new. So far, Microsoft has released three versions of ADO: 1.0, 1.5, and 2.0.

The first release, version 1.0, was a subset of the functionality of RDO. It was targeted at developers building Active Server Pages (ASP) for Internet Information Server (IIS).

The next release, version 1.5, was shipped with IIS 4.0 and Internet Explorer (IE) 4.0. It was also included in the Microsoft Data Access Components (MDAC). With version 1.5, ADO became a database interface that rivaled (or exceeded) RDO and DAO in functionality and performance.

The latest release, version 2.0, added new functionality to ADO that is not found in other database client technologies. ADO 2.0 is actually housed in MSADO15.DLL, which is the same filename as the ADO 1.5 DLL. The filename is the same, but additional ADO COM interfaces are implemented in the ADO 2.0 version of the DLL.

The new functionality in ADO 2.0 includes

Before you delve into the latest and coolest features of ADO 2.0, however, you need to understand some of the basics of ADO. You will begin with ADO's use of COM.

ADO and COM

You will recall from Day 9, "Understanding Com," that a COM server can

ADO does all these things as a COM server. ADO is housed in MSADO15.DLL. ADO has a dual interface, meaning it has custom (vtable) interfaces and Automation interfaces, and ADO has a type library, so you can discover the ADO objects and the functions they expose.

The ADO Type Library

You can view the ADO type library by running the OLE-COM Object Viewer. Run the OLE-COM Object Viewer now. It can be found under the Microsoft Visual Studio 6.0 Tools menu, Ole View.

From the File menu, select View TypeLib… and navigate to the MSADO15.DLL file. The MSADO15.DLL file is typically installed in the C:\Program Files\Common Files\ System\ADO directory.

The left pane of the OLE-COM Object Viewer window contains a tree control with the elements of the type library. The right pane of the window shows the Interface Definition Language (IDL) code that the MIDL compiler used to create the type library.

You will recall from yesterday that IDL is basically a language-independent C++ header file. The MIDL compiler, which runs when you build a COM server project in Visual C++, uses the IDL code to build the type library.

As you select elements in the tree control in the left pane of the OLE-COM Object Viewer window, the right pane shows the corresponding IDL code. Scroll down in the tree control and select the interface _Connection element. This is the custom COM interface for the ADO _Connection object. The ITypeLib Viewer window should look like Figure 10.7. The GUID for the _Connection interface might change between releases of ADO, so it might look similar but not identical to Figure 10.7.

The right pane shows the IDL for the ADO _Connection object. This IDL lists the _Connection object's functions and their arguments.

Figure 10.7 : Viewing the ADO type library.

You can find documentation for IDL in the Visual C++ help system. Basically, IDL is like C++ function declarations with the addition of attributes in brackets before each function and each argument. The attributes you will see most frequently are

Expand the tree control under the interface _Connection element. Highlight the Execute function. The right pane will show the IDL code for the ADO _Connection object's Execute function as shown in Figure 10.8.

As you can see, the type library provides documentation for the functions in the COM interfaces of a COM server. You can use the #import directive in Visual C++ to have the compiler read the type library and produce C++ header files that match it. As you will recall, that's what you did in the code you wrote in Days 4, 5, and 6. You will learn more about using #import with ADO in the next section.

Figure 10.8 : Viewing the type library for the Execute function of the ADO _Connection custom interface.

Now scroll up the left pane of the OLE-COM Object Viewer window and expand the dispinterface _Connection element in the tree control. This is the dispatch interface for the ADO _Connection object.

You will recall from yesterday that the dispatch interface (IDispatch) is the COM interface that provides an Invoke function. Programming languages that don't have pointers (but are Automation capable) can call functions in COM servers by passing the DISPID of the function to the IDispatch Invoke function. This technology was originally called OLE Automation but is now called simply Automation (with a capital A).

Select the Execute method under dispinterface _Connection in the OLE-COM Object Viewer. Your window will look like Figure 10.9.

You can see that the IDL for the Execute function in ADO's Automation interface in Figure 10.9 is similar to the IDL for the Execute function in the custom interface (refer to Figure 10.8). The difference between the functions is that the Automation function does not return an HRESULT. Rather, the retval argument in the custom interface is listed as the value returned from the Automation function.

Figure 10.9 : Viewing the type library for the Execute function of ADO _Connection Dispinterface.

When an application uses the custom interface version of the _Connection::Execute function, the application must pass a pointer to a pointer to a Recordset as the fifth argument, and the function returns an HRESULT.

When an application uses #import, however, the _Connection::Execute function returns a pointer to a Recordset, as in the Automation version of the function.

ADO and the #import Directive

Open your ADOMFC1 project. Open the MSADO15.TLH file and the MSADO15.TLI files. You can find the MSADO15.TLH file and the MSADO15.TLI files by navigating to your build output directory (probably Debug). These are the C++ header files that the compiler created from the ADO type library at build time.

These header files contain two types of functions: high-level wrapper functions and low-level direct functions. The high-level wrapper functions have the same name as the functions in the type library. The low-level functions use the function name with a prefix of raw_.

For functions that have a retval argument, a wrapper is created with the same function name but with the retval argument removed and the return type changed to the retval pointer type. You saw the same technique used in the Automation version of the function in the type library shown in Figure 10.9.

MSADO15.TLI contains the inline implementations of the high-level wrapper functions. The low-level functions are invoked by the high-level functions, as shown in Listing 10.1.


Listing 10.1  A High-Level Wrapper Function in MSADO15.TLI

 1:  inline _RecordsetPtr _Connection::Execute ( _bstr_t CommandText,
 2:    VARIANT * RecordsAffected, long Options )
 3:  {
 4:    struct _Recordset * _result;
 5:    HRESULT _hr = raw_Execute(CommandText,
 6:      RecordsAffected, Options, &_result);
 7:    if (FAILED(_hr)) _
 8:      com_issue_errorex(_hr, this, __uuidof(this));
 9:    return _RecordsetPtr(_result, false);
10:  }

The raw_Execute function in line 5 of Listing 10.1 is the low-level function being invoked by this high-level function. You can see that this code calls the low-level function (raw_Execute), checks the return value to see whether it's a failed HRESULT, and then throws an exception if it is. You can call the low-level functions from your code if you want to. This would enable you to evaluate the return values of the functions rather than have to catch exceptions.

MSADO15.TLH contains the declarations of the high-level and low-level functions. MSADO15.TLH also contains the #include statement for Comdef.h, the forward references for the GUIDs in the type library, smart pointer declarations, enumerated types in the type library, and the #include statement for MSADO15.TLI.

Listing 10.2 shows the portion of MSADO15.TLH that contains the forward references for the GUIDs in the type library. The GUID for the _Connection interface might change between releases of ADO, so it might look similar, but not identical, to Listing 10.2.


Listing 10.2  Forward References for the GUIDs in MSADO15.TLH

 1:  //
 2:  // Forward references and typedefs
 3:  //
 4:
 5:  struct __declspec(uuid("00000512-0000-0010-8000-00aa006d2ea4"))
 6:  /* dual interface */ _Collection;
 7:  struct __declspec(uuid("00000513-0000-0010-8000-00aa006d2ea4"))
 8:  /* dual interface */ _DynaCollection;
 9:  struct __declspec(uuid("00000534-0000-0010-8000-00aa006d2ea4"))
10:  /* dual interface */ _ADO;
11:  struct __declspec(uuid("00000504-0000-0010-8000-00aa006d2ea4"))
12:  /* dual interface */ Properties;
13:  struct __declspec(uuid("00000503-0000-0010-8000-00aa006d2ea4"))
14:  /* dual interface */ Property;
15:  struct __declspec(uuid("00000500-0000-0010-8000-00aa006d2ea4"))
16:  /* dual interface */ Error;
17:  struct __declspec(uuid("00000501-0000-0010-8000-00aa006d2ea4"))
18:  /* dual interface */ Errors;
19:  struct __declspec(uuid("00000508-0000-0010-8000-00aa006d2ea4"))
20:  /* dual interface */ _Command;
21:  struct __declspec(uuid("00000515-0000-0010-8000-00aa006d2ea4"))
22:  /* dual interface */ _Connection;
23:  struct __declspec(uuid("0000050e-0000-0010-8000-00aa006d2ea4"))
24:  /* dual interface */ _Recordset;
25:  struct __declspec(uuid("00000506-0000-0010-8000-00aa006d2ea4"))
26:  /* dual interface */ Fields;
27:  struct __declspec(uuid("00000505-0000-0010-8000-00aa006d2ea4"))
28:  /* dual interface */ Field;
29:  struct __declspec(uuid("0000050c-0000-0010-8000-00aa006d2ea4"))
30:  /* dual interface */ _Parameter;
31:  struct __declspec(uuid("0000050d-0000-0010-8000-00aa006d2ea4"))
32:  /* dual interface */ Parameters;
33:  struct __declspec(uuid("00000538-0000-0010-8000-00aa006d2ea4"))
34:  /* interface */ ADODebugging;
35:  struct __declspec(uuid("00000402-0000-0010-8000-00aa006d2ea4"))
36:  /* interface */ ConnectionEventsVtbl;
37:  struct __declspec(uuid("00000403-0000-0010-8000-00aa006d2ea4"))
38:  /* interface */ RecordsetEventsVtbl;
39:  struct __declspec(uuid("00000400-0000-0010-8000-00aa006d2ea4"))
40:  /* dispinterface */ ConnectionEvents;
41:  struct __declspec(uuid("00000266-0000-0010-8000-00aa006d2ea4"))
42:  /* dispinterface */ RecordsetEvents;
43:  struct __declspec(uuid("00000516-0000-0010-8000-00aa006d2ea4"))
44:  /* interface */ ADOConnectionConstruction;
45:  struct /* coclass */ Connection;
46:  struct /* coclass */ Command;
47:  struct /* coclass */ Recordset;
48:  struct __declspec(uuid("00000283-0000-0010-8000-00aa006d2ea4"))
49:  /* interface */ ADORecordsetConstruction;
50:  struct /* coclass */ Parameter;

Listing 10.2 shows the GUIDs for the ADO objects. The GUIDs defined as dual interfaces, as in lines 21 and 22, are the ones you want to pass to CoCreateInstance or CreateInstance to instantiate ADO objects. Other GUIDs are defined here that are not defined as dual interfaces. As in lines 43 and 44, these GUIDs are used internally by ADO.

After the forward references in MSADO15.TLH, you will see the declarations of the smart pointers for each ADO object. Listing 10.3 shows these declarations.


Listing 10.3  Smart Pointer Declarations in MSADO15.TLH

 1:  //
 2:  // Smart pointer typedef declarations
 3:  //
 4:
 5:  _COM_SMARTPTR_TYPEDEF(_Collection, __uuidof(_Collection));
 6:  _COM_SMARTPTR_TYPEDEF(_DynaCollection, __uuidof(_DynaCollection));
 7:  _COM_SMARTPTR_TYPEDEF(_ADO, __uuidof(_ADO));
 8:  _COM_SMARTPTR_TYPEDEF(Properties, __uuidof(Properties));
 9:  _COM_SMARTPTR_TYPEDEF(Property, __uuidof(Property));
10:  _COM_SMARTPTR_TYPEDEF(Error, __uuidof(Error));
11:  _COM_SMARTPTR_TYPEDEF(Errors, __uuidof(Errors));
12:  _COM_SMARTPTR_TYPEDEF(_Command, __uuidof(_Command));
13:  _COM_SMARTPTR_TYPEDEF(_Connection, __uuidof(_Connection));
14:  _COM_SMARTPTR_TYPEDEF(_Recordset, __uuidof(_Recordset));
15:  _COM_SMARTPTR_TYPEDEF(Fields, __uuidof(Fields));
16:  _COM_SMARTPTR_TYPEDEF(Field, __uuidof(Field));
17:  _COM_SMARTPTR_TYPEDEF(_Parameter, __uuidof(_Parameter));
18:  _COM_SMARTPTR_TYPEDEF(Parameters, __uuidof(Parameters));
19:  _COM_SMARTPTR_TYPEDEF(ADODebugging, __uuidof(ADODebugging));
20:  _COM_SMARTPTR_TYPEDEF(ConnectionEventsVtbl,
                           __uuidof(ConnectionEventsVtbl));
21:  _COM_SMARTPTR_TYPEDEF(RecordsetEventsVtbl,
                           __uuidof(RecordsetEventsVtbl));
22:  _COM_SMARTPTR_TYPEDEF(ConnectionEvents, __uuidof(IDispatch));
23:  _COM_SMARTPTR_TYPEDEF(RecordsetEvents, __uuidof(IDispatch));
24:  _COM_SMARTPTR_TYPEDEF(ADOConnectionConstruction, 
                           __uuidof(ADOConnectionConstruction));
25:  _COM_SMARTPTR_TYPEDEF(ADORecordsetConstruction,
                           __uuidof(ADORecordsetConstruction));

The name of the smart pointer that #import generates for use in your code is the name of the first argument (with a Ptr suffix) passed to the _COM_SMARTPTR_TYPEDEF macro. For example, line 13 in Listing 10.3 creates a smart pointer derived class called _ConnectionPtr.

You can see that the second argument passed to the _COM_SMARTPTR_TYPEDEF macro uses the __uuidof keyword. The _COM_SMARTPTR_TYPEDEF macros shown in Listing 10.3 refer to the forward declaration of the GUIDs defined earlier in MSADO15.TLH. The __uuidof keyword is a Microsoft-specific C++ extension and retrieves the GUID for the argument.

Following the smart pointer declarations, MSADO15.TLH contains the enumerated types defined in the ADO type library. Listing 10.4 shows the ConnectModeEnum enumerated type as it appears in MSADO15.TLH.


Listing 10.4  The Enumerated Type ConnectModeEnum Defined in the ADO Type Library

 1:  enum ConnectModeEnum
 2:  {
 3:    adModeUnknown = 0,
 4:    adModeRead = 1,
 5:    adModeWrite = 2,
 6:    adModeReadWrite = 3,
 7:    adModeShareDenyRead = 4,
 8:    adModeShareDenyWrite = 8,
 9:    adModeShareExclusive = 12,
10:    adModeShareDenyNone = 16
11:  };

You will recall in Day 4 that you wrote the code shown in Listings 10.5 and 10.6 to create an instance of the ADO Connection object in your code. (Listing 10.5 duplicates Listing 4.5, and Listing 10.6 duplicates Listing 4.8.)


Listing 10.5.  Changes to the ADOMFC1 Document Header File

 1:  class CADOMFC1Doc : public CDocument
 2:  {
 3:  // Attributes
 4:  public:
 5:      BOOL m_IsConnectionOpen;
 6:      _ConnectionPtr m_pConnection;


Listing 10.6.  Changes to the ADOMFC1 OnNewDocument Function

 1:  BOOL CADOMFC1Doc::OnNewDocument()
 2:  {
 3:    if (!CDocument::OnNewDocument())
 4:      return FALSE;
 5:
 6:    HRESULT hr;
 7:
 8:    try
 9:    {
10:      hr = m_pConnection.CreateInstance( __uuidof( Connection ) );
11:      if (SUCCEEDED(hr))
12:      {
13:        hr = m_pConnection->Open(
14:        _bstr_t(L"Provider=Microsoft.Jet.OLEDB.3.51;
                   Data Source=c:\\tysdbvc\\vcdb.mdb;"),
15:        _bstr_t(L""),
16:        _bstr_t(L""),
17:        adModeUnknown);
18:        if (SUCCEEDED(hr))
19:        {
20:          m_IsConnectionOpen = TRUE;
21:        }
22:      }
23:    }
24:    catch( _com_error &e )
25:    {
26:      // Get info from _com_error
27:      _bstr_t bstrSource(e.Source());
28:      _bstr_t bstrDescription(e.Description());
29:      TRACE( "Exception thrown for classes generated by #import" );
30:      TRACE( "\tCode = %08lx\n", e.Error());
31:      TRACE( "\tCode meaning = %s\n", e.ErrorMessage());
32:      TRACE( "\tSource = %s\n", (LPCTSTR) bstrSource);
33:      TRACE( "\tDescription = %s\n", (LPCTSTR) bstrDescription);
34:    }
35:    catch(...)
36:    {
37:      TRACE( "*** Unhandled Exception ***" );
38:    }
39:
40:    return TRUE;
41:  }

You can see that line 6 of Listing 10.5 defines a _ConnectionPtr instance as a member of the CDocument class. Note that the name _ConnectionPtr matches the argument (with a Ptr suffix) passed to the _COM_SMARTPTR_TYPEDEF macro in line 13 of Listing 10.3.

Line 10 of Listing 10.6 calls the CreateInstance function of the _ConnectionPtr class and passes the GUID for the ADO _Connection class (using the __uuidof keyword). CreateInstance will return a failed HRESULT rather than throw an exception.

Line 17 of Listing 10.6 passes a value in ConnectModeEnum as an argument to the Open function for the Connection object. The Open function will throw an exception if it fails (see the code for the _Connection::Open function in MSADO15.TLI).

Following the enumerated type declarations, MSADO15.TLH contains the declarations of the ADO objects in the type library. Listing 10.7 shows the portion of MSADO15.TLH that contains the declaration for the ADO _Connection object.


Listing 10.7  The ADO _Connection Object Declaration in MSADO15.TLH

  1:  struct __declspec(uuid("00000515-0000-0010-8000-00aa006d2ea4"))
  2:  Connection : _ADO
  3:  {
  4:    //
  5:    // Property data
  6:    //
  7:
  8:    __declspec(property(get=GetConnectionString,
                            put=PutConnectionString))
  9:    _bstr_t ConnectionString;
 10:    __declspec(property(get=GetCommandTimeout,put=PutCommandTimeout))
 11:    long CommandTimeout;
 12:    __declspec(property(get=GetConnectionTimeout,
                            put=PutConnectionTimeout))
 13:    long ConnectionTimeout;
 14:    __declspec(property(get=GetVersion))
 15:    _bstr_t Version;
 16:    __declspec(property(get=GetErrors))
 17:    ErrorsPtr Errors;
 18:    __declspec(property(get=GetDefaultDatabase,put=PutDefaultDatabase))
 19:    _bstr_t DefaultDatabase;
 20:    __declspec(property(get=GetIsolationLevel,put=PutIsolationLevel))
 21:    enum IsolationLevelEnum IsolationLevel;
 22:    __declspec(property(get=GetAttributes,put=PutAttributes))
 23:    long Attributes;
 24:    __declspec(property(get=GetCursorLocation,put=PutCursorLocation))
 25:    enum CursorLocationEnum CursorLocation;
 26:    __declspec(property(get=GetMode,put=PutMode))
 27:    enum ConnectModeEnum Mode;
 28:    __declspec(property(get=GetProvider,put=PutProvider))
 29:    _bstr_t Provider;
 30:    __declspec(property(get=GetState))
 31:    long State;
 32:
 33:    //
 34:    // Wrapper methods for error-handling
 35:    //
 36:
 37:    _bstr_t GetConnectionString ( );
 38:    void PutConnectionString (
 39:        _bstr_t pbstr );
 40:    long GetCommandTimeout ( );
 41:    void PutCommandTimeout (
 42:        long plTimeout );
 43:    long GetConnectionTimeout ( );
 44:    void PutConnectionTimeout (
 45:        long plTimeout );
 46:    _bstr_t GetVersion ( );
 47:    HRESULT Close ( );
 48:    _RecordsetPtr Execute (
 49:        _bstr_t CommandText,
 50:        VARIANT * RecordsAffected,
 51:        long Options );
 52:    long BeginTrans ( );
 53:    HRESULT CommitTrans ( );
 54:    HRESULT RollbackTrans ( );
 55:    HRESULT Open (
 56:        _bstr_t ConnectionString,
 57:        _bstr_t UserID,
 58:        _bstr_t Password,
 59:        long Options );
 60:    ErrorsPtr GetErrors ( );
 61:    _bstr_t GetDefaultDatabase ( );
 62:    void PutDefaultDatabase (
 63:        _bstr_t pbstr );
 64:    enum IsolationLevelEnum GetIsolationLevel ( );
 65:    void PutIsolationLevel (
 66:        enum IsolationLevelEnum Level );
 67:    long GetAttributes ( );
 68:    void PutAttributes (
 69:        long plAttr );
 70:    enum CursorLocationEnum GetCursorLocation ( );
 71:    void PutCursorLocation (
 72:        enum CursorLocationEnum plCursorLoc );
 73:    enum ConnectModeEnum GetMode ( );
 74:    void PutMode (
 75:        enum ConnectModeEnum plMode );
 76:    _bstr_t GetProvider ( );
 77:    void PutProvider (
 78:        _bstr_t pbstr );
 79:    long GetState ( );
 80:    _RecordsetPtr OpenSchema (
 81:        enum SchemaEnum Schema,
 82:        const _variant_t & Restrictions = vtMissing,
 83:        const _variant_t & SchemaID = vtMissing );
 84:    HRESULT Cancel ( );
 85:
 86:    //
 87:    // Raw methods provided by interface
 88:    //
 89:
 90:    virtual HRESULT __stdcall get_ConnectionString (
 91:        BSTR * pbstr ) = 0;
 92:    virtual HRESULT __stdcall put_ConnectionString (
 93:        BSTR pbstr ) = 0;
 94:    virtual HRESULT __stdcall get_CommandTimeout (
 95:        long * plTimeout ) = 0;
 96:    virtual HRESULT __stdcall put_CommandTimeout (
 97:        long plTimeout ) = 0;
 98:    virtual HRESULT __stdcall get_ConnectionTimeout (
 99:        long * plTimeout ) = 0;
100:    virtual HRESULT __stdcall put_ConnectionTimeout (
101:        long plTimeout ) = 0;
102:    virtual HRESULT __stdcall get_Version (
103:        BSTR * pbstr ) = 0;
104:    virtual HRESULT __stdcall raw_Close ( ) = 0;
105:    virtual HRESULT __stdcall raw_Execute (
106:        BSTR CommandText,
107:        VARIANT * RecordsAffected,
108:        long Options,
109:        struct _Recordset * * ppiRset ) = 0;
110:    virtual HRESULT __stdcall raw_BeginTrans (
111:        long * TransactionLevel ) = 0;
112:    virtual HRESULT __stdcall raw_CommitTrans ( ) = 0;
113:    virtual HRESULT __stdcall raw_RollbackTrans ( ) = 0;
114:    virtual HRESULT __stdcall raw_Open (
115:        BSTR ConnectionString,
116:        BSTR UserID,
117:        BSTR Password,
118:        long Options ) = 0;
119:    virtual HRESULT __stdcall get_Errors (
120:        struct Errors * * ppvObject ) = 0;
121:    virtual HRESULT __stdcall get_DefaultDatabase (
122:        BSTR * pbstr ) = 0;
123:    virtual HRESULT __stdcall put_DefaultDatabase (
124:        BSTR pbstr ) = 0;
125:    virtual HRESULT __stdcall get_IsolationLevel (
126:        enum IsolationLevelEnum * Level ) = 0;
127:    virtual HRESULT __stdcall put_IsolationLevel (
128:        enum IsolationLevelEnum Level ) = 0;
129:    virtual HRESULT __stdcall get_Attributes (
130:        long * plAttr ) = 0;
131:    virtual HRESULT __stdcall put_Attributes (
132:        long plAttr ) = 0;
133:    virtual HRESULT __stdcall get_CursorLocation (
134:        enum CursorLocationEnum * plCursorLoc ) = 0;
135:    virtual HRESULT __stdcall put_CursorLocation (
136:        enum CursorLocationEnum plCursorLoc ) = 0;
137:    virtual HRESULT __stdcall get_Mode (
138:        enum ConnectModeEnum * plMode ) = 0;
139:    virtual HRESULT __stdcall put_Mode (
140:        enum ConnectModeEnum plMode ) = 0;
141:    virtual HRESULT __stdcall get_Provider (
142:        BSTR * pbstr ) = 0;
143:    virtual HRESULT __stdcall put_Provider (
144:        BSTR pbstr ) = 0;
145:    virtual HRESULT __stdcall get_State (
146:        long * plObjState ) = 0;
147:    virtual HRESULT __stdcall raw_OpenSchema (
148:        enum SchemaEnum Schema,
149:        VARIANT Restrictions,
150:        VARIANT SchemaID,
151:        struct _Recordset * * pprset ) = 0;
152:    virtual HRESULT __stdcall raw_Cancel ( ) = 0;
153:  };

Line 1 in Listing 10.7 shows the interface identifier (IID) for the ADO Connection object. You can see in line 2 that the Connection class is derived from the ADO class, which is declared earlier in the MSADO15.TLH file.

COM objects can have properties, which are basically data members, in addition to member functions or methods. Visual C++ uses get, put, and putref methods to make the properties in a COM object accessible to COM clients. Lines 4-31 in Listing 10.7 assign these COM property-handling functions to the high-level wrapper functions that are created with Get, Put, and PutRef prefixes.

Lines 33-84 in Listing 10.7 declare the high-level functions for the ADO Connection object. The code for these high-level functions is found in MSADO15.TLI.

Lines 86-153 in Listing 10.7 declare the functions found in the ADO type library. They use the function name from the type library with a raw_ prefix. Low-level functions for property get, put, and putref methods are prefixed by get_, put_, and _putref.

This completes your exploration of the use of #import with ADO. You should now have a good understanding of the .tlh and .tli files generated by #import with the ADO type library.

You can use the MSADO15.TLH file and the MSADO15.TLI files in combination with the existing ADO documentation in order to understand ADO and how to effectively use it in your C++ applications.

Summary

Today you learned about database client technologies. Several database client technologies are available to C++ programmers. Each technology has its own strengths and weaknesses, and each one has an historical context that defines how it relates to the other technologies.

The two database client technologies that will be updated and improved on in the future are OLE DB and ADO. ADO offers a good balance of code size, performance, and ease of use. You can best understand the ADO object model by examining the MSADO15. TLH file and the MSADO15.TLI files, coupled with the ADO documentation.

Q&A

Q
Isn't the performance of a native database API always better than the performance of the database client technologies mentioned today?
A
Not necessarily. It depends on the implementation of the API. Some database vendors do provide a native API that is faster than the ODBC driver for their database. However, other database vendors (notably Microsoft) provide ODBC drivers and OLE DB providers that are highly optimized and are as fast as, or faster than, the native APIs for their databases.
Q
Is OLE DB just another layer on top of ODBC?
A
No. OLE DB can directly communicate with any data source for which there is an OLE DB provider. OLE DB providers do not generally communicate with data sources through ODBC. Rather, OLE DB providers directly communicate with the data source. There is one OLE DB provider, MSDASQL, that can communicate with data sources through ODBC. MSDASQL should be used only with those data sources that don't have a native OLE DB provider but do have an ODBC driver.
Q
What database client technologies are compatible with Web servers?
A
Web servers using CGI can communicate with CGI-compatible executables. The executable can conceivably talk to any data source to which it can gain access and for which it has a programming interface. However, several newer technologies provide better scalability for Web servers than CGI. These newer technologies include NSAPI on Netscape's Web servers and ISAPI on Microsoft's IIS. On the Windows platform, ODBC, OLE DB, and ADO are compatible with CGI interfaces, as well as with the newer Web server interface technologies.

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 goal or purpose of ODBC?
  2. How is ODBC's call-level interface different from embedded SQL?
  3. Where does the ADO type library reside and how can you view it?
  4. Why does ADO throw exceptions when errors occur?
  5. What function that you use with #import does not throw exceptions but returns a failed HRESULT instead?

Exercises

  1. Set break points in the inline functions in MSADO15.TLI, such as the _Connection::Open function, and run ADOMFC1 in debug mode to develop a feel for how code in MSADO15.TLH and MSADO15.TLI is executed. Debug step into all the functions to discern when you are executing code in your ADOMFC1 project and when you are executing code in the ADO DLL.
  2. Modify the code in Listing 10.7 so that the call to the ADO Command Execute function in line 24 directly calls the low-level Execute function.

© Copyright, Sams Publishing. All rights reserved.