225 44 33MB
English Pages [424]
Designed
for
m.
Microsoft '
Windows NT* Windows*9
CD-ROM Included
Microsoft's
mm
•
Component Object Model
•
The C++ Programmer's Key
•
to the Distributed
•
ActiveX
Controls
Dale Rogerson Microsoft Press
Dale Rogerson Microsoft Press
PUBLISHED BY Microsoft Press
A
Division of Microsoft Corporation
One
Microsoft
Way
Redmond, Washington 98052-6399 Copyright
©
1997 by Dale Rogerson
No part of the contents of this book may be reproduced or transmitted in any form or by any means without the written permission of the publisher.
All rights reserved.
Library of Congress Cataloging-in-Publication Data Rogerson, Dale E., 1966Inside COM / Dale E. Rogerson. cm. p.
Includes index.
ISBN 1-57231-349-8 1 Object-oriented programming (Computer (Computer program language) I. Tide. QA76.64.R64 1997 .
science)
bound 8 9
Distributed to the
A CIP
C++
96-45094 CIP
005.2'762-dc21
Printed and
2.
in the
MLML
United States of America. 2
10
book trade
in
9
Canada by Penguin Books Canada Limited.
catalogue record for this book
is
available
from the
British Library.
Microsoft Press books are available through booksellers and distributors worldwide. For further informadon about international editions, contact your local Microsoft Corporation office. Or contact Microsoft Press International directly at faix (206) 936-7329.
MS, Visual Basic, Visual C++, Win32, Windows, and Windows NT are registered trademarks and ActiveX, Developer Studio, DirectX, and Visual J++ are trademarks of Microsoft Corporation. Microsoft,
Photography copyright ©1997 by Kathleen Atkins For the subject of the "Component Bride and Groom" photograph, thanks Glamorama, Seattle, Washington http://www.glamorama.com Acquisitions Editor: Eric Stroo
Project Editor: Kathleen Atkins
Technical Editor: Gary
S.
Nelson
to
To
Sara with an h and to Lynn with an
e.
Digitized by the Internet Archive in
2011
http://www.archive.org/details/insidecomOOroge
CONTENTS SUMMARY Acknowledgments
xv
Introduction
xvii
CHAPTER ONE
Components
1
CHAPTER TWO
The Interface
15
CHAPTER THREE Querylnterface
37
CHAPTER FOUR
63
Reference Counting
CHAPTER FIVE
Dynamic Linking
85
CHAPTER SIX
HRESULTs, GUIDs,
and Other
Details
103
Component Reuse: Containment and Aggregation
159
the Registry,
CHAPTER SEVEN
The Class Factory
131
CHAPTER EIGHT
CHAPTER NINE Making
It
Easier
211
INSIDE COM
CHAPTER TEN Servers
in
EXEs
247
CHAPTER ELEVEN Dispatch Interfaces and Automation
279
CHAPTER TWELVE Multiple
Threads
309
CHAPTER THIRTEEN Putting Index
VI
It
All
Together
341 361
L
=%-ili
TABLE OF CONTENTS xv
Acknowledgments Introduction
xvii
CHAPTER ONE
Components Component
1
Benefits
3
Application Customization
Component Libraries Distributed Components Component Requirements Dynamic Linking
3 4 4 5
6 6
Encapsulation
COM
9
COM Components Are COM Is Not The COM Library The Way of COM COM Exceeds the Need Component Conclusions
9 10 11 11 11
13
CHAPTER TWO
The
Interface
15
Interfaces Are Everything
16
Reusing Application Architectures Other Benefits of
Implementing a
COM
Interfaces
COM Interface
17
17 18
Coding Conventions
19
A Complete Example
20
Non-Interface Communication
24
Implementation Details
25
Interface Theory, Part Interfaces Don't
Polymorphism
II
Change
27 27
27
VII
INSIDE COM
Behind the Interface Virtual
28
Function Tables
28
and Instance Data
30
Furthermore, Multiple Instances
31
vtbl Pointers
Different Classes,
Same
32
vtbis
The Building Blocks, Summarized
34
CHAPTER THREE
37
Querylnterface
Querying for Interfaces
38
About lUnknown
39
Getting an
40
Getting to
/L/n/Fy2()
:
trace( "Client: Delete the component.") delete pA
:
;
;
return
;
The output from
this
program
is
Client: Create an instance of the component. Client: Use the IX interface.
CA::Fxl
CA::Fx2 Client: Use the lY interface.
CA::Fyl
CA::Fy2 Client: Delete the component.
As you can see
in this
example, the cHent and the component communiThe interfaces are implemented using the two pure
cate through two interfaces.
abstract base classes
/Xand
77.
The component is implemented by the class TY. Class CA implements the members
CA, which inherits from both /X and
of both interfaces.
23
INSIDE COM
The
an instance of the component. The client gets pointsupported by the component. The client then uses these
client creates
ers to the interfaces
interface pointers just as
implemented
as
it
would C++
pure abstract base
class pointers, since interfaces are
classes.
The key points in
this
example are
the following:
COM interfaces are implemented in C++ as pure abstract base classes.
A single COM component can support multiple interfaces. A C++ class can
use multiple inheritance to implement a compo-
nent that supports multiple interfaces. I
left
some encapsulation holes in this example, which I'll fill in the next like to discuss some of these problems now as they ap-
few chapters. But I'd pear in Listing 2-1.
Non-Interface Communication
Remember when
I
said that the client
and the component communicate only
through interfaces? The client in Listing 2-1 isn't following the rules. It communicates with the component using /?A, which is not a pointer to an interface, but a pointer to the class CA. This might seem like a minor point, but it is very important. Using a pointer to CA requires that the client know the class declaration (usually in a header file) for CA. The class declaration for CA has lots of implementation details in it. Changing these details would require the client to be recompiled. Components should be able to add and remove interfaces (as I've said) without breaking existing users. This is one of the reasons that we insist that the client and the component communicate only through an interface. Remember, interfaces are implemented as pure abstract base classes with no associated implementation.
Of course,
isolating the client
they are in the same source
file.
and the component isn't necessary when
However, such isolation
is
necessary when the
and the component are dynamically linked together, especially if you don't have the source code. In Chapter 3, we'll fix this example so that it doesn't use a pointer to CA. The client will no longer need the class declaraclient
tion for CA.
Using a pointer is
to
CA isn't the only time
the client in the above example
not communicating via interfaces. The client uses the new and
tors to control the
component's
lifetime.
Not only are new and
delete operdi-
delete
not part
of any interface, they are also specific to the C++ language. In Chapter
24
4, we'll
TWO:
The Interface
look at a way to delete the component using an interface instead of using a 6, "HRESULTs, GUIDs, the Registry, Chapter "The Class Factory," we'll develop a much and Other Details," and 7, more powerful way to create components. Now let's examine some of the more esoteric details of how the client and
language-specific operator. In Chapter
the
component are implemented
in Listing 2-1.
Implementation Details C++ program. There really isn't anything unusual about this program other than that it happens to be our first step in creating a COM component and client. It's easy to confuse what COM requires a component to be and how the component is implemented. In this section, I will clear up some areas where people commonly confuse an implementation detail with the way COM requires things to be done. Listing 2-1
A
is
a standard
Not a Component CA implements a single component. There is no COM requirement forcing a single C++ class to correspond to a single COM component. You can implement a single COM component using several C++ classes. In fact, you can implement a COM component without using any C++ classes at all. C++ classes are not used when people implement COM components in C, so they don't have to be used when you develop COM components in C++ either. Itjust so happens that implementing COM components using classes is much easier than building them by hand. Class
Is
In Listing 2-1, class
Interfaces Are Not
CA
Always Inherited
inherits the interfaces that
it
supports.
COM doesn't require
implementing an interface to inherit that interface because the the inheritance hierarchy of a
the class
client never sees
COM component. Inheriting interfaces
is
purely
an implementation detail. Instead of using one class to implement several interfaces, you can implement each interface in a separate class and use pointers to these classes. Kraig Brockschmidt uses this method in his book Inside OLE. We'll be using a single class to implement all the interfaces because this method is simpler and easier to understand, and it makes COM programming
more
natural in C++.
Multiple Interfaces and Multiple Inheritance Components can support any number of interfaces. To support multiple interfaces, we are using multiple inheritance. In Listing 2-1, CA uses multiple inheritance to inherit the two interfaces, TXand lY, that it supports. By supporting multiple interfaces, a component can be thought of as a set of interfaces.
25
INSIDE COM
This gives component architecture a recursively partitioned nature. (See 2-3.) An interface is a set of functions, a component is a set of interfaces,
Figure
and a system tures.
When
is
a set of components.
Some people equate
interfaces with fea-
they add an interface to a component, they say the
now supports this feature. interfaces that a
I
like to
component The set of
think of interfaces as behaviors.
component supports corresponds
to the behaviors of that
component.
Component 2
Component n
S
Fx1
Fx1
1
Fx2
o-
4
'f 1
'^1
Fxn
Fxn
w Fx1
o-
Fx2 •
1X2
Fx1 ...
0.
Fxn
IXn
'''
1X2
L
Fx1
o-
I
m
Fxn
'f Fxn
1
'
Fxl
\
QueryInterface(IID_IX. :
if
I
(void**)&pIX)
(SUCCEEDED(hr))
{
trace("Cl ient: pIX->Fx() :
//
Succeeded getting IX.") Use interface IX.
;
}
Get interface lY.")
trace("Cl ient:
lY* pIY = NULL hr = pIUnknown->QueryInterface( IID_IY,
:
:
if
{void**)&pIY)
:
(SUCCEEDED(hr))
{
L
trace("C1 ient: pIY->Fy() ;
;
Ask for an unsupported interface.")
trace("Cl ient: IZ* pIZ = NULL
Succeeded getting lY.") interface lY.
// Use
;
;
hr = pIUnknown->QueryInterface( IID_IZ,
(void**)&pIZ)
;
if (SUCCEEDED(hr)) {
trace("Cl ient: pIZ->F2()
Succeeded in getting interface IZ.")
;
;
}
(continued)
49
N S
I
COM
D E
lUNKNOWN.CPP
continued
else {
Could not get interface IZ.")
traceC'CI lent: }
Get interface lY from interface IX.")
traceC'CI ient: lY* pIYfromlX = NULL
;
;
hr = pIX->QueryInterface(IID_IY,
(
void**)&pIYf romIX)
;
if (SUCCEEDED(hr)) {
traceC'Client: pIYfromIX->Fy()
Succeeded getting lY.")
;
;
}
Get interface lUnknown from lY.")
traceC'Client;
lUnknown* pIUnknownFromlY = NULL hr = pIY->QueryInterface( IID_IUnknown, 1f (SUCCEEDED(hr))
;
;
(void**)&pIUnknownFromIY)
{
cout Release()
;
;
}
else {
cout CreateInstance(pUnknownOuter, // Release the class factory. pIFactory->Release( ) ;
}
return hr
140
;
iid,
ppv)
SEVEN:
The Class Factory
CoCreatelnstance C3.\ls CoGetClassObject and gets the IClassFactory interface
on the
class factory.
Using
this IClassFactory pointer, CoCreatelnstance Xhen calls
IClassFactory r.Createlnstance,
Why Use
which
results in a
new component.
CoGetClassObject?
we can create components using CoCreatelnstance and forget about CoGetClassObject. However, there are two cases in which CoGetClassObject should be used instead of CoCreatelnstance. First you must use CoGetClassObject if you want to create an object using a creation interface other than IClassFactory. So if you want to use IClassFactory 2, you have to use CoGetClassObject. Second, if you want to create a bunch of components all at one time, it's more efficient to create the class factory once for all of the components instead of creating and releasing the class factory for every instance of a component. CoIn most cases,
much-needed control over the creation process. Creating components by hand using class factories is much more confusing than letting CoCreatelnstance do the work for you. But if you remember that the class factory is simply a component that creates other components, you will GetClassObject gives the client
be well along the road
to
understanding
class factories.
Class Factories Encapsulate Creation would like to point out a few characteristics of class factories before I show you how to implement one. First, an instance of a class factory creates components corresponding to a single CLSID. We saw this because CoGetClassObject takes a CLSID, while IClassFactory r.Createlnstance does not. Second, the class factory for a particular CLSID is built by the same developer who implements the component. The class factor) component, in most cases, is contained in the same DLL as the component it creates. Since an instance of a class factory corresponds to a single CLSID and both the class factory and the component it creates are developed by the same person, the class factory can and does have special knowledge of the component it creates. Implementing the class factory so that it has special knowledge of the component is not sloppy programming. The purpose of the class factory is to know how to create a component and to encapsulate this knowledge so that the client can be as isolated as possible from the special needs that a I
component has.
Implementing the Class Factory In this section, we'll look at the implementation of the special attention to the to see
how
implementation of the
class factories
component, paying But first we have
class factory.
themselves are created. 141
INSIDE COM
Using DIIGetClassObject In Chapter
5, CallCreatelnstance
called the function Createlnstance in the
DLL
component. Similarly, CoGetClassObject needs an entry point in the DLL to create the component's class factory, which is implemented in the DLL along with the component. The entry point is named DIIGetClassObject. CoGetClassObject calls the function DIIGetClassObject, which actually creates the class factory. The declaration of DIIGetClassObject is shown here: to create the
STDAPI DIlGetClassObjecK
const CLSID& clsid, const IID& iid,
void** ppv )
;
The three parameters passed to DIIGetClassObject should look familiar. The same three parameters are used by CoGetClassObject. The first parameter is the class ID of the component that the class factory will create. The second parameter
The
the ID of the interface in the class factory the client wants to use.
is
interface pointer
Passing the
a single
is
CLSID
returned in the
last
parameter.
to DIIGetClassObject is significant. This
parameter allows
DLL to support any number of components because
be used to pick the appropriate
the
CLSID can
class factory.
The View from Above To put
all
we can
see the major players involved in creating a
is
let's
look at an illustration. In Figure
component.
First,
7-1,
there
the client, which initiates the request by calling CoGetClassObject. Second,
there is
of this into perspective,
the
is
the
COM
Library,
which implements
DLL. The DLL contains the function
CoGetClassObject. Third, there
DIIGetClassObject,
which
is
called
by CoGetClassObject. DllGetClassObject's ]oh is to create the requested class factory. The way DIIGetClassObject creates the class factory is left completely up to the developer because it is hidden from the client. After the class factory
is
created, the client uses the IClassFactory interface
component. How IClassFactory:: Createlnstance creates the component is left up to the developer. Again, IClassFactory encapsulates the process so the class factory can use its internal knowledge of the component to to create the
create
142
it.
SEVEN:
COM
Client
A
Calls CoGetClassObject
A
^li^i^^^c^^t^r,, pICIassFactory
,
CoGetClassObject I
A
•'•
AX
,
DIIGetClassObject j..
Ws' p^^%
^ ^»P
^"O^
,^,
_
,
ICIassFactory
Returns /X
m
p/X
^
A
Calls ICIassFactory: :Createlnstance
f
A
DLL
Library
Returns ICIassFactory
/°\
The Class Factory
Calls /X.Fx
IX
Jr Creates Component
Figure 7-1. Connect the dots
creating the component with the help of the
to follow the client
COM Library and the class factory. Now we
are ready to look at the implementation of the component.
Component Code
Listing
The component and
its class factory are implemented in Listing 7-2. The class implemented by the C++ class CFactory. The first thing you should notice about CFactory is that it's just another component. It implements lUnknown the way any other component does. The only difference between the implementation of CFactory and CA is the interfaces that they support. As you read through the code, pay special attention to CFactory ::CreateInstance and DIIGetClassObject.
factory
is
CMPNT.CPP 1 //
// Cmpnt.cpp //
//include y/inc1ude //include "Iface.h" //include "Registry.
//
h"
//
Interface declarations Registry helper functions
Listing 7-2.
The complete code for the component,
(continued)
the class factory,
and thefunctions
exported by the DLL.
143
INSIDE COM
CMPNT.CPP
continued
H
II Trace function
void traceCconst char* msg)
cout Release()
;
} }
// Initialize the component by creating the contained component. HRESULT CA::Init()
(continued)
165
INSIDE COM
Containment Code
in
CONTAIN \CI\/IPNT1
continued
trace( "Create contained component.") HRESULT hr = :CoCreateInstance(CLSID_Component2, ;
:
NULL,
CLSCTX_INPROC_SERVER, IID_IY,
(void**)&m_pIY) f
:
(FAILED(hr)) trace( "Could not create contained component.")
return E_FAIL
;
;
1se
return S_OK
;
}
Let's
works.
examine how
this
code for the outer component (Component
1)
A new method named Init creates the inner component (Component
same way that all clients create components, by calling CoCreateInstance. In making this function call, the outer component asks for an lY pointer on the inner component, and if the call is successful it stores this 2) in the
pointer in m_prY. In the code just shown, I didn't show the implementations of Querylnterface and the other /t/n^nown functions. They work exactly as if containment weren't
being used.
nent
1
When
the client asks
returns a pointer to
its
Component
lY interface.
1
for the interface lY,
on Component 1, Component 1 then forwards the happens in the following line of code: virtual void
FyO
{
ni_pIY->Fy()
When Component
1
Compo-
When the client calls interface FY calls to
Component 2. This
:)
destroys
itself, its
destructor calls Release on the
Component 2 to free itself as well. The class factory for Component 1 is little changed from our class factory in the last chapter. The only added step is that the Createlnstance function calls Component I's /nonfunction after it creates Component 1. Here in Listing pointer m_prY, causing
8-2
166
is
the code for this function:
EIGHT:
Createlnstance Function HRESULT
Code
in
Component Reuse: Containment and Aggregation
C0NTAIN\CMPNT1
stdcall CFactory: cCreatelnstancedUnknown* pUnknownOuter,
const IID& iid, void** ppv) {
// Cannot aggregate
if (pUnknownOuter
!= NULL)
{
return CLASS_E_NOAGGREGATION
;
}
// Create component.
CA* pA = new CA
;
if (pA == NULL) {
return E_OUTOFMEMORY
;
}
// Initialize the component. HRESULT hr = pA->In1t() ;
if (FAILED(lir)) {
//
Initialization failed.
pA->Release() return hr
Delete component.
;
;
}
// Get the requested interface. hr = pA->QueryInterface(iid, ppv)
pA->Release( return hr
)
;
;
;
}
Listing 8-2.
The class factory for the outer component calls
the Init function
on
the newly
created component.
That's at
all
there
is
to
implementing containment.
how containment can be
Now let's
take a look
used.
Extending Interfaces
One of the major uses of containment is to extend an interface by adding code to
an existing interface. Let's take an example. You might have the
class
167
INSIDE COM
lAirplane
thdit
you want
to
become
IFloatPlane.
The
following are the defini-
tions of these interfaces: interface lAirplane
lUnknown
{
void TakeOffO
FlyO void LandO void
}
;
;
;
:
interface IFloatPlane
lAirplane
:
{
void LandingSurfacedJINT iSurfaceType) void Float( void Sink(
)
)
;
void RustO
;
void DrainBankAccount( }
;
;
)
;
:
Suppose you have already implemented lAirplane in a component named An outer component could easily contain MyAirplane and use the lAirplane interface to implement the lAirplane members that IFloatPlane MyAirplane. inherits:
void CMyFloatPlane::Fly() {
ni_pIAirplane->Fly(
)
;
)
The other members
in lAirplane will
probably need to be modified to
support landing and taking off on the water: void CMyFloatPlane:
LandO
:
{
if (m_iLandingSurface == WATER) {
WaterLanding(
)
;
)
else {
m_pIAirplane->Land(
)
;
} }
As you can
using containment with these classes is easy. However, if had a bunch of members, it would be a pain to write wrap-
see,
interface lAirplane
per code to forward
calls to
MyAirplane. Luckily,
because once an interface has been published, provides
168
some
relief for the lazy
it
this isn't
a maintenance issue
doesn't change. Aggregation
programmer who doesn't want to implement
EIGHT:
Component Reuse: Containment and Aggregation
the code to forward calls to the inner object. But you can't add your to
an interface when you use aggregation. We'll look
at
own code
aggregation next.
Implementing Aggregation how
The client queries the outer component for interface 77. Instead of implementing TY, the outer component queries the inner component for its lY interface and passes this interface Here's an overview of
aggregation works.
pointer to the client. WTien the client uses interface
lY, it is
direcdy calling the
/F member functions implemented in the inner component. The outer component is out of the picture, as far as interface FY is concerned, and has relinquished control of the FY interface to the inner component. While aggregation sounds very simple, there are some difficulties in correctly implementing the lUnknown interface for the inner component, as we will see. The magic of aggregation happens in the QueryInterface cAX. Let's implement Query Interface for the outer component so that it returns a pointer to the inner object.
and Aggregation
C+-I-
C++ doesn't have a feature equivalent to aggregation. Aggregation is dynamic form of inheritance, and C++ inheritance is always static. The best way to simulate aggregation in C++ is to override the dereference operator (o/>«rafo7->). We'll examine this technique when we implement smart pointers in the next chapter. Overriding operator-> is much more limited than COM aggregation. You can forward calls to only one other class, while in COM you can aggregate as many a
interfaces as
you want.
Querylnterface Magic Here is the declaration of an outer component and offers interface FY through aggregation. class CA
:
that
implements interface IX
public IX
{
publ ic: //
lUnknown
virtual HRESULT virtual ULONG virtual
ULONG
stdcall
QuerylnterfaceCconst IID& iid, void** ppv)
stdcall AddRefO stdcall
ReleaseO
;
;
;
(continued)
169
INSIDE COM
// Interface IX virtual void stdcall
//
Fx()
{
cout AddRef( return S_OK ;
}
In this example, the outer component's Querylnterface simply calls the
inner component's Querylnterface. All very nice and simple, except
work
it
doesn't
correctly!
The problem is not in the code just shown. The problem is in the inner component's lUnknown interface. The inner component must handle lUnknown
member function
way when it is aggregated. We'll see that it lUnknown implementations. Let's look now at why implementing the inner component's lUnknown interface in the usual way causes problems. Then we'll see how to implement two lUnknown interfaces to correct this problem. We'll also see how to modify the inner component's creation to make sure both components get the pointers calls in a special
actually needs two
they need. Finally, we'll see ers to inner
Incorrect
how to
component interfaces
provide the outer
—a
trickier process
component with
point-
than you might expect.
lUnknown
The goal of aggregation is to convince the client that an interface implemented by the inner component is implemented by the outer component. You have to pass a pointer from the inner component direcdy to the client and convince the client that the interface pointer belongs to the outer component. If we pass to the client an interface pointer implemented in the usual way by the inner component, the client gets a split view of the component. The inner component's interface calls the Querylnterface implemented in the inner component while the outer component has its own separate Querylnterface. When the client queries interfaces belonging to the inner component, it gets a different view of the capabilities of the component than it does when it queries an interface on the outer component. See Figure 8-4 on the next page. An example will make this clearer. Suppose we have an aggregated component. The outer component supports interfaces /Xand lY. It implements the IX interface and aggregates the lY interface. The inner component implements the lY and IZ interfaces. After we create the outer component, we get its lUnknown interface pointer. We can successfully query this interface for IX
171
INSIDE COM
Outer Component
IX Querylnterface
Outer component's I
lUnknown
AddRef
implementation
Release
Fx
Inner
Component lY
'
i
Querylnterface
—
AddRef
r^.»
Inner component's
lUnknown implementation
Release
Fy
Figure 8-4. Separate components have separate
or for
lY,
but querying for IZ
the lY interface pointer,
we query
we
lUnknown will
implementations.
return
E_NOINTERFACE.
If
we query for
get the lY interface pointer from the inner com-
77 pointer for IZ, the query will succeed. This is beis implementing the lUnknown functions for the /y interface. Similarly, querying the lY interface for /X will fail because the inner component doesn't support 7X. This condition breaks the fundamental rule of implementing Querylnterface: if you can get there from here, you can get there from anywhere. The culprit is the inner component's lUnknown interface. The client sees two different unknowns: the inner lUnknown and the outer lUnknown. This confuses the client because each lUnknown implements a different Querylnterface and each Querylnterface supports a different set of interfaces. The client should be completely independent of the implementation of the aggregate component. It should not know that the outer component is aggregating the inner component and should never see the inner component's lUnknown. As discussed in Chapter 3, two interfaces are implemented by the same component if and only if both interfaces return the same pointer when queried for lUnknown. Therefore, we must hide the inner component's lUnknown from ponent.
If
this
cause the inner component
the client
172
and present the
client with a single
lUnknown. The interfaces on the
EIGHT:
Component Reuse: Containment and Aggregation
inner component must use the lUnknown interface implemented by the outer
component. The outer component's lUnknown goes by the nickname unknown or controlling unknown.
Unknown The
outer
Interfaces for Aggregation simplest way for the inner
forward
calls to
the inner
know
the outer
component
component needs a pointer
that
it is
calls to
to the outer
unknownn
is
to
the outer unknown,
unknown.
It
also
needs to
being aggregated.
Outer Unknown you remember from Chapter
If
Instance are passed an HRESULT
to use the outer
unknown. To forward
7,
CoCreatelnstance
lUnknown pointer
that
and
IClassFactoryr.Create-
we have not
used:
stdcall CoCreateInstance(
const CLSID& clsid,
lUnknown* pUnlcnownOuter, // Outer Component // Server Context
DWORD dwClsContext, const IID& iid,
void** ppv )
:
HRESULT
stdcall Createlnstance(
lUnknown* pUnknownOuter. const IID& iid, void** ppv )
;
The outer component passes its lUnknown interface pointer to the inner component using \he pUnknoiunOuter \)Z.rdimeiev If the outer unknown pointer is non-NULL, the component is being aggregated. Using the lUnknoxvn pointer passed with Createlnstance, a component knows whether it is being aggregated and who is aggregating it. If a component is not being aggregated, it uses its own implementation of lUnknown. If a component is being aggregated, it should delegate to the outer unknown. .
Delegating and Nondelegating
To support
Unknowns
component actually implements two lUnknown interfaces. The nondelegating unknown implements lUnknown for the inner component in the usual way. The delegating unknown forwards lUnknown member function calls to either the outer unknown or the nondelegating unknown. If the inner component is not aggregated, the delegating unknown forwards calls to the nondelegating unknown. If the inner component is aggregated, the delegating unknown forwards calls to the outer unknown, which aggregation, the inner
173
'
INSIDE COM
implemented by the outer component. Clients of the aggregate call the component manipulates the inner component through the nondelegating unknown. Some pictures should help to is
delegating unknown, while the outer clarify the situation.
Figure 8-5 shows the nonaggregated case, while Figure
8-6 shows the aggregated case. Nonaggregated Component
Delegating
''•*
'AddRef( return S_OK
)
;
;
}
Notice the cast from the inner component's delegatingUnknown pointer. This cast
is
this
pointer to an INon-
very important. By casting the
this
we guarantee that we return the nondelegating unknown. The nondelegating unknown always returns a pointer to pointer io INondelegatingUnknown
first,
itself when queried for IID_IUnknown. Without this cast, the delegating unknown would be returned instead of the nondelegating unknown. When the component is aggregated, the delegating unknown defers to the outer object
for
all
Querylnterface, AddRef,
and
Clients of the aggregated
Release calls.
component never
get pointers to the
nondelegating unknown of the inner component. Whenever the client asks for
lUnknown of the outer component. Only the outer component gets a pointer to the inner component's nondelegating unknown. Let's see how to implement the delegating unknown. an lUnknown pointer,
it
gets a pointer to the
Implementing the Delegating Unknown
unknown is trivial; it forwards unknown or the nondelegating unknown. Here's the declaration of a component that supports aggregation. The component contains a pointer named m_pUnknownOuter. When the component is aggregated, this pointer points to the outer unknown. If the component isn't aggregated, the pointer points to the nondelegating unknown. Whenever the delegating unknown is called, the call is forwarded to the interface pointed to by m_pUnknownOuter. The delegating unknown is implemented inline: Luckily, the implementation of the delegating
the calls to either the outer
176
) )
EIGHT:
class CB
:
Component Reuse: Containment and Aggregation
public lY,
public INondelegatingUnknown {
public: // Delegating
IUnlQueryInterface(iid, ppv)
;
}
stdcall AddRef (
virtual ULONG {
// Delegate AddRef.
return m_pUnknownOuter->AddRef (
)
;
)
virtual ULONG
stdcall
Releasee
{
//
Delegate Release.
return m_pUnknownOuter->Release(
)
;
}
// Nondelegating
virtual
HRESULT
lUnknown stdcall
NondelegatingQueryInterface(const IID& iid, void** ppv) virtual ULONG stdcall NondelegatingAddRef ( ) virtual ULONG stdcall NondelegatingReleaseC )
;
;
:
//
Interface lY
virtual //
void
stdcall
FyO
{
cout Nondelegat1ngRelease() return hr ;
:
}
The
Createlnstance function
above
calls NondelegatingQuerylnterface
rather
than Querylnterface to get the requested interface on the newly created inner
component is being aggregated, it would delegate a unknown. The class factory needs to return a pointer the nondelegating unknown, so it calls NondelegatingQuerylnterface.
component.
If
the inner
Querylnterface to the outer
to
179
INSIDE COM
The Inner Component's Constructor on the previous page, Createlnstance pzsses the outer unknown pointer to the inner component's constructor. The constructor initiaHzes m_pUnknownOuter, which is used by the delegating unknown to forward calls In the code
nondelegating or the outer unknown implementation. If the component is not being aggregated {pUnknoumOuter'is NULL), the constructor to either the
sets
m_pUnknownOuter equal
to the
nondelegating unknown. This
is
shown
in
the code below: CB::CB(IUnknown* pUnknownOuter) :
nucRefd)
{ :
:
InterlockedIncrement(&g_cComponents)
;
if (pUnknownOuter == NULL) {
// Not being aggregated; use nondelegating unknown. nupUnknownOuter = reinterpret_cast( static_cast
(this))
:
}
else {
// Being aggregated; use outer unknown. nupUnknownOuter = pUnknownOuter ;
}
Outer Component Pointers to Inner Component Interfaces
When I implemented CAr.Init as the first step to creating the inner component, I
asked for an lUnknown interface instead of lY. However, our component is So it's a good idea to see whether the inner compo-
actually aggregating lY.
nent really implements lY before we continue. But as I said before, when you're aggregating a component, the outer component can ask only for an lUnknown interface. CFactory::CreateInstance returns
CLASS_E_NOAGGREGATION if you
pass something other than IID_IUnknown to
the inner
it.
Therefore, we need to query
component for IID_IY after we have created
it.
But beware of a super-duper gotcha here. When you call Querylnterface from m_pUnknownInner, it calls AddRef on the pointer like a good Boy Scout would. Since the inner component is aggregated, it delegates the AddRef call to the outer unknown. The result is that the outer component's reference count is incremented and not the inner component's reference count. I'll repeat that for emphasis. When the outer to get an //Z)_/y interface pointer
180
EIGHT:
Component Reuse: Containment and Aggregation
component queries the nondelegating unknown or any other interface belonging to the inner component, the reference count of the outer component is incremented. This is exactly what we want to happen when the client queries an interface belonging to the inner component. But now the outer component holds a pointer, lY, and the outer component's reference count is incremented for this pointer. So the outer component holds a reference count on itself! If we allow this, the outer component's reference count will never go to and the outer component will never be released from memory. Since the lifetime of the outer component's 77 pointer the lifetime of the outer component,
we don't need
this
is
embedded
in
reference count. But
we must treat were individually reference counted. (For our implementation of the inner component, which pointer we release doesn't matter. But for other implementations it might be crucial.) Releasing interface 77 might release resources it needs. Therefore, the rule is to call Release using the pointer that was passed to CoCreatelnstance. A version of CAwInit that queries for the lY interface is implemented below: don't
call
Releaseon 77 to remove the reference count because
the TY interface as
if it
stdcall CA::Init()
HRESULT {
// Get pointer to outer unknown.
IUnlReleasehec2LUse doesn't have a reference count We removed this reference count in the outer component's Init function it
it.
TY pointer. Now we need to reverse the procedure by restoring the reference count and calling Release on the 77 pointer. We have to be
after getting the
careful, though, since otherwise, this last Release call will
component's reference count go back
to 0,
make
the outer
causing the component to
try to
release itself again.
Therefore, releasing our interface pointer to the inner component
is
a
make sure that our component doesn't attempt to release itself again. Second, we have to call AddRef on the outer component because any Release on the inner component will call Release on the outer component. Finally, we can release the outer component. The code to do this is shown below: three-step process. First,
we have
Increment the reference count to avoid
//
1.
//
recursive calls to Release.
m_cRef =
1
;
AddRef the outer unknown. lUnknown* pUnknownOuter = this
// 2.
pUnknownOuter->AddRef
(
)
;
// 3. Release the interface. m_pIY->Release() ;
182
to
;
EIGHT:
Let's take a
ponent. The
we do
is
first
Component Reuse: Containment and Aggregation
mental walk through thing we do
increment
is
count to
this
this
code
set the reference 2.
Next we
as it works in the outer comcount to 1. The second thing
call Release
on
component will decrement the reference count to
1
,
the
The The outer
interface lY.
inner component will delegate the Release to the outer component.
reference count from 2 to 1. If we had not set component would have attempted to delete itself
its
a second time. In our implementation, /2^/^a5e
when
the inner
component
is
aggregated,
its
function just delegates to the outer unknown. However, the outer
component has
to treat
an inner component
as if
it is
doing per-interface
reference counting because another implementation of the inner
component
might do more than just delegate Release to the outer component. It might also release resources or perform other operations. A lot of this code might look redundant or unnecessary. But if the outer component itself is aggregated by another component, it becomes very important to follow these steps. The example in Chapter 9 shows a component aggregating a component that is aggregating another component. "That's all there is to implementing aggregation," he says with a smile. Actually, once you set it up, it works great and you can forget about it. However, getting it set up the first time leads
many people
to call
it
"aggravation" instead of "aggregation."
A Complete Example Let's
implement
Component However,
it
I
a
component
that aggregates an interface. In this example.
supports two interfaces, just as
implements only IX.
It
it
did in the containment example.
won't implement an lY interface, nor
will
Component 2's implementation. Instead, when the client queries Component 1 for interface lY, Component 1 returns a pointer to the lY interface implemented by the inner component. Component 2. Listing it
forward
calls to
8-3 shows the outer
The
client
is
component, and Listing 8-4 shows the inner component. same old client, and it doesn't care whether we use
basically the
aggregation or containment.
AGGRGATE\CMPNT1 //
//
Cmpntl.cpp
-
Component
1
//
#1nclude y/include
Listing 8-3.
(continued)
The implementation of the outer (aggregating) component.
183
INSIDE COM
AGGRGATE\CMPNT1
continued
#include "Iface.h" #include "Registry. h" void traceCconst char* msg)
cout QueryInterface(IID_IY, (vo1d**)&nL.pIY) if (FAILED(hr)) ;
;
{
trace("Inner component does not support interface lY.") m_pUnknownInner->Release( ) m_pUnknownInner = NULL
;
;
;
{continued)
186
EIGHT:
AGGRGATE\CMPNT1
Component Reuse: Containment and Aggregation
continued
itupIY = NULL
// Just to be safe
;
return E_FAIL
;
}
release the reference count added to the So call Release // on the pointer you passed to CoCreatelnstance. pUnknownOuter->Release() : return S_OK // We need to
^
// outer component in the above call.
;
// //
lUnknown implementation
//
HRESULT
stdcall CA: :QueryInterface(const IID& iid. void** ppv)
{
if (iid == IID_IUnknown)
*ppv = static_cast(this)
;
else if (iid == IID_IX) *ppv = static_cast(this)
;
else if (iid == IID_IY)
traceC'Return inner component's lY interface.") #if
;
1
// You can query for the
interface. return in_pUnknownInner->QueryInterface(11d,ppv)
;
#else // Or you can return a cached pointer. •ppv = m_pIY // Fall through so it will get AddRef'ed ;
#endif }
el se {
*ppv = NULL return E_NOINTERFACE :
:
}
reinterpret_cast(*ppv)->AddRef return S_OK
(
)
:
:
}
ULONG
stdcall
CA::AddRef()
{
return InterlockedIncrement(&m_cRef )
:
}
(continued)
187
)
INSIDE COM
AGGRGATE\CMPNT1 UL0N6
amtnmfrl
stdcall CA: :Release(
{
if (Inter! ockedDecrement(&in_cRef) == 0) {
delete this return
;
;
}
return nucRef
;
}
lllllllllllllllllllllllllllllllllllllllllllllllllllllllllll II II Class
factory
//
class CFactory
public IClassFactory
:
{
public: //
lUnknown
virtual HRESULT
stdcall QuerylnterfaceCconst IID& iid, void** ppv)
virtual
ULONG
stdcall AddRefO
virtual
ULONG
stdcall
Releasee
;
;
)
;
// Interface IClassFactory
virtual HRESULT
stdcall Createlnstance( lUnknown* pUnknownOuter,
const IID& iid, void** ppv) virtual HRESULT
stdcall
LockServerCBOOL bLock)
:
;
// Constructor
CFactory ()
m_cRef(l)
:
{}
// Destructor
-CFactoryO
{}
private: long nucRef }
;
:
//
// Class factory lUnknown implementation //
(continued)
188
EIGHT:
AGGRGATE\CMPNT1
Component Reuse: Containment and Aggregation
continued
stdcall CFactory: :QueryInterface(REFIID iid, void** ppv)
HRESULT {
lUnknown* pi 1f
=
(did
;
IID_IUnknown)
||
(iid == IID_IC1assFactory))
{
pi =
static_cast(this)
:
}
else {
«ppv = NULL
;
return E_NOINTERFACE
;
}
pI->AddRef() *ppv = pi
:
;
return S_OK
;
}
ULONG CFactory: :AddRef() {
return
::
Inter! ockedIncrement(&in_cRef)
;
}
ULONG CFactory: :Re1ease() {
if
(
Inter! ockedDecrenient(&in_cRef)
=
0)
{
delete this return
;
:
}
return m_cRef
;
}
//
//
IClassFactory implementation
//
HRESULT
stdcal! CFactory: :CreateInstance(IUnknown* pUnknownOuter,
const IID& iid, void** ppv) {
HRESULT hr = E_FAIL
:
(continued)
189
INSIDE COM
AGGRGATE\CMPNT1
continued
II Cannot aggregate.
if (pUnknownOuter
!= NULL)
{
return CLASS_E_NOAGGREGATION
;
}
// Create component.
CA* pA = new CA if (pA == NULL)
;
{
return E_OUTOFMEMORY
;
}
// Initialize the component.
hr = pA->Init()
;
if (FAILEO(hr)) {
//
Initialization failed. Delete component.
pA->Release() return hr
;
;
}
// Get the requested interface,
hr = pA->QueryInterface(iid, ppv)
pA->Release( return hr
)
;
;
;
}
II LockServer
HRESULT
stdcall CFactory:
:
LockServer(BOOL bLock)
{
if (bLock)
I W:
{
InterlockedIncrement(&g_cServerLocks)
;
}
else {
Interl ockedDecrement(&g_cServerLocks
)
;
}
return S_OK
;
}
(continued)
190
EIGHT:
AGGRGATE\CMPNT1
Component Reuse: Containment and Aggregation
continued
iiiiiiiiiiiiiiii II II iiiiiii nil mil mil II iiiiiiii mil III II II
Exported functions
//
STDAPI DnCanUnloadNowO {
if
(
(g_cConiponents == 0) && (g_cServerLocks == 0))
{
return S_OK
;
}
el se {
return S_FALSE
;
n
)
}
^
// // Get class
factory.
//
STDAPI DllGetClassObjectCconst CLSID& clsid. const IID& iid, void** ppv) {
//
Can we create this component?
if (clsid
!=
CLSID_Componentl)
{
return CLASS_E_CLASSNOTAVAILABLE
:
}
// Create class factory. CFactory* pFactory = new CFactory if (pFactory == NULL)
:
{
return E_OUTOFMEMORY
;
}
II Get requested interface. HRESULT hr = pFactory->QueryInterf ace(i id, ppv) pFactory->Release( ) ;
return hr
;
;
4 (continued)
191
,
INSIDE COM
AGGRGATE\CMPNT1
continued
II
II Server registration //
STDAPI DllRegisterServerO {
return RegisterServer{g_hModu1e,
CLSID_Componentl, g_szFriendlyName. g_szVerIndProgID, g_S2ProgID)
;
}
STDAPI DllUnregisterServerO {
return UnregisterServer(CLSID_Componentl
g_szVerIndProgID, g_szProgID)
;
}
lllllllllllllllllllllllllllllllllllllllllllllllllllllllllll II
II DLL module information //
BOOL APIENTRY
Dl
lMain{HANDLE hModule. DWORD dwReason, void* IpReserved)
{
if (dwReason == DLL_PROCESS_ATTACH) {
g_hModu1e = hModule
;
}
return TRUE
;
}
AGGRGATE\CMPNT2 //
// Cmpnt2.cpp
-
Component
2
//
#include y;finclude
Listing 8-4.
The implementation of the inner (aggregated) component.
192
(continued)
EIGHT:
AGGRGATE\CMPNT2 //include "If ace.
Component Reuse: Containment and Aggregation
continued
h"
//include "Registry.
h"
void trace(const char* msg)
{
cout QueryInterface(i1d. ppv)
;
}
This procedure
is
called blind aggregation because the outer
blindly passes interface IDs to the inner
component.
component
When you
use blind
aggregation, the outer component relinquishes control over which of the inner
component's interfaces use blind delegation.
it
One
client. In
most cases, you should not
that the inner
component might support
exposes to the reason
is
interfaces that are incompatible with the interfaces supported by the outer
component. For example, an outer component might support an interface for
component supports a different interface Suppose the client always asks for IFastFile before asking (or ISlowFile. If the outer component blindly aggregates the inner component, the client will get the IFastFile pointer to the inner component. The inner component doesn't know anything about the outer component, so it will not correcdy save any information associated with the outer component. The easiest way to avoid these conflicts is to avoid using blind delegation.
saving
files, ISlowFile,
for saving
while the inner
files, IFastFile.
201
N S
I
D E
COM
If complete
abstinence seems a
ways to avoid these
conflicts. First,
little
drastic,
you can use two less drastic the outer component,
when implementing
don't include interfaces that are likely to duplicate the functionality of interfaces belonging to the inner
ponent and the matched pairs.
component. Second, you can build the outer com-
client or the outer
component and
the inner
component as
Metainterfaces Interfaces that cause conflict between the inner
component are that the outer
component and
the outer
usually interfaces that overlap in functionality. If you ensure
component's interfaces don't overlap
inner component's,
it's less
in functionality with the
likely that the interfaces will conflict.
This
is
not
component could be upgraded to support new interfaces that the outer component didn't expect. Interfaces that are the least likely to conflict with existing interfaces on the component are metainterfaces, or class interfaces. Metainterfaces manipulate the component itself and not the abstraction that the component implements. For example, suppose we have a bitmap-morphing program. The user can modify a bitmap using a variety of morphing algorithms. These morphing algorithms are implemented as inner components that the user can add to the system at run time. Each inner component can input and output bitmaps and morph them according to its own particular algorithm. The outer component has an interface ISetColors that sets the colors with which the inner components work. The outer component also has an interface IToolInfo that displays icons for the morphing algorithms on the toolbar and creates a contained component when the user selects its icon. See Figure 8-7. ISetColors is an example of a normal interface that extends the abstraction of the morphing algorithm. A morph component probably already supports an interface, such as IColors, for manipulating its color set. The second interface, IToolInfo, is an example of a metainterface. All of its operations provide the application with a way to treat the morphing algorithms as tools. They have nothing to do with morphing bitmaps. This interface doesn't extend the abstraction of the morph component but provides the client with information about the component itself. Metainterfaces are most useful when implemented on a set of different components in a system of components. The metainterfaces give the system a uniform way of manipulating its components, thus increasing code reuse through polymorphism. Adding metainterfaces to existing components is most often done by developers of the client. Using metainterfaces, the client can get components from a variety of different sources and treat them all the same. a simple task because the inner
202
EIGHT:
Component Reuse: Containment and Aggregation
Outer Component
n
Morphing Tool IToollnfo
ISetColors Inner
Component
Morph Algorithm IMorph /Colors
Figure 8-7. Interface
IToollnfo
is
a metainterface that
operates in the
is
unlikely to conflict with the
not a metainterface and domain of the inner component. ISetColors conflicts with
interfaces in the inner component.
ISetColors
is
the
IColors method in the inner component.
Matched Pairs The other way to avoid
interface conflicts
is
component or and outer comare implemented
to give the inner
the client knowledge of the outer component. If the client
ponent are
built together, the client
knows which
interfaces
by the outer component and can use those interfaces instead of any interfaces that the inner
component might implement.
If
the inner and outer components
are built together, they can easily be designed not to have conflicting interfaces.
However, these matching pair methods require that you have control over the inner component or die client in addition to control over the outer component.
Aggregation and Containment
in
the Real World
We have now seen how to reuse components using containment and aggregation. All
is
the outer ize a
not peaches and cream, however,
component
member
function.
It
when you reuse components.
only a client of the inner component,
it
Since
can special-
function by calling other functions only before and after that
can't insert
a particular
is
new behavior into the middle of the function. Also, since can set or change an internal state of a compo-
member function
nent that the client can't access, you can't replace the entire implementation of a particular function in an interface.
203
INSIDE COM
you use an interface to open a file, you must call that same interface to close the file because you don't know anything about the implementation of the interface or any of its functions. Again, the outer component doesn't have any special access or abilities superior to those of a normal cliFor example,
ent.
The outer component
correctly, just like
tate
to
if
has to use the inner
any other
component consistently and
client.
To overcome these shortcomings, a component must be designed to facilireuse. The same is true of C++ classes, which are difficult, if not impossible,
extend or specialize without proper design. C++
zation have protected at internal state
interfaces to
classes
designed for customi-
member functions that are used by the derived class to get
information about the base
make themselves
class.
COM components can use
easier to contain or aggregate.
Providing Information About Internal States You do everything
in
COM using an interface. Therefore, the inner compo-
nent can supply internal state information about itself by adding another interface. This interface would supply the outer component with information or services to help you customize the component. See Figure 8-8. There is nothing magic about internal state interfaces. They are normal interfaces that happen to provide controlled access to the inner component's internal state to simplify customization or maybe even to make it possible. Normal clients could use internal state interfaces if they wanted to, but in most cases these interfaces would not be useful to a normal client. Outer Component
IX
The outer component uses llnternalState
to get
extra information from
the inner component.
llnternalState provides
the outer
component
with information about this
component's
internal state.
Figure 8-8. Inner components can simplify customization by providing interfaces that give the outer
204
component a peek into
their internal state.
EIGHT:
Component Reuse: Containment and Aggregation
Interfaces that supply the outer
are important in
component with
this
kind of information
COM. COM components are generally made up of many small
interfaces. Internally, the
implementations for these interfaces are interdepen-
same member variables and other internal state conditions. This means that you can't choose just a single interface from the component to use by itself because that interface might be dependent on information from or the state of one of the other interfaces to control some aspect of the component's internal state. See Figure 8-9 below. Once again, the outer component is just a client of the inner component. It doesn't have any special dent, sharing the
capabilities.
Interfaces That Share
Independently Implemented Interfaces
IX
implementation lY
lY
\-l
implementation IZ
iJ-
implementation
The implementations
The implementations
of the interfaces
are seldom independent.
of the
interfaces are usually intertwined.
Figure 8-9.
The client views
interfaces as independent entities.
share implementation details. This makes specialize components.
it
more
However, interfaces generally difficult to
extend
Adding interfaces to give components an
and
internal view
can simplify customization.
Simulating Virtual Functions Adding
interfaces can not only provide a
member functions,
it
COM equivalent to C++ protected
can also enable interfaces to replace virtual functions.
used as callbacks. A base class can call a virtual function before, during, or after an operation to give the derived class an opportunity to customize the operation. COM components can do the same thing by defining a customization interface. The component doesn't implement In
many
cases, virtual functions are
this interface
but instead
calls
the customization interface. Clients that want to
customize the component implement the customization interface and pass pointer to the component.
The
its
client can use this technique without using
containment or aggregation. See Figure 8-10 on the following page.
205
INSIDE COM
Component
Client
The
client
uses
IX.-
The component
calls
ICustomize to give the
/Customize
o-
I
a chance to customize the
client
behavior of
IX.
Figure 8-10.
The components define an outgoing interface that
it
calls to facilitate
customization.
The only real difference between
inheritance and using a callback or an
that in the latter case you must manuconnect the client to the component. We'll examine this technique in Chapter 13. When using this technique, you have to be careful of circular reference counts. Generally, you want to implement ICustomize sls part of its
outgoing interface such as ICustomize
is
ally
own component with
its
own
separate reference count.
Nice components supply potential customizers with internal
state infor-
mation and customization interfaces. Really nice components supply default implementations for the customization interfaces. These default implementations save the client time implementing an interface because the client can aggregate the default implementation. There you have it. Virtually everything offered by implementation inheritance can be re-created using interfaces, containment, and aggregation. It's obvious that C++ implementation inheritance is more convenient than the COM solution pictured in Figure 8-11. However, C++ implementation inheritance requires the source code to the base class, which means that the program has to be recompiled when the base class changes. In a component architecture where components are in constant asynchronous change and the source code is unavailable, recompiling is not an option. By making component reuse the same as component use, COM reduces the interdependency
between a component and the component
206
it
specializes.
EIGHT:
Component Reuse: Containment and Aggregation
Component
Client
'The component calls a customization interface that
allows the client
ICustomize
i
The component
J-K>
calls
ICustomize.
customize its implementation
to
oi IX.
The
client's
ICustomize Default
implementation contains
Custom Component The component
or aggregates the
provides a
default customization
customization
component provided by
component that provides a default
the component.
implementation of ICustomize.
Figure 8-11.
The component provides a default implementation of the outgoing interface.
Summary We have seen how components can be reused, extended, and specialized using containment and aggregation. Reusing a component is as simple as containing the component in another component. In this case, the outer component is a client of the inner component. If you want to specialize the component, you can add code before and after you call the member functions belonging to
its
interfaces. If
you are only going
to
add
interfaces to a
component, you can use
aggregation instead of containment. Aggregation, as I'm fond of saying,
When
is
a
component aggregates an interface belonging to the inner component, the outer component passes the interface pointer directly to the client. The outer component doesn't special version of containment.
reimplement the interface or forward
the outer
calls.
207
INSIDE COM
A component cannot be aggregated unless aggregation. faces.
implemented to support lUnknown interimplements the lUnknown member it is
The inner component must have two
One lUnknown
interface actually
different
The oihex lUnknown delegates function calls to the nondelegating unknown when the component is not aggregated and to the lUnknown belonging to the outer component when it is aggregated. functions.
Containment and aggregation provide robust mechanisms for reusing and customizing components. These techniques replace the need for implementation inheritance
in
COM architectures. Implementation inheritance
is
not desirable in component systems in which clients must be isolated from the
implementations of their components.
mentation details of the components
from the implemust be rewritten,
If a client isn't isolated it
uses, the client
when the component changes. we learned how to reuse components. In the next chapter, we're going to look at a different kind of reuse: instead of reusing components, we'll reuse C++ source code. We'll implement base classes for lUnknown and for IClassFactory from which our components can be derived. recompiled, or relinked In this chapter,
about reuse has made me hungry. I think edible rich people really are. I hear that they are crunchy. All this talk
208
I'll
find out
how
-;U
CHAPTER NINE
Making INo
It
Easier
matter what people do, they want
when
it
to
be easier
—
especially,
it
seems,
they are exercising, which could defeat the whole purpose of exercise.
Television
is full
of apparendy successful advertising for ab cruncher machines
The ab cruncher machine mechanizes one of the simmaking it even simpler to execute. (The best argument for the machine is that it helps concentrate your effort in your abs and not in your neck or elsewhere. Who knows!) Since everyone is so interested in making things simple, I'll follow the trend and make using and implementing COM components easier. Writing a COM component is harder than doing ab crunches, and you are neither more virtuous nor in better shape if you do it the hard way. First, we'll use C++ classes to make using COM components more like using C++ classes. Second, we'll create some C++ classes that do the grunt work and other such
devices.
plest exercise ideas in existence,
of implementing components.
Client-Side Simplification
COM components is not as you have reference counting. If you forget to call AddRef for a pointer, you can kiss your weekend goodbye. With an incorrect reference count, you might attempt to access an interface I
don't need to convince
many of you
simple as using your typical C++
Compiler Support for
that using
class. First,
COM
The Microsoft Visual C++ version 5.0 compiler adds extensions to the C++ language to simplify developing and using COM components. These extensions are currendy imder development, so refer to the oudine docu-
mentation for Visual C++ version 5.0 for more information.
211
INSIDE COM
pointer on a
component that has already released
itself,
crash. Finding the missing AddRef or Release isn't easy.
and
that can cause a
To make matters worse,
the component might not always get released at the same point in the program each time you run it. While I really enjoy hunting an intermittent bug in the company of a pizza and a couple of friends, I have a feeling that not many
people share this passion. Even if you do call Release when you should, your program might not. C++ exception handlers don't know anything about COM components. Therefore, exceptions don't call Release to clean up COM components after an exception has occurred. HsLndling AddRef dind Release simply and correctly
We
need
also
to streamline
our code for calling
is
only part of the batde.
Querylnterface.
As I'm sure
you've noticed in the examples, a single Querylnterface call takes several lines of code.
It's
easy for a couple of Querylnterface calls to overshadow the real code
in a function.
I
avoid this problem by caching interface pointers instead of
querying for interfaces when
I need them. This improves performance and code readability at the expense of memory. But Querylnterface has another even worse problem it's not type safe. If you garble the parameters you pass to Querylnterface, the compiler won't help you out. For example, the following code compiles fine even though it assigns an lY interface pointer to an IZ
—
interface: IZ* pIZ
:
pIX->QueryInterface(IID_IY. (void**)&pIZ)
The
evil void
:
pointer strikes again, hiding types from our friend the compiler.
We can fix these problems through encapsulation. Either we can encapsulate the interface pointer with a smart pointer class, or
the interface
itself with
a wrapper
class. Let's
we can encapsulate
examine techniques,
starting with
smart pointers.
Smart Interface Pointers The
first
way
to simplify the client
is
to access
components through smart
interface pointers instead of through ordinary interface pointers. Using a
smart interface pointer
is
the
same
as using a
normal C++ pointer. The smart
interface pointer hides the reference counting.
pointer goes out of scope, the interface interface similar to using a
212
C++
object.
is
When
released. This
the smart interface
makes using a
COM
NINE:
What
A
Is
Making
It
Easier
a Smart Pointer?
smart pointer
is
a class that overrides operator->.
contains a pointer to another object.
The smart pointer
class
When the user calls operator-> on a smart
pointer, the smart pointer delegates, or forwards, the call to the object pointed
by the contained pointer.
to
A smart interface pointer is a smart pointer that
contains a pointer to an interface. Let's look at a simple example. CFooPointer has the bare
needed
to
be a smart pointer
class. It
contains a pointer, and
it
minimum overrides
operator->. class CFoo {
public: virtual }
void
BarO
;
:
class CFooPointer {
public:
CFooPointer(CFoo* p) { m_p = p ;) CFoo* operator->{) { return rn_p ;} private: CFoo* m_p ;
}
;
void FunlBar()
spFoo->Bar()
;
;
}
In the example above, the Funky function creates a CFooPointer nzmed spFoo
and
initializes
it
with pFoo.
The
client
then dereferences spFoo to
call
the
Bar function. The pointer spFoo delegates the call to m_p, which contains jb/bo. With SpFoo, you can call any function implemented in CFoo. The cool thing is that you don't need to explicitly list all of the members oiCFoo in CFooPointer. To Cfoo, the function o/)^fl^or-> means "dereference me." To CFooPointer, the function operator-> mea.ns "don't dereference
me — dereference Tn_p instead."
See Figure 9-1 on the following page.
213
INSIDE COM
Client Client
Smart pointer
code
class
The
Component
client calls
interface directly
members by using
operator->.
Smart pointer class
members
.!
are
accessed using the "dot" notation.
Figure 9-1. Smart
interface pointers delegate, orforward, calls to
an
interface pointer
embedded in the class.
CFooPointer
is
pretty stupid for a smart pointer.
It
doesn't do anything.
A good optimizing compiler can probably optimize CFooPointer into oblivion. CFooPointer doesn' t act very
to m_p.
much
like a
pointer either. Try assigning pFoo to
doesn't work because operator= isn't overridden to assign the value
spFoo. It
To convince
the client that a CFooPointer
is
the
same
as a pointer to
bunch of other operators. These include and operator&, which need to operate on the contained ot_/) pointer and not on the CFooPointer object itself. CFoo, CFooPointer needs to override a
operator*
Implementing the Interface Pointer Class There aren't as many smart pointer classes for manipulating COM interfaces as there are string classes, but it's a close race. The ActiveX Template Library (ATL) has the COM interface pointer classes CComPtr and CComQIPtr. The Microsoft Foundation Class Library now has one named CIP, which it uses internally. (Look in the file AFXCOM_.H.) CIP is the full-featured kitchen sink version of a smart interface pointer
present
my own
similar to the classes in
ATL and
to
214
make
it
There
is
easier to read.
does just about everything.
my code
is
easier to read.
MFC, just not as complete. named IPtr and is implemented
My
I'll
class
is
in
My interface pointer class is PTR.H in the example. You see it in listing scare you.
class. It
here, basically because
in the file
Listing 9-1. Don't let the length of the
very litde code.
I just
included a
lot of white
space
NINE:
Template
IPtr
Making
It
Easier
PTR.H
in
// -
Smart Interface Pointer
//
Use:
IPtr spIX
//
Do not use with lUnknown;
//
IPtr
not compile.
will
//
;
IPtr Instead, use lUnknownPtr.
//
template class IPtr {
public: // Constructors
IPtrO {
m_pl = NULL
;
}
IPtr(T* Ip) {
m_pl = Ip if (m_pl
:
!= NULL)
{
m_pI->AddRef()
;
}
}
^^' IPtr( lUnknown* pl) {
in_pl
= NULL
if (pi
;
!= NULL)
{
pI->QueryInterface(*piid. (void **)&in_pl)
;
) )
1
// Destructor
-IPtrO {
ReleaseO
;
}
//
Reset
void ReleaseO {
if (in_pl
!= NULL)
{
Listing 9-1.
The smart
(continued)
interface pointer class IPtr.
215
INSIDE COM
IPtr
Template
in
PTR.H
continued
T* pOld = m_pl m_pl = NULL
;
;
p01d->Release()
;
}
}
// Conversion operator T*()
//
return
{
tn_pl
;}
Pointer operations
T& operator*()
{
T** operator&()
{
assert(m_pl != NULL) assert(m_pl == NULL)
T* operator->()
{
assert(rn_pl
!= NULL)
;
return *m_pl
:
return &m_pl
;
return nupl
;} ;} ;}
// Assignment from the same interface
T* operator=(T* pi) {
if (ni_pl
!= pi)
{
lUnknown* pOld = m_pl m_pl = pi if (m_pl
;
// Save current value.
// Assign new value,
;
!= NULL)
{
m_pI->AddRef()
;
}
if (pOld
!= NULL)
{
p01d->Release(
)
;
// Release the old interface.
}
}
return m_pl
;
}
//
Assignment from another interface
T* operator=(IUnknown* pi) {
lUnknown* pOld = m_pl m_pl == NULL
;
// Save current value.
;
// Query for correct interface.
if (pi
!= NULL)
{
HRESULT hr = pI->QueryInterface(*piid, (void**)&m_pI) assert(SUCCEEDED(hr) && (m_pl != NULL))
:
;
}
(continued)
216
)
NINE:
/Pfr
Template
in
PTR.H
if (pOld
!=
Making
It
Easier
continued
NULL)
{
p01d->Release(
)
//
;
Release old pointer
)
return m_pl
;
}
// Boolean functions BOOL operator! () { return (m_pl == NULL)
?
TRUE
:
FALSE
;}
// Requires a compiler that supports BOOL operator BOOL() const {
return (m_pl
!=
NULL)
?
TRUE
:
FALSE
;
)
P
GUID const IID& iid() //
{
return *piid
;}
private: // Pointer variable T* m_pl ;
}
;
Using the Interface Pointer Class Using an instance of IPtr is easy, especially for a template
class. First, you create and a pointer to its IID. (We'd like to use a reference as the template parameter, but most compilers don't support this.) Now we can call CoCreatelnstance to create the component and get its pointer. You can see in the example below how effectively IPtr emulates a real pointer. We can use operator& on an IPtr a.s if it were a real pointer:
the pointer by passing the type of the interface
void mai n( {
IPtr spIX HRESULT hr = :CoCreateInstance(CLSID_Componentl :
:
,
NULL,
CLSCTX_ALL,
spIX.iidO. (
if
void**)&spIX)
;
(SUCCEEDED(hr))
{
spIX->Fx()
;
} }
217
INSIDE COM
The preceding call
to CoCreatelnstance
is
not type
but we could make
safe,
type safe by defining within the IPtr template another function:
it
HRESULT CreateInstance(const CLSID& clsid, c^ sctx)
lUnknown* pi,
DWORD
{
Releasee) return CoCreateInstance(cl sid, pi, clsctx, ;
*piid,
(void**)&m_pI
)
;
)
You would use
like this:
it
IPtr spIX HRESULT hr = spIX CreateInstance(CLSID_Componentl :
.
,
NULL,
CLSCTX_INPROC_SERVER)
By the way, that
I
I
like to
use the prefix sp for
my
;
smart pointer variables so
know they are smart pointers and not intellectually challenged pointers.
Reference Counting In this example, the coolest thing about the smart pointer to
remember
to call Release.
automatically calls Release in will also
C++
be released
if
When
its
destructor. As an
an exception
that we don't have
is
the smart pointer goes out of scope,
is
added
it
benefit, the interface
thrown, because the smart pointer
is
a
object. If
you want
shouldn't
to release the interface pointed to by a smart pointer,
call Release
members
on the smart
pointer.
you
The smart pointer doesn't know
—
you are calling on the pointer it just blindly delegates to them. So the interface could be released, but the smart pointer will still have the
a
that
non-NULL value
we'll get
for
its
pointer variable.
If we try to
use the smart pointer,
an access violation.
when you want to implement a Release func-
Different smart pointers have different ways to signal release the interface. tion,
which you
spIX. Releasee
)
call
Most of them, including
IPtr,
using the "dot" notation instead of operator->:
;
Another way to release an interface on IPtr is spIX = NULL
To
see
why
operator.
218
to assign
it
the
NULL value:
:
this
works,
we look next
at
how
IPtr overrides the
assignment
NINE:
Making
It
Easier
Assignment The
IPtr class overrides operator= so that
to the
an interface pointer can be assigned
contained pointer:
T* operator=(T* pi) {
if
(ni_pl
!= pi)
{
lUnknown* pOld = m_pl m_pl = pi if (ni_pl != NULL)
;
:
{
ni_pI->AddRef()
;
}
if
(pOld
!=
NULL)
{
p01d->Release(
)
;
} }
return m_pl
;
}
Notice two interesting aspects of the implementation of operator=. it
calls Adrfi?^/
and
Release for us so that
interface pointer releases
its
we don't have
Second, the smart
new pointer. component from memory before we make
current pointer
This prevents the removal of the
to.
First,
after
it
assigns the
the assignment.
The code fragment below assigns the pointer value pIXl to the m_p member of spIX. The assignment operator calls AddRef on the pointer in the process. After using spIX, the
code assigns pIX2
operator= function releases the contained pointer,
pIXl, stores pIX2,
and
void FuzzyCIX* pIXl.
calls
AddRef on pIX2.
IX*
pIX2)
to spIX.
The overloaded
which points
to interface
{
IPtr spIX; spIX = pIXl ;
spIX->Fx()
;
spIX = pIX2
spIX->Fx()
:
:
}
219
INSIDE COM
supplied a conversion operator so that you can assign an ZPfr object to another /Pfr object of the same type. For an example of how that works, look I
at this:
typedef IPtr SPIX SPIX g_spIX void Wuzzy(SPIX spIX)
:
;
{
g_spIX = spIX
:
}
This assignment operator works only Notice the use of typedef to
make
if
both pointers are of the same
type.
the code easier to read.
Unknown Assignments As you remember, one of our goals is to simplify calling Querylnterface. This can be done with another overload of the assignment operator. T* operator=(IUnknown* plUnknown)
If you assign
;
an interface pointer of a different type to a smart pointer,
the assignment operator automatically calls Querylnterface for you. For ex-
ample, in the following code fragment, an lY pointer is assigned to an IX smart pointer.
ceeded.
Remember to check the pointer to see whether the assignment sucSome smart interface pointer classes throw an exception if a Query-
Interface call fails.
void WasABeardY* pIY) {
IPtr spIX spIX = pIY
;
:
if (spIX) {
spIX->Fx()
;
}
}
Personally,
I
don't like the assignment operator calling Querylnterface.
of the rules of overloading
behaves the same way as the
is
to
make
One
sure that the overloaded operator
built-in operator.
This
is
clearly
not the case for
an overloaded assignment operator that calls Querylnterface. A C++ assignment operator doesn't fail. But Querylnterface Cdia fail, so an assignment operator that calls Querylnterface
can
fail.
Unfortunately, the industry
is
against
me on this.
Microsoft Visual Basic
implements an assignment operator that calls Querylnterface. The smart interface pointers in ATL and MFC also overload the assignment operator to call Querylnterface.
220
NINE:
Making
It
Easier
mterface_cast don't like hiding timeIsClassID(clsid))
;
{
//
Found the ClassID in the array of components we can
// create. //
So create
a
class factory for this component.
Pass the CFactoryData structure to the class factory
// so that it knows what kind of components to create. *ppv = (lUnknown*) new CFactory(pData) if (*ppv == NULL) ;
{
return E_OUTOFMEMORY
;
}
return NOERROR
;
} }
return CLASS_E_CLASSNOTAVAILABLE
;
}
Listing 9-5.
The implementation of CFactoTy/.GetClassOhject.
236
NINE:
Createlnstance Implementation HRESULT
in
Making
It
Easier
CFACTORY.CPP
stdcall CFactory: :CreateInstance(IUnknown* pUnknownOuter,
const IID& iid,
void** ppv) {
// Aggregate only if the requested IID is
IID_IUnknown.
if ((pUnknownOuter != NULL) && (iid != IID_IUnknown)) {
return CLASS_E_NOAGGREGATION
:
}
// Create the component. CUnknown* pNewComponent
;
HRESULT hr = tn_pFactoryData->CreateInstance(pUnknownOuter,
SpNewComponent)
;
if (FAILED(hr)) {
return hr
;
)
//
Initialize the component,
hr = pNewComponent->Init(
)
;
if (FAILED(hr)) {
Release the component. // Initialization failed. pNewComponent->Nondelegat1ngRelease( ) return hr ;
;
}
// Get the requested interface. hr = pNewComponent->NondelegatingQueryInterface(iid, ppv)
;
// Release the reference held by the class factory. pNewComponent->NondelegatingRelease( ) ;
return hr
;
}
Listing 9-6.
The implementation of CFactory::CreateInstance. That's the skinny on creating components using CFactory. All you have to
do
is
implement the components and put
their data into the structure
—and
away you go!
237
INSIDE COM
Using
CUnknown and CFactory I'm so glad that we can
and of the
now reuse implementations of the unknown interfaces
class factory.
I'm sure you're sick of seeing the same code for
and Release repeated over and over. I know I'm sick of components won't reimplement AddRef diwd Release. They'll add only the new interfaces they need to their Querylnterface. And they can implement a simple instance creation function rather than an entire new class factory. These new clients will look like the following client, shown in Querylnterface, AddRef,
it.
From now
on, our
Listing 9-7:
CMPNT2.H // // Cmpnt2.h
-
Component
2
//
#include "Iface.h" //include "CUnknown. h" // Base class for lUnknown
lllllllllllllllllllllllllllllllllllllllllllllllllllllllllll II
II Component B //
class CB
:
public CUnknown,
public lY {
public: // Creation
static HRESULT Createlnstance( lUnknown* pUnknownOuter. CUnknown** ppNewComponent)
;
private: // Declare the delegating lUnknown. DECLARE_IUNKNOWN
//
Nondelegating lUnknown
virtual HRESULT
stdcall
NondelegatingQuerylnterfaceCconst IID& iid, void** ppv) II
;
Interface lY
virtual
void
stdcall
FyO
;
// Initialization
virtual HRESULT InitO
:
{continued)
Listing 9-7.
A component that uses lUnknown implemented by
238
CUnknown.
NINE:
CMPNT2.H
Making
It
Easier
continued
II Cleanup
virtual void FinalRelease(
)
;
CBCIUnknown* pUnknownOuter)
;
// Constructor
// Destructor
~CB() //
:
Pointer to inner object being aggregated
lUnknown* m_pUnknownInner
;
// Pointer to IZ interface supported by inner component
IZ* m_pIZ
;
}
Listing 9-7 shows the header this chapter. We'll
file
for
Component 2 from our example for
look at the implementation code in a moment. In
example, Component
this
implements interface IX itself. Component 1 aggregates Component 2 in order to offer interfaces 77 and IZ. Component 2 implements TV and aggregates Component 3, which in turn implements IZ. Thus Component 2 both aggregates and is aggregated. Let's start at the top of Listing 9-7 and work our way down. I'll introduce each item of interest, and then we'll examine each of them in more detail. The component inherits from CUnknown, which supplies an implemen1
tation for lUnknown. Next,
component.
create the
so
we can
call it
After that,
we declare
a static function that CFactory uses to
CFactory doesn't
depend on the name of this function,
anything we want.
we implement the delegating unknown using the macro
DECLARE_IUNKNOWN. DECLARE_IUNKNOWN implements the delegatCUnknown implements the nondelegating unknown. While CUnknown completely implements AddRefa.nd Release, it can't fully implement Query Interface because it doesn't know the interfaces our component supports. Therefore, our component implements NondelegatingQuerying unknown, and
Interface to
handle the interfaces
it
supports.
Derived classes override Init to create other components for aggregation or containment. CUnknown: :NondelegatingRelease
Components
calls FinalRelease right be-
need to release interface pointers to inner components override this function. CUnknown: :FinalRelease increments the reference count to prevent recursive destruction of the component.
fore
it
deletes the object.
Let's
now
take a look at
implemented. Listing 9-8
is
how
that
Component Component 2.
these various aspects of
the implementation for
2 are
239
,
,
INSIDE COM
CMPNT2.CPP // // Cmpnt2.cpp
Component
-
2
//
^include y/include "Iface.h"
#inc1ude "Util.h" //include "CUnknown.h" // Base class for lUnknown #include "Cmpnt2.h" static inline void trace(char* msg)
{Util::Trace( "Component 2", msg, S_OK)
:}
static inline void trace(char* msg, HRESULT hr) {Util :Trace("Component 2", msg, hr) ;) :
lllllllllllllllllllllllllllllllllllllllllllllllllllllllllll II
II
Interface lY implementation
//
void
stdcall CB::Fy()
{
traceC'Fy")
;
}
//
//
Constructor
//
CB: :CB( lUnknown* pUnknownOuter) :
CUnknown(pUnknownOuter) m_pUnknownInner(NULL) m_pIZ(NULL)
{
//
Empty
}
//
// Destructor //
CB::~CB() {
traceC'Destroy self.")
;
}
Listing 9-8.
Implementation of a component that uses
240
(continued)
CUnknown
anrfCFactory.
,
NINE:
CMPNT2.CPP
Making
It
Easier
continued
II
II
NondelegatingQuery Interface implementation
//
HRESULT __stdcan CB: :Nonde1egatingQueryInterface(const IID& iid,
void** ppv)
{
if (iid == IID_IY) {
return FinishQI(static_cast(this)
ppv)
,
;
)
else if (iid == IID_IZ) {
return m_pUnknownInner->QueryInterface(iid, ppv)
;
}
else {
return CUnknown: :NondelegatingQueryInterface(iid, ppv)
;
} }
//
//
Initialize the component, and create the contained component.
//
HRESULT CB::Init() {
traceC'Create the aggregated component.") HRESULT hr =
;
CoCreateInstance(CLSID_Component3, GetOuterUnknown( ) CLSCTX_INPROC_SERVER. 1 1 D_I Unknown, (void**)&m_pUnknownInner)
;
If (FAILED(hr)) {
traceC'Could not create inner component.", hr) return E_FAIL
;
;
)
trace("Get pointer to interface IZ to cache.") hr = m_pUnknownInner->QueryInterface(IID_IZ, (void**)&m_pIZ) ;
;
if (FAILED(hr))
(continued)
241
INSIDE COM
CMPNT2.CPP
continued
{
traceC'Inner component does not support 11.". hr) m_pUnknownInner->Release( )
;
;
tn_pUnknownInner = NULL
return E_FAIL
;
;
)
// Decrement the reference count caused by the QI
traceC'Got
GetOuterUnknown( )->Release{ return S_OK
call
interface pointer. Release reference.")
IZ
)
;
;
// //
FInalRelease
-
Called by Release before it deletes the component
//
void CB::FinalRelease() {
// Call
base class to incremement m_cRef and prevent recursion.
CUnknown: Final Releasee :
)
;
// Counter the GetOuterUnknown( )->Release in the Init method. GetOuterUnknown()->AddRef() ;
// Properly release the pointer,
as there might be
// per-interface reference counts.
m_pIZ->Release(
)
;
// Release the contained component. //
(We can do this now since we've released the interfaces.)
if (m_pUnknownInner != NULL) {
m_pUnknownInner->Release(
)
;
} }
lllllllllllllllllllllllllllllllllllllllllllllllllllllllllll II
II Creation function used by CFactory //
HRESULT CB: :CreateInstance(IUnknown* pUnknownOuter, CUnknown** ppNewComponent) {
*ppNewComponent = new CB(pUnknownOuter) return S_OK )
242
;
;
NINE:
Making
It
Easier
NondelegatingQuerylnterface Probably the most interesting part of the component lnterface.
We
implement NondelegatingQuerylnterface
implemented
is
NondelegatingQuery-
similar to the
way we
But notice two differences. First, we use the function FinishQI, and we use it purely for our convenience; we don't have to use it if we don't want to. FinishQI ']\xs\. makes implementing Querylnterface in the previous chapters.
NondelegatingQuerylnterface in the derived class a tation oi FinishQI
is
little easier.
The implemen-
shown below:
HRESULT CUnknown: :FinishQI(IUnknown* pi, void** ppv) {
*ppv = pi
;
pI->AdclRef()
return S_OK
:
;
}
class
The second difference is that we don't have to handle lUnknown. The base handles lUnknown and any interfaces we don't know about:
HRESULT
stdcall
CUnknown: :NondelegatingQueryInterface(const IID& iid, void** ppv) {
// CUnknown supports only
lUnknown.
if (iid == IID_IUnknown) {
return FinishQI(reinterpret_cast
(static_cast(this))
,
ppv)
;
}
else {
*ppv = NULL
;
return E_NOINTERFACE
;
} )
Putting All the Pieces Together Step by Step
The example code you just saw shows how easy it is to vmte a component using CUnknown and CFactory. Let's summarize the procedure. The following is a step-by-step list of how to create a component, its class factory, and the DLL that contains
L
it:
Write the
class that
Derive the
implements the component.
component from CUnknown or from another
that derives
class
from CUnknown.
243
N S
I
D E
COM
Use the
DECLARE.IUNKNOWN
macro
to
implement the
delegating unknown.
CUnknown
Initialize
Implement
in
your constructor.
NondelegatingQuerylnterface,
that this class supports
and
that
its
adding the interfaces
base class doesn't support.
Call the base class for interfaces that
you don't handle.
your component requires initialization after construction. Set up contained and aggregated components, if
Implement Init
if
there are any.
Implement
FinalRelease
cleanup after
it
your component needs to do any
if
has been released but before
it is
destroyed.
Release pointers to any contained or aggregated components.
Implement a
static Createlnstance
Implement the 2.
Repeat Step the
3.
1
function for your component.
interface (s) supported by your
for each additional
component.
component you want
to have in
DLL.
Write the
class factory.
Create a
file
to contain the global CFactoryData array, g_Factory-
DataArray.
Define g_FactoryDataArray and
component served by
this
fill it
with information about each
DLL.
Define g^cFactoryDatoEntries, which contains the number of com-
ponents in the g_FactoryDataArray.
DEF
4.
Make
5.
Compile and
a
file
with the
link
DLL
entry points.
your code together with
CUNKNOWN. CPP and
CFACTORY.CPP. 6.
Send me postcards
This whole process five
244
minutes.
is
vsath pictures
simple.
I
of river rapids and waterfalls.
can make a new component in
less
than
NINE:
Making
It
Easier
Summary With a simple smart interface pointer class, using COM components becomes
more
like
using a C++
class,
since the smart interface pointer class can hide
reference counting. In addition to hiding reference counting, smart interface
pointer classes help reduce errors by obtaining interface pointers in a typesafe way. Interface
Many smart interface pointer classes override operator= to call Querywhen assigning a pointer from one type of interface to another.
While a smart interface pointer class makes using COM objects simpler, a couple of simple C++ classes make implementing COM components as easy as can be. The CUnknown and CFactory classes simplify building COM components by providing reusable implementations oi lUnknown and IClassFactory. With all of the contemporary emphasis on making things simpler, I'm surprised someone hasn't made breathing easier. Oh, I almost forgot, there is a company that makes devices that fit on your nose, which are reported to make breathing easier. Several professional bicyclists are using them. I guess it doesn't hurt to have a little help when you're making new things.
245
CHAPTER TEN Servers
in
EXEs
1 he wall had not yet come down crossing a boundary when
I
left
the last time
I
was
in Berlin.
I
was obviously
the American sector at Checkpoint Charlie
and
The razor wire, the minefields, and the guards carmachine guns made the crossing noticeable. But even beyond the for-
traveled into East Berlin.
rying
I saw clues: on the East side of the wall, small two-stroke cars spewed smoke and long lines of people stood outside the stores. Changes happen whenever you cross a boundary, no matter how subtle the differences between one place and another. This chapter is about crossing boundaries mainly the boundary between different processes. In this chapter, we also look at crossing the boundary between machines. Why do we need to cross the process boundary? Because in some cases, we would like to implement components as EXEs instead of DLLs. One reason for implementing components as EXEs is that your application is already an EXE. With a little work, you could expose the services of your application, and clients could then automate your application. The component and the client in different EXEs will also be in different processes, since each EXE gets its own process. Communications between the client and the component will need to cross the process boundary. Fortunately, we won't need to change the code for our component, although we do need to make some changes in the CFactory class introduced in the last chapter. However, before we look at implementation, we need to see the problems of and solutions for communicating across the process boundary with COM
tified line,
oily
—
interfaces.
Different Every space.
Processes
EXE
runs in a different process. Every process has a separate address
The logical address OxOOOOABBA in one process accesses a different memory location than does the logical address OxOOOOABBA in another
physical
247
INSIDE COM
process. If one process passed the address
OxOOOOABBA to another memory than
the second process would access a different piece of
process,
the
first
process intended. (See Figure 10-1.)
While each EXE gets its own process, DLLs are mapped in the process EXE to which they are linked. For this reason, DLLs are referred to as
of the
in-process (in-proc) servers ers.
EXEs are
also
while
EXEs
sometimes called
are called out-of-process (out-of-proc) serv-
local servers to differentiate
other kind of out-of-process server, the remote server.
them from the
A remote server is an out-
of-process server that resides in a different machine.
Process
Process 1 address space
1
Physical
memory
'
0x00001234
0X0000ABBA
Process 2
pFoo
I
Process 2 address space
m—Mm P
^1
0X0000ABBA
_ B^.
S^"""''''^]
I
I
Figure 10-1.
The same memory address in
different processes accesses different locations in
physical memory.
we discussed how important it was that the component client. The component passes an inthe client. An interface is basically an array of function pointers. The
In Chapter
5,
shared the same address space with the terface to client
must be able
to access the
memory
associated with the interface. If a
component is in a DLL, your client can easily access the memory because the component and the client are in the same address space. But if the client and component are in different address spaces, the client can't access the memory in the component's process. If the client can't even access the memory associated with an interface, there isn't any way for
it
to call the functions in
the interface. If this were our situation, our interfaces would be pretty useless.
248
TEN:
For an interface to cross a process boundary, we need
to
Servers
in
EXEs
be able to count
on the following conditions:
A process needs
to
be able to
call a
function in another process.
A process must be able to pass data to another process. The
client shouldn't
have to care whether
it is
accessing in-proc or
out-of-proc components.
The Local Procedure
Call
(LPC)
There are many options for communicating between processes, including Dynamic Data Exchange (DDE), named pipes, and shared memory. However, COM uses local procedure calls (LPCs). LPCs are a means of communication between different processes on the same machine. LPCs constitute a proprietary, single-machine, interprocess-communication technique based on the remote procedure call (RFC). (See Figure 10-2.)
The standard for RPCs is defined in Open Software Foundation's (OSF) Distributed Computing Environment (DCE) RPC specification. RPCs allow applications on different machines to communicate with each other using a varietyof network transport mechanisms. Distributed COM (DCOM), which we'll see later in this chapter, uses RPCs to communicate across the network. How do LPCs work? Magic. Actually, they aren't magic, but they are the next best thing; they are implemented by the operating system. The operating system knows the physical addresses corresponding to the logical address
space for each process; therefore, the operating system can
call
functions in
any process.
Figure 10-2.
A
client in
an EXE uses the Win32 LPC mechanism
to
call/unctions in
components in EXEs.
249
INSIDE COM
Marshaling Calling functions in an
EXE is only part of the battle. We still need to get the
parameters passed to a function from the address space of the client to the address space of the component. This is known as marshaling. According to
my
dictionary, to marshal
This word
will
If both
is
"to arrange, place, or set in
be on your vocabulary
processes are
test
on
methodical order."
Friday.
on the same machine, marshaling is a fairly straight-
forward process. The data in one process needs to be copied to the address space of the other process.
If
the processes are
on
different machines, the data
has to be put into a standard format to account for the differences between
machines, such as the order in which they store bytes in a word.
The LPC mechanism can take care of copying the data from one process But it needs more information than a C++ header file contains to be able to pack up the parameters and send them over to the other process. to another.
For example, it has to treat pointers to structures differently than it treats integers. Marshaling a pointer involves copying the structure referenced by the pointer over to the other process. However, pointer, the
memory referenced
if
the pointer
is
an interface
by the pointer shouldn't be copied. As you
is more work than a simple memcpy command to marshaling. To marshal a component, implement an interface named IMarshal. COM queries your component for IMarshal as part of its creation strategy. It then calls member functions in IMarshal to marshal and unmarshal the parameters before and after calling functions. The COM Library implements a standard version of IMarshal that will work for most interfaces. The main reason for
can
see, there
implementing a custom version of IMarshal •
Kraig Brockschmidt's book Inside
is
for optimizing performance.
OLE covers custom
marshaling in
detail.
Proxy/Stub DLLs I
haven't spent the
last
nine chapters discussing
how to
call
COM components
through interfaces only to start calling them via LPCs. One of our goals from the very beginning was to have the client communicate v^dth in-proc, local, and remote components in the same way. Clearly, if the client must worry about LPCs, this goal is not achieved. COM achieves the goal in a simple way. Unbeknownst to most Windows developers, they use LPCs almost every time they a
call a
Win32
DLL that calls,
ture keeps your
function. Calling a
Win32 function
program
in a different process
function in
from the Windows code. Since program can't trash the
different processes have different address spaces, your
operating system.
250
calls a
by using an LPC, the actual code in Windows. This architec-
TEN:
Servers
in
EXEs
COM uses a very similar structure. The client communicates with a DLL DLL does the marshaling and LPC calls for component is called a proxy. In COM terms, a proxy is a component that acts like another component. Proxies must be DLLs because they need access to the address space of the client that
mimics the component. This
the client. In
COM,
this
so they can marshal data passed to interface functions. Marshaling the data
only half the task; the
component
also requires a
DLL, named the
is
stub, to
unmarshal the data sent from the client. The stub also marshals any data that the component sends back to the client. (See Figure 10-3.)
EXE
EXE
Client
m DLL
Stub unmarshals parameters.
Proxy marshals parameters. Local procedure
call
Figure 10-3.
The client communicates with a proxy DLL. The proxy marshals thefunction and calls the stub DLL using L PCs. The stub DLL unmarshals the
parameters parameters
and calls
the correct interface function in the component,
passing it
the parameters.
Figure 10-3 greatly simplifies this entire process. However, vide this
enough information
to let you
know
it
does pro-
that there will be a lot of code to get
working.
251
INSIDE COM
Too Much Code! component into an EXE, we need to write the proxy and the stub code. We also need to learn about LPCs so that we can make calls across the process boundary. In addition, we need to implement /Mar^Aa/ to marshal the data from the client to the component and back. Sounds like too much work to me. I'd rather spend my time watching metal rust. Fortunately, we don't have to do all this work, at least not in most cases. Forget
It!
That's
Let's see, to put a
Introduction to IDL/MIDL You
more interested in writing cool components than you are bunch of code just to get the components talking to each other. I know that I'd rather spend my time writing an OpenGL program than writing code to move data from one process to another. Fortunately, we don't have are probably
in writing a
remoting code ourselves. By writing a description of the interfaces in named IDL (Interface Description Language), we can use the MIDL compiler to generate the proxy and stub DLLs for us. Of course, if you want to do everything yourself, you are more than welcome. That's one of the beauties of COM: it provides a default implementation for most things but allows you to implement your own interface if you want to do it yourself. But most interfaces don't need custom marshaling, and most components don't need handcrafted proxies and stubs. Therefore, we're going to do things the easy way. Of course, "the easy way" is a relative term, and it still takes work on our part. to write
a language
The IDL language, like the UUID design and the RFC specification, was borrowed from Open Software Foundation's (OSF) Distributed Computing Environment (DCE). With a syntax resembling that of C and C++, IDL richly describes the interfaces and data shared by the client and the component. While COM interfaces use only a subset of IDL, they do require several nonstandard extensions that Microsoft added to support COM. At Microsoft, we always feel we can improve on a standard. After describing your interfaces and components in IDL, you run them through the MIDL compiler (which is Microsoft's IDL compiler). The MIDL compiler takes the IDL description of your interfaces and generates C code for the proxy and stub DLL. Just compile and link these C files, and you have a DLL that implements the proxy and stub code for you! Believe me, this is much better than doing things the hard way.
252
TEN:
Servers
EXEs
in
About IDL Although you have been saved the effort of learning about LPCs, you still need to learn how to describe your interfaces in IDL. Describing your interfaces in IDL isn't difficult, but it can be very frustrating because IDL is not consistent, the documentation is poor, good examples are hard to find, and the error messages are somewhat cryptic. My favorite is, "Try to find a work around." Because IDL is going to save you a lot of time and work, we won't complain about it (for long) My advice is to set aside a day and read the IDL documen.
tation
MSDN CD. The documentation is dry and boring, but it's a lot
on the
better to read
it
in
how
to figure out
advance than to wait until the night your interface
to describe
it
You might be tempted at this point to do You can
advantage to
it.
also build type libraries by
In short,
it's
a
MIDL compiler to build type
hand, but there
much better use of your time
which you get both the proxy code and the type
Example Interfaces
any from
really isn't
to learn IDL,
libraries at the
same
time.
IDL
in
Let's take a look at
in IDL. Below is an excerpt from the Chapter 10 example.
an interface written
SERVER.IDL included
file
due
things the hard way instead of
using IDL. But in the next chapter, we'll use the libraries.
is
in IDL.
import "unknwn.idl"
in the
;
Interface IX
// [
object,
Uuid(32bb8323-b41b-llcf-a6bb-0080c7b2d682), helpstringC'IX Interface"), pointer_default( unique) ]
interface IX
:
lUnknown
{
HRESULT FxStringIn([in, string] wchar_t* szln)
;
HRESULT FxStringOut([out, string] wchar_t** szOut) }
;
:
In C++, the functions belonging to this code would look like virtual virtual
HRESULT HRESULT
stdcall FxStri ngln(wchar_t* szln) FxStringOut(wchar_t** pszOut)
stdcall
this:
;
;
253
INSIDE COM
You can see that MIDL syntax isn't that much different from that of C++. The most obvious difference is the information delimited by the square brackEach interface has an attribute
header before the interface body. In the preceding example, the interface header has four entries. First, the keyword object means that this interface is a COM interface. The object keyword is a Microsoft extension to IDL. The second keyword, uuid,
ets ([]).
specifies the IID for this interface.
The
Hst or interface
third
keyword
is
used
to
put a help string
into a type library. Stay tuned. We'll cover type libraries in the next chapter
because they aren't directly related to out-of-proc servers. The fourth keyword, pointer_default,
is
a
little
more confusing, and
we'll talk
about that next.
The pointer_default Keyword One purpose of IDL is to supply sufficient information so that function parameters can be marshaled. To do this, IDL needs information about how to treat such things as pointers. The pointer_default keyword tells the MIDL compiler how to treat pointers if no other attribute is given for a pointer. The keyword has three different options:
pointer_default
—Pointers are treated as references. They will always point to a
ref-
and can always be dereferenced. They can't be NULL. They point to the same memory before a call as they do after a call. They also can't be made into aliases within the function. valid location
unique
—These pointers can be NULL. They can also change
within a function. However, they can't be
made
into aliases within
the function.
—This option specifies that the default pointer
ptr
the
C
pointer.
The pointer can be an
alias, it
is
can be
equivalent to
NULL, and
it
can change.
MIDL uses these values to optimize the proxy and stub code that it generates. In
and Out Parameters
MIDL
also uses the in
and
in
IDL
out
parameter attributes
to further
optimize the
proxy and stubs. If a parameter is marked as in, MIDL knows that the parameter needs to go only from the client to the component. The stub code does not need to send any information back. The out keyword tells MIDL that the
parameter is used only to return data from the component to the client. The proxy doesn't need to marshal an out parameter and send it to the component.
254
TEN:
Servers
in
EXEs
Parameters can also be marked using both of these keywords: HRESULT foo([in] int
Strings it.
It's
int* z)
[out]
;
is
MIDL
IDL
in
To marshal
all
int* y,
both an in and an out parameter. The out parameters be pointers.
In the preceding fragment, y
compiler requires that
out]
[in,
x,
you need to know how big it is so that you can copy how long a C++ string is; search for the ending null
a piece of data,
easy to determine
By placing the string modifier on the function, MIDL knows that the parameter is a string and that it can determine the length of the string by character.
looking for the terminating null character.
The standard convention handle Unicode. This its
COM for strings
is
to use
strings.
LPOLESTR, which
HRESULTs
in
is
the reason that the preceding example uses the iuchar_t
But instead of using are defined in the
wchar_t,
you can use
IDL
page 253 return an the
OLECHAR or
COM header files.
You'll also notice that both of the functions in the interface
marked with
Unicode charac-
even on systems such as Microsoft Windows 95 that don't natively
ters, wchar_t,
type for
in
HRESULT. MIDL
object
modifier return
IX in the code on
requires that functions in interfaces
HRESULTs. The main reason
for this
restriction is for the sake of remote servers. WTien you are connecting to a remote server, any function can fail due to network problems. Therefore, you must have a way for all functions to signal a possible network failure. The easiest way is to have all functions return an HRESULT. For this reason, most COM functions return an HRESULT. (Many people write wrapper classes for COM interfaces that throw exceptions when a mem-
ber function returns a failure code. In
fact,
5.0 compiler can import a type library
members a wrapper class
the Microsoft Visual
and automatically generate for its on failed HRESULTs.) If
that will throw exceptions
a function needs to return a value that isn't an is
used
in
its
C++ version
place. In FxStringOut,
HRESULT, an
an out parameter
is
out parameter used to return a string
from the component. The function FxStringOut sAlocdites the memory for the string using CoTaskMemAlloc. The client must deallocate the memory with CoTaskMemFree. The following code from CLIENT.CPP in the Chapter 10 example demonstrates how to use interface IX as defined on page 253.
255
INSIDE COM
wchar_t* szOut = NULL HRESULT hr = pIX->FxStri ngln( L"Thi assert(SUCCEEDED(hr)) hr = pIX->FxStringOut(&szOut) ;
s
is
the test.")
;
;
;
assert(SUCCEEDED(hr))
;
// Display returned string. ostrstream sout sout FyArray In(si zein arrayln) ;
assert(SUCCEEDED(hr)) //
;
;
,
;
Get the array back from the component.
Get the size of the array, long sizeOut = //
;
hr = pIY->FyCount(&sizeOut)
assert(SUCCEEDED(hr))
;
;
Allocate the array. long* arrayOut = new long[sizeOut] //
;
(continued)
257
INSIDE COM
Get the array.
//
hr = pIY->FyArrayOut(&sizeOut,
assert(SUCCEEDED(hr))
arrayOut)
;
;
// Display the array returned from the function, ostrstream sout ;
sout m_pICl assFactory = pIFactory pData->ni_dwRegister = dwRegister
//
;
;
}
return TRUE
;
}
This code uses two class.
The
new member variables I've added
to the CFactoryData
variable m_pIClassFactory holds a pointer to the running class fac-
tory for the Class ID in m_pCLSID.
The variable
in_dwRegisterho\ds the magic
cookie' for this factory.
1.
Someone
told
me
that "cookie" wasn't an industry term, but a Microsoftie term.
I
don't
know
most Web browsers store cookie files on your drive. Anyway, at Microsoft we use the term cookie to refer to a data structure that identifies something. The client asks the server for a resource. The server allocates the resource and passes the client a cookie, which the client can use to identify the resource in the future. As far as the client is concerned, the cookie is a random number with no meaning except to the server. that
266
it is,
especially since
,
,
TEN:
As you can
see, registering the class factories
ing the class factory and passing
its
is
Servers
in
EXEs
a simple matter of creat-
interface pointer to CoRegisterClassObject.
Most of the parameters of CoRegisterClassObject are
easily
deciphered from the
A reference to the CLSID of the class being registered comes first, followed by a pointer to its class factory. A magic cookie is passed back preceding code.
in the last parameter. This
magic cookie
registered with CoRevokeClassObject.
is
The
used when the
third
class factory
is
un-
and fourth parameters are
DWORD flags that control the behavior of CoRegisterClassObject. CoRegisterClassObject Flags The third and fourth parameters to CoRegisterClassObject are used together, and the meaning of one of the parameters changes depending on the other parameter. This makes deciphering these parameters very confusing. The fourth parameter reports whether a single instance of your EXE can serve more than one instance of a component. The easiest way to think about this is to compare your EXE component server to an SDI (single document interface) application.
EXE
to load multiple
An SDI application
an SDI application
MDI applicaEXE component server is similar
documents, while a single instance of an
tion can load multiple documents. If your to
requires multiple instances of the
in that
it
can serve only a single component, you must
REGCLS_SINGLEUSE and CLSCTX_LOCAL_SERVER. If your EXE component server can support multiple instances of its components the way an MDI application can support multiple documents, you use REGCLS_MULTI_SEPARATE: use
hr =
:
:CoRegisterClassObject(c1sid, plUnknown, CLSCTX_LOCAL.SERVER, REGCLS_MULTI_SEPARATE, SidwRegister)
;
This causes an interesting situation. Suppose we have an
EXE
that reg-
some components. Suppose this EXE needs to use one of the components that it is registering. If we registered the class factory using the statement above, another instance of our EXE would be loaded to serve our EXE its own component. This clearly is not as efficient as we would like in most cases. To register the EXE server as the server for its own in-proc components, combine the isters
CLSCTX_INPROC_SERVER flag with
the
CLSCTX_LOG\L_SERVER, as you
see here: hr =
:
:CoRegi sterCl assObject(cl Sid,
plUnknown,
CLSCTX_LOCAI_SERVER CLSCTX_INPROC_SERVER) REGCLS_MULTI_SEPARATE &dwRegister) |
;
267
)
INSIDE COM
EXE server can serve itself its own incommon case, a special flag, REGCLS_MULTIPLEUSE, is used to automatically enable CLSCTX_INPROC_SERVER when CLSCTX_LOCAL_SERVER is supplied. The following is equivalent to By combining these two
proc components. Since
the previous hr =
:
this
is
flags,
the
the most
call:
:CoRegi sterClassObjecKcl sid
,
plUnknown,
CLSCTX_LOCAL_SERVER. REGCLS_MULTIPLEUSE. &dwRegister)
;
By modifying the example program, you can see the difference between
REGCLS_MULTIPLEUSE and REGCLS_MULTI_SEPARATE. ter the in-proc server with this
First,
unregis-
command:
regsvr32 /u server.dll
This ensures that the only server available client
and select the second option
Then run the The local server will
the local server.
is
run the
to
local server.
CMPNTl.CPP and CMPNT2.CPP that we created the inner component using CLSCTX_INPROC_SERVER, but here we just unregistered the in-proc server. Therefore, the EXE is providing itself run
fine.
Notice in the
/n?7
functions in
the in-proc versions of these components.
Now change REGCLS.MULTIPLEUSE to REGCLS_MULTI_SEPARATE in CFactoryr.StartFactories.
(The
lines to
change are marked
in
CFACTORY.CPP with
@Multi) After rebuilding the client and server, run the client and select the
ond option. The create call fails because proc server to
satisfy their creation,
the inner
sec-
components don't have an
and REGCLS_MULTI_SEPARATE
COM Library not to satisfy requests for in-proc components with
tells
in-
the
itself.
Hold the Presses and Stop the Factories
When the server shuts down, it must remove the class factories from the internal table. CFactory
The
COM Library function
member function
class factories
CoRevokeClassObject does this job.
StopFactories calls CoRevokeClassObject for all
The
of the
supported by the EXE:
void CFactory: :StopFactories( {
CFactoryOata* pStart = &g_FactoryDataArray[0]
;
const CFactoryOata* pEnd =
&g_FactoryDataArray[g_cFactoryDataEntries for (CFactoryOata* pOata = pStart
;
1]
;
pData m_dwRegister ;
if (dwRegister != 0) { :
:CoRevokeClassObject( dwRegister)
;
}
// Release the class factory.
= pData->m_pIClassFactory
IClassFactory* pIFactory if (pIFactory
;
!= NULL)
{
pIFactory->Release(
)
;
}
} }
Notice that we pass the famous magic cookie we got from CoRegisterClassObject to CoRevokeClassObject.
Modifying LockServer In-proc servers export the function DllCanllnloadNow.
The
COM Library calls
function to see whether it can unload the server from memory. DllCanUnloadNow is implemented by the static function CFactoTy::CanUnloadNow, which checks the static variable CUnknown: :s_ActiveComponents to determine whether the server can be unloaded from memory. Whenever we create a new component, we increment the static variable CUnknown: s_ActiveComponents However, as we discussed in Chapter 7, we don't increment s_ActiveComponents when a new class factory is created. Therefore, a server can have open class factories and still allow itself to be shut down. It should now be clear why we don't count the class factories among our active components. The first thing that a local server does is to create all of its this
:
class factories. ries. If
The
last
thing that the local server does
is
to stop these facto-
the server waited for these factories to get destroyed before
it
unloaded,
would have a long wait because the server
it
unloads. Therefore, clients use the function IClassFactory:: LockServer
want
to
guarantee that a server
stays in
itself
when
has to destroy them
it
memory while
if
they
they are attempting to
create components.
We
need
servers. Let
to
make
me state
a slight modification of LockServer for use with local
the reason for
this.
DLLs
aren't in control of their
lives.
An EXE
loads a DLL, and an EXE unloads a DLL. EXEs, however, are in and can load and unload themselves. No one is going to unload the EXE because it has to unload itself. Therefore, we need to modify LockServer to quit the EXE when the lock count goes to 0. I added to CFactory a new control
269
INSIDE COM
member cation's
function CloseExe, which sends a
message loop,
WM_QUIT
message
to the
appH-
like this:
#ifdef _OUTPROC_SERVER_ static void CFactory: :CloseExe(
)
{
if (CanUnloadNowO == S_OK) { :
:PostThreadMessage(s_dwThreadID, WM_QUIT,
0)
0.
;
) }
#el se
static void CloseExe()
/*Empty*/
{
}
#endif
Notice that
this
code stupendously HRESULT
stdcall
function does nothing in in-proc servers.
effective,
I
simply
call CloseExe
from
To make
this
LockServer.
CFactory: LockServerCBOOL block) :
{
if (block) { :
:
InterlockedIncrement(&s_cServerlocks)
;
:
Inter1ockedDecrement(&s_cServerlocks)
;
}
else { :
}
If this
//
is an out-of-proc server,
check to see
// whether we should shut down.
CloseExeO return S_OK
:
;
}
We also need to call CbseExe from the destructor for our components, since this
is
another opportunity in which the
unload
itself. I
EXE can determine whether it needs to
modified the destructor of CUnknown
to
CUnknown: :~CUnknown() { :
:
//
InterlockedDecrement(&s_cActi veComponents) If this
is an
inactive EXE server, shut it down.
CFactory: :CloseExe() }
270
;
:
do
this for us:
TEN:
Servers
in
EXEs
Message Loop Message Loop Message Loop... C and C++ programs have a standard entry point named main. Execution starts larly,
main function. The program stops when the main function exits. SimiWindow programs have a WinMain function. Therefore, to keep an EXE
from
exiting,
at the
Since our
it
needs to have a loop to keep it from exiting main or WinMain. server is running on Windows, I added a Windows
component
message loop. This is a simplified version of the message loop used by all Windows programs. The message loop is contained in the file OUTPROC.CPP. This file is compiled and linked only when the out-of-proc server is being built.
Count the Users Remember back when we
ran the server before running the client? After the
memory. The users of the server are also and should get their own lock count. So when the user creates the component, we increment CFactory::s_cServerLocks. This way, the server will stay client finished, the server stayed in clients
in
memory
as
long as the user
is
using
it.
How can we determine when the user has started the server instead of the COM Library? When CoGetClassObject loads the local ser\'er's EXE, it adds the zx^umeni Embedding to the command line. The EXE checks ior Embedding on the
command line. If it doesn't find Embedding, it increments s_cServerLocks and
creates the
window for the user
to interact with.
When the user closes the server, clients could still be using its services. Therefore, the server
unless
it
should shut down
has finished serving
WM_QUrT message when returns
S_OK You can
it
its
gets a
its
UI when the user exits, but it should not exit
clients.
The
server therefore doesn't post a
WM_DESTROY message unless CanUnbadNow
see this code for yourself in
OUTPROC.CPP.
Going Remote The most amazing thing about
we implemented in this chapWithout changing anything in either CLIENT.EXE or SERVER.EXE, they can work with each other across a network. To set this up, you need to have at least two systems, running either ter
is
that
it is
also a
remote
the local server
server.
Windows NT 4.0, or Windows 95 with Distributed COM (DCOM) Windows 95 installed. Of course the two systems need to be connected to each other by some sort of network. To tell the client to use the remote sener, we'll use the DCOM configuration tool, DCOMCNFG.EXE, which is included witii Windows NT. The DCOM Microsoft
for
configuration tool allows you to change various parameters for the applications installed
on your system, including whether they run
locally
or remotely.
271
INSIDE COM
Table 10-2 contains step-by-step instructions for running SERVER.EXE remotely:
Action
Local
Remote
System
System
•
Build CLIENT.EXE, SERVER.EXE, and PROXY.DLL, using nmake -f makefile. You don't have to rebuild them if you already have them built. (I built them on my Windows 95 system and copied them to my Windows NT system.)
/
Copy CLIENT.EXE, SERVER.EXE, and
PROXY.DLL
to the
remote system.
Register the local server using the
command
/
/
^
/
/
/
server /RegSeruer.
Register the proxy using the
command
regsvr32 Proxy.dll.
Run CLIENT.EXE,
selecting the local server
option. This step checks to
make
sure the
programs work on both systems.
Run DCOMCNFG.EXE. Inside
and
COM
Select the
/
component
Chapter 10 Example Component
1,
click Properties. Select the Location tab.
Deselect the option
Computer, and
On The
Run
Application
select the option
Run
On
This Application
Following Computer. Type the
the remote computer that will run Click the Identity tab,
and
name of
SERVER.EXE.
select the Interactive
/
User radio button.
Depending on your access permissions, you might need to change the settings on the
^
/
Security tab.
Run SERVER.EXE so that you can see its output. Run CLIENT.EXE, and select option 2 to choose
/
/
the local server.
You should
see output in the
/
SERVER.EXE
window.
Output should also appear in the console window associated with CLIENT.EXE. Table 10-2.
Running SERVER.EXE from a
272
remote server.
/
TEN:
/ find
it
really exciting that simply
local server into a
remote
server.
by running
The question
is,
this tool
how does
Servers
in
EXEs
we can turn our it
work?
What Does DCOMCNFG.EXE Do? If you
run
REGEDIT.EXE on
CNFG.EXE, you can
the local
machine
see that part of the magic
is
after
you have run
in the Registry.
DCOM-
Look up the
following key in the Registry:
HKEY_CLASSES_ROOT\ CLSID\
{0C092C29-882C-11CF-A6BB-0080C7B2D682} You'll see a
new named value
(not a subkey), AppID, in addition to the friendly
name. A CLSID identifies a component, and its Registry entry contains information about that component. The LocalSeruer32 key reports the path to the application in which the component is implemented, but otherwise a CLSID has nothing to do with that application. However, DCOM needs to associate information with the application containing the component. Therefore, DCOM created the AppID.
The value of an AppID, like that of a CLSID, is a QUID. Information about AppIDs is stored in the AppID branch of the Registry; again, this is as it is for CLSIDs. To access the information in the Registry about the AppID for SERVER.EXE, look up
the following key in the Registry:
HKEY_CLASSES_ROOT\ AppID\ {0C092C29-882C-11CF-A6BB-0080C7B2D682}
The AppID v^dll have at least three values. The default value is a friendly name. Other named values are RemoteServerName, which contains the name of the server where the application is located, and RunAs, which tells DCOM how to run the application. The Registry structure is illustrated in Figure 10-6 on the following page. In addition, the key.
You should
name of the application is stored directiy under the AppID
see an entry for the following key:
HKEY_CLASSES_ROOT\ AppIDN server.exe
This entry has a single
named value, which
points back to the AppID.
273
— INSIDE COM
HKEY_CLASSES_ROOT
—
AppID I
1l {0C092C29-882C-11CF-A6BB 0080C7B2D682}
* ^ server.exe
—
h
AppID "{9C092C29-882C-llCF-A6BB-8e80C7B2O682)'
J
CLSID
X~^
I
-C{0C092C29-882C-11CF-A6BB- I^T 0080C7B2D682}
I
AppID
"(0C092C29-882C-llCF-A6BB-e980C7B2D6B2}"
|
J
Figure 10-6. Organization of Registry entries for AppIDi.
But
How Does
It
Work?
Adding entries to the the entries.
Registry isn't useful unless you have
some code
of the function CoGetClassObject. Now, not only is CoGetClassObject powerful,
it is
also
much more
nent server with the correct context. CoGetClassObject looks the
whether it has an AppID.
Name in to
much more
confusing. CoGetClassObject can work a lot of
different ways. Normally CoGetClassObject takes a
_SERVER,
to read
DCOM enhances the COM Library, including the implementation
If it does,
If
CLSID and opens a compo-
the context
component up
is
CLSCTX_LOCAL-
in the Registry to see
the function then looks for the RemoteServer-
the Registry. If it finds a server name, CoGetClassObject then attempts
run the server remotely. This
is
what happened
in the instance previously
described.
Other
DCOM Information While it is possible to monkey with the Registry and turn a local server into a remote server, you can also programmatically specify that you want to access a remote server. This requires replacing CoCreatelnstance with CoCreatelnstanceEx or modifying your calls to CoGetClassObject. The following is an example of using CoCreatelnstanceEx to create a remote component:
274
TEN:
Servers
in
EXEs
// Create a structure to hold server information. COSERVERINFO Serverlnfo // Initialize structure to 0. ;
memsetC&Serverlnfo,
sizeof(Serverlnfo))
0,
// Set the name of the remote server. Serverlnfo. pwszName = L"MyRemoteServer"
//
Set up
;
MULTI_QI structure with the desired interfaces.
a
MULTI_QI mqi[3]
mqi[0].pIID mqi[0].pltf mqi[0].hr mqi[l].pIID mqi[l].pltf mqi[l].hr
;
;
= IID_IX = NULL
;
= S_OK
;
= IID_IY = NULL
;
= S_OK
;
mqi[2].pIID = IID_IZ mqi[2].pltf = NULL = S_OK mqi[2].hr
;
IID of desired interface
//
[in]
//
[out] Pointer to interface
//
[out] Result of QI for pointer
;
:
;
;
HRESULT hr = CoCreateInstanceEx(CLSID_Componentl, NULL,
CLSCTX_REMOTE_SERVER. &ServerInfo. 3,
// Number of interfaces
&mqi
The Instance
name
)
;
difference you see between CoCreatelnstanceEx and CoCreate-
first
that CoCreatelnstanceEx takes a
is
COSERVERINFO structure with the
of the remote server. However, the most interesting aspect oi CoCreate-
lnstanceEx
is
the
MULTI_QI
structure.
MULTLQI To
To components in But when you need to move
in-proc components, Querylnterface calls are very
local servers, the Querylnterface
is still
pretty
fast.
fast.
across a network, the overhead of calling a function increases greatly.
inconceivable for an application to grind to a halt as function
calls,
including Querylnterface
of calling Querylnterface,
calls.
it
It's
not
repeatedly makes any
Therefore, to reduce the impact
DCOM has created a new structure named MULTI_QI.
The MULTI_QI structure allows you to query for several
interfaces at the
same
time. This can save considerable overhead.
In the example above, the
same
time.
When
we
are asking for interfaces IX, lY,
CoCreatelnstanceEx exits,
to retrieve all of the interfaces in the
it
returns
MULTI_QI
and
IZ, all at
S_OK if it was
structure.
It
able
returns
E_NOINTERFACE if it can't get any of the interfaces. If it can get only some of the interfaces, it returns CO_S_NOTALLINTERFACES. 275
INSIDE COM
The status of each individual interface is stored in the hr field of the MULTI_QI structure. The pointer is returned in the />/
Dispinterface DISPID
Name
1
•Too"
AQuerylntBrtace
pIDispatch
/
AAddRef SiRelease
2
GetlDsOfNames
&GetTypelnfoCount
3
"Bar" ,
'FooBar"]
function
&GetTypelnfo
FooBar Interface
AGetlDsOfNames Invoke -
»r
pVtbl^
AFoo
function
Silnvoke
&Bar
AFooBar
Figure 11-2. Implementing IDispatch::Invoke using a
The FooBar interface
COM interface.
inherits
from the IDispatch interface. IDispatch'
pVtbl
iOuerylnterface
Dispinterface
1'
pIDispatch
/
AAddRef
GetlDsOfNames
AGetTypelnfoCount
DISPID
Name
1
"FOO"
2
'Bar"
3
"FooBar"
function
AGetTypelnfo
AGetlDsOfNames Invoke •••»f ;
GetIDsOfNanies(
For the
around
IID_NULL.
// Must be IID_NULL
&name,
// Name of function
1,
//
GetUserDefaultLCIDO.
//
Localization info
Stdispid)
//
Dispatch ID
DISPID is simply an optimization to avoid passing For the server, the DISPID identifies the function the client
client, the
strings.
wants to
:
Number of names
call.
Now
that
we have the DISPID
for Fx,
we can execute the function by is a complex function. The call
passing the DISPID to IDispatch: .Invoke, which
below is one of the simplest possible Invoke calls.
It calls
a function without any
parameters: // Prepare the arguments for Fx. DISPPARAMS dispparamsNoArgs = {
NULL, NULL. 0,
// Zero arguments // Zero named arguments
}
;
// Simplest Invoke call. pIDispatch->Invoke(dispid, // IID_NULL, // GetUserDefaultLCIDO, DISPATCH_METHOD, // &dispparamsNoArgs, //
DISPID Must be IID_NULL
Method Method arguments
NULL.
//
Results
NULL.
//
Exception
NULL)
;
// Arg error
The Automation controller doesn't have to know anything about the Automation server. The Automation controller doesn't need a header file with a definition ofFx. Information about function Fxis not statically compiled into the program. Contrast this with the IDispatch interface itself, which is a COM interface. IDispatch is defined in OAIDL.IDL. The compiler generates code for the call to IDispatch members at compile time, and this code is static. However,
286
ELEVEN:
Dispatch Interfaces and Automation
the parameters to Invoke determine what function gets called. These parame-
function parameters, are dynamic and can change at run time. Turning the preceding code fragments into a program that can call any
ters, like all
dispatch function without parameters
two strings, the ProgID and the function name, and pass these strings to CLSIDFromProglD and GetlDsOfNames. The code calling Invoke stays the same. The power oilnvoke'xs that it can be used polymorphically. Any compois
easy. Just ask the user for the
nent that implements Invoke cdLii be called using the same code. However, this power comes at a price. One of the tasks of IDispatch:. Invoke is to pass parameters to the functions that it executes. The types of parameters that Invoke can pass to the functions in a dispinterface are limited in number. We will talk more about this limitation in the section below on dispatch parameters and in the section on VARIANTS. But before we talk about the parameters to functions in dispinterfaces, let's discuss the parameters to IDispatch: .Invoke itself.
Invoke Arguments arguments to the Invoke function. The first three are easily explained. The first parameter is the DISPID of the function the controller wants to call. The second parameter is reserved and must always be IID_NULL. The third parameter holds localization information. Let's look in more detail at the remaining arguments, beginning with the fourth. Let's take a closer look at the
Methods and Properties of a COM interface are functions. COM interfaces, many C++ and even the Win32 API simulate variable access using "Set" and "Get" functions. For example, suppose SetVisible makes a window visible while GetVisible gets the current visibility of a window:
All
members
classes,
if (pIWinctow->GetVisib1e() == FALSE) {
pIWindow->SetVisible(TRUE)
;
}
But "Set" and "Get" functions aren't good enough for Visual Basic. The main purpose of Visual Basic is to make everything as easy as possible for the developer. Visual Basic supports a concept called
properties.
Properties are
programmer treats like variables. programmer can use variable syntax:
"Get"/"Set" functions that the Visual Basic
So instead of using function syntax, the •
VB Code
If Window. Visible = False Then
Window. Visible = True End If
287
INSIDE COM
The propget and propput IDL attributes signify that a COM function is be treated as a property. For example, look
to
at this:
[
object.
uuicl(D15B6E20-0978-lld0-A6BB-0080C7B2D682), pointer_default( unique), dual ]
interface IWindow
IDispatch
:
{
[propput] HRESULT Visible([in] VARIANT_BOOL bVisible) [propget]
;
HRESULT Visible([out, retval] VARIANT_BOOL* pbVisible)
;
)
Here we have defined an interface with the Visible property. The function marked propput takes a parameter as a value. The function marked propget returns a value as an out parameter. The name of the property is the same as the
name
of the function.
^nd propput functions, fore, C++ users would VARIANT_BOOL vb
it
When MIDL generates the header file for the /?ro^g-^
Invoke( if (FAILED(hr)) ;
&excepinfo)
;
{
//
Invoke failed.
if (hr == DISP_E_EXCEPTION) {
// Method raised an exception. // Server can defer filling
EXCEPINFO.
if (excepinfo.pfnOeferredFil
1
In
!= NULL)
{
Fill the EXCEPINFO structure. (*(excepinfo.pfnDef erred Fill In))(&excepinfo)
//
;
}
strstream sout
;
sout >
/
m^,
CHAPTER TWELVE Multiple Visitors to
Threads
my office
on an approximately
routinely hit their heads
long black object hanging from the ceiling. The object of a Bell 206B-IIIJet Ranger. instead of a sideward
It's
rotor,
tail
it
is
12-inch-
a 1:32 scale
model
not the most accurate scale model because
has a rearward pusher propeller.
The pusher
around in a circle. The helicopter works this way: First, you turn it on, causing a small electric motor to turn the propeller in the rear. The propeller doesn't provide enough thrust to move the helicopter by itself. To get it moving, you provide a slight push, which causes the helicopter to swing on the line by which it's suspended. Gradually, the thrust provided by the propeller is enough to get the helicopter spinning in a circle. The angle between the line and the ceiling gets smaller and smaller until the helicopter is quickly spinning around propeller allows the helicopter to
"fly"
close to the ceiling.
Now,
this little helicopter
Asche. Ruediger and
I
Developer Network. Ruediger kernel,
where the
specializes in
is
light
is
very special.
It
was given
worked together writing is
an expert
in the
to
me by Ruediger
articles for the Microsoft
dark depths of Windows NT's
from the GUI never shines. One of the areas Ruediger
multithreaded programming. Which brings us to the topic of
this chapter. If we
wanted
to write a simulation
to use multiple threads.
One
of the model helicopter, we might want
thread could manage the user interface that
would allow the user to manipulate the three-dimensional view of the rotating helicopter. Another thread could handle the calculations of the position of the helicopter as it moved in a circle and closer to the ceiling. All in
all,
however, simulating the rotating helicopter doesn't require the
use of multiple threads.
Where
multiple threads are really beneficial
is
in
providing responsive user interfaces. Delegating work to a background worker
thread can
more
make
more alive and available. Nowhere is this web browsers. Most web browsers download a page
the user interface
noticeable than with
311
INSIDE
COM
using one thread, render
it
using another thread, and allow the user to ma-
downloading by means of a third thread.
I
myself can't stand waiting for a bunch of useless graphics to download, so
I
nipulate the page even while
routinely click
it is
on jumps before the graphics have been downloaded and
dered. This convenience
is
made
ren-
possible through the use of multiple threads.
Since threads are important for responsive applications,
it's
reasonable
COM components from multiple threads. However, there are some unique challenges to using COM components from multiple threads, which we will examine in this chapter. The challenges presented by COM are to expect to access
minor, almost insignificant, compared with the more general problem of multithreaded programming. We won't cover multithreaded programming broadly but discuss only
COM components.
how
multiple threads affect developing and using
For more information on multithreaded programming,
read Ruediger Asche's articles in
COM Threading
MSDN.
Models
COM uses Win32 threads and doesn't define a new type of thread or process. COM doesn't have ovm synchronization primitives. COM just uses the norits
mal Win32 APIs for creating and synchronizing threads. Using threads with
COM
is
basically the same, with nuances, as using threads in a
cation. We'll
Win32
appli-
examine these nuances, but let's first take a general look at Win32
threads.
Win32 Threads
A typical Win32 application has two kinds of threads: user-interface threads and one or more windows. These threads have message loops that keep the windows alive and responsive to the user's input. Worker threads are used for background processing and aren't associated v^th a window. Worker threads usually don't have message loops. A single process can have multiple user-interface threads and multiple worker threads. User-interface threads exhibit an interesting behavior. As I just mentioned, each user-interface thread owns one or more windows. The window procedure for a window is called only by the thread that owns the window. The thread that owns a window is the thread that created the window. So, the window procedure is always executed on the same thread regardless of the thread that sent the message it is processing. The end result is that all messages are synchronized. The window is guaranteed to get messages in the proper order. worker threads. User-interface threads are associated with
312
i
TWELVE:
The
Multiple
Threads
is that you don't need to write window procedures. Writing thread-safe code is not trixial and can be very time-consuming. Since Windows guarantees that messages are synchronized, you don't need to worry about your procedure being accessed by tvvo or more threads simultaneously. This synchronization is very beneficial to threads that manage user interfaces. After all, we want the user's events to reach the window in the order the user performed them.
benefit to you, the programmer,
thread-safe
COM Threads COM uses the same two t)'pes of threads, although COM has different names one a user-interface thread, COM uses the term
for them. Instead of calling
apartment thread.
part about
terminology
SDK uses I'll
The term yr^^ thread
COM is
threading
that
is its
is
used instead oi worker thread. The hardest
terminology-.
none of the documentation
The hardest
is
a different set of terms than that used by the
avoid as
much of the
early. In this chapter,
I
part about the
really consistent.
The Win32
COM specifications.
terminology as possible and otherwise define
am
my terms
going to use the term apartment thread for a user-
interface-style thread diudfree thread for a worker-style thread.
Why does COM even care about threading if it's all The
the
same
as
Win32?
reasons are marshaling and synchronization. We'll discuss marshaling and
synchronization in detail after we define apartment, apartment threading, dnd free threading.
The Apartment new terminology,
Although
I
apartment.
An apartment is a conceptual
style
really
wanted
to avoid
entit)'
I'll
now
define the term
consisting of a user-interface-
thread (aka apartment thread) and a message loop.
Take a typical Win32 application, which consists of a process, a message and a window procedure. All processes have at least one thread. A graphical representation of a Windows application is shown in Figure 12-1 on the following page. The box with the dotted line represents a process. The box with a loop in it represents the Windows message loop. The other box represents the window procedure and other program code. All of this is sitting on loop,
top of an arrow that represents a thread of execution. In addition to illustrating a process, Figure 12-1 also illustrates an apart-
ment. The single thread
is
an apartment thread.
Figure 12-2 uses the same graphical representation as shown in Figure
show a typical COM application consisting of a client and two in-proc components. Such a program resides in a single process that has a single 12-1 to
313
INSIDE COM
thread of execution. In-proc components don't have their own message loops but instead share the message loop of their client EXE. Again, Figure 12-2 illustrates a single apartment.
Message loop Process boundary
Program code
Thread
of
execution
Figure 12-1. Graphical representation of a Windows application showing the thread of execution, the message loop, the process boundary,
and
the code.
Colnitialize
In-proc
^ Connponent
Client
component
Client
P
Component CoUninitialize
T Figure 12-2. Graphical representation of a only
314
a
single thread,
and
the
client
and two in-^oc components. There
components share the
client 's
message
loop.
is
.
TWELVE:
Multiple
Threads
COM components doesn't change the fundamental strucWindows apphcation. The most noticeable difference from a process without components is that the EXE must call Colnitialize before using any COM Library services and CoUninitialize before exiting. Using in-proc
ture of a
Adding Out-of-Process Components The picture changes when the client connects
to
an out-of-proc component.
Figure 12-3 illustrates a client connected to an out-of-proc component.
component lives in its
own thread
vides
it
a process separate
The
of execution.
with a message loop.
If you
see the message-loop code in
from that of the
client.
out-of-proc server for the
The
Each process has
component
pro-
look at the example in Chapter 10, you can
OUTPROC.CPP. Another
processes with in-proc components
is
big difference from
that cross-process calls
must be mar-
The "lightning bolt" represents a marshaled call. In Chapter 10, we saw how to build a proxy/stub DLL, which was used to marshal data from the shaled.
client to the out-of-proc
component.
Figure 12-3 contains two apartments.
component is in the other apartment. ment is the same thing as a process, but the
The
client is in one apartment, and might now appear that an apartisn't. A single process can have
It it
multiple apartments.
Out-of-proc
component server
In-proc calls are
not marshaled.
Cross-process calls are
Q
marshaled.
CoUninitialize
CoUninitialize
Out-of-proc
T
message
loop
T
Figure 12-3.
An
out-of-proc
component has
its
own message
loop
and
thread.
315
INSIDE COM
changed the component in Figure 12-3 from an out-ofan in-proc component in a different apartment. The long-
In Figure 12-4,
proc component to
1
dash lines represent the different apartments. The dotted lines continue
to
represent the process boundary.
Notice
around the
how
drew another box the objects were now in the same
similar the two figures are. Basically,
original figure
and
stated that
I
—
my point should be obvious apartments are similar to (single-threaded) processes in all of the following respects: A process has its own message loop. An apartment has its own message loop. Function calls within a (single-threaded) process and function calls within an apartment are not marshaled. Internal synchronization is provided because the process and the apartment have only a single thread. Synchronization of function calls across processes or across apartment boundaries is performed through the message loop. As a final detail, each process must initialize the process. While this
is
largely hand-waving,
Calls
Process boundary
between apartments
are marshaled. In-proc
component :
in a different apartment
A message loop in
is
used
the thread
procedure.
Figure 12-4.
A
client
communicating with an in-jnoc component
apartment.
316
in a different
TWELVE:
Multiple
Threads
COM Library. Likewise, each apartment must initialize the COM Library. you look back
to Figure 12-2
components coexisting
Apartment Threads An apartment thread
in
on page
314, you should
If
now see a client and two
an apartment.
and only thread found in an apartment. But when you hear the term apartment thread, think user-interface thread. Remember that a user-interface thread owns the windows that it creates. Only the thread that creates a window calls its window procedure. An apartment thread has exactly the same relationship to the components it creates. An apartment thread owns the components it creates. A component in an apart-
ment will be
is
the one
called only by the apartment thread.
window owned by a different thread, Windows places the message into the message queue for the window. The message loop for the window runs on the thread that created the window. When the message loop pulls out the message and calls the window procedure, it's calling the window procedure on the same thread that created the window. The case is the same for a component in an apartment. Suppose another thread calls a method on our component, which is in an apartment. Behind the scenes, COM places the call into the queue for the apartment. The message loop pulls the call off and calls the method on the apartment thread. The end result is that a component in an apartment is called only on the apartment thread, and the component doesn't need to worry about synchronizing calls. Since COM guarantees that all calls to the component are sychronized, the component doesn't need to be thread safe. This makes writing components much easier. None of the components we have written in this book have been thread safe. But as long as they are created by apartment threads, we can be sure that they will never be accessed from different threads If
a thread sends a message to a
simultaneously.
This
is
where
free threads differ
from apartment threads.
Free Threads
COM synchronizes calls to components on apartment threads. COM doesn't synchronize
calls to
components created by
free threads. If a
component
is
can be called by any thread and at any time. The developer of the component must ensure that the component synchronizes created by a free thread,
it
The component must be thread safe. Free threading moves the burden of synchronization from COM to the component.
access to itself
317
INSIDE COM
Since
COM doesn't synchronize calls to a component, free threads don't
need message
loops.
A component
free-threaded component. created
it
but
is
shared
created by a free thread
The component
among
all
isn't
owned by
is
considered a
the thread that
threads and can be accessed freely by
all
threads.
Apartment threads are the only threads you can use with COM in Microsoft Windows NT 3.51 and Microsoft Windows 95. In Windows NT 4.0 and in Windows 95 with DCOM, you can use free threads. That's the mile-high view of free threading.
happen when we
discuss marshaling
More
interesting things
and synchronization.
Marshaling and Synchronization
COM needs to know what kind of thread a component
is running on so that component. Generally, in apartment-threaded cases, COM does all of the marshaling and synchronization. In free-threaded cases, marshaling might not be necessary and it's the component's job to synchronize. it
can properly marshal and synchronize
The general
rules to
remember
calls to that
are these:
between processes are always marshaled. Chapter 10. Calls
Calls
We discussed this in
on the same thread are not marshaled.
Calls to a
component on an apartment thread
Calls to a
component on
Calls to
are marshaled.
a free thread are not always marshaled.
an apartment thread are synchronized.
Calls to a free thread are not synchronized. Calls
on the same thread are synchronized by the
Now we'll
look at the possible combinations of apartment threads
ing free threads. Let's start with the easy cases. Unless
assume that the threads are running Calls
thread.
in the
I tell
call-
you otherwise,
same process.
on the Same Thread
component on the same thread, the call is synchronized just by being on the same thread. COM doesn't need to do any synchronization, and the components don't have to be thread safe. Calls that If a client
stay
on the same thread
this rule
318
on any thread
throughout
calls a
also don't have to
this
book.
be marshaled.
We have been using
TWELVE:
Multiple
Threads
Apartment to Apartment If a client
thread, if
on an apartment thread
calls a
component on
a different apartment
COM synchronizes the call. COM also marshals the interfaces, even
the threads are in the
same process. In some
cases,
you need
marshal an interface between apartment threads. We'll look
when we implement an apartment thread. Calling a component ment thread is similar to calling an out-of-process component. Free to Free on a
If a client
chronize the
call.
The
call will
is
an apart-
COM won't syn-
execute on the client's thread.
The component
to itself
called by a different client
process, the call
in
when
component,
free thread calls a free-threaded
must synchronize access
manually
to
at this later
on a
because
it
might be simultaneously getting
different thread. If the threads are in the
same
not marshaled.
Free to Apartment If a client in
nizes the
a free thread calls a
call.
The component
component in an apartment, will
be called on
its
COM synchro-
apartment thread. The
interface also needs to be marshaled whether the threads share the
process or not. In most cases, times, as you'll see in a
COM does the marshaling for you.
little bit,
you have
to
same
But some-
do the marshaling yourself.
Apartment to Free on an apartment thread calls a free thread, COM doesn't synchronize the call. The synchronization is left to the free-threaded component. The interface is marshaled. But if both threads are in the same process, COM can optimize the marshaling to pass pointers directly to the client. More on this when we implement some free threads. As you can see, threading with COM components isn't conceptually that different from normal Win32 threading. A process can have any number of threads. These threads can be apartment threads or free threads. From the programmer's standpoint, there are only two interesting aspects of COM threading: synchronization and marshaling. COM synchronizes calls to components on apartment threads. The developer synchronizes calls to components on free threads. Synchronizing components for use on free threads is a general multithreading problem and is not specific to COM. Marshaling, however, is specific to COM and is the one truly unique area of dealing with COM components from multiple threads. We'll examine the manual marshaling of interfaces in detail next when we actually implement an apartment thread and a client thread. If
a client running
319
INSIDE COM
Implementing Apartment Threading The
best thing about
thread
safe.
matter whether the threads.
components
in
apartments
is
that they don't
need
COM synchronizes access to components in apartments.
Behind the
It
to
be
doesn't
come from other apartment threads or from free scenes, COM uses a hidden Windows message queue to
calls
synchronize client calls to the component. This makes implementing components for single-threaded apartments very similar to writing window procedures. (A message loop
is
used to synchronize access to a window procedure,
and COM uses the same mechanism apartment.)
The
to synchronize access to a single-threaded
following are the key requirements of an apartment:
It
must
It
can have only a single thread.
It
must have a message loop.
It
must marshal interface pointers when passing them
call Colnitialize
or
Olelnitialize.
to other
apartments.
must have thread-safe component. It
It
I'll
DLL entry points if it is an
might need a thread-safe
further refine
A component can
live
class factory.
some of these
points in the following paragraphs.
live
on a
single thread.
accessed only by the thread that created
works
—
it
A component
only on a single thread.
threaded apartment must
in-proc
it.
This
in a single-
Such a component can be how a window procedure
is
can be called only by the thread that created the window. Since the
component can be accessed only by a single
thread,
it is
for
all
practical pur-
poses running in a single-threaded environment and doesn't need to worry about synchronization. However, the component must still protect its global data, because it is reentrant, just as a window procedure is reentrant.
must be marshaled across apartment boundaries. Calls from other threads must be marshaled so that they are made on the same thread as the component. Since an apartment has only a single thread, all other Interfaces
threads are outside the apartment. Calls must always be marshaled across
apartment boundaries. We'll look
more
320
detail in a
moment.
at
marshaling interface pointers in a
little
TWELVE:
Multiple
Threads
DLL entry points must be thread safe. Components in single-threaded apartments don't need to be thread safe because they are accessed only by the thread on which they are created. But the entry points to the in-proc server DLL, such asDllGetClassObject3.ndDllCanUnbadNow, do need to be thread safe. Multiple clients in different threads can all call DllGetClassObject at the same time.
To make
these functions thread safe, ensure that
tected against simultaneous access. In factory
must
also
be thread
some
cases, this
all
shared data
means
is
pro-
that your class
safe.
Class factories might need to be thread safe.
If you create a different class
component you
create, the class factory doesn't need to be be accessed only by a single client thread at a time. But \i DllGetClassObject creates a single class factory from which all instances of your component are created, you must ensure that the class factory is thread safe because multiple threads can access the factory simultaneously. An out-of-proc component might use a single instance of a class factory create all instances of the components. This class factory also must be thread to safe. Making most class factories thread safe is trivial because they don't modify shared data other than reference counts. Interlocklncrement and Interlock-
factory for every
thread safe because
it
will
Decrement cdiU be used to protect the reference counts, as
moons ago in Chapter 4. An in-proc component Registry that
it
is
covered
in the section
end of this chapter. For now,
showed you many
that meets these requirements indicates in the
supports apartment threading.
threading model
I
let's
How a component registers its
"Threading Registry Keys" near the
get a detailed look at what we have to
do
to
marshal an interface pointer that we want to pass to another thread.
When a component on an apartment thread passes its interface to a client on a different
thread, the interface
other thread
an apartment thread or a free thread,
is
Automatic Marshaling In many cases, the interface
is
must be marshaled. it still
It
doesn't matter
needs
to
if
the
be marshaled.
automatically marshaled for you by
COM.
In
we discussed proxy/stub DLLs, which marshal interfaces between processes. From the programmer's standpoint, threading doesn't change the use of these proxy/stub DLLs. They automatically take care of marshaling Chapter
10,
across processes.
COM also uses these proxy/stub DLLs to marshal interfaces between an apartment thread and other threads within the same process. So when you access an interface in a component in a different apartment, COM automatically makes the call through the proxy and the interface is marshaled.
321
INSIDE COM
Manual Marshaling So when do you need to marshal an interface pointer yourself? Basically, whenever you are crossing an apartment boundary but aren't communicating through COM. Let's look at two examples. First, let's take the case where the client creates an apartment thread that creates and manipulates a component. Both the main thread and the apartment thread might need to manipulate the component. The apartment thread has an interface pointer to the component because the apartment thread created the component. The main thread can't directly use this interface pointer because it's in a different apartment from the one in which the component was created. For the main thread to use the component, the apartment thread must marshal the interface pointer and give it to the main thread. The main thread must unmarshal the interface pointer before using
it.
The second case occurs when the class factory for an in-proc component creates the component instances on different threads. This scenario is basically the same as the one above, but in this case, the component creates itself on different threads, while in the previous case, the client created the threads. The client calls CoCreatelnstance, which eventually results in the component's class factory getting created.
the class factory creates a
component.
When the client calls IClassFactory::CreateInstance,
new apartment
IClassFactory::CreateInstance
thread. This
new thread creates
the
must pass an interface pointer back
But Createlnstance can't pass the interface pointer created in the directly back to the client because the client is on a different thread. So the apartment thread must marshal the interface pointer to Createlnstance, which then unmarshals the interface pointer and passes it back to the client. to the client.
new apartment thread
The Longest Win32 API Name Now that we know when to marshal an marshal the interface. You can do
all
interface,
we need
to look at
how
to
of the work yourself using CoMarshal-
Interface a.nd CoUnMarshallnterface. If you
have better things to do, you can use
the helper functions CoMarshallnterThreadlnterfacelnStreamsind CoGetlnterfaceAndReleaseStream,
Win32
whose names have
to
be two of the longest names
among the
APIs. (At this rate, we'll soon have paragraphs instead of function
names.)
These functions are easy to the foUovydng:
322
use.
To marshal an /Xinterface pointer,
use
TWELVE:
Multiple
Threads
IStream* plStream = NULL HRESULT hr = CoMarshal InterThreadlnterf acelnStreamC IID_IX, // IID of interface to marshal pIX, // Interface to marshal SpIStream) // Stream to put marshaled interface ;
;
To unmarshal
the above interface, use the following:
IX* pIXmarshaled HRESULT hr = CoGetInterfaceAndReleaseStream( pIStream, // Stream containing interface IID_IX, // IID of interface to unmarshal (void**)&pIXmarshaled) // Unmarshaled interface pointer ;
;
Now,
that was pretty painless, wasn't it? That's because used the proxy/stub DLL for us behind the scenes.
It's
COM automatically
Code Time This chapter has been pretty conceptual up to reason for
that: the
this point,
and
there's a
good
concepts are more troublesome than the implementation.
Suppose you want to increment a counter on a background and occasionally you want to update the display. If you were writing a normal Win32 program, you would create a worker thread to do the counting for you in the background. We will do the same thing here, but we will use an apartment thread instead of a worker thread. The main thread will create an apartment thread. The apartment thread will create a component and periodically update a counter in the component. The apartment thread will pass an interface pointer to the main thread so the main thread can get the current count and display it. This entire process is exactly like normal Win32 multithreaded programming, except the apartment thread does this: Let's take a simple example.
component
in the
Initializes the
COM Library
Has a message loop Marshals the interface pointer back to the main thread
The component
is exactly like the components we have been writing. Now, the most troublesome thing about developing a single-threaded apartment is that it's just a concept, not a piece of code. You create a thread, just as you always do. You create a message loop, just as you always do. Since I wanted to make the "apartment" seem more real, I created a small class named CSimpleApartment to do these tasks.
323
INSIDE COM
CSimpleApartment and CCIientApartment CSinipleApartment
is
a simple class that encapsulates the creation of a
nent on another thread. You can find CSimpleApartment in the
and APART.CPP this
in the folder
CHAP12\APT_THD on
book. CSimpleApartment: .StartThread creates and
CSimpleApartment::CreateComponent takes the ates
the
it
CLSID
files
compo-
APART.H
CD that comes with
new thread. component and cre-
starts a
for a
on the thread begini by StartThread. is where things get interesting (or confusing). CSimpleApartment
This
straddles both threads. Part of CSimpleApartm£nt
and part
new
is
called by the original thread
mechanism communicate with the new thread. Since CSimpleApartment: :CreateComponent is called from the original thread, CreateComponent can't direcdy create the new component. The component must be created on the new thread. So CreateComponent uses an event to signal the new apartment thread when it's time to create a new component. The apartment thread calls the function CreateComponentOnThread to actually create the component. is
called by the
thread. CSimpleApartment provides a
for the original thread to
CSimpleApartment: :CreateComponentOnThread
be defined by a derived
class.
In this
first
is
a pure virtual function that must
example, the derived
class CCIient-
Apartment implements a version of CreateComponentOnThread tha.t creates the
component
in a perfectly
A Walk Through
normal way by using
CoCreatelnstance.
the Apartment-Thread Example
Table 12-1 shows an outiine of the code we are going to look at. All of the code on the right of the table is running in the apartment thread created by CSimpleApartmen t: StartThread. :
Main Thread
Apartment Thread
WinMain
CSimpleApartment
CSimpleApartment
InitializeApartment
StartThread
RealThreadProc Class ThreadProc
CCIientApartment: :WorkerFunction
CreateComponent
CreateComponentOn Thread() CCIientApartment::
CreateComponentOn Thread
Table 12-1. Flow of code in
324
the apartment-thread example.
.
TWELVE:
Multiple
Threads
CSimpleApartment: -.StartThread All of the
excitement
starts in
CLIENT. CPP with the function InitializeThe follow-
Apartment. InitializeApartment calls CSimpleApartment: .StartThread.
ing
is
the implementation of StartThread:
BOOL CSimpleApartment: :StartThread(DWORD WaitTime) {
if
(IsThreadStartedO)
{
return FALSE
;
}
Create the thread. m_hThread = :CreateThread(NULL. // Default security // Default stack size 0. RealThreadProc (void*)thi s CREATE_SUSPENDED, &m_ThreadId) if (m_hThread == NULL) //
:
,
;
I
traceC'StartThread failed to create thread.". GetLastErrorC return FALSE
) )
;
;
)
traceC'StartThread successfully created thread.")
;
// Create an event to signal the thread to create the component. m_hCreateComponentEvent = :CreateEvent(NULL. FALSE, FALSE. NULL) if (m_hCreateComponentEvent == NULL) :
;
{
return FALSE
;
)
// Create an event for the thread to signal when it is finished. m_hComponentReadyEvent = :CreateEvent(NULL. FALSE. FALSE. NULL) if (ni_hComponentReadyEvent == NULL) :
{
return FALSE
;
)
trace("StartThread successfully created the events.")
;
// Initialize the wait time. m_WaitTime = WaitTime ;
// Thread was created suspended; start the thread. DWORD r = ResumeThread(m_hThread) assertCr != Gxffffffff) ;
;
(continued)
325
I
INSIDE COM
Wait for the thread to start up before we continue. t hMes s age Loop (m_hConiponent Ready Event)
//
Wai tWi
i
;
return TRUE
;
)
new thread using r.CreateThread. synchronize the two threads. The
CSimpleApartmentr.StartThread creates the It
also creates two events that are used to
function CSimpleApartinentr.ClassThreadProc, running in the apartment thread, uses m_hComporienlRead\Eiient twice, started
and
at
the
end
signal that this
first to
to signal that
it
has stopped.
new thread has
The function
CSimple-
Apartmentr.CreateComponent uses the event m_hCreateComponentEvent to signal
when
it
wants the apartment thread to
ComponentOnThread that
it
sets
CSunpleApartment::CreateComponent-
call
OnTfiread lo create the component. After
has created the component, Create-
it
m_hComponentReadyEvent to signal to CreateComponent
has finished creating the component.
CSimpkApaiimentr.WaitWithMessageLoop
an event. If you
doesn'tjust wait for an event;
to
also processes
it
Windows messages.
hang. Your user interface should always process messages whenever
waiting. WaitWithMessageLoop uses the
which
a helper function that waits for
wait for an event without processing messages, your user interface will
appear it is
It
is
we'll
examine
Win32 API MsgWaitForMultipleObjects,
later.
CSimpleApartment::ClassThreadProc
When
the thread starts,
it
the static function RealThreadProc, which calls
calls
Windows can't call C++ funcrions, so all Win32 callbacks must be static functions. When we create the thread procedure, we pass it a pointer to our class so that it can call ClassThreadProc. The code for ClassThreadProc is ClassThreadProc.
listed below:
DWORD
CSimpleApartment: :ClassThreadProc()
{
// Initialize the COM Library. HRESULT hr = Coini tial ize( NULL) if (SUCCEEDED(hr))
;
{
//
Signal
that we are starting.
Se t E ven t ( m_hC ompon en t Ready Event)
Wait for the signal BOOL bContinue = TRUE
to create
//
;
a
!
component.
;
(continued)
326
\
,
,
TWELVE:
Multiple
Threads
while (bContinue) {
switch(::MsgWaitForMuHipleObjects( 1,
&m_hCreateComponentEvent FALSE.
m_WaitTime, QS_ALLINPUT)) {
// Create the component. case WAIT_OBJECT_0:
CreateComponentOnThreadC break
)
;
;
// Process Windows messages, case (WAIT_OBJECT_0 +1): MSG msg while(PeekMessage(&msg, NULL. 0. ;
PM_REMOVE))
0,
{
if
(msg. message == WM_QUIT)
{
bContinue break
FALSE
=
:
;
}
Di
spatchMessage(&msg)
:
)
break
;
// Do background processing, case WAIT_TIMEOUT:
WorkerFunction( break
)
;
;
default: trace("Wa1t failed.". GetLastError(
)
)
;
)
}
//
Uninitialize the COM Library.
CoUnini tial ize(
)
;
)
//
that we have finished.
Signal
Set E V en t(m_hComponent Ready Event)
return
:
;
}
Apartments must
initialize
the
COM
Library'
and have message
loops.
ClassThreadProc satisfies these requirements. Instead of just a GetMessage/
DispatchMessage loop, ClassThreadProc uses MsgW'aitForMultipleObjects, which
327
)
INSIDE COM
waits for
I one of three things
message, or a time-out.
If
to
happen: m_hCrealeComponentEvent, a Windows
the event m_hCreateComp(mentEvent
FarMultiplcObjects stops waiting
is
set,
MsgWait-
and ClassThreadProc caWs CreateComponentOn-
Windows message is sent to the thread, a PeekMessage/DispatchMessage loop removes and dispatches the message (and any others in the queue). If Thread. If a
the wait times out, CSimpleApartment::WorkerFunction
is
called.
This function
is
implemented by the derived class CClientApartment, which we'll talk about later. You can use a pure GetMessage/DispatchMessage if you want to. Instead of using an event to create the component, you can use PostThreadMessage. But MsgWaitForMultipleObjects
is
more
efficient than GetMessage/DispatchMessage.
CSimpleApartment::CreateComponent
Now
that
This
starts
we have created the thread, we are ready to create the component. with a call by the main thread to CSimpleApartmentr.CreateComponent. The code for CreateComponent is shown here: HRESULT CSimpleApartment: :CreateConiponent(const CLSID& clsid, const IID& iid, lUnknown** ppl) {
// Initialize the shared data. m_pIStream = NULL m_piid = &iid in_pclsid = &clsid ;
;
;
//
Signal
the thread to create
component.
a
Set E vent (m_hCreateConiponent Event)
;
// Wait for the component to be created. traceC'Wait for the component to be created.") i f (Wai twit hMes sage Loop (m_hComponent Ready Event)
;
{
traceC'The wait succeeded.") if
(FAILED(m_hr))
//
;
Did GetCl assFactory fail?
{
return m_hr
;
)
if
(m_pIStream == NULL)
//
Did the marshaling fail?
{
return E_FAIL
;
}
(continued)
328
)
TWELVE:
traceCUnmarshal the interface pointer.")
Multiple
Threads
;
Unmarshal the interface. CoGetlnterf aceAndRel easeStream(n)_pIStreani, HRESULT hr =
//
:
:
iid,
(void**)ppI)
;
m_pIStream = NULL; if (FAILED(hr)) {
traceCCoGetlnterfaceAndRel easeStream failed.", hr) return E_FAIL
;
:
}
return S_OK
;
}
trace("What happened here?") return E_FAIL
;
;
)
The CreateComponent function performs four main copies
its
create the it
parameters into
member variables. Second,
component. Third,
it
waits for the
it
component
operations.
First, it
signals the thread to to
be created. Fourth,
unmarshals the requested interface to the component.
CSimpleApartment::CreateComponentOnThread
When
CreateComponent sets m_hCrealeComponentEvent, ClassThreadProc calls the
private, internal version o{ CreateComponentOnThread,
operations.
It
calls
which performs two main
the pure \irtual version oi CrealeComportetit(hiThread, passing
the parameters that were passed to CreateComponent. Passing the parameters directly to CreateComponentOnThread simplifies
the derived void
class.
Second,
it
implementing the function
in
marshals the interface:
CSimpleApartment: :CreateComponentOnThread(
{
lUnknown* pi = NULL Call the derived class to actually create the component. ni_hr = CreateComponentOnThread(*m_pcl Sid, *m_piid. &pl) if (SUCCEEDED(m_hr)) :
//
;
{
trace("Successful ly created component.") Marshal the interface pointer to the server. HRESULT hr = :CoMarshal InterThreadInterfaceInStream( ;
//
:
m_pi
id,
Pl.
&m_pIStream) assert(SUCCEEDED(hr))
;
;
// Release the pi Pointer. pI->Release( )
;
)
(continued)
329
:
INSIDE COM
el se {
traceC'CreateComponentOnThread failed.". m_hr) }
traceC'Signal the main thread that the component is ready.") SetEvent(m_hComponent Ready Event) ;
CreateComponentOnThread uses the function CoMarshallnterThreadlnterface-
The code
InStream to marshal the interface pointer to the other thread.
for
CreateComponent unmarshals the interface pointer.
CCIientApartment In this
first
example, CCIientApartment implements the two
virtual functions
CreateComponentOnThread 2ind WorkerFunction. CCIientApartment
is
designed for
use by clients that want to create components on different threads.
CreateComponentOnThread to
It
overrides
call CoCreatelnstance:
HRESULT CCl ientApartment: :CreateComponentOnThread(const CLSID& clsid, const IID& iid, lUnknown** ppl) {
HRESULT hr =
:
CoCreateInstance(cl sid
,
NULL,
CLSCTX_INPROC_SERVER, iid,
(void**)ppI) if
I
;
(SUCCEEDED(hr))
{
Query for the IX interface to use in WorkerFunction. void** )&m_pIX ) (*ppl )->QueryInterface(IID_IX, if (FAILED(hr)) //
hr =
(
;
{
use it, don't let anybody use it. ->Release( ) return E_FAIL //
If we can't
(*ppl
)
;
;
}
I
}
return hr
330
;
TWELVE:
Multiple
CClientApartmentr.CreateComponentOnThread queries the creates for
its
IX interface so
that
it
can manipulate
it
in
its
Threads
component
it
WorkerFunction:
CC1 lent Apartment: :WorkerFunction()
void {
tn_pIX->Tick()
;
}
CLIENT.CPP At
this point, the
thread and the
component have been
created. WTienever
CSimpleApartment::m_ WaitTime passes, CSimpkApartmentr.ClassThreadProc calls CClientApartment::WorkerFunction. So every few clock cycles our
getting updated.
a timer.
To
display these changes in our window, the client creates
When it gets a WM_TIMER message, it calls
OnTick, which calls IXr.Get-
CurrentCount and then displays this count in the window. IXr.GetCurrentCount, the call
When
component is
is
When
the client calls
marshaled to cross the apartment boundary. it is calling from the same apartment
WorkerFunction calls IX::Tick,
thread,
and the
call is
not marshaled.
Clients aren't the only items that can create apartment threads.
components
You can
apartment threads. In fact, you can create a in different apartment threads. There you have it. As you can see, the hardest part of implementing an apartment thread is handling the threads. Now that we are experts in apartment threads, let's take a look at free also build
to create
class factory that creates
components
threading.
Implementing Free Threading If you
are used to writing multithreaded programs, free threading isn't going
many really new challenges. Free threads are created and managed using the usual Win32 threading functions, such as CreateThread, ResumeThread, to present
WaitForMultipleObjects, WaitForSingleObject, CreateMutex,
and
CreateEvent.
Using
and semamaking the
the standard threading objects, such as mutexes, critical sections,
phores, you protect access to your component's internal data,
component thread safe. While ensuring
that a
component is truly thread safe
problem, the well-defined COM interface makes obvious when your component is getting accessed. is
never a
triNial
blatantly
new to writing multithreaded programs, the sample program a good place to start learning. We'll use a couple of mutexes prevent two threads from accessing the same data at the same time. If you
are
in this section to
it
is
331
INSIDE COM
There are
requirements for free threading beyond ensuring that the components are thread safe. The first requirement is that your operating system must support COM free threading. Windows NT version 4.0 supports
really only three
COM free threading. Windows 95 supports COM free threadDCOM extensions. We discussed in Chapter
ing after you have installed the
10
how
to
programmatically determine whether your operating system sup-
ports free threading. (Basically, you look in
OLE32.DLL
for the function
CoInitializeEx.)
Speaking o{ CoInitializeEx, the second requirement is that the thread must with the parameter COINIT_MULTITHREADED to mark
call CoInitializeEx itself as free
The thread calls
threaded.
What does it mean to mark a thread as free threaded? component determines how a component handles
that creates a
from different threads.
free thread can call that
Once
If
a free thread creates a
component
at
component, any other
any time.
a free thread has called CoInitalizeEx using
COINIT_MULTI-
THREADED, it can't call it again with a different parameter. Since Olelnitialize calls CoInitializeEx with the value COINIT.APARTMENTTHREADED, you can't use the OLE Library from a free thread. The
third requirement
is
not really a requirement of free threading, but
of apartment threading. Interface pointers must be marshaled when they are
passed to apartment threads. This applies, by the way, only the interface pointer through a
if
you don't pass
COM interface. If you do pass the interface
COM interface, COM marshals for you. If the client in a different COM marshals the interface for you in that instance also. Of course, you have to give COM a proxy/stub DLL before will do the marshaling. We through a
it
is
process,
it
discussed marshaling between apartment threads in the previous section. Free
threads use the same functions, CoMarshallnterThreadlnterfacelnStream and CoGetlnterfaceAndReleaseStream, to manually marshal interfaces.
COM can op-
timize this marshaling behind the scenes, as we'll see later.
In-proc components have a fourth requirement.
themselves as free threaded in the Registry. This
is
They must
register
covered in the section
"Threading Registry Keys." As you can see, except for the need to marshal interface pointers to other apartments, the requirements for free threading are straightforward. The biggest burden presented by free threading is the need to ensure that the components are thread safe. However, this is not a COM requirement but a standard problem with multithreading.
332
^
)
.
TWELVE:
A Walk Through The code
Multiple
Threads
the Free-Thread Example
for creating a free thread isn't
creating an apartment thread.
The
much different from the code for \CHAP12\FREE_THD contains
folder
code to create two free threads that share a single component. The first free thread increments the component's counter (as in the apartment thread example). The other free thread decrements the counter. In addition, we now think of the counter as being in one "hand" or the other. The first free thread moves it to the left hand, and the other moves it to the right hand. The primary thread, an apartment thread, gets a marshaled copy of the interface pointer, which it uses to periodically determine and report the component's status. Most of the code is similar to the code used to create the apartment thread earlier. Instead of repeating myself, I'll just point out how the two examples differ.
Obvious Differences The most obvious difference to CSimpleFree.
ment but just
When you
is
that I've
changed the name CSimpleApartment
create a free thread, you are not creating an apart-
a thread. Similarly, CClientApartment
is
now
CClientFree.
would like to stress that CSimpleFree is not a generic solution for creating and managing free threads. CSimpleFree itself is not thread safe. It is designed only to be used by an apartment-threaded client to create free threads. What CSimpleFree \a.cks in robustness it more than makes up for in simplicity. I
CSimpleFree: :Class ThreadProc The only function in CSimpleFree significantly different from is
ClassThreadProc. Instead of calling Colnitialize for
it
CSimpleApartment
as CSimpleApartment does,
we can use we must define _WINNT32_WINNT = 0x0400 or _WIN32_DCOM. If you don't define one of these, OBJBASE.H won't contain the definition of CoInitializeEx. Second, we need to check at run time to ensure that the operating system we are running on supports CoInitializeEx. All of this is shown in the code here: CSimpleFree czWs CoInitializeEx(0,COINT_MULTITHREADED). Before CoInitializeEx,
DWORD
we have
to
do two
things. First,
CSimpleFree: ClassThreadProc :
(
{
Check for the existence of CoInitializeEx. typedef HRESULT stdcall *FPCOMINITIALIZE)( void* FPCOMINITIALIZE pCoIni ti al i zeEx = reinterpret_cast( :GetProcAddress( GetModul eHandl e( "ol e32") "CoInitializeEx")) if (pCoInitializeEx == NULL) //
(
:
:
,
DWORD)
;
:
;
(continued)
333
i
:
)
,
INSIDE COM
{
traceC'This program requires the free-thread support in DCOM.") Set Event(m_h Component Ready Event) return ;
;
)
Initialize the COM Library. HRESULT hr = pCoIni ti al zeEx(0 if (SUCCEEDED(hr)) //
,
COINIT_MULTITHREADED)
;
{
Signal that we are starting. SetEvent(ni_hComponent Ready Event)
//
;
Set up array of events. HANDLE hEventArray[2] = { ni_hCreateComponentEvent m_hStopThreadEvent ]
//
;
// Wait for the signal BOOL bContinue = TRUE while (bContinue)
to create
a
component.
;
{
switchC
:
:Wai tForMul ti pi eObjects(
2
,
hEventArray, FALSE. tTime)
ni_Wai {
Create the component, case WAIT_OBJECT_0: //
CreateComponentOnThread( break
)
;
;
Stop the thread, case (WAIT_OBJECT_0 +1): bContinue = FALSE break
//
;
;
// Do background processing, case WAIT_TIMEOUT:
WorkerFunctionC break
)
;
;
defaul
t
traceC'Wait failed.", GetLastErrorC
) )
:
} }
// Uninitialize the COM Library. CoUninitial ize( )
;
(continued)
334
TWELVE:
Threads
that we have finished.
Signal
//
Multiple
Set E ven t (ni_hCompon en t Ready Event) return
;
;
}
Since CSimpleFree credXes free threads, I
it
doesn't need a message loop. So
replaced MsgWaitForMultipleObjects with WaitForMultipleObjects.
m_hStopThreadEvent replaces
WM_QUIT for stopping the
The event
thread.
While we no longer need MsgWaitForMultipleObjects in ClassThreadProc, it is still used by CSimpleFreer.StartThread and CSimpleFree: :CreateComponent. These functions are called from the primary thread, which is an apartment thread, so they still need to process messages to keep the user interface from locking up. These are really the only differences between CSimpleFree and CSimpleApartment.
CCIientFree What we want to demonstrate next is the operation of two free threads sharing the same component without marshaling their interface pointers. To do this, I added two member functions to CCIientFree. CCIientFree is equivalent in this free-thread example to CClientAparttnent from the previous example. CCIientFree inherits from CSimpleFree and implements the CreateComponentOnThread and WorkerFunction virtual functions. CCIientFree s two new functions are ShareUnmarshaledlnterfacePointer and UseUnmarshaledlnterfacePointer. (I was inspired by the long names of some of the COM functions, so I decided to give these functions long names.) Pointer,
The
The
first
function, Sharellnmarshaledlnterface-
returns the FX. interface pointer used by CCIientFree in
interface
is
not marshaled, so
it
second function, UseUnmarshaledlnterfacePointer, that the CCIientFree object will use in
its
WorkerFunction.
can be used only from a free thread. The its
sets the
IX interface pointer
WorkerFunction. Let's look at
how these
CLIENT.CPP. In CLIENT.CPP, the function InitializeThread is used to create a free thread and a component. This function is similar to the InitializeApartment call
functions are used by
used in the single-threaded apartment
cd&e. After calling InitializeThread, the
second thread. However, instead of creating a second component, it shares the component created by the first thread. The code for InitializeThread! is shown here: client calls InitializeThread!. This function creates a
void
Initial izeThread2(
)
{
if
(g_pThread == NULL)
{
return
;
)
(continued)
335
N S
I
D E
COM
Create the second thread. This thread has a different WorkerFunction. g_pThread2 = new CClientFreeZ // //
;
//
if
Start the thread. (g_pThread2->StartThread())
{
traceC'Successf ul ly started second thread.")
;
// Get the same pointer used by the first thread. IX* pIX = NULL pIX = g_pThread->ShareUnmarshaledInterfacePointer( :
assert(pIX
NULL)
!=
)
;
Use this pointer in the second thread. g_pThread2->UseUnmarsha1edInterfacePointer(pIX) pIX->Release() //
;
;
}
}
InitializeThread2 creates a CClientFreeZ object instead of a CClientFree ob-
from CClientFree. The object CClientFree2 differs from implementation of WorkerFunction. The two Worker-
ject. CClientFree2 inherits
CClientFree only in
its
Functions are listed here: void
CClientFree: :WorkerFunction()
{
CSimpleLock Lock(m_hInterf aceMutex) m_pIX->Tick(l) m_pIX->Left()
;
:
;
}
void
CClientFreeZ: :WorkerFunction()
{
CSimpleLock Lock(m_hInterf aceMutex) m_pIX->Tick(-l) m_pIX->Right()
;
;
;
)
We'll discuss CSimpleLock in a second.
amount by which
I
modified IXr.Tick to take the
increment the count. I also added the methods Left and Right. These functions control which "hand" the count is contained in. CClientFree increments the count and places it in the left hand. CClientFree2 decrements the count and places it in the right hand. The InRightHand function returns TRUE if the count is in the right hand. So InRightHand tells us which was the last thread to use the component.
336
to
)
TWELVE:
Multiple
Threads
Component Changes some member functions to the component, we must also make the component thread safe. After all, we'll have two separate threads incrementing and decrementing the count simultaneously. To add protection to the component, I implemented a simple class named CSimpleLock: In addition to adding
class CSimpleLock {
publ ic: //
Lock
CSimpleLock(HANDLE hMutex) {
m_hMutex = hMutex WaitForSingleObject(hMutex, ;
INFINITE)
;
}
// Unlock ~CSimpleLock( {
ReleaseMutex(m_hMutex)
;
}
pri vate:
HANDLE m_hMutex }
;
:
You
mutex to the constructor for CSimpleLock. The constructor waits for the mutex before returning. The destructor for CSimpleLock releases the mutex when the function goes out of scope. To protect a pass a handle to a
function, you just create a CSimpleLock object: HRESULT
stdcall
CA::Tick(int delta)
{
CSimpleLock Lock(m_hCountMutex) m_count += delta return S_OK
;
;
;
}
HRESULT
stdcall
CA::Left()
{
CSimpleLock Lock(m_hHandMutex)
m_bRightHand = FALSE return S_OK
;
:
;
)
337
— INSIDE COM
Our component uses two different mutexes, m_hHandMutex and m_hCountMutex. One mutex protects access to the count while the other protects the hand. Having two separate mutexes allows a thread to access the
hand
one advantage of using free threads instead of apartment threads. Components on an apartment thread can be accessed only by a single thread: the apartment thread. If the component was on an apartment thread, one thread could not call Left if another was calling Tick. However, with free threads, synchronization is left to the component developer, who can use internal knowledge about the component to
while another thread
optimize
its
is
accessing the count. This
is
synchronization.
Free-Threading Marshaling Optimization Marshaling and synchronization are both slow. Avoid them of the rules about apartment threads
is
if
possible.
One
that you have to marshal interfaces to
apartment threads. But suppose a client in an apartment thread wants to use an interface on a free-threaded component in the same process. We really don't need to marshal because we are in the same process. We don't need COM to synchronize calls to our component either; after all, we made the component thread safe so that we could use it simultaneously from multiple threads. It seems as if a component in a free thread should be able to give pointers directly to apartment threads in the same process. Well, they can.
Not only
is
this
optimization possible, but the
COM Library supplies a
component that will do it for you. CoCreateFreeThreadedMarshaler'imTpXtxnenXs a component with an IMarshal interface that determines
special aggregatable
whether the client of the interface is in the same process. If the client is in the same process, it marshals the interface by leaving all the pointers the same and passing them unchanged. If the client is in a different process, the interface is marshaled using the standard marshaler. The cool thing about using CoCreateFreeThreadedMarshaler is that you don't have to care who the client is the magic happens behind your back. This optimization also works with CoMarshallnterThreadlnterfacelnStream and CoGetlnterfaceAndReleaseStream. So always explicitly marshal your interfaces and let COM handle the optimization. Following is the code that creates the free-threaded marshaler. The implementation of Querylnterface, which delegates IMarshal queries to the free-threaded marshaler, is also shown.
338
,
TWELVE:
Multiple
Threads
HRESULT CA::Init() {
HRESULT hr = CUnknown if (FAILED(hr)) return hr
:
:
Ini t(
)
;
;
/ Create a mutex to protect the count. m_hCountMutex = CreateMutex(0, FALSE, 0) if (m_hCountMutex == NULL)
return E_FAIL
;
/ Create a mutex to protect the hand. _hHandMutex = CreateMutex(0, FALSE, 0) f (m_hHandMutex == NULL)
return E_FAIL
/
r
;
;
;
Aggregate the free-threaded marshaler. = :CoCreateFreeThreadedMarshal er( GetOuterUnknown( &m_pIUnknownFreeThreadedMarshaler) (FAILED(hr)) :
)
f
return E_FAIL return S_OK
;
:
}
HRESULT
stdcall
CA:
:
Nondel egatingQuerylnterf ace(const
IID& iid, void** ppv)
{
if
(iid == IID_IX)
{
return Fini shQI(static_cast(this)
,
ppv)
;
}
(continued)
339
INSIDE COM
else if (iid == IID_IMarshal
)
{
return m_pIUnknownFreeThreadedMarshal er->Query Interfaced" id, ppv) }
el se {
return CUnknown
:
:
Nondel egatingQuerylnterfaceC
i
id
,
ppv)
;
}
A Note on Terminology As I mentioned at the beginning of this chapter, COM threading terminology varies considerably from document to document. The au-
Win32 SDK use the word "apartment" a little differently What I call an apartment, they call a "single-threaded apart-
thors of the
than
I
do.
ment." They also use the term "multi-threaded apartment" to refer to of the free threads together. Using their terminology, a process can
all
have any number of "single-threaded apartments" but only one "multi-
you read the Win32
SDK documentation,
threaded apartment."
If
hope
helps you avoid confusion.
this clarification
I
Threading Registry Keys COM needs to know the threading model supported by in-proc components and synchronized when crossmodel of your in-proc component, add a named value called ThreadingModel to your component's InprocServer32 key. {ThreadingModel is a named value and not a subkey!) There are three possible so that their interfaces are properly marshaled
ing threads.
To
register the threading
values for ThreadingModel: Apartment, It
Free,
or Both.
should be obvious that components that can be used in apartment
threads set the ThreadingModel
can be used in free threads
named value to named value
set the
Components that Components that can
Apartment. to Free.
be used by either free threads or apartment threads set the named value to Both. If a component doesn't know anything about threads, it doesn't specify a value. If the named value doesn't exist, no support for multiple threads is assumed. All components provided by an in-proc server must have the same threading model.
340
TWELVE:
Multiple
Threads
Summary In this single chapter,
we not only have learned how to implement apartment
threads and free threads, we have also learned what an apartment
ment
is
a conceptual entity consisting of a thread
apartment thread
is
similar to a typical
thread and a message loop.
ment threads and
A single
and
Win32 process in
a
is.
An apartAn
message loop.
that both have a single
process can have any
number
of apart-
free threads.
Apartment threads must
initialize
shal interface pointers to other threads.
COM, have a message loop, and mar-
A component created in an apartment
thread must be called only by the thread that created
it. This rule is the same window procedure. In-proc servers must write thread-safe entry points, but the components don't need to be thread safe because COM provides the
as for a
synchronization.
Free threads must initialize COM by using CoInitializeEx. They don't need message loops but should still marshal interfaces to apartment threads and to other processes. They don't need to marshal interfaces to other free threads in the same process. Components created by free threads must be thread safe because COM doesn't synchronize calls between free threads in the same process.
The question becomes, which
type of thread should you use? User intermust use apartment threads. This ensures that messages will be processed and the user won't think that the program has locked up. If you need only to perform a simple operation in background, apartment threads are the way to go. They are much easier to implement because you don't need to make the components used in them thread safe. No matter what, however, all calls to an apartment thread must be marshaled. This can be a significant performance hit. So if you need to do a lot of communication between code in different threads, either put the code into the same thread or use free threads. Calls between free threads in the same process aren't marshaled and can be much faster, depending on how the components implement their synchronization code. So which should you use to implement our simulated helicopter? I'll
face code
leave that as an exercise for you.
341
*
.•«^^#f>^
1
^^^^^^^H^^^^^L^^^^^^B^ ^^^^^^\!!^^^tL^^^^^^^^S, '
4"^
^H^gl^J^
^
CHAPTER THIRTEEN Putting
It
All
Together
he 200-year-old (or so) Chinese puzzle of tangrams consists of seven pieces form different shapes. (See Figure 13-1 .) I like tangrams because the pieces are simple, but the shapes you can make with them are limitless and complex. The tangram pieces include five isosceles right triangles: two small, one medium, and two large. The other two pieces are a square and a rhomboid. The seven pieces are shown in Figure 13-1 below. The shapes you can construct from the tangram pieces range from abstract geometric patterns to people, animals, trees, machines, and even all the letters in the alphabet. (See examples in Figure 13-2 on the following page.) Many of the images you can build are surprisingly evocative and subtle. For example, by slightly rotating the square used to represent the head, the apparent strain endured by a tangram rower in a tangram rowboat can be increased. J.
that are arranged to
Figure 13-1.
The seven tangram pieces have simple geometric shapes.
343
I
N S
I
COM
D E
i0
Figure 13-2.
You can arrange helicopter,
and a
the
tangram pieces
cat are
Like tangrams, built from
this
above.
COM
is
a variety of images.
very simple at the
tangrams
book together
as the basis for a
A rabbit,
same time
COM components can be very powerful.
fore, to use
of
to form
shown
I
a
that the applications
think it only fitting, there-
sample program that brings the ideas
in a single application.
The Tangram Program My original plan was
to base this book on a single program instead of a sepaexample for each chapter. But feedback from my reviewers quickly showed that this was not a viable approach. COM is the skeleton of an elephant mean, application. The application itself is flesh skin and muscles supported by the skeleton. It's hard to see someone's skeleton because flesh gets in the way, although it must be said that muscles animate the skeleton and skin protects and helps make the whole creature viable and identifiable. Similarly, COM lurks in a real application, behind all of the other code that makes the application perform its job. Throughout the book, then, to make COM stand out as much as possible, I decided to use very simple examples in each chapter. rate
—
344
—
—
By breaking the you
seal
this disk
on
Agreementls) included
If
pack,
Microsoft License accept the terms of the
you do not agree
in this
book.
please to these terrns,
unopened, the disk packs, contents, package the of rest along with the Press. to Microsoft
return the
book and
all
^•^•^^i
\TANGRAM\ SOURCE subfolder, as are the directions for building and regisTangram. The Tangram program
tering
is
constructed from several components and
many
components and interfaces are prefixed with Tangram and ITangram, respectively. The Tangram prefix makes it easy to determine which interfaces belong to the Tangram sample.
interfaces. All Tangram-specific
345
by the skeleton. It's hard to see someone's skeleton because flesh gets in the way, although it must be said that muscles animate the skeleton and skin protects
and helps make the whole creature
viable
and
identifiable. Similarly,
COM lurks in a real application, behind all of the other code that makes the job. Throughout the book, then, to make COM stand application perform its
out as
344
much as possible, I decided to use very simple examples in each chapter.
THIRTEEN:
Still, I
think
it's
helpful to see
ten Tangram, a complete
new ideas
It
All
Together
in context. Therefore, I've writ-
COM application for Microsoft Windows. Tangram
book together in a single In addition, Tangram demonstrates some OLE, ActiveX, and COM
demonstrates most of the techniques presented in application.
Putting
interfaces that
I
haven't discussed
this
yet.
Running Tangram
A compiled version of Tangram is included in theXTANGRAM folder on the First, run REGISTER.BAT to register the components. Then run the application by double-clicking its icon. you can When you start Tangram, a dialog box presents you with these options for how to run the program:
companion CD.
A list box allows you to choose which "world" component that you want drawing the tangrams on the screen. The TangramGdiWorld component draws a two-dimensional view, and the TangramGLWorld draws a three-dimensional view. stalled
If
the
OpenGL Library is
on your system, only the TangramGdiWorld option
A check box allows you
to
is
not
in-
available.
choose whether the "model" compo-
nents that represent the tangram pieces are to be run in-process or
components are run locally (unless them to run remotely, as described in
out-of-process. Out-of-process
you explicitly arrange for Chapter 10).
Once Tangram is running, you'll see seven shapes on the screen. Use the mouse to move these shapes around. Clicking the right mouse button rotates a shape counterclockwise. Holding the Shift key down while clicking the right mouse button rotates a shape clockwise. Try it, and see what kinds of images you can produce!
Pieces and Parts The source code for the Tangram program is on the accompanying CD in the \TANGRAM\SOURCE subfolder, as are the directions for building and registering
Tangram.
The Tangram program
is
constructed from several components and
many
components and interfaces are prefixed with Tangram and ITangram, respectively. The Tangram prefix makes it easy to de-
interfaces. All Tangram-specific
termine which interfaces belong to the Tangram sample.
345
INSIDE COM
To make it easy to find the Tangram-related interfaces and components in the Windows Registry, all Tangram GUIDs have the following digits in common: b53313xxx-20c4-lld0-9c6c-00a0c90a632c
The Tangram
application consists mainly of the
components and
inter-
faces listed in Table 13-1:
Components
Interfaces
Synopsis
TangramModel
ITangramModel ITangramTransform
position information
IConnectionPointContainer
for a single piece
ITangramVisual ITangramGdiVisual
Draws one piece
Tangram Gdi Visual
Contains shape and
ITa ngra mModelEven t TangramGdiWorld
ITangramWorld ITangramGdiWorld ITangramCanvas
Coordinates overall drawing
TangramCanvas
ITangramCanvas
Handles display needs
Table 13-1. The principal components and interfaces for the Tangram
application.
The interfaces and components in the preceding table that include Gdi in their names have equivalent OpenGL versions with GL in their names. The OpenGL version oiTangramGdiVisual is TangramGLVisual. The GDI versions of the interfaces and components present a two-dimensional view of the tangram playing field while the the tangram playing
OpenGL versions present a three-dimensional view of
field.
The following sections give a brief description of the main components that make up the Tangram program. Figure 13-3 shows a simplified schematic of the architecture of this application.
346
THIRTEEN:
Client
EXE
Putting
It
All
Together
TangramModel ITangramModel
I
Figure 13-3. Schematic of the architecture of the
(You might be thinking,
if
Tangram program.
that's the simplified view,
you probably don't want
to see the complicated view.)
The
Client
The
EXE
client
EXE contains the client code
that glues
all
of the components
to-
The client EXE doesn't have any interfaces but is simply normal Win32 C++ code, although the client uses MFC to make things a little easier. The client EXE consists of a bunch of pointers to the interfaces gether to form an application.
it
controls.
It
talks to
Tangram*Worldv\dL ITangramWorld, to the currently
lected visual through ITangramVisual,
and
to
se-
TangramModel through ITangram-
Model and ITa ngra m Tra nsform.
The Tangram program includes seven instances of the TangramModel component one for each tangram piece. Each TangramModel has a corre-
—
sponding Tangram*Visual. Tangram*Visual communicdites'mth TangramModel
347
INSIDE COM
ITangramModel interface. The Tangram* World component contains the seven Tangram* Visuals. Tangram*World controls e2.ch Tangram* Visual through its ITangram* Visual interfa.ce. Tangram*World also aggregates TangramCanvas
via the
to get
an implementation for ITangramCanvas, which
is
used by the client EXE.
The Tangra/nMoc/e/ Component TangramModel is the foundation of the Tangram program. A TangramModel component, which I sometimes just call a model, is a polygon that represents one tangram piece. The client manipulates a tangram piece by using the interfaces ITangramModel and ITangramTransform.
The ITangramModel Interface TTangramModel encapsulates the coordinates of the polygons that represent a tangram piece. The Tangram program places all tangram pieces on a 20-by20 virtual playing field and manipulates the pieces by using only coordinates
on this virtual field. It's up to the components that implement ITangramWorld and ITangramVisual to translate the vertices from this virtual playing field into shapes that can be displayed on the screen.
The ITangramTransform
Interface
The client uses ITangramTransform to move and rotate a tangram piece. In the Tangram program, translation is performed using the coordinates of the virtual playing field. Rotation
is
performed
in degrees.
The IConnectlonPointContainer Interface This tail
is
a standard
later in this
COM/ActiveX
interface. This interface
is
discussed in de-
chapter in the section "Events and Connection Points." The
interface provides a versatile
way for TangramModel to inform its correspondits position has changed.
ing Tangram*Visual component that
The TangramGdIVisual an6 TangramGLVIsual Components Every TangramModel gets an associated Tangram* Vwwa/ component, or visual
The TangramGdiVisual component
Windows GDI
draw a two-dimensional representation of a tangram shape. TangramGLVisual uses OpenGL to render an extruded tangram shape in a three-dimensional world. Each of these components maintains a pointer to TangramModeVs ITangramModel interfa.ce. Using this interface, a Tangram* FwMa/ component can get the vertices for its associated TangramModel and convert them to the appropriate screen coordinates. Tangram* VwMa/ components implement three interfaces: ITangramVisual, ITangram*Visual, and ITangramModelEvent. for short.
348
uses the
to
.
THIRTEEN:
Putting
It
All
Together
The ITangramVisual Interlace The Tangram program associated with a visual
how
uses the ITangramVisual interface to get the
and
model
to select a particular visual. Selecting a visual affects
the associated tangram piece
is
drawn.
The ITangramGdiWorld and ITangramGLWorld The TangramGdiWorld component
Interfaces
uses ITangramGdiWorld to display
screen a two-dimensional representation of the TangramModeh.
on the
The Tangram-
GLWbrW component communicates with each TangramGLVisualhy using the ITangramGLVisualinteridice to draw three-dimensional versions of Tangram-
Modeh.
The
use of multiple interfaces isolates the client from implementation-
TangramGdiWorld and TangramGLWorld encapsuvs. 3-D and keep the client completely isolated from them. It can happily manipulate the tangram pieces as if they were on a flat 20-by-20 playing field, regardless of how that field is actually displayed by the Tangram*World and Tangram* Visual components. While the client and TangramModel can ignore the way the tangram shapes are drawn, Tangram*World and Tangram*Visual must pay attention to the part each plays in drawing the tangram shapes. Tangram*World prepares the screen on which each Tangram*Visual draws. These components are written together as a cooperative pair. Given the way the interfaces are defined, it's almost impossible to write one without having the code to write the other. In this case, you can think of these two COM classes together as a single "component." specific details of drawing. late the details
of drawing in 2-D
ITangramModelEvent
A
Tangram* Visual component needs to know when the vertices of its corresponding TangramModel have changed. To do this, TangramModel defines an
named ITangramModelEvent. Whenever the vertices change, TangramModel calls the function ITangramModelEvent: lOnChangedModel in any
event interface
component that's
listening for events (in this case, just the associated visual)
We will discuss events in
the section "Events
and Connection Points"
later in
this chapter.
The TangramGdiWorld ax\6 TangramGL World Components Each Tangram*Visual component is contained in the corresponding Tangram*WbrM component. Tangram*World is in charge of preparing the display for Tangram* Visuals to draw on. The Tangram*World component is also in charge of handling all screen updates and palette issues. Tangram* World supports three interfaces: ITangramWorld, ITangram* World, and ITangramCanvas.
349
INSIDE COM
ITangramWorld The client EXE controls
component through the catchITangramWorld interface. The client EXE communicates very little with Tangram* Visuals, preferring instead to talk to Tangram*World dind. have it do the talking with Tangram* Visuals. the Tangram* World
all
The ITangramGdiWorld and ITangramGLWorld
Interfaces
These interfaces are used by the Tangram* Visuals components to communicate with their respective Tangram* Worlds. Back pointers from components to clients are very powerful,
but they can create reference cycles that result in
components whose reference counts never go to and thus never get released from memory. We cover this issue in the section "Circular Reference Counts" that begins on page 352.
The ITangramCanvas Interface The client EXE delegates to TangramWorld's ITangramCanvas
interface the
and updating the screen and handling the palette. But while both TangramGdiWorld and TangramGLWbr/(i support the ITangramCanvas interface, neither one implements it. Instead, they aggregate the component TangramCanvas, which implements the responsibility for
all its
display needs, including painting
interface.
Demonstrations As I said earlier, Tangram demonstrates most of the techniques presented in this book. Here I will quickly tell you where to look for some of the most interesting ones.
Aggregation Tangram* Wbr/rf aggregates TangramCanvas to provide the implementation for ITangramCanvas to the client EXE.
Containment
Containment is used often in the Tangram program. As you can on page 347, Tangram*World contmns the Tangram*Visunls, each
see in Figure 13-3
of which contains a TangramModel.
Tangram defines the Tangram Wor/rf component cateTangram World component category is a component that implements ITangramWorld and ITangramCanvas. The client EXE uses component categories to find the registered components that implement ITangramWorld a.nd ITangramCanvas. It then allows the user to pick the com-
Component categories gory.
A member of the
ponent she wants
350
to use.
THIRTEEN:
Putting
It
All
Together
Interchangeable components One of the goals of COM is to be able to replace one component with another component that supports the same interface. TangramGLWorld and TangramGLVisual can be interchanged with
TangramGdiWorld and TangramGdiVisual Both of these component sets dison the screen, but they draw completely different rep-
play the tangram shapes
resentations of the tangrams.
and remote components The TangramModelcovcvponenxs, can remote components. The client EXE asks users where they want to run the TangrawMoJ^/ components. The next three sections discuss some aspects of the Tangram sample that In-proc, local,
be run
as in-proc, local, or
weren't discussed in previous chapters of
this
book.
IDL Files we have been using a single IDL file to describe all of the interfaces and components in an application. While this is fine for an example, we would like a component to see only the interfaces it uses. Therefore, Tangram puts a single interface or a couple of related interfaces into each IDL file. These IDL files include an _/ suffix in their names. For example, MODEL_I.IDL contains the definitions for ITangramModel and ITangramTransform. To build a type library, a coclass statement and a library statement are needed. The rocZa55 statements describing the components are placed in separate IDL files, each marked with a _C suffix. These IDL files import the _I IDL files of the interface they use. The nice thing about this approach is that it is very flexible. However, each IDL file produces several other files, which results in a confusing proliferation of files. The following will help you keep things straight. Think of the _C as meaning CLSID. Think of the _I as meaning IID. If your code uses an IID, you In the last couple of chapters,
must include the associated
header file. For example, IID_ITangramModel is defined in MODEL_I.IDL. If I query for IID_ITangramModel, I must include MODEL_I.H and link with MODEL.I.C. If I am creating the TangramModel component, I need CLSID_TangramModel. The TangramModel component is described in MODEL_C.IDL. Therefore, I need to include MODEL.C.H and link to MODEL_C.C. If your IDL file imports another IDL file, your C++ code needs to include the header for the imported IDL file. For example, MODEL.I.IDL imports EVENTS_I.IDL. So if you include MODEL_I.H, you need to include EVENTS_I.H. _I
351
INSIDE COM
The _I and _C suffixes, by the way, are my own
convention. You can name Without suffixes, I was always confused about what was where. Now, if the compiler says that it can't find a CLSID, I immediately these
files
know
whatever you
that
I
need
like.
_C
to include or link to a
file.
The DLLDATA.C File The MIDL compiler doesn't always generate In
many cases, you might like
to
a
new version of DLLDATA.C.
have a single proxy/stub
DLL that supports IDL files. adds the new in-
multiple interfaces. However, these interfaces are defined in separate If
MIDL compiler finds an
the
terfaces to
it
existing
DLLDATA.C
file, it
new file. So you should check DLLDATA.C
instead of creating a
occasionally to be sure that only the interfaces you want are listed there.
Circular Reference When nent,
Counts
the TangramGdiWorld
it
component
creates a TangramGdiVisual compo-
passes an ITangramGdiWorld interface pointer.
component uses the
The TangramGdiVisual
interface pointer to convert coordinates
from model units
to pixels. Unfortunately, this creates a circular reference. (See Figure 13-4.)
TangramGdiWorld points to TangramGdiVisual, which points back to TangramGdiWorld.
and reference counting don't mix very well because circular references can result in components that never get released from memory. For example, TangramGdiWorld credited TangramGdiVisual and gets Circular references
a ITangramGdiVisualinteriace, for which
component
it
calls
AddRef.
The TangramGdiWorld
ITangramGdiWorld interface to TangramGdiVisual, which calls AddRef for that pointer. Now both the TangramGdiVisual and TangramGdiWorld components have reference counts of at least 1. also passes
its
TangramGdiWorld
TangramGdiVisual ITangramGdiVisual
List of ptrs to visuals
ITangramGdiWorld
\»-^-^
Figure 13-4. Reference cycle in the
352
Tangram program.
m_pGdiWorld
THIRTEEN:
Putting
Next, TangramGdiWorld releases ITangramGdiVisual in
when
its
All
Together
destructor,
But TangramGdiWorld haid a pointer to TangramGdiWorld' s ITangramGdiWorld inierfsLce, and it doesn't release this interface until its reference count goes to 0. The result is a deadlock. TangramGdiWorld won't release TangramGdiVisual until TangramGdiVisual releases it. TangramGdiVisual ']\xs\. as stubborn and refuses to release until TangramGdiWorld does. It's very similar to Dr. Seuss's South-Going Zax and North-Going Zax refusing to move to the side to let the other one pass. You can choose one of three solutions to this problem: don't call AddRef, explicitly terminate a component, or use another component.
which
is
called
its
reference count goes to
It
0.
'\s
Don't Call
AddRef
The first solution
is
the simplest. Don't reference count
in the reference cycle. This
is
one of the
interfaces
the solution used by the TangramGdiVisual com-
AddRef (or the ITangramGdiWorld inby TangramGdiWorld. TangramGdiVisual knovfs that is contained in the lifetime of TangramGdiWorld. It knows that as long as it alive, TangramGdiWorld is alive and the back pointer is valid.
ponent.. TangramGdiVisual doesn't call terface pointer passed to it
is
This technique
is
it
used frequently enough to have been given
its
own
A reference to an interface that isn't reference counted is known as a weak reference. A weak reference doesn't keep a component in memory. A strong reference is a reference that has been reference counted. A strong reference
name.
keeps a component in memory. (See Figure 13-5.) While this method is the simplest, it's not always possible to use it. A component that has a weak reference to another component needs to know
when it
the reference
because
its
lifetime isn't nested,
invalid.
is
lifetime
is
it
needs some other indication that the reference
TangramGdi World List of ptrs to 3 visuals
TangramGdiVisual doesn' t have to worry about
nested in that of TangramGdiWorld. But if a component's is
invalid.
TangramGdiVisual ITangramGdiVisual
p
ITangramGdiWorld^^-*
moGdiWorld
Figure 13-5.
TangramGdiVisual has a weak reference
is
reference fo ITangramGdiWorld.
The weak
indicated by the thin arrow.
353
N S
Use
I
COM
D E
Explicit Termination
Another way to break the deadlock is to give one (or both) of the components a way to exphcitly terminate the other component. Instead of waiting for the component's references to go to 0, one component needs to be able to tell the other component to release ter of creating a
all
of its interface pointers. This
new interface with
is
a simple mat-
a function that terminates the component.
(See Figure 13-6.)
TangramGdiWorld
TangramGdiVisual
!
ITangramGdiVisual
List of ptrs to visuals
Explicitly
break the
references to
j^m
this
ILifeTime |
component.
ITangramGdiWorld
I
|—O"^
m_pGdiWorld
Ir Figure 13-6. Reference cycles can be broken ifyou use a separate function that forces a component to release its references to other
components before
its
own reference count goes to
0.
But you have to watch out. In real-world programs, someone else might need the component that you are explicidy terminating. So it's a good idea to implement another reference count, a really strong reference count, in addition to the traditional one. IClassFactory::LockServer is an example of a really strong reference-counting function. The reference counts on class factory components won't keep a DLL in memory. But if you need to ensure that the DLL stays in memory while you are using a class factory, use the function IClassFactoryr.LockServer. See Chapter 7 and Chapter 10 for more on these topics. Other examples of really strong references are lOleContainer: .LockContainer and IExternalConnection::AddConnection. These functions give their clients ways to explicitly control the lifetimes of the components. still
Use a Separate Component Another way to break the circular reference is to provide a separate object or subcomponent to which one of the components in the cycle points. This subcomponent maintains a weak reference to its outer object. Figure 13-6 shows this graphically. In Figure 13-7, TangramGdiWorld controls the lifetime of TangramGdiVisual, which controls the lifetime of the TangramGdiWorld subcomponent.
354
THIRTEEN:
TangramGdiWorld
It
All
Together
TangramGdiVisual
——(>-—
List of ptrs to visuals
Weak
Putting
iTangramGdiVisual
I
reference
ITangramGdiWorld
j—O
Subcomponent
Strong references
Figure 13-7. Circular reference can he broken by using subcomponents that maintain lueak references to their parents.
Subcomponents are the most flexible method for avoiding circular reference counts. You don't need access to the source code or extra knowledge about the components other than the interface you are supporting to implement subcomponents. Inserting a subcomponent with a weak link can fix a circular reference cycle.
TangramGdiWorld and TangramGdiVisual don\ use a subcomponent avoid the circular reference counting. TangramGdiVisual
itself acts like
to
a sub-
component and maintains a weak reference to TangramGdiWorld. TangramGdiVisual and TangramModel dowse a subcomponent to avoid circular references in their implementation of connection points. I'll talk more about this in the next section, "Events and Connection Points."
Events and Connection Points we have
communication in which component can act like a client and control another component. Except during aggregation, a component in this book has never had a pointer to its client. Tangram changes all this. In the previous section, we saw how TangraynGdiVisual had a back pointer So
far,
exclusively used single-directional
the client controls the component. But the
One of the most common uses of a back pointer is to allow inform the client of various events. As we saw in the previ-
to TangramGdiWorld.
the
component
to
ous section, the simplest way to inform the client of an event is by using a weak reference. But a more versatile method is to use a subcomponent with a
weak reference
to
one of the components.
355
INSIDE COM
When ActiveX (OLE)
controls were developed, a versatile
and
flexible
The solution turned out to be connection points. A connection point is like a socket. The client implements an interface, which it plugs into the connection point. The component then calls the interface implemented by the client. These interfaces are known as outgoing, or source, event mechanism was needed.
interfaces.
In IDL, you
mark an outgoing interface with
an example, look in the
The
interface
is
ing the client.
source of the
component is callbecause the component is the
called an outgoing interface because the
It's
called a source interface
calls to this interface.
Let's look at a very simple callback calls
the source attribute. (For
TANGRAM\SOURCE\MODEL\MODEL_C.IDL.)
file
scheme. In Figure 13-8, TangramModel
the interface ITangramModelEvent
when
it
encounters an interesting
is implemented by TangramGdiVisual. To avoid a subcomponent implements ITangramModelEvent and TangramGdiVisual. (We saw this setup in the section "Circu-
event. ITangramModelEvent
circular reference, a
forwards lar
calls to
Reference Counts.") In Figure 13-8, TangramModel
interface
calls.
In this figure,
is
the source of the ITangramModelEvent
TangramGdiVisualis the eventual event sink for this interface.
we assume
m_pEvents. This solution
is
that
ITangramModel has a function that
simple and v«ll work. But
it's
initializes
not very versatile.
First,
there isn't a standard way for the client to discover the events supported by the components. Second, only a single event interface is supported. Third, TangramModel csin fire events only to a single client. In many cases, we would like to inform several clients when an event occurs. Connection points solve these problems.
TangramGdiVisual
Tangrarr)Model
m_plTangram Visual
ITangramModel
ITangramModelEvent
•
I
ITangramModelEvent |-^-0-^
Event sink
Event source
Figure 13-8. Simple callback scheme that isn 't used by the Tangram program.
356
THIRTEEN:
The
first
problem
is
Putting
It
All
Together
solved by the interface IConnectionPointContainer.
IConnectionPointContainer implements two functions, FindConnectionPoint and
The function FindConnectionPoint takes
EnumConnectionPoints.
the IID for an
outgoing interface and returns a pointer to the connection point for that interface. EnumConnectionPoints returns an object that enumerates all of the
connection points supported by this component. This satisfies the second requirement that a component support more than one outgoing interface. We'll discuss enumerators, by the way, in the next section. A connection point is actually an object that implements IConnectionPoint. Each outgoing interface corresponds to a single connection point. Each connection point can have multiple sinks. IConnectionPointr.EnumConnections returns an lEnumConnections pointer to an object that enumerates all of the connections. Each sink can also have multiple sources, but each of these is up to the sink to implement. Figure 13-9 illustrates the architecture of the TangramModel connecnon points. TangramGdiVisual uses IConnectionPointContainer to find \he IConnection-
Point that corresponds to IID_ITangramModelEvent.
TangramModel
TangramGdiVisual m_plTangramModel
ITangramModelEvent
•.
The TangramGdiVisual
•
IConnectionPomtContameA
CEnumConnectionPoints
lEnumConnectionPoints i
|
CTangramModelEventSink
\ ' ITangramModelEvent
"i
Figure 13-9. Connection point architecture.
357
INSIDE COM
component passes its FTangramModelEvent interface
to IConnectionPoint::Advise.
This informs the connection point that TangramGdiVisual wants to be notified
of events. TangramModel uses several simple
COM
objects to
implement
a
connection point. These objects are created using the C++ nax; operator, they don't have CLSIDs, and they are not registered in the Windows Registry. Figure 13-9 on the preceding page draws these objects as boxes composed of
dashed lines. These objects implement the enumerators for the collection of connection points, the collection of connections, and the connection point component itself. TangramModel has only a single connection point, but it still implements an object supporting the lEnumConnectionPoints interface. We'll discuss the enumerators in the following section. This connection point architecture (in Figure 13-9) is complex. Each connection point is a separate object, and there are two enumerators. But the extra complexity pays off in flexibility.
lEnumXXX Collections of interface pointers and other data are very important in component architectures. Therefore, COM defines a standard pattern for how to enumerate a collection. It doesn't have a standard interface because all versions of this pattern manipulate different types of data. The pattern for an enumerator is defined hy lEnumXXX, which has the member functions Reset, Next, Skip, and Clone. We saw two examples of this interface in the previous example, lEnumConnectionPoints and lEnumConnections. In Chapter 6, we actually used an enumerator interface, lEnumCATEGORYINFO, to enumerate the available categories.
The Next method
an enumerator interface returns items from the collection. One of the interesting aspects of the Mxf method is that you can get any number of items from the collection at one time. Instead of copying one item at a time from the component, the NextmeXhod allows you to specify how many items you want to copy. This can greatly increase performance when using remote components because it cuts down on trips back and forth across in
the network.
Using and implementing enumerators is easy. The trickiest part is rememcall AddRefa.nd Release for any interface pointers you're enumerating. If you're implementing the Next method, you must call AddRef for the interface pointers before giving them to the client. If you are using the Next
bering to
method, you must make sure you calli?^/^a5^ for the pointers. Either enumerators are snapshots, or they are alive. Most are snapshots. lEnumConnections and lEnumConnectionPoints both are snapshots. When you call IConnectionPoint::EnumConnections, the enumerator object you get repre-
358
THIRTEEN:
Putting
It
Together
All
sents a snapshot of the current connections that the connection point ing. If the
connections change because another cHent
calls
is
advis-
Advise to add a
connection or Unadvise to remove a connection, your snapshot
will
not be
updated.
COM
Is
About Standard Interfaces
As I've said many times before, COM is all about interfaces. The more components that use the same interfaces, the more opportunity there is to use components polymorphically. Many interfaces have already been defined by COM, OLE, ActiveX Controls, ActiveX Documents, and Automation. It's a good idea for the COM component developer to study these existing interfaces. Even if you decide not to use these interfaces in your ov^n application, you'll learn a lot about designing flexible component architectures from them.
Phweeeeeee! You now know how to create COM interfaces in C++, implement lUnknown and IClassFactory, and register your component in the Windows Registry. You also know how to build applications from components that contain and aggregate other components. You know what's involved in making your life simpler by using C++ classes and smart pointers. You can also write your interfaces as IDL files to get marshaling and type libraries for free. Well, that's
it.
Implementing IDispatch is a simple process of using ITypelnfo. Finally, you can code apartment-threaded components with the authority of a landlord. Given the knowledge you now have, should you decide to write a COM component, the only ingredients you'd be missing are specific COM interfaces. You know how to implement an interface. Now you need either to create one of your own interfaces or to find a standard interface to implement. There are hundreds of interfaces already designed by Microsoft for use by its ActiveX, DirectX, and OLE technologies. An ActiveX control is simply an implementation of a set of interfaces.
An ActiveX document is also just a bunch
faces with implementation. ActiveX controls
of inter-
and documents share many of the
same standard interfaces. Implementing ActiveX, DirectX, and
OLE interfaces is not trivial. Howimplementation details. The trouble is not with COM, because after reading this book you are a bona fide COM expert. (Send your name and address on the back of a $100 bill for your free certificate of expertise.) If you play with the Tangram sample long enough, you will also be a tangram expert. Enjoy! ever, these, as they say, are
359
INDEX Special Characters .
aggregation, continued
(dot notation), 218, 224
smart pointers
(se?
smart interface pointers)
Tangram example, 350
unknown abstract base classes. See pure abstract base
Annotated
interface problem, 171-73
C++ Reference Manual,
ActiveX technologies,
2, 4,
224, 359
ActiveX Template Library (ATL), 214, 226, 229
AddRef function,
39, 64-67. See also reference
counting
apartment threading.
apartment threads, 317 automatic marshaling, 321
example, 323-31
implementing, 320-31
aggregation, 162. See also reuse,
component
200-203
manual marshaling, 322 marshaling and synchronization, 318-19
vs.,
160-63
(see also
containment)
creating inner component, 178-80
delegating and nondelegating unknowns,
173-74
marshaling functions, 322-23 out-of-process
components and, 315-17
Registry keys, 340
APIs. See
COM Library; Win32
example, 183-203
AppID Registry
implementing, 169-83
APPIDs
implementing delegating unknowns, 176-77
applications. See also clients;
implementing nondelegating unknowns, 174-76 inner component constructor, 180
key,
273
(application identifiers), 120
breaking single monolithic, into multiple
customization
debugging, 69
metainterfaces, 202-3
example
outer component initialization, 178-79
reusable architectures, 17
component
unknown
interfaces,
180-83
interface, 173
Query Interface and, 169-71
reference counting and, 69
files,
92-98
matching pairs of inner and outer components, 203
outer component pointers to inner
DLL servers; EXE
servers
inner component creation function, 179
outer
and
See also threading
example, 69-77
containment
(Ellis
apartments, 313-17
avoiding circular reference counts, 353
blind,
The
Stroustrup), 45
classes
distributed,
as single
Win32
of,
with components, 3
4-5
{see
applications, example)
monolithic
files,
threads, 312-13
applications,
1-2
(see also
threading)
example
aggregation, 183-200 building, with
MIDL, 260-61
simulating, with C++, 169
361
INSIDE COM
Automation, 279-309
applications, example, continued
143-51
class factory,
IDispatch interface
class factor)' base class data array, class factor)'
base with
unknown
233-37
base
class,
238-43 client for
interface guidelines,
308-9
network HRESULTs, 109 type libraries, 299-305
DLL component, 93-94 DLL, 88-91
client to load
client using CoCreatelnstance, client with smart pointers,
complete
IDispatch interface)
(see
COM
(see
135-37
222-24
Tangram example
B back pointers, 355
backward compatibility,
8, 59. See also
versioning
Basic. See Microsoft Visual Basic
application)
binary form of components,
containment, 163-67
blind aggregation, 200-203
dual interface, 292-94
331-40
IDL
252-59
interfaces,
26
binding, run-time, 295
DLL component, 94-98 free threading,
7, 10,
BSTR
interface implementations,
(binary string) data type, 297-98
20-24
programming conventions, 19-20 Querylnterface,
C++ (language),
46-52
reference counting, 69-77 single-file clients
2. See
including components,
20-24, 46-52
classes vs.
components, 25
COM,
class libraries vs.
smart interface pointer
aba Microsoft Visual C++
calling conventions, 21
class,
214-17
10
compilation, 20, 260-62
threading, 323-31
dispinterfaces, 288
type library, 300-301, 302-3
multiple inheritance and casting, 43-45
unknown
protected
base
class,
architectures. See
arguments. See
227-31
component
also
architectures
parameters
dispinterface errors, 292
Invoke
member function, 287-92
member
pure abstract base
functions, 204 classes (see
pure abstract
base classes) simplifying
component implementation with
classes (see simplification)
simulating aggregation, 169
named, 289-91 optional dispinterface, 297
vtbl structure,
30
(see also
virtual function
tables (vtbls))
arrays
wrapper
class factory
base
class,
classes,
callbacks, 205,
passing, between client
calling conventions, 21
256-58
unknown
356
and component, casting,
assignment interface pointer,
219-20
interface pointer, 220
43-45, 176
categories,
CATIDs
component,
57, 120, 123-25,
(category identifiers), 120
CClientApariment
362
224-26
233-37
class,
324-31
350
Index
333-38
CClientFree class, cdecl
class factories, continued
implementing, 141-54
convention, 21
265-69
CFactory class. See class factory base class
local server,
characters, Unicode, 255, 298
preventing
DLLs from unloading, 156-57
registering
components, 153-54
352-55
circular reference counts, 206,
reusing, 154-55
avoiding AddRef, 353 explicit termination,
supporting multiple components in single
354
DLL, 154-55
separate components or subcomponents,
354-55
threading and, 321,322
class contexts,
unloading DLLs, 156-57
133-35, 264, 267-68
class factory
classes
class factory base (see class factory
components factories
and
32-34
{see also
vtbls,
31-32
pure abstract base
components with C++
smart pointer
unknown base
(see
class factories,
(see
smart interface pointers)
(see
unknown
base class)
131-57
vs.
communication with
component example,
as,
279
DLL servers,
architecture and,
6,
as single file including
example, for
(see class factor)'
CoGetClassObject
base class)
142-43,
37-38
components,
CoCreatelnstance,
140-41
and components, 151-53
142-43
components
determining whether
DLL component, 93-94
example, to load DLL, 88-91 example, using CoCreatelnstance, 135-37
CoCreatelnstance fimction)
control flow of clients
creating
Automation controllers
20-24, 46-52
CoGetClassObject, 138-39, 141
creating,
214
151-53
wrapper, 224-26
(see also
243-44
202-3
commimication with components, 24-25
simplification)
base class
servers (see local
clients
(see
classes)
simplifying
step-by-step procedure,
class libraries, 10,
pointers)
EXE
servers)
class interfaces,
multiple instances and (see
233-37
creation function prototype, 232
30-31
interfaces)
pure abstract base
231-37
array,
modifying, to support
vtbl pointers,
interfaces, inheritance, and,
pointers
class,
example, 238-43
factories)
(see class
instance data
base class)
25
vs.,
base
component data
apartment threading, 323-31
example, with smart pointers, 222-24 multiple components and multiple, 98-100
quer)ing for interfaces
(see
Querylnterface
function) with,
DLL
138-41 can be unloaded,
simplification
wrapper
(see
smart interface pointers;
classes)
Tangram example, 347-48
156 DllGetClassObject, 142, 151
threading, 313-14
encapsulation, 141
CLSCTX.
example, 143-51
CLSIDFromProgID function, 121
IClassFactory2 interface, 140 IClassFactory interface,
constants, 133-35, 264, 267-68
CLSIDFromString iuncixon, 128
139-40
363
INSIDE COM
CLSIDs
COM
(class identifiers)
(Component Object Model),
continued
CoCreatelnstance and, 116
computer languages, DLLs, and APIs
converting string, to and from GUIDs, 127-28
Distributed
{see
inheritance
statement, 301
inheritance)
{see
CoCreateFreeThreadedMarshaler function, 338
interfaces {see interfaces)
CoCreateGuid function, 112
Library
CoCreatelnslanceEx function, 274-75
Microsoft Visual
CoCreatelnstance function, 91, 116, 132-37
as
CoGetClassObject function
vs.,
138-39, 140-41
style,
1
Querylnterface function)
reference counting rules, 80-83
{see also
reference counting) specification, 9
276
standard interfaces, 359
declaration, 132-33
threading
example, 135-37 inflexibility of,
C++ compiler support, 211
programming {see also
factories)
(see also class
COM Library)
{see
Query Interface rules and regulations, 52-56
133-35
CLSIDs, GUIDs, and, 91, 116
DCOM and,
10
history of, 12
Registry keys, 118-19
class context,
vs.,
COM
Distributed
(DCOM))
getting ProglDs from, 121-22
coclass
COM
{see
versioning
137
threading, manual marshaling, and, 322
threading)
{see
versioning)
and CoUnMarshallnterface
CoMarshallnterface
functions, 322 using, 133
codes. See
CoMarshallnterThreadlnterfacelnStream function,
HRESULTs
322-23, 332, 338
coding conventions, 19-20.
See also
COM Library,
programming
11,
125-28
component management
services, 11
CoFreeJJnusedLibraries function, 156
converting string CLSIDs to and from GUIDs, CoGetClassObject function, 138-39, 140-41, 265,
127-28
274 creating components, 91, 132-37 CoGetlnterfaceAndReleaseStream function, 322-23,
determining free-threading support, 276-77
332, 338
initializing,
CoGetMalloc function, 127 Colnitialize
and
CoUninitialize functions, 126, 315,
command line parameters EXE server, 271
320 CoInilializeEx function, 126,
COM
332
(Component Object Model),
Automation and
(5e«
2,
9-12
Automation)
{see
Tangram
component
architecture requirements and,
components)
364
component
features,
9-10
(Microsoft
component
example application)
{see also
265
compilation. See Microsoft Visual C++;
complete example application
11-12
registration,
compatibility, backward, 8, 59. See also versioning
clients {see clients)
component
126
memory management, 127
architectures)
{see also
IDL compiler)
architectures, 1-13
application customization, 3 benefits,
3-5
MIDL
Index
component
containment, 161-62. See
architectures, continued
COM as, 9-12
COM
(see also
(Component
Object Model specification))
component
libraries,
4-5
wrapper
controlling
ActiveX Template Library (ATL), 214, 226, 229
Automation servers
279-80
as,
component)
{see {see
{see also
Crealelnstance function, 40, 51,
component
class factory base class,
64-65, 78-79
{see also
reference
CoCreatelnstance function
DLL server,
153-54
getting
Registry information, 118-22
{see also
Windows
Registry)
reuse,
component)
as sets of interfaces,
simplification
unknown
{see also class
{see
CoCreatelnstance
function)
multiple clients and multiple, 98-100
{see
232
factory base class)
counting)
registering,
component
class factories {see class factories)
115-16
lifetimes, 24,
86
CreateTypeLib function, 299 creation,
architectures) identifiers,
265-68
268-69
127, 258
{see also
DLL servers) EXE servers)
features of, 9-10
naming
conventions
CoTaskMemAlloc and CoTaskMemFree functions,
56-57
Querylnterface function)
DLLs
173, 231
CoRevokeClassObject function,
definition of, by Querylnterface,
EXEs
unknown,
CoRegisterClassObject function,
25 creation,
See also IDispatch
cookies, 266
categories, 57, 120, 123-25, 350
in
Automation, 279.
conventions. See calling conventions;
{see also
IDispatch interface)
in
224-26
interface
components
reusing
classes,
controllers,
Component Category Manager, 124-25
{see
component
aggregation)
contracts, interfaces as implicit, 60
encapsulation, 6-8
creation
{see also
Tangram example, 350
linking, 6
vs.,
also reuse,
extending interfaces, 167-69
distributed components,
classes
160-63
vs.,
example, 163-69
4
component requirements, 5-8
dynamic
aggregation
16-18
{see class
{see also
interfaces)
factory base class;
base class)
lUnknown pointer, 40
inflexibility issue, 100, class,
CSimpleFree class,
333-38
CUnknown
class. See
137
323-31
CSimpleApartment
unknown base
class
customization, application, 3
customization interfaces, 205-7
Tangram example, 348-50,
351
threads and, 320 unknovfcfn interface {see
86-91
lUnknown
interface)
GUIDs as identifiers, 115-16 {see also GUIDs (Globally Unique Identifiers))
D data
using
versions
{see
class instance,
versioning)
size
connection points, 355-59
enumerating
collections,
events and, 355-58
class factory base class,
358-59
30-31
233-37
{see also
instances, class)
of passed, in IDL, 256-58
types {see 1)^65)
DCOM.
See Distributed
COM (DCOM)
365
INSIDE COM
DCOMCNFG.EXE utility, 271, 273-74 DDE (dynamic data exchange), 12
DLL servers,
files,
dynamic linking and component architecture, 5-6, 10, 11
implementing, 176-77
example, 93-98
macro implementing, 229-30
EXEs
operator, 24
providing information about internal
vs.,
90-91
{see also
EXE
servers)
exporting functions from, 86-88
designing components for reuse, 203-7 states,
free threading and, 332 inflexible creation and, 100
204-5
loading, 88-91
simulating virtual functions, 205-7
DlSPlDs (dispatch
identifiers),
multiple clients and multiple components,
282
98-100
dispinterfaces (dispatch interfaces), 282-83, 309. See also dual interfaces; IDispatch interface
Distributed
can be unloaded,
dumping symbols exported from, 87
87-88
delegating unknowns, 173-74
delete
DLL
156
debugging, reference counting and, 69
DEF
continued
determining whether
COM
(DCOM),
preventing unloading, 156-57
11
Registry information, 118-19
APPIDs, 120
configuration
utility,
self-registration, 122-23,
126
CoInitializeEx function,
determining
multiple components in single, 154-55
simplification
271, 273-74
unknown base
276-77
availability of,
{see class
153-54
factory base class;
class)
Tangram example, 351
free threading, 318, 332
querying for multiple interfaces, 57, 275-76
unloading, 156-57 DllUnregisterSeruer function, 122, 153-54,
Registry information, 274-77
265
dot notation, 218, 224
remote servers and, 271-73
dual interfaces, 283-85, 309. See
Windows 95 and, 276
also IDispatch
interface
distributed components, 4-5
example, 292-94 Distributed
Computing Environment (DCE),
113,249,252 DllCanUnloadNow function, 156, 265, 321
DLLDATA.C file,
DUMPB1N.EXE utility, 87 dumping DLL exported symbols,
352
DllGetClassObject function, 142, 151, 265, 321
dynamic
linking, 5-6, \0, ll,9\. See also
servers;
DllMain function, 153-54 DllRegisterServer function, 122-23,
DLL servers,
85-100. See
also
153-54, 265
Embedding
apartment threading and, 313-15, 316, 321 breaking applications into multiple
files,
92-98
133-35
class factories {see class factories)
line parameter, 271
encapsulation. See also polymorphism class factories
components, 86-91
as
and, 141
17-18
component requirement,
smart interface pointers pointers)
wrapper
366
command
COM interfaces and,
building, 260-61
client creation of
DLL
proxy DLLs; stub DLLs
proxy DLLs; stub
DLLs
class contexts,
87
dynamic data exchange (DDE), 12
classes,
224-26
{see
5,
6-8
smart interface
1
)
Index
enumerators, 357-59 errors. See also
FormatMessage function, 107
HRESULTs
changing codes
for,
free threading. See also threading
example, 333-38
109
317-18
dispinterface argument, 292
free threads,
dispinterface exceptions, 291-92, 307-8
implementing, 331-40
Win32, 107
marshaling and synchronization, 318-19
events. See
marshaling optimization, 338-40
connection points
EXCEPINFO
structure, 291-92,
307-8
exceptions, dispinterface, 291-92 raising,
Registry keys, 340
functions
COM
307-8
Library
{see
COM Library)
282-83
execution contexts, 133-35, 264, 267-68
dispinterface,
EXEs,
exported DLL, 86-88
EXE
client. See applications; clients
servers,
loading DLLs, 88-89
247-77
apartment threading and, 315-17
LPCs and, 250
building, 260-61
methods, 287-89, 297
building proxy DLLs, 261-62
pure
133-35
class contexts,
vs.,
90-91
(see
IDL
(Interface Definition
Language) local
GelProcAddress function,
procedure
(LPCs), 249
calls
GUIDGEN.EXE
marshaling, 250
GUIDs
DLL
registration,
262-64
proxy and stub DLLs, 250-52
remote servers
{see
remote
servers)
Tangram example, 351
extern
EXE
Unique
Identifiers), 111-16
APPIDs, 120
CATIDs, 120
CLSIDs
{see
CLSIDs
(class identifiers))
converting string CLSIDs to and from, 127-28
354
declaring and defining, 113-15
exported functions, DLL, 86-88 replacing, in
112
utility,
(Globally
comparing, 115
threading, 315-17 explicit termination,
88-89
global variables, reference counting and, 80, 82
local servers {see local servers)
proxy and stub
virtual functions; virtual
DLL servers)
{see also
IDL, MIDL, and
pure
threading, 331
different processes and, 247-52
DLLs
virtual {see
function tables (vtbls))
servers,
265
"C" statement, 86
DISPIDs, 282 history of, 113 IIDs, 40, 57, 120
LIBIDs, 120, 304
long integers facility
codes, 105-6
FACILITY_ITF
FAILED macro,
facility
vs.,
112
passing, by reference, 116
code,
1
10-1
40, 108
failure codes, 109. See also
ProglDs, 119, 120-22 using, as
component
identifiers,
115-16
HRESULTs
367
INSIDE COM
H
IDispatch interface, continued
marshaling, 308
history of
COM,
history of
GUIDs, 113
12
methods and
HKEY_CLASSES_ROOT
Registry key, 118-19
307-8
returning results, 291
HRESULTs, 104-11 changing
properties, 287-89
raising exceptions,
failure codes, 109 (see also errors)
SAFEARRAY data
type,
298-99
285-99
definitions of, 106-7
using,
IDL, 255-56
VARIANT structure, 294-97 IDL
interface-specific codes, 110-11
multiple status codes, 108
(see
MIDL
(Microsoft
IDL compiler))
creating type libraries, 299-302
networking and, 109
example
40
Querylnterface,
(Interface Definition Language), 252-64
compiler
interfaces in,
252-59
HRESULTs, 255-56 281-82
IDispatch interface, I
importing other IDL
ICatlnformation interface, 124-25 ICatRegister interface,
in
124-25
size
139-40 {see class
structures,
Createlnslance function, 179
IConnectionPointConlainer interface, 357-58 ICreateErrorlnfo interface,
ICreateTypeLib interface, identifiers. See
GUIDs
299-300
Unique
errors,
280-81
282-83
292-94
guidelines for types of interfaces, 308-9
Identifiers)
implementations. See applications, example import keyword, IDL,
256
281-82
inheritance casting
and multiple, AA-Ab
component reuse
vs.,
interfaces and, 19,
implementing, 305-8
multiple, 16, 19,
Invoke function arguments, 287-92
nonvirtual, 42
368
Unique
inflexible creation issue, 100, 137
exceptions, 291-92
file for,
(Globally
IMultiQI interface, 57, 275-76
dual interfaces, 283-85
IDL
IIDs (interface identifiers), 40, 57, 120. See also
implementation inheritance, 159
dispatch parameters, 289-91
interface,
358-59
IMarshal interface, 250
292
data type, 297-98
example dual
interface pattern,
IIDFromString function, 128
IMalloc interface, 127
280-85
COM communication vs., dispinterfaces,
lEnumConnections interface, 357
GUIDs
Identifiers)
IDispatch interface,
lEnumConnectionPoints interface, 358
lEnumXXX
307
(Globally
258-59
Tangram example, 351-52
LockServer function, 156-57, 269-71
BSTR
255
factory base
class)
argument
of passed data, 256-58
strings,
base class implementing
256
pointers, 254
IClassFactory2 interface, 140 IClassFactory interface, 138,
files,
and out parameters, 254-55
159-60
25-26
25-26
)
Index
interfaces, continued
inheritance, continued
159-60
polymorphism vs., polymorphism) vtbls
inheritance and, 19, 25-26, 159-60
{see also
and, 32-34
Init function,
178-79, 231
initialization,
COM Library,
126
internal state,
204-5
lUnknown
lUnknown
as
{see
memory structures,
interface)
15,
28-34
IDL, 255
metainterfaces, 202-3
reference counting and, 82
multiple, 24, 25-28
naming conventions, 19-20, 59
parameters IDL, 254-55
outgoing or source, 356
reference counting and, 81
Pascal calling convention, 21
in-process servers. See
polymorphism and, 27-28
DLL servers
as
InprocSeruer32 Registry key, 118-19 Inside
OLE
(Brockschmidt), 25
pure abstract base 28-34
querying for
instances, class
and
vtbls,
{see also
reference
counting) result codes {see
interfaces,
18-19, 24,
Querylnterface function)
{see
reference counting, 67-69
31-32
30-31
vtbl pointers and, interface_casl
classes, 16,
querying for multiple, 57, 275-76
155
class factory,
multiple,
{see also
virtual function tables (vtbls)
in-out parameters
in
{see also
inheritance)
HRESULTs)
template function, 221 return codes specific
15-34
to,
110-11
reusing application architectures, 17
client-component communication and, 24-25, 280-81
smart interface pointer classes
{see
smart
interface pointers)
components as sets components)
of,
16-18
{see also
standard, 359
customization, 205-7 definition macros, 129
dispatch
{see
IDispatch interface)
unchanging
status of,
versions
{see
versioning)
wrapper
classes,
in IDL,
and
InterlockedDecrement
functions, 70, 321
example, 20-24 example,
224-26
Interlockedlncrement
encapsulation and, 6-7
27
internal state interfaces,
253-59
Invoke
extending, with aggregation
(s««
member
204-5
function, 282-85
aggregation) ISupportErrorlnfo interface, 307
extending, with containment, 167-69
{see also
ITypelnfo interface, 303,
containment) functions
{see
pure
virtual functions)
identifiers {see
GUIDs
(Globally
Unique
Identifiers); IIDs (interface identifiers))
implementing, 18-27
ITypeLib interface, 303
lUnknown
guidelines for selecting, 308-9
305-6
interface, 19
aggregation and
{see
unknown
interfaces,
aggregation)
base class implementing
{see
unknown base
class)
as implicit contracts,
60
369
INSIDE COM
lUnknown
LoadRegTypeLib function, 302
interface, continued
component
lifetime control, 24, 64-65,
78-79
LoadTypeLib and LoadTypeLibFromResource functions, 302
reference counting)
(see also
functions, 38-40,
64-65
local
procedure
(LPCs), 249, 250
calls
nonvirtual inheritance and, 42
LocalServer32 Registry key, 265
pointer class for, 221
local servers, 90-91,
pointers
counting users, 271
40
to,
Querylnterface function (see Querylnterface
function)
determining whether server can
quit,
269-71
preventing, from quitting, 271
each component instance, 52-53
single, for
264-71
smart interface pointers and, 220-21
replacing exported functions, 265
running, as remote servers
{see
remote
servers)
running example, 264 starting class factories, keys, Registry, 117. See also
Windows
Registry
265-68
stopping class factories, 268-69
Tangram example, 351 local variables, reference
languages, programming. See also
C++
(language); Microsoft Visual Basic;
Microsoft Visual
COM vs.,
C++
DLL servers, EXE servers,
(library identifiers), 120,
304
M
{see
main function, 271
COM Library)
makefiles, 93, 99, 260-61, 292
MAKE_HRESULT macro,
component, 4 dynamic link
{see
DLL servers; dynamic
linking)
{see
automatic
type libraries)
memory component
lifetimes
{see also
component,
24, 64-65,
78-79
{see also
reference counting) server,
linking, static
vs.
dynamic, 91. See
LoadLibrary function, 88-89, 156
on-demand task
269-71
linking
370
manual, and threading, 321-22
threading and, 318-19, 320
120
licensing interface, 140
EXE
vs.
Automation, 308
statement, IDL, 260, 300-301
licenses,
1 1
mangling schemes, 86-87 marshaling, 250
OpenGL, 345
library
also
templates
Automation, 298, 308
type
269-71
macros, interface definition, 129. See
libraries
COM
156-57
10
independence from, and components, 7-8, 9 LIBIDs
counting and, 80, 82
locking
lifetime control, 24, 64-65,
reference counting) resources, 69
memory
allocator, 127,
258
vtbl {see virtual function tables (vtbls) also
dynamic
message loops, 271, 313-17 metainterfaces, 202-3
methods, 287-89, 297.
See also functions
78-79
Index
Microsoft Foundation Class Library, 10, 214, 294
naming conventions,
dual interfaces, 284-85
return codes, 105
IDispatch interface, 280
nested lifetimes, 78
methods and
properties, 287-88
Object Browser
continued
ProglDs, 120-21
Microsoft Visual Basic
utility,
networks
303
type libraries, 299
Microsoft Visual C++. See also C++ (language)
component
architecture and, 4-5,
HRESULTs
and, 109
remote servers
{see
remote
new operator, 24
COM support, 211
nondelegating unknowns, 173-74
implementing, 174-76, 230
112
utilities,
10
servers)
compilation, 20, 92-93, 99, 292
GUID
7,
makefiles, 93, 99, 260-61, 292
nonvirtual inheritance, 42
template function support, 221
wrapper
224
classes,
Microsoft Windows, 276, 318, 332. See also
COM
Distributed
Object Browser
(DCOM); Windows object
utility,
303
keyword, IDL, 254
Registry
MIDL
IDL compiler), 252, 259-64. IDL (Interface Definition
object linking and
embedding (OLE), 12
(Microsoft See also
ODL, 300 OLE32.DLL.
Language)
See
COM Library
building example program, 260-61
OLEAUT32.DLL,
298, 308
building proxy DLL, 261-62
OLE Automation.
See
files
Olelnitialize
generated, 259-60
proxy and stub
MkTypLib
module
utility,
DLL
registration,
262-64
definition
files,
87-88
multiple inheritance, 16, 19, 24, 25-26 casting and,
43-45
multiple interfaces, 24, 25-28. See also interfaces
querying
for, 57,
275-76
multiple threads. See threading
MULTI_QI
structure,
275-76
OleUninitialize functions, 126,
320
OleView
300
and
Automation
utility,
on-demand
125, 303
resources, 69
OpenGL Library, 345 Open Software Foundation operator=, 214,
operalor==,
1
(OSF), 113, 249, 252
213-14
operator->, 169,
219-20
15
optimization free-threading marshaling, 338-40
mutexes, 331-38
reference counting, 77-83
outer unknown, 173, 231
N
outgoing interfaces, 356
named arguments, 289-91 name mangling, 86-87
out-of-process servers. See
EXE servers
out parameters
naming conventions function
name
IDL, 254-55
clashing and, 26-27
reference counting and, 81
IDL
files,
352
interfaces, 19-20,
overlapped lifetimes, 78-79 59
371
INSIDE COM
programming, continued languages
parameters. See also arguments
command command
line, for
EXE
servers, 271
line, for registration,
propget
40
reference counting, 80-83
GUIDs by
EXE servers
and, 250-52
registration,
262-64
ProxyStubClsid32 Registry key, 263
reference, 116
pure abstract base
permissions interface, 140 pointer_default keyword, IDL,
functions
254
(see
pure
I
28-34
classes,
virtual functions; virtual
function tables (vtbls))
pointers
inheritance and, 32-34, 160
aggregation outer component, to inner
component
interfaces,
interfaces as, 16, 18-19, 24
180-83
back, 355
multiple instances, 31-32
getting lUnknown, 40
pointers
IDL and, 254 multiple inheritance and casting,
43-45
non-interface communication and, 24-25
smart
288
threading, 321
Pascal calling convention, 21
passing
attributes,
MIDL, 261-62
building, with
marshaling)
(see
Querylnterface,
and propput IDL
proxy DLLs
254-55
marshaling
I
COM functions as, 287-89
properties,
265
dispatch, 289-91 in IDL,
languages, programming)
(see
Pascal calling convention, 21
(see
smart interface pointers)
specifiers, 18
pure
virtual functions, 18-19. See also virtual
function tables (vtbls)
clashing
parameters
See also
and naming convention, 26-27
(see
as properties,
parameters)
287-89
Querylnterface (see Querylnterface function)
example, 33-34
lUnknown
30-31
pure
name
encapsulation
inheritance
instances,
calling conventions, 21
vtbl (5«« virtual function tables (vtbls))
polymorphism, 17-18, 27-28.
and
159-60
vs.,
(see also
interface and,
inheritance)
QueryMultiplelnterfaces,
57
reference counting, 64-67
39-40
(see also
AddRef
function; reference counting; Release
processes
function) class contexts
and, 133-35 simulating,
205-7
EXEs and, 90-91, 247-52 ProglDFromCLSID function, 121 ProglDs (programmatic getting
identifiers), 119,
CLSIDs from, 121-22
naming convention, 120-21 Registry format, 121
Querylnterface function, 37-61
aggregation and, 169-71 casting pointers and,
cHent
programming applications
120-22
(see
applications)
role,
37-38
definition of components by,
coding conventions, 19-20
encapsulating, 221
COM as style of,
example, 46-52
372
11
43-44
56-57
I
Index
Query Interface function, continued
Release function, continued
implementing, 41-44
aggregation and, 181-83
lUnknown interface and, 38-40, 52-53 /Unknown interface)
{see also
parameters, 40
new
relocatability, 7, 10
versions of
components and, 57-60
versioning)
{see also
remote procedure remote
querying for interfaces, 38-52 rules
example, 69-77 smart interface pointers and, 218
servers,
calls
(RPCs), 249
90-91, 271-77
CoGetClassObject function, 274
and regulations, 52-56
DCOM DCOM
simplifying, 220
using, 41
configuration tool, 271, 273-74 Registry information, 274-77
determining
QueryMultiplelnterJaces function, 57
DCOM availability, 276-77
querying for multiple interfaces, 275-76
running
local servers as,
271-73
{see also
local
servers)
reference, passing
GUIDs
by,
Tangram example, 351
116
Windows 95 and, 276
reference counting, 63-83
requirements, component, 5-8. See also
aggregation and, 180-83 circular, 206,
component
352-55
COM
debugging, 69
65-67
AddRef function;
{see also
Release function)
encapsulation, 6-8
on-demand, 69
HRESULTs
results, dispinterface,
smart interface pointers and, 67, 218
{see also
REFIID, REFCLSID, and
REFGUID
reuse, class factory, reuse,
smart interface pointers) expressions,
291
154-55
component, 159-63
containment
vs.
aggregation, 160-63
{see also
aggregation; containment)
116
REGEDT.EXE and REGEDT32.EXE
utilities,
118,263
Windows
command
REGSVR32.EXE
designing components inheritance
RegisteiTypeLib function, 302
Regserver
versions, 8 {see also versioning)
linking, 6
result codes. See
80-83
Registry. See
(Component
type libraries as, 302
resources, 69
optimization, 77-80 rules,
COM
resources
64-65
memory-management technique, 65-77
on-demand
{see also
language independence, 7-8
67-69
lifetime control and, 24, as
component dynamic
implementing, for whole components, 69-77 interfaces,
and, 11-12
Object Model specification))
enumerators, 358 functions,
architectures
utility,
for,
203-7
159-60
interfaces and, 17
Registry
line parameter,
vs.,
providing information about internal
265
123, 153
Release function, 39, 64-67. See also reference
states,
204-5 simulating virtual functions, 205-7
run-time binding, 295
counting
373
INSIDE COM
structures, IDL,
SAFEARRAY data
DLLs
stub
298-99
type,
components;
servers. See
DLL servers; EXE
EXE
211-45
simplification,
250-52
servers and,
262-64
registration,
servers
258-59
threading and, 321
ActiveX Template Library (ATL), 226
subcomponents, 354-55
C++ wrapper
subkeys, Registry,
class factory
224-26
classes,
base
class,
SUCCEEDED
231-37
example, 238-43 server-side,
See also
7.
Windows
Registry
synchronization, threading, 318-19
211-26
client-side,
1 1
macro, 40, 51, 108
and
SysAllocString
SysFreeString functions,
297-98
226-44
smart interface pointers, 212-24 step-by-step
component procedure, 243-44
unknown base size_is
class,
227-31
Tangram example
application,
circular reference counts,
modifier, IDL, 256-58
client
smart interface pointers, 212-24
343-59
352-55
EXE, 347-48
components and
interfaces,
345-47
assignment of interface pointers, 219-20
display components,
encapsulating Querylnterface, 221
drawdng components, 348-49
implementing
client,
222-24
events and connection points, 355-59
implementing interface pointer
class,
as pointers to interface pointers,
214-17
213-14
problems, 224
unknown
assignments, 220
unknown
pointer
class,
using interface pointer classes
vs.,
225
221 class,
217-18
(see also
wrapper
termination, explicit, 354 this
pointer, 43-45, 176
COM models, 312-19 COM threads, 313 HRESULTs
convention, 21 functions, 127
example apartment, 323-31 example
free,
333-38
free-threading marshaling optimization,
338-40 type,
297-98
converting, to
and from GUIDs, 127-28
IDL, 255 strong references, 353
374
258
apartment threads, 317
strings
BSTR
allocator, 127,
apartments, 313-17
91
status codes, 106-7, 108. See also
.
memory
threading, 70, 311-41
standard interfaces, 359
.
351-52
templates, 226, 229
source interfaces, 356
SlringFrom.
files,
techniques demonstrated by, 350-51
software. See applications
stdcall
IDL
polygon component, 348
task
classes)
static linking,
interfaces in multiple
running, 345
reference counting, 67, 218
wrapper
349-50
free threads, 317-18
implementing apartment, 320-31 implementing
free,
331-40
marshaling and synchronization, 318-19
Index
unknown
threading, continued
delegating and nondelegating, 173-74
threads, 312-13
Win32
type libraries, 57, 295,
implementing delegating, 176-77
299-305
implementing nondelegating, 174-76
299-302
creating, with IDL,
outer, 173
distributing, 302
problem of incorrect, 171-73
example, 300-301 loading, registering,
MIDL
and
using,
command
UnRegServer
302-3
line parameter,
265
upgradability. See versioning
and, 260
ODL, MkTypLib,
interfaces, aggregation, 171-77. See also
lUnknown interface
Registry keys, 340
user-interface threads, 312. See also apartment
and, 300
threading
304-5
Registry information, 120,
UUIDGEN.EXE
TypeLib Registry key, 304
utility,
112
types
BSTR, 297-98
component
categories
as,
variables, reference
124
variable syntax, 287
converting, 296-97
GUID
{see
GUIDs
counting and, 80, 82
(Globally
VARIANTARG structure, 289-91
Unique
VariantChangeType funcdon, 296
Identifiers))
VARIANT structure,
libraries (see type libraries)
290-91, 294-97
Query Interface and, 212
converting types, 296-97
run-time checking, 295
optional arguments, 297
SAFEARRAY, 298-99
run-time binding, 295 versioning, 57-60
string (see strings)
structures in IDL,
258-59
component
VARIANT (see VARIANT structure) VARIANTARG, 289-91 wchar_t, 255,
architecture and,
7, 8,
10
implicit contracts, 60
interfaces and, 27
298
naming
type safety, 212, 295
interface versions, 59
ProglDsand, 121
when
u
to
make new
versions, 59
virtual function tables (vtbls), 28-34. See also
pure
Unicode characters, 255, 298
unknown
base
class,
virtual functions
dual interfaces, 283-85
227-31
{see also
IDispatch
interface)
constructor, 231
inheritance and, 32-34 creating and releasing components, 231 interfaces,
example, 238-43
macro
to
delegate
unknown
309
{see also
interfaces)
multiple instances and, 31-32 interface,
229-30 pointers and instance data, 30-31
nondelegating unknown, 230 returning pointer to outer unknown, 231 step-by-step procedure,
243-44
pure abstract base classes and, 28-30 pure abstract base classes)
(see also
Visual Basic. See Microsoft Visual Basic Visual C++. See Microsoft Visual
C++
375
INSIDE COM
w
Windows
Registry, continued
DLL server self-registration, 122-23 EXE server registration, 265, 267-68
wchar_t type, 255, 298
weak references, 353
OleView
Win32
utility,
125
application threads, 312-13
organization
COM vs.,
ProglDs, 120-22
10
125,303
threading functions, 331
Windows
See Microsoft
Registry,
component
Windows
116-25
categories,
Registry Editor, 118
Tangram example,
Registry functions, 123
Windows.
123-25
type library information, 120,
WINERROR.H
file,
304-5
104, 106-7
WinMain function, 271
component CLSIDs, 118-19
COMsubkeys, 119-20
wrapper
converting string CLSIDs to and from GUIDs,
345, 346
threading information, 340
worker threads, 312. classes,
See also free
threading
224-26
containment, smart pointers, and, 225 smart interface pointers)
127-28
DCOM information,
153-54
registering type libraries, 302-3
functions and LPC, 250 utility,
117
registering components,
error codes, 107
OleView
of,
273-77
dispinterface, 294
multiple interfaces and, 225-26
376
(see also
Dale Rogerson
Many readers will know Dale Rogerson
as the
man
in
the motorcycle helmet from the Microsoft Developer Network News. As a writer for MSDN, Dale wrote articles and sample applications about topics concerning C, C++, MFC, COM, OLE, and OpenGL. Currently Dale is
a developer for the Microsoft Developer Studio, the
integrated development environment for Microsoft
developer
tools,
such as Microsoft Visual C++ and
Visual J++.
Before seeing the light and joining Microsoft, Dale did
penance
as a quality
UNIX computers. fully
beaten the
vi
It
assurance hardware engineer on
has taken years, but he has success-
and Emacs
habits, vi
and other use-
were learned in Hot 'Lanta at the Georgia of Technology, where Dale earned a bachelor's
ful job skills
Institute
degree in electrical engineering.
When not working at Microsoft or writing books, Dale and his kayak can be found getting trashed in the rivers and creeks of the local Cascade Mountains. If the weather takes a turn for the worse and the sun comes out, Dale is forced to go biking, hiking, or even motorcycling. In the winter, when his hands get frostbitten, it's time to put up the paddle and pull down the skis for
some telemarking. Contrary to rumor. Dale does not commute to work in a Cobra helicopter gunship; instead,
he and
his friends share a Sikorsky S-76C+ so that
they can take advantage of the
HOV lanes.
Theand
manuscript for
submitted to Microsoft Press in electronic
form. Text 6.0 for
files
text in
were prepared using Microsoft
Word
Windows. Pages were composed by Microsoft
Press using
bold.
book was prepared
this
New
Adobe PageMaker Baskerville
and
Composed pages were
6.0 for
Windows, with
display type in Helvetica
delivered to the printer
as electronic prepress files.
Cover Art Direction
Gregory Erickson Cover Graphic Designer
Robin Hjellen Cover Illustrator
John Bleck Interior Graphic Designers
Pamela Hidaka and Kim Eggleston Interior Graphic Artists
Michael Victor and Joel Panchot Photographer
Kathleen Atkins Principal Compositor
Frog Mountain Productions Principal Proofreader/ Copy Editor
Shawn Peck Indexer
Shane-Armstrong Information Services
Quick.
COM,
Explain
OLE,and
^ ^H
ActiveX.
UNDERSTANDING
Mrhen
iiiActiv^X
-•^
1
I
tion—one
to strategic technologies like these,
Here
A GUIDE FOR
them a
that gives
ing of the parts
exactly
a good explana-
quick, clear understand-
and the greater whole. And
that's
what Understanding ActiveX and OLE does.
you'll learn
the strategic significance of the
managers
for Microsoft's object technology. You'll
the evolution of OLE.
DAVID
first is
Component Object Model (COM) as the foundation
developers & LJ>,
comes
what decision makers need
V^H f
it
You'll
ActiveX technology for the Internet.
C
understand
discover the powerful
And
in all
these
subjects and more, you'll gain a firm conceptual Kticmsalt Press
grounding without extraneous details or implementing specifics.
Understanding ActiveX™ and OLE U.S.A. $22.95 ($30.95 Canada) U.K. £20.99 1-57231-216-5 ISBN
Understanding ActiveX and OLE
to browse, with colorful illustrations
margin notes. Get
it
quick.
and
And get up
to
is
also easy
"fast track"
speed on a
fundamental business technology.
The Strategic Technology Series is for executives, business pianners, software designers, and teciinicai managers wlio need a quicit, compreliensive introduction to important tectinoiogies and ttieir impli-
Microsoft Press* products are available worldwide wherever quality
computer books are sold. For more Information, contact your book computer reseller, or local Microsoft Sales Office.
retailer,
cations for business.
To locate your nearest source for Microsoft Press products, reach us at
www.microsoft.com/mspress/ or call 1-800-MSPRESS (in Canada: 1-800-667-1115 or 416-293-8464). .
To order Microsoft Press products, call (in
in
1-800-MSPRESS
the U.S.
in
the U.S.
Canada: 1-800-667-1115 or 416-293-8464).
Prices
and
availability
dates are subject to change.
Microsoft Press
i
Blueprint for
excellence. This classic from Steve McConnell
is
a practical guide to the art
and science of constructing software. Examples are provided Pascal, Basic, Fortran, Sofluiiis
and Ada, but the focus
is
C,
in
on successful
programming techniques. Code Complete provides a
larger per-
DiEtnicliiiii
spective on the role of construction
process that projects
same
will
the software development
in
inform and stimulate your thinking about your
—enabling you
own
to take strategic action rather than fight the
and again.
battles again
Get
all
of the Best Practices books.
WinnerSTEVE M.
"Very few books
given
me
as
I
have encountered
much
—Ray Duncan
§1 "The
definitive
book on software construction. This
is
a bool< that belongs on every
software developer's bookshelf."
Vhmnt SolM Cod* Keuffel,
$24.95 ($32,95 Canada: £21.95 ISBN 1-55615-551-4
U.S>.
"Every working
cannot adequately express how good
this
book
really
is... a
Jeff
programmer should own
—IEEE Spectrum
work of bnlliance."
—
the last few years have
pleasure to read as this one."
Steve Maguire
—Warren
Software Development
"I
in
Duntemann.
this tK)Ok."
Steve Maguire
$24.95 ($32.95 Canada: £21.95 ISBN 1-55615-650-2
U.S>.
you are or aspire to be a professional programmer, investment you'll ever make."
this
may be
the wisest
U.K.)
Otbugglnt the Devatopmwit Process
PC Technh^ues
"If
U.K.)
$35
"A
milestone
in
the
game
U.K.)
of hitting milestones."
—ACM Computing Reviews
—
IEEE Micro
Dynamics of Software Development Jim McCarthy
$24.95 ($33.95 Canada; £22.99 U.K) ISBN 1-55615-823-8 U.SJV.
"I recommend —Jesse Berst.
Microsoft Press* products are available worldwide wherever quality computer books are sold. For
more
To locate your nearest source or call
1-800-MSPRESS
in
without reservation to every developer."
editonal director.
Windows Watcher Newsletter
1-800-667-1115 and
or
Press products, reach us at wvvw.microsoft.com/msDress/ Canada: 1-800-667-1115 or 416-293-8464).
for Microsoft
the U.S.
To order Microsoft Press products,
Prices
it
information, contact your book retailer, computer reseller, or local Microsoft Sales Office.
(in
call
1-800-MSPRESS
.
in
the U.S.
(in
Canada:
416-293-8464).
availability
dates are subject to change.
Microsoft Press
"David Kruglinski does
an excellent job
of
explaining the capabilities
and uses
of the various tools
that are part of
VC++."
—PC Techniques ISBN 1-55615-891-2 with one CD-ROM $45.00 ($59.95 Canada)
860 pages
he Microsofr Visual C++' development I
system combines the power of objectoriented programming with the efficiency of the ^^^ in
C language. And the application framework approach Visual
C++— centering on
the Microsoft Foundation Class
Library— enables programmers to simplify and streamline the process of creating robust, professional applications for Windows*. Inside Visual
C++ takes you one step
at a time through the process of creating
Windows— the
C++ way. Using ample source code examples, this book explores MFC, App Studio, and the product's nifty "wizards"— AppWizard and ClassWizard— in action. The book also provides a good explanation of application framework theory, along with tips for exploiting real-world applications for
hidden features of the
MFC
Visual
library.
Microsoft Press® products are available worldwide wherever quality computer books are sold. For
more information, contact your book
retailer,
computer
reseller, or local Microsoft
Sales Office.
To locate your nearest source for Microsoft Press products, reach us at www.mlcrosoft.com/msDress/ or .
call
1-800-MSPRESS
in
the U.S.
To order Microsoft Press products,
call
or 416-293-8464). Prices
(in
Canada: 1-800-667-1115 or 416-293-8464). in the U.S. (in Canada: 1-800-667-1115 dates are subject to change.
1-800-MSPRESS and
availability
II
IMPORTANT— READ CAREFULLY BEFORE OPENING SOFTWARE PACKET(S).
By opening
the sealed packet(s) containing
you indicate your acceptance of the following Microsoft License Agreement.
the software,
MICROSOFT LICENSE AGREEMENT (Book Companion CD) This
is
a legal
agreement between you (either an individual or an entity ) and Microsoft Corporation. By opening the sealed software packet(s)
you are agreeing
to be
bound by
the terms of this agreement. If you
do not agree
to the terms of this agreement,
software packet(s) and any accompanying written materials to the place you obtained them for a
full
promptly return the unopened
refund.
MICROSOFT SOFTWARE LICENSE 1
GRANT OF LICENSE.
Microsoft grants to you the right to use one copy of the Microsoft software program included with
this
book
(the
"SOFTWARE") on a single terminal connected to a single computer. The SOFTWARE is in "use" on a computer when is loaded into the temporary memory (i.e.. RAM) or installed into the permanent memory (e.g., hard disk, CD-ROM, or other storage device) of that computer. it
SOFTWARE or otherwise use on more than one computer or computer terminal at the same time. SOFTWARE is owned by Microsoft or its suppliers and is protected by United States copyright laws and international treaty provisions. Therefore, you must treat the SOFTWARE like any other copyrighted material (e.g., a book or musical recording) except that you may either (a) make one copy of the SOFTWARE solely for backup or archival purposes, or (b) transfer the SOFTWARE to a single You may
2.
not network the
COPYRIGHT.
it
The
hard disk provided you keep the original solely for backup or archival purposes.
You may
not
copy
the written materials
accompanying the
SOFTWARE. 3. OTHER RESTRICTIONS. You may not rent or lease the SOFTWARE, but you may transfer the SOFTWARE and accompanying written materials on a pennanent basis provided you retain no copies and the recipient agrees to the terms of this Agreement.
SOFTWARE.
engineer, decompile, or disassemble the
most recent update and 4.
all
You may
not reverse
SOFTWARE is an update or has been updated, any transfer must include the
prior versions.
DUAL MEDIA SOFTWARE.
may
If the
If the
SOFTWARE package contains
use only the disks appropriate for your single-user computer.
more than one kind of disk
You may
(3.5", 5.25",
and
CD-ROM),
then you
not use the other disks on another computer or loan, rent, lease,
all SOFTWARE and written materials. SOFTWARE includes Sample Code, then Microsoft grants you a royalty-free right to reproduce and distribute the sample code of the SOFTWARE provided that you: (a) distribute the sample code only in conjunction with and as a part of your software
or transfer them to another user except as part of the permanent transfer (as provided above) of 5.
SAMPLE CODE.
If the
product; (b) do not use Microsoft' s or that appears
on the
SOFTWARE
its
authors' names, logos, or trademarks to market your software product; (c) include the copyright notice
on your product
label
indemnify, hold harmless, and defend Microsoft and
its
and as a part of the sign-on message for your software product; and
(d) agree to
authors from and against any claims or lawsuits, including attorneys" fees, that arise
or result from the use or distribution of your software product.
DISCLAIMER OF WARRANTY is provided "AS IS" WITHOUT WARRANTY OF ANY KIND. MICROSOFT FURTHER DISCLAIMS ALL IMPLIED WARRANTIES INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR OF FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK ARISING OUT OF THE USE OR PERFORMANCE OF THE SOFTWARE AND DOCUMENTATION REMAINS WITH YOU.
The SOFTWARE (including instructions for its
use)
NO EVENT SHALL MICROSOFT, ITS AUTHORS, OR ANYONE ELSE INVOLVED IN THE CREATION, PRODUCTION, OR DELIVERY OF THE SOFTWARE BE LIABLE FOR ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR INABILITY TO USE THE SOFTWARE OR DOCUMENTATION, EVEN IF MICROSOFT HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. BECAUSE SOME STATES/COUNTRIES DO NOT ALLOW THE EXCLUSION OR LIMITATION OF LIABILITY FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES, THE ABOVE LIMITATION MAY NOT APPLY TO YOU. U.S. GOVERNMENT RESTRICTED RIGHTS IN
The
SOFTWARE
and documentation are provided with
RESTRICTED RIGHTS.
subject to restrictions as set forth in subparagraph (c)(l)(ii) of
252.227-701 3 or subparagraphs
Manufacturer If
you acquired
(c)(
1 )
this
One Microsoft Way, Redmond,
product in the United States, this Agreement
questions concerning this Agreement, or
Way, Redmond.
WA 98052-6399.
in
if
you desire
to contact
is
Use, duplication, or disclosure by the Government
Technical Data and Computer Software clause
and (2) of the Commercial Computer Software
Microsoft Corporation,
is
The Rights
— Restricted Rights 48 CFR 52.227-
1
at
is
DEARS
9, as applicable.
WA 98052-6399.
governed by the laws of the State of Washington. Should you have any
Microsoft Press for any reason, please write: Microsoft Press,
One Microsoft
i
Register Today f Return Inside
this
COM
registration card for
a Microsoft Press catalog
U
U.S. and Canada addresses only.
1-5723 1-349-8 A
INSTITUTION OR
Fill in
information below and mail postage-free. Please mail only the bottom half of this
INSIDE COM
p
Owner Registratio
COMPANY NAME
ADDRESS
STATE
ZIP
Microsoft Press Quality
Computer Books
For a free catalog of Microsoft Press* products,
call
1-800-MSPRESS
-I-
NO POSTAGE NECESSARY IF
MAILED IN
THE
UNITED STATES
BUSINESS REPLY MAIL FIRST-CLASS MAIL
PERMIT NO. 53
BOTHELL,
WA
POSTAGE WILL BE PAID BY ADDRESSEE
MICROSOFT PRESS REGISTRATION INSIDE
COM
PO BOX
3019
BOTHELL
WA
98041-9946
ll.l..l..l.ll....l,.l...lll.l,.l.l...l..l.ll..ll...l
Insid
Microsoft's
COM
The companion CD-ROM includes all source code from the book, a full sample application that runs under Microsoft Windows 95 and
without the complexity.
Microsoft's It's
Component Object Model
Component Object Model (COM) has emerged as a
the basis of Microsoft's approach to distributed computing.
method
for
customizing applications, present and future. And
foundation of OLE and ActiveX™
development. And
A
clear
this
and simple,
is
In short,
COM
the book that unlocks
COM.
In
practical guide to building elegant
insightful, progressive
Plenty of examples
in
view of
COM
a powerful
it's
the
Microsoft
COM
documentation, development and more.
tools,
COM
it,
you'll discover:
COM components can be— especially
design
the form of code samples
Dale Rogerson writer
Inside
com
is
advanced C++ programmers; COM,
for intermediate to
and OLE programmers; academics
with an interest
and programmers who want
COM when
to
and other environments. To put fast—and
if
use
it
simply,
Windows NT, the
Specification, online
helps unlock the future of
An eye-opening presentation of how accessible for those who have already mastered C++
An
vital tool.
It's
it's
COM-based
you work with any of them, then Inside
in
ActiveX,
component design;
ported to UNIX, MVS,
contributor to the Microsoft
Developer Network on the subject of COM.
codes is
a veteran
and a founding
interfaces are spreading
COM
is
written for you.
internal
He currently
COM
interfaces
as part of the Microsoft Visual
U.S.A. U.K.
Canada
$34.99 £22.99 $46.99
C++ development team.
[V.A.T. included]
[Recommended]
ISBN 1-57231-349
90000
7
'"90145"13498"'
i
9 '781572"313491
Windows Development/ C/C-H- Programming
Microsott Press