<< Contents >>
Jargon
Initialising COM
Using Existing COM Objects
Interfaces
IDL
HRESULT
COM Macros
COM Utilities
References
Example Code
coclasses | Fancy term for components |
CLSID | Class ID. Identifies a coclass |
IID | Interface ID |
Hive | One of the HKEY_* top level registry keys |
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
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.
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.
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:
Creates a new (but initialised) instance of the object
Calls QueryInterface on the new object, passing riid and ppvObject as the parameters.
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.
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:
The order of methods within the interface is of vital
importance
This is not normally a problem when coding as the definition of an interface
is so closely related to the implementation of that class that changes flow
through. Later interfaces can be "sub-classed" from earlier ones
so that this order is preserved even for interfaces not originally defined
by me.
Form a binary standard, not a language specific one
Cannot be multiply inherited in C++ as no way to tell which interface would be at the front of the list.
All COM objects implement the IUnknown interface, which is always the first interface in the "vtable" and contains (in order?):
QueryInterface
Returns a pointer to the requested interface or ?? on error.
Automatically calls QueryRef for that interface on success.
When called for IUnknown, QueryInterface must return the same value
whenever it is called for the same object as this is the only way to compare
objects (in COM we can never get a reference to an object, only to its
interfaces). If the object implements multiple QueryInterface functions
(i.e. has multiple interfaces) they must all return the same value
for the same object when called for IUnknown.
Once QueryInterface says it supports a particular interface for that object
it must continue to support that interface for that object until the object
is destroyed. It is not under any obligation to support that interface for
any other object.
AddRef
Increments a usage reference counter by one. In theory because all
interfaces are derived from IUnknown unlike QueryInterface each interface
could implment its own version of AddRef and therefore implement
per-interface reference counters. In practice its much easier to point all
QueryInterface, AddRefs and Releases to the same block of code and implement
an all-interface reference count. Although AddRef returns a reference count,
it cannot be trusted due to behind the scenes optimisations.
Release
Opposite of AddRef. When the reference count reaches zero the object
should destoy itself. As per AddRef, Release returns a reference count than
cannot be trusted.
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 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
// 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.cinterface 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.cinterface 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
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.
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() {...};
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; |
|
} | |
} |
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.
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