[Top] [Contents] [Index] [ ? ]

COM/DCOM/COM+ with GNAT

1. Brief Introduction to COM, Distributed COM, ActiveX, and COM+  
2. What are COM Objects and Interfaces?  
3. Using COM objects with GNAT  
4. Creating Type Libraries for COM objects  
5. Creating COM objects with GNAT  
5.4 Using GNAT COM objects as Distributed COM objects  
6. Debugging COM objects with gdb  
Index  


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1. Brief Introduction to COM, Distributed COM, ActiveX, and COM+

In addition to creating distributed applications with Annex E, GNAT is capable of creating and using components that conform to Microsoft's Component Object Model (COM) and Distributed COM (DCOM) on Windows NT.

Microsoft introduced COM as the underlying model for the Object Linking and Embedding 2 (OLE2) services. COM provided OLE2 with the extensibility to meet the growing demand for interoperability of applications that users required and implementation flexibility developers required. It did this by providing a binary level, rather than a source level, object oriented specification for standard interfaces to executable objects allowing them to be used in a class wide fashion and implemented in the language best meeting the object's requirements. It also specified inter and remote process communication, to be provided by the operating system and optionally customized by the developer, and dynamic loading of the executable objects.

Microsoft has since advanced many new technologies built on top of COM including ADO, DirectX, Extended MAPI, and ActiveX (OCX) Controls. COM has remained stable and has only been slightly extended to include newer threading models, better security and more advance distribution. The fundamental principles have remained unchanged and it has proven itself as a powerful model for component based development.

The first use of COM to extend non-Microsoft technologies was a specification for OLE Automation Objects. Using this specification, developers could create custom components by creating COM objects that use a predefined set of interfaces and APIs. Developers could also create custom interfaces of their own and provide the interface specifications in a language neutral format called a Type Library. Client code could then be created in any language to make dynamic use of these binary components.

Microsoft then added a number of other specifications for creating custom components building on the OLE Automation Object specification such as the OLE Control (OCX) specification that creates a uniform way of implementing GUI components.

Technologies based on OLE Automation were later renamed for marketing purposes to ActiveX or ActiveX Controls. The ActiveX name was later expanded to include any COM object including those that do not conform to the OLE Automation specifications.

Distributed COM (DCOM) is the technology that allows COM objects to be transparently distributed across a network. Using settings in the registry of the client and server, when creating a COM object the operating system can create or use supplied proxies and stubs to access the COM object on a remote machine.

COM+ is Microsoft's latest extension to COM technology. The base services of COM+ are COM and DCOM and are identical with only the addition of another threading model. COM+ also designates the integration of Microsoft Transaction Server (MTS) and Microsoft Message Queueing (MSMQ).

In order to use COM or DCOM on Windows NT 4.0, service pack three or later must be installed. The additional services of COM+ (MTS and MSMQ) become available by adding the Microsoft Option Packs or upgrading to Windows 2000.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2. What are COM Objects and Interfaces?

COM objects are collections of public interfaces and private data. The internal representation of the COM object is irrelevant as clients of COM objects can only access interfaces. Win32 provides APIs for creating an instance of a COM object and requesting the first interface to the object. Clients are not required to specify the location of the COM objects as the operating system will resolve their location and take the needed steps to create the object, but the location or hints to the location may be specified if needed.

Every COM object must support at least one interface called IUnknown. Depending on the particular interfaces supported in addition to IUnknown, the object is referred to by different names in Microsoft documentation. For example if the object supports the interface IDispatch it is called an OLE Automation object, if it supports the various GUI related interfaces and persistence interfaces, it may be called an OLE, OCX, or ActiveX control.

All interfaces and COM objects are identified using a GUID, a globally unique identifier. These IDs are created by the Win32 API function CoCreateGuid, or assigned by Microsoft. The GUIDs referring to interfaces are called IIDs, interface identifiers, those referring to the COM object as CLSIDs, class IDs. If and object has registered a textual name, called a ProgID, for itself in the Windows registry, it is possible to retrieve the CLSID using the ProgID with the API function CLSIDFromProgID.

An interface is a record containing an access to a collection of function accesses. The collection is usually implemented as a record with access to subprogram objects, and private data. The first parameter of the function is always an access to the interface it belongs to and usually returns an HRESULT, an unsigned integer that returns success or failure information. An interface is said to derive from another interface if it contains a superset of another interface's collection of function accesses in the same order.

The most important interface is called IUnknown and all other interfaces derive from it. It contains three functions QueryInterface, AddRef, and Release. These three functions provide polymorphism and reference counting. The QueryInterface method is called on any interface to request the COM object to return an access to another supported interface. AddRef and Release provide reference counting for the interface. An implicit AddRef exists when an interface is returned from a function such as QueryInterface or through API calls. Release is called by the client when the interface is no longer in use reducing the interface's internal reference count. When the reference count reaches zero for all interfaces to the COM object, the COM object is required to deallocate itself from memory.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3. Using COM objects with GNAT

3.1 The Static Method  
3.2 The Dynamic Method  

COM objects can be used via two methods, the Static method, by calling methods of its interfaces, or the dynamic method if it supports IDispatch, as most do, by calling the Invoke method of the IDispatch interface using property and method IDs. The second method is the only one available to scripting languages (such as VBScript and JavaScript) and invocation speed is generally four to ten times slower then the first method. GNAT can access COM objects using both methods.

Complete examples of both methods are available with the GNAT examples. `COM_Static.adb' demonstrates the static method and `COM_Dynamic.adb' demonstrates the dynamic method.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.1 The Static Method

Step 1 - Create Binding

The first step in using COM objects from GNAT is to create an Ada binding to the interfaces supported by the COM Object and to the needed GUIDs for the object and the interfaces. The author of the object provides this information in source format, or as a Type Library. There are a number of tools that can read Type Libraries and provide the needed information including COMScope (see http://www.adapower.com/com) and Microsoft's OLE/COM object viewer distributed with the Win32 platform development kit.

An aliased CLSID should be created for the GUID of the object to be created and IIDs for each interface.

For example:

 
--  CLSID of COM object to create
--  {45F9F481-787C-11D3-821C-52544C1913DE}

CLSID_GNATCOMClass : aliased CLSID :=
  (16#45F9F481#, 16#787C#, 16#11D3#,
    (char'Val (16#82#), char'Val (16#1C#),
     char'Val (16#52#), char'Val (16#54#),
     char'Val (16#4C#), char'Val (16#19#),
     char'Val (16#13#), char'Val (16#DE#)));

--  IID of the IGNATMessage interface
--  {45F9F482-787C-11D3-821C-52544C1913DE}

IID_IGNATMessage : aliased IID :=
  (16#45F9F482#, 16#787C#, 16#11D3#,
    (char'Val (16#82#), char'Val (16#1C#),
     char'Val (16#52#), char'Val (16#54#),
     char'Val (16#4C#), char'Val (16#19#),
     char'Val (16#13#), char'Val (16#DE#)));

Then create access methods for the members of the interface you are planning to create and any inherited methods. Note that the first parameter of every function should be a pointer back to the interface itself. This provides the C++ style this pointer to the COM methods being called.

For example:

 
--  Inherited methods from IUnknown
type af_IGNATStat_QueryInterface is access
  function (This   : access IGNATStat;
            riid   : in     REFIID;
            ppvObj : access Win32.PVOID)
  return Win32.Winerror.HRESULT;
pragma Convention (StdCall, af_IGNATStat_QueryInterface);

type af_IGNATStat_AddRef is access
  function (This : access IGNATStat)
  return Win32.ULONG;
pragma Convention (StdCall, af_IGNATStat_AddRef);

type af_IGNATStat_Release is access
  function (This : access IGNATStat)
  return Win32.ULONG;
pragma Convention (StdCall, af_IGNATStat_Release);

--  Methods of IGNATMessage

type af_IGNATStat_Calls is access
  function (This          : access IGNATStat;
            NumberOfTimes : access Interfaces.C.int)
  return Win32.Winerror.HRESULT;
pragma Convention (StdCall, af_IGNATStat_Calls);
--  Return number of times methods of the IGNATMessage interface
--  were called

Then construct the interface and the table of functions used to access the methods.

For example:

 
type IGNATStat is record
      lpVtbl : Pointer_To_IGNATStatVtbl;
end record;
pragma Convention (C_Pass_By_Copy, IGNATStat);

type IGNATStatVtbl is record
      QueryInterface : af_IGNATStat_QueryInterface;
      AddRef         : af_IGNATStat_AddRef;
      Release        : af_IGNATStat_Release;
      Calls          : af_IGNATStat_Calls;
end record;
pragma Convention (C_Pass_By_Copy, IGNATStatVtbl);

Step 2 - Initialize COM

Initialize the COM libraries with a call to CoInitialize:

 
hr : Win32.Winerror.HRESULT;
hr := CoInitialize (System.Null_Address);

Step 3 - Create Object

Create the object using CoCreateInstance that will return and access to the requested interface in the COM object.

For example:

 
--  The requested interfaces is IID_IGNATMessage

hr := CoCreateInstance (CLSID_GNATCOMClass'Unchecked_Access,
                        null,
                        Win32.DWORD (CLSCTX_ALL),
                        IID_IGNATMessage'Unchecked_Access,
                        RetPointer'Unchecked_Access);

GnatMessage_Ref := To_Pointer_To_IGNATMessage (RetPointer);

Step 4 - QueryInterface

Use methods in the returned interface or request alternate interfaces using QueryInterface.

For example:

 
--  To call a method
hr := GnatMessage_Ref.lpvtbl.Beep (GnatMessage_Ref);

--  To access a different interfaces of the object
--  in this case IID_IGNATStat

hr := GnatMessage_Ref.lpvtbl.QueryInterface
       (GnatMessage_Ref,
        IID_IGNATStat'Unchecked_Access,
        RetPointer'Unchecked_Access);

GnatStat_Ref := To_Pointer_To_IGNATStat (RetPointer);

Step 5 - Release

When any interface is no longer needed a call to Release should be made.

For example:

 
refcount : Win32.ULONG;
refcount := GnatStat_Ref.lpvtbl.Release (GnatStat_Ref);

The reference count that is returned can be bogus and should not be relied upon for any purpose.

Step 6 - Unitialize COM

Shutdown the COM libraries with a call to CoUninitialize.

 
CoUninitialize;

The client code should be compiled with the linker option -lole32 and -loleaut32 to include the needed linker libraries. See the GNAT example `com_static.adb' for a complete example of using COM objects.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.2 The Dynamic Method

Step 1 - Initialize COM

Initialize the COM libraries with a call to CoInitialize:

 
hr : Win32.Winerror.HRESULT;
hr := CoInitialize (System.Null_Address);

Step 2 - Get CLSID from PROGID

Retrieve the CLSID of the object using the Program ID (this may also be done for the static method) and the API call CLSIDFromProgID.

For example:

 
Class_ID : aliased CLSID;
Hr := CLSIDFromProgID
  (To_LPCOLESTR (To_C
   (Wide_String'("GNATCOMLibrary.GNATCOMClass"))'Address),
   Class_ID'Unchecked_Access);

Step 3 - Create Object

Create the object and request the IDispatch interface.

For Example:

 
hr := CoCreateInstance (Class_ID'Unchecked_Access,
                        null,
                        Win32.DWORD (CLSCTX_ALL),
                        IDispatch_ID'Unchecked_Access,
                        RetPointer'Unchecked_Access);
Dispatch_Ref := To_Pointer_To_Idispatch (RetPointer);

Step 4 - Look up ID and Invoke

Look up the dispatch IDs for methods and properties using the GetIDsOfNames method of IDispatch followed by calls to its invoke method.

For example:

 
declare
   Method_Name  : aliased Win32.ObjBase.LPOLESTR :=
     To_LPOLESTR (To_C (Wide_String'("Beep"))'Address);
   ID           : aliased Win32.OleAuto.DISPID;

   No_Arguments : aliased Win32.OleAuto.DISPPARAMS :=
     (null, null, 0, 0);
   Result       : aliased Win32.OleAuto.Variant;
   Excep_Info   : aliased Win32.OleAuto.EXCEPINFO;
   Arg_Err      : aliased Win32.UINT;
begin
   Put_Line ("Look up ID of method to call");

   Hr := Dispatch_Ref.lpvtbl.GetIDsOfNames
           (Dispatch_Ref,
            To_LPIID(System.Null_Address),
            Method_Name'Unchecked_Access,
            1,
            0,
            ID'Unchecked_Access);

   Hr := Dispatch_Ref.lpvtbl.Invoke (Dispatch_Ref,
                                     ID,
                                     To_LPIID (System.Null_Address),
                                     0,
                                     DISPATCH_METHOD,
                                     No_Arguments'Unchecked_Access,
                                     Result'Unchecked_Access,
                                     Excep_Info'Unchecked_Access,
                                     Arg_Err'Unchecked_Access);
end;

Step 5 - Release

When the interface is no longer needed a call to Release should be made.

For example:

 
refcount : Win32.ULONG;
refcount := Dispatch_Ref.lpvtbl.Release (Dispatch_Ref);

The reference count that is returned can be bogus and should not be relied upon for any purpose.

Step 6 - Unitialize COM

Shutdown the COM libraries with a call to CoUninitialize.

 
CoUninitialize;

The client code should be compiled with the linker option -lole32 and -loleaut32 to include the needed linker libraries. See the GNAT example `com_dynamic.adb' for a complete example of using COM objects dynamically.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4. Creating Type Libraries for COM objects

The first step to building a COM object with GNAT is the construction of a type library. This can be done by compiling a specification created in Microsoft IDL (Interface Definition Language) and using Microsoft's MIDL compiler. The IDL specification must be constructed to contain a type library definition and it is highly recommended that it conform to the ole automation specifications. It is possible to create with GNAT COM objects that use any valid IDL that would work with other languages.

This documentation details an ideal IDL for easy implementation in Ada. See the GNAT example `gnatexample.idl' for the complete example.

Microsoft IDL is a superset of DCE RPC IDL which is very similar in syntax to C. IDL files for COM objects used with GNAT will generally take the form of interface definitions followed by a type library block that defines the composition of each COM object in the library.

IDL files should begin with the following two lines that import needed specifications:

 
import "unknwn.idl";
import "oaidl.idl";

IDL uses brackets to contain attributes and braces to define blocks. For example:

 
[
        object,
        uuid(45F9F483-787C-11d3-821C-52544C1913DE),
        helpstring("Statistics on object interface"),
        oleautomation
]
interface IGNATStat : IUnknown
{
        [
                helpstring("Return number of times methods were called")
        ]
        HRESULT Calls([out] int *NumberOfTimes);
}

Each interface block should have the attributes object, uuid, helpstring, and oleautomation. If this interface is to also support dynamic use through IDispatch for script languages, it should also be marked with the attribute dual. The Object attribute designates that this is a COM interface and not an RPC interface. The uuid attribute provides the univeral unique ID that this interface will be known as and must be unique for each uuid attribute used. This number can be generated using tools such as `uuidgen' or `guidgen' from the Microsoft SDK, or by a call to the Win32 API function CoCreateGuid. The helpstring attribute allows type library browsers and binding generators to display the help string that is set. OleAutomation tells the MIDL compiler to insure that the interface conforms to Ole Automation specifications insuring the type library marshaling can be used for the object (see the section on creating COM objects with GNAT).

Every interface must derive from either IUnknown, IDispatch or any other child of IUnknown in order to be used with COM objects. Objects that are derived from IDispatch should include the attribute dual to the interface. Interfaces are defined using the interface key word followed by the interface name then a colon and the parent interface. The methods of the interface are then defined in the interface block between brackets.

Each method of the interface may contain a helpstring attribute in the same manner as the interface. If the interface is derived from IDispatch, you may optionally add the attribute ID to set its dispatch ID, an ID used for dynamic invocation of the interface. If no ID is given, MIDL will assign one automatically. For example:

 
[
 id(1),
 helpstring("Audio Alert")
]
HRESULT Beep();

Every method must return a type called an HRESULT that returns the success or failure of the method. Parameters if each method need to include an attribute of in or out. Out parameters must be pointer types. Allowable types are boolean, unsigned char, double, float, int, long, short, BSTR, CURRENCY, DATE, SCODE, enum, IDispatch*, IUnknown*, SAFEARRAY of any OLE automation type, and pointers to any of the above types. For example:

 
[
 id(2),
 helpstring("Display Message Box")
]
HRESULT MessageBox([in] BSTR Message);

Following all interfaces definitions, a library block should be created with the attributed uuid, helpstring, and version. With in the library block the first statement should be the following:

 
importlib("stdole32.tlb");

This imports the standard system library of OLE automation types. Following a line is added for each interface to import it in to the library. For example:

 
interface IGNATMessage;
interface IGNATStat;

Once the interfaces are now part of the library COM objects are defined that use the available interfaces using coclass blocks. Each block should have the attributes uuid and helpstring. With in the block each interface is listed and one interface should be chosen as the default interface. For example:

 
[
       uuid (45F9F481-787C-11d3-821C-52544C1913DE),
       helpstring("GNAT Example Class")
]
coclass GNATCOMClass
{
       [default] interface IGNATMessage;
       interface IGNATStat;
}

After compiling the IDL file using MIDL, only the .tlb file will be used in creating the COM object with GNAT.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5. Creating COM objects with GNAT

5.1 The COM object implementation  
5.2 InProc container implementation  
5.3 LocalServer container implementation  

COM objects are contained in DLLs, known as InProc Servers, or in EXEs, known as Local Servers. Containers may house more then one object and use clearly defined methods of exposing the objects to the operating system and publishing their existence in the system registry. The complete example is found in the `COMObject' directory.

The implementation of the COM objects are identical regardless of the container type, but the methods for publishing their existence and exposing of the factory object that creates the COM objects are different.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.1 The COM object implementation

5.1.1 Step 1 - The Resource File  
5.1.2 Step 2 - Base package  
5.1.3 Step 3 - Object Implementation  
5.1.4 Step 4 - Class Factory Implementation  


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.1.1 Step 1 - The Resource File

The type library must be embedded in to every COM object in order to let the operating system know how to handle proxying and marshaling of the object when needed. It also provides information to binding generators, visual tools, and scripting languages about the object. This is done by creating and compiling with the GNU `windres' or Microsoft's `rc' followed by `res2coff' a resource script that contains the line:

 
1 TypeLib "gnatexample.tlb"

Where `gnatexample.tlb' would be the name of your type library file. It is also advisable to include a version info resource with a StringFileInfo block containing the value pair OLESelfRegister and a blank to indicate that the COM object container will handle registration of the COM object and type library when requested to do so from the command line for Local Servers or using the utility `regsvr32.exe' for InProc Servers. For example:

 
VS_VERSION_INFO VERSIONINFO
 FILEVERSION 1,0,0,1
 PRODUCTVERSION 1,0,0,1
 FILEFLAGSMASK 0x3fL
 FILEFLAGS 0x0L
 FILEOS 0x40004L
 FILETYPE 0x2L
 FILESUBTYPE 0x0L
BEGIN
    BLOCK "StringFileInfo"
    BEGIN
       BLOCK "040904b0"
       BEGIN
          VALUE "CompanyName", "Your Company Here\0"
          VALUE "FileDescription", "gnatexample\0"
          VALUE "FileVersion", "1, 0, 0, 1\0"
          VALUE "InternalName", "gnatexample\0"
          VALUE "LegalCopyright", "Copyright©\0"
          VALUE "OriginalFilename", "gnatexample\0"
          VALUE "ProductName", "gnatexample\0"
          VALUE "ProductVersion", "1, 0, 0, 1\0"
          VALUE "OLESelfRegister", ""
       END
    END
    BLOCK "VarFileInfo"
    BEGIN
      VALUE "Translation", 0x409, 1200
    END


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.1.2 Step 2 - Base package

The top level package for implementing the COM object will contain global reference counters, GUID definitions, and types.

There are two main global reference counters needed for COM objects a component count and a server lock count. The component count is used to identify when to shutdown the server and the server lock provides a means to lock the server in memory to enhance performance when objects will be created and deleted frequently.

There are three types of GUID definitions needed, library ID, class IDs, and interface IDs. These are the GUIDs defined in the IDL for the library, coclass'es, and interface's. For example:

 
--  LIBID of library GNATCOMLibrary
--  {45F9F480-787C-11D3-821C-52544C1913DE}

LIBID_GNATCOMLibrary : aliased Win32.Objbase.IID :=
  (16#45F9F480#, 16#787C#, 16#11D3#,
    (char'Val (16#82#), char'Val (16#1C#),
     char'Val (16#52#), char'Val (16#54#),
     char'Val (16#4C#), char'Val (16#19#),
     char'Val (16#13#), char'Val (16#DE#)));

The types needed for implementation are access functions for every method used by the interfaces of the COM object including those used in the interfaces' parents. Along with an interface type that will be the will be a an access parameter in the first position of every method. The interface type will contain the address of the instance data for the object and a reference count for the interface. This reference count allows for instances of the interface to automatically be deallocated from memory when the interface count reaches zero. For example:

 
--  Access function types for IGNATStat

type af_IGNATStat_Calls is access
  function (This          : access Interface;
            NumberOfTimes : access Interfaces.C.int)
  return HRESULT;
pragma Convention (StdCall, af_IGNATStat_Calls);

type Interface is record
   Vtbl      : System.Address;
   Ref_Count : aliased Win32.LONG := 1;
   CoClass   : System.Address;
end record;
pragma Convention (C, Interface);

Additionally a boolean variable is added to the package indicating if the object is in an InProc Server or a Local Server, and a procedure called Can_Close that is called every time an object is released, so that in the event the object is contained in a Local Server, if all objects are released and there are no server locks, the server can close itself down. The implementation of Can_Close is:

 
procedure Can_Close is
   bResult : Win32.BOOL;
begin
   if Server_Lock_Count = 0
     and then Component_Count = 0
     and then InProcServer /= True
   then
      bResult := Win32.Winuser.PostThreadMessage
        (Win32.Winbase.GetCurrentThreadId,
         Win32.Winuser.WM_QUIT,
         0,
         0);
   end if;
end Can_Close;


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.1.3 Step 3 - Object Implementation

The process of creating the actual COM object begins with creating implementations of each method of every interface the object will be using. Even though a COM object may support multiple interfaces and it is not necessary, although certainly possible if needed, to repeat the implementations of methods shared by each interface. For example it is not necessary to define multiple instances of the AddRef method. Interfaces are views of the object, and not objects themselves.

Each method must conform to the same signature as the access functions they will be assigned to as they have been defined in the main package. All objects will include implementations for IUnknown and those that are dual interfaces, will also support IDispatch.

Records need to be created for each interface that will contain a table of addresses of each method implementation. For example:

 
type IGNATStat_Vtbl_Record is
   record
      --  IUnknown
      QueryInterface   : Af_IUnknown_QueryInterface
        := IUnknown_QueryInterface'Access;
      AddRef           : Af_IUnknown_AddRef
        := IUnknown_AddRef'Access;
      Release          : Af_IUnknown_Release
        := IUnknown_Release'Access;

      --  IGNATMessage
      Calls             : Af_IGNATStat_Calls
        := IGNATStat_Calls'Access;
   end record;
pragma Convention (C, IGNATStat_Vtbl_Record);
type IGNATStat_Vtbl_Pointer is access all IGNATMessage_Vtbl_Record;

An instance of each record must be present in memory so that the interface can be passed to clients of the COM object. This is done by creating an aliased object of the record type.

 
IGNATStat_Vtbl : aliased IGNATStat_Vtbl_Record;

A record representing the instance data for the object is also created and a function to create a new COM object with that data is also needed. This record also contains a reference count for the COM object. Every time any interface reference is adjusted the object's reference count is also adjusted. When there are no interfaces in use the reference count will reach none and the instance data for the COM object can then be freed.

 
--  Internal data for the COM object. Notice that there is
--  no Convention pragma on this type. This type is an Ada type.

type GNATCOMClass_Object is record
   Ref_Count : aliased Win32.LONG := 1;
   Data      : Controlled_Information;
   Count     : Integer := 0;
end record;
type GNATCOMClass_Pointer is access all GNATCOMClass_Object;

function New_Object return Interface_Pointer;

The function that creates the COM object returns an access to the objects IUnknown interface to be used bye the objects class factory. Typically the function will be like this one:

 
function New_Object return Interface_Pointer is
   New_Interface : aliased Interface_Pointer;
   New_Object    : aliased GNATCOMClass_Pointer;
begin
   New_Interface := new Interface;
   New_Object    := new GNATCOMClass_Object;
   New_Interface.CoClass := New_Object.all'Address;
   New_Interface.Vtbl := IGNATMessage_Vtbl'Address;
   return New_Interface;
end New_Object;

COM objects that will support dual interfaces will most likely want to take advantage of the Win32 APIs that implement IDispatch using the objects type library. In order for these APIs to function, they need access to the type library. A sound way of implementing this is using a controlled object that loads the type library when the object is created and frees it when the object is destroyed. For example:

 
--  Controlled type for handling information that should be
--  processed when the object is created or destroyed.

type Controlled_Information is
  new Ada.Finalization.Controlled with record
      Type_Information : aliased Win32.OleAuto.LPTYPEINFO := null;
  end record;

procedure Initialize (This : in out Controlled_Information);
procedure Finalize (This : in out Controlled_Information);
procedure Adjust (This : in out Controlled_Information);

The implementation of this example uses the Win32 API LoadRegTypeLib to load the type library interface and then uses Adjust and Finalize to handle the reference counting issues related to the interface. Implementation of the IDispatch methods then just call the appropriate Win32 APIs passing the type information interface contained in the controlled object.

Every object will need to implement the IUnknown functions. The Addref and Release methods will be the same for each object.

 
---------------------
-- IUnknown_AddRef --
---------------------

function IUnknown_AddRef (This : access Interface)
  return Win32.ULONG
is
   lResult : Win32.Long;
   Object  : GNATCOMClass_Pointer := To_Object_Pointer (This.CoClass);
begin
   --  InterlockedIncrement is a thread protected Win32 API function
   --  to increment a long

   --  Interface reference increment
   lResult :=
     Win32.Winbase.InterlockedIncrement (This.Ref_Count'Access);

   --  Object reference increment
   lResult :=
     Win32.Winbase.InterlockedIncrement (Object.Ref_Count'Access);

   return Win32.ULONG (This.Ref_Count);

end IUnknown_AddRef;

----------------------
-- IUnknown_Release --
----------------------

function IUnknown_Release (This : access Interface)
  return Win32.ULONG
is
   use type Interfaces.C.Long;

   lResult : Win32.Long;
   Object  : GNATCOMClass_Pointer := To_Object_Pointer (This.CoClass);
begin
   --  InterlockedDecrement is a thread protected Win32 API function
   --  to decrement a long
   lResult :=
     Win32.Winbase.InterlockedDecrement (Object.Ref_Count'Access);

   if 
     Win32.Winbase.InterlockedDecrement (This.Ref_Count'Access) /= 0
   then
      return Win32.ULONG (This.Ref_Count);

   else
      --  Last reference to Interface so free it
      Free (This.all'Address);
      return 0;
   end if;

end IUnknown_Release;

The free procedure deallocates the interface instance from memory since the reference counter has reached zero. With in the free procedure the reference count of the object is also decremented. If it reaches zero, the the instance data is also freed destroying the COM object as it is no longer in use.

The implementation of QueryInterface is similar from one object to the next, but needs to be customized to support the interfaces exposed by the COM object. In the example below, an elsif for each supported interface needs to be added after the support for IUnknown. The same pattern is followed, but the assignment of the table of access to functions is changed to the appropriate one for the requested interface.

 
function IUnknown_QueryInterface
  (This      : access Interface;
   riid      : in     Win32.Objbase.REFIID;
   ppvObject : access Win32.PVOID)
  return HRESULT
is
   use type Win32.Rpcdce.Guid;

   New_Interface : aliased Interface_Pointer;
   lResult       : Win32.LONG;
   Result        : Win32.ULONG;
   Object        : GNATCOMClass_Pointer :=
                        To_Object_Pointer (This.CoClass);
begin
   if riid.all = IID_IUnknown then
      -- Since IUnknown is the parent of every interface, just
      -- return back a pointer to this interface with an additional
      -- reference count and the client will use it as if it was
      -- IUnknown
      ppvObject.all := This.all'Address;
      Result := IUnknown_AddRef (This);

   elsif Riid.all = IID_IDispatch then
      New_Interface := new Interface;
      New_Interface.CoClass := This.CoClass;
      lResult := 
         Win32.Winbase.InterlockedIncrement (Object.Ref_Count'Access);
      New_Interface.Vtbl := IGNATMessage_Vtbl'Address;
      ppvObject.all := New_Interface.all'Address;

   elsif Riid.all = IID_IGNATMessage then
      New_Interface := new Interface;
      New_Interface.CoClass := This.CoClass;
      lResult := 
         Win32.Winbase.InterlockedIncrement (Object.Ref_Count'Access);
      New_Interface.Vtbl := IGNATMessage_Vtbl'Address;
      ppvObject.all := New_Interface.all'Address;

   elsif Riid.all = IID_IGNATStat then
      New_Interface := new Interface;
      New_Interface.CoClass := This.CoClass;
      lResult := 
         Win32.Winbase.InterlockedIncrement (Object.Ref_Count'Access);
      New_Interface.Vtbl := IGNATStat_Vtbl'Address;
      ppvObject.all := New_Interface.all'Address;

   else
      ppvObject.all := System.Null_Address;
      return Win32.Winerror.E_NOINTERFACE;
   end if;

   return Win32.Winerror.S_OK;
end Iunknown_QueryInterface;

Once IUnknown methods have been implemented the only additional work is to implement each interface method. In order to have access to the object instance data, a conversion function is used on the CoClass member of the interface type like this one:

 
function To_Object_Pointer is
   new Ada.Unchecked_Conversion (System.Address,
                                 GNATCOMClass_Pointer);

A simple method implementation may look something like this:

 
function IGNATStat_Calls
  (This          : access Interface;
   NumberOfTimes : access Interfaces.C.int)
  return HRESULT
is
   Object : GNATCOMClass_Pointer := To_Object_Pointer (This.CoClass);
begin
   Message_Box("Calls", Integer'Image (Object.Count));
   NumberOfTimes.all := Interfaces.C.int (Object.Count);

   return Win32.Winerror.S_OK;
end IGNATStat_Calls;


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.1.4 Step 4 - Class Factory Implementation

The implementation of the class factory for any COM object will be identical to the implementation found in the example files `gnatexample-factory.ad?'. The only difference will be in the function IClassFactory_CreateInstance where the function used to create the COM object will be the one created in Step 3.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.2 InProc container implementation

Step 1 - The export file

Every InProc container (DLL housing a COM object), needs to export four functions in its export (.def) file.

The def file must start exactly like this, but may contain additional exports following these four:

 
EXPORTS
   DllGetClassObject=DllGetClassObject@12     @2
   DllCanUnloadNow=DllCanUnloadNow@0          @3
   DllRegisterServer=DllRegisterServer@0      @4
   DllUnregisterServer=DllUnregisterServer@0  @5

Step 2 - The dll implementation

In addition to the four exports mentioned every DLL must contain an exported DllMain procedure. The implementation for the DllMain will be the same for all objects, unless additional functionality is needed on the containers startup or shutdown:

 
function DllMain (hinstDLL    : Win32.Windef.HINSTANCE;
                  fdwReason   : Win32.DWORD;
                  lpvReserved : Win32.LPVOID)
  return Win32.BOOL
is
   procedure Adainit;
   pragma Import (C, Adainit);

   procedure Adafinal;
   pragma Import (C, Adafinal);

   DLL_PROCESS_DETACH : constant := 0;
   DLL_PROCESS_ATTACH : constant := 1;
begin
   case fdwReason is
      when DLL_PROCESS_ATTACH =>
         AdaInit;
         hInstance := hinstDll;
         return 1;

      when DLL_PROCESS_DETACH =>
         AdaFinal;
         return 1;

      when others =>
         return 1;
   end case;
end DllMain;

The Instance handle assigned to the Dll is save in a variable hInstance for use by the exported COM Dll functions.

The DllGetClassObject is used by COM to request from the Dll a pointer to the factory object for a particular type of COM object. The implementation needs to check that it does in fact support creation of those objects and then needs to return a pointer to the class factory that will produces objects of the requested type. See the implementation in the `gnatexample-dll.adb' file for an example.

The DllCanUnloadNow function is called periodically by the operating system to determine if the Dll can be unloaded or needs to remain in memory. The Dll needs to remain in memory and returns S_FALSE if there are any server locks or if there are any created objects still being used. For example:

 
function DllCanUnloadNow return HRESULT is
   use type Interfaces.C.Long;
begin
   if Server_Lock_Count = 0 and then Component_Count = 0 then
      return Win32.Winerror.S_OK;
   else
      return Win32.Winerror.S_FALSE;
   end if;
end DllCanUnloadNow;

The functions DllRegisterServer and DllUnregisterServer are called by the application regsvr32.exe (or an install program) to request that the Dll add or remove all the relevant COM entries of the registry for its supported objects and type libraries.

The first step in DllRegisterServer is to register the type libraries. This is done by first obtaining the full path file name of the Dll. Using this path, the Win32 APIs LoadTypeLib and RegisterTypeLib are used to load and register the type library. Then the following entries are registered to the NT registry under HKEY_CLASSES_ROOT (using a simple helper procedure in the example file):

 
Register ("CLSID\" & CLSID, "", Library_Description);
--  CLSID = CLASS ID of the COM Object (CoClass GUID)
--  Library_Description = Description of object to appear in COM
--  browsers

Register ("CLSID\" & CLSID, "AppID", CLSID);
--  This entry links together the COM information (under \CLSID)
--  with the remoting information for DCOM (under \APPID)

Register ("CLSID\" & CLSID & "\InProcServer32",
          "",
          C.To_Ada (DllPath));
--  Path to InProcServer 32 bit implementation of the COM object
--  (a DLL)

Register ("CLSID\" & CLSID & "\InProcServer32",
          "ThreadingModel",
          "Apartment");
--  Tells the operating system that this object is aware of
--  threading issues, but the OS should synchronize access to
--  the object.
--  This does not affect the type of clients that can use this
--  object,ie. a client usingCoInitializeEx(CLSCTX_MULTI_THREADED)
--  can create this object and the OS will handle synchronization
--  of calls made by each thread trying to access the object.

Register ("CLSID\" & CLSID & "\ProgID",
          "",
          Object_Name & "." & Library_Version);
--  Links the CLSID to the PROGID
--  The ProgID is the human readable name of the COM object used
--  by VB and other languages instead of the GUID.
--  The PROGID also contains a dot followed by the version number.

Register ("CLSID\" & CLSID & "\VersionIndependentProgID",
          "",
          Object_Name);
--  Links the CLSID to the PROGID with out the version number
      
Register (Object_Name, "", Library_Description);
--  Creates the version independant PROGID entry
      
Register (Object_Name & "\CLSID", "", CLSID);
--  Links the PROGID to the CLSID of the object
      
Register (Object_Name & "\CurVer",
          "",
          Object_Name & "." & Library_Version);
--  Links the version independant PROGID to the version
--  dependant PROGID
      
Register (Object_Name & "." & Library_Version,
          "",
          Object_Name);
--  Creates the PROGID entry
      
Register (Object_Name & "." & Library_Version & "\CLSID",
          "",
          CLSID);
--  Links the PROGID to the CLSID
      
Register ("AppID\" & CLSID, "", Library_Description);
--  Creates the AppID entry where remoting infromation about the
--  COM object is stored.
      
Register ("AppID\" & CLSID, "DllSurrogate", "");
--  Tells the opearting system that if a request is made to run
--  the COM object as a DCOM object or as a separate process
--  (LocalServer) to provide this for the dll using a built in NT
--  application DLLHOST.EXE

The DllUnregisterServer function first removes the registered type library using the Win32 API function UnregisterTypeLib, it then removes all the entries in the NT registery created by DllRegisterServer.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.3 LocalServer container implementation

LocalServers (Exe's containing COM objects) must handle three command line parameters, /Embedding, /RegServer, and /UnregServer.

The LocalServer is called with /Embedding when the operating system needs to create COM objects that are contained in the server. It may also be used form the command line to pre-load the container in memory so that future requests of clients to create COM objects will be quicker. When embedding, the Win32 API CoInitialize is called to load the COM environment. Then a class factory for each COM object type that can be created is registered with the operating system using the CoRegisterClassObject API call. In order for the server to wait for the COM objects to be used and to later shutdown it starts up a Win32 message loop. Upon receiving notice that the server is no longer needed (all the COM objects it created were destroyed and there are no server locks), the messag loop exits and the API function CoUnregisterClassObject is called for each factory that was registered. The application then completes after calling the CoUnitialize API function to shutdown the COM environment.

When started with /RegServer or /UnregServer then same process used in the InProc implementation is used to register or unregister the type libraries and NT registry entries.

See the `gnatexample-exe.adb' file for a complete example.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

5.4 Using GNAT COM objects as Distributed COM objects

Using COM objects in a distributed fashion is more of a configuration issue then a programming issue. For both the client code and the server code there are no changes that need to be done. Remote machines need to have their registry updated to include the various COM entries and the type library must be registered. A simple application that does this is in `remote.adb'.

It registers the type library and inserts the same registry entries as the COM containers, but does not insert a server type and uses the key/value pair RemoteServerName/ServerName to specify the IP address or name of the remote server. It is possible to override the specified server name by using CoCreateInstanceEx instead of CoCreateInstance in the client code.

When using DCOM, you must also configure permissions to remote machines to access the COM objects. This is done using the Windows NT utility dcomcnfg.exe.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6. Debugging COM objects with gdb

Depending on the type of COM object and the client type there are different techniques used to debug a COM object.

The easiest form of COM object to debug is the Local Server (EXE) type. The COM object is compiled with the -g option to gnatmake and run in gdb as any other application. Break points can then be set and when the client compiled with gdb compatible debugging information or not is run the break points will be hit in the local server. It is also possible to use the gdb command attach to connect to an already running local server. The program ID that is used with the attach command can be located using the task manager.

Since Inproc Server COM objects are DLLs, they run in the same address space as the executable using them. If the client of the COM object is compiled with gdb debug information it is possible to debug normally and step through the client directly in to the COM object. Break points though can not be set in the COM object until after the object is created in the client code.

If the client does not contain gdb debug information (such as when the client is a Microsoft Visual C++ or Visual Basic client), an alternate techniques must used. The executable of the client can either be run with in gdb or attached to using the gdb attach command. Since it is not possible to set a break point in the client code it may not be possible to easily determine when the COM object has been created in order to stop execution of the client application and to set break points. One technique to assist in determining this point is to place a MessageBox or similar blocking function in the objects creation code. Once the message box displays a ctrl-c sequence can be sent to gdb to pause execution of the application and break points can then be set in to COM object. A continue statement can then be sent to gdb and the dialog box's OK button can be pushed to continue program execution and debugging.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

Index

Jump to:   A   C   D   G   H   I   L   M   O   P   Q   R   S   T   U   W  

Index Entry Section

A
ActiveX1. Brief Introduction to COM, Distributed COM, ActiveX, and COM+
AddRef2. What are COM Objects and Interfaces?
AddRef5.1.3 Step 3 - Object Implementation
ADO1. Brief Introduction to COM, Distributed COM, ActiveX, and COM+

C
CLSID2. What are COM Objects and Interfaces?
CLSID3.1 The Static Method
CLSID3.2 The Dynamic Method
CLSIDFromProgID3.2 The Dynamic Method
CoClass5.1.3 Step 3 - Object Implementation
CoCreateGuid2. What are COM Objects and Interfaces?
CoCreateInstance3.1 The Static Method
CoCreateInstanceEx5.4 Using GNAT COM objects as Distributed COM objects
CoInitialize3.1 The Static Method
CoInitialize3.2 The Dynamic Method
CoInitialize5.3 LocalServer container implementation
COM1. Brief Introduction to COM, Distributed COM, ActiveX, and COM+
COM+1. Brief Introduction to COM, Distributed COM, ActiveX, and COM+
COM, creating5. Creating COM objects with GNAT
COM, InProc servers5.2 InProc container implementation
COM, Local servers5.3 LocalServer container implementation
COM, resource file5.1.1 Step 1 - The Resource File
COM, Resource File, example5.1.1 Step 1 - The Resource File
COM, Virtual table3.1 The Static Method
COM, Vtbl3.1 The Static Method
CoRegisterClassObject5.3 LocalServer container implementation
CoUninitialize3.1 The Static Method
CoUninitialize3.2 The Dynamic Method
CoUninitialize5.3 LocalServer container implementation
CoUnregisterClassObject5.3 LocalServer container implementation

D
DCOM1. Brief Introduction to COM, Distributed COM, ActiveX, and COM+
DCOM1. Brief Introduction to COM, Distributed COM, ActiveX, and COM+
DCOM5.4 Using GNAT COM objects as Distributed COM objects
Debugging6. Debugging COM objects with gdb
DirectX1. Brief Introduction to COM, Distributed COM, ActiveX, and COM+
DllCanUnloadNow5.2 InProc container implementation
DllGetClassObject5.2 InProc container implementation
DllMain5.2 InProc container implementation
DllRegisterServer5.2 InProc container implementation
DllRegisterServer5.2 InProc container implementation
DllUnregisterServer5.2 InProc container implementation
DllUnregisterServer5.2 InProc container implementation
DllUnregisterServer5.2 InProc container implementation
DllUnregisterTypeLib5.2 InProc container implementation

G
gdb6. Debugging COM objects with gdb
GetIDsOfNames3.2 The Dynamic Method
GUID2. What are COM Objects and Interfaces?
GUID3.1 The Static Method
GUID5.1.2 Step 2 - Base package

H
HKEY_CLASSES_ROOT5.2 InProc container implementation

I
IDispatch2. What are COM Objects and Interfaces?
IDispatch3.2 The Dynamic Method
IDL4. Creating Type Libraries for COM objects
IDL, base types4. Creating Type Libraries for COM objects
IDL, BSTR4. Creating Type Libraries for COM objects
IDL, CURRENCY4. Creating Type Libraries for COM objects
IDL, enum4. Creating Type Libraries for COM objects
IDL, IDispatch4. Creating Type Libraries for COM objects
IDL, IUnknown4. Creating Type Libraries for COM objects
IDL, SAFEARRAY4. Creating Type Libraries for COM objects
IDL, SCODE4. Creating Type Libraries for COM objects
IID2. What are COM Objects and Interfaces?
IID3.1 The Static Method
IID5.1.2 Step 2 - Base package
InProc Servers5. Creating COM objects with GNAT
Interfaces2. What are COM Objects and Interfaces?
IUnknown2. What are COM Objects and Interfaces?
IUnknown, AddRef5.1.3 Step 3 - Object Implementation
IUnknown, QueryInterface5.1.3 Step 3 - Object Implementation
IUnknown, Release5.1.3 Step 3 - Object Implementation
IUnknwon2. What are COM Objects and Interfaces?

L
LoadRegTypeLib5.1.3 Step 3 - Object Implementation
LoadTypeLib5.2 InProc container implementation
Local Servers5. Creating COM objects with GNAT
Local servers, /Embedding5.3 LocalServer container implementation
Local servers, /RegServer5.3 LocalServer container implementation
Local servers, /UnregServer5.3 LocalServer container implementation

M
MAPI1. Brief Introduction to COM, Distributed COM, ActiveX, and COM+
MIDL compiler4. Creating Type Libraries for COM objects
MSMQ1. Brief Introduction to COM, Distributed COM, ActiveX, and COM+
MTS1. Brief Introduction to COM, Distributed COM, ActiveX, and COM+

O
Objects2. What are COM Objects and Interfaces?
OCX1. Brief Introduction to COM, Distributed COM, ActiveX, and COM+
OLE Automation1. Brief Introduction to COM, Distributed COM, ActiveX, and COM+
OLE21. Brief Introduction to COM, Distributed COM, ActiveX, and COM+
OLESelfRegister5.1.1 Step 1 - The Resource File

P
ProgID2. What are COM Objects and Interfaces?

Q
QueryInterface2. What are COM Objects and Interfaces?
QueryInterface3.1 The Static Method

R
rc5.1.1 Step 1 - The Resource File
Reference Count2. What are COM Objects and Interfaces?
RegisterTypeLib5.2 InProc container implementation
regsvr32.exe5.1.1 Step 1 - The Resource File
Release2. What are COM Objects and Interfaces?
Release3.1 The Static Method
Release3.2 The Dynamic Method
res2coff5.1.1 Step 1 - The Resource File

S
StringFileInfo5.1.1 Step 1 - The Resource File

T
Type Library1. Brief Introduction to COM, Distributed COM, ActiveX, and COM+
Type Library3.1 The Static Method
Type Library4. Creating Type Libraries for COM objects

U
Using COM objects3. Using COM objects with GNAT
Using COM objects, The Dynamic Method3.2 The Dynamic Method
Using COM objects, The Static Method3.1 The Static Method

W
windres5.1.1 Step 1 - The Resource File

Jump to:   A   C   D   G   H   I   L   M   O   P   Q   R   S   T   U   W  


[Top] [Contents] [Index] [ ? ]

Table of Contents


[Top] [Contents] [Index] [ ? ]

Short Table of Contents

1. Brief Introduction to COM, Distributed COM, ActiveX, and COM+
2. What are COM Objects and Interfaces?
3. Using COM objects with GNAT
4. Creating Type Libraries for COM objects
5. Creating COM objects with GNAT
6. Debugging COM objects with gdb
Index

[Top] [Contents] [Index] [ ? ]

About this document

This document was generated by Vasiliy Fofanov on February, 18 2003 using texi2html

The buttons in the navigation panels have the following meaning:

Button Name Go to From 1.2.3 go to
[ < ] Back previous section in reading order 1.2.2
[ > ] Forward next section in reading order 1.2.4
[ << ] FastBack previous or up-and-previous section 1.1
[ Up ] Up up section 1.2
[ >> ] FastForward next or up-and-next section 1.3
[Top] Top cover (top) of document  
[Contents] Contents table of contents  
[Index] Index concept index  
[ ? ] About this page  

where the Example assumes that the current position is at Subsubsection One-Two-Three of a document of the following structure:

This document was generated by Vasiliy Fofanov on February, 18 2003 using texi2html