ActiveX and the VCL (part 2) : Connecting to ActiveX Servers

By: Andrew Ames

Abstract: Discusses various methods of using ActiveX components.

ActiveX and the VCL (part 2) Go to Part 1
Connecting to ActiveX Servers

The main purpose of this article is to describe the various means of connecting to an ActiveX server with C++ Builder 4 and 5.  This article assumes some familiarity with ActiveX servers, the Windows Registry, and the COM/OLE Win32 library functions.

SDKs with Source Code
By far, connecting to and using the services of a COM library is easiest when using a COM based SDK in which header files and GUID libraries (or source) are provided in the language and development environment of choice.  In this case, a client application simply requires including the header files and linking the library or source module that contains the GUIDs.  These header files are similar to the *_TLB.h file that is created when building an ActiveX server with C++ Builder.  They contain external references to all GUIDs used by the server and all data types used as parameters to interface methods.  The DirectX SDK is an example of this scenario.  C++ header files are provided for type definitions and VC++ and Borland compatible static libraries are provided for the GUID definitions.  No type library is registered with Windows nor is there one linked into the server's module.  Following is a snippet of code illustrating the use of DirectDraw (ddraw.dll).

#include <ddraw.h>    // shipped with the DX SDK

// ddraw.h contains the following...
// DEFINE_GUID(CLSID_DirectDraw, 0xD7B70EE0,0x4340,
//             0x11CF,0xB0,0x63,0x00,0x20,0xAF,
//             0xC2,0xCD,0x35);
// DEFINE_GUID(IID_IDirectDraw, 0x6C14DB80,0xA733,
//             0x11CE,0xA5,0x21,0x00,0x20,0xAF,
//             0x0B,0xE5,0x60);
// DECLARE_INTERFACE_(IDirectDraw, IUnknown) {
//    /*** IUnknown methods ***/
//    STDMETHOD(QueryInterface) (THIS_ REFIID riid,
//                               LPVOID FAR * ppvObj) PURE;
//    STDMETHOD_(ULONG,AddRef) (THIS)  PURE;
//    STDMETHOD_(ULONG,Release) (THIS) PURE;
//    /*** IDirectDraw methods ***/
//    .....
//    STDMETHOD(GetCaps)(THIS_ LPDDCAPS, LPDDCAPS) PURE;
//    .....
// };
// typedef struct _DDCAPS {
//     DWORD    dwSize;
//     DWORD    dwCaps;
//     DWORD    dwCaps2;
//    .....
// } DDCAPS,FAR* LPDDCAPS;

WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
    CoInitialize(NULL);    // Initialize the COM library

    IDirectDraw* pDD = 0;
    CoCreateInstance(CLSID_DirectDraw, NULL,
                     CLSCTX_INPROC_SERVER,
                     IID_IDirectDraw,
                     (LPVOID*)pDD);
    DDCAPS ddcaps;
    ddcaps.size = sizeof(DDCAPS);
    pDD->GetCaps(NULL, &ddcaps);
    pDD->Release();

    CoUninitialize();
    return 0;
}

The DirectDraw interface declarations use macros that expand into C++ classes with pure virtual methods if a C++ compiler is used or it expands into a C structure with list of function pointers that emulates a virtual function table.  These macros are defined in objbase.h.

When CoCreateInstance is called in the preceeding example, the Windows registry is searched for a key under HKEY_CLASSES_ROOT/CLSID that has the name equal to CLSID_DirectDraw.  If it is found, it will have a sub key named InprocServer with a string value indicating the location of the module that serves the DirectDraw component (ddraw.dll).  Windows loads this library and calls its exported function, DLLGetClassObject, obtaining the IClassFactory interface for this component.  Then, IClassFactory::CreateInstance is called with IID_IDirectDraw as the requested interface.  This interface is returned to WinMain.

Another way to get at the DirectDraw component is with the "DirectDraw" ProgID.  The COM library has a function called CLSIDFromProgID.  The following illustrates.

CLSID clsid;
HRESULT hr = CLSIDFromProgID(OLESTR("DirectDraw"), &clsid);

This function searches the registry for the key "HKEY_CLASSES_ROOT/DirectDraw/CLSID" and, if found, the value of this key is converted to a CLSID structure.  The CLSID can then be used in calls to CoGetClassObject and CoCreateInstance.  Of course, you still need all types and interface declarations for DirectDraw.

If you are developing both the ActiveX server and the client, the *_TLB used to build the server can be used to build the client as well.  Simply link the OBJ file from the compiled unit and include the header.  If the client is not a VCL application, but a standard Win32 GUI, then the *_TLB files must have their references to vcl.h removed and rebuilt.

Many libraries and SDKs provide global functions that create a particular instance of a component and return a particular instance of an interface.  The Win32 SDK and the COM library provide many such routines.  For example, CoGetMalloc returns a pointer to an IMalloc interface, which is implemented by the OLE standard libraries.  The declaration for this interface and all types required by its methods can be found in the Win32 header objidl.h.  This header file was automatically generated from the objid.idl file, before it was shipped with the SDK.  More on IDL files later.  Another example of such a global function is DirectDrawCreate in the DX SDK.  This function encapsulates all the code required to load ddraw.dll and obtain an instance of the IDirectDraw interface.

Type Library
In part 1 of this series, we saw that when an ActiveX server is created and a COM object is added to it, a type library is created (.tlb extension) and added to the project.  The type library is a binary file that contains all the types, interfaces, methods, arguments to methods, components, and structures associated with the server.  These types are stored in a language independent format, therefore, any language or development environment can gain access to the type information and use it to make calls to the components' interfaces programmatically.  Of course, simply knowing the interfaces a component supports and the arguments required to call these methods is not enough information to be able to programmatically make use of a component.  Originally, type libraries were used only for Automation and ActiveX controls.  A container site for ActiveX controls could query the type library of the control to ensure that it supported the interfaces required by the container and, vice versa, the control could query the container to ensure it supported the interfaces required by the control.  With the introduction of object browsers, which allow a human to browse the components served by a COM library, all its associated type information, and even help information, type libraries have begun to be used outside the context of Automation and ActiveX controls.  Development tools can allow a programmer to automatically create header and source files from a type library that are compatible with the developer's language and IDE of choice.  Most Win32 development packages, such as C++ Builder, Delphi, JBuilder, and Visual C++, have such a tool.

If a component has an associated type library, the type library's GUID (LIBID) will be registered under the CLSID of the component.  Each type library referenced by a component will also be registered under "HKEY_CLASSES_ROOT/TypeLib/<LIBID>".  Each type library entry provides the location of the TLB file.

To generate the type library source files, start a new application and go to Project | Import Type Library.  The list box shows all the type libraries that are registered in the registry's "HKEY_CLASSES_ROOT/TypeLib" key.  The Class Names list box will display the names of the VCL classes created to wrap the controls in the type library.  If a type library does not contain any controls, then this list box will be empty, however, you can still create declarations for all types in the library.  The add button allows you to choose a type library file from disk and register it in the registry, thus adding it to the type library list box.

If there is no type library registered for a component and no type library can be found, it can be extracted from the DLL, OCX, or EXE that serves the component, as long as the type library was linked with the module when it was built.  This can be done by choosing Add in the Import Type Library dialog box and browsing for the module.  If the module doesn't contain a binary type library, an error that it cannot find the type library will appear.

Interface Description Language (IDL)
The source and header files needed to connect to a COM server can be built from an IDL file which describes all types, components, and interfaces in a language independent format.  If you have the IDL file, but no type library, use Microsoft's command-line IDL compiler (MIDL) to create the header and source files.  This is usually a last resort since working with MIDL outside of the VC++ environment can be quite tedious.  MIDL.exe can be found in C++ Builder's bin directory.  The files generated may have VC++ specific commands that need to be translated.  See the "MIDL Programmers Reference" for more information on using MIDL and the command-line arguments required by this tool.

VCL's Interface Wrappers
The type library header file (*_TLB.h) created by importing a type library contains template classes that simplify instantiating components and using its interfaces.  For the following examples, create a COM server with the module name Server.dll and add a single interface to it named IInterface.  Add a method to this interface named Show that takes an LPSTR as an argument and calls ShowMessage to display the string.  Once Server.dll is built and registered, create a new application named Client.exe as either a VCL or straight Win32 application.  Then, import the type library for Server.dll.  In the Import Type Library dialog's list box, it will say Server Library (Version 1.0) if all the default properties for the type library were used.

The Server_TLB.h file generated for the client contains the following declarations:

typedef TComInterface<IInterface, &IID_IInterface> IInterfacePtr;

typedef IInterface Interface;
typedef TComInterface<Interface, &IID_IInterface> InterfacePtr;

template <class T /* IInterface */ >
class TCOMIInterfaceT : public TComInterface<IInterface>,
                        public TComInterfaceBase<IUnknown> {
public:
  TCOMIInterfaceT() {}
  TCOMIInterfaceT(IInterface *intf, bool addRef = false) :
                    TComInterface<IInterface>(intf, addRef) {}
  TCOMIInterfaceT(const TCOMIInterfaceT& src) :
                    TComInterface<IInterface>(src) {}
  TCOMIInterfaceT& operator=(const TCOMIInterfaceT& src)
                    {Bind(src, true); return *this;}

  HRESULT         __fastcall Show(LPSTR Str/*[in]*/);

};
typedef TCOMIInterfaceT<IInterface> TCOMIInterface;

typedef TCoClassCreatorT<TCOMIInterface, IInterface,
                         &CLSID_Interface,
                         &IID_IInterface> CoInterface;

TComInterface
This class template, defined in utilcls.h, performs AddRef calls when an interface is copied by a constructor or by assignment.  Release is called when the class is destroyed.  Also, if a TComInterface is assigned a TComInterface with different template arguments (i.e. it points to another interface), QueryInterface is called to see if the new interface is supported and, if so, the assignment succeeds.  Following is an example of using this template class.  No error checking is performed to keep the example brief.

InterfacePtr pIUnk;
CoCreateInstance(CLSID_Interface, NULL, CLSCTX_INPROC_SERVER,
                 IID_IUnknown, (LPVOID*)&pIUnk);
InterfacePtr pIIface = pIUnk;
pIIface->Show("Hello");

There's no need to AddRef or Release these interface pointers in most situations.  The TComInterfaceT template is also used when creating COM interface methods where a pointer to an interface pointer is an argument.

TCOMIInterface
This template class, defined in utilcls.h, adds additional functionality to the TComInterface class that makes using IInterface much like using a VCL component.  The following example illustrates.

TCOMIInterface Iface;
CoCreateInstance(CLSID_Interface, NULL, CLSCTX_INPROC_SERVER,
                 IID_IInterface, (LPVOID*)&Iface);
Iface.Show("Hello");

TCoClassCreator
This template class, also defined in utilcls.h, provides a concise way to create components and retrieve its interfaces.

CoInterface CoIface;
TCOMIInterface Iface = CoIface.Create();
Iface.Show("Hello");

Summary
Using COM and ActiveX controls is by far easier than building them.


Server Response from: ETNASC04