Tips & Tricks
By Binh Ly

http://www.techvanguards.com

 

The following is a list of tips and tricks that might be useful to COM developers of all levels. If you have an interesting tip that's not listed here, please submit it to me and I'll add it to the list if I think it's useful.

 

 

1. Prefer early binding over late binding 

Late binding is a facility that enables script-based client applications to access and manipulate COM objects. Late binding is based on the concept of runtime discovery and execution of an object's methods through the COM IDispatch interface. When you declare and use object variables as variants, you are using late-binding. Unfortunately, each late bound method call incurs quite a bit of overhead and may slow down your client application considerably. To avoid this, use early (vtable) binding instead.

 

Assuming that you have a coclass Foo contained in a FooServer server. The following shows how to early bind to Foo from your client:

 

In Delphi

 

Use the Project | Import Type Library menu and import FooServer from the list of registered servers. Delphi generates a unit named FooServer_TLB.pas. Include that unit into your project and instantiate Foo as follows:

 

uses FooServer_TLB;

var Foo : IFoo;

begin

  Foo := CoFoo.Create; 

  Foo.Bar;  //call Bar method

end;

 

In C++ Builder

 

Use the Project | Import Type Library menu and import FooServer from the list of registered servers. CBuilder generates a module named FooServer_TLB.h (and FooServer_TLB.cpp). Include that module into your project and instantiate Foo as follows:

 

#include "FooServer_TLB.h"

TCOMIFoo Foo = CoFoo::Create ();

Foo->Bar ();  //call Bar method

 

2. Use Connection Points judiciously 

COM Connection Points is not the only way to enable server-to-client callbacks. In fact, a simple interface handed from the client down to the server will do the trick. However, connection points are widely supported by a lot of applications (especially Microsoft applications), which might force you to eventually deal with it one way or another.

 

Know these when implementing connection points:

 

A lot of connection point implementations are used to trigger server-to-client dispinterface callbacks. A dispinterface is simply a specification for IDispatch.Invoke. Because of this, a server will need to know how to make an IDispatch.Invoke call and a client will need to know how to implement IDispatch.Invoke. Implementing IDispatch.Invoke is not for the faint of heart - trust me! If you're implementing connection points in your server for non-scripting clients, forget dispinterfaces and simply implement vtable-based connection points. That would make it a lot easier for you to implement both the client and the server. In addition, a vtable-interface eliminates some of the overhead involved in dispinterface-based calls.

 

When using connection points, a client that connects to the server will require at least 3 roundtrip calls:

 

a) Server.QueryInterface (IConnectionPointContainer),

b) IConnectionPointContainer.FindConnectionPoint (CP), and

c) IConnectionPoint.Advise (IUnknown)

 

Since IConnectionPoint.Advise takes an IUnknown, the server will also make at least 1 extra roundtrip QueryInterface call back to the client (to obtain IDispatch when using dispinterfaces, or ICustomCallback when using a custom vtable interface)

 

Also, unless the client caches the connection point interface, disconnecting from the server will again require at least 3 roundtrip calls:

 

a) Server.QueryInterface (IConnectionPointContainer),

b) IConnectionPointContainer.FindConnectionPoint (CP), and

c) IConnectionPoint.Unadvise (Cookie)

 

Therefore, a single client negotiating with the server using connection points will normally require at least 7 roundtrip calls between client and server. For in-process (or maybe local out-of-process) servers, this is normally Ok. However, for remote servers, this is a little bit too much network traffic for a single client - what if a lot of clients connect to the remote server?

 

In short, use connection points only where appropriate. For remote callbacks, prefer a hand-coded mechanism of passing a client's callback interface down to the server through a custom server interface.

 

Connection points are designed in such a way that it's difficult to distinguish among the connected clients. It is difficult, if not impossible, to selectively "filter" certain clients that you may want to call back depending on certain circumstances. In other words, if you don't care about filtering or identifying which client is which, then connection points maybe a good choice for you. Otherwise, resort to a hand-coded mechanism.

 

3. Initialize threads that interact with COM 

Ever get the "CoInitialize has not been called" (800401F0 hex) error?

 

Each thread in your application that interacts with COM (i.e. creates COM objects, calls COM APIs, etc.) must initialize itself into an apartment. A thread can either join a single threaded apartment (STA) or the multithreaded apartment (MTA).

 

The STA is system-synchronized based on a windows message queue. Use the STA if your object or thread relies on thread-relative resources such as UI elements. The following shows how to initialize a thread into an STA:

 

procedure FooThreadFunc;  //or TFooThread.Execute

begin

  CoInitializeEx (NIL, COINIT_APARTMENTTHREADED);

  ... do your stuff here ...

  CoUninitialize;

end;

 

The MTA is system-guaranteed to be ruthless. Objects in the MTA will receive incoming calls from anywhere anytime. Use the MTA for non-UI related objects, but synchronize carefully! The following shows how to initialize a thread into the MTA:

 

procedure FooThreadFunc;  //or TFooThread.Execute

begin

  CoInitializeEx (NIL, COINIT_MULTITHREADED);

  ... do your stuff here ...

  CoUninitialize;

end;

 

4. Marshal interface pointers across apartments 

Ever get the "The application called an interface that was marshaled for a different thread" (8001010E hex) error?

 

When passing an interface pointer from apartment to apartment, it is a violation of COM's threading rules if you don't perform marshaling. This is because you will bypass any necessary requirements that COM might need in order to successfully make cross-apartment calls. Marshaling interface pointers involve using CoMarshalInterface and CoUnmarshalInterface. However for practical purposes, we tend to prefer the easier CoMarshalInterThreadInterfaceInStream and CoGetInterfaceAndReleaseStream API pair.

 

The following shows how to marshal an interface pointer from Foo1Thread to Foo2Thread, both from different apartments:

 

var MarshalStream : pointer;

 

//original thread

procedure Foo1ThreadFunc;  //or TFoo1.Execute

var Foo : IFoo;

begin

  //assuming Foo2Thread is currently suspended

  CoInitializeEx (...);

  Foo := CoFoo.Create;

  //marshal

  CoMarshalInterThreadInterfaceInStream (IFoo, Foo, IStream (MarshalStream));

  //tell Foo2Thread that MarshalStream is ready

  Foo2Thread.Resume;

  CoUninitialize;

end;

 

//user thread

procedure Foo2ThreadFunc;  //or TFoo2.Execute

var Foo : IFoo;

begin

  CoInitializeEx (...);

  //unmarshal

  CoGetInterfaceAndReleaseStream (IStream (MarshalStream), IFoo, Foo);

  MarshalStream := NIL;

  //use Foo

  Foo.Bar;

  CoUninitialize;

end;

 

The marshaling technique shown above is also described as marshal once-unmarshal once. If you want to marshal once and unmarshal as many times as you wish, use the COM (NT 4 SP3) provided Global Interface Table (GIT). The GIT allows you to marshal in interface pointer into a cookie and the unmarshaling threads can use this cookie to unmarshal how ever many times they want. Using the GIT, the above example can be written:

 

const

  CLSID_StdGlobalInterfaceTable : TGUID =

  '{00000323-0000-0000-C000-000000000046}';

 

type

  IGlobalInterfaceTable = interface(IUnknown)

    ['{00000146-0000-0000-C000-000000000046}']

    function RegisterInterfaceInGlobal (pUnk : IUnknown; const riid: TIID;

      out dwCookie : DWORD): HResult; stdcall;

    function RevokeInterfaceFromGlobal (dwCookie: DWORD): HResult; stdcall;

    function GetInterfaceFromGlobal (dwCookie: DWORD; const riid: TIID;

      out ppv): HResult; stdcall;

  end;

 

function GIT : IGlobalInterfaceTable;

const

  cGIT : IGlobalInterfaceTable = NIL;

begin

  if (cGIT = NIL) then

    OleCheck (CoCreateInstance (CLSID_StdGlobalInterfaceTable, NIL, CLSCTX_ALL,

      IGlobalInterfaceTable, cGIT));

  Result := cGIT;

end;

 

var MarshalCookie : dword;

 

//original thread

procedure Foo1ThreadFunc;  //or TFoo1.Execute

var Foo : IFoo;

begin

  //assuming Foo2Thread is currently suspended

  CoInitializeEx (...);

  Foo := CoFoo.Create;

  //marshal

  GIT.RegisterInterfaceInGlobal (Foo, IFoo, MarshalCookie)

  //tell Foo2Thread that MarshalCookie is ready

  Foo2Thread.Resume;

  CoUninitialize;

end;

 

//user thread

procedure Foo2ThreadFunc;  //or TFoo2.Execute

var Foo : IFoo;

begin

  CoInitializeEx (...);

  //unmarshal

  GIT.GetInterfaceFromGlobal (MarshalCookie, IFoo, Foo)

  //use Foo

  Foo.Bar;

  CoUninitialize;

end;

 

And don't forget to remove the interface from the GIT when you not longer wish to use it:

 

GIT.RevokeInterfaceFromGlobal (MarshalCookie);

MarshalCookie := 0;

 

If you dislike the low-level GIT gunk, you can use the friendlier TGIP class from my ComLib library.

 

5. Don't call AddRef and Release unless necessary 

With the advent of smart compilers and smart pointers, explicitly calling IUnknown.AddRef and IUnknown.Release is a thing of the past.

 

In Delphi

 

var Foo, AnotherFoo : IFoo;

Foo := CoFoo.Create;

AnotherFoo := Foo;

 

The assignment to AnotherFoo implicitly calls AddRef on the Foo instance, compliments of the Delphi compiler. Furthermore, when Foo and AnotherFoo go out of scope (or if you assign NIL to them), Delphi will also implicitly call Release for you.

 

In C++ Builder

 

TCOMIFoo Foo = CoFoo::Create ();

IFooPtr AnotherFoo = Foo;

 

The assignment to AnotherFoo implicitly calls AddRef on the Foo instance, compliments of the TComInterface smart pointer class (note though that a lot of other assignment operators in TComInterface don't AddRef the source). Furthermore, when Foo and AnotherFoo go out of scope, TComInterface will implicitly call Release for you.

 

6. Implement error handling correctly 

In COM, every interface method must return an error code to the client. An error code is a standardized 32-bit value called an HRESULT. The 32 bits in an HRESULT are actually divided into parts as follows: a bit that indicates success or failure (severity), a few bits that indicate the classification of the error (facility), and another few bits that indicate the actual error number (code). What we're most interested in is how to get the error number part into the HRESULT. In addition, COM suggests that our error numbers should be in the range of 0200 hex to FFFF hex.

 

Unfortunately, an HRESULT is rather limiting because in addition to the error number, we might also want the server to tell the client what the error is (description), where it happened (source), and where the client can possibly get more help on the error (help file and help context). For this, COM introduces another interface, IErrorInfo, that the client can use to obtain additional information on an error, if any. In simple terms, IErrorInfo can be thought of as a storage for the error description, source, help file, and help context. If the server passes error information to the client thru IErrorInfo, COM also suggests that the server implement ISupportErrorInfo. Although this is not required, it is a good idea to do so because some clients, like Visual Basic, will ask the server for this interface.

 

In Delphi

 

Delphi has something called the safecall calling convention. What this means is that if you raise an internal exception within your object's implementation, Delphi will automatically "trap" the exception and convert it to a COM HRESULT, and populate an IErrorInfo structure ready for shipping to the client. This is all done in the HandleSafeCallException function in ComObj. In addition, the VCL classes also implement ISupportErrorInfo for you already.

 

Here's the fun part: When you raise an EWhatever exception in the server, it will always be seen by the client as EOleException. EOleException contains all the goodies in HRESULT and IErrorInfo combined, i.e. the error number, description, source, help file, and help context. The thing is, in order to set up the goodies for the client, the server must raise EOleSysError instead of EWhatever. More specifically, when you raise an EOleSysError, you should make sure the error number you give it is a conventionally formatted HRESULT. To see what I mean, let's say we have an object named FooServer.Foo that has a Bar method. In Bar, we want to raise an error whose number=5, description="Error Message", help file="HelpFile.hlp", help context = 1, and the obvious source="FooServer.Foo". This is how we do it:

 

uses ComServ;

 

const

  CODE_BASE = $200; //recommended codes are from 0x0200 - 0xFFFF

 

procedure TFoo.Bar;

begin

  //can be assigned once (globally) from somewhere

  ComServer.HelpFileName := 'HelpFile.hlp';  //help file

  //raise error: message='Error Message', number=5 + CODE_BASE, help context=1

  raise EOleSysError.Create (

    'Error Message', //error message

    ErrorNumberToHResult (5 + CODE_BASE), //HRESULT

    1 //help context

    );

end;

 

function ErrorNumberToHResult (ErrorNumber : integer) : HResult;

const

  SEVERITY_ERROR = 1;

  FACILITY_ITF = 4;

begin

  Result := (SEVERITY_ERROR shl 31) or (FACILITY_ITF shl 16) or word (ErrorNumber);

end;

 

If you look at the highlighted line closely, the ErrorNumberToHResult call simply converts our error number into a standard HRESULT. Also, we add CODE_BASE (200 hex) to our error number so that we follow COM's suggestion that custom error numbers be in the range of 0200 hex to FFFF hex. Note that we didn't specify the source="FooServer.Foo" value. That's because Delphi "knows" in which object the exception was raised and will automatically fill that in for you.

 

On the client side, this is how we trap the error through EOleException:

 

const

  CODE_BASE = $200; //recommended codes are from 0x0200 - 0xFFFF

 

procedure CallFooBar;

var

  Foo : IFoo;

begin

  Foo := CoFoo.Create;

  try

    Foo.Bar;

  except

    on E : EOleException do

      ShowMessage ('Error message: ' + E.Message + #13 +

        'Error number: ' + IntToStr (HResultToErrorNumber (E.ErrorCode) - CODE_BASE) + #13 +

        'Source: ' + E.Source + #13 +

        'HelpFile: ' + E.HelpFile + #13 +

        'HelpContext: ' + IntToStr (E.HelpContext)

        );

  end;

end;

 

function HResultToErrorNumber (hr : HResult) : integer;

begin

  Result := (hr and $FFFF);

end;

 

Again, I've highlighted the only important line. We call HResultToErrorNumber to extract the error number part from HRESULT and then we subtract CODE_BASE (200 hex) to compensate for the conventional error base.

 

In C++ Builder

 

CBuilder uses ATL. On the server, ATL provides the AtlReportError function that an object can call to return the error number plus the IErrorInfo goodies to the client. To see what I mean, let's say we have an object named FooServer.Foo that has a Bar method. In Bar, we want to raise an error whose number=5, description="Error Message", help file="HelpFile.hlp", help context = 1, and the obvious source="FooServer.Foo". This is how we do it:

 

const

  CODE_BASE = 0x200;  //recommended codes are from 0x0200 - 0xFFFF

 

STDMETHODIMP TFooImpl::Bar()

{

  return AtlReportError (

    GetObjectCLSID (), //which class generated the error

    "Error Message", //error description

    1, //help context

    "HelpFile.hlp", //help file

    IID_IFoo, //which interface generated the error

    ErrorNumberToHRESULT (5 + CODE_BASE) //HRESULT

    );

}

 

HRESULT ErrorNumberToHRESULT (int ErrorNumber)

{

  return MAKE_HRESULT (SEVERITY_ERROR, FACILITY_ITF, ErrorNumber);

}

 

Look at the highlighted line closely. ErrorNumberToHRESULT converts our error number to a standard HRESULT. Also, we add CODE_BASE (200 hex) to our error number so that we follow COM's suggestion that custom error numbers be in the range of 0200 hex to FFFF hex. Note that we didn't specify the source="FooServer.Foo" value. That's because ATL will convert the CLSID that you pass in (first parameter: GetObjectCLSID ()) to its corresponding PROGID and will automatically fill that in for you.

 

If you (normally) derive from CComCoClass, you can also simply call the overloaded Error methods in CComCoClass, which eventually call AtlReportError.

 

 

The second thing we need to do is to implement ISupportErrorInfo. In order to do that, simply add the simple ATL-provided ISupportErrorInfoImpl class to TFooImpl:

 

class ATL_NO_VTABLE TFooImpl :

  public CComObjectRoot,

  public CComCoClass<TFooImpl, &CLSID_Foo>,

  public IDispatchImpl<IFoo, &IID_IFoo, &LIBID_FooServer>,

  public ISupportErrorInfoImpl <&IID_IFoo>

{

...

BEGIN_COM_MAP(TFooImpl)

  COM_INTERFACE_ENTRY(IFoo)

  COM_INTERFACE_ENTRY(IDispatch)

  COM_INTERFACE_ENTRY(ISupportErrorInfo)

END_COM_MAP()

...

};

 

On the client side, we can implement a standard mechanism for extracting the error number out of the returned HRESULT value and the extra error information from IErrorInfo. Fortunately, there exists an EOleException exception class in the VCL that can hold all of this error information. To do this, I've created a simple function, CheckResult, that properly extracts all error information, puts them into an EOleException, and then raises the exception. Here's how you'd use this function:

 

#include <comobj.hpp>

 

const

  CODE_BASE = 0x200;  //recommended codes are from 0x0200 - 0xFFFF

 

void CallFooBar ()

{

  TCOMIFoo Foo = CoFoo::Create ();

  try

  {

    CheckResult (

      Foo->Bar (),  //invoke method which returns the HRESULT

      Foo,  //specifies the IUnknown* of the object we're extracting error info from

      IID_IFoo  //specifies the IID of the interface that contains the method invoked above

      );

  }

  catch (EOleException& e)

  {

    ShowMessage ("Error Message: " + e.Message + '\n' +

       "Error Number: " + (HRESULTToErrorNumber (e.ErrorCode) - CODE_BASE) + '\n' +

      "Source: " + e.Source + '\n' +

      "HelpFile: " + e.HelpFile + '\n' +

      "HelpContext: " + e.HelpContext

      );

  }

}

 

int HRESULTToErrorNumber (int hr)

{

  return HRESULT_CODE (hr);

}

 

Again, I've highlighted the only important line. We call HRESULTToErrorNumber to extract the error number part from HRESULT and then we subtract CODE_BASE (200 hex) to compensate for the conventional error base.

 

Finally, here's the ugly CheckResult function that you don't need to mess with:

 

#include <comobj.hpp>

 

void CheckResult (

  HRESULT hr,  //HRESULT returned from method

  IUnknown* Object = NULL,  //Object in which we invoked the method

  REFIID ErrorIID = GUID_NULL  //IID of the interface that contains the method invoked

)

{

  if (FAILED (hr) && (HRESULT_FACILITY (hr) == FACILITY_ITF))

  {

    bool HasErrorInfo = false;

 

    if (Object && (!IsEqualGUID (ErrorIID, GUID_NULL)))

    {

      //check ISupportErrorInfo

      ISupportErrorInfo* SupportErrorInfo = NULL;

      HRESULT hr = Object->QueryInterface (IID_ISupportErrorInfo, (void**)&SupportErrorInfo);

      if (SUCCEEDED (hr))

      {

        if (SupportErrorInfo->InterfaceSupportsErrorInfo (ErrorIID) == S_OK)

          HasErrorInfo = true;

        SupportErrorInfo->Release ();

      }

    }

    else

      //assume caller don't care about ISupportErrorInfo!

      HasErrorInfo = true;

 

    if (HasErrorInfo)

    {

      int ErrorCode = hr;

      WideString Description, Source, HelpFile;

      ULONG HelpContext = 0;

 

      //get error info

      IErrorInfo* ErrorInfo = NULL;

      if (SUCCEEDED (GetErrorInfo (0, &ErrorInfo)))

      {

        ErrorInfo->GetDescription (&Description);

        ErrorInfo->GetSource (&Source);

        ErrorInfo->GetHelpFile (&HelpFile);

        ErrorInfo->GetHelpContext (&HelpContext);

        ErrorInfo->Release ();

      }

 

      throw EOleException (Description, ErrorCode, Source, HelpFile, HelpContext);

    }

  }

}

 

For Visual Basic Clients

 

If you're wondering how this all fits into the Err object in a VB client, here's how you'd trap the error raised above:

 

Private Sub CallFooBar()

  On Error GoTo ErrorHandler

  Dim Foo As New FooServer.Foo

  Foo.Bar

  Exit Sub

 

ErrorHandler:

  MsgBox "Error Message: " & Err.Description & vbCr & _

    "Error Number: " & Err.Number - vbObjectError - &H200 & vbCr & _

    "Source: " & Err.Source & vbCr & _

    "HelpFile: " & Err.HelpFile & vbCr & _

    "HelpContext: " & Err.HelpContext

End Sub

 

On the highlighted line, subtracting vbObjectError extracts the error number from the HRESULT, and then subtracting &H200 (200 hex) compensates for the conventional error base.

 

7. Know how to implement multiple interfaces 

The ability to implement any interface in any object is one of COM's greatest strengths. Different development environments provide different means of implementing COM interfaces. Let's say you have a coclass named FooBar (that already supports IFooBar) in which you want to implement 2 external interfaces: IFoo and IBar. IFoo and IBar are defined as follows:

 

IFoo = interface

  procedure Foo;  //implicit HRESULT assumed

end;

 

IBar = interface

  procedure Bar;  //implicit HRESULT assumed

end;

 

In Delphi

 

type

  TFooBar = class (TAutoObject, IFooBar, IFoo, IBar)

  protected

    //IFooBar

    ... IFooBar methods here ...

    //IFoo methods

    procedure Foo;

    //IBar methods

    procedure Bar;

  ...

  end;

 

procedure TFooBar.Foo;

begin

end;

 

procedure TFooBar.Bar;

begin

end;

 

If IFooBar, IFoo, and IBar were all IDispatch-based, TAutoObject will pick IFooBar (the primary/default interface) to implement IDispatch, i.e. a script-based client will only be able to see IFooBar's methods.

 

In C++ Builder

 

class ATL_NO_VTABLE TFooBarImpl :

  public CComObjectRoot,

  public CComCoClass <TFooBarImpl, &CLSID_FooBar>,

  public IFooBar,

  public IFoo,

  public IBar

{

BEGIN_COM_MAP(TFooBarImpl)

  COM_INTERFACE_ENTRY(IFooBar)

  COM_INTERFACE_ENTRY(IFoo)

  COM_INTERFACE_ENTRY(IBar)

END_COM_MAP()

protected:

  //IFoo methods

  STDMETHOD (Foo) ();

  //IBar methods

  STDMETHOD (Bar) ();

}

 

STDMETHODIMP TFooBarImpl::Foo ()

{

}

 

STDMETHODIMP TFooBarImpl::Bar ()

{

}

 

If IFooBar, IFoo, and IBar were all IDispatch-based, you'd probably want to implement TFooBarImpl this way:

 

class ATL_NO_VTABLE TFooBarImpl :

  public CComObjectRoot,

  public CComCoClass <TFooBarImpl, &CLSID_FooBar>,

  public IDispatchImpl <IFooBar, &IID_IFooBar, &LIBID_FooBarServer>,

  public IDispatchImpl <IFoo, &IID_IFoo, &LIBID_FooBarServer>,

  public IDispatchImpl <IBar, &IID_IBar, &LIBID_FooBarServer>

{

BEGIN_COM_MAP(TFooBarImpl)

  COM_INTERFACE_ENTRY(IFooBar)

  COM_INTERFACE_ENTRY_IID(IID_IDispatch, IFooBar)

  COM_INTERFACE_ENTRY(IFoo)

  COM_INTERFACE_ENTRY(IBar)

END_COM_MAP()

protected:

  //... implement methods here ...

}

 

Note how I use COM_INTERFACE_ENTRY_IID () for IDispatch. What this does is whenever TFooBarImpl is QI'd for IDispatch, it will return the IFooBar part of it, i.e. a script-based client will only be able to see IFooBar's methods. This also gets rid of a compile time error because the compiler cannot resolve which of the 3 IDispatches (IFooBar, IFoo, or IBar) to use as its IDispatch implementation.

 

8. Know which classes do what 

In Delphi

 

Delphi provides quite a number of classes for COM development: TInterfacedObject, TComObject, TTypedComObject, TAutoObject, TAutoIntfObject, TComObjectFactory, TTypedComObjectFactory, TAutoObjectFactory, etc. How do we know which one to use?

 

Here's the scoop. Ready?!

 

TInterfacedObject

 

TInterfacedObject provides you with an implementation of IUnknown. If you want to create an "internal" object that implements "internal" interfaces that (usually) have nothing to do with COM, TInterfacedObject is the best class to derive from. Of course, you can still use TInterfacedObject to create an object that can be passed to a COM client - just remember, the only support you get from it is IUnknown, nothing more, nothing less.

 

TComObject

 

TComObject provides you with implementations of IUnknown, ISupportErrorInfo, standard COM aggregation support, and a matching coclass class factory support. If you want to create a lightweight client-creatable COM object that implements IUnknown-based interfaces, TComObject is the best class to derive from.

 

TComObjectFactory

 

TComObjectFactory works in tandem with TComObject. It exposes its corresponding TComObject to the outside world as a coclass. Among the goodies TComObjectFactory brings are coclass registration (CLSIDs, ThreadingModel, ProgID, etc.), IClassFactory & IClassFactory2 support, and standard COM object licensing support. In short, if you have a TComObject, use TComObjectFactory with it.

 

TTypedComObject

 

TTypedComObject is TComObject + support for IProvideClassInfo. IProvideClassInfo is simply an automation standard to expose an object's type information (available names, methods, supported interfaces, etc.) stored in an associated type library. In addition to TComObject, TTypedComObject is also useful for objects that want to provide clients with the ability to browse their type information at runtime. For instance, Visual Basic's TypeName function expects an object to implement IProvideClassInfo so that it can determine the object's "documented name"  based on type information stored in the type library.

 

TTypedComObjectFactory

 

TTypedComObjectFactory works in tandem with TTypedComObject. It is TComObjectFactory + it provides a cached type information (ITypeInfo) reference for TTypedComObject to use. In short, if you have a TTypedComObject, use TTypedComObjectFactory with it.

 

TAutoObject

 

TAutoObject is TTypedComObject + support for IDispatch. TAutoObject's IDispatch support is automatic and is based on type information stored in the type library - ever wonder why you never had to implement any of the 4 IDispatch methods in your automation objects? If you want to create standard client-creatable automation (supports IDispatch) objects, TAutoObject is the best class to derive from. In addition, as of D4, TAutoObject provides a built-in standard connection point mechanism support.

 

TAutoObjectFactory

 

TAutoObjectFactory works in tandem with TAutoObject. It is TTypedComObjectFactory + it provides cached type information (ITypeInfo) references for your TAutoObject's default/primary (IDispatch-based) interface and it's connection point's event interface (if any). In short, if you have a TAutoObject, use TAutoObjectFactory with it.

 

TAutoIntfObject

 

TAutoIntfObject is TInterfacedObject + support for IDispatch. More specifically TAutoIntfObject's IDispatch support is type library-based similar to how TAutoObject does it. In contrast to TAutoObject, TAutoIntfObject has no corresponding class factory (coclass) support meaning that external clients cannot directly instantiate a TAutoIntfObject-derived class. However, TAutoIntfObject is excellent for (IDispatch-based) sub-level objects or sub-properties (that are themselves objects) that you want to expose to the clients.

 

In C++ Builder

 

C++ Builder uses ATL. Here's a few important things to know about how your objects are constructed using ATL:

 

In ATL, a COM object = Abstract Class + CComObject (or CComObject variants)

 

The abstract class is what we're familiar with, you know... the class with the infamous ATL_NO_VTABLE tag:

 

class ATL_NO_VTABLE TFooImpl ...

 

TFooImpl is an abstract class because we cannot directly instantiate TFooImpl - try it an you'll see. ATL has this interesting concept that your abstract class must first marry CComObject in order to exist as one valid COM object:

 

CComObject <TFooImpl> *Foo;

CComObject <TFooImpl>::CreateInstance (&Foo);

//Foo is now an instance of our TFooImpl class

 

Let's backtrack a bit and dig deeper into the abstract class. First, we need to support IUnknown. That's what CComObjectRoot/Ex is for:

 

class ATL_NO_VTABLE TFooImpl :

  public CComObjectRoot/Ex <SomeThreadingModel>, ...

 

For details on the differences between CComObjectRoot and CComObjectRootEx, consult ATL Internals by Brent Rector and Chris Sells

 

Next, we decide if we need coclass support, i.e. do we want external clients to be able to create our object. That's what CComCoClass is for:

 

class ATL_NO_VTABLE TFooImpl :

  public CComObjectRoot,

  public CComCoClass (TFooImpl, &CLSID_Foo), ...

 

CComCoClass takes care of IClassFactory and aggregation support, registration gunk, some standard COM error handling stuff, etc.

 

To complete CComCoClass' coclass support, we also need an entry in the global OBJECT_MAP structure:

 

BEGIN_OBJECT_MAP(ObjectMap)

  OBJECT_ENTRY(CLSID_Foo, TFooImpl)

END_OBJECT_MAP()

 

Also, CComCoClass automatically does the CComObject <TFooImpl>::CreateInstance call for us whenever it is asked to create an instance of our TFooImpl class.

 

With IUnknown and coclass support available, we can then implement our custom interfaces. Let's say we want to implement an interface IFoo declared as follows:

 

IFoo = interface

  ...

end;

 

To do this we need to add IFoo to our abstract class, both in the inheritance chain and the interface map:

 

class ATL_NO_VTABLE TFooImpl :

  public CComObjectRoot,

  public CComCoClass <TFooImpl, &CLSID_Foo>,

  public IFoo

{

BEGIN_COM_MAP(TFooBarImpl)

  COM_INTERFACE_ENTRY(IFooBar)

  COM_INTERFACE_ENTRY(IFoo)

END_COM_MAP()

}

 

The "public IFoo" part means that we're implementing IFoo in our class. The COM_INTERFACE_ENTRY (IFoo) part means that clients can get to our IFoo implementation, i.e. clients can QI for IFoo.

 

But what if we want IFoo to be IDispatch-based, as it is "normally":

 

IFoo = interface (IDispatch)

  ...

end;

 

Well, we can still use the above class declaration but we'd also have to manually implement the 4 IDispatch methods. But there's an easier way: define IFoo in a type library (usually in your server's type library) and then use ATL's IDispatchImpl class for built-in IDispatch support, i.e. we don't need to bother with manually implementing the 4 IDispatch methods.

 

Thus:

 

class ATL_NO_VTABLE TFooImpl :

  public CComObjectRoot,

  public CComCoClass <TFooImpl, &CLSID_Foo>,

  public IDispatchImpl <IFoo, &IID_IFoo, &LIBID_FooServer>

{

BEGIN_COM_MAP(TFooBarImpl)

  COM_INTERFACE_ENTRY(IFooBar)

  COM_INTERFACE_ENTRY(IFoo)

  COM_INTERFACE_ENTRY(IDispatch)

END_COM_MAP()

}

 

Note that IDispatchImpl requires the &IID_IFoo and &LIBID_FooServer parts because it will implement IDispatch for us based on the IFoo that we have defined in the type library. Also note that we need an extra COM_INTERFACE_ENTRY (IDispatch) entry in the interface map so that a client that QIs for IDispatch will get our default IDispatch implementation - IFoo.

 

There's a lot more to ATL than what I've shown you here. If you're up to it, check out ATL Internals by Brent Rector and Chris Sells or ATL-A Developer's Guide by Tom Armstrong.

 

9. Know the 3 most important things in COM security 

Nothing can be more frustrating than not knowing why your (DCOM) server denies access to your clients. In a nutshell, COM security can be described as follows:

 

Authentication

 

From the server's point of view, your first job is to determine whether or not you want to identify your clients, i.e. do you care who your clients are? This is called authentication. In simple terms, when the server authenticates the client, the server asks the client "show me your ID", the client hands the server its ID, and the server verifies if indeed the ID is legitimate or not. In COM, the ID is the client's username and password account. The server's verification process will involve contacting a domain controller (if any) to determine if the client's account information is valid or not.

 

Authentication can be of different levels ranging from no authentication (server doesn't care to ask the client for its ID) to paranoid authentication (server will ensure that the client is identified and in addition, all communication between the client and server is encrypted to avoid eavesdroppers from finding out what the client and the server are up to). The authentication levels that you're probably most familiar with (assuming you have used DCOMCNFG) are None and Connect. None, as the name implies, means no authentication. Connect, as the name implies, means authenticate only the first time the client connects to the server. There are other levels of authentication but we won't talk about them in detail here.

 

The most important thing to remember about an authentication level is that they are ranked from lowest authentication (None) to highest authentication (Packet Privacy). Using this ranking, if a server specifies an authentication level of X, any clients who try to connect (make calls) to the server at a lower authentication level than X will automatically be rejected by COM. For example: you specify an authentication level of Connect on your server. An unknown client (who has no account on your network) such as Joe Bum from the Internet tries to contact your server. Since your server specified Connect, COM will perform authentication. COM won't be able to identify Joe Bum and so therefore, COM will flag Joe Bum right at the door with an "Access Denied" message.

 

Access Control

 

Being able to identify the client is only part of the story. Once the server identifies the client, it will need to decide whether or not the client can access it. This concept is similar to that big dude at your local bar: you might be able to produce an ID but he'll let you in only if you're of legal drinking age. This is called access control.

 

There's really nothing much to access control. Your server simply specifies that only users X, Y, and Z have access to it. All other users, even though they have valid IDs, are denied access. In DCOMCNFG terms, the "Use custom access permissions" and "Use custom launch permissions" options under the security tab is what I mean by access control.

 

Server Identity

 

Aside from authentication and access control, another important thing is that a server should be running before clients can connect to it. Before the server runs, there's one last thing that it needs to decide: under which account should it run as? This is called server identity.

 

There are 4 main options for a server's identity:

 

Use the account of the user that's currently logged in to the server machine (Interactive User)

Use the account of the client that connects to the server (Launching User)

Use a predefined account designed for use only by the server (This User)

Use the SYSTEM account (available only for an NT service COM server)

Opting for Interactive User has some interesting aspects. First, the server will only have privileges of the user that's currently logged in to the server machine - and these privileges vary depending on who that user is. In addition, if nobody is logged in, your server can't assume as "nobody" so it will refuse to run. On the bright side, this option is the only one where you get to see your server's forms (if any) and any message boxes that your server pops up. Because of this, Interactive User is excellent for debugging purposes.

 

Opting for the Launching User also has some interesting aspects. First, since the server wants to assume the identity of the client, each client will launch its own separate copy of the server. This is logical because a single instance of the server cannot assume the identity of multiple clients all at the same time. Also, this option is rather crippled in that COM will deny the server access to any remote/network resources from the server machine, even including connecting back to the client machine.

 

Opting for This User is recommended for production deployment. Using this option, you can designate a distinct user account only for purposes of your server. This way, you can easily give or deny permissions to this particular account depending on what kinds of resources your server needs to access.

 

Summary

 

These 3 aspects are the most important in understanding COM security. However, my discussions here are rather incomplete, at best. For more detailed information, consult Don Box's Essential COM and the following excellent sites:

 

DCOM 95 Frequently Asked Questions

COM Security Frequently Asked Questions

COM Internet Services

Using Distributed COM with Firewalls

Dan Miser's DCOM FAQ

 

10. Get rid of that nagging DCOM callback problem 

If you've ever implemented callbacks across machines, you might have discovered seemingly odd and unexplainable errors. I some other cases, the callback call just doesn't seem to work correctly.

 

Here's why:

 

First, read the Authentication part of Tip #9 above.

 

When the server makes a call back into the client, COM will also perform authentication at the authentication level specified by the client (normally the Default Authentication level in DCOMCNFG). If authentication fails, COM will fail the call. Now here's an interesting aspect of the authentication level. When a client connects to the server, COM will always pick the higher authentication level between the client and the server as the negotiated authentication level. This means that calls from the client to the server and vice-versa will be authenticated at the negotiated authentication level.

 

How is this important? Well if you think that lowering the authentication level on the client side to None (meaning that you want calls from the server back to the client to be unauthenticated) fixes our DCOM callback problem, you're only half right. This is because COM will always look at both the client and the server to determine the negotiated authentication level. If the client specifies None but the server specifies something higher than None, say Connect, COM will use Connect as the negotiated authentication level. This means that when the server calls back into the client, COM will authenticate at Connect level, even if the client didn't really need/want it. Because of this, the authentication must succeed in order for the call to succeed. If authentication fails (i.e. the client cannot verify who the server is), the call will fail.

 

Obviously, one way to solve this problem is to 1) set both authentication levels for client and server to None or 2) make sure the server's identity can be authenticated on the client's domain. For debugging purposes, lowering the authentication level to None for both client and server is simply the easiest.

 

The authentication level can be set in DCOMCNFG or by calling the CoInitializeSecurity API.

 

Another way is to somehow lower the authentication level (to None) at runtime, before the server makes its call back into the client. This can be done using the IClientSecurity interface or the CoSetProxyBlanket API. I won't show you the details of how to do this but you can check out Don Box's Essential COM for a complete discussion.

 

11. Understand marshaling concepts 

Ever get the "Interface not supported/registered" (80004002 hex) error?

Ever wonder why you have to register at least the type library on a client machine for early binding to work?

 

COM's concept of location transparency is made possible through behind-the-scene helper objects known as proxies and stubs. When a client talks to an object on a remote machine (or, technically, in another apartment), the client really talks to the proxy, which talks to COM, which talks to the stub, which finally talks to the object:

 

 

 

Whenever the client makes a method call, the proxy packs the method parameters into a flat array and gives it to COM. COM then transports the array to the stub, which unpacks the array back to individual method parameters, which then invokes the method on the server object. This process is called marshaling.

 

One of the things you probably never thought about is where do the proxy and the stub come from? The simple answer is that proxies and stubs are also COM objects! In fact, the proxy and stub objects are contained in COM DLLs (also called proxy-stub DLLs) that get registered on your system.

 

An interesting thing about this is that COM provides a built-in proxy-stub DLL that's capable of creating proxy and stub objects on-the-fly based on information contained in a type library. This DLL (oleaut32.dll) is called the type library marshaler or the universal marshaler. Although this marshaler is good enough for most purposes, it is only capable of marshaling parameter data that can be represented using the automation VARIANT data type.

 

In your type library, you must annotate your interface definition with the [oleautomation] flag to specify that you want your interface marshaled using the type library marshaler. The [oleautomation] flag can be used on any interface. A lot of COM newbies think that it is used only for IDispatch-based interfaces. On the contrary, it is perfectly Ok to annotate IUnknown-based interfaces with it (of course, as long as your method parameters are all VARIANT compatible). [oleautomation] simply tells COM to use the type library marshaler for the associated interface and has nothing to do with IDispatch or automation.

 

Delphi and CBuilder takes advantage of the type library marshaler and relies heavily on your type library. If you come from a Visual C++ background, understand that Delphi and CBuilder does not have the ability to easily create custom proxy-stub DLLs useful for non-VARIANT parameter data types.

 

Since the type library marshaler relies on information in your type library, it's obvious that your type library has to be registered on both client and server machines for it work. If you forget to do this, you'll most likely get the dreaded "Interface not supported/registered" error!

 

Type library registration is required only if you use early binding. If you use late binding (i.e. variants or dispinterface binding), COM uses the IDispatch interface which is already registered on your system with a well-known proxy-stub DLL. Therefore, late binding does not require registration of your type library file.

 

12. Understand COM identity concepts 

The COM specification is very clear about a few important and elementary concepts of what constitutes something called the "COM Identity". Understanding COM identity is very important and is a must for every COM developer.

 

First and foremost, COM mandates that an object's implementation of QueryInterface for IUnknown, and only IUnknown, must always return the same pointer value. Consider an object FooBar that implements IFooBar, IFoo, and IBar:

 

Foo = FooBar.QueryInterface (IFoo);

Bar = FooBar.QueryInterface (IBar);

Assert (Foo.QueryInterface (IUnknown) = Bar.QueryInterface (IUnknown))

 

The above assertion must always hold true! This IUnknown requirement, among other things, enables a client to compare 2 IUnknown values and definitively tell whether or not they point to the same server object. Note that this requirement is only for IUnknown. An object is not required to follow this requirement for any other interface that it supports.

 

Second, COM mandates that QueryInterface must be symmetric, reflexive, and transitive:

 

Symmetric means that if you got a pointer from a QueryInterface call for a given IID, then calling QueryInterface on that pointer using the same IID must succeed:

 

Foo = FooBar.QueryInterface (IFoo);

Foo.QueryInterface (IFoo) must succeed

 

Reflexive means that if you got a pointer from a QueryInterface call for a given IID, then calling QueryInterface on that pointer using the IID of the original pointer must succeed:

 

Foo = FooBar.QueryInterface (IFoo);

Bar = Foo.QueryInterface (IBar);

Bar.QueryInterface (IFoo) must succeed

 

Transitive means that if you get a pointer that's 2 (or more) QueryInterface calls away from the original pointer, you should be able to get to that pointer directly from the original pointer:

 

FooBar = FooBar.QueryInterface (IFooBar);

Foo = FooBar.QueryInterface (IFoo);

Bar = Foo.QueryInterface (IBar);

FooBar.QueryInterface (IBar) or Bar.QueryInterface (IFooBar) must succeed

 

Third, COM mandates that if a QueryInterface call succeeded (or failed) for a given IID, calling QueryInterface for that same IID at a later time must always succeed (or fail):

 

Foo = FooBar.QueryInterface (IFoo);

 

If the above call succeeds (i.e. FooBar supports IFoo), then calling FooBar.QueryInterface (IFoo) on the same FooBar instance must always succeed. Furthermore, if the above call fails (i.e. FooBar doesn't support IFoo), then calling FooBar.QueryInterface (IFoo) on the same FooBar instance must always fail.

 

All these requirements are important for COM to be able to properly implement it's runtime magic and so that identity (or QueryInterface) semantics are predictable and consistent. For example, you should not selectively accept (or deny) a QueryInterface call for an IID based on the client's identity or the time of the day because your object may never be asked to perform that QueryInterface again. To illustrate, let's say you need FooBar to expose IFoo only to Jack but not to Jill.

 

If Jack calls FooBar.QueryInterface (IFoo), your object hands Jack its IFoo. If Jill calls FooBar.QueryInterface (IFoo), your object fails the call with E_NOINTERFACE. What happens if Jack calls FooBar.QueryInterface (IFoo) and then hands the pointer to Jill? If, in this process, a QueryInterface call never happens (most likely), then Jill will get an IFoo, which is not what you wanted!

 

The symmetric, reflexive, and transitive aspects of QueryInterface ensures that a client does not have to know (or is not required to perform) a particular sequence of QueryInterface calls to get to a particular interface. What this means is that if FooBar implements 5 interfaces:

 

FooBar = class (TFooBar, IFooBar, IFoo, IBar, IJack, IJill)

 

Given a FooBar instance, if the client wants IJill, you must not require the client to go through a particular sequence of QueryInterface calls to get to IJill, i.e. FooBar.QueryInterface (IJill) should be all the client needs to call. Imagine if you required the client to do this to get to IJill:

 

Foo = FooBar.QueryInterface (IFoo);

Bar = Foo.QueryInterface (IBar);

Jack = Bar.QueryInterface (IJack);

Jill = Jack.QueryInterface (IJill);  //finally, my IJill

 

Not only would that be a burden on the client, it'd also hard-code "internal knowledge" of the exact sequence into the client application.

 

A note on Delphi

Delphi 4 introduced the implements keyword that enables us to implement "tear-off" interfaces that can be used to optimize on resources (in cases where the client never queries for the tear-off interface). For instance, consider a FooBar class that implements IBar as a tear-off:

 

type

  TFooBar = class (TComObject, IFooBar, IBar)

  ...

  public

    property BarTearOff : IBar read GetBar implements IBar;

  end;

 

The above construct has the advantage that if the client never queries for IBar, the BarTearOff property will never be invoked and thus, any resources involved in doing that is never wasted. However, beware! If the client does this:

 

FooBar := CoFooBar.Create;

Bar := FooBar as IBar;  //calls FooBar.QueryInterface (IBar)

BackToFooBar := Bar as IFooBar;  //must succeed because of QI reflexivity

 

The last line above looks reasonably correct and should succeed. But, it will succeed only if you implement your BarTearOff property/class correctly. In particular, your BarTearOff class may need to AddRef and Release TFooBar correctly, and forward QueryInterface calls to TFooBar appropriately. If you don't do this extra work in BarTearOff, you'll be violating the laws of COM identity.

 

 

13. Design simple and efficient interfaces 

Designing interfaces can be harder than designing object-oriented classes. This is because an interface is a contract of interoperability between the client and your object. Once you deploy your objects and interfaces, it will be extremely difficult, if not impossible, to make changes to your interfaces.

 

It's difficult to quantify what's a simple or what's an efficient interface. However, I'll show you some practices that can result in complex or inefficient interfaces. Based on these, you'll know how to avoid them when designing your own interfaces.

 

An interface should consist of methods that reflect its functionality. If you find yourself in a situation where you need to publish a method in your object and feel that the new method does not belong in an existing interface, then create a new interface, put that method in there, and add that interface to your object. It's very easy to create a smorgasbord of (hundreds of) unrelated methods into 1 single interface but it's extremely hard to maintain it on the object side and extremely hard to use it on the client side. Nobody likes to use (or maintain) an interface with 100+ methods!

 

Minimize interface inheritance. Being from an OO world, you might be led to think that designing layers and layers of inherited interfaces would impress your colleagues. Consider this simple inheritance chain:

 

IFoo = interface

  procedure Foo;

end;

 

IBar = interface (IFoo)

  procedure Bar;

end;

 

From an OO standpoint, the advantage of making IBar inherit IFoo is so that given an IBar, a client can call IBar.Foo. Here's the problem: Sooner or later, you might need to add additional functionality to IFoo. If you already deployed IFoo to your clients, you know that it is bad to go back in and simply change IFoo (this would easily break existing clients). What you'd normally do instead is to create a newer IFoo, say IFoo2 (stands for IFoo version 2):

 

IFoo2 = interface (IFoo)  //inherits all IFoo methods

  procedure Foo2;

end;

 

Since your OO mentality dictates that you want IBar to inherit IFoo's features, you'll also want to create a new IBar2 that corresponds to IFoo2 (remember the old IBar inherits from the old IFoo, which can't be changed):

 

IBar2 = interface (IFoo2)  //inherits all IFoo2 methods

  procedure Bar;

end;

 

Note that since IBar2 can't inherit from both IFoo2 and the old IBar (COM interface inheritance is based on a single inheritance chain), we have to retype method Bar (or more specifically, all IBar methods) into IBar2. If we had 10 IBar methods, we'd have to manually copy all 10 methods into IBar2. If we later evolve the interfaces (IFoo3, IFoo4, IBar3, etc.), you can see how messy this can get.

 

But let's go back to the problem at hand. You wanted IBar to inherit from IFoo because all you wanted to do was for the client to be able to call any of IFoo's methods on an IBar pointer:

 

Bar = CoBar.Create;

Bar.Foo;

 

But that's not really necessary. For instance, the client can simply make a QueryInterface call to get to the IFoo part of Bar and invoke method Foo achieving the same result (assuming of course that your object supports both IFoo and IBar):

 

Bar = CoBar.Create;

Foo = Bar.QueryInterface (IFoo);

Foo.Foo;

 

Furthermore, this second approach does not and will not require nested interface inheritance chains. For instance, on the server side, Bar can simply be implemented as follows:

 

IFoo = interface

  procedure Foo;

end;

 

IBar = interface  //no IFoo inheritance here!

  procedure Bar;

end;

 

Bar = class (TComObject, IFoo, IBar);

 

And more importantly, as IFoo and IBar evolve, we simply evolve Bar using a "flat" (instead of hierarchical) implementation style:

 

IFoo2 = interface (IFoo)

  ...

end;

 

IBar2 = interface (IBar)  //no need to inherit IFoo2 here!

  ...

end;

 

Bar = class (TComObject, IFoo, IBar, IFoo2, IBar2, ...)

 

Be careful when implementing collection interfaces for remote objects. The classic collection interface looks something like this:

 

IItems = interface

  property Count : integer;

  property Item [Index : integer] : IItem;

end;

 

Given an IItems pointer, the client can easily iterate the collection using the Count and Item [] properties like this:

 

Items = ServerObject.GetItems;  //assume GetItems returns IItems

for i = 1 to Items.Count do

begin

  AnItem = Items.Item [i];

  DoSomething (AnItem);

end;

 

This is simply classic collection iteration and, in fact, is very object-oriented. What you probably don't realize is that if the server object resides on a remote machine, the above iteration will require at least Items.Count roundtrip calls across the network (due to the IItems.Item [] call inside the for-loop). If there are 100 elements in IItems, that would translate to 100 roundtrips across the wire - obviously not a very efficient scenario.

 

What you can do instead is to packet groups of IItems elements into an array and ship that array in chunks from the server to the client. This way if 100 items were packed into groups of 50 elements, that would require only 2 roundtrips (2 x 50 = 100) to bring the entire collection down to the client. In COM, arrays of data can be sent using COM arrays, a very common example of which is the automation safearray (or variant array). I won't go into the details of how to do this but you can check out my DCOM tutorial to see an implementation of this concept.

 

In general, when designing interfaces for remote objects, the more information you can transfer in one method call, the more efficient your applications are. Consider a simple interface with 4 properties:

 

IFoo = interface

  property Foo;

  property Bar;

  property Jack;

  property Jill;

end;

 

Given a Foo instance, a client will retrieve all 4 properties like this:

 

Foo = CoFoo.Create;

FooProperty = Foo.Foo;

BarProperty = Foo.Bar;

JackProperty = Foo.Jack;

JillProperty = Foo.Jill;

 

That's 4 roundtrip calls across the wire if Foo is a remote object. Put that in a loop and that could easily magnify as trouble.

 

A more efficient approach would be to add a method that retrieves (or sets) all properties in 1 shot:

 

IFoo = interface

  property Foo;

  property Bar;

  property Jack;

  property Jill;

  procedure GetProperties (out Foo; out Bar; out Jack; out Jill);

end;

 

The client would now simply make 1 roundtrip call to retrieve all 4 properties:

 

Foo = CoFoo.Create;

Foo.GetProperties (FooProperty, BarProperty, JackProperty, JillProperty);

 

Note that we still might want to keep the 4 individual properties in the interface in case we need granular control of which properties to manipulate. However, by adding a GetProperties method, we can sometimes reduce overhead that may not have been apparent at the time we originally designed our interface.

 

14. IDispatch, dispinterfaces, vtable interfaces, dual interfaces, etc. 

If you, like me, are troubled by the exact meanings of IDispatch, dispinterfaces, vtable interfaces, dual interfaces, etc. at one time or another, here's the best I can do to finally clarify things for you.

 

IDispatch is a widely used COM interface. When you use the Delphi/CBuilder File | New | Automation Object wizard, the IDE will create an automation object whose interface is IDispatch-based (if you look in the library editor, your new interface will have IDispatch as its parent interface). What this means is you are allowing script-based clients to be able to call methods of your object, nothing more, nothing less.

 

IDispatch contains 2 methods that are most useful for script-based clients: GetIDsOfNames and Invoke. Whenever a client calls a method on your object (through IDispatch), it will, under-the-hood, call GetIDsOfNames followed by Invoke, always. For a detailed discussion on this, check out the automation chapter on my site.

 

Half of the IDispatch protocol is invoking a method based on its associated numeric ID (dispid). In fact, this is precisely what IDispatch.Invoke does. Because of this, it is perfectly reasonable to create an interface that defines only the dispid numbers (and method parameter signatures) as the actual methods of the interface. In theory, a definition of this interface looks like this:

 

DispFoo = interface

  dispid_1 (param1);

  dispid_2 (param1, param2);

  dispid_n (param1, param2, param3, ...);

end;

 

Using this interface, the client would make method calls directly using dispids:

 

FooDisp = CoFoo.Create;

FooDisp.dispid_1 (param1);  //invoke method whose dispid = 1

FooDisp.dispid_2 (param1, param2);  //invoke method whose dispid = 2

 

In reality, each of the dispid_n method calls above really boils down to an IDispatch.Invoke call. Notice though, that using this new interface, we only make IDispatch.Invoke calls. We can forget about IDispatch.GetIDsOfNames simply because we already know the dispids up front. In COM, this interface is what is called a dispinterface. In simple terms, a dispinterface is an interface that specifies how to make IDispatch.Invoke calls.

 

In contrast to the IDispatch protocol, a more common way for non-script-based clients to call an object's methods is through early binding (or vtable binding). Early binding is based on the concept of low-level stack-based method invocations very similar to how you invoke internal methods and procedures within your application. The object's interface in which you are making early bound method calls into is also known as a vtable interface. In simple terms, if a client makes an early bound method call into your object, it is using (one of) your object's vtable interface.

 

It is possible for an interface to be both IDispatch-based and vtable-based. This allows an interface to be usable to both script-based and non-script-based clients. In COM, such an interface is called a dual interface (contains a dispinterface part and a vtable part). In simple terms, given a dual interface, you can call its methods using either IDispatch (GetIDsOfNames and Invoke) or early binding.

 

15. Know how to implement objects that support Visual Basic's For Each construct 

If you or your colleagues develop in Visual Basic, there might be a time where somebody has asked how you can enable VB's For Each construct to work with your Delphi or CBuilder COM collection objects. 

 

For Each allows a VB client to iterate a collection's elements in a standard manner. For example:

 

Dim Items as Server.IItems  //declare variable that holds collection

Dim Item as Server.IItem  //declare variable that holds element

 

Set Items = ServerObject.GetItems  //retrieve IItems collection from server object

//iterate Items in a For Each-loop

For Each Item in Items

  Call DoSomething (Item)  //do something to each item in the collection

Next

 

How do we make this work? The answer lies in a COM interface called IEnumVARIANT:

 

IEnumVARIANT = interface (IUnknown)

  function Next (celt; var rgvar; pceltFetched): HResult;

  function Skip (celt): HResult;

  function Reset: HResult;

  function Clone(out Enum): HResult;

end;

 

For Each is really nothing but a construct that knows how to call the IEnumVARIANT methods (particularly Next) to iterate through all elements in the collection. Although it's really not that difficult to learn the semantics of IEnumVARIANT, it's often easier to create a high-level reusable class that encapsulates it because you might find yourself implementing IEnumVARIANT a lot of times.

 

A specific mechanism dictates how we expose IEnumVARIANT to the client. For instance, let's say you have a collection interface that looks like this:

 

//single Foo item

IFooItem = interface (IDispatch);

 

//collection of Foo items

IFooItems = interface (IDispatch)

  property Count : integer;

  property Item [Index : integer] : IFoo;

end;

 

First, to be able to use IEnumVARIANT, your collection interface must support automation (be IDispatch-based) and your individual collection item data type must be VARIANT compatible (automation compatible). In simple terms, IFooItems must be IDispatch-based and IFooItem must be VARIANT compatible (e.g. byte, BSTR, long, IUnknown, IDispatch, etc.).

 

Second, we go into the type library and add a read-only property named _NewEnum to the collection interface. _NewEnum must return IUnknown and must have a dispid = -4 (DISPID_NEWENUM). Applying this to IFooItems:

 

IFooItems = interface (IDispatch)

  property Count : integer;

  property Item [Index : integer] : IFoo;

  property _NewEnum : IUnknown; dispid -4;

end;

 

Third, we implement _NewEnum by returning our IEnumVARIANT pointer from that property.

 

In Delphi

 

I've created a reusable class (TEnumVariantCollection in ComLib.pas) that hides the details of IEnumVARIANT. In order to plug TEnumVariantCollection into your collection object, you'll need to implement an interface with 3 simple methods:

 

IVariantCollection = interface

  //used by enumerator to lock list owner

  function GetController : IUnknown; stdcall;

  //used by enumerator to determine how many items

  function GetCount : integer; stdcall;

  //used by enumerator to retrieve items

  function GetItems (Index : olevariant) : olevariant; stdcall;

end;

 

To see this in action, let's try it on our IFooItems interface:

 

type

  //Foo items collection

  TFooItems = class (TSomeBaseClass, IFooItems, IVariantCollection)

  protected

    { IVariantCollection }

    function GetController : IUnknown; stdcall;

    function GetCount : integer; stdcall;

    function GetItems (Index : olevariant) : olevariant; stdcall;

  protected

    FItems : TInterfaceList;  //internal list of Foo items;

  ...

end;

 

function TFooItems.GetController: IUnknown;

begin

  //always return Self/collection owner here

  Result := Self;

end;

 

function TFooItems.GetCount: integer;

begin

  //always return collection count here

  Result := FItems.Count;

end;

 

function TFooItems.GetItems(Index: olevariant): olevariant;

begin

  //always return collection item here

  //cast as IDispatch because each Foo item is IDispatch-based

  Result := FItems.Items [Index] as IDispatch;

end;

 

Finally, we implement _NewEnum as follows:

 

function TFooItems.Get__NewEnum: IUnknown;

begin

  //use my TEnumVariantCollection helper class :)

  Result := TEnumVariantCollection.Create (Self);

end;

 

That's it! And say goodbye to that nagging For Each problem!

 

In C++ Builder

 

ATL provides a slew of COM enumerator classes for almost any IEnumWhatever that you can think of.  In particular, CComEnum (and CComEnumImpl) are good enough to produce an IEnumVARIANT enumerator that makes VB happy.

 

The general idea when using CComEnum together with IEnumVARIANT is to first produce an array of VARIANTs, then populate this array with our collection's items, then pass this array to an instance of CComEnum, and finally hand out CComEnum's IUnknown to the _NewEnum property.

 

To see this in action, let's try it on our IFooItems interface:

 

//implementation of property _NewEnum

STDMETHODIMP TFooItemsImpl::get__NewEnum (LPUNKNOWN* Value)

{

  //create VARIANT array

  VARIANT* varray = new VARIANT [ItemCount - 1];

 

  //populate array with each Foo item

  for (int i = 0; i < ItemCount; i++)

  {

    VariantInit (&varray [i]);

    VariantCopy (&varray [i], WhereverYouStoreFooItem [i]);

  }

 

  //initialize CComEnum

  typedef CComEnum <IEnumVARIANT, &IID_IEnumVARIANT, VARIANT,

    _Copy <VARIANT> > MyEnumT;

 

  //create enumerator

  CComObject <MyEnumT> *Enum;

  CComObject <MyEnumT>::CreateInstance (&Enum);

 

  //initialize our enumerator with our array

  Enum->Init (

    &varray [0],  //collection low bound

    &varray [ItemCount],  //1 + collection high bound

    GetUnknown (),  //collection owner's IUnknown

    AtlFlagTakeOwnership  //means, Enum will be responsible for releasing varray

  );

 

  //finally return enumerator's IUnknown to _NewEnum

  return Enum->QueryInterface (&Value);

}

 

If you've noticed, it can be a pain in the neck to create a temporary array of VARIANTs everytime we want to hand out IEnumVARIANT. This is because CComEnum expects a contiguous array of the exact data type of each element that we're dealing with (in this case VARIANT).

 

ATL 3.0 alleviates this problem by allowing enumerator's to "sit" directly on top of a container (especially STL containers) eliminating the need for the temporary array. However, BCB 4 doesn't support ATL 3.0 yet, so we'll just have to make do with what we have.

 

That's it! And say goodbye to that nagging For Each problem!

 

16. Know how to implement clients that iterate IEnumVARIANT-based collections (ala Visual Basic's For Each construct) 

In Visual Basic, there's something called a For Each construct that allows a client to easily enumerate an IEnumVARIANT-based collection (see tip above). Assuming that you have an object, Foo, that has an IEnumVARIANT-based property, Items, this is how we use VB's For Each syntax to iterate Foo.Items:

 

Dim Foo as FooServer.Foo

Dim Item as Variant

 

Set Foo = CreateObject ("FooServer.Foo")

For Each Item in Foo.Items

  call DoSomething (Item)

Next

 

In Delphi

 

Is there an equivalent to For Each in Delphi? The answer is there's the hard way and the easy way. The hard way, obviously, is to familiarize yourself with IEnumVARIANT, specifically IEnumVARIANT.Reset and IEnumVARIANT.Next. The easy way is to use a class, TEnumVariant, that I created for such purpose. Since, in general, it is safe to assume that everybody wants the easy way, I'll show you how to use TEnumVariant against Foo.Items:

 

uses ComLib;

 

var

  Foo : IFoo;

  Item : olevariant;

  Enum : TEnumVariant;

begin

  Foo := CreateOleObject ('FooServer.Foo') as IFoo;  //or CoFoo.Create

  Enum := TEnumVariant.Create (Foo.Items);

  while (Enum.ForEach (Item)) do

    DoSomething (Item);

  Enum.Free;

end;

 

What could be easier than this?!

 

In C++ Builder

 

Is there an equivalent to For Each in CBuilder? The answer is there's the hard way and the easy way. The hard way, obviously, is to familiarize yourself with IEnumVARIANT, specifically IEnumVARIANT.Reset and IEnumVARIANT.Next. The easy way is to use a class, TEnumVariant, that I created for such purpose. Since, in general, it is safe to assume that everybody wants the easy way, I'll show you how to use TEnumVariant against Foo.Items:

 

{

  TCOMIFoo Foo = CoFoo::Create ();

  TEnumVariant Enum (Foo->Items);

  Variant Item;

  while (Enum.ForEach (Item))

    DoSomething (Item);

}

 

And here's the TEnumVariant class that you don't want to mess with:

 

//supports IEnumVARIANT for IDispatch-based DISPID_NEWENUM properties

class TEnumVariant

{

public:

  TEnumVariant () : mEnum (0)

  {

  }

 

  TEnumVariant (IDispatch *Collection) : mEnum (0)

  {

    Attach (Collection);

  }

 

  ~TEnumVariant ()

  {

    Detach ();

  }

 

  void Attach (IDispatch *Collection)

  {

    Detach ();

    bool ValidEnum = false;

    if (Collection)

    {

      VARIANT Result;

      VariantInit (&Result);

      DISPPARAMS DispParamsEmpty;

      memset (&DispParamsEmpty, 0, sizeof (DispParamsEmpty));

      //get prop DISPID_NEWENUM

      HRESULT hr = Collection->Invoke (

        DISPID_NEWENUM, GUID_NULL, LOCALE_SYSTEM_DEFAULT,

        DISPATCH_PROPERTYGET, &DispParamsEmpty, &Result, NULL, NULL);

      if (SUCCEEDED (hr))

      {

        //get IEnumVARIANT*

        Result.punkVal->QueryInterface (IID_IEnumVARIANT, (void**)&mEnum);

        VariantClear (&Result);

        Reset ();

        ValidEnum = (mEnum != NULL);

      }

    }

 

    //raise exception if collection does not support IEnumVARIANT

    if (!ValidEnum)

      throw Exception ("Object does not support enumeration (IEnumVariant)");

  }

 

  void Detach ()

  {

    if (mEnum)

    {

      mEnum->Release ();

      mEnum = NULL;

    }

  }

 

  void Reset ()

  {

    if (mEnum) mEnum->Reset ();

  }

 

  bool ForEach (Variant &Data)

  {

    if (!mEnum) return false;

    ULONG Fetched = 0;

    VARIANT Item;

    VariantInit (&Item);

    HRESULT hr = mEnum->Next (1, &Item, &Fetched);

    if (SUCCEEDED (hr))

    {

      if (Fetched > 0)

      {

        Data = Item;

        VariantClear (&Item);

      }

      return (Fetched > 0);

    }

    else

      return false;

  }

protected:

  IEnumVARIANT *mEnum;

};

 

17. Know how to use aggregation and containment 

COM aggregation and containment are two techniques of reusing existing COM objects while still preserving the concept of COM identity. To see why you'd want to use aggregation or containment, consider this simple scenario: You bought 2 COM objects from a vendor, Foo (IFoo) and Bar (IBar). You then want to create your own object, FooBar, that exposes the facilities of Foo and Bar combined. In other words, your FooBar class will look something like this:

 

IFoo = interface

  procedure Foo;

end;

 

IBar = interface

  procedure Bar;

end;

 

type

  FooBar = class (BaseClass,

                          IFoo,  //FooBar exposes IFoo

                          IBar   //FooBar exposes IBar

                         )

  end;

 

What you want to do is to (re)use Foo when implementing your IFoo methods and to (re)use Bar when implementing your IBar methods. This is where aggregation and containment can help.

 

Containment

 

Let's start with containment first because that's easier. Containment is simply the process of instantiating an inner object (object to reuse) and then delegating method calls into that inner object. This is how we do containment for IFoo in FooBar:

 

In Delphi

 

type

  TFooBar = class (TComObject, IFoo)

  protected

    //IFoo methods

    procedure Foo;

  protected

    FInnerFoo : IFoo;

    function GetInnerFoo : IFoo;

  end;

 

procedure TFooBar.Foo;

var

  Foo : IFoo;

begin

  //obtain internal Foo object

  Foo := GetInnerFoo;

  //delegate call to internal Foo object

  Foo.Foo;

end;

 

function TFooBar.GetInnerFoo : IFoo;

begin

  //create internal Foo object if not yet initialized

  if (FInnerFoo = NIL) then

    FInnerFoo := CreateComObject (Class_Foo) as IFoo;

  //return internal Foo object

  Result := FInnerFoo;

end;

 

Doing something like this is not delegation and, thus, is not considered containment:

 

type

  TFooBar = class (TComObject, IFoo)

  protected

    function GetInnerFoo : IFoo;

    property InnerFoo : IFoo read GetInnerFoo implements IFoo;

  end;

 

The difference between this and the prior class is that in the prior class, TFooBar is the one exposing IFoo (and internally delegates implementation method-by-method to InnerFoo). In this class, it is InnerFoo's IFoo that the client actually sees, so no delegation is happening.

 

In C++ Builder

 

class ATL_NO_VTABLE TFooBar :

  public CComObjectRoot,

  public CComCoClass<TFooBar, &CLSID_FooBar>,

  public IFoo

{

protected:

  BEGIN_COM_MAP(TFooBar)

    COM_INTERFACE_ENTRY(IFoo)

  END_COM_MAP()

 

  //IFoo methods

  STDMETHOD (Foo) ();

protected:

  IFoo *mInnerFoo;

  IFoo* GetInnerFoo ();

public:

  void FinalRelease ();

end;

 

STDMETHODIMP TFooBar::Foo ()

{

  //obtain internal Foo object

  IFoo * Foo = GetInnerFoo ();

  //delegate call to internal Foo object

  Foo->Foo ();

}

 

IFoo* TFooBar::GetInnerFoo ()

{

  //create internal Foo object if not yet initialized

  if (mInnerFoo == NULL)

  {

    HResult hr = CoCreateInstance (

      CLSID_Foo, NULL, CLSCTX_INPROC, IID_IFoo, (void**)&mInnerFoo);

    ErrorCheck (hr);

  }

  //return internal Foo object

  return mInnerFoo;

}

 

void FinalRelease ()

{

  //release inner Foo

  if (mFoo)

  {

    mFoo->Release ();

    mFoo = NULL;

  }

}

 

Note that the concept of containment is delegation! You simply forward/delegate all calls from the outer object into the inner object.

 

Aggregation

 

Implementing containment can be tedious because if the inner object's interface has a lot of methods, you'll have to do a lot of typing when delegating from the outer object ("owner" object) to the inner object. In other words, if IFoo has 20 methods, then you'll have to type all 20 methods into TFooBar and delegate each one of them to InnerFoo. Another thing about containment is that you have to explicitly know the inner's interface up-front so that you can delegate properly. This means that if the inner's interface evolves, you might need to revisit the outer and rebuild it in case you want to expose new functionality from the inner.

 

These are some of the reasons why you might want to look at aggregation. Simply put, aggregation is the mechanism of directly exposing the inner to the client, while correctly preserving COM identity.

 

The first rule about aggregation is that you can aggregate an inner object *only* if it supports aggregation. This means that the inner must know how to implement the delegating and the non-delegating QIs.

 

To learn more about the details on the delegating and non-delegating QIs, consult Inside COM by Dale Rogerson.

 

The second rule of aggregation is that when the outer constructs the inner, it should 

 

Pass in it's (outer) IUnknown into the inner as part of the CoCreateInstance call, and

Ask for the inner's IUnknown, and only IUnknown

In addition, it is recommended that the outer forwards a QI call to the inner only if the client asks for the inner's interface (some texts refer to this as planned aggregation). Assuming that Foo is aggregatable, this is how we aggregate Foo into TFooBar:

 

In Delphi

 

The QI forwarding for IFoo is easily implemented using Delphi's implements keyword.

 

type

  TFooBar = class (TComObject, IFoo)

  protected

    function GetControllingUnknown : IUnknown;

    function GetInnerFoo : IFoo;

    property InnerFoo : IFoo read GetInnerFoo implements IFoo;  //exposes IFoo directly from InnerFoo

  protected

    FInnerFoo : IUnknown;

  end;

 

function TFoo.GetControllingUnknown : IUnknown;

begin

  //returns the correct outer unknown for aggregation

  if (Controller <> NIL) then

    Result := Controller

  else

    Result := Self as IUnknown;

end;

 

function TFooBar.GetInnerFoo : IFoo;

begin

  //create internal Foo object if not yet initialized

  if (FInnerFoo = NIL) then

    CoCreateInstance (

      CLASS_Foo,                    //Foo's CLSID

      GetControllingUnknown,  //outer passes it's controlling IUnknown into inner

      CLSCTX_INPROC,          //assume Foo is inproc

      IUnknown,                      //ask for Foo's IUnknown, and only IUnknown

      FInnerFoo                      //output inner Foo

    );

  //return internal Foo object

  Result := FInnerFoo as IFoo;

end;

 

When implementing the inner (aggregatable) object itself, Delphi's TComObject (root of all COM objects) has the aggregation feature built-in already. In simple terms, any COM object that ultimately derives from TComObject is aggregatable (supports aggregation).

 

 

In C++ Builder

 

You can simply use ATL's COM_INTERFACE_ENTRY_AGGREGATE macro to aggregate an inner into the outer's interface map. 

 

class ATL_NO_VTABLE TFooBar :

  public CComObjectRoot,

  public CComCoClass<TFooBar, &CLSID_FooBar>

{

protected:

  BEGIN_COM_MAP(TFooBar)

    COM_INTERFACE_ENTRY(IFoo)

    COM_INTERFACE_ENTRY_AGGREGATE(IID_IFoo, mInnerFoo)

  END_COM_MAP()

 

  //this is used for GetControllingUnknown!!!

  DECLARE_GET_CONTROLLING_UNKNOWN()

protected:

  IUnknown *mInnerFoo;

public:

  HRESULT FinalConstruct ();

  void FinalRelease ();

end;

 

HRESULT FinalConstruct ()

{

  //create inner Foo

  HRESULT hr = CoCreateInstance (

    CLSID_Foo,                        //Foo's CLSID

    GetControllingUnknown (),  //outer passes it's controlling IUnknown into inner

    CLSCTX_INPROC,               //assume Foo is inproc

    IID_IUnknown,                   //ask for Foo's IUnknown, and only IUnknown

    (void**)&mInnerFoo            //output inner Foo

  );

  return hr;

}

 

void FinalRelease ()

{

  //release inner Foo

  if (mFoo)

  {

    mFoo->Release ();

    mFoo = NULL;

  }

}

 

When implementing the inner (aggregatable) object itself, ATL's CComCoClass has the aggregation feature built-in already. This is made possible because of the DECLARE_AGGREGATABLE () macro in CComCoClass. For a detailed discussion on ATL aggregation support, consult ATL Internals by Brent Rector and Chris Sells.

In simple terms, it is safe to assume that any COM object that ultimately derives from CComCoClass is aggregatable (supports aggregation) by default.

 

 

That's it for aggregation. And don't forget, aggregation only works if the inner is written to support aggregation. If it's not, then aggregation is the wrong choice, whereas containment would be a right choice.

 

18. Understand the class factory Instancing property (SingleInstance, MultiInstance) 

A lot of folks get confused with the class factory Instancing property. This is probably because of reading incorrect documentation and/or listening to incorrect advise. In fact, the COM documentation (on MSDN, for instance) is very clear about the instancing property - and that's where everyone should be reading about instancing from. Let's translate COM instancing into lay terms.

 

The class factory Instancing property applies *only* to EXE servers. For DLL servers, Instancing is undefined and inapplicable!

 

The class factory Instancing property is not a property of the EXE server nor the COM object. It does not dictate, per se, how EXE servers are launched depending on client requests. So forget about those confusing "one object per server or multiple objects per server" rules.

This is what Instancing really means:

 

Each object in your server that a client can create has an associated object called a factory object (or class factory). If your server consists of 2 objects, Foo and Bar, there will be a class factory for Foo and another class factory for Bar. Whenever the client requests to create an object in your server, COM will actually ask the associated class factory to create the object. In effect, the class factory is a gatekeeper for object creation through COM.

 

Class factories are registered with the COM runtime when an EXE server runs (and they are revoked when the server terminates). Registration allows, among others, COM to locate and request any given registered class factory to create the object that the client requests. COM allows each class factory to register using 3 instancing modes: SingleUse, MultiUse, and MultiSeparateUse. We'll only talk about SingleUse and MultiUse because these are the 2 common ones.

 

SingleUse means that COM will request the class factory to create *at most 1 instance* of the it's associated object. After a SingleUse class factory has created its one instance, COM will revoke it from runtime. Thus, when the next client comes along and requests to create an object from this class factory, COM sees that it's no longer registered and will launch another instance of the EXE server to be able to obtain the class factory again. Relaunching repeats the process: factories get registered again, COM finds the factory, requests it to create the object and then, if it's SingleUse, immediately revokes the factory from runtime. This cycle just keeps on going and going.

 

MultiUse, on the other hand, means that COM will request the class factory to create *however many instances* of it's associated object. This means that, unlike SingeUse, COM will not revoke the factory from runtime at all. Thus, when the next client comes along and requests to create an object from this class factory, COM will always see that it's still registered and will happily create the object using the class factory from within the currently running EXE instance.

 

In Delphi

 

In Delphi terms:

 

ciSingleInstance = SingleUse

ciMultiInstance = MultiUse

 

And ciInternal has nothing to do with COM. ciInternal simply means that your Delphi COM object doesn't get registered into the registry nor does the class factory get registered with the COM runtime. In effect, clients won't be able to see (and create) COM objects marked with the ciInternal factory instancing flag.

 

I've always used this definition of class factory Instancing and I've never been confused! Ever!

 

19. Know how to implement servers that support GetActiveObject 

If you've been working with MS Office automation, you're probably familiar with the global "Application" object per server. For instance, MS Word allows you to connect to it's running Application (_Application interface) instance as follows:

 

var

  Word : variant;

begin

  //connect to running instance of Word if available

  //GetActiveOleObject will raise an exception if there is no active instance of Word

  Word := GetActiveOleObject ('Word.Application');

end;

 

This facility can sometimes be useful when developing your own COM servers. Here's how we can do this type of thing.

 

First, in your server, you'll need to register an instance of your global Application object with something called the COM Running Object Table (ROT). The ROT is nothing but a location where you register named object instances to be accessible by client applications. We can easily get our Application object into the ROT using the RegisterActiveObject API:

 

function RegisterActiveObject (

  unk: IUnknown;              //object to register

  const clsid: TCLSID;      //CLSID of object to register

  dwFlags: Longint;           //registration option flags, generally use ACTIVEOBJECT_STRONG

  out dwRegister: Longint  //handle returned by COM on successful registration

): HResult; stdcall;

 

And, as you might have already expected, you can later revoke a registered object from the ROT to make it unavailable to clients. Revoking is done using the RevokeActiveObject API:

 

function RevokeActiveObject (

  dwRegister: Longint;   //registration handle obtained from calling RegisterActiveObject

  pvReserved: Pointer    //set to NIL/NULL

): HResult; stdcall;

 

Practically speaking, registering an object into the ROT means that your server should not terminate at least until after you revoke it from the ROT. But the question is, who should (or when should you) revoke the object from the ROT? 

 

The practical convention seems to be that the object should revoke itself in response to a Quit (or Exit) command call from the client. This practice is apparent in the MS Office applications. In other words, in your global Application object, you'll probably want to expose a Quit method and in that method, call RevokeActiveObject to remove your global object from the ROT.

 

The actual conventions on when to call RegisterActiveObject and RevokeActiveObject are documented by Microsoft. For more details on this, check out the Automation Programmer's Reference.

On a different note, all this stuff about registration into the ROT is only practical for EXE servers. For a DLL server, it might be a bit more tricky to determine when to register/revoke an object from the ROT because the lifetime of a DLL server is dependent on the client host.

 

 

Assuming that we want a global Foo object to be accessible from the ROT, here's how we implement it:

 

In Delphi

 

In your DPR file:

 

begin

  Application.Initialize;

  RegisterGlobalFoo;

  Application.CreateForm(TForm1, Form1);

  Application.Run;

end.

 

var

  GlobalFooHandle : longint = 0;

 

procedure RegisterGlobalFoo;

var

  GlobalFoo : IFoo;

begin

  //create Foo instance

  GlobalFoo := CoFoo.Create;

  //register into ROT

  OleCheck (RegisterActiveObject (

    GlobalFoo, //Foo instance

    Class_Foo, //Foo's CLSID

    ACTIVEOBJECT_STRONG, //strong registration flag

    GlobalFooHandle //registration handle result

  ));

end;

 

Then we add a Quit method to Foo (IFoo) and revoke the GlobalFoo instance from there:

 

procedure TFoo.Quit;

begin

  RevokeGlobalFoo;

end;

 

procedure RevokeGlobalFoo;

begin

  if (GlobalFooHandle <> 0) then

  begin

    //revoke

    OleCheck (RevokeActiveObject (

      GlobalFooHandle, //registration handle

      NIL //reserved, use NIL

    ));

    //make sure we mark as revoked

    GlobalFooHandle := 0;

  end;

end;

 

Here's how a Delphi client locates our GlobalFoo from the ROT using the GetActiveObject API:

 

var

  FooUnk : IUnknown;

  Foo : IFoo;

begin

  //check if Foo is active

  //can also use Delphi's GetActiveOleObject function here if Foo is an automation object

  if (Succeeded (GetActiveObject (

    Class_Foo, //Foo's CLSID

    NIL, //reserved, use NIL

    FooUnk //returned Foo from ROT

  )))

  then begin

    //QI for IFoo

    Foo := FooUnk as IFoo;

 

    //...do something with Foo here...

 

    //terminate global Foo, will revoke from the ROT

    Foo.Quit;

  end;

end;

 

Delphi also has a GetActiveOleObject function that accepts a PROGID instead of the object's CLSID. GetActiveOleObject internally calls GetActiveObject, and only works for automation (IDispatch-based) objects.

 

In C++ Builder

 

In your server's main CPP file:

 

WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)

{

  try

  {

    Application->Initialize();

    RegisterGlobalFoo ();

    Application->CreateForm(__classid(TForm1), &Form1);

    Application->Run();

  }

  catch (Exception &exception)

  {

    Application->ShowException(&exception);

  }

  return 0;

}

 

static DWORD GlobalFooHandle = 0;

 

void RegisterGlobalFoo ()

{

  //create Foo instance

  TCOMIFoo GlobalFoo = CoFoo::Create ();

  //register into ROT

  HRESULT hr = RegisterActiveObject (

    GlobalFoo, //Foo instance

    CLSID_Foo, //Foo's CLSID

    ACTIVEOBJECT_STRONG, //strong registration flag

    &GlobalFooHandle //registration handle result

  );

  ErrorCheck (hr);

}

 

Then we add a Quit method to Foo (IFoo) and revoke the GlobalFoo instance from there:

 

STDMETHODIMP TFooImpl::Quit()

{

  RevokeGlobalFoo ();

  return S_OK;

}

 

void RevokeGlobalFoo ()

{

  if (GlobalFooHandle != 0)

  {

    //revoke

    HRESULT hr = RevokeActiveObject (

      GlobalFooHandle, //registration handle

      NULL //reserved, use NULL

    );

    ErrorCheck (hr);

 

    //make sure we mark as revoked

    GlobalFooHandle = 0;

  }

}

 

Here's how a CBuilder client locates our GlobalFoo from the ROT using the GetActiveObject API:

 

{

  IUnknown *FooUnk = NULL;

  IFoo *Foo = NULL;

 

  //check if Foo is active

  HRESULT hr = GetActiveObject (

    CLSID_Foo, //Foo's CLSID

    NULL, //reserved, use NULL

    &FooUnk //returned Foo from ROT

  );

  if (SUCCEEDED (hr))

  {

    //QI for IFoo

    FooUnk->QueryInterface (IID_IFoo, (void**)&Foo);

    FooUnk->Release ();

 

    //...do something with Foo here...

 

    //terminate global Foo, will revoke from the ROT

    Foo->Quit ();

    Foo->Release ();

  }

}

 

20. Know how to implement an object property that supports the automation default-property syntax 

Assuming you created an automation interface like this:

 

ICollection = interface (IDispatch)

  property Item [Index : variant] : variant;

end;

 

For a client, given an ICollection pointer, you can get to any item using this syntax:

 

Collection.Item [Index]

 

You might have come across other automation-based collections where they let you be lazy and do something like this instead:

 

Collection [Index]

 

Allowing clients (particularly VB clients) this syntax can be very convenient specially if you have deep levels of hierarchies of collections. To understand what I mean, compare this:

 

Collection.Item [Index].SubCollection.Item [Index].SubsubCollection.Item [Index]

 

to this simpler syntax:

 

Collection [Index].SubCollection [Index].SubsubCollection [Index]

 

Fortunately for us, automation allows us to easily do this kind of thing. In the type library, simply mark your Item [] property with a dispid value of 0 (DISPID_VALUE). This means that COM will automatically "hint at" the Item [] property as the default property of your collection.

 

Since this default property facility is based on dispids, this only works for automation (IDispatch-based) interfaces. For pure vtable interfaces, there is no such thing as default properties - so you can forget about this :).

Íàçàä