In this section we will look at what a server needs to do to
implement a class object and expose it to the COM runtime system. In
order to do so, we will first focus on the simplest case - a DLL
server. After that, we will look at the differences in technique for
an EXE server.
Self-registration
The SCM uses a persistent database of CLSID-to-server filename
mappings. It uses the registry. We saw earlier the entries needed by
the SCM for loading DLL and EXE servers. Let's formalize this.
The minimum entries we need for a server to work are as follows:
- A mapping from the CLSID to the server
- A mapping from the ProgID to the CLSID
- A mapping from the CLSID to the ProgID
A ProgID is short for a programmatic ID that is a text-based
alias used in CLSID-challenged environments, such as Visual Basic,
where programmers cannot remember CLSIDs for fun. :-) They are not
strictly necessary in C++. COM APIs CLSIDFromProgID() and ProgIDFromCLSID() can be used to deduce one from
the other.
Here is a diagram of the information we need.

It is up to the server itself to put this information in the
registry. In-process servers support self-registration by exporting
two well-known entry-point functions.
- DllRegisterServer()
- Called to instruct the server to put its details in the
registry.
- DllUnregisterServer()
- Called to instruct the server to take its details out of the
registry.
These functions are generally called by some registration tool
(such as regsvr32.exe that comes with Microsoft Visual C++), by a
product installation procedure or by the COM runtime system itself
(such as when the component download service pulls a component down
across the network onto a local client machine).
A typical implementation of these routines will be data-driven,
perhaps via some kind of script file or a table. The data merely
defines the keys and values that need to go in the registry. The
logic that parses the data will just use registry APIs, such as RegCreateKeyEx() RegOpenKey() RegDeleteKey() RegSetValueEx() and so on, in order to add and
remove the keys and values from the registry.
Here is a table-driven implementation of DllRegisterServer() and
DllUnregisterServer() and the minimum set of entries we require.
TCHAR g_szFileName[MAX_PATH];
BOOL WINAPI DllMain(HINSTANCE h, DWORD
dwReason, void *) { if (dwReason ==
DLL_PROCESS_ATTACH)
GetModuleFileName(h, g_szFileName, MAX_PATH);
return TRUE; }
struct REG_DATA {
const TCHAR *pszKeyName; const TCHAR
*pszValueName; const TCHAR
*pszValue; BOOL
bDeleteOnUnregister; };
const REG_DATA g_rgEntries[] =
{
{__TEXT("CLSID\\{E1C1F25E-8BF1-11D2-AAC8-006008449275}"),
0,__TEXT("Snafu Class"),TRUE},
{__TEXT("CLSID\\{E1C1F25E-8BF1-11D2-AAC8-006008449275}\\InprocServer32"),0,g_szFileName,TRUE},
{__TEXT("CLSID\\{E1C1F25E-8BF1-11D2-AAC8-006008449275}\\ProgID"),0,__TEXT("SnafuSrv.Snafu.1"),TRUE},
{__TEXT("SnafuSrv.Snafu"),0,__TEXT("Snafu"),TRUE},
{__TEXT("SnafuSrv.Snafu\\CLSID"),0,__TEXT("{E1C1F25E-8BF1-11D2-AAC8-006008449275}"),TRUE} };
const
int g_cEntries =
sizeof(g_rgEntries)/sizeof(*g_rgEntries);
static HRESULT
Unregister(const REG_DATA *rgEntries, int cEntries)
{ BOOL bSuccess = TRUE;
for (int i = cEntries - 1; i >= 0; i--)
{ if
(rgEntries[i].bDeleteOnUnregister)
{
LONG err =
RegDeleteKey(HKEY_CLASSES_ROOT,rgEntries[i].pszKeyName);
if (err !=
ERROR_SUCCESS)
bSuccess =
FALSE;
} } return bSuccess ?
S_OK : S_FALSE; }
static HRESULT Register(const REG_DATA
*rgEntries, int cEntries) { BOOL bSuccess =
TRUE; const REG_DATA *pEntry =
rgEntries; while (pEntry < rgEntries +
cEntries) { HKEY
hkey; LONG err =
RegCreateKey(HKEY_CLASSES_ROOT, pEntry->pszKeyName,
&hkey); if (err ==
ERROR_SUCCESS)
{
if
(pEntry->pszValue)
err = RegSetValueEx(hkey, pEntry->pszValueName, 0, REG_SZ, (const
BYTE*)pEntry->pszValue,
(lstrlen(pEntry->pszValue) + 1) *
sizeof(TCHAR));
if (err != ERROR_SUCCESS)
{
bSuccess =
FALSE;
Unregister(rgEntries, 1 + pEntry -
rgEntries);
}
RegCloseKey(hkey);
}
if (err != ERROR_SUCCESS)
{
bSuccess =
FALSE;
if (pEntry !=
rgEntries)
Unregister(rgEntries, pEntry -
rgEntries);
}
pEntry++; } return
bSuccess ? S_OK : E_FAIL; }
STDAPI
DllRegisterServer(void) { return
Register(g_rgEntries, g_cEntries); }
STDAPI
DllUnregisterServer(void) { return
Unregister(g_rgEntries, g_cEntries); }
The DLL server must export these functions from the DLL. Here is
the canonical way of doing this via a linker module definition
file.
LIBRARY
"snafu.DLL"
EXPORTS
DllCanUnloadNow @1
PRIVATE DllGetClassObject @2
PRIVATE DllRegisterServer
@3 PRIVATE DllUnregisterServer @4
PRIVATE
Categories and self-registration
Component categories group together COM classes with the
same characteristics. They can be used to display a subset of all of
the installed components. Each component category is identified by a
GUID called a Category ID (CATID). Each CATID has a list of
locale-tagged, human-readable names associated with it. All
categories are stored in the registry as shown by the following
diagram.

COM classes express their behavior purely in terms of the
interfaces they expose. A component that implements the
functionality for OLE document embedding does so by virtue of the
fact that it supports a whole bunch of OLE document embedding
interfaces required by an OLE document container. As previously
shown, embeddable objects can be classified within a component
category.
To define a component category it is first necessary to document
what it takes for a component to qualify for the category. Next, the
following information would be added to the Registry.

Each class that implements the functionality corresponding to a
component category lists the category ID for that category within
its HKCR\CLSID\{...}\Implemented Categories key in the Registry.
One component can support a wide range of functionality so
components can belong to multiple component categories. For example,
a particular OLE control might support all of the functionality
required to participate as OLE document embedding, Visual Basic data
binding, and Internet functionality. Note that previously,
embeddable objects would have been identified by
their HKCR\CLSID\{...}\Insertable key in the Registry. Component
categories are much less ad hoc and much more extensible. This
scheme allows clients to browse by component functionality without
having to instantiate the object or have access to its type
information.
Component category information will need to be registered from
the server self-registration code. This will affect our registration
code like this.
const REG_DATA g_rgEntries[] =
{
{__TEXT("CLSID\\{E1C1F25E-8BF1-11D2-AAC8-006008449275}"),
0,__TEXT("Snafu Class"),TRUE},
{__TEXT("CLSID\\{E1C1F25E-8BF1-11D2-AAC8-006008449275}\\InprocServer32"),0,g_szFileName,TRUE},
{__TEXT("CLSID\\{E1C1F25E-8BF1-11D2-AAC8-006008449275}\\ProgID"),0,__TEXT("SnafuSrv.Snafu.1"),TRUE},
{__TEXT("SnafuSrv.Snafu"),0,__TEXT("Snafu"),TRUE},
{__TEXT("SnafuSrv.Snafu\\CLSID"),0,__TEXT("{E1C1F25E-8BF1-11D2-AAC8-006008449275}"),TRUE},
{__TEXT("Component
Categories\\{93C480F1-A0F8-11d2-AAC9-006008449275}"),__TEXT("409"),
__TEXT("Things that go wibble"),FALSE},
{__TEXT("Component
Categories\\{93C480F2-A0F8-11d2-AAC9-006008449275}"),__TEXT("409"),
__TEXT("Things that wobble"),FALSE},
{__TEXT("CLSID\\{E1C1F25E-8BF1-11D2-AAC8-006008449275}\\Implemented
Categories"),0,0,TRUE},
{__TEXT("CLSID\\{E1C1F25E-8BF1-11D2-AAC8-006008449275}\\Implemented
Categories\\{93C480F1-A0F8-11d2-AAC9-006008449275}"),0,0,TRUE},
{__TEXT("CLSID\\{E1C1F25E-8BF1-11D2-AAC8-006008449275}\\Implemented
Categories\\{93C480F2-A0F8-11d2-AAC9-006008449275}"),0,0,TRUE} };
Remember that there are no COM category police. There are no
runtime checks to validate that a component in a given category
really does adhere to the characteristics defined by that category.
Just because a developer decided to mark a component as embeddable,
it doesn't mean that the component is forced to support all the
necessary OLE document embedding interfaces. It just means that the
developer chose to include the component in that category. However,
if a component doesn't adhere to the characteristics of the category
that it advertises itself as being part of, clients will get very
upset with it and the company that sells it!
Type libraries and self-registration
We have already seen how interfaces and classes can be specified
in IDL. We have also seen that when we compile an IDL file
containing a library block we get a Type Library. A type library
contains language independent bindings usable from any language and
is available to browsers through COM APIs and system-defined
interfaces. The interfaces are ITypeLib that describes the library and ITypeInfo that describes each GUID-tagged element
within it. The APIs are LoadTypeLib() that loads a type library by fully
qualified pathname and yields an ITypeLib and LoadRegTypeLib() that loads a type library from
the type library GUID (this assumes the library is registered) and
returns an ITypeLib. Here is a diagram that illustrates the
relationship.

So, this type library describes (some of) the functionality of
the class as far as interested browsers are concerned. The type
library is advertised to COM by calling the COM API RegisterTypeLib(). It puts information in the
registry about the type library itself and certain interfaces
contained within (those that can be type library marshalled - see
the section on marshalling for more details). Many COM servers
bundle the type library as a custom resource. Here is the resource
script syntax to do this.
1 TYPELIB ˇ°snafu.tlbˇ±
The LoadTypeLib() and RegisterTypeLib() APIs can accept a .tlb
file or an .exe/.dll/.ocx server module file where the type library
has been bundled as described. After RegisterTypeLib() has been
called, the registry will look like this:

The registry entries for the interfaces are discussed in a later
section on "Standard
Marshalling."
RegisterTypeLib() is normally called from within the server
self-registration code. This amends the registration code slightly
again.
static HRESULT Unregister(const REG_DATA
*rgEntries, int cEntries) { HRESULT hr =
UnRegisterTypeLib(LIBID_SnafuLib,1,0,0,SYS_WIN32);
BOOL bSuccess = SUCCEEDED(hr); for (int i =
cEntries - 1; i >= 0; i--)
{ if
(rgEntries[i].bDeleteOnUnregister)
{
LONG err =
RegDeleteKey(HKEY_CLASSES_ROOT,rgEntries[i].pszKeyName);
if (err !=
ERROR_SUCCESS)
bSuccess = FALSE;
} } return bSuccess ?
S_OK : S_FALSE; }
static HRESULT Register(const REG_DATA
*rgEntries, int cEntries) { BOOL bSuccess =
TRUE; const REG_DATA *pEntry =
rgEntries; while (pEntry < rgEntries +
cEntries) { HKEY
hkey; LONG err =
RegCreateKey(HKEY_CLASSES_ROOT, pEntry->pszKeyName,
&hkey); if (err ==
ERROR_SUCCESS)
{
if
(pEntry->pszValue)
err = RegSetValueEx(hkey, pEntry->pszValueName, 0, REG_SZ, (const
BYTE*)pEntry->pszValue,
(lstrlen(pEntry->pszValue) + 1) *
sizeof(TCHAR));
if (err != ERROR_SUCCESS)
{
bSuccess =
FALSE;
Unregister(rgEntries, 1 + pEntry -
rgEntries);
}
RegCloseKey(hkey);
}
if (err != ERROR_SUCCESS)
{
bSuccess =
FALSE;
if (pEntry !=
rgEntries)
Unregister(rgEntries, pEntry -
rgEntries);
}
pEntry++; } if
(!bSuccess) return
bSuccess ? S_OK : E_FAIL; ITypeLib *ptl =
0; HRESULT hr = LoadTypeLib(g_szFileName,
&ptl); if
(SUCCEEDED(hr))
{ hr =
RegisterTypeLib(ptl, g_szFileName,
0);
ptl->Release(); }
return hr; }
Implementing a class object
Really a class object is just a COM object like any other. At a
minimum it supports IUnknown and IClassFactory, but possibly also
other custom creation interfaces. All that the creation interfaces
need to do is create an associated coclass instance and return the
required interface reference on it. Here is a simple example:
class SnafuClassObject : public
IClassFactory, public ISnafuFactory {
public: // IUnknown methods deleted for
clarity ....
STDMETHODIMP CreateInstance(IUnknown *pUnkOuter, REFIID riid, void
**ppv) { *ppv =
0; // If the first
parameter is not NULL then this object is required to be part of a
bigger aggregate object. // For the sake of simplicity we don't
support this and so we return an
error. if (pUnkOuter
!= 0) return
CLASS_E_NOAGGREGATION;
Snafu *pobj = new
Snafu; if (!pobj)
return E_OUTOFMEMORY;
// Stabilize the reference
count pobj->AddRef(
); HRESULT hr =
pobj->QueryInterface(riid,
ppv); // Destabilize
the reference count
pobj->Release( );
return hr; } //
remaining IClassfactory method deleted for
clarity ....
STDMETHODIMP CreateSnafu(BSTR bstrName, IName **ppName)
{ *ppName =
0; Snafu *pobj = new
Snafu(bstrName); if
(!pobj) return
E_OUTOFMEMORY; //
Stabilize the reference
count pobj->AddRef(
); HRESULT hr =
pobj->QueryInterface(IID_IName,
ppName); //
Destabilize the reference
count
pobj->Release( );
return hr; } //
remaining members deleted for clarity
.... };
Notice the use of QueryInterface() on the created object. What
better way to figure out if the object supports an interface and, if
it does, for its reference count to be correct? Although the class
object and the object it creates are coupled, there is no sense in
having them more tightly coupled than they need be.
Notice also the matching AddRef() and Release() around the
initial QueryInterface() on the object. This is called 'reference
count stabilization.' It artificially bumps up the object's
reference count to 1 before the initial QueryInterface(). This
ensures the following:
- That the object's reference count doesn't transition from 0 to
1 to 0 during the initial (ultimately successful) QueryInterface()
and cause the object to terminate prematurely.
- That the object will delete itself naturally if the initial
QueryInterface() fails. We cannot be left with a zombie object
that nobody has any references to. The Release() after
QueryInterface() is the neatest way of clearing up if
QueryInterface() fails because it will transition the object
reference count from 1 back to 0. If the initial QueryInterface()
is successful, the Release() will put the reference count from 2
back to 1.
Exposing in-proc class objects
The SCM 'pulls' class objects from a DLL server through a
well-known, exported entry-point function called DllGetClassObject(). This function is called by
the SCM whenever it needs access to the class object for a given
CLSID, i.e., whenever a client calls
CoCreateInstance()/CoCreateInstanceEx() or CoGetClassObject().
Class objects are static in nature. That is, they need to be
around for the lifetime of the DLL in order to create objects. Quite
often (although not necessarily) they will be allocated statically.
Here is an example:
SnafuClassObject
g_coSnafu; FubbClassObject g_coFubb;
STDAPI
DllGetClassObject(REFCLSID rclsid, REFIID riid, void**ppv)
{ if (rclsid ==
CLSID_Snafu) return
g_coSnafu.QueryInterface(riid, ppv); else if
(rclsid == CLSID_Fubb)
return g_coFubb.QueryInterface(riid, ppv);
*ppv = 0; return
CLASS_E_CLASSNOTAVAILABLE; }
Notice again the use
of QueryInterface() on the class object as a great way to figure out
if the class object supports a creation interface and, if it does,
for its reference count to be correct.
Again, the DLL server must export this function from the DLL.
Here is the linker module definition file.
LIBRARY
"snafu.DLL"
EXPORTS
DllCanUnloadNow @1
PRIVATE DllGetClassObject
@2 PRIVATE
DllRegisterServer @3 PRIVATE
DllUnregisterServer @4 PRIVATE
Here is an animation
that shows what happens when the client calls
CoGetClassObject().
Here is another animation
that shows what happens when the client calls
CoCreateInstance().
Unloading class code
COM objects in a DLL come and go. When there are no more
outstanding references to any objects (or class objects) in the DLL,
there is no need for the DLL to stay around. Remember that DLLs are
passive. They cannot decide to unload themselves when they detect
that the last object reference has gone away. There is no background
garbage collector in the COM runtime. By default, class code will
stay loaded until the last call to CoUninitialize() in the process.
That aside, the only way that a long running client can force
garbage collection of unwanted DLLs is to call the COM runtime API
CoFreeUnusedLibraries(). This will force COM to
walk the list of COM DLL servers it has loaded and ask them if they
can be unloaded. It does this by calling a well-known entry point
routine, DllCanUnloadNow(). This function has to return
S_OK if the DLL can be unloaded and S_FALSE if it cannot. So how
does it decide? Normally, the DLL keeps a module 'lock count' whose
value will decide. The value of the module lock count is based
on external references that have been given out and are still held.
Let's look closer.
A heap-based object will keep a reference count. With such an
object it is possible to determine when the object reference count
transitions from 0 to 1 when the first external reference is given
out, then later, from 1 down to 0 when the last external reference
is released. Such an object would affect the reference count in the
following way.
LONG g_cLocks = 0;
STDAPI DllCanUnloadNow( )
{ return g_cLocks ? S_FALSE :
S_OK; }
//Thread-safe lock/unlock void
ModuleLock( ) { InterlockedIncrement(&g_cLocks); } void
ModuleUnlock( ) { InterlockedDecrement(&g_cLocks); }
STDMETHODIMP_(ULONG) Snafu::AddRef( ) {
if (m_cRef == 0) // lock at first
AddRef ModuleLock(
); // thread-safe object reference
count return
InterlockedIncrement(&m_cRef); }
STDMETHODIMP_(ULONG) Snafu::Release( )
{ // thread-safe object reference
count LONG r =
InterlockedDecrement(&m_cRef); if (r ==
0) { delete
this; ModuleUnlock();
// unlock at last Release
} return r; }
A non heap-based object does not need to keep a reference count.
After all, what is it going to do when the reference count goes down
to 0? Delete itself? No! With such an object it is not possible to
determine when the first object reference is given out or the last
object reference released. It was mentioned earlier that a class
object is static in nature and is commonly implemented as a non
heap-based object. Such a class object in a DLL would affect the
module lock count in the following way by locking/unlocking on every
AddRef()/Release().
STDMETHODIMP_(ULONG)
SnafuClassObject::AddRef( ) {
ModuleLock(); return 2; }
STDMETHODIMP_(ULONG)
SnafuClassObject::Release( ) {
ModuleUnlock(); return 1; }
There is one other scenario where the lock count is affected and
that is in the IClassfactory::LockServer() implementation that is
used by the client to lock the server into memory even when there
are no outstanding references. The use of this method really only
makes sense for EXE servers (if it makes sense at all!), so we shall
talk about it later on when we look at EXE servers. For now though,
here is the code.
STDMETHODIMP
SnafuClassObject::LockServer(BOOL bLock) { if
(bLock)
ModuleLock();
else
ModuleUnlock(); return S_OK; }
Yet again, the DLL server must export this function from the DLL.
Here is the linker module definition file.
LIBRARY
"snafu.DLL"
EXPORTS
DllCanUnloadNow @1
PRIVATE DllGetClassObject
@2 PRIVATE DllRegisterServer @3
PRIVATE DllUnregisterServer @4
PRIVATE
Here we are going to do a lab that illustrates the plumbing needed by
a DLL server in order to expose coclasses.
Next, we will look at COM
Apartments.

|