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.
|
Connect with Us