ActiveX and the VCL (part 1) : Introducing the wizards

By: Andrew Ames

Abstract: Discusses the code used and generated by the ActiveX wizards

ActiveX and the VCL (part 1) Go to part 2
Introducing the wizards

The purpose of these articles is to provide the reader with the background information necessary to create and use ActiveX and COM servers with Borland C++ Builder 4.  The main focus of part 1 is on the code used and generated by the ActiveX wizards.  This article assumes familiarity with DLLs and COM.  For a good book on these topics, see "Inside COM" by Microsoft Press.

In-process Servers
An in-process server is a DLL that serves one or more COM components.  It's referred to as in-process because, when DLLs, are loaded by an application's process, they are placed within that process' address space.  To create an in-process COM server in C++ Builder, go to File | New, and on the ActiveX tab, choose ActiveX Library.  Two units are created: Project1.cpp and Project1_ATL.cpp.  These two units manage most of the details associated with COM servers such as, component registration, the DLL's lifetime control, and serving the component classes implemented in the server.  C++ Builder's COM wrappers are built upon the Active Template Library (ATL).  The ATL is Microsoft's template class library that removes much of the repetitive tasks involved in building COM servers.  For example, the ATL provides a template class that implements a generic version of IUnknown (CComObjectRootEx).  The ATL also provides a template class that implements the methods that are exported by an in-process COM server (CComModule).  The ATL was specifically designed to be used by the Visual C++ ActiveX wizards, therefore, documentation on the ATL nearly always assumes you're using VC++.

The ATL and the ATLVCL source files are located in "[BCB]includeatl".  I refer to C++ Builder's ATL wrapper as ATLVCL.  The ATL consists of the following files:

  • statreg.h/cpp : Performs high level registration key parsing. (CRegObject and CRegParser).
  • atlbase.h : Contains declarations of many supporting classes such as smart pointers, BSTR and VARIANT wrappers, threading model classes, and declarations for global ATL functions (_ATL_MODULE, CComPtr, CComQIPtr, CComBSTR, CComVariant, CComCriticalSection, CComMultiThreadModel, etc.).
  • atliface.h : Generated from atliface.idl.  Contains the declaration for IRegistrar, implemented by component CLSID_Registrar.  This component is served by atl.dll.  To avoid requiring the distribution of this DLL, the ATLVCL wrappers duplicate its functionality.  Therefore, atliface.h is rarely ever used in conjunction with C++ Builder.
  • atlcom.h : This file has most of the ATL declarations including classes and functions for reference count debugging, component creation via CreateInstance, and implementing IUnknown, IClassFactory, and IDispatch (CComObjectRootEx, CComCreator, CComClassFactory, IDispatchImpl, etc.).
  • atlconv.h/cpp : Various functions for converting and copying strings (e.g. converting from ANSI to UNICODE, copying OLESTR strings, etc.).
  • atlctl.h/cpp : Helper classes for implementing ActiveX controls. (CComControl, CComDispatchDriver, IPersistImpl, IPersistStorageImpl, IPersistPropertyBagImpl, etc.).
  • wtypes.h : Generated from wtypes.idl.  Contains declarations of many Win32 types to allow these types to be used by automation servers.
  • atlwin.h/cpp : Contains generic implementations of windows and dialog boxes used with out-of-process COM servers.
  • build_.h : Contains macros for the current ATL build version.
  • atlimpl.cpp : Implements most of the global functions and some class methods declared in the ATL header files.
The ATLVCL consists of the following:
  • atlmod.h : Contains TATLModule (derived from CComModule) and registry manipulation classes for components.  TATLModule provides extra functionality for out-of-process servers in addition to CComModule's functionality.  The registry manipulation template classes, TRegistrarBaseT, TComServerRegistrarT, TTypedComServerRegistrarT, TRemoteDataModuleRegistrar, and TAxControlRegistrar, provide the same functionality as component CLSID_Registrar, served by atl.dll.
  • atlvcl.h/cpp : Contains helper classes for VCL-specific ActiveX controls. (IDataBrokerImpl, TVCLComControl, TVCLControlImpl).


The Project1_ATL source unit created for an ActiveX Library provides the necessary ATL and ATLVCL headers and source files for the DLL.  Project1.cpp is the main source file for the DLL and exports the following functions required for an in-process COM server.

  • int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void*)
  • STDAPI __export DllCanUnloadNow(void)
  • STDAPI __export DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
  • STDAPI __export DllRegisterServer(void)
  • STDAPI __export DllUnregisterServer(void)
Found near the top of this file are the following lines:
 
TComModule  Project1Module;
TComModule &_Module = Project1Module;


This creates a global instance of TComModule and assigns it to the reference _Module.  _Module is used in all ATL and ATLVCL source files to refer to the single global instance of the CComModule-derived class.  This name must never be changed.  Before continuing, save this project as "Server.bpr".

Bare bones COM object
Before creating any components, you should save the library to specify the name of the DLL.  To create a bare bones COM object (bare bones meaning it's not an automation server and all interfaces derive only from IUnknown), go to File | New, select the ActiveX tab and choose COM Object.  The New COM Object dialog appears.  "CoClass Name" refers to the name of the component.  The wizard will append this name to CLSID_ for the CoClass identifier and will create a default interface named IName, where "Name" is the CoClass Name.  The "Threading Model" can be chosen here and it can be changed later.  The Threading Model edit box simply tells the server what value to give the Threading Model registration sub key for this component.  It is still up to the component designer to ensure that the component follows the model it claims to support in the registry.  The "Description" text is used as a human readable string associated with this component's CLSID.  This can be changed later as well.  For this example, specify the CoClass Name as "StandardCOM".

After creating this COM object, the type library editor is displayed with two entries: one for the CoClass and one for the CoClass' default interface.  Select the IStandardCOM interface and go to the Flags page.  Uncheck Dual and Ole Automation.  Return to the Attributes page and change the Parent Interface to IUnknown.  What we have now, is a component (CLSID_StandardCOM) which implements an interface (IStandardCOM) that derives only from IUnknown.

Three files have been added to the project: the Server_TLB source unit, the Server.tlb type library, and StandardCOMImpl source unit.  The Server_TLB unit contains declarations for all GUIDs and interfaces that the COM server implements and that are required by clients to access the server.  Development environments such as C++ Builder, Delphi, JBuilder, and Visual C++, can import type libraries, allowing them to automatically generate the Server_TLB source unit compatible with that particular development language and environment.  Any changes made to the type library will be reflected in the Server_TLB unit.  A consequence of this in C++ Builder, is that you cannot make changes to Server_TLB.h or Server_TLB.cpp and expect them to remain.  The StandardCOMImpl source unit implements IStandardCOM.  We are free to edit both the CPP and H file of this unit.

At this point, you can open the type library and add methods to IStandardCOM that will be implemented in StandardCOMImpl.h

Automation COM Object
An automation object is a component that implements IDispatch.  IDispatch provides methods that allow scripting languages and applications such as MS-Word to access a COM Object.  IDispatch uses a form of run-time type checking at the expense of speed.  This makes development of components much more difficult for the developer, while making it easier for those who use macro and interpretive languages.  Fortunately, C++ Builder handles all the tedious details of automation components for us.  To create an automation object, go to File | New, and on the ActiveX tab, choose Automation Object.  Use "AutomationCOM" for the CoClass Name.

Another source unit, AutomationCOMImpl, is added to the project and two entries, AutomationCOM and IAutomationCOM, are added to the type library.  This time, we're going to leave the Parent Interface as IDispatch and Dual and Ole Automation will remain checked.  The Dual flag indicates that this component is to be accessible through automation and the virtual function table, thus allowing languages such as C++ and Object Pascal efficient access (through the vtable) and allowing scripting languages access at all (through the slow IDispatch interface).

ActiveX Control
The term, ActiveX, is thrown around quite loosely these days.  Often the term is interchangeable with COM and OLE.  For a little clarification, COM is the underlying technology on which ActiveX and OLE are both based.  COM is predominantly a means of software versioning and maintaining strict implementation independence.  OLE refers to all of it.  It encompasses COM, automation, document embedding, etc.  An ActiveX control is a component that implements a specifically defined set of interfaces, allowing it to be embedded in a document or application that can act as a container, such as a web browser.  Some of these interfaces are IOleObject, IOleInPlaceObject, IOleControl, IPersistStream, etc.  An ActiveX control has a key in the registry named "Control".  More on component registration later in these articles.

To create an ActiveX control, go to File | New, select the ActiveX tab and choose ActiveX Control.  ActiveX controls in C++ Builder are based upon a VCL control that is already implemented.  The VCL Class Name drop down list box has every VCL component that derives from TWinControl as well as any custom components that exist in the current application.  For this example, choose TDateTimePicker.  (Why?  I don't know.)  Another implementation unit, DateTimePickerImpl, is added to the project.  The type library has several additions to it.  Two interfaces are implemented by the component, IDateTimePickerX (for the properties and methods) and IDateTimePickerEventsX (for the events).  Enumeration types have been added for all enumerations used by TDateTimePicker: TxDTCalAlignment, TxDTDateFormat, TxDTDateMode, TxDragMode, TxImeMode, and TxDateTimeKind.

Notice that the the project manager now displays "Server.ocx" as the root of the project as opposed to "Server.dll".  This is the file extension used by ActiveX controls.

ActiveForm
This is just a special ActiveX control that uses either TActiveForm or another TCustomActiveForm-derived VCL component for its implementation.  Go to File | New, ActiveX, ActiveForm.  A form appears, allowing full use of the form designer and object inspector when creating the control.

Limitations
Before continuing our exploration of the code generated by the wizards, I'd like to point out some inherent limitations with using the wizards.  Unfortunately, automating any programming task involves imposing limitations on the programmer.  You can't avoid it.  Some of these limitations can be sidestepped with simple workarounds, while others are nearly impossible to provide a solution for.  Personally, I've gone to great lengths attempting to raise some of the restrictions and have discovered that, more often than not, you just have to roll with them.

One of the more obvious limitations is that, even when not using OLE automation, the programmer is restricted to using only automation compatible types for interface methods.  Therefore, using a REFGUID (typedefed as GUID& in objbase.h) is just not possible, since a C++ reference is not automation compatible.  Of course the simple solution is to use a GUID* instead.  This limitation also means that you cannot place "#include <windows.h>" at the top of Server_TLB.h and expect to use the structures and types defined in it.  That #include will be erased the next time C++ Builder decides to regenerate the Server_TLB unit. Or perhaps you want to add your own types and structures.  You'll run into the same problem.  If these types are defined in a header file, they must be added to the type library manually before they can be used.  Pointers to pointers also are tricky because the wrappers force a smart pointer on you, altering the type to a pointer to a class (which implements a smart pointer).  Another side affect of this limitation is a loss of compile time checking for more complex types.  For example, if you want to pass function pointers to interface methods, you can do so by casting to a PVOID (which is automation compatible), but no type checking is enforced.  This limitation isn't very restrictive when you start building an entire COM application from scratch using the wizards and tools, but you run into it often when porting non-COM applications to COM applications.

Another restriction is that multiple inheritance cannot be used for interface declarations.  The type library editor allows only one parent interface and changing Server_TLB.h will not help you once again.  However, a component can implement multiple interfaces, but to jump from one implemented interface to another, QueryInterface must be called, even when calling methods through the vtable and not through IDispatch.  Allowing multiple inheritance in the interface declaration could provide a performance boost for APIs written in and used by languages that support multiple inheritance.

Most of the limitations exist when attempting to use language specific features in a server for language specific clients.  Although COM is actually intended as a language independent solution, there are many other benefits of COM to be reaped even when not building language independent software.  Workarounds for these restrictions will be covered throughout these articles.

The Component Object Map
If you've created each of the objects discussed in this article, you'll have the following code near the top of Server.cpp:

BEGIN_OBJECT_MAP(ObjectMap)
  OBJECT_ENTRY(CLSID_StandardCOM, TStandardCOMImpl)
  OBJECT_ENTRY(CLSID_AutomationCOM, TAutomationCOMImpl)
  OBJECT_ENTRY(CLSID_DateTimePickerX, TDateTimePickerXImpl)
  OBJECT_ENTRY(CLSID_ActiveFormX, TActiveFormXImpl)
END_OBJECT_MAP()

These macros create an array of _ATL_OBJMAP_ENTRY structures, called ObjectMap, which is passed to the TComModule::Init method when the DLL is loaded.  The definitions of these macros are found in atlcom.h and the _ATL_OBJMAP_ENTRY structure can be found in atlbase.h.  Following, is the definition of _ATL_OBJMAP_ENTRY:

typedef HRESULT (WINAPI _ATL_CREATORFUNC)(void* pv, REFIID riid,
                                          LPVOID* ppv);
typedef HRESULT (WINAPI _ATL_CREATORARGFUNC)(void* pv, REFIID riid,
                                             LPVOID* ppv, DWORD dw);
typedef HRESULT (WINAPI _ATL_MODULEFUNC)(DWORD dw);
typedef LPCTSTR (WINAPI _ATL_DESCRIPTIONFUNC)();

struct _ATL_OBJMAP_ENTRY {
    const CLSID* pclsid;
    HRESULT (WINAPI *pfnUpdateRegistry)(BOOL bRegister);
    _ATL_CREATORFUNC* pfnGetClassObject;
    _ATL_CREATORFUNC* pfnCreateInstance;
    IUnknown* pCF;
    DWORD dwRegister;
    _ATL_DESCRIPTIONFUNC* pfnGetObjectDescription;
    HRESULT WINAPI RevokeClassObject() {
        return CoRevokeClassObject(dwRegister);
    }
    HRESULT WINAPI RegisterClassObject(DWORD dwClsContext,
                                       DWORD dwFlags) {
        IUnknown* p = NULL;
        HRESULT hRes = pfnGetClassObject(pfnCreateInstance,
                                         IID_IUnknown,
                                         (LPVOID*) &p);
        if (SUCCEEDED(hRes))
            hRes = CoRegisterClassObject(*pclsid, p,
                                         dwClsContext,
                                         dwFlags, &dwRegister);
        if (p != NULL)
            p->Release();
        return hRes;
    }
};

This structure contains pointers to functions that perform tasks specific to each component implementation, such as updating the registry, obtaining the class factory, and creating an instance of the component.  Following is the code created by expanding the object map macros in Server.cpp:

static _ATL_OBJMAP_ENTRY ObjectMap[] = {

    {&CLSID_StandardCOM, &TStandardCOMImpl::UpdateRegistry,
     &TStandardCOMImpl::_ClassFactoryCreatorClass::CreateInstance,
     &TStandardCOMImpl::_CreatorClass::CreateInstance, NULL, 0,
     &TStandardCOMImpl::GetObjectDescription},

    {&CLSID_AutomationCOM, &TAutomationCOMImpl::UpdateRegistry,
     &TAutomationCOMImpl::_ClassFactoryCreatorClass::CreateInstance,
     &TAutomationCOMImpl::_CreatorClass::CreateInstance, NULL, 0,
     &TAutomationCOMImpl::GetObjectDescription},

    {&CLSID_DateTimePickerX, &TDateTimePickerXImpl::UpdateRegistry,
     &TDateTimePickerXImpl::_ClassFactoryCreatorClass::CreateInstance,
     &TDateTimePickerXImpl::_CreatorClass::CreateInstance, NULL, 0,
     &TDateTimePickerXImpl::GetObjectDescription},

    {&CLSID_ActiveFormX, &TActiveFormXImpl::UpdateRegistry,
     &TActiveFormXImpl::_ClassFactoryCreatorClass::CreateInstance,
     &TActiveFormXImpl::_CreatorClass::CreateInstance, NULL, 0,
     &TActiveFormXImpl::GetObjectDescription},

    {NULL, NULL, NULL, NULL}
};

Each class implementation (e.g. TStandardCOMImpl) provides static methods for updating the registry, creating instances of the class factory, etc.  The global instance of TComModule (_Module) uses this ObjectMap array to implement the in-process COM server's exported functions, as described in the next section.

Exports

DllCanUnloadNow
TComModule contains a member (LONG m_nLockCnt) that is incremented and decremented by calling TComModule::Lock and TComModule::Unlock.  DllCanUnloadNow simply checks if this member is zero and, if it is, returns true, indicating that there are no locks on this module.  TComModule::Lock and TComModule::Unlock are called in two circumstances: 1) when a COM object is created or destroyed (including implementations of IClassFactrory), and 2) when IClassFactory::LockServer is called.

DllGetClassObject
This exported function calls TComModule::GetClassObject, providing the CLSID of the component, the IID used to communicate with the class factory (either IID_IUnknown, IID_IClassFactory, or IID_IClassFactory2), and the address of the pointer that will receive the class factory interface.  When TComModule::GetClassObject is called, the module loops through all entries in the object map array, looking for the entry that has the requested CLSID.  If and when it is found, the class factory is created (if it hasn't been created for this CLSID already) by calling the function pointed to by the pfnGetClassObject member of the _ATL_OBJMAP_ENTRY structure.  This method is provided by the CComCoClass template, which is a base class of the implementation class (e.g. TStandardCOMImpl).

DllRegisterServer and DllUnregisterServer
These methods call TComModule::RegisterServer and TComModule::UnregisterServer.  Both of these methods loop through all entries in the _ATL_OBJMAP_ENTRY array, calling the function pointed to by the pfnUpdateRegistry member.  This function is provided by each implementation class (e.g. TStandardCOMImpl).  The UpdateRegistry methods can be found in the header files of each COM implementation.  Following is the code for TStandardCOMImpl::UpdateRegistry:

// Data used when registering Object
//
DECLARE_THREADING_MODEL(otBoth);
DECLARE_PROGID("Server.StandardCOM");
DECLARE_DESCRIPTION("Standard COM Object, no automation");

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

TTypedComServerRegistrarT is one of many class templates where all the action takes place when registering and unregistering COM objects.  Other classes that perform similar functionality are TComServerRegisrarT, TRemoteDataModuleRegistrar, and TAxControlRegistrar.  Each of these classes derives from TRegistrarBaseT and can be found in atlmod.h.  The DECLARE_**** macros provide implementations for the GetObjectCLSID, GetProgID, and GetDescription methods in the component implementation class.

Type Library Source
The Server_TLB.h file contains declarations for all interfaces and GUIDs used in the COM server and required by client applications.  Wrapper classes are also provided for clients using C++ Builder that simplify the creation and use of components.  Server_TLB.cpp contains the GUID definitions that are referenced throughout the source files.  Remember that changes made to this file will be lost since these files are periodically updated from the type library.

Component Implementation Source
These units, such as TStandardCOMImpl, contain the actual implementation of each component.  Each implementation derives from several ATL and ATLVCL class templates depending on the type of component (e.g. COM object, Automation Object, ActiveX Control, etc.) and other various attributes of the component (e.g. Thread Model, Aggregatable, etc.).  Each component implementation has a COM interface map associated with it declared similar to the following:

BEGIN_COM_MAP(TStandardCOMImpl)
  COM_INTERFACE_ENTRY(IStandardCOM)
END_COM_MAP()

This map declares methods that manage an array of the interfaces implemented by the component for use with QueryInterface calls.  These macros are defined in atlcom.h and expand to the following:

public:
    typedef TStandardCOMImpl _ComMapClass;
    static HRESULT WINAPI _Cache(void* pv, REFIID iid,
                                 void** ppvObject,
                                 DWORD dw) {
        _ComMapClass* p = (_ComMapClass*)pv;
        p->Lock();
        HRESULT hRes = CComObjectRootBase::_Cache(pv, iid,
                                                  ppvObject,
                                                  dw);
        p->Unlock();
        return hRes;
    }
    IUnknown* GetUnknown() {
        _ASSERTE(_GetEntries()[0].pFunc == _ATL_SIMPLEMAPENTRY);
        return (IUnknown*)((int)this+_GetEntries()->dw);
    }
    HRESULT _InternalQueryInterface(REFIID iid, void** ppvObject) {
        return InternalQueryInterface(this, _GetEntries(),
                                      iid, ppvObject);
    }
    const static _ATL_INTMAP_ENTRY* WINAPI _GetEntries() {
        static const _ATL_INTMAP_ENTRY _entries[] = {
            DEBUG_QI_ENTRY(TStandardCOMImpl)

            {&IID_IStandardCOM,
             offsetofclass(IStandardCOM, _ComMapClass),
             _ATL_SIMPLEMAPENTRY},

            {NULL, 0, 0}
        };
        return _entries;
    }

The important thing to understand is that whenever QueryInterface is called on this component, _GetEntries is called which returns an array of all interfaces implemented by this component.  This array is used to determine if the interface requested via QueryInterface is implemented and, if so, a pointer to the interface is returned.

StandardCOM
The implementation class for this component has the following declaration:

class ATL_NO_VTABLE TStandardCOMImpl :
  public CComObjectRootEx<CComMultiThreadModel>,
  public CComCoClass<TStandardCOMImpl,
                     &CLSID_StandardCOM>,
  public IStandardCOM

CComObjectRootEx implements IUnknown using the CComSingleThreadModel if the Apartment Thread Model was chosen or CComMultiThreadModel if either the Free or Both Thread Models are chosen.  The thread model simply determines how the reference count for each component's interfaces are handled.  However, the component's implmentation must adhere to the threading model chosen and regsitered for the component.

CComCoClass implements the class factory and handles implementation details of aggregatable components.

AutomationCOM
The implementation class for this component has the following declaration:

class ATL_NO_VTABLE TAutomationCOMImpl :
  public CComObjectRootEx<CComMultiThreadModel>,
  public CComCoClass<TAutomationCOMImpl,
                     &CLSID_AutomationCOM>,
  public IDispatchImpl<IAutomationCOM,
                       &IID_IAutomationCOM,
                       &LIBID_Server>

Instead of deriving directly from IAutomationCOM, this class derives from a class template which implements IDispatch.

DateTimePickerX
The implementation class for this component has the following declaration:

class ATL_NO_VTABLE TDateTimePickerXImpl:
  VCLCONTROL_IMPL(TDateTimePickerXImpl,
                  DateTimePickerX,
                  TDateTimePicker,
                  IDateTimePickerX,
                  DIID_IDateTimePickerXEvents)

The VCLCONTROL_IMPL macro expands to the following:

public TVclControlImpl<TDateTimePickerXImpl,
                       TDateTimePicker,
                       &CLSID_DateTimePickerX,
                       &IID_IDateTimePickerX,
                       &DIID_IDateTimePickerXEvents,
                       LIBID_OF_DateTimePickerX>,
public IDispatchImpl<IDateTimePickerX,
                     &IID_IDateTimePickerX,
                     LIBID_OF_DateTimePickerX>,
public TEvents_DateTimePickerX<TDateTimePickerXImpl>

TVclControlImpl implements all interfaces required by ActiveX controls (e.g. IUnknown, IPersistStorage, IOleInPlaceActivateObject, ISimpleFrameSite, etc.) as well as the class factory for the component.

ActiveFormX
This component's implementation unit contains both the ActiveForm derived VCL form class (TActiveFormX) and the ActiveX control object that wraps the form into a COM interface (TActiveFormXImpl).  All other details are similar to DateTimePickerX.

Out-of-process Servers
Out-of-process servers are primarily used when there is a pre-existing EXE application and the developer wants to allow third party client applications to use the services provided by the EXE.  Examples of this are Word and Excel.  When these applications are launched, they register their components with the OLE function CoRegisterClassObject.  This allows other applications to connect to its COM interfaces.  Unlike in-process servers, applications cannot export a DllGetClassObject function.  If a client calls CoCreateInstance requesting a component implemented by an out-of-process server, the application is automatically started and the interface is returned.  Also, if the local server was launched via CoCreateInstance, and the client app releases all references to its components, the local server application automatically shuts down.

Out-of-process servers can be multiple use servers or single use servers.  With single use servers, a different instance of the server application is started for every client application requesting its services.  While multiple use servers allow multiple connections to its components.  The Project Options | ATL tab allows changing this setting.

Local server registration is accomplished by passing the /REGISTER or /UNREGISTER command-line arguments when executing the application.  If either of these arguments is supplied, the application will quit as soon as the registration process is complete.

All of the functionality described above is implemented by the VCL's TATLModule.  All other details of component servers are the same for out-of-process and in-process servers.

To create an out-of-process server, open any application (or create a new one) and go to File | New, select the ActiveX tab and choose COM Object.  The type library editor appears and its source files (*_TLB) are added to the project along with the implementation unit.  These files are identical to those in an in-process server.  Out-of-process servers are initialized slightly differently than in-process servers.  When the global TComModule (_Module) is constructed in the application's main source file, a zero is passed to the constructor.  This argument can be a TProcedure component that is called during system initialization (during the call to TApplication::Initialize() at the beginning of WinMain).  The default system initialization procedure used when no procedure is provided to the constructor of TComModule is TATLModule::InitATLServer.  Inside this function is a call to _Module::Init with ObjectMap and Sysinit::HInstance provided as arguments.

Summary
This article, although it didn't go very in-depth into any one ActiveX topic, provided much of the background information revolving around the code used and generated by C++ Builder's ActiveX wizards.  At this point, the reader should have enough familiarity with the wizards to be aware of their attributes and limitations.  The reader should also be comfortable enough with the ATL and ATLVCL source files to learn more details about how ActiveX components are implemented.  The next article in this series discusses the various means of building applications that connect to COM servers.  Topics that will be covered include importing type libraries, the Interface Description Language (IDL), and the VCL's COM interface wrappers.


Server Response from: ETNASC03