Athena

How To Do COM In C++Builder 5

Brian Long (www.blong.com)

If you find this article useful then please consider making a donation. It will be appreciated however big or small it might be and will encourage Brian to continue researching and writing about interesting subjects in the future.


Introduction

COM (the Component Object Model) is playing an increasing rôle in Windows development, and C++Builder offers full support for this technology. It is becoming more and more common for developers to build COM objects in their chosen language, and to then make those objects available to systems potentially written using other languages.

The beauty of COM objects is that, whilst currently they are limited to working on Windows platforms, they are language independent. Because the COM specification defines how COM objects must be laid out at the implementation level, designers of all languages can make objects work with COM.

Whilst COM objects are currently restricted to working on the Windows platform, DCOM (Distributed COM) allows an object to be on a completely different machine to the application that talks to it.

This paper will look at the subject of building COM objects and then how to access those COM objects from client applications. Along the way we will encounter numerous pieces of terminology that will be explained as they arise.

The paper starts with an overview of the important aspects of COM applications before taking you through the process of building COM servers and clients in a tutorial style.

You can download the files that accompany this paper by clicking here.

Interfaces

Before embarking on our COM mission, we must first understand the concept of an interface, as this knowledge is vital for us to work successfully with COM.

An interface is a definition of a set of related methods, typically which all work together to perform some sort of job or service. An interface definition lists the method names, parameters and return types, but no implementation at all, and no data fields.

The purpose of an interface is to describe some functionality that might be implemented by a COM object (or several COM objects). A COM object can then be designed to implement any chosen interface, and may implement many different interfaces simultaneously. The COM object is said to support any interfaces that it implements.

When an application (called a client application) talks to a COM object, it is not able to do so directly. Instead, it must talk to it through any one of the interfaces that the object implements (or supports).

The client application can talk to the object through any interface it knows about. The COM object may implement more interfaces than the client knows about, but the client is restricted to working with what it knows.

By restricting access to COM objects through interfaces, the COM object developer is at liberty to change other aspects of the object, such as the implementation of the interface methods, or other aspects not related to the interface, without affecting the object's relationship to its clients. So these formalised interfaces to the object provide flexibility to the COM object developer.

Interfaces by convention all have a prefix letter of I, for example IMalloc, IStream and IDispatch.

Abstract Classes

In C++ you can define an interface as an abstract class, or pure virtual class (or more typically a pure virtual struct). Since an interface describes available behaviour in an object, such a class or struct will have the pure virtual methods declared in its public section and will have no other sections and no data fields defined.

In fact, the symbol interface is defined in terms of struct in basetyps.h, to make interface definitions more obvious.

You can make one interface be a superset of another by inheriting one such interface class from another. The new interface is said to be based on the original interface. Although the interface is defined using class inheritance, the lack of actual functionality in an interface leads many people to avoid using the term inheritance to describe building new interfaces from old ones.

IUnknown

Whilst it is possible to build classes that inherit nothing at all, an interface must always be based on another interface. At the root of this resultant hierarchy is the most basic interface of all, IUnknown.

This base interface defines three methods that accomplish two key services of any COM object, lifetime management and interface querying. The interface definition (in C++ syntax) is shown in Listing 1, where you can see the Add(), Release() and QueryInterface() methods that implement these services.

Listing 1: The IUnknown interface

MIDL_INTERFACE("00000000-0000-0000-C000-000000000046")
IUnknown
{
public:
  BEGIN_INTERFACE
  virtual HRESULT STDMETHODCALLTYPE QueryInterface(
      /* [in] */ REFIID riid,
      /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject ) = 0;
  virtual ULONG STDMETHODCALLTYPE AddRef( void ) = 0;
  virtual ULONG STDMETHODCALLTYPE Release( void ) = 0;
  END_INTERFACE
};

Since all interfaces are based on IUnknown, all interfaces will have these methods within them. This means that all COM objects (which are accessed through interfaces) offer lifetime management and interface querying facilities.

Before looking at the purpose of these methods, it is useful to see the definition of IUnknown without any pre-processor directives masking things. Listing 2 serves this need.

Listing 2: An expanded version of IUnknown

struct __declspec(uuid("00000000-0000-0000-C000-000000000046")) __declspec(novtable)
IUnknown
{
public:
  virtual HRESULT __stdcall QueryInterface( const IID &riid, void * *ppvObject ) = 0;
  virtual ULONG __stdcall AddRef( void ) = 0;
  virtual ULONG __stdcall Release( void ) = 0;
};

Lifetime Management

You create normal objects either on the stack, or on the heap (using pointer notation). If an object is created on the stack, it will be automatically destroyed when its scope ends. An object created on the heap must be explicitly destroyed to reclaim its resources.

COM objects live on a heap (in some process address space, not necessarily your own), but the management of their lifetime is performed somewhat differently. Take the scenario where you cause a COM object to be created and you use it for a while. Before you finish, several other COM client applications connect to the same object.

When you finish with the object, destroying it would serve your own purpose, but not the other client applications who may still be using the object. If you destroy the object, the other clients will get Access Violation errors when they try to call methods of the object.

So in short, you never destroy COM objects. Instead, COM objects use a reference counting mechanism to manage their own lifetime. Each time you store a reference to a COM object's interface, you increment the reference count by calling AddRef(). Each time you finish with an interface, you decrement the reference count with Release().

When all clients are done with the object, they will have all called Release() and the reference count will fall back to 0. When this happens, the object can destroy itself.

AddRef() and Release() work together to ensure that objects only exist for as long as they need to.

Interface Querying

Any given COM object can support several interfaces. To start with, a client application is given the object's IUnknown interface. In order to access the other interfaces (which may or may not be supported by any given implementation), the client must query the object as to whether it supports the required interface.

This interface querying is done using the QueryInterface() method. QueryInterface() is coded to understand all the implemented interfaces. When a client asks the object if it supports a given interface, QueryInterface() will return an appropriate interface reference if possible, otherwise it will indicate the interface is not supported.

GUIDs

In order for any given COM entity to be uniquely identified apart from any other, everything is given a different GUID. A GUID is Microsoft's Globally Unique Identifier, pronounced either gwid or goo-id.

A GUID is a 128-bit number, typically displayed as a specially formatted string of hexadecimal numbers (see the first line of Listing 1). Microsoft has implemented logic in COM to be able to fabricate unique GUIDs upon demand (the CoCreateGuid() API).

The values are concocted from an incrementing internal count, in addition to your network card identifier and other system details.

The GUID is a tailored version of Unix's Universally Unique Identifier, or UUID and is used to identify various COM elements. Interfaces are uniquely identified by a GUID called an IID (Interface Identifier). A COM object with an associated class factory is identified by a CLSID (Class Identifier). A type library is identified by a LIBID (Library Identifier).

Implementations of QueryInterface() rely on interfaces having associated IIDs in order to known which interface is being asked for. Listing 1 shows an IID being specified using the MIDL_INTERFACE macro, which as Listing 2 shows, expands to a struct definition followed by a __declspec(uuid()) declaration specifier.

In textual display form, a GUID is a string of hexadecimal digits with four hyphens placed within, surrounded by double quotes. The C++Builder editor can generate a GUID on demand simply by pressing Shift+Ctrl+G, saving you calling CoCreateGuid() in code. This produces a GUID, although instead of a pair of double quotes, it will be surrounded by a pair of apostrophes and a pair of square brackets, for example:

['{84DF4740-647A-11D4-96EC-0060978E1359}']

This keystroke is more often used in Delphi, where strings are delimited by single quotes.

Sometimes you need to represent a GUID in a non-textual form, as an initialised GUID struct. The definition of this struct can be seen in Listing 3, along with an initialised struct representation of the above GUID.

Listing 3: The GUID struct and a sample GUID

typedef struct _GUID {
    unsigned long  Data1;
    unsigned short Data2;
    unsigned short Data3;
    unsigned char  Data4[ 8 ];
} GUID;

const GUID SomeGUID = {0x84DF4740, 0x647A, 0x11D4, {0x96, 0xEC, 0x00, 0x60, 0x97, 0x8E, 0x13, 0x59} };

Interface Definition Language (IDL)

Because interfaces are access points in COM objects, and COM objects are programming language independent, a dedicated Interface Definition Language (IDL) is used to describe them unambiguously and without bias to any given language.

IDL is a C-like language that has been developed to help represent all the attributes of an interface without any suggestion of an implementation language, or a language for the client application.

IDL was originally part of the Open Software Foundation's Distributed Computing Environment (the OSF's DCE) and was used to describe function signatures for Remote Procedure Calls (RPCs). IDL-enabled compilers generate proxy and stub code to allow function parameters to be translated from one process to another (marshaling), where the two processes reside on different machines.

Microsoft extended IDL to accommodate all the trappings of COM interfaces including vtable interfaces (early bound) and dispatch interfaces (late bound). Microsoft's tool, MIDL.EXE, compiles IDL code and generates various C++ source files and also type libraries.

Microsoft developers start their COM objects by defining the interfaces in IDL, then generating all the C++ code including the COM object with stub methods to implement the appropriate interfaces, as well as generating a type library.

C++Builder takes an alternative direction to building COM objects and defining interfaces, as we shall see as the paper progresses.

Listing 4 shows what IUnknown looks like in IDL syntax.

Listing 4: IUnknown in IDL

[
  local,
  object,
  uuid(00000000-0000-0000-C000-000000000046),

  pointer_default(unique)
]

interface IUnknown
{
  typedef [unique] IUnknown *LPUNKNOWN;

  HRESULT QueryInterface(
    [in] REFIID riid,
    [out, iid_is(riid)] void **ppvObject);
  ULONG AddRef();
  ULONG Release();
}

HRESULT Return Type

It is highly recommended that every method you define returns the same type of value, a HRESULT. As you can see in Listing 4, AddRef() and Release() do not follow this guideline, but they are an exception to the rule. Since practically all COM methods return a HRESULT, error detection becomes quite consistent.

A HRESULT, despite its name and the normal Windows type-naming convention, is not a handle to anything. It is just a 32-bit integer value used to return success, failure or warning codes.

The high bit of a HRESULT (bit 31, called the severity bit) indicates success (if clear) or failure (if set). The next four bits are reserved by Windows and must currently be zero. The other eleven bits of the high word represent the facility code, and specify which group of status codes the HRESULT belongs to, effectively identifying the system service that generated the error. The low word represents the error code and indicates what happened.

To explicitly examine the severity bit, which means to find out if the HRESULT-generating call succeeded or failed, you have several options. The preferred approach is to use the SUCCEEDED() or FAILED() macros which do the check for you.

Other options include calling HRESULT_SEVERITY() and comparing the result against SEVERITY_SUCCESS or SEVERITY_ERROR. Finally, there is an IS_ERROR() macro which does the same as FAILED().

Routines also exist for getting the other sections out of a HRESULT, including HRESULT_CODE() and HRESULT_FACILITY(). More exist to manufacture HRESULT values, such as MAKE_HRESULT() (out of a severity, facility and status code) and HRESULT_FROM_WIN32().

Some common HRESULT values include S_OK, which is a simple success indicator and S_FALSE, which is a successful failure. E_UNEXPECTED is a common generic error code. For custom HRESULT values from your own interface methods, they should come from the FACILITY_ITF facility with codes greater than 0x1ff, for example:

#define E_MY_ERROR MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, 0x200)

All these macros and constants are declared in WinError.h, along with all the facility codes and many more error constants.

Additionally, ComObj.hpp defines a helper routine called OleCheck(), which takes a HRESULT and raises a descriptive exception if it indicates a failure. The exception description is generated by passing the HRESULT to SysErrorMessage().

COM Objects

A COM object is an object that lives in a COM server and can be accessed in some way by a client application. A COM object has at least one interface implemented that can be accessed by the client application.

Client applications can make requests to create new COM objects from the appropriate server, but not all COM objects can be externally created. A COM object that can be created by a suitable COM call from a client application is referred to as a coclass. There are various requirements for a COM object to be a coclass, most of which are satisfied by having an appropriate class factory.

Class Factories

A class factory is a utility object in a COM server that takes responsibility for creating a COM object in the server when a client application requests it. Without a class factory, the COM object is not a coclass as it cannot be created.

Typically, any COM server will have class factory objects for all potential COM objects created at startup. When a COM object is required, the corresponding class factory will be asked to create an instance of the COM object.

Class factories are all quite small objects. The COM objects, on the other hand, could be of a non-trivial size. Ensuring the COM objects are only created when needed prevents the COM server taking up more resources than it needs to.

One of the jobs of a class factory in a C++Builder COM server is to register all the COM objects in the Windows registry when requested. We will see how to register and unregister a COM server later, but it is useful to note that it is the class factories that ultimately do this job.

The COM Object Wizard

When you create a COM object in C++Builder, you use a wizard (from the ActiveX page of the File | New... dialog) that asks a few questions about your target object (see Figure 1).

Figure 1: The COM Object wizard

The first thing it wants is the coclass name. A coclass name of Foo will make the default interface to be implemented called IFoo. The class that will be generated to implement the interface will be called TFooImpl, but the type library will advertise the coclass as Foo.

You can change the name of this interface, or choose an existing interface from a list compiled from all the registered interfaces (with the List button). It can often be very helpful to have C++Builder generate stub methods for an existing registered interface that you wish to implement.

The object can support event methods in a dedicated events interface that clients can respond to with the Generate Event support code checkbox. This causes extra code to be added to the implementing class to support client applications (called event sinks) connecting to your server's events interface. This can be useful but will not be explored in the paper. You should be able to find out about COM events from my Advanced Delphi Automation paper (see Reference 1), Binh Ly's COM Library (see Reference 2) or the C++Builder 5 Developer's Guide (see Reference 8).

The other checkbox ensures a flag is set in the COM server's type library. This flag enables out-of-process COM servers to take advantage of Automation marshaling, rather than requiring dedicated marshaling code to be written by the developer.

Note: the tooltip for this checkbox erroneously suggests it is most useful for in-process servers, whereas in fact it is irrelevant to in-proc servers.

Apart from the Description entry, which is fairly self-explanatory, the only other option on the wizard is Threading Model, whose default value is Apartment.

Apartments

When you write any COM object, you will either take the trouble to make it completely thread-safe (able to operate correctly in a multi-threaded application scenario) or you will not. Depending on which way you go will dictate how you want your object used.

If the object is thread-safe, you will be happy for multiple clients to simultaneously call methods in your COM object. If the object is not thread-safe, you will not want this to happen, as the code cannot handle it.

COM uses apartments to enforce exactly these types of rules. Any process that makes use of COM (be it a server or client) has one or more apartments in it. An apartment is used to define thread awareness of the COM objects found within it.

Any thread that makes COM calls belongs in an apartment (and stays in the same apartment). When a thread creates a COM object, that COM object belongs to the same apartment as the thread. Using behind-the-scenes magic, COM ensures that only threads in an apartment can call objects in that apartment (this is important).

There are two types of apartment that exist (although COM+ on Windows 2000 adds another one). There can be at most one Multi-Threaded Apartment (MTA) in a process, but there can be one or more Single-Threaded Apartments (STAs).

An MTA can have as many threads in it at any time as it likes. Since the MTA's threads can call an MTA's objects, there is high potential for multi-threaded calls to be executed.

An STA can only have a single thread in it. If that thread creates an object, the object will be in the same STA. That STA's single thread is the only thread allowed to call the object's methods, so all method calls to the objects in the STA will be made on this one thread.

COM ensures that even if simultaneous STA method calls are made from threads in another process, or other apartments in the same process, they will execute one at a time. This guarantees thread safety for objects that cannot do it for themselves.

The new COM+/Windows 2000 apartment is a Thread-Neutral Apartment (TNA). When an object is in a TNA, multiple threads from other apartments can call the objects methods simultaneously, but again, COM ensures that there are no concurency issues by serialising the calls.

Threading Models

To indicate which type of apartment would be best, the COM object wizard (Figure 1) allows you to specify your chosen threading model. The available options are shown in Table 1.

Table 1: Threading models available in the wizard

Threading model

Meaning

Single

The object can work in an STA, so thread safety will be enforced. But it is assumedthere will only be one STA, so global data needs no protecting.

Apartment

The object can work in an STA, so thread safety will be enforced. There may bemany STAs in the process, so global data must be protected. This is the default option.

Free

The object can work in an MTA, so is thread-safe.

Both

The object can work in an STA or an MTA, so is very flexible (and thread-safe).

Neutral

This model is specific to COM+. When COM+ is not available it maps to Apartment.Should not be used for controls with a UI, but is recommended for other objects.

By specifying a threading model, the object advertises how thread-safe it is, and COM can work out how best to deal with situations before they arise.

As we will see, this option is only relevant for in-process COM servers.

For more information on apartments, see Reference 3.

Proxy Objects

In order to allow COM to enforce thread safety where needed between different apartments, COM creates proxy objects. When a client application (in, say, an MTA) asks COM to create an object that advertises an Apartment threading model, COM creates a proxy object to intercede.

To the client, the proxy object looks exactly like the real object, but it uses internal trickery (a hidden window and a Windows message pump) to ensure that calls to the STA occur one at a time.

These proxy objects are made automatically to cater with cross-apartment, or cross-process calls. The proxy object is also responsible for marshaling data between apartments and processes.

In order for COM to create proxy objects, the corresponding interface must appear in a type library registered on the system. The type library is read to find out all the details of the interface method parameters, so the object can look exactly like it should, and know what data it needs to marshal.

For more information on proxy objects, see Reference 3.

Marshaling

To cater for inter-apartment and inter-process COM method calls, COM must know how to transfer the data successfully from one apartment/process to another. Remember that the data transfer could potentially be from one machine to another. This data transfer process is called marshaling.

Developers using Microsoft tools get marshaling code generated for free as one of the results of running MIDL.EXE over their IDL. MIDL generates proxy/stub code automatically so that proxy objects are already defined, and COM has to do very little.

Borland developers do not use MIDL and so do not have the proxy/stub code generated on their behalf. This would seem to suggest that Borland developers who need marshaling would need to write it themselves, which is done by making your object support the IMarshal interface. But fortunately, the checkbox mentioned earlier and visible in Figure 1 means this rarely needs to be the case.

Automation Marshaling

There is a special type of COM server, where the COM object(s) support the IDispatch interface, called an Automation server. The IDispatch interface allows many scripting languages to control the COM object's methods and properties using run-time method calls to IDispatch (a method call dispatching interface).

Information on writing and controlling Automation servers can be found in Reference 4 if you need to review it. However, all we need to know right now is that Automation has its own marshaling mechanism that can successfully deal with a certain number of data types.

The permissible types are:

As long as we restrict ourselves to these supported data types, the COM object wizard's checkbox gives us access to Automation marshaling for free. If you neglect to keep the checkbox on the wizard checked, you can get the same result in the type library editor. For each interface you create, the Flags page of the type library editor has an Ole Automation checkbox (see Figure 2).

Figure 2: The flag that requests Automation marshaling

VARIANT Arrays For Custom Data

Automation marshaling may seem a little restrictive at first, since custom structs are not permitted. However, you should keep in mind that a VARIANT can hold an array of elements (called a safe array).

The type of each element can be any type supported by a VARIANT, including a VARIANT. So you could create a safe array of bytes of an appropriate size, which could easily be given the struct data.

The VCL supports safe arrays in the Variant class to simplify their management, calling them Variant arrays. Look up the term SafeArrays in the online help for more information, or look at the example project under C++Builder's Examples\Doc\VarArray directory for a project that uses Variant arrays.

Type Libraries

As mentioned earlier, a type library is part of the result of running MIDL.EXE across some IDL for Microsoft developers. But this is not the pattern employed by C++Builder developers.

Instead, a type library enters the equation right at the start of COM development. However, before worrying about the development side of things, let's find out what purpose a type library fulfils.

A type library is a binary file that can accompany a COM server (either as a separate file, or bound into the executable as a resource). It defines all the public interfaces available from the COM server, as well as custom enumeration values and other type information used by them.

Any program that needs to know what is on offer from a particular COM server can do so by examining the type library, if there is one.

Development tools such as Delphi and C++Builder can use the information in a server's type library to generate local language declarations of all the interfaces available from the COM server and store them in a dedicated source file (called a type library import unit).

C++Builder generates C++ interface definitions, whilst Delphi generates Object Pascal interface types. This saves the developer having to locate (or write) the appropriate definitions for themselves.

Type Library Editor

C++Builder has a dedicated type library editor window, available through View | Type Library when a type library is in the active project (see Figure 3).

Figure 3: The type library editor

When developing COM servers with the original Microsoft tools, you would first define your interfaces in IDL, which would be run through MIDL.EXE. This generated the type library file and C++ source files. The source would define the interfaces, and also define the classes that implement those interfaces, with stub methods waiting to be filled in with the implementation.

C++Builder turns this process into a visual development cycle. Once you add a COM object (via a dedicated wizard) into a C++Builder project, it automatically generates a type library. It also adds the description of the interface and the coclass into the type library and generates a type library import unit.

This unit has a name based on the library name (the top node in the tree on the left of the type library editor). If the project is called XXXX, the type library name will also be called XXXX by default (although this can be changed in the type library editor), and will be saved as XXXX.tlb. If the type library name is XXXX, the import unit will be called XXXX_TLB.CPP with an associated header file.

You can switch between the type library editor and the import unit by pressing F12 ( View | Toggle Form/Unit).

The wizard continues by defining an implementation unit, which contains the definition of a class that implements your interface (i.e. the COM class or coclass), leaving stub methods waiting to be filled in with the implementation.

The type library editor then allows you to add properties and methods to the interface, as well as defining other interfaces and coclasses. At any stage, the type library editor's Refresh button can be pressed to update the type library import unit, as well as the implementation unit. Any new interface methods will have a corresponding stub method added to the implementing class.

All this is done automatically and without resort to IDL. But if you want, you can always export an IDL version of the type library contents with the Export button on the type library editor toolbar (visible in Figure 3). You can also edit the type library content in IDL format on the various Text pages of the type library editor's page control.

COM Servers

A COM server is an executable or DLL that contains coclasses and typically comes with a type library. There are various differences to how the executable and DLL servers manage themselves, so we should take a look at the details.

In-Process Servers

A DLL-based sever is called an in-process server (or in-proc server) because DLLs are loaded into the address space of the calling application. As a consequence, the COM object code and the client code are in the same process.

This means the client code can talk directly to the COM object methods (unless there is a clash between the client apartment and the threading model of the COM object, whereupon COM will provide proxy objects to manage the difference.

Either way, the code is in the same process and so the job of marshaling the data from client to object and back is trivial. Consequently, marshaling is not an issue with in-proc servers, and method calls to in-proc servers are more efficient. This explains why most COM servers are built as in-proc servers.

In order for COM to detect a clash, in-process server objects advertise their threading model in the Windows registry. When COM is creating the COM object it can check on the client apartment and compare it with the threading model and make proxy objects if necessary.

A DLL-based COM server is simply a normal DLL. In-proc servers typically have a .DLL or .OCX extension, although the extension is immaterial. To make it fulfil the requirements to be a COM server, four routines are exported in order for COM to liase with it correctly.

You can make an in-proc server by choosing the ActiveX Library icon on the ActiveX page of the File | New... dialog. If you look at the bottom of the project source (Project | View Source) you will see the four exported routines: DllCanUnloadNow(), DllGetClassObject(), DllRegisterServer() and DllUnregisterServer().

DllGetClassObject() is called by COM to get access to a COM object's class factory, which is then asked to create an instance of its associated class.

DllCanUnloadNow() is called to see if any COM objects still exist in the server. If all objects have been destroyed, the DLL can be unloaded.

The other two routines are used for registering and unregistering the in-proc server.

COM objects can now be added to the server using the supplied wizards on the ActiveX page of the File | New... dialog.

Out-Of-Process Servers

An executable based COM server is called an out-of-process server (or out-of-proc server) because the client will inherently be in a separate process address space from the server. Consequently, COM will always give proxy objects to the client application. The proxy objects deal with marshaling parameters from client to server and back again. Out-of-process servers will always be less efficient than in-process servers for this reason.

You can make an out-of-proc server by simply making a new C++Builder application. COM objects can be added to the server using the supplied wizards on the ActiveX page of the File | New... dialog.

When a COM object in an out-of-process server is made with the COM Object wizard, the threading model choice is not used for anything. It is primarily for in-proc servers. However, you must decide the default apartment type for the server. When the server starts up, COM is initialised and a flag determines if an STA or MTA is entered.

You can set that flag on the ATL page of the Project | Options... dialog. Figure 4 shows the options, which are all used to customise how ATL will work. ATL is Microsoft's Active Template Library, which is used to implement the functionality in C++Builder COM servers.

Figure 4: The ATL options

The various threading models of the COM objects in the server should be used to decide the best apartment type to enter.

If all objects are Apartment threaded, you should choose the APARTMENTTHREADED option, to get a value of COINIT_APARTMENTTHREADED passed to the COM initialisation routine.

If you have any objects with a threading model of Free or Both, you should choose the other radio button to get a value of COINIT_MULTITHREADED passed to the initialisation call.

Incidentally, you should only use the OLE Initialization COINIT_xxx Flag radio buttons to set the primary thread apartment type. The other radio buttons in the Threading Model group are for backward compatibility with C++Builder 3.

When COM invokes an out-of-process server, it expects the server to make class factory known by calling CoRegisterClassObject on startup and removing it from view with CoRevokeClassObject before terminating (this is done automatically by VCL code in the COM support units).

Remote Process Servers

DCOM allows out-of-proc servers to be located on different machines to the client, with the client not necessarily being aware of the server's disparate location. DCOM uses RPC to transport the method calls around the network.

Proxy objects are always used by the client and so, as explained earlier, the server's type library must be registered on the client machine to permit this to work.

Instancing Options

Another option you can set with the ATL options (Figure 4) is the instancing option of your server. This is only relevant to out-of-process servers as it controls how many COM object instances can be managed by a single instance of your server application.

The Single Use option means that each server instance can only manage a single instance of a COM object. Each new created instance will be housed in additional instances of the COM server.

For COM servers that have a UI, this option corresponds to a Single Document Interface (SDI) application.

The other option, Multiple Use (the default), allows many separate instances of the COM object (created by potentially many client applications) to be housed in the same server executable instance.

For COM servers that have a UI, this option corresponds to a Multiple Document Interface (MDI) application. This option would allow all COM objects access to global server data, so you should make sure such data is protected with synchronisation objects.

COM Registration

In order for COM to be able to locate a given COM object (identified by its CLSID), the corresponding server must be registered.

This means that information about the server, its location and content must be added to the Windows registry in order for the server to be useable. The details that are stored will vary between in-proc servers and out-of-proc servers, but the server deals with it all when asked without any fuss or bother.

In-proc Servers

An in-process server exports four routines from the project source, as seen earlier. Two of these routines have already been described. The other two, DllRegisterServer() and DllUnregisterServer(), clearly do the job of registering and unregistering the server.

There are various ways to get the appropriate exported routine called.

The IDE

If you are developing the server in the IDE, you can use the Run | Register ActiveX Server and Run | Unregister ActiveX Server menu items.

These menu items compile the server, then load it into the IDE's address space (with LoadLibrary). The GetProcAddress() API is then used to locate either DllRegisterServer() or DllUnregisterServer() and, assuming one is found, it is called.

TRegSvr

TRegSvr.Exe (Borland Turbo Register Server) is a command-line tool supplied in C++Builder's Bin directory. It (by default) registers and (with a -u switch) unregisters in-proc servers and type libraries.

RegSvr32

RegSrv32.Exe is a similar tool to TRegSvr.exe, but which comes with all Win32 platforms in the Windows System directory. It (by default) registers and (with a /u switch) unregisters in-proc COM servers, but cannot handle type libraries.

Out-Of-Proc Servers

Out-of-proc servers are not DLLs, so they do not export special routines for registration and un-registration. Instead, being executables they take command-line parameters to do the job.

Command-Line Parameters

The /regserver command-line parameter registers a COM server. With this parameter, the server will start, register and then close without displaying any UI.

You can also register an out-of-proc server by simply running it. This will cause it to register before displaying its main form, but the application will stay running until you close it.

The /unregserver command-line parameter unregisters a COM server. With this parameter, the server will start, unregister and then close without displaying any UI.

Remote Servers

Typically, a remote out-of-proc server will be registered just as described above. However, as discussed earlier , the server's type library must also be registered on the client machine to allow Automation marshalling to work.

This assumes that the client application knows which machine the server is on. If the client is unaware of the server being on a different machine, then additional registration must occur on the client machine to allow COM to know that the server is remote, and where to find it.

Building A Server

To test out the theory described thus far, we can now create a DLL server. This will be an in-proc server with a simple coclass defined therein.

Select File | New... and choose ActiveX Library from the ActiveX page of the dialog. This project can be saved as COMServer.bpr (that is the name of the sample server project that accompanies this paper).

Now select File | New... and choose COM Object from the ActiveX page of the dialog. Specify a coclass name of Foo and enter a description. Choose File | Save All and accept the suggested name of FooImpl.cpp for the Foo coclass implementation unit.

In the header for this unit will be the declaration of a class called TFooImpl, which is the C++ representation of the Foo coclass. The details of this class are not so important right now, but we will look at some of the ancestor classes a bit later.

COM Properties

Using the type library editor (View | Type Library) add in two read-only properties called UserName and MachineName. Each should be of type BSTR, which is the Automation-compatible wide character string type. Automation does not support any string types made of ANSI characters.

Press Refresh to update the implementation unit. If you now look at the FooImpl.cpp unit, you will see that two stub property reader methods have been constructed (see Listing 5).

Listing 5: Two new COM property reader methods

STDMETHODIMP TFooImpl::get_MachineName(BSTR* Value)
{
  try
  {

  }
  catch(Exception &e)
  {
    return Error(e.Message.c_str(), IID_IFoo);
  }
  return S_OK;
};


STDMETHODIMP TFooImpl::get_UserName(BSTR* Value)
{
  try
  {

  }
  catch(Exception &e)
  {
    return Error(e.Message.c_str(), IID_IFoo);
  }
  return S_OK;
};

The properties are of type BSTR, but the property reader methods must return a HRESULT (accomplished by the STDMETHODIMP macro).

Rich Errors

Notice that all VCL exceptions are trapped (it is an error to let exceptions bubble out of a COM method). Any trapped exception has its message passed along to the ATL Error() method (a method of the CComCoClass class). This sets up rich error information by setting up a COM error object to represent the exception and then returns an error-indicating HRESULT.

Any interested client code can spot the error HRESULT, then test to see if rich error information is supported. If it is, it can extract the information from the COM error object and if needed recreate an exception from the stored message.

Returning Strings

All that is left for this simple server is to make the property reader methods return the appropriate data. Listing 6 shows the code that can do the job.

Listing 6: Implementing the two properties

STDMETHODIMP TFooImpl::get_MachineName(BSTR* Value)
{
  try
  {
    char Name[MAX_COMPUTERNAME_LENGTH + 1];
    unsigned long Len = sizeof(Name);
    Win32Check(GetComputerName(Name, &Len));
    WideString WName = Name;
    *Value = WName.Copy();
  }
  catch(Exception &e)
  {
    return Error(e.Message.c_str(), IID_IFoo);
  }
  return S_OK;
};


STDMETHODIMP TFooImpl::get_UserName(BSTR* Value)
{
  try
  {
    char Name[256];
    unsigned long Len = sizeof(Name);
    Win32Check(GetUserName(Name, &Len));
    WideString WName = Name;
    *Value = WName.Copy();
  }
  catch(Exception &e)
  {
    return Error(e.Message.c_str(), IID_IFoo);
  }
  return S_OK;
};

Both GetComputerName() and GetUserName() use normal char * string buffers for their work. They also both return 0 when they fail (you call GetLastError() to find an error code indicating what the failure was).

The code uses the VCL helper routine, Win32Check(), to take the API return values and raise appropriately descriptive exceptions upon failure.

The code also uses a VCL WideString object to translate the ANSI strings into wide strings. A BSTR version of a WideString object can be obtained using the c_bstr() method, but in this case the Copy() method is used to ensure we get a duplicate wide string, as the original version will be disposed of when the WideString object goes out of scope.

With the server complete, you can register it (choose Run | Register ActiveX Server).

COM Clients

With the server finished, we can build a client application to test it with. But before proceeding, we should have an understanding of what is placed in the type library import unit.

Type Library Import Unit

The unit itself (the .CPP file) has little in it except for some initialised GUID struct constants (interface IDs, class IDs and type library IDs). However, the header file is where all the stuff of interest lies (remember you can switch between a source file and header with Ctrl+F6, or by right-clicking and choosing Open Source/Header File).

A namespace is defined, based on the name of the unit. Within the namespace, the following items can be found.

External references to the GUID constants in the unit source itself. For each coclass, XXX, there will be an interface ID for the primary interface IXXX, called IID_IXXX, a class ID for the coclass called CLSID_XXX. There will also be a type library ID called LIBID_YYY, where YYY is the name of the type library.

For a project called COMServer.bpr with a coclass called Foo, Listing 7 shows the constants.

Listing 7: GUID constants

extern __declspec (package) const GUID LIBID_COMServer;
extern __declspec (package) const GUID IID_IFoo;
extern __declspec (package) const GUID CLSID_Foo;

The next item is a forward declaration of the coclass primary interface (IFoo) including its IID, which is accompanied by a type definition that wraps the interface pointer type. This type can be used by clients to reference the interface (IFooPtr), as shown in Listing 8. We will see TComInterface again shortly, as it acts as a base class for smart interface wrappers which increment and decrement an interface's reference count automatically.

Listing 8: Type declarations

interface DECLSPEC_UUID("{01AD712E-66D8-11D4-96EC-0060978E1359}") IFoo;
typedef TComInterface<IFoo, &IID_IFoo> IFooPtr;

Next are more type definitions, mapping each coclass to its default interface (see Listing 9). You can also se a simple macro that returns the type library ID for the Foo coclass.

Listing 9: More type declarations

typedef IFoo Foo;
typedef IFooPtr FooPtr;

#define LIBID_OF_Foo (&LIBID_COMServer)

At last we get to see the definition of the primary interface itself, IFoo (see Listing 10). The two property reader methods are declared as pure virtual methods that return a HRESULT and have a pointer parameter that returns the required BSTR.

Listing 10: The primary interface of the coclass

interface IFoo  : public IUnknown
{
public:
  virtual HRESULT STDMETHODCALLTYPE get_UserName(BSTR* Value/*[out,retval]*/) = 0; // [1]
  virtual HRESULT STDMETHODCALLTYPE get_MachineName(BSTR* Value/*[out,retval]*/) = 0; // [2]
};

Smart Interface Wrappers

Following the interface definition (a pure virtual struct), another class is defined that acts as a smart interface wrapper. This class can be used by clients to access the interface, and will deal with incrementing and decrementing the interface reference count automatically, simplifying the COM programmer's task significantly.

The class inherits from the TComInterface template class, which is passed the appropriate interface that it will work with (see Listing 11) and also TComInterfaceBase<IUnknown>. You can see a number of constructors defined to cater for different circumstances, and also overloaded versions of the property reader methods. For flexibility, there are two definitions of each method related to a property.

One definition returns a HRESULT, and gives access to the BSTR through a pointer parameter (as in the original interface in Listing 10). The other definition takes no parameters and simply returns a BSTR, rather like the original property definition in the type library.

However, C++ interfaces do not use properties, and so Listing 10 only shows methods. This new property-like method is used by the smart interface class in the definition of a property that corresponds to the one defined in the type library.

Listing 11: The smart interface wrapper class

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

  HRESULT         __fastcall get_UserName(BSTR* Value/*[out,retval]*/);
  BSTR            __fastcall get_UserName(void);
  HRESULT         __fastcall get_MachineName(BSTR* Value/*[out,retval]*/);
  BSTR            __fastcall get_MachineName(void);

  __property   BSTR            UserName = {read = get_UserName};
  __property   BSTR            MachineName = {read = get_MachineName};
};
typedef TCOMIFooT<IFoo> TCOMIFoo;

You can see that for any coclass XXX, the smart interface wrapper class for its primary interface is defined by a typedef to be TCOMIXXX.

Immediately following this template class definition are the implementations of the interface access methods (four in this case). As an example, Listing 12 shows the two get_UserName() methods.

Listing 12: The smart interface wrapper class methods

template <class T> HRESULT __fastcall
TCOMIFooT<T>::get_UserName(BSTR* Value/*[out,retval]*/)
{
  return (*this)->get_UserName(Value);
}

template <class T> BSTR __fastcall
TCOMIFooT<T>::get_UserName(void)
{
  BSTR Value = 0;
  OLECHECK(this->get_UserName((BSTR*)&Value));
  return Value;
}

The first one is straightforward. Since this method matches the signature of the interface method, a direct call is made, and the HRESULT from the interface method call is passed back to the caller.

The second one, on the other hand, is designed to be more convenient to call (it is called when you read from the smart interface wrapper class property UserName) and so has a different signature. Consequently, the implementation is a little more fiddly. A local BSTR is defined, passed as a parameter to the other method, and then returned to the caller. The HRESULT from the other method call is passed to OLECHECK() to see if an error is indicated.

Error Handling

Notice in the second method in Listing 12 that the HRESULT returned from the call to the first method is passed to OLECHECK(). OLECHECK() is a macro defined in UtilCls.h that calls DebugHlpr_HRCHECK() from the same unit. The job of DebugHlpr_HRCHECK() is to examine a HRESULT and take appropriate action if it indicates an error (otherwise do nothing).

Appropriate action means either raising an EOleException exception (if NO_PROMPT_ON_HRCHECK_FAILURE is defined) or to call DebugHlpr_PROMPT(). DebugHlpr_PROMPT() displays a message box, allowing the user to choose whether an exception should be raised, the problem should be ignored, or the debugger should break the program at the current point.

Figure 5: The COM error message dialog

To avoid getting the message box, ensure that NO_PROMPT_ON_HRCHECK_FAILURE is defined before UtilCls.h is included in your unit. Alternatively, in your own code you can use the VCL routine OleCheck() from the ComObj.hpp header, which always throws an EOleSysError exception when it is passed an error-indicating HRESULT.

Using either of these helper routines is much easier than deciphering HRESULT values yourself.

Creator Objects

The final thing of note in the type library import unit header generated for the COM server is another class (Listing 13). This class is also usable by clients to help simplify the process of connecting to an instance of a server.

Listing 13: The default interface creator class

typedef TCoClassCreatorT<TCOMIFoo, IFoo, &CLSID_Foo, &IID_IFoo> CoFoo; 

For a coclass called XXX, the default interface creator object will be called CoXXX. It has two key static methods called Create() and CreateRemote(), which are designed to create a local or remote copy of the coclass respectively, using standard COM/DCOM calls, and return a reference to the primary implemented interface.

Note: since this class uses static methods, you do not need to construct an instance of it.

This client helper class does not seem to have an official description, so you can call it a creator object, a default interface creator class, or a client-side coclass proxy class.

Building A Client

With knowledge of the type library import unit content under our metaphorical belt, we can now try and build a client application that can connect to the server. So start by making a new project. The example that accompanies this paper is called Client.bpr.

Since we will need to gain access to types defined in the type library import unit you will need to include the header for the unit in your form unit source. Assuming the client and server are in different directories, you will either need to include the relative path to the header file in the #include directive or add an appropriate search path in the Project | Options... dialog on the Directories/Conditionals page.

To avoid any COM method calls resulting in the aforementioned message box, define NO_PROMPT_ON_HRCHECK_FAILURE above the #include directive.

You should also add the COMServer_TLB.cpp file to the client project as this unit defines a number of aforementioned initialised GUID struct constants that the linker will need to make the final EXE. You can do this with Project | Add to Project..., or Shift+F11.

Note: versions of C++Builder earlier than 5 had a bug in the project manager. If you have the client and server project in a project group, you will not be able to add the server's type library import unit to the client project with the project group open. Instead you must close the project group, and open up the client project on its own in order to accomplish the task.

Next, we need a variable that we can use to access the interface of the COM server object. In the form constructor, declare a variable called Foo. To make your life easy, define it in terms of the smart interface wrapper class, TCOMIFoo, described earlier.

The variable can be initialised by assigning it the return value of a call to the static Create() method of the CoFoo class.

So far, the form unit (ClientMainForm.cpp in this example) looks like Listing 14. The added code is highlighted in bold.

Listing 14: Connecting to the server

#include <vcl.h>
#pragma hdrstop

#define NO_PROMPT_ON_HRCHECK_FAILURE
#include "..\Server\COMServer_TLB.h"
#include "ClientMainForm.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TClientForm *ClientForm;
//---------------------------------------------------------------------------
__fastcall TClientForm::TClientForm(TComponent* Owner)
  : TForm(Owner)
{
  //Connect to server
  TCOMIFoo Foo = CoFoo::Create();
}

Now to make use of the COM object's properties, add two labels on the form. The labels can be given names of lblMachineName and lblUserName, and will be assigned the values read from the UserName and MachineName COM object properties.

To finish the job off, we can add the property reading code to the form constructor (see Listing 15). The client application can be seen running in Figure 5.

Listing 15: Reading the COM properties

__fastcall TClientForm::TClientForm(TComponent* Owner)
  : TForm(Owner)
{
  //Connect to server and read properties
  TCOMIFoo Foo = CoFoo::Create();
  WideString UserName = Foo.UserName;
  lblUserName->Caption = "Logged in user: " + UserName;
  WideString MachineName = Foo.MachineName;
  lblMachineName->Caption = "Machine name: " + MachineName;
}

Figure 6: The finished client running

The VCL-ATL Bridge

Now that we have learnt about the pertinent terms in COM application development, and seen what's involved in building COM servers and clients, we will now explore some of the inner workings of these applications as well as browsing through some of the client-side helper classes.

C++Builder COM server applications use portions of Microsoft's ATL to do their job. You can learn about the ATL from many sources, including the book ATL Internals (see Reference 7). Additionally, one of the Borland developers who works on the COM support has discussed how ATL is used in C++Builder COM applications in papers presented at Borland's annual conference in 1999 and 2000 (see Reference 5 and Reference 6).

The ATL has been enhanced to accommodate Borland's requirements. The modifications reside in $(BCB)\include\atl\atlmod.h, which contains ATL-based helper classes, and $(BCB)\include\atl\atlvcl.h, which contains the connective tissue between the ATL framework and VCL components.

TATLModule and TComModule

In a fresh ActiveX Library project, the project source file contains an instantiation of a TComModule object:

TComModule Project1Module;
TComModule &_Module = Project1Module;

TComModule is defined in atlmod.h as:

typedef TATLModule<CComModule> TComModule;
extern TComModule &_Module;

where TATLModule is a special wrapper template for ATL's CComModule.

In an in-process server, TComModule acts identical to CComModule. In an out-of-process server, it performs additional tasks:

The _Module variable is referred to by the routines exported by an in-proc server (DllCanUnloadNow(), DllGetClassObject(), DllRegisterServer() and DllUnregisterServer()), among other places.

CComObjectRootEx and CComCoClass

When we first created a COM server earlier, a C++ class was created, looking like Listing 16.

Listing 16: The C++ representation of a coclass

/////////////////////////////////////////////////////////////////////////////
// TFooImpl     Implements IFoo, default interface of Foo
// ThreadingModel : Apartment
// Dual Interface : FALSE
// Event Support  : FALSE
// Default ProgID : COMServer.Foo
// Description    : The Foo coclass
/////////////////////////////////////////////////////////////////////////////
class ATL_NO_VTABLE TFooImpl : 
  public CComObjectRootEx<CComSingleThreadModel>,
  public CComCoClass<TFooImpl, &CLSID_Foo>,
  public IFoo
{
public:
  TFooImpl()
  {
  }

  // Data used when registering Object 
  //
  DECLARE_THREADING_MODEL(otApartment);
  DECLARE_PROGID("COMServer.Foo");
  DECLARE_DESCRIPTION("The Foo coclass");

  // Function invoked to (un)register object
  //
  static HRESULT WINAPI UpdateRegistry(BOOL bRegister)
  {
    TTypedComServerRegistrarT<TFooImpl> 
    regObj(GetObjectCLSID(), GetProgID(), GetDescription());
    return regObj.UpdateRegistry(bRegister);
  }

BEGIN_COM_MAP(TFooImpl)
  COM_INTERFACE_ENTRY(IFoo)
END_COM_MAP()

// IFoo
public:

};

The class inherits from the ATL template CComObjectRootEx, which implements the lifetime management and interface querying methods of IUnknown methods, and some of the synchronisation required in some threading models. Since this coclass uses the Apartment threading model, it inherits from CComObjectRootEx<CComSingleThreadModel>.

CComCoClass is another ATL template which provides the default class factory, and sets up error information. TFooImpl inherits this template to get correct class factory support. As you can see, CComCoClass associates a CLSID with your C++ class. The association is completed in the object map in the project source file.

Listing 17: The COM server object map

// The ATL Object map holds an array of _ATL_OBJMAP_ENTRY structures that
// described the objects of your OLE server. The MAP is handed to your
// project's CComModule-derived _Module object via the Init method.
//
BEGIN_OBJECT_MAP(ObjectMap)
  OBJECT_ENTRY(CLSID_Foo, TFooImpl)
END_OBJECT_MAP()

TFooImpl also inherits IFoo in order to have the relevant pure virtual interface methods defined.

The ATL_NO_VTABLE macro visible in Listing 16 specifies that this class should not have a virtual method table (or vtable), as it will never be directly instantiated. Instead, this class will always be used internally by the server as a parameter for another template (which will have a vtable).

Registrar Classes

C++Builder COM server registration support is provided by registrar template classes. Listing 16 shows the COM object's static UpdateRegistry method using a registrar object to manage the actual (un)registration process. These classes can mostly be found in atlmod.h.

The base registrar class is TRegistrarBaseT, which defines methods for adding and deleting registry entries. If you define the SHOW_CREATEREGKEY_VALUE symbol before including atlmod.h, all the registrar classes will display each created registry key in a Windows message box.

TComServerRegistrarT inherits from TRegistrarBaseT, and supports registering a basic COM server (not including its type library). It adds various fields that hold information about the COM server, and an UpdateRegistry virtual method. COM servers that require special registration (such as ActiveX controls or MIDAS servers) can define a custom registrar class and override this method, calling the base class version first.

TTypedComServerRegistrarT inherits from TComServerRegistrarT, adding support for resistering the COM server's type library.

TAxControlRegistrar inherits from TTypedComServerRegistrarT and adds appropriate support for registering ActiveX controls.

TRemoteDataModuleRegistrar also inherits from TTypedComServerRegistrarT and adds support for controlling the accessibility of the remote data module (used in a MIDAS application) and also whether the module can be pooled between clients. This class resides in atlvcl.h.

TComInterface and TComInterfaceBase

When you look in a type library import unit, as used in a COM client application, you find several types and classes defined, and these have been discussed earlier on in this paper. Two classes were mentioned in the section on smart interface wrappers and we will look at them in a little more detail now.

The smart wrapper classes typically inherit from TComInterface<SomeInterface> and TComInterfaceBase<IUnknown>, both of which can be found in utilcls.h.

TComInterface is a generic COM interface wrapper which takes care of calling AddRef and Release when the object is copied, assigned to and deleted. In Listing 11, you can see the template class TCOMIFooT being defined in terms of TComInterface. You can also then see a more useful class type being declared at the end of the listing. TCOMIFoo is the class that you will typically use from inside a client application.

TComInterfaceBase is a class designed to provide access to system-wide data which is often required by COM routines. This includes the locale ID (lcid) and a TNoParam object that can be used in calls to Automation server methods as a parameter marked as optional, whose default value you wish to use (OptParam). It also includes a pair of values that can be used to pass True or False values to methods that have VARIANT parameters (Variant_True and Variant_False).

More information on Automation and surrounding issues can be found in my Automation paper (see Reference 4).

Summary

COM is a language-independent distributed object model. This paper describes the steps required to build a COM server in C++Builder 5, and also how to connect to that same server from a client written in C++Builder 5. Any other COM-capable language, such as Delphi or Visual Basic, can also connect to this server using steps appropriate to that language.

You can learn more about COM in C++Builder by reading the pertinent parts of C++Builder 5 Developer's Guide (see Reference 8).

References

  1. More Automation In Delphi, an ICon UK 2000 paper, Brian Long.
    This article looks at more advanced COM and Automation topics, such as events and the Running Object Table (ROT), from a Delphi perspective, but the background information is language-neutral.

  2. Binh Ly's COM Library.
    This includes coverage of events/callbacks with a variable amount of material in Delphi and C++Builder syntax.

  3. Multi-Threading And COM, The Delphi Magazine, Issue 60, Brian Long.
    This article that looks at how multi-threaded COM applications work, and what COM does to ensure they work. Whilst it is a Delphi article, much of the discussion is language-neutral.
  4. Writing And Controlling Automation Servers In C++Builder, an ICon 1999 paper, Brian Long.
    This article discusses the details of accessing and creating Automation servers.
  5. The Glue that Binds: Understanding the internals of C++Builder's ActiveX support, an ICon 1999 paper, Robert West.
    This paper discusses the classes and macros used in C++Builder COM applications, and shows how ATL is used therein.
  6. Bridging the Gap: Understanding the C++Builder ActiveX Framework, an ICon 2000 paper, Robert West.
    This update paper discusses the classes and macros used in C++Builder COM applications, and shows how ATL is used therein.
  7. ATL Internals, Brent Rector & Chris Sells, Addison-Wesley, 1999.
    This book looks intimately at the workings of Microsoft's Active Template Library.
  8. C++Builder 5 Developer's Guide, Jarrod Hollingworth, Dan Butterfield, Bob Swart, Jamie Allsop, et al, SAMS, 2000.
    This book has good coverage of many aspects of C++Builder 5. In relation to this paper, you should read Chapter 16, COM Programming, by Ionel Munoz, and perhaps also Chapter 17, Going Distributed: DCOM, by Eduardo Bezerra.

About Brian Long

Brian Long used to work at Borland UK, performing a number of duties including Technical Support on all the programming tools. Since leaving in 1995, Brian has spent the intervening years as a trainer, trouble-shooter and mentor focusing on the use of the C#, Delphi and C++ languages, and of the Win32 and .NET platforms. In his spare time Brian actively researches and employs strategies for the convenient identification, isolation and removal of malware. If you need training in these areas or need solutions to problems you have with them, please get in touch or visit Brian's Web site.

Brian authored a Borland Pascal problem-solving book in 1994 and occasionally acts as a Technical Editor for Wiley (previously Sybex); he was the Technical Editor for Mastering Delphi 7 and Mastering Delphi 2005 and also contributed a chapter to Delphi for .NET Developer Guide. Brian is a regular columnist in The Delphi Magazine and has had numerous articles published in Developer's Review, Computing, Delphi Developer's Journal and EXE Magazine. He was nominated for the Spirit of Delphi award in 2000.