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/.