Overview
Here we look at class basics, how classes
are used from the client's perspective and how classes
are implemented in a DLL server.
Relationship between classes and interfaces
As we have seen, interface-based programming allows the
separation of interface from implementation. Interfaces are
abstract. Clients have knowledge of the interface syntax, and loose
semantics and access an interface via an interface reference. The
client uses the interface without knowing or caring about the
details of the underlying implementation.
But we need some code behind the interface. The precise working
details of an interface are filled in by a class implementation.
Such a class is referred to as a coclass.
Typically, one coclass will support (implement) many interfaces.
Many coclasses with overlapping functionality can implement common
sets of interfaces. This is the whole point, right? The behavior of
a particular category of component is purely defined by a set of
interfaces. Examples of different categories might be programmable
components, serializable components, and embeddable components. A
coclass that wants to be a certain category of component has no
choice but to implement those interfaces the category defines. There
will be many different coclass implementations in the same category.
Some coclass implementations will span categories.
| Interface description and naming
|
Just like interfaces in the public domain, publicly createable
coclasses need to be described and uniquely identified. IDL is the
description language. Here is an example description of a coclass
and the interfaces it supports.
import "unknwn.idl"; import
"foo.idl";
[uuid(E1C1F25D-8BF1-11D2-AAC8-006008449275), helpstring("IName
Interface"), pointer_default(unique)] interface IName :
IUnknown { [helpstring("method GetName")]
HRESULT Getname([out,retval]BSTR *pbstrName);
[helpstring("method SetName")] HRESULT SetName([in]BSTR
bstrName); };
[
uuid(E1C1F251-8BF1-11D2-AAC8-006008449275),
version(1.0), helpstring("snafu 1.0 Type
Library") ] library SnafuLib {
importlib("stdole32.tlb");
importlib("stdole2.tlb");
[
uuid(E1C1F25E-8BF1-11D2-AAC8-006008449275),
helpstring("snafu Class")
] coclass snafu
{ [default] interface
IName; interface
IFoo; }; };
Notice that the coclass has a 'universally unique name' that is a
GUID known as a CLSID. Naming a coclass with a GUID prevents name
clashes. A coclass will generally list those programmatic interfaces
it supports. The [default] attribute
on the IName interface in the coclass statement means that this
interface is used if a client tried to use this object in an
unqualified way. For instance, in Visual Basic one could write code
such as this:
Dim s as snafu
Set s = New Snafu
s.SetName "Fred"
Here is a diagram that shows the relationship between interfaces
and classes.

A set of abstract interfaces is not much use to clients unless
they can bind them to particular coclass implementations. Binding is
the process of coupling an interface with a desired implementation
of that interface by dynamically loading the implementation code. We
have already seen from the "COM as a better C++" chapter that
clients don't statically link against components. Dynamically
binding an interface to an implementation at runtime is much more
flexible. It allows the client to choose which implementation it
wants at runtime and obtain all of the advantages of runtime
polymorphism. Static linking could not work in the common scenario
where a client does not actually know or care which implementation
sits behind an interface implementation.
To dynamically bind an interface to an implementation at runtime,
the client needs to identify and find the implementation. The client
must choose a binding algorithm, supply it with the information
necessary to identify the implementation and perhaps its initial
state, and specify the interface that it wants returned. The
simplest binding algorithm to consider is creating a new, virgin
object instance of a given class. (More complicated algorithms, such
as finding existing objects or creating an object and initializing
it with some persistent state, are beyond the scope of this
discussion. To find out more, check out COM Monikers).
How did we used to create objects?
Do you remember how we have created objects up to now? We
exported a single well known function from a DLL (commonly named
across all DLLs) and used that to create objects of different kinds
within the DLL. The client can choose at runtime which DLL to load
and which object to create. We have since learned that the point of
contact between a client and a server is via interfaces. It is not a
great leap of imagination to take the code from the exported DLL
object creator function and wrap it up within a method of a
well-known interface. That is to jump from this:
void CreateObject(char *szObjectType,
IID riid, void **) { ... }
to something like this:
[uuid(E1C1F252-8BF1-11D2-AAC8-006008449275), helpstring("IObjectCreator
Interface"), pointer_default(unique)] interface IObjectCreator
: IUnknown { [helpstring("method
CreatObject")] HRESULT CreateObject([in]BSTR bstrType, [in]REFIID
riid,
[out, iid_is(riid),retval]void **ppName); };
Our object creator function wouldn't have made much sense in an
EXE anyway. Functions exported from an EXE must be remote functions.
That would probably mean using an RPC interface for object
creation.If we are going to do this, we may as well use a COM
interface for object creation.
Another drawback of our creator function is that we didn't have a
way of uniquely identifying a class implementation. (We used
strings.) We will need a way of uniquely identifying classes in a
world of components that span different languages and environments.
Given a unique naming scheme it would be nice to abstract the actual
location and loading of the class code behind some configurable
system service so that the client doesn't have to do it. If we can
do that, perhaps we can define many different ways of creating or
finding objects without the onus being on the client to understand
them all.
How do we create objects the COM way?
So how does a client create a new instance of a coclass and thus
bind an interface to an implementation in the world of COM? Well,
the algorithm used is encapsulated by the COM runtime APIs CoCreateInstance() CoCreateInstanceEx() and CoGetClassObject(). The client must specify as
input parameters the CLSID of the required coclass, a preferred
location (have the object instance supplied from a DLL or a
local/remote EXE), and an interface identifier (IID) that describes
the initial interface to be returned on the new object instance. The
client must also specify, as an output parameter to be filled in by
the COM runtime, a place to put the returned interface
reference.
The algorithm used by CoCreateInstance(Ex)() and
CoGetClassObject() will use the CLSID as a key into the registry to
figure out which server to load into memory. Here is a snippet of
code (that will be explained later) that essentially loads the DLL
that contains the snafu coclass implementation code (if it is not
already loaded), creates a new instance of the snafu coclass and
obtains an IFoo reference from it.
const CLSID CLSID_snafu =
{0xE1C1F25E,0x8BF1,0x11D2,{0xAA,0xC8,0x00,0x60,0x08,0x44,0x92,0x75}}; const
IID IID_IFoo =
{0x7BF49AFD,0x8A15,0x11D2,{0x91,0xAE,0x00,0xA0,0x24,0x4C,0x42,0x06}}; IFoo
*pFoo=NULL; HRESULT hr = CoCreateInstance(CLSID_snafu, NULL,
CLSCTX_INPROC_SERVER, IID_IFoo, (void
**)&pFoo);
COM components can be physically packaged in one of two ways:
- In-process. In a DLL in the client's address space.
- Out-of process. In an EXE outside of the client's
address space.
Within this physical packaging, servers have logical class
context attributes that essentially determine how far way the
object is.
- In a DLL
- An in-proc server that contains the whole object
implementation.
- An in-proc handler that contains a smart
proxy to the real object. A smart proxy is a disconnected
replacement for the real object in the client's address space.
It can do most of the real object's work but may need to connect
back to the fully functional object in an EXE server.
- In an EXE
- A local EXE server on the same machine as the
client.
- A remote EXE server on a different machine to the
client.
The pros and cons of choosing one form of packaging over the
other are fairly straightforward. The pros/cons of using DLLs and
EXEs are symmetrically opposite.
- In a DLL
- Pros
- They are fast and efficient to call. There is direct
access (i.e., no proxy/stub) between the client and the
object.
- They are fast to load. Loading a DLL is faster than
loading an EXE.
- Cons
- They are not robust. If an object crashes, it may bring
down the client. If the client crashes, it may bring down the
object.
- They have security considerations. The object has to run
in the client's security context.
- One cannot easily distribute the object to make it remote
from the client.
- DLLs must be the same bit-ness as the client.
- In an EXE
- Pros
- They are robust. If an object crashes, it won't bring down
the client. If the client crashes, it won't bring down the
object.
- They have security benefits. The object does not have to
run in the client's security context.
- It is easy to distribute the object to make it remote from
the client.
- An EXE server does not have to be the same bit-ness as the
client.
- Cons
- They are slower and less efficient to call. There is
always a proxy/stub between the client and the object.
- They are slower to load. Loading a DLL is faster than
loading an EXE.
| Choosing and locating class context
|
When the client creates an object, they suggest a class context
for the object that can be in-process server, in-proc handler, local
exe server, or remote exe server. Later, we will look at the details
of how this is specified programmatically by the client. We will
also look at the default class context order the COM runtime uses to
search for components when the client isn't specific.
The system uses the InProcServer32 key under
HKEY_CLASSES_ROOT\CLSID\{snafu's class id} to figure out which DLL
server contains the snafu coclass.


The system uses the LocalServer32 key under
HKEY_CLASSES_ROOT\CLSID\{snafu's class id} to figure out which EXE
server contains the snafu coclass.


In order for the COM runtime to map a CLSID to the location of
the server module that contains the coclass, the server must have
previously put this location information in system object repository
(the registry) so it can be found. We will look at how the server
does this later.
Once the server is loaded into memory the COM class loader needs
to be able to create an instance of the coclass. To allow this, a
server must expose a single class object per CLSID.
Class Objects
For each coclass that can be publicly created, a server must
expose a single-class object that represents the class code. In fact
it represents the following:
- The meta data for the coclass
- The shared static behavior of the coclass
- The instanceless static behavior of the coclass
- The means by which the system class loader maps a CLSID to a
coclass implementation
- The means by which the system class loader can ask the server
to find or (more commonly) create new object instances, based on
CLSID
A class object is often called a class factory in the COM
documentation/literature.
| Relationship of class object to coclass
|
One class object can create many coclasses. In fact, the class
object can implement any policy it likes to effect the creation of
objects. For instance it could create only one 'singleton' object
the first time it was asked to create an object, then pass out
references to this single object for subsequent creation requests.
Alternatively it could maintain a limited pool of objects. It could
create each object on a new thread. You get the idea. The 'typical'
operation of a class object is to create a new instance for each
creation request.
Here is a diagram to show relationship of class objects to
coclasses.

Class objects can support any custom interface they choose for
the creation of, or location of, objects. Commonly they support at
least IClassFactory. This can be thought of as the
'default constructor' interface for objects.
Let's look next at
using class objects and creating objects from the client's
perspective.

|