Part Three: Advanced Discussion and Related Information

Part three discusses how to use #import with COM, including:

A Closer Look at #import

ADO is an excellent way to learn about COM, and #import is an excellent way to utilize COM. This section describes how to implement #import in COM and how it affects an ADO application. Even if you are not a developer, the discussion describes what your development environment provides invisibly. Most of the text in this section was borrowed from the "Mastering COM Development with Visual C++" course offered by Microsoft Authorized Training and Education Centers.

#import and COM

The #import directive instructs the compiler to read a specified type library and generate two header files that are automatically included in your Visual C++ source file. Both header files are placed in the output directory and given the same timestamp as the type library. The type library header file has the name <typelibname>.tlh and contains a Visual C++ equivalent of the original type library. The type library implementation file has the name <typelibname>.tli and contains implementations of any wrapper functions.

The type library header (.tlh) file contains the definitions constructed from the type library items. It is divided into the following sections:

The functions that the compiler generates can be divided into two types: high-level error-handling functions and low-level direct functions. The high-level wrapper functions invoke the low-level method, perform error checking of the returned HRESULT, and throw a _com_error C++ exception if an error occurs. The low-level functions call the method directly and return an HRESULT. High-level functions are generated by default, but you can change the default by specifying the raw_interfaces_only attribute in your #import statement.

Using the Type Library with the #import Directive

When you build the COM object server, the first utility that runs is the Microsoft Interface Definition Language (MIDL) compiler. The MIDL compiler creates a type library.

To use the #import directive to import a type library into ADO 1.5, use the following example code:

#import <msado15.h> rename ( "EOF", "adoEOF" )

In MFC client applications, you can place the #import directive in the stdafx.h file after the #include <afxwin.h> directive.

The #import directive creates two header files that reconstruct the type library contents in C++ source code: a primary and a secondary header file.

The primary header file is similar to the one produced by the MIDL compiler, but it also contains additional compiler-generated code and data. This header file has the same base name as the type library, with a .tlh extension.

If the type library exposes custom interfaces and methods, their wrappers are placed in the secondary header file. This header file has the same base name as the type library, with a .tli extension. The secondary header file contains the implementations for compiler-generated member functions, and is included (#include) in the primary header file.

Both header files are placed in the output directory of the project. The files are read and compiled as if the primary header file was named by a #include directive.

You can import the type library (.tlb file), the DLL, or the executable file with the #import directive. It may be easier to import the .tlb file because it stays in the same place for both Debug and Release builds.

For more information about the contents of the primary and secondary header files, see the online Help in Microsoft Visual Studio.

A number of support classes that are used in the implementation of compiler COM support are also available for use in your client application.

_com_error

This class represents a C++ exception thrown by a high-level wrapper function or COM support class. It encapsulates the HRESULT and optional IErrorInfo object.

_com_ptr_t

This template class is a smart-pointer implementation that encapsulates interface pointers and eliminates the need to call AddRef, Release, and QueryInterface functions. Compiler COM support uses the _COM_SMARTPTR_TYPEDEF (IKnown, _uuidof(IKnown) macro when generating interface pointers from a type library; this macro expands to the following:

typedef _com_ptr_t<_com_IIID<IKnown, uuidof(IKnown)> > IKnownPtr;

This means that you no longer must remember to call Release. When the smart pointer goes out of scope, the destructor handles the release for you automatically. You can arrange a resource cleanup whenever you like by simply setting the pointer to NULL.

_variant_t

This class is a wrapper for the Automation Variant data type. It provides memory allocation and deallocation, and calls SysAllocString and SysFreeString when appropriate.

_bstr_t

This class is a wrapper for the Automation Bstr data type. It provides memory allocation and deallocation, and calls VariantInit and VariantClear when appropriate.

Dissecting the .Tlh and .Tli Files Generated by #import

For any given type library, #import generates two files whose names match the name of the file that contains the type library, and with the extensions .tlh and .tli, respectively. The .tlh (or type library header) contains forward references on GUIDs in the type library, smart pointer declarations, and other type library items. The .tli (or type library implementation) file contains wrappers for invoking methods and accessing properties for each object in the type library.

Forward references are created to type a GUID to a particular object within the type library. Consider the following forward references generated from ADO 1.5 for various Connection objects defined in the ADO type library:

struct __declspec(uuid("00000515-0000-0010-8000-00aa006d2ea4"))
/* dual interface */ _Connection;

struct __declspec(uuid("00000516-0000-0010-8000-00aa006d2ea4"))
/* interface */ ADOConnectionConstruction;
...
struct /* coclass */ Connection;
...

Not all objects have multiple implementations, because this is specific to the Connection object. For ADO, most interfaces are defined as dual interfaces and are what you typically use. In this case, ADOConnectionConstruction is an internal interface used by ADO to bind to an OLE DB Provider, and should not ever be used directly. It is unsupported, undocumented, and will remain so in the future. This raises one of the more interesting points about #import or reading the type library in general. Not all of the objects exposed are useful to ADO developers. Some are artifacts for backward compatibility, some are there out of necessity in order to actually implement ADO itself (such as ADOConnectionConstruction), and some are of limited use to ADO developers (such as Unspecified enumerated values). In general, though, the documented portions of the type library are what you should be using, and hidden or undocumented elements of the type library are usually undocumented for a reason. There is no implicit advantage for using an undocumented element of the type library, and anything not documented is unsupported by Microsoft Product Support Services.

Following these forward references, you see the declarations of smart pointers for each object:

_COM_SMARTPTR_TYPEDEF(_Connection, __uuidof(_Connection));
...

Note that smart pointers are generated only on the dual interface and coclass Connection objects. Typically, though, you use only the smart pointer created on the dual interface objects for ADO. To invoke the Connection object you would use the following code to declare an instance of the smart pointer and create an actual ADO Connection object:

...
_ConnectionPtr   conn1;
Conn1.CreateInstance( __uuidof( Connection ) );

The __uuidof clause refers back to the forward declaration for the Connection object defined earlier in order to provide the correct GUID.

Other type library items are provided within the .tlh file, such as enumerated types defined within the type library. This is especially important for the Visual C++ developer. However, it can also expose items in the type library that you were not meant to see. Consider the following definition for CommandTypeEnum:

enum CommandTypeEnum
{
   adCmdUnspecified = -1,
   adCmdUnknown = 8,
   adCmdText = 1,
   adCmdTable = 2,
   adCmdStoredProc = 4,
   adCmdFile = 256,
   adCmdTableDirect = 512
};

Following the declarations for enumerated types exposed by the type library, you see the declarations of the classes wrapped by the smart pointers defined previously. Below is an excerpt of the _Connection object's smart pointer class:

struct __declspec(uuid("00000515-0000-0010-8000-00aa006d2ea4"))
_Connection : _ADO
{
   //
   // Property data
   //

   __declspec(property(get=GetConnectionString,put=PutConnectionString))
   _bstr_t ConnectionString;
   ...

   //
   // Wrapper methods for error-handling
   //

   _bstr_t GetConnectionString ( );
   void PutConnectionString (
      _bstr_t pbstr );

   //
   // Raw methods provided by interface
   //

   virtual HRESULT __stdcall get_ConnectionString (
      BSTR * pbstr ) = 0;
   virtual HRESULT __stdcall put_ConnectionString (
      BSTR pbstr ) = 0;
   ...

};

You can use various arguments with the #import statement to control whether you get the wrappers, raw interfaces or, by default, both. In this case the declspec( property( get=GetConnectionString, put=PutConnectionString ) ) defines the methods for getting and putting the value of a property. In this case it utilizes the GetConnectionString() and PutConnectionString() methods, which themselves are wrappers around the raw interface methods, as shown in this excerpt from the .tlh file. You can disable these helpers, but they allow you to assign and retrieve the value of the ConnectionString property without actually invoking a method:

inline _bstr_t _Connection::GetConnectionString ( ) {
   BSTR _result;
   HRESULT _hr = get_ConnectionString(&_result);
   if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
   return _bstr_t(_result, false);
}

inline void _Connection::PutConnectionString ( _bstr_t pbstr ) {
   HRESULT _hr = put_ConnectionString(pbstr);
   if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
}

These wrapper functions are typical of every implementation method found in the .tli file. That is, if a failed HRESULT is returned by the raw interface method, you need to trap the _com_error exception that #import raises. Within that exception is the value of the failed HRESULT as well as other information #import can collect about the description and origin of this error. You should still check the ADO Errors collection for additional information. If the provider raises more than one error, only the first error is wrapped in an exception. If ADO generates the error, the _com_error exception is your only way to trap the information ADO tries to give to you when it encounters an error:

   _ConnectionPtr   conn1;
   _bstr_t          bstrConnect(L"DSN=AdoDemo;UID=admin;PWD=sa" );

   conn1.CreateInstance( __uuidof( Connection ) );
   conn1->ConnectionString = bstrConnect;

Single vs. Double Quotation Marks in SQL Statements

If you have code that uses SQL syntax to make changes in a datastore, and you migrate that code from DAO to ADO, you might have to change some of the syntax, particularly double quotation mark characters. With DAO, the Microsoft Jet database engine expects double quotation marks in SQL syntax. With ADO, the Microsoft Access ODBC Driver expects single quotation marks in SQL syntax. This example works within DAO (but not within ADO) to insert a new record into a table:

   INSERT INTO Authors ( Au_ID, Author ) VALUES (  54, "Record # 54" )

You must change the double quotation marks to single quotation marks for the example to execute successfully in ADO, as shown here:

   INSERT INTO Authors ( Au_ID, Author ) VALUES (  54, 'Record # 54' )

Ironically, the OLE DB Provider for the Microsoft Jet Engine can handle either form of syntax, as shown in the following code snippet:

Dim cnn2 As New ADODB.Connection
cnn2.provider = "Microsoft.Jet.OLEDB.3.51"
cnn2.Open "C:\_data\adodemo.mdb", "admin", ""
cnn2.Execute _
   "INSERT INTO Authors ( Au_ID, Author ) VALUES (  55, 'Record 55' )"
cnn2.Execute _
   "INSERT INTO Authors ( Au_ID, Author ) VALUES (  56, ""Record 56"" )"
cnn2.Close

Conclusion

One of the misconceptions about ADO development in Visual C++ or Java is that it is inherently more difficult than in Visual Basic for Applications (VBA). However, the difficulty lies not so much with ADO as with COM. Specifically, you need to consider how easily the language or implementation you choose exposes and lets you manipulate COM objects. VBA is easier to use with COM objects, but this does not mean you have to write 50 lines of code in Visual C++ for every two lines in VBA.

To give Visual C++ and Java developers some of the advantages that VBA developers experience in minimal code size, several tools are available to help create ADO applications in languages other than VBA. For Visual C++ developers, this includes the MFC OLE classes and #import. For Java developers, the Java Type Library Wizard and the Microsoft SDK for Java help eliminate much of the difficulty of creating ADO applications.

Bibliography

ADO and RDS Knowledge Base articles are concentrated within a single product rollup, "ActiveX Data Objects," so as to prevent ADO expertise and technical content from fracturing between the dozen or so products that use ADO. In addition, "Programmer's Guide" articles are available and scattered within various product rollups so that you have a roadmap pointing you to additional content. ADO articles often have sample code in VBA, Visual C++, and Java, and can help you translate any articles that do not have sample code written in all three languages.

This article represents the "big picture" of what many of the Knowledge Base articles listed below present in detail.

Q166112: PRB: Conflict with EOF When Using #import with ADO

Q167802: SAMPLE: EXCEPTEX Traps MFC and Win32 Structured Exceptions

Q172391: FILE: GetRows.exe Demonstrates #import with ADO and GetRows()

Q172392: INFO: Programmer's Guide to Using ADO in Visual C++

Q172403: FILE: Adovb.exe Demonstrates How to Use ADO with Visual Basic

Q172441: INFO: Programmer's Guide to Using ADO in Visual J++

Q172589: INFO: Programmer's Guide to Using ADO in Visual InterDev

Q172927: INFO: Programmer's Guide to Using ADO in Active Server Pages

Q173645: BUG: Access Violation in Msdaer.dll with _com_error Exceptions

Q173647: INFO: Programmer's Guide to Using ADO in Visual Basic

Q174565: FILE: Adovc.exe Demonstrates How to Use ADO with Visual C++

Q181733: FILE: Adovcbtd.exe #import Using UpdateBatch and CancelBatch

Q182067: INFO: Programmer's Guide to Using RDS in Visual Basic

Q182389: FILE: Adovcbm.exe ADO 1.5 with #import and Getrows/Bookmarks

Q182442: FILE: Adomts.exe Shows Using ADO w/ an MTS component via DCOM

Q182443: INFO: Programmer's Guide to Using RDS in Visual Basic

Q182782: FILE: Adovj.exe Demonstrates How to Use ADO with Visual J++

Q183609: FILE: Rdsvb.exe Demonstrates How to Use RDS with Visual Basic

Q183616: HOWTO: Trap JScript Errors on a Web Page

Q184381: INFO: Programmer's Guide to Using MDAC in Visual Basic

Q184382: INFO: Programmer's Guide to Using RDS in Visual C++

Q184383: INFO: Programmer's Guide to Using RDS in Visual J++

Q184493: INFO: Programmer's Guide to Using Index Server Provider in VC++

Q184736: INFO: Troubleshooting Guide for 80004005 & Other Error Messages

Q184736: INFO: Programmer's Guide to Using Index Server Provider in VB

Q185033: FILE: Adoacc.exe Demonstrates Using ADO with MS Access 97

Other Sources of Information

Dale Rogerson, Inside COM. Microsoft Press, 1997.

Automation Programmer's Reference. Microsoft Press, 1997.

The Data Access SDK can be obtained from www.microsoft.com/data/.

The OLE DB 1.5 SDK can be obtained from www.microsoft.com/data/oledb/.