Inside COM (Microsoft Programming Series) 1572313498, 9781572313491


205 44 33MB

English Pages [424]

Report DMCA / Copyright

DOWNLOAD PDF FILE

Recommend Papers

Inside COM (Microsoft Programming Series)
 1572313498, 9781572313491

  • 0 0 0
  • Like this paper and download? You can publish your own PDF file online for free in a few minutes! Sign Up
File loading please wait...
Citation preview

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