Recall that the SCM supports three activation primitives (binding to class objects, binding to class instances, binding to persistent instances from files). As shown in Figure 3.2, these primitives are layered upon one another logically.5 The lowest-level primitive is to bind to a class object. This primitive is also the simplest to understand.
Instead of manually loading the class’s code, clients use the services of the SCM via the low-level COM API function CoGetClassObject. This function asks the SCM to bind a pointer to the requested class object:
HRESULT CoGetClassObject(
[in] REFCLSID rclsid, // which class object?
[in] DWORD dwClsCtx, // locality?
[in] COSERVERINFO *pcsi, // host/security info
[in] REFIID riid, // which interface?
[out, iid_is(riid)] void **ppv); // put it here!
The first parameter to CoGetClassObject indicates which implementation class is desired. The last parameter is a reference to the interface pointer to be bound, and the fourth parameter is simply the IID that describes the type of interface pointer referred to by the last parameter. The more interesting
Figure 3.2 Activation Primitives
parameters are the second and third parameters, which determine where the class object should be activated.
CoGetClassObject takes as its second parameter a bitmask that allows the client to indicate the desired latency and robustness characteristics of the object (i.e., whether the object should run in process, out of process, or on a different host machine). The valid values for this bitmask are defined in the standard CLSCTX enumeration:
enum tagCLSCTX { CLSCTX_INPROC_SERVER = 0x1, // run inprocess CLSCTX_INPROC_HANDLER = 0x2, // see note
6CLSCTX_LOCAL_SERVER = 0x4, // run out-of-process CLSCTX_REMOTE_SERVER = 0x10 // run off-host } CLSCTX;
These flags can be bitwise-ORed together, and when more than one requested CLSCTX is available, COM will select the most efficient type of server (which means that COM will use the least significant bit of the bitmask when possible). The SDK header files also include several shortcut macros that combine several CLSCTX flags used in many common scenarios:
#define CLSCTX_INPROC (CLSCTX_INPROC_SERVER|\
CLSCTX_INPROC_HANDLER)
#define CLSCTX_SERVER (CLSCTX_INPROC_SERVER|\
CLSCTX_LOCAL_SERVER|\
CLSCTX_REMOTE_SERVER)
#define CLSCTX_ALL (CLSCTX_INPROC_SERVER|\
CLSCTX_INPROC_HANDLER|\
CLSCTX_LOCAL_SERVER|\
CLSCTX_REMOTE_SERVER)
Note that environments such as Visual Basic and Java always use CLSCTX_ALL, indicating that any available implementation will suffice.
The third parameter to CoGetClassObject is a pointer to a structure that contains remoting and security information. This structure is of type COSERVERINFO and allows clients to specify explicitly which machine to activate the object at as well as how to configure the security settings used to make the object activation request:
typedef struct _COSERVERINFO {
DWORD dwReserved1; // reserved, must be zero
LPWSTR pwszName; // desired host name, or null
COAUTHINFO *pAuthInfo; // desired security settings
DWORD dwReserved2; // reserved, must be zero
} COSERVERINFO;
If the client does not specify a host name but uses only the CLSCTX_
REMOTE_SERVER flag, then COM will use per-CLSID configuration information to determine which machine should be used to activate the object. If the client passes an explicit host name, this host name overrides any preconfigured host names that COM may know about. If the client does not wish to pass explicit security information or a host name to CoGetClassObject, a null COSERVERINFO pointer is acceptable.
Given the availability of CoGetClassObject, the client can ask the SCM to bind an interface pointer to a class object:
HRESULT GetGorillaClass(IApeClass * &rpgc) {
// declare the CLSID for Gorilla as a GUID
const CLSID CLSID_Gorilla = { 0x571F1680, 0xCC83, 0x11d0,
{ 0x8C, 0x48, 0x00, 0x80, 0xC7, 0x39, 0x25, 0xBA } };
// call CoGetClassObject directly
return CoGetClassObject(CLSID_Gorilla, CLSCTX_ALL, 0,
IID_IApeClass, (void**)&rpgc);
}
Note that if the requested class is available as an in-process server, COM will automatically load the corresponding DLL and call a well-known exported function that returns a pointer to the desired class object. COM then returns that pointer to provide the client with direct accesses to the desired class object.7 Once the call to CoGetClassObject is completed, the COM library and the SCM are completely out of the picture. Had the class been available only from an out-of-process or remote server, COM would instead have returned a proxy that would allow the client to access the class object remotely.
Recall that the IApeClass interface is designed to allow clients to find or create instances of a given class. Consider the following example:
HRESULT FindAGorillaAndEatBanana(long nGorillaID) {
IApeClass *pgc = 0;
// find the class object via CoGetClassObject
HRESULT hr = CoGetClassObject(CLSID_Gorilla, CLSCTX_ALL,
0, IID_IApeClass, (void**)&pgc);
if (SUCCEEDED(hr)) {
IApe *pApe = 0;
// use the class object to find an existing gorilla
hr = pgc->GetApe(nGorillaID, &pApe);
if (SUCCEEDED(hr)) {
// tell the designated gorilla to eat a banana
hr = pApe->EatBanana();
pApe->Release();
}
pgc->Release();
}
return hr;
}
This example uses the class object for Gorilla to find a named object and inform it to eat a banana. For this example to work, some external agent must have provided the caller with the name of a well-known gorilla. If, instead, any anonymous gorilla could be used to satisfy the request, the following example would suffice:
HRESULT CreateAGorillaAndEatBanana(void) {
IApeClass *pgc = 0;
// find the class object via CoGetClassObject
HRESULT hr = CoGetClassObject(CLSID_Gorilla, CLSCTX_ALL,
0, IID_IApeClass, (void**)&pgc);
if (SUCCEEDED(hr)) {
IApe *pApe = 0;
// use the class object to create a new gorilla
hr = pgc->CreateApe(&pApe);
if (SUCCEEDED(hr)) {
// tell the new gorilla to eat a banana
hr = pApe->EatBanana();
pApe->Release();
}
pgc->Release();
}
return hr;
}
Note that except for the particular IApeClass method used, the two examples are the same. Because class objects can export arbitrarily complex interfaces, they can be used to model fairly sophisticated object activation, initialization, and location policies.
5 This layering is largely conceptual, as the COM library and wire-protocol implement each primitive as a distinct code path and packet format.
6 In-process handlers are largely a holdover from OLE Documents. In-process handlers are in-process components that act as the client-side representation of an object that actually resides in a different process. Handlers are used in OLE Documents to cache renderings at the client in order to reduce IPC traffic when redrawing the screen. Although handlers make sense in the general case, they have not been widely deployed outside the context of OLE Documents. Windows NT 5.0 will provide additional facilities for implementing handlers, but the details of how this will be accomplished were sketchy at the time of this writing.
7 Technically, the concurrency requirements of the class must be compatible with those of the calling thread.