COM Notes

<< Contents >>

 

Jargon
Initialising COM
Using Existing COM Objects
Interfaces
IDL
HRESULT
COM Macros
COM Utilities
References

Example Code

 

 

Jargon:

coclasses  Fancy term for components
CLSID     Class ID. Identifies a coclass
IID Interface ID
Hive One of the HKEY_* top level registry keys

 

Initialising COM

Both clients and servers have to first initialise COM by calling CoInitialize (or CoInitializeEx) from each thread that will be using COM. When the thread has finished with COM (usually just before exiting) it should call CoUninitialize. DLL servers should not initialise COM because they are loaded into a thread which must have already initialised COM

 

CoInitialize takes a single (reserved) parameter of NULL. CoUninitialize takes no parameters

 

Using Existing COM Objects

Objects are instantiated through a call to CoCreateInstance. CoCreateInstance takes the following parameters:

 

HRESULT STDAPI CoCreateInstance (
    REFCLSID rclsid, ID of the class to be instatiated
    LPUNKNOWN pUnkOuter, "Outer unknown pointer". Used for COM aggregation (where COM objects can contain other COM objects). Pass NULL if aggregation is not involved.
    DWORD dwClsContext, Combination of the following constants which determine where the object will reside:
    CLSCTX_INPROC_SERVER (1)
    CLSCTX_INPROC_HANDLER (2)
    CLSCTX_LOCAL_SERVER (4)
    CLSCTX_REMOTE_SERVER (16)
    CLSCTX_SERVER (16+4+1)
    CLSCTX_ALL (16+4+2+1)

If multiple severs are allowed (e.g. CLSCTX_ALL) COM will pick the one with the best performance (i.e. in-process if available).
    REFIID riid, The interface we are interested in (as passed to QueryInterface)
    void** ppv Pointer to receive address of the riid interface 
);

 

Example of CoCreateInstance:

 

IJack* pIJ = NULL
HRESULT hr = CoCreateInstance(CLSID_IJack, NULL,
    CLSCTX_INPROC_SERVER, IID_IJack,
    reinterpret_cast<void**>(&pIJ));
if (FAILED(hr)) MessageBox("Ouch!")

("reinterpret_cast < type-id > ( expression )" is a C++ specific function that simply lets you cast anything to anything. Its intrinsically unsafe, but useful!)

 

When COM recieves the CoCreateInstance call it looks up the passed CLSID in the HKEY_CLASSES_ROOT\CLSID\ "directory" of the registry. This can hold many entries, but of key interest are:

 

Key Example value  
InprocServer32\(Default) "E:\MyStull\MyDll.DLL" Location of local in-process DLL server
InprocServer32\ThreadingModel "Apartment" ?Threading model
ProgID\(Default) "DJB.MyStuff.Mything" String used in languages such as VFP and VB in commands such as CreateObject.
LocalServer32\(Default) "E:\PROGRA~1\MICROS~2\Office\EXCEL.EXE /automation" Location of local out-of-process EXE server

 

These registry entries are normally created by the server itself. Such servers mark themselves with the string "OleSelfRegister" in their version resource. Self-registering EXEs support -RegServer and -UnRegServer CLI arguments. Self-registering DLLs support (or should that be export!) DDLRegisterServer() and DDLUnregisterServer() functions which can be called by an external program such as RegSvr32.exe.

The absolute minimum amount of registry entries for a class is the InprocServer32\(Default) for DLLs and  LocalServer32\(Default) for EXEs. Everything else is optional.

 

ProgIDs can be converted to and from CLSIDs by the ProgIDFromCLSID() and CLSIDFromProgID() functions. Note that ProgIDs have considerable detail not covered here, including entries in their own right directly under HKEY_CLASSES_ROOT.

Using DLL and EXE servers

Once COM has located the server through the registry what happens next depends on whether it is an in-process DLL or and out-of-process EXE. In either case the result is an object for creating objects of the passed CLSID, not an object of that CLSID itself. These class objects are often called Class Factories.

 

All COM DLL servers support DllGetClassObject which COM simply calls as follows:

 

STDAPI DllGetClassObject(
    REFCLSID rclsid, The CLSID of the object it (eventually) wants instantiated
    REFIID riid, The IID for the requested interface. This is normally IClassFactory
    LPVOID* pvv Memory location to receive a pointer to the requested interface on the newly created object.
);

 

All COM EXE servers have to pre-register the COM objects they support by calling actually creating all the "class factory" objects in advance, registering them with CoRegisterClassObject and then passing IUnknown pointers for the "class factory" objects to the OS. When the EXE shuts down it  calls  CoUnregisterClassObject for each "class factory" object it previously registered.

Class Factories

Simplified, the IClassFactory interface looks like this:

 

interface IClassFactory : IUnknown {
    HRESULT CreateInstance (
        [in, unique] IUnknown *pUnkOuter, More aggregation stuff
        [in] REFIID riid, The requested interface
        [out, iid_id(riid)] void ** ppvObject The returned pointer to interface
    )
    HRESULT LockServer (
        [in] BOOL fLock);
See section on Locking for details
}

 

Note the complete lack of a CLSID parameter to CreateInstance. This is because a class factory (which was created using a specific CLDID) cannot be used to create objects of a different class than its own.

CreateInstance:

  1. Creates a new (but initialised) instance of the object

  2. Calls QueryInterface on the new object, passing riid and ppvObject as the parameters.

  3. Is what is called by the CoCreateInstance function (via CoGetClassObject) right back at the start. Note that it is only worth calling CoGetClassObject directly if (a) you are creating multiple objects from the same class (CoCreateObject may create and delete the class factory object with each call) or (b) the class factory uses some interface other than IClassFactory.

 

Interfaces

Conceptually the same as in Java (i.e. defines values in & out, expected behaviour but not implementation details)

One of the points of COM is that you cannot get a reference to a COM object itself, only to its interfaces.

Uses a C++ style v-table. Thus:

All COM objects implement the IUnknown interface, which is always the first interface in the "vtable" and contains (in order?):

IUnknown's GUID is the reasonably distinctive {00000000-0000-0000-C000-0000000000000046}

All interfaces are derived from IUnknown. Because of this, IUnknown is not normally shown on COM interface diagrams.

 

Interface IDs do not have to be recorded in the registry (as they are obviously hard coded in the server and client), but doing so has certain advantages. For example, the oleview.exe tool works by calling QueryInterface for every interface registered in the registry. If the interface is not registered it will not appear in oleview.exe.

IDL (Interface Definition Language)

IDL is compiled by MIDL to generate type-libraries, header containing useful constants such as interface GUIDs, partial class definitions (to be completed by you) and C++ source files for proxy stubs that allow the COM object to be run in-process (DLL), out-of process (EXE) and remotely (RPC/EXE).

 

IDL consists of two blocks per interface. The first block uses square brackets and tells the compiler that we are defining a COM interface and the GUID that should be used for that interface:

 

[  
   local,  
  object,  Tells IDL its a COM interface rather than an RPC interface
   uuid(041AE4F1-454D-11d4-A452-009027A54115),   GUID identifier to be used for this interface (note the lack of curly braces)
  pointer_default(unique)
 
]

This block is then followed by the interface definition proper:

 

interface IDavesInterface Name of the interface. 
 
   HRESULT QueryInterface(
        [in] REFIID riid,
        [out, iid_is(riid)] void ** ppvObject);
    ULONG AddRef();
    ULONG Release();
    HRESULT MyFunction(
        [in] long myval,
        [out] long myresult);
}
Interface function definitions. Very similar to a function prototype except values in and out are marked with [in] and [out]. Functions that modify a parameter can mark these as [in, out].

Note that the interface functions are defined in vtable order so the first three are effectively IUnknown. We could (and should) have imported the stdole32.tlb file and declared IDavesInterface as 
    interface IDavesInterface :  public IUnknown
and left the first three functions out of the definition

Example IDL File

// Dave_IDL.idl  
import "oaidl.idl"; Bring in standard types, such as HRESULT
    [
        object,
        uuid(0BD7CDF0-4551-11d4-A452-009027A54115)
    ]
Definition of one interface.
This guid is an Interface ID (IID) which will become the IID_IJack constant defined in Dave_IDL_i.c
    interface IJack : IUnknown { HRESULT Test();};
   
    [
        object,
        uuid(3C9DB700-4551-11d4-A452-009027A54115)
    ]
Definition of another interface.
This guid is also an Interface ID (IID) which will become the IID_IClavdivs constant defined in Dave_IDL_i.c
    interface IClavdivs : IUnknown { HRESULT DisplayStuff();};
   
[
    uuid(041AE4F1-454D-11d4-A452-009027A54115),
    version(1.0)
]
UIID for the library
library DaveComponentLib { Start definition of the library
    importlib("stdole32.tlb");  
    [
        uuid(041AE4F2-454D-11d4-A452-009027A54115)
    ]
UIID for the first (and only) object in the library. This is the CLSID and is defined in Dave_IDL_i.c
    coclass DaveComponent {
        [default] interface IJack;
        interface IClavdivs;
    }
Final proof than an object is defined by its interfaces!
} End of library definition

HRESULT

Most COM results are return a HRESULT. You should not compare HRESULT against a specific success (S_OK) or failure (E_FAIL) code, but use the SUCCESS(..) and FAILED(...) macros as there are many difference success and failure codes. Even if you think you know all the codes an interface can return be aware that Windows can add to the error list - for example under RPC if the remote PC can't be reached.

The HRESULT_FROM_WIN32() macro can be used to embed a Win32 error code in a HRESULT. The HRESULT_CODE() macro can be user to retrieve such an embedded code. 

COM Macros

Macro Expansion
interface struct
pure =0
STDMETHOD(method_name) __stdcall virtual HRESULT method_name
STDMETHOD_(return_type, method) __stdcall virtual return_type method_name
STDMETHODIMP function __stdcall HRESULT function
STDMETHODIMP_(type) function __stdcall HRESULT type function
STDAPI extern "C" HRESULT __stdcall

 

Thus the code to produce the vtable for IUnknown:

 

class IUnknown {
    public:
        __stdcall virtual HRESULT QueryInterface(REFIID riid, void** ppvObject) =0;
        __stdcall virtual HRESULT AddRef() = 0;
        __stdcall virtual HRESULT Release() = 0;
}

become the much more readable:

interface IUnknown {
    STDMETHOD(QueryInterface) (REFIID riid, void** ppvObject) PURE;
    STDMETHOD_(ULONG, AddRef) PURE;
    STDMETHOD_(ULONG, Release) PURE;

Note that although STDMETHOD and STDMETHOD_ can be used in class definitions (i.e. in header files) you must use STDMETHODIMP and STDMETHODIMP_ in the actual implementations (i.e. in the cpp files). For example:

 

    class test {

        STDMETHOD(QueryInterface) (REFIID riid...) PURE;

        STDMETHOD_(ULONG, AddRef) PURE;
        STDMETHOD_(ULONG, Release) PURE;
    }

 

would have QueryInterface implemented as 

 

    STDMETHODIMP Test::QueryInterface(REFIID ...) {...};

    STDMETHODIMP_(ULONG) Test::AddRef() {...};

    STDMETHODIMP_(ULONG) Test::Release() {...};

Implementing IUnknown

This is best shown through commented code:

 

#include "Windows.h"
#include "Dave_IDL.h" Bring in the MIDL generated header file
class CDaveComponent: public IJack, 
     public IClavdivs {
Since both interfaces contain IUnknown as the first three entries in the vtable, it doesn't matter which interface the compiler places at the beginning of the vtable. Thus we can use multiple inheritance. 
  private:
    long m_lRefCount; The object reference count
 
  public:
    CDaveComponent() : m_lRefCount(0)
       {};
Constructor initialises reference count to zero
     ~CDaveComponent() {}; Destructor does nothing
 
    // The IUnknown Methods
    STDMETHOD(QueryInterface) (REFIID riid, void** ppv) {
        if (riid == IID_IUnknown)
            *ppv = static_case<IUnknown>
                        (static_cast<IJack*> (this));
Multiple inheritance means the compiler cannot tell which IUnknown interface (the one in IJack or the one in JClavdivs) it should cast to without us supplying the path.

We could stop casting at IJack because the outer case doesn't change the pointer value (the inner one could) and a pointer to IJack is, in effect, a pointer to IJacks copy of  IUnknown. 
       else if (riid == IID_IJack)
            *ppv = static_cast<IJack*> (this);
       else if (riid == IID_IClavdivs)
            *ppv = static_cast<IClavdivs*> (this);
       else {
            *ppv = NULL;
            return E_NOINTERFACE;
       } 
We have been asked for an interface we do not support, tell the caller so.
 
    static_cast<IUnknown*>(*pvv)->AddRef(); A successful request for an interface should increase the interface count.
 
    }
 
    STDMETHOD_(ULONG, AddRef) () {
        return InterlockedIncrement(&m_lRefCount) Increment the reference count by one in a thread-safe manner.
    }
 
    STDMETHOD_(ULONG, Release) () {
        ULONG uTemp;
        uTemp = InterlockedDecrement(&m_lRefCount);
Decrease the reference count by one in a thread-safe manner.
        if (uTemp == 0) delete this; Nobody is using the object any more, delete it.
        return uTemp; Return the buffered value (as if we've deleted the object m_lRefCount is no longer valid)
    }
 
// IJack interface methods
    STDMETHOD(Test) () {
        MessageBox(NULL, "Hello World!", "IJack:Test",
            MB_OK);
        return S_OK;
    }
 
// IClavdivs interface methods
    STDMETHOD(DisplayStuff) () {
        MessageBox(NULL, "STUFF!",
            "IClavdivs:DisplayStuff",
            MB_OK);
        return S_OK;
    }
 
}

 

 

Locking

IClassFactory::LockServer(BOOL) should be implemented such that a usage count is incremented by one when TRUE is passed and decremented by one when FALSE is passed. When the reference count falls to zero the class object can be destroyed.

 

If the server is a DLL then it must also implement the function STDAPI DllCanUnloadNow() (note this is a exported function from the DLL and not part of an interface). The client should call CoFreeUnusedLibraries which in turn calls DllCanUnloadNow so that no libraries that should not be unloaded are unloaded. If the client fails to call CoFreeUnusedLibraries the libraries will not be released until CoUnitialize is called.

 

If the server is an EXE things are simpler. When both the usage count and the LockServer count fall to zero the EXE simply exits. Class factories are excluded from this usage count (else the server would never release) so it is necessary to specifically call LockServer(TRUE) when using a COM EXE to prevent it immediately being released.

 

Utils

GUID can be created with the "...\Microsoft Visual Studio\Common\Tools\guidgen.exe" applet.

oleview.exe listed the (registered) interfaces for a class and allows basic creation and release of objects.

 

References:
Beginning ATL COM Programming, Grimes, Stockton, Reilly & Templeman Wrox Press ISBN 1-861000-11-1