INFO: Using ActiveX Data Objects (ADO) via #import in VC++

Last reviewed: February 12, 1998
Article ID: Q169496
The information in this article applies to:
  • ActiveX Data Objects (ADO) included with: - Microsoft Visual C++, 32-bit Editions, versions 4.2, 5.0

SUMMARY

The #import directive in Visual C++ offers a powerful new mechanism for manipulating OLE servers. When used with ActiveX Data Objects (ADO), #import can simplify getting at your data. This article discusses what is necessary to take advantage of #import with ADO.

MORE INFORMATION

Before You Instantiate Any Classes Created by #import

It's important to initialize OLE before creating any instances of classes created by #import. For example, the code below is safe, as it declares a #import smart pointer, then initializes OLE, then instantiates the smart pointer:

   // Declare smart pointer of Recordset
    _RecordsetPtr     p;

   void main( void )
   {
      // Initialize OLE.
      ::CoInitialize(NULL);

      // Instantiate smart pointer.
      HRESULT hr = p.CreateInstance( __uuidof( Recordset ) );

      ...
   }

The next code sample, however, is not safe and will generate an unhandled exception. The global smart pointer p is both declared and instantiated (by virtue of passing a specific uuid in the constructor):

   // Declare & instantiate smart pointer of Recordset
    _RecordsetPtr     p( __uuidof( _Recordset ) );

   void main( void )
   {
      // Initialize OLE
      ::CoInitialize(NULL);

      ...
   }

Because p is a global variable, it is instantiated before CoInitialize is ever called in main(). You can correct this with the code snippet below:

   struct InitOle {
      InitOle()  { ::CoInitialize(NULL); }
      ~InitOle() { ::CoUninitialize();   }
    } _init_InitOle_;

   // Declare & instantiate smart pointer of Recordset
    _RecordsetPtr     p( __uuidof( _Recordset ) );

   ...

An instance of the struct InitOle is declared, and instantiated before p, and, therefore, initializes OLE in it's constructor. Without this kind of fail-safe, you will see the following error message:

   Unhandled exception in [Program] (KERNEL32.DLL): 0xE06D7363
   Microsoft C++ Exception.

Correct Implementation of #import

It is important to invoke ADO correctly in your program, or you can have compiler errors. Below shows the correct way to use #import with Msado10.dll:

   #import "C:\oledbsdk\redist\msado10.dll" \
           no_namespace                     \
           rename( "EOF", "adoEOF" )

The actual path to Msado10.dll may vary depending on how you installed ADO; however, the other two arguments are critical.

Passing the License Key to ADO and ADO Redistribution

ADO, while freely redistributable, does require a license key when run on any system that had ADO installed in a mechanism other than a Microsoft product. For more information on how to pass a license key to ADO using #import and how to redistribute required ADO and OLE-DB DLLs, please see the articles listed in the REFERENCES section below.

Error Handling

With ADO, you may get an error in the HRESULT returned from an ADO method, you may get an exception raised by #import generated classes, and for either condition the ADO Errors Collection may be populated. In order to get at the Errors Collection you need a valid connection object.

For more information please see the following article:

   ARTICLE-ID: Q169498
   TITLE:      INFO: Extracting Error Information from ADO in VC++ with
               #import

ADO and Dbdaoint.h

If you attempt to mix ADO (via #import) and either MFC DAO or the DAO SDK in the same implementation file, as shown below:

   #include <afxdao.h>  // MFC DAO
   // -or-
   #include <dbdao.h>   // DAO SDK

   #import "...msado10.dll" no_namespace ...

ADO will generate the following six errors:

   error C2011: 'EditModeEnum' : 'enum' type redefinition
   error C2011: 'LockTypeEnum' : 'enum' type redefinition
   error C2011: 'FieldAttributeEnum' : 'enum' type redefinition
   error C2011: 'DataTypeEnum' : 'enum' type redefinition
   error C2011: 'ParameterDirectionEnum' : 'enum' type redefinition
   error C2011: 'RecordStatusEnum' : 'enum' type redefinition

While very nearly identical in content, the actual values in each enumerated type differ between what is required by ADO and what is required by DAO. You have several options to work around this:
  • Separate ADO and DAO code into separate .cpp files. Keep the use of #import or #include <afxdao.h/dbdao.h> in separate implementation files as well.
  • Modify the #import statement to create a namespace for anything generated for ADO. This means you will have to reference the namespace when referencing an ADO object as shown in the two functions below. The first shows how to use ADO exclusively within a function. The second shows how to mix-and-match ADO and DAO objects. This is possible only by explicitly referencing the ADO namespace for any ADO class or enumerated type:

          #include <afxdao.h>
    

          #import "C:\oledbsdk\redist\msado10.dll" \
    
                  rename_namespace("AdoNS") rename( "EOF", "adoEOF" )
    
          void ADOOnly( void )
          {
              using namespace AdoNS;
    
              _RecordsetPtr   prs;
    
              // Generates Compile Errors:
              CDaoRecordset   rs;
          }
    
          void MixAdoAndDao( void )
          {
              AdoNS::_RecordsetPtr  prs;
    
              // Compiles just fine
              CDaoRecordset   drs;
          }
    
    

Dissecting and using Msado10.tlh/Msado10.tli

#import generates two files, Msado10.tlh and Msado10.tli off of the typelib contained within Msado10.dll. The structure of the .tlh file can be broken out as follows:

  • Forward References and Typedefs
  • Smart Pointer Typedef and Declarations
  • Type Library Items

Each is described in detail below.

Forward References and Typedefs

Forward References and Typedefs are created through the use of struct __declspec(uuid("...")) on the GUID for any Dual Interface, Interface, and CoClass defined in the typelib.

   ...
   struct __declspec(uuid("00000274-0000-0010-8000-00aa006d2ea4"))
   /* dual interface */ _Connection;
   ...
   struct __declspec(uuid("00000275-0000-0010-8000-00aa006d2ea4"))
   /* interface */ ICADOConnection;
   ...
   struct /* coclass */ Connection;
   ...

Not all interfaces, such as Connection, have multiple implementations. This depends on the typelib, but for ADO most interfaces are dual and not implemented as interface or coclass.

Smart Pointer TypeDef Declarations

For Interfaces and Dual Interfaces, smart pointers are declared, which greatly simplifies using the interface:

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

Note that no smart pointer was declared for the coclass Connection interface.

Type Library Items

This includes any enumerated types defined in the typelib, as well implementation of the smart pointers and typelib items:

   enum CursorTypeEnum
   {
      adOpenUnspecified = -1,
      adOpenForwardOnly = 0,
      adOpenKeyset = 1,
      adOpenDynamic = 2,
      adOpenStatic = 3
   };

   ...

   struct __declspec(uuid("00000274-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;
      ...
   };

In the code fragment above, the Property Data section uses declspec to declare get and put methods for ConnectionString. The Wrapper methods section provides methods created by #import, which wrap these methods, and raise an _com_error exception if they are not successful. The Raw Methods section declares the actual method that is invoked by the interface.

While you could call GetConnectionString or PutConnectionString, it is really unnecessary. Since ConnectionString is a property you would reference it as shown below:

   _ConnectionPtr   p;
   bstr             bstrConnect;
   ...
   bstrConnect = SysAllocString( L"DSN=AdoDemo;UID=admin;PWD=sa" );
   p->ConnectionString = bstrConnect;

The actual implementation of GetConnectionString/PutConnectionString can be found in the Msado10.tli file.

When it comes time to use the Connection object in your code, you would use an instance of the smart pointer for the dual interface defined in Msado10.tlh as shown below:

   _ConnectionPtr p;
   bstrConnect
   HRESULT           hr = S_OK;
   _ConnectionPtr    pConn;

   hr = pConn.CreateInstance( __uuidof( Connection ) );

      if( !FAILED( hr ) )
          hr = pConn->Open( L"AdoDemo", L"admin", L"" );

Where AdoDemo is an ODBC data source.

#import and Explicitly Calling Release()

The advantage of #import is that it takes care of AddRef, QueryInterface, and Release for you automatically. However, if you decide to start calling Release() explicitly, you can create problems for yourself.

Within _com_ptr_t is a member variable, m_pInterface. As #import is a very thin wrapper, it makes no distinction with m_pInterface after the object is actually released, versus just decrementing its reference count without actually destroying the object. By explicitly calling Release()--without very explicitly calling AddRef() to balance it--#import will gladly try to release an object that doesn't exist, creating interesting side effects and crashing behavior.

Best advice, you didn't AddRef() it (or at least no need to), don't release it either.

REFERENCES

- Inside Com by Dale Rogerson ISBN 1-57231-349-8

  • The OLE-COM Object Viewer (Oleview.exe) that ships with Visual C++ for examining the contents of a typelib.
  • Visual C++ online documentation: search on #import

For more information, see the following articles in the Microsoft Knowledge Base:

   ARTICLE-ID:  Q166112
   TITLE:       PRB: Conflict with EOF when using #import with ADO

   ARTICLE-ID:  Q170459
   TITLE:       HOWTO: Passing a License Key to ADO in Visual C++ Using
                #import

   ARTICLE-ID:  Q168122
   TITLE:       HOWTO: Redistribute ADO 1.0 or ADO/R 1.0 with OLE/DB 1.1

   ARTICLE-ID: Q168354
   TITLE     : INFO: Underlying OLE and OLEDB Provider Errors Exposed via
               ADO
Keywords          : kbcode kbusage adoall adovc
Technology        : kbMfc
Platform          : WINDOWS
Issue type        : kbinfo


================================================================================


THE INFORMATION PROVIDED IN THE MICROSOFT KNOWLEDGE BASE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. MICROSOFT DISCLAIMS ALL WARRANTIES, EITHER EXPRESS OR IMPLIED, INCLUDING THE WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL MICROSOFT CORPORATION OR ITS SUPPLIERS BE LIABLE FOR ANY DAMAGES WHATSOEVER INCLUDING DIRECT, INDIRECT, INCIDENTAL, CONSEQUENTIAL, LOSS OF BUSINESS PROFITS OR SPECIAL DAMAGES, EVEN IF MICROSOFT CORPORATION OR ITS SUPPLIERS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. SOME STATES DO NOT ALLOW THE EXCLUSION OR LIMITATION OF LIABILITY FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES SO THE FOREGOING LIMITATION MAY NOT APPLY.

Last reviewed: February 12, 1998
© 1998 Microsoft Corporation. All rights reserved. Terms of Use.