| |
COM supports two paradigms with regard to clients creating
objects directly.
- Allow the client to instruct the COM runtime to create a new
object. This is done using the CoCreateInstance()/CoCreateInstanceEx() COM API functions. The
client passes in the CLSID of the required coclass, the module
location (e.g., DLL, EXE) and the interface it requires back on
the new object. These APIs will cause the COM Service Control
Manager (SCM) to load the server if it needs to, obtain the class
object for the requested CLSID, and use its IClassFactory
interface to create an object instance. This is fast but
inflexible.
- Allow the client to obtain the class object for a particular
CLSID. This is done using the CoGetClassObject() COM API function. The client
passes in the CLSID of the required coclass, the module location
(e.g., DLL, EXE) and the interface it requires back on the class
object. These APIs will cause the COM Service Control Manager
(SCM) to load the server if it needs to, obtain the class object
for the requested CLSID and pass it back to the client. Clients
can then use any custom interface that it knows about and that the
class object implements in order to create new objects in
non-default ways or find existing objects. An example of creating
objects in non-default ways might be to pass initialization data
through to the object at creation time, i.e., 'non-default
constructor interfaces'. This is flexible, but somewhat slower
than 1 (we will see why later).
In fact, there is a third paradigm that involves the client using
a 'finder object' to create or locate the object. This finder object
is a called moniker and simply encapsulates the binding algorithm
used to create or locate the object. As long as the client gives it
enough information to bind to the object then the client is
blissfully unaware of the process. Monikers are very powerful. So
powerful that we are not going to blow our minds with them right
now!
Let's look at both mechanisms available to the client. In order
to do so, we will first focus on the simplest case - a client
creating a DLL-based object. In fact, it doesn't make much
difference to the client whether it is dealing with a DLL or EXE
server.
| Creating a new object with CoGetClassObject()
and a custom interface |
Let's consider the following custom interface that is implemented
by our class object. We can think of this interface as being
- A non-default constructor interface for our object that allows
us to create an object with a specific name
- A collection of previously created named objects that we can
use to find an object by name.
[uuid(E1C1F253-8BF1-11D2-AAC8-006008449275), helpstring("ISnafuFactory
Interface"), pointer_default(unique)] interface ISnafuFactory
: IUnknown { [helpstring("method
CreateSnafu")] HRESULT CreateSnafu([in]BSTR bstrName, [out,
retval]IName **ppName); [helpstring("method
FindSnafu")] HRESULT FindSnafu([in]BSTR bstrName, [out, retval]IName
**ppName); };
The client could write the following code to create a new named
object by passing initialization data to the object at creation
time.
HRESULT CreateSnafuByName(BSTR bstrName,
IName **ppName) { ISnafuFactory *pSF =
NULL; const CLSID CLSID_snafu =
{0xE1C1F25E,0x8BF1,0x11D2,{0xAA,0xC8,0x00,0x60,0x08,0x44,0x92,0x75}};
const IID IID_ISnafuFactory =
{0xE1C1F253,0x8BF1,0x11D2,{0xAA,0xC8,0x00,0x60,0x08,0x44,0x92,0x75}};
HRESULT hr = CoGetClassObject(CLSID_snafu,
CLSCTX_INPROC_SERVER, NULL, IID_ISnafuFactory, (void
**)&pSF ); if (SUCCEEDED(hr))
{ hr =
pSF->CreateSnafu(bstrName,
ppName);
pSF->Release(); }
return hr; }
Notice the use of the CLSCTX enumeration to define that we were
interested in loading a DLL-based version of the snafu coclass.
| Creating a new object with CoGetClassObject()
and the IClassFactory interface |
As mentioned before, class objects can support any custom
interface they like for creating objects. We have just seen an
example of this. These can be thought of as 'non-default
constructor' interfaces. They can be used to create objects in
specialist ways. The client has to have an intimate knowledge of
these specialist creation interfaces. The COM runtime system
certainly does not know about them. Because of this, there has to be
a 'default constructor' interface that the system or any client
knows about that defines the minimum requirement for creating a new,
virgin object. This interface is called IClassFactory. Its IClassFactory::CreateInstance()
is the method we are interested in right now. The COM run-time
system or the client can use this to create a new, uninitialised
object.
The client could thus write the following code to create a new,
uninitialised object.
HRESULT CreateSnafu(IName **ppName)
{ IClassFactory *pCF =
NULL; const CLSID CLSID_snafu =
{0xE1C1F25E,0x8BF1,0x11D2,{0xAA,0xC8,0x00,0x60,0x08,0x44,0x92,0x75}};
// IClassFactory is defined in a system header
file HRESULT hr =
CoGetClassObject(CLSID_snafu, CLSCTX_INPROC_SERVER, NULL,
IID_IClassFactory, (void **)&pCF );
if (SUCCEEDED(hr)) {
hr = pCF->CreateInstance(NULL, IID_IName, (void
**)ppName);
pCF->Release(); }
return hr; }
Later, we will look at when and how the COM runtime system uses
the IClassFactory interface.
| Finding an existing object with
CoGetClassObject() and a custom interface
|
The custom interface we looked at earlier could also be used by
the client to find an existing named object.
HRESULT FindSnafuByName(BSTR bstrName,
IName **ppName) { ISnafuFactory *pSF =
NULL; const CLSID CLSID_snafu =
{0xE1C1F25E,0x8BF1,0x11D2,{0xAA,0xC8,0x00,0x60,0x08,0x44,0x92,0x75}};
const IID IID_ISnafuFactory =
{0xE1C1F253,0x8BF1,0x11D2,{0xAA,0xC8,0x00,0x60,0x08,0x44,0x92,0x75}};
HRESULT hr = CoGetClassObject(CLSID_snafu,
CLSCTX_INPROC_SERVER, NULL, IID_ISnafuFactory, (void
**)&pSF ); if (SUCCEEDED(hr))
{ hr =
pSF->FindSnafu(bstrName,
ppName);
pSF->Release(); }
return hr; }
| Class Loading in more detail: The Service
Control Manager (SCM) |
COM class loading on a given machine is performed by the COM
Service Control Manager (SCM). Do not confuse this with the
NT Service Control Manager - they are completely different. The SCM
has a split implementation. Part of it is implemented in OLE32.DLL
and is thus within any COM process. Part of it is implemented in
RPCSS.EXE, a separate process.
There is one SCM per-machine and it acts as a broker for
activation requests on that machine. It first decides whether
activation request needs forwarding to another machine or not. On
any given machine, the SCM decides whether server code needs loading
or not and loads servers into memory if required. It loads COM DLL
servers into the client's process address space if they are not
already loaded. It loads COM EXE servers into their own address
space. The SCM may end up launching a new EXE server or using one
that is already running. What it does partly depends on who the
client is (the security principal of the activator) and partly on
the characteristics of the server the SCM is dealing with (single
use or multiple use - more of this later).
The client decides which execution context (locality) the server
runs in by specifying a flag from the CLSCTX enumeration in the call to
CoGetClassObject() or CoCreateInstance()/CoCreateInstanceEx(). The
client can optionally specify a reference to a COSERVERINFO structure that outlines which remote
machine to run the server on and some security information for
authentication purposes when the server is started.
Here is a diagram that illustrates the structure of the SCM and
the relationship between SCMs on different machines.
So, when the client asks the SCM for a class object or for a new
object instance, the SCM starts or finds the server, gets hold of
the class object for the required coclass, and does one of the
following:
- Passes back the required class object reference
- Instructs the class object to create a new instance of the
required coclass, via IClassFactory, and passes back the required
reference to it
Here is an animated
diagram that illustrates this
earlier code snippet.
| Creating a new object with
CoCreateInstance() |
Previously, we looked at ways in which the client could use the
class object directly to create objects, either using a custom
interface or IClassFactory. However, the downside with this scenario
is that it involves at least three round-trips to the server in the
distributed case.
- CoGetClassObject()
- IClassFactory::CreateInstance() (for example)
- IClassFactory::Release()
A better solution would be to perform class loading and object
creation in one round-trip and this is exactly what CoCreateInstance() does. CoCreateInstance() will
cause the SCM to forward the activation request to the appropriate
machine to load the class on that machine then to create the object
on that machine before sending back the remote interface reference
to the object. Note that CoCreateInstance() allows us to specify the
details of the interface reference that we require on the object. It
does not accept any parameters that are relevant to how the object
is created. So the COM runtime system will use IClassFactory to
create the object. The moral of the story is "if your class object
doesn't support IClassFactory, clients cannot use
CoCreateInstance()/CoCreateInstanceEx() to create the object it is
responsible for."
Here is how the client sees it:
HRESULT CreateSnafuMoreEfficiently(IName
**ppName) { const CLSID CLSID_snafu =
{0xE1C1F25E,0x8BF1,0x11D2,{0xAA,0xC8,0x00,0x60,0x08,0x44,0x92,0x75}};
HRESULT hr = CoCreateInstance(CLSID_snafu, NULL, CLSCTX_ALL,
IID_IName, (void **)ppName ); return
hr; }
| Creating a new object with
CoCreateInstanceEx() |
There are two drawbacks to CoCreateInstance():
- It is not possible to fetch more than one interface pointer at
object creation time.
- It is not possible to programmatically select the machine name
on which a remote object is to be created or to select the
security principal of the activator of the server.
It's time to introduce CoCreateInstanceEx().***end*** By allowing us to
define a MULTI_QI structure, we can obtain multiple
interfaces at object creation time. This obviously saves round trips
to the server for subsequent QueryInterface() calls on the object.
By allowing us to specify a COSERVERINFO structure we can affect activation
settings programmatically. ***start***
Here is the client code:
HRESULT
CreateSnafuEvenMoreEfficiently(IName **ppName, IFoo **ppFoo)
{ const CLSID CLSID_snafu =
{0xE1C1F25E,0x8BF1,0x11D2,{0xAA,0xC8,0x00,0x60,0x08,0x44,0x92,0x75}};
MULTI_QI mqi[] =
{{&IID_IName,0,0},{&IID_IFoo,0,0}};
HRESULT hr = CoCreateInstanceEx(CLSID_snafu, NULL, CLSCTX_ALL,
NULL, 2, mqi ); if (SUCCEEDED(hr)) {
//Note that SUCCEEDED only implies that some interfaces were
acquired, we must check
each. if
(SUCCEEDED(mqi[0].hr))
*ppName = reinterpret_cast<IName
*>(mqi[0].pItf); if
(SUCCEEDED(mqi[1].hr))
*ppFoo = reinterpret_cast<IFoo
*>(mqi[1].pItf); }
return hr; }
Here we are going to do a lab that illustrates the possibilities
available to a client when using a class object, explicitly or
implicitly, to activate classes.
Next
we look at implementing and exposing class objects from the DLL
server's perspective.

|