John E. Swanke - COM Programming by Example - Using MFC, ActiveX, ATL, ADO, and COM+ (With CD-ROM-CMP (2000)
John E. Swanke - COM Programming by Example - Using MFC, ActiveX, ATL, ADO, and COM+ (With CD-ROM-CMP (2000)
by Example
Using MFC, ActiveX, ATL, ADO, and
COM+
John E. Swanke
R&D Books
Lawrence, Kansas 66046
R&D Books
CMP Media, Inc.
1601 W. 23rd Street, Suite 200
Lawrence, KS 66046
USA
Designations used by companies to distinguish their products are often claimed as trade-
marks. In all instances where R&D is aware of a trademark claim, the product name
appears in initial capital letters, in all capital letters, or in accordance with the vendor’s
capitalization preference. Readers should contact the appropriate companies for more
complete information on trademarks and trademark registrations. All trademarks and
registered trademarks in this book are the property of their respective holders.
Copyright © 2000 by John Swanke, except where noted otherwise. Published by R&D
Books, CMP Media, Inc. All rights reserved. Printed in the United States of America. No
part of this publication may be reproduced or distributed in any form or by any means, or
stored in a database or retrieval system, without the prior written permission of the pub-
lisher; with the exception that the program listings may be entered, stored, and executed in
a computer system, but they may not be reproduced for publication.
The programs in this book are presented for instructional value. The programs have been
carefully tested, but are not guaranteed for any particular purpose. The publisher does
not offer any warranties and does not guarantee the accuracy, adequacy, or completeness
of any information herein and is not responsible for any errors or omissions. The pub-
lisher assumes no liability for damages resulting from the use of the information in this
book or for any infringement of the intellectual property rights of third parties that
would result from the use of this information.
v
This Page Intentionally Left Blank
Table of Contents
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .xix
What’s In Store . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xix
Section I: COM Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xix
Section II: COM Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .xx
About the CD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .xx
About the SampleWizard. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .xx
vii
viii Table of Contents
Chapter 4 COM+ . . . . . . . . . . . . . . . . . . . . . . . . . . 75
The Evolution of Client/Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .76
The Evolution of COM. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .78
DLL Surrogates. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .80
Writing Your Own DLL Surrogate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .82
Microsoft Transaction Server (MTS) . . . . . . . . . . . . . . . . . . . . . . . . . . . .82
Stateless Programming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .83
IObjectContext . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .84
Thread Safety . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .85
Writing Your MTS Compliant COM Server . . . . . . . . . . . . . . . . . . .85
Registering Your MTS Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .86
What is COM+? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .86
Just-in-Time Activation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .88
Object Pooling. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .89
Load Balancing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .90
A New Threading Model. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .91
Event Service . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .93
Queued Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .94
Security at the Method Level . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .97
Other Minor Changes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .98
x Table of Contents
Attribute Programming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
The Component Catalog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
COM+ vs. EJB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
Summary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
Steps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
Notes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
CD Notes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
Listings — DLL Definition File . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
Listings — Interface Defintion Language File. . . . . . . . . . . . . . . . . 146
Listings — Make File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
Example 10 Writing a COM DLL Server with MFC . . . . . . . . . . . . . 147
Objective . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
Strategy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
Steps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
Notes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
CD Notes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
Listings — COM Class .H File . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
Listings — COM Class .CPP File . . . . . . . . . . . . . . . . . . . . . . . . . . 153
Example 11 Writing a COM EXE Server with MFC . . . . . . . . . . . . . 156
Objective . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
Strategy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
Steps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
Notes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
CD Notes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
Listings — COM Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
Example 12 Writing a COM Server that Supports Late Binding
with MFC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
Objective . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
Strategy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
Steps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
Notes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
CD Notes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
Example 13 Writing a COM Server with a Connection Point
with MFC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
Objective . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
Strategy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
Steps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
Notes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
CD Notes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
Example 14 Writing a COM Client with a Sink Using MFC . . . . . . . 163
Objective . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
Strategy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
Steps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
Table of Contents xiii
Notes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .166
CD Notes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .166
Example 15 Writing a COM Singleton Server with MFC. . . . . . . . . . .166
Objective . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .166
Strategy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .167
Steps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .167
Notes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .169
CD Notes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .169
Listings — COM Class .H File. . . . . . . . . . . . . . . . . . . . . . . . . . . . .169
Example 16 Aggregating a COM Object with MFC. . . . . . . . . . . . . . .173
Objective . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .173
Strategy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .173
Steps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .174
Notes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .177
CD Notes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .177
Listings — Aggregating COM Class .H File . . . . . . . . . . . . . . . . . . .178
Listings — Aggregating COM Class .CPP File . . . . . . . . . . . . . . . . .180
Example 17 Writing an ActiveX Control Using MFC . . . . . . . . . . . . .184
Objective . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .184
Strategy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .184
Steps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .185
Notes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .190
CD Notes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .190
CD Notes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .230
Listings — Tooltip Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .230
Steps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .287
Notes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .292
Example 39 Using the COM+ Event Server . . . . . . . . . . . . . . . . . . . . .294
Objective . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .294
Strategy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .294
Steps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .294
Notes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .296
Example 40 Writing and Using a COM+ Queued COM Server . . . . . .296
Objective . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .296
Strategy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .296
Steps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .298
Notes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .300
Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353
What’s In Store
The examples in this book are grouped into chapters that cover several dif-
ferent aspects of using COM, from in-process DLL’s to remotely accessed
applications, and from using the COM API directly (macho COM) to let-
ting the Active Template Libraries (ATL) classes do most of the work. Simi-
lar chapters have been organized into one of the following sections.
xix
xx Introduction
You can certainly skip this section if you want, but I will be referring back
to it later.
About the CD
Included with this book is a CD containing a working Visual C++, Visual
Basic, or Visual J++ v6.0 project for every example in this book. If you want
to find the project for an example, just locate its example number among
the subdirectories on the CD.
COM Objects
In Chapter 1, we take a look at how to use the COM API to create a COM
object. We also look at just what’s involved in preparing your DLL or EXE
so that the COM API can access the class objects inside. We do this using
macho COM (using the API directly), the COM classes of MFC, and ATL’s
classes, both of which wrap the COM API. We also look at the benefits of
creating all your COM objects as DLL’s and letting MTS/COM+ manage
them for you.
COM Communication
Having created a COM object, we now exchange data with it in Chapter 2.
We will find that data exchange can be no more complicated then passing
1
2 Section I: COM Basics
COM+
In this final basic chapter (4), we review the newest enhancements to COM.
Contrary to its name, COM+ is not the next version of COM, but the next
version of a certain portion of COM called the Microsoft Transaction
Server (MTS). Together they represent the cutting edge application of COM
to allow you to write applications that can be massively scaled, potentially
allowing you to replace a mainframe with a room full of PC’s — or at least
that’s the hope. What did they say about a room full of monkeys with type-
writers?
1
Chapter 1
COM Objects
In this chapter, we will review the nature of a COM object — what it is,
how to create one and access its functionality, and how to put your own
functionality into one. We will examine the API that allows you to work
with COM objects as well as the many ways you can add COM to your
application.
What is COM?
Simply put, COM is a system API that allows your application to access the
functions and data in another application (EXE) or a dynamically linked
library (DLL). But wait a minute, you might say. You could already do that
using windows messaging, pipes, sockets, dynamic data exchange (DDE),
remote procedure calling (RPC), and a few other system API calls.
And you’re right. COM doesn’t offer your application anything new
except for one thing: a client/server standard. Because unlike any protocols
and functionality you could come up with on your own, when you put your
application into a COM object, anyone else following the standard can
access it. (Not to mention your own client/server is probably filled with bugs
anyway.)
3
4 Chapter 1: COM Objects
the same reasons that C++ is a faster, more versatile language than the others, it
also requires that you have a more intimate knowledge of what COM is doing
— although it has gotten a lot better in recent years. So if you don’t want any-
thing to do with C++ and aren’t curious about how COM dynamically instanti-
ated classes, please feel free to skip down to those sections in this chapter.
A lot of COM books today give short shrift to C++. Just the other day
I heard it referred to as a lower level language. VB and VJ++ are much
easier to program in, creating more robust applications faster. But, as
mentioned, they’re also twice as slow and as one of my managers put it
“like programming with a crayon”. But aren’t dumbed down lan-
guages in the future of most programmers? If you’re operating a space
ship in 2112 with gillions of lines of code can you afford to look for an
exception error in a module way at the bottom? In fact, managers seem
to be trading performance and flexibility for robustness and ease of
use. As Stalin put it, “Quantity is a quality all its own.” Look for appli-
cations of the future to have lots more functionality but with lots less
features — if you know what I mean. (Windows Explorer can’t even
show you two subdirectories at the same time but will show that one
directory on any device and without crashing.)
What follows then is a quick overview of what goes on when you create
a COM object using C++ (and internally with other languages):
1. First, you call the ::CoCreateInstance() COM API call, either directly
or through a C++ class that wraps it. You specify two ID’s, one for the
class you want to instantiate and one for the DLL or EXE file it lives in.
Very unique ID’s are used instead of a class or filename to avoid the
problem of having two COM objects with the same names on the same
large system.
2. Since the DLL or EXE filename is specified with an ID, COM has to first
convert it to an actual DLL or EXE filename by looking in the system
registry where it was stored when the DLL/EXE was installed.
3. Now supplied with the actual filename, COM loads up the requested
DLL using the LoadLibrary() API call or starts the desired EXE using
the RPC (Remote Procedure Call) API.
How Does COM Work? 7
4. Then COM tells the DLL or EXE which class to instantiate by using the
other ID you specified to ::CoCreateInstance(). Once the DLL or EXE I 1
has created an object, ::CoCreateInstance() returns its pointer.
5. If the class lives in a DLL on your own system, calling its methods and
passing arguments to it is not unlike any DLL method call using a class
pointer, i.e., p->Func(a,b);.
6. If however you’re calling a method on an object that was created in an
EXE or on another machine, the arguments you pass to it go through the
same serialization process that you would expect between any client and
its server over a network — except COM does this for you automatically
and almost transparently.
7. Destroying a COM object is a matter of telling the COM API you no
longer need it by decrementing a reference count in the object. Once it
reaches zero, COM destroys the object.
8. If all of the objects a DLL has supplied to your application are destroyed,
COM unloads the DLL. If all of the objects an EXE has supplied any
application on your system or the network are destroyed, COM stops the
EXE.
Please see Figure 1.1 for this oversimplification of COM.
Rather then specify an actual class name or DLL/EXE filename, you use
very unique id’s called GUID’s (Globally Universal ID), so unique that theo-
retically there’s no two alike in the entire universe (please see the sidebar on
GUIDs). Using a very unique id, instead of actual class and filenames, is to
prevent two objects with the same name winding up on the same system and
conflicting with each other.
Guids
where
rrrrrrrr is a 32 bit random number
tttt-tttt is the time stamp with the least significant 16 bit
word appearing first
oooo is related to the number of times the machine has
been rebooted.
aa-aa-aa-aa-aa-aa is a string of six bytes which are usually the
address on the network card.
You’ll find that guids come in three different and exciting formats.
For use in the system registry,
{4323CD20-2559-11d2-9BD8-00AA003D8695}
You’ll notice that the degree to which the numbers of a guid change
go from left to right with the numbers on the left changing the most to
the numbers on the right not changing at all when generated on your
machine. In other words, if you’re looking to compare two guid num-
bers visually, look to the left.
10 Chapter 1: COM Objects
If your application will require several guids, you may want to con-
sider generating a range of them so that they appear consecutively in
the system registry. You can do this automatically with:
guidgen /n50 /s >guids.txt
which would generate fifty (50) sequential guids, or you can do it man-
ually just by incrementing the random number:
{4323CD20-...the same...
{4323CD21-...the same...
{4323CD22-...the same...
Unfortunately, the wizards get into the act of generating guids for
you, so you may not have this option.
There are two id’s used to create a COM object. The Class ID (CLSID)
represents the DLL or EXE filename, and the Interface ID (IID) refers to the
class within the DLL/EXE you want to create. Since these IDs are 128 bits
long, you’re actually passing arrays that you include as globals in your code.
CoCreateInstance converts the CLSID into an actual filename by looking
in the system registry where these id’s were associated with their filenames
when the DLL or EXE was first installed. Most COM DLL’s and EXE’s have
the smarts to do this themselves. Please see the sidebar on the other things
you’ll find in the system registry.
System Registry
Program ID’s
The COM Program ID’s such as Access.Application or
Access.Application.8 associate a long Class ID with this much shorter
name for easier use in your application. In the case of Access.Applica-
tion.8, Access represents the DLL or EXE file the class lives in, Appli-
cation represents the class, and the “.8” specifies that this is the eighth
How Do You Create a COM Object Using C++? 11
using this pointer by first type casting it with a class definition of that class as
seen here:
IMyComClass *p=(IMyComClass)::CoCreateInstance(…);
p->Method1(…);
We’ll see how this class type is created below, but suffice it to say, you
can now call any of the methods of this DLL as if it was a class that existed
in your own application.
If the class lives in an EXE, CoCreateInstance() executes the applica-
tion, then asks it to create the class specified by the Interface ID and returns
the pointer to you. And you again call its methods as if the class existed in
your own app.
For an example of using the COM API to create a COM object, please
see Example 1 (page 104).
Note: Actually, to step into the proxy, you need to do some fancy foot- I 1
work because the Visual Studio debugger is smart enough to skip over
this code and take you directly to the server method. To see the proxy
at work, you need to open up the disassembly view of your code and
step through the assembler until _IT_ takes you into the proxy at the
“call” opcode.
So where did these magical proxy and stub functions come from and
how did you wind up calling them instead of the server method? COM pro-
vides them automatically. When CoCreateInstance() created the object,
not only did it start up the EXE and create the object, but rather than return
its pointer back to you (which wouldn’t do you any good anyway), it
returned a pointer to the proxy function instead. So depending on what type
of COM object you created, you could be getting a pointer to the real object
or just a proxy function.
So where did this proxy function come from? From a proxy/stub DLL.
CoCreateInstance() finds this DLL by mapping the Interface ID to a DLL
filename under the Interface key in the System Registry. It then loads the
same DLL into both your application and also the COM EXE server.
To see how this works, please see Figure 1.2.
So who wrote the proxy/stub DLL? You did — or at least you can at the
same time you build your own COM object, or you can just use the stan-
dard proxy/stub that comes inside of OLE32.DLL — but not if your method
requires something fancy be passed to it. There’ll be much more on the
proxy/stub DLL later in this chapter as well as in the next.
Process Boundaries
The program address that you find in a data pointer (e.g., char *p) is
not a pointer to an actual physical address in memory. Instead, it’s a
logical address that the CPU assigned your application when it brought
the application into memory. If the CPU were to assign your applica-
tion an actual physical address, it would be constantly modifying your
application whenever it moved it in memory to make room for another
application. As a result, the logical address you’ll find in a pointer
would be a worthless token to another application. Thus the need to
bend over backwards to pass data between applications.
How Do You Destroy Your COM Object? 15
And when passing a pointer to another function, you increment the refer-
ence count by calling:
p->AddRef();
Note: COM will automatically clean up your COM objects too (aka,
“garbage collection”). In the case of a DLL, once your application termi-
nates any object it created, it will be destroyed as with non-COM. And a
COM EXE server continually checks to make sure that the client it cre-
16 Chapter 1: COM Objects
Note: I may have said C++ programmers used to have to use .c files in I 1
the past, but considering the rapid public development of COM and
the need to support legacy applications, all of the technologies men-
tioned in this book will probably be in use for years to come.
where the definition of XXX is added to your source code by the #import
directive.
To access the COM object’s methods using this class, you would use:
cls->Method();
You would then access the COM object’s methods by first de-referencing
the pointer with:
*(pCls)->Method();
18 Chapter 1: COM Objects
Now you can destroy a COM object as simply as you can a C++ object
— just by destroying the instance of the smart pointer:
delete pCls;
Note: Although the IDL file is a separate entity in V6.0 of VC++, V7.0 I 1
will allow you to define the additional IDL syntax right in a standard
.h file. To differentiate between C++ syntax and IDL, all IDL syntax is
bracketed with braces (i.e., []).
In C++ terms, what you are defining with your IDL file is a pure virtual
C++ class with all public member functions and no member variables. And,
in fact, when an IDL file is compiled, one of the files generated is a standard
C++ .h file that contains this pure virtual class. You can then use this .h file
directly to type cast the pointer returned by CoCreateInstance() as seen
earlier. On the server side, this pure virtual class forces you to implement
every method defined — otherwise the object wouldn’t compile.
By convention, names for these pure virtual classes are prepended with
the letter “I” and the classes themselves are called Interfaces instead of
classes. But conceptually, it’s probably easier to visualize them as just a pure
virtual class that keeps the client and server synchronized.
IDL files are compiled using the Microsoft Interface Definition Language
(MIDL) compiler, which you are no more likely to invoke directly than you
would the C++ compiler. And instead of generating a binary object file, the
compiler generates several binary and source files. An IDL file called
MyInterface would cause MIDL to generate the following files:
MyInterface.h
This is the actual C++ abstract class definition. You derive your server
object from this class and use it to typecast a COM object pointer in your
client.
MyInterface_i.c
This file contains the GUID’s that identify a DLL/EXE using this interface to
your client. Since the GUID is actually an array of 128 bits, it must be
included in your client as a global constant that’s passed to the API as a
pointer to this array — quite a mess to get such uniqueness and fortunately,
unnecessary when you use #import and smart pointers.
MyInterface_p.c,dlldata.c
These additional files can be used to create a proxy/stub DLL for your
COM object. This is the proxy/stub DLL mentioned earlier that helps your
COM object perform interprocess communication and will be discussed in
detail in the next chapter. Suffice it to say for now that you don’t even need
20 Chapter 1: COM Objects
to create it unless your object needs to pass an argument that’s not already
in OLE32.DLL’s repertoire.
MyInterface.tlb
A binary called a type library defines the information in MyInterface.h
(after all, an .h file is only good to a compiler). When OLE32.DLL needs the
definition of your COM class, it turns to this binary file.
There is no IDL syntax to add a member variable to your interface class.
Because accessing the member variables of a COM object from a client suf-
fer from the same process boundary problems that functions do, it was
decided that the member variables of a COM object would be accessed
using new set and get functions. That way only one interprocess communi-
cation infrastructure had to be developed. The member variables of a COM
object are also referred to as the properties of that COM object.
IUnknown
Another aspect to writing a COM class is that unlike a standard C++ class is
that all COM objects have the same base class for the usual C++ advantages
(every COM object will provide the same limited function set and all COM
objects can be used polymorphically). This base class is called IUnknown, the
reasons for which are unknown. A more accurate name might be along the
lines of MFC’s CObject — perhaps IComObject.
We’ve already covered two of the functions that IUnknown defines:
Release() and AddRef(). The third and last function is called QueryInter-
face() which allows you to ask an object if it also contains a COM class
you specify with an Interface ID — if it does, you get a pointer to it, else you
get an error back.
And notice I said that IUnknown defines these functions — it doesn’t actu-
ally implement them. That’s your job as the author of a COM object — but
don’t worry about it, you can get help doing it.
Binding
Another choice when writing a COM object is how your client will bind to
your object. In other words, will the client have access to information on
your object at compile time (in the form of IDL output files) or could the cli-
ent have been written months before and therefore not had access.
When a client does have access to the IDL information, specifically the
class type override, your compiler fills in the addresses of any methods you
How Do You Write a COM Object Using C++? 21
call in a technique called binding or early binding. But when this informa-
tion isn’t available, you can still build functionality into your COM object I 1
so that a suitably prepared client can actually query your object at runtime
to find out what methods it supports and what argument those methods
require in a technique called late binding.
To support late binding in your object, you have to implement yet
another interface called IDispatch — and yet again you have help. You are
also forced to use just the calling arguments that OLE32.DLL supports (you
can’t use your own proxy/stub). Most developers add support for both ways
to bind to their object.
The plus side to late binding is that you have total separation between
client and server — they don’t know about each other until show time. On
the other hand, calling a method is a lot slower because of this querying.
Late binding was originally called automation because it was used by one
application to automatically control another application. As an example, a
configuration application might tell a word processor to spell check a docu-
ment for it.
Please see Figure 1.5 for a view of early vs. late binding. For an example
of late binding, please see Examples 3 and 4 (beginning on page 116).
22 Chapter 1: COM Objects
Singletons
A COM class that returns a pointer to the exact same object no matter how
many time you create and destroy it is called a Singleton. Singletons aren’t
used very often, but can be used to maintain global data among the COM
objects of your COM server. COM has no built-in support for Singleton
How Do You Write a COM Object Using C++? 23
objects but you can find an example of one in Examples 15 (page 166) and
23 (page 213). I 1
Thread Safety
You can have COM make your object thread-safe rather than build that
support in yourself. Thread safety involves preventing two threads in your
multitasking application from writing to the same data area at the same
time. Before COM, you would have to make your application thread-safe
by putting all accesses to the same data area in the same thread or by using
mutexes that forced each thread to take a turn at the data. COM gives you
the additional option of letting it force all objects that access the same data
into the same thread — no matter what thread the object was created in.
You choose this protection at development time by picking a threading
model for your object that’s stored in the system registry under its CLSID.
There’s much more on COM thread safety in Chapter 3.
24 Chapter 1: COM Objects
DLL Host
And before we move on, don’t think that if your server will be on another
machine that you can’t use DLL’s for your COM objects. Yes, a DLL needs
an executable to run, and a client can’t directly load up a DLL that’s sitting
on another machine. But what if there was a friendly EXE sitting on that
other machine that was willing to load up that DLL for your client and pass
information between that object and your client. In fact COM provides
both an API to create your own friendly DLL Surrogate application and it
also provides one pre-written for you called DLLHOST.EXE.
This last technique of using a surrogate process to load DLL’s on a
remote machine may not look it, but it’s the foundation of the strategy of
using COM to revolutionize the way applications are written. Needless to
say, there’s lots more on this technique in Chapter 4.
For those type of applications, there’s the ATL — a class library that sim-
ply wraps the COM API without bringing along any extra baggage. You I 1
still need an ATL DLL, but this DLL’s been built for speed and size.
And so we begin.
All regsvr32 does actually is call a method that MFC added to your DLL
that registers the DLL.
26 Chapter 1: COM Objects
To register a COM EXE, you need to execute it. That’s right — MFC
stuck a COM registration routine into your application’s InitInstance().
And having registered your COM object, your client is ready to use it.
Custom Interface
But what if you want to use early binding instead? What’s the “real chore”
mentioned earlier? You start by having to create your own interface project
without the help of the AppWizard. (Actually, you can just use the project on
the CD for Example 9). This project contains two source files — the IDL file
you define your COM classes with and a make file that uses MIDL to com-
pile it — and the C compiler and linker to create a proxy/stub DLL.
Once this Interface project is created, creating your DLL or EXE project
is as easy as before. But this time when you use the ClassWizard to create
your COM class, instead of specifying “Automation” at the bottom, you
specify “Creatable by Type ID”.
As with the Interface project, adding methods and properties to your
COM class is also a manual process. You have to add several macros your-
self as well as implement IUnknown (i.e., AddRef(), Release(), and Query-
Interface()). But then again, hopefully Examples 10 and 11 (beginning on
page 147) make it clear what to add.
Early binding in MFC is also known as adding a “Custom Interface”.
Late binding is also known as adding an “Automation Interface” or
dispinterface. You can support both in the same COM class just by
cross-referencing the method calls.
Don’t worry — as we will see, adding early binding support with ATL is
a lot easier. So why haven’t they made it easier in MFC? Because early bind-
ing just doesn’t make much sense for an MFC application. The MFC DLL is
stuffed with user interface classes that are wasted space with the majority of
number crunching COM objects. Sure, you also need COM objects that
deal with the interface, but that’s what ActiveX COM objects are for, and
MFC does have an ActiveX Wizard that makes writing those COM classes
easier. Let’s just say that a lot of early COM programmers had to worry
about early binding until ATL came along.
Note: There is one area where MFC early binding is still important —
when it comes to COM EXE servers that you also want to support
MFC. ATL allows MFC support in the DLL projects it creates, but you
need to bend over backwards to get the same support in an EXE.
How Do You Write a COM Object Using ATL? 27
To write a VB COM DLL or EXE, you just open a new project and pick
ActiveX EXE, ActiveX DLL, or ActiveX Control and add your methods as
public subroutines:
Public Sub MyMethod()
: :
End Sub
Summary
In this chapter we explored the creation and destruction of a COM object.
We also looked at how hard it is to write one in C++ versus VB or VJ++;
however, we also saw why C++ creates a faster COM object. We looked at
the minutia involved with writing a C++ COM class — especially in the case
of a custom MFC interface — but also saw how ATL takes care of a lot of
the minutia for us.
One aspect to calling a COM method that I carefully avoided was how
exactly COM automatically pass arguments to and from the method —
especially when the COM object is on another machine, potentially in
another country. We will see how it’s done and what you need to make it
work in the next chapter.
30 Chapter 1: COM Objects
COM Communication
In Chapter 1 we briefly reviewed how method arguments are passed to and
from a COM object. We saw how COM automated interprocess communi-
cations using an IDL file we write to create a custom communications DLL.
In this chapter we’ll take a closer look at that IDL file with an emphasis on
using it to optimize our communications with a C++ COM object. We’ll
also take a look what data types C++ programmers should be aware of
when communicating with a Visual Basic or Visual J++ client. Since the IDL
file is taken care of by VB and VJ++, there’s no need to review communica-
tions between objects written in those languages — once again the solution
is simple yet unchangeable.
31
32 Chapter 2: COM Communication
custom communications DLL that was built from the files generated once
again by your IDL file.
In both cases when you make a call, your arguments are formatted into a
standard communications protocol called Network Data Representation
(NDR) transfer syntax. In fact if you look in the XXX_p.c file used to build
your custom proxy/stub DLL, you’ll find that 99% of it is your calling argu-
ments formatted into line after line of NDR macros.
From there, COM uses the RPC API (RPCNSS.EXE) to call the OLE32.DLL
or custom proxy/stub DLL on the server. The RPC API itself uses the net-
work transport layer to actually communicate with the other DLL. That
DLL unpacks your calling arguments and makes the actual call to the
method. Returned arguments are repacked and resent back to the client.
Please see Figure 2.1 for an overview of this process. Incidentally, sending
and receiving data from a COM object is called “marshalling” the data.
As you can see in both cases, the IDL file plays a key role in how your
calling arguments are passed to the object, but what exactly goes into an
IDL file?
Early-Binding Interface I
In its most primitive form, an early-binding IDL file is almost indistinguish-
able from a C++ header file with the following differences:
2
• “Interface” is used instead of class.
• All COM classes (unlike C++) share the same base class IUnknown.
• The class, its methods and even its arguments are further defined with
keywords that are specified within brackets (i.e., []).
These keywords or attributes are perhaps the most imposing aspect of an
IDL file as seen here:
[
object,
uuid(E1637ED6-1746-11D2-9BC7-00AA003D8695)
]
interface IWzd : IUnknown
{
HRESULT Initialize();
HRESULT method1([in] short nIn,
[out] char *pOut,
[in, out] char *pInOut );
HRESULT method2([in, out] char *pInOut);
};
But remove all of the stuff between the braces ([]) and replace the IDL
tokens with their C++ equivalents and you have this:
class CWzd : public CUnknown
{
long Initialize();
long method1(short nIn, char *pOut, char *pInOut );
long method2(char *pInOut);
};
Late-Binding Interface
When a COM server supports only a late-binding interface, it exposes only
one class, IDispatch. The reason for this is that all clients in the future and
in the past can always access a late-bound server because it will always have
this same class interface.
IDL files used for a late-binding interface still create an address table of
methods, but the client must use one of IDispatch’s four methods to query
and access those addresses by a simple ID you define in the IDL file.
There are three IDispatch methods used for querying an object for its
methods and arguments and one for calling a method.
• GetTypeInfo() and GetTypeInfoCount() can be used by an inquisitive
client to find out all of the methods and functions in a COM object that
supports late binding. These methods allow a programmatic way for a
client to look at an object’s type library and return not only the ID of a
method to call but also what should be in its argument list. But a COM
server isn’t required to return this information, which can be the case for
a particularly sensitive application.
• GetIDsOfNames() can be used to match up a list of method names with
their ID’s, for a client that already knows the method name and argu-
ment list of its COM server but needs to know exactly which ID to call.
• And the Invoke() method is used to actually call the method when sup-
plied with the method ID and the argument list in an array of DISPPARMS.
So unlike the IDL file in an early-binding interface, a late-binding inter-
face must have at least one method attribute defining its id. Here it’s shown
as “id(1)” and “id(2)”:
dispinterface CWzd
{
properties:
[id(1)] int property1;
methods:
[id(2)] HRESULT method1();
};
}
The “properties:” seen above define methods that store and retrieve
some “property” of a COM object. You can think of properties as the mem-
ber variables of a COM class — although you’re accessing them through a
method, they don’t necessarily represent a member variable. Which brings
Basic IDL File Formats 35
up the question: where’s the member variable declarations for a COM class?
There are none. Because a COM object doesn’t necessarily reside with the I
client — it could, in fact, be in Chicago — you must always access its func-
tionality through method calls, thus the invention of “Properties”. 2
Dual Interface
As you might expect, a dual interface is an interface that can allow a COM
server to support both early and late binding. The IDL file for a dual inter-
face might look something like this:
[
uuid(E1637ED6-1746-11D2-9BC7-00AA003D8695),
oleautomation,
dual
]
interface IHello : IDispatch
{
[id(1)] HRESULT Initialize(); HRESULT method1([in] short nIn,
[out] char *pOut, [in, out] char *pInOut );
[id(2)] HRESULT method2([in, out] char *pInOut);
};
The VTBL
As an application writer, you shouldn’t care, but the locations I referred
to above are in what’s called a virtual table (VTBL) which is generated by
the C++ compiler as an address table at the start of every class object and
the members of that table point to every method in that object. With a
COM server that supports both types of interfaces, the first three addresses
of the VTBL will contain IUnknown’s methods (remember every COM class
is ultimately derived from IUnknown), followed by IDispatch’s four meth-
ods, followed by all of your custom methods.
36 Chapter 2: COM Communication
[
uuid(DCBC68C9-4E2A-11D2-AB34-00C04FA3729B),
]
coclass WzdClass
{
[default] interface IWzd;
};
};
Note: Items marked as (1), etc., refer to notes at the end of the table.
VB VJ++
What to send IDL type C++ type
type type
Unsigned 8 bits char char Byte char
Unsigned 8 bits boolean bool (1) (1)
Unsigned 8 bits byte BYTE (1) (1)
Unsigned 16 bits unsigned short unsigned short (1) (1)
Signed 16 bits short short Integer short
Unsigned 32 bits unsigned long unsigned long (1) (1)
Signed 32 bits long long Long int
Unsigned 64 bits unsigned hyper unsigned hyper (1) (1)
Signed 64 bits hyper hyper (1) (1)
32-bit floating point float float Single float
64-bit floating point double double Double double
Enumerator Enum(2) Enum(2) Enum(2) Enum(2)
38 Chapter 2: COM Communication
VB VJ++
What to send IDL type C++ type
type type
Currency (8 bytes) CY CY CY CY
Date (double) DATE DATE DATE DATE
Variable Attributes
Besides an argument type, in an IDL file you must also declare an argu-
ment’s attribute(s). Fortunately there aren’t that many to remember:
• [in] tells COM to only transmit this argument from the client to the
server.
• [out] tells COM there’s nothing to transmit to the server, but the
server is going to have something to return back to the client. The cli-
ent therefore must fill this argument with a valid pointer or NULL
before it can call the method.
• [in,out] tells COM to not only send this argument to the server but to
also return the data from the server back to the client. Again, the client
must fill this argument with a valid pointer or NULL.
Arrays 39
• [out,retval] these attributes tell the client that this argument should be
returned as the result of the method call. In other words, it moves I
“value” out of the argument list to precede the method call as in this
example: 2
value=method();
As you might expect, you can only use retval on one of your arguments
and it must be the last one.
Wherever you use the “out” variable attribute, your argument type must
also be a pointer:
…{out] long *pValue,…
Other variable attributes help to define variable sized arrays as seen next.
Arrays
When I say arrays, I mean the simple array of the form:
values[x];
where x is the number of elements in the array. Both IDL and the COM
DLL support simple arrays. Therefore both C++ and VJ++ support sim-
ple arrays. But since the native type for an array in Visual Basic is a
SafeArray, it does not. We will be revisiting SafeArrays later in this
chapter.
You define an array as fixed or variable sized in IDL, and when vari-
able sized, another argument in the argument list is called on to specify
how big the missing size is. There’s no way for COM to send an array if it
doesn’t know all of its sizes. Unlike a friendly call between methods in
the same application, COM has to keep track of every byte to be sent.
Otherwise it wouldn’t know how many bytes to transmit.
Please refer to Table 2.3 for a list of variable attributes needed to declare
an array in the IDL file and C++. For consistency sake, the table only shows
arrays of longs being passed from a client to a server and where an array
size is fixed, twenty-five (25) elements is used.
40 Chapter 2: COM Communication
For fixed sized arrays, COM allocates exactly that much on the other
side and transmits that much. For variable sized arrays, COM gets the
number of bytes to allocate and transmit from another argument in the
argument list. Note that only one dimension of an array can be variable.
For variable sized arrays, you can also optimize performance by telling
COM to transmit only a certain number of elements, either by supplying the
start and end element number or by supplying a start element and number
of elements to follow. COM still allocates the full size of the array on the
other side — it just doesn’t transmit all of its elements.
For the syntax to use with VJ++, please refer to Example 35 (page 270).
long *pPointer;
float fElement;
I
} MYSTRUCT;
: : : 2
[in] MYSTRUCT myStruct
As for COM classes, because all COM classes are derived from the IUn-
known class, use that argument type or IDispatch for a COM class that sup-
ports late binding in the IDL file and type cast the pointer on the other side:
…,[in] IUnknown *iUnknown,…
: : :
IWzd *pWzd=(IWzd*)myClass;
Please see the next chapter for how to pass your COM pointer in a mul-
titasking environment.
Encapsulated Unions
An encapsulated union allows you to put several different data types in
the same union but pick at runtime what exact type represents so that COM
can determine how many bytes to transmit. Variants operate along similar
lines, however, an encapsulated union can be much more diverse. You can,
for instance, unionize a structure and an integer. Encapsulated unions don’t
work with late binding so you’re stuck with variants for VB and VJ++.
As with structures, you define an encapsulated union at the top of
your IDL file like so:
// define above any interface definitions:
typedef [switch_type(long)] union //"switch_type()" makes switch a "long"
{
[case(1)]
float fFloat[2];
[case(25)]
double dDouble;
42 Chapter 2: COM Communication
[case(27)]
MYSTRUCT myStruct;
[default]
long lLong;
} MYEUNION;
: : :
[in] long lType, [in,switch_is(lType)] MYEUNION myEUnion
pPtr->EUnions1(lType,myEUnion);
return S_OK;
}
Memory Pointers
When it comes to memory pointers, COM must not only transmit the
pointer but also any memory the pointer was pointing to. It determines
how many bytes to send based on how much memory is allocated to the
pointer. Theoretically, a memory pointer can be pointing to any kind of
memory. However, if there’s the potential that the server is going to deallo-
cate the memory that the client allocated, COM provides a set of memory
API’s called ::CoTaskMemAlloc() and ::CoTaskMemDealloc(). Not only do
these functions keep memory from getting lost between objects, but they
also create memory that’s thread safe (two threads working with the same
memory pointer can’t write to the memory pointer at the same time).
There are three variable attributes you can apply to a memory pointer
argument: ref, unique, and ptr:
Visual Basic Argument Types 43
• [ref] is the least overhead to COM. It just passes the memory pointer
and any data it points to and the server promises not to change or deallo- I
cate the memory.
• [unique] is the next least overhead and the default for COM. This 2
time the server can change the memory or even deallocate it, but the
client and server promise that this is the only pointer to this particular
chunk of memory. This way COM doesn’t have to worry about reconcil-
ing what multiple pointers do to that chunk of memory while in the
server before it sends it back to the client.
• [ptr] is the most overhead to COM, but the most transparent to you.
You don’t have to worry much about anything except making sure this
argument is NULL if you’re not using it.
BSTRs
If you wanted to pass a character string using C++, you could use an array
of characters. But to pass a character string to VB, you will need to use a
binary string. Binary strings are composed of Unicode characters and are
allocated with the Win32 API call ::SysAllocString(). In C++, you use a
BSTR argument type to refer to a binary string argument. Or you can use a
C++ helper class called _bstr_t.
44 Chapter 2: COM Communication
SafeArrays
To pass just a simple array of numbers between C++ objects or even to
VJ++, you would use the standard C++ array. But to pass an array to VB,
you would use a SafeArray. A SafeArray, as its name implies, is more
robust than a simple array, using a collection of Win32 API calls to allocate
itself and prevent writing to more memory than you have. In C++, you
would use a SAFEARRAY argument type to refer to a SafeArray.
Variants
Visual Basic is one of the few languages that still allows you to use a
variable that isn’t typed. To retrieve and pass such an argument to VB,
you would use a Variant argument type. A Variant is simply the world’s
biggest union of just about every argument type there is. At any given
moment, a Variant is just one of those types, such as an integer. But a
Win32 API call will easily change it to another type if how the variable
is used changes. As an example, if the variant that contained an integer
was used in a text message, it would be converted to a BSTR. In C++, you
would use a VARIANT argument type to refer to a Variant, or you can use a
C++ helper class called _variant_t.
For more details on how to create and destroy these argument types
using the API in C++ and classes in VJ++, please refer to Example 34
(page 265). Please refer to Table 2.3 for a list of how to specify these types
in the IDL file and C++ and VJ++.
Reverse Communication I
Rather than tie up a client waiting for an event to occur at the server, it’s
much more efficient for the server to inform the client when something hap-
pens. There are several ways to do this yourself. One would be for your cli- 2
ent to pass the server the address of a function to call when something
happens. Another would be for a server to send the client a window mes-
sage. But rather than implement either of these methods yourself, COM
provides two standard ways for a server to communicate with its client that
uses both of these solutions.
Early-Binding Interface
Since Visual Basic and Visual J++ only support a late-binding interface,
you can’t use early binding for its connection point and sink either. Only
MFC and ATL support early-binding connection points and sinks, and
surprisingly, it’s almost as hard to implement in ATL as it is in MFC.
To implement a sink in an MFC client, you need to start by creating a
COM interface project for that client with its own COM class and meth-
ods, IDL file and proxy/stub DLL. You then pick a client class that is
derived from CCmdTarget — such as the main window (CMainFrame or CDi-
alog) — and stick the same MFC macros in its .h file that you would use
in a regular MFC COM server to implement a COM class:
DECLARE_INTERFACE_MAP()
BEGIN_INTERFACE_PART(WzdSinkClass, IWzdSink)
STDMETHOD_(HRESULT,Callback)(long);
END_INTERFACE_PART(WzdSinkClass)
Connection Points and Sinks 47
In this example, IWzdSink is the name of your client’s COM class inter-
face, WzdSinkClass is the implementation of that COM class interface and I
Callback is the method of that COM class.
You then implement WzdSinkClass in your client class as usual — it’s just 2
that now one of its methods is the callback function. Two more steps and
we’re done: create an instance of this COM class and pass it to the COM
server supporting the connection point. Actually, you don’t need to create
an instance of your COM class because, as the main window of your appli-
cation, it will already exist, so all you need to do is use the QueryInter-
face() function you just wrote to get a pointer to itself:
hr = m_xWzdSinkClass.QueryInterface (IID_IWzdSink, (void**) &iWzdSink);
where CWzdSrv is the regular COM server class, IID_IWzdSink is the Inter-
face ID of the client’s sink mini-server, and CallBackCP is the name of a new
member variable of CWzdSrv declared with MFC’s ConnectionPoint class
and called m_xCallBackCP. This class not only keeps track of the address of
your client’s sink, but also any client that connects to this server. However,
48 Chapter 2: COM Communication
you have to implement the method that actually calls the client back. This
you do in a new member function of your COM class like so:
void CWzdSrv::CallBackClients(long data)
{
const CPtrArray *pConnections=m_xCallBackCP.GetConnections();
int nConnections=pConnections->GetSize();
for (int i=0;i<nConnections;i++)
{
IWzdSink *pWzdSink=(IWzdSink*)(pConnections->GetAt(i));
pWzdSink->Callback(data);
}
}
Notice how you use the new member variable m_xCallBackCP to get the
number of clients and the addresses of their sinks. For a real example of an
MFC connection point, please refer to Example 13 (page 160).
If you write your connection point in an ATL COM server, the ATL
Object Wizard will add the connection map for you, however you still need
to fill it in manually and add the routine that will be calling the clients back
manually as well. To ask the wizard to add the connection map, you select
“Supports Connection Points” when creating the object. This causes the
wizard to generate the map in your COM class’s .h file. You then need to fill
this map in yourself like so:
BEGIN_CONNECTION_POINT_MAP(CWzd)
CONNECTION_POINT_ENTRY(IID_IWzdSink)
END_CONNECTION_POINT_MAP()
T* pT = (T*)this;
pT->Lock();
I
HRESULT ret;
IUnknown** pp = m_vec.begin(); 2
while (pp < m_vec.end())
{
if (*pp != NULL)
{
IWzdSink *pWzdSinks = reinterpret_cast<IWzdSink*>(*pp);
ret = pWzdSinks->Callback( lArg );
}
pp++;
}
pT->Unlock();
return ret;
}
};
This class becomes one of the many root classes of your ATL COM class
so that you can again call it from anywhere in your COM server. For a real
example, please see Example 25 (page 220).
Late-Binding Interface
With early binding, we created a whole new interface project for our client
that included an IDL file and a proxy/stub DLL to define the mini-COM
server that will support the sink. Where then do we stick the IDL file for our
late-binding clients’ Visual Basic and Visual J++? COM’s solution was to
define it in the server with the connection point. And if you think about it,
that makes a lot of sense. It’s not like you can use a connection point with
just any sink anyway. They must each support the same unique methods
and argument lists. So why not join their definitions together in the same
IDL file?
To add a connection point to an ATL server, you start by specifying
“Supports Connection Points” in the ATL Object Wizard. Only this time,
rather than manually adding your own methods to the connection map, you
add the sink methods just like you would any ATL method through the
ClassView. The Studio not only automatically adds the connection point
50 Chapter 2: COM Communication
automatically to your connection map, but it also sticks a definition for the
client’s mini-server into the IDL file that’s stored in the type library:
library SERVERLib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");
[
uuid(37D9335E-A706-11D3-A398-00C04F570E2C),
helpstring("_IWzdEvents Interface")
]
dispinterface _IWzdEvents
{
properties:
methods:
};
[
uuid(37D9335D-A706-11D3-A398-00C04F570E2C),
helpstring("Wzd Class")
]
coclass Wzd
{
[default] interface IWzd;
[default, source] dispinterface _IWzdEvents;
};
}
For a Visual J++ client, the definition for the sink is pulled out of the type
library when you ask the VJ++ Studio to wrap a COM server with a Java I
class. You can then use “implements” to implement the sink in your VJ++
classes. Please see Example 30 (page 240). 2
ActiveX Events
The ActiveX standard takes an entirely different approach to server to
client communication. Instead of directly calling a client method,
ActiveX uses the approach of sending the client a window message. In an
MFC client, this message is processed in an event map you create with the
ClassWizard. In VB, you’re totally removed from the process and simply use
the VB Studio to add an ActiveX’s events to your forms.
Summary
Although COM does its best to allow any COM object to communicate
transparently with any other COM object, the native data types of each
computer language make this an impossible goal. You could always pro-
gram for the least common denominator (Visual Basic), but to write the
most optimized code, you need to carefully evaluate what COM object will
be talking with whom and why. And the tables in this chapter should hope-
fully help keep this Tower of Babel together.
Now that we’ve covered what goes into a COM object and how they
communicate with each other, there remains the subject of everything
else. Can or should COM classes derive their functionality from one
another? How do you implement licensing for your work when your work
is a COM object that’s part of another application? How will your COM
object behave in a multitasking environment?
52 Chapter 2: COM Communication
53
54 Chapter 3: Other COM Issues
Encapsulation
3
With encapsulation, you’re simply creating an instance of a COM “base”
class from within your “derived” COM class and passing any method calls
directly to the base class. You create the base class in your derived class’s
constructor using COM and release it in your destructor. The really hard
part is wrapping all of the base class’s methods with your derived class.
With MFC, this would look something like this:
STDMETHODIMP CWzdSrv::XWzdClass::Method2(long lArg, unsigned long ulArg)
{
METHOD_PROLOGUE(CWzdSrv, WzdClass);
return m_iBaseClass->Method2(lArg,ulArg);
}
Not only is this tedious, but you’re prone to make mistakes, or at least
more mistakes than with traditional inheritance. Please see Figure 3.2.
Aggregation
Another technique called aggregation starts out just like encapsulation,
except instead of you manually wrapping every method, your client must
call QueryInterface() from your derived class to get an interface to your
base class’s methods. Effectively for your user, you’ve just replaced their call
to CreateInstance() with a call to QueryInterface() for the base class. So
with aggregation you’ve delegated all the work to your client and eliminated
the possibility of introducing bugs into your own code. But it’s not really
inheritance is it? What you do get — other than the automatic creation of
two COM objects at once — is the assurance that both objects are in the
same state, and your derived class can control your base class. Aggregation
is used in the next chapter for transactions in COM+ for this reason. Please
see Figure 3.3.
Another positive side to aggregation is that ATL and MFC both provide
support for it. In MFC, you create the base class as usual, except you call
EnableAggregation() in its constructor. In the derived (aggregating) class,
you use the ClassWizard to override CCmdTarget’s OnCreateAggregates()
member function and create the base class there using COM as usual. If it
fails, you return false and the derived class won’t be created. You also
release the base class in your OnFinalRelease() member function and stick
Security 57
a macro in your interface map so a QueryInterface() will work. For all the
details, please see Example 16 (page 173). I
With ATL, you create the base class as usual using the ATL Object Wiz-
ard, making sure to select the option that will allow the object to be aggre-
gated. In the derived (aggregating) class you create the base class in its
FinalConstruct() and release the base class in its FinalRelease(). Once
more, you add a special ATL macro to the interface map so that a client 3
using QueryInterface() will be able to get a pointer to this base class. For
all the details of using ATL aggregation, please refer to Example 26
(page 226).
As for how your client will access this base class, smart pointers make it
fairly easy. If you’ve already created a COM object using CreateInstance()
and you want to create a smart pointer to access the base class, you can use:
IBaseClassPtr pBaseClass(pDerivedClassPtr);
where IBaseClassPtr is the base class smart pointer. Internally, the smart
pointer is still doing a QueryInterface(), but now you don’t have to write
and test it. Be ready to catch exceptions though if the smart pointer can’t
find the base class’s interface. Your client’s application might then look
something like this:
pBaseClass->Method1(1234,&lArg);
pDerivedClass->Method2(lArg2, ulArg);
Even though one object is aggregated from the other, you must still
release both interfaces. However, with smart pointers, you don’t need to
worry.
Security
Another topic of concern to COM writers is security. Your COM classes can
have access to some very secure data sources, so you wouldn’t want just
anybody to be able to create and use them. For objects running on your own
system, COM relies on whatever security you decide to enable using Win-
dows — the assumption being that if you have access to a system and can
log into it, then you have enough privilege to use its software.
What about users on other systems in your network? How do you pre-
vent them from creating and using the COM objects on your system using
DCOM? In this case, COM provides two types of security: Activation Secu-
rity and Application Security.
58 Chapter 3: Other COM Issues
Activation Security
The first type of security, called Activation Security, simply prevents a COM
object from being created by a user on any other system. To do this, you
change a setting in your system’s registry, either using the Registry Editor
(regedit.exe) or OLE/COM Object Viewer (oleview.exe). Please refer to
Figure 3.4 for how to turn off DCOM access to your machine using
OLE/COM Object Viewer.
Object Security
The second type of security allows you to be more selective about what
objects can or can’t be created and by whom. Actually, the first hurdle to a
remote user accessing your COM objects on a Windows NT/2000 system is
that they need to have an account on your machine. This account must be
identical to the one with which they logged into their own system. In other
words, if they logged into their machine using the User ID and Password of
Security 59
“Smith” and “12345”, then they must have the identical User ID and Pass-
word on your system. That’s because when COM goes to create an object I
on your system, it firsts automatically logs into your system as that individ-
ual.
The second hurdle a remote user must pass is a security check you add to
each COM object using the OLE/COM Object Viewer. With this utility, you
can tell a COM object just which users are authorized to access it. Security 3
is configured in two parts: Launch Permissions determines which users are
able to create the COM server and its objects, and Access Permissions deter-
mine which users can access the object once it’s been created. This latter
security is important if the object’s pointer is passed to a user on a third
machine. Launch Permissions should always be more restrictive then Access
Permissions — at least according to the documentation. My philosophy
would be that you shouldn’t be able to access an object if you can’t create it,
so I always make sure the settings are the same.
Security permissions for a COM object are stored with its other settings
in the system registry. You can access these settings using the OLE/COM
Object Viewer as seen in Figure 3.5.
CoInitializeSecurity()
An even finer granularity of security can be achieved for your object pro-
grammatically using CoInitializeSecurity(). COM uses this same API
call itself with your system registry settings as its input. But if you call this
function first, before the first out-of-process method has been called, you
can override these settings. Unfortunately, this API call is rather complicated
and really reserved for a truly advanced user. And since your settings in the
system registry will take care of 99% of your security needs, why bother?
Licensing
A topic that’s closely related to security is licensing. Again, you’re limiting
access to a COM object but for a different reason (“I don’t care about your
nuclear secrets, I just want my money.”). A lot of documentation has been
devoted to an interface called IClassFactory2 that standardizes how to pre-
vent an object from running on a machine without a license. However, as
with any standard, talk is cheap; so how do I get my project done on time?
After all, you can add your own proprietary licensing protection scheme to
your COM object which would probably be tougher to crack then if it were
to follow some well-published approach.
On the other hand, both MFC and ATL do provide some support for
licensing that you might want to take advantage of — although, as usual,
MFC is harder to use. That’s because you need to override a member func-
tion of an MFC class that’s hard coded into a macro. However, once you
follow what to do in Example 44 (page 324), it’s a simple matter to add
logic that checks for a license. In this example, we use MFC’s own
AfxVerifyLicFile() function to look for a license file on a disk and com-
pare it to what it should be:
virtual BOOL VerifyUserLicense()
{
return AfxVerifyLicFile(AfxGetInstanceHandle(),
Multitasking 61
"licence.lic",
L"1234567890",
I
-1);
}
Of course, if your user is crafty enough to copy your COM object onto a
floppy, they’re probably also going to copy the license file too — so you may 3
want to hide it somewhere or require it be refreshed or check against some-
thing else instead. But the effect you’re going for is that if this is an unli-
censed machine, you want to return a FALSE from VerifyUserLicense().
ATL is somewhat easier to use. As shown in Example 45 (page 329), you
don’t have to tear apart some ATL macro to have it use your licensing logic.
Instead, you declare it with DECLARE_CLASSFACTORY2(CWzdLicense).
You still have to manually add the class that supports this logic, which
can look very similar to the MFC code:
class CWzdLicense
{
protected:
static BOOL VerifyLicenseKey(BSTR bstr)
{
// compare bstr with embedded license info
return TRUE; // valid license
}
}
Multitasking
So how does your COM object behave when created from a thread other
then your main process? The simple answer is: just fine. Other then trying to
remember to initialize the COM DLL from every thread in which you want
to use a COM object, your object will behave no differently than if it were a
non-COM object. The trouble starts however in that COM has decided to
get into the thread safety business. In other words, at the same time that
COM creates your object and lets you talk to it, in certain circumstances, it
will also synchronize your communications with it so that no other thread
can talk to it at the same time. Why is that required in a multitasking envi-
ronment?
62 Chapter 3: Other COM Issues
So how does COM force an object to run in a thread different from its
caller? Actually, it’s pretty clever. Instead of calling a method directly, your
call is converted into a message and that message is sent to a message queue
in the communal thread. The communal thread then processes these mes-
sages one-by-one at its own leisure. If two or more threads are using COM
objects that access a share resource, the first thread to call a COM object is
processed while the other thread’s calls sit waiting for a response from their
messages. In other words, COM is using message loops to synchronize
access to the shared resource.
This protection kicks in whenever you first create your COM object.
Without any protection, the pointer returned to you from COM simply
points to a VTBL of methods available from that COM object. With protec-
tion, the returned pointer points to a proxy routine that does the message
loop waiting.
If any of this sounds familiar, that’s because a similar technology is also
used when your application makes out-of-process method calls to an EXE
server. What’s different is that simple message queues are used for marshal-
ling instead of the Remote Procedure Call (RPC) API. You still need a cus-
tom proxy/stub DLL if you are passing custom data. Although passing data
within a process is less CPU-intensive then out-of-process, your data is still
man-handled onto the thread’s stack — let’s just hope they optimized as best
they could.
Multitasking 65
Please see Figure 3.8 for a review of automatic COM thread safety.
I
Figure 3.8 Thread Safety Through Message Loop Synchronization
• what threading model you specify for your objects: Single, Apartment,
Free or Both;
• whether or not your COM objects reside in a DLL or an EXE;
• and whether or not your COM object is being created by another COM
object.
As you can see, the permutations can get ugly fast, which is probably
why Microsoft came up with an apartment analogy (more on that later).
But to really get a feeling for what’s going on, let’s go through the permuta-
tions setting by setting. Tables 3.1 and 3.2 show what happens when you
create COM objects from in-process (DLL) servers. Tables 3.3 and 3.4 show
what happens with out-of-process servers (EXE) whether local or remote.
Tables 3.1 and 3.3 show what happens when you use the
COINIT_APARTMENTTHREADED concurrency mode when initializing COM and
Tables 3.2 and 3.4 show the same for COINIT_MULTITHREADED. As an exam-
ple of reading these tables, the first entry in Table 3.4 would read, “The
main process creating an object with the Single, Apartment, or Both thread-
ing model would run this object in the main process’s thread and any calls
to its methods would be directly to that object’s VTBL — not through a
message queue.”
Synchronization Loopholes
If while perusing Tables 3.1 to 3.4 you find yourself wishing you could call a
method directly instead of going through the message queue, or conversely,
force your call to go through the message queue so you can safely break into
an apartment, COM provides three API calls to do just that.
• CoCreateFreeThreadedMarshaler() allows you to get a direct pointer
into a COM object no matter what the original configuration. In Exam-
ples 47 and 48 (beginning on page 337), we actually aggregate our COM
object to the COM object created by this API call. In COM terminology,
you’ve replaced your regular marshaller (as supplied by the COM DLL
or your own proxy/stub DLL) with a “Free Threaded” marshaller. And
this free-thread marshaller, rather then marshalling anything, simply
directs your calls right to their methods.
• CoMarshalInterThreadInterfaceInStream() and CoGetInterfaceAn-
dReleaseStream() convert a direct pointer into a synchronized pointer
while taking a short excursion in a stream created just for the occasion
— although not always. In fact, it’s up to COM whether or not to syn-
chronize the pointer based on all of the factors listed above (threading
models, etc.) If, for instance, you are passing a COM pointer between
70 Chapter 3: Other COM Issues
two objects in the same thread, the pointer you extract on the other side
won’t be synchronized. Please see Example 32 (page 260).
Apartments
Probably the most complicated part of COM threading is the terminology
that Microsoft uses to describe it. To this end, they came up with the con-
cept of COM objects living in an apartment. If you thought the whole topic
of threading required some abstract thinking, an apartment is worse.
As mentioned earlier, one of the ways COM uses to automatically make
your objects thread safe is to force them to all run in the same thread and
then synchronize any access to that thread using a message loop. By syn-
chronizing access, any thread calling a method of an object in the protected
thread has to wait until any other thread has finished their call, even if it’s to
another method on another object. This protected thread is called a Single
Threaded Apartment (STA) — providing thread safety for any COM object
living inside.
If you decide to provide your own thread safety in your COM object by
using critical sections, any thread at any time can access the methods of
your object. Collectively, all of the COM objects that allow this asynchro-
nous access are considered to be living in a Multithreaded Apartment
(MTA). Because that means all, there’s only one MTA in an application.
So you might be going along thinking, “ah, an apartment is just a thread
with synchronized access to the functionality inside” until you come across
a multithreaded apartment which is really just a rag-tag collection of objects
that can fight for themselves — an anti-apartment really. Please see
Figure 3.9 for more, but if this still doesn’t help, you really don’t need to
72 Chapter 3: Other COM Issues
Summary
In this chapter, we found that COM provides two weak forms of class
inheritance where you do most of the work. Encapsulation is the concept of
wrapping a “base” class’s methods with your own “derived” class. Aggrega-
tion is the concept of your derived class starting up a base class and then
exposing its methods, although a client still has to use QueryInterface() to
get to them.
Summary 73
We also looked at the facilities that MFC and ATL provide for support-
ing licensing of your COM objects, and at Windows NT/2000 support for I
security. Finally, we looked at using COM to make your objects thread safe
and almost blacked out trying to understand what an apartment is.
In the next chapter, we will be entering the wonderful world of COM+,
which rather than what the name infers, is really an enhancement of just
one aspect of COM — an aspect that we really haven’t even discussed yet: 3
using DLLs out-of-process.
74 Chapter 3: Other COM Issues
COM+
A while ago, when I was first reading the literature on COM, I ran across an
obscure article on DLL surrogates. Until then, I thought that COM DLL
servers could only be used in-process, and that if you wanted to develop a
server on another machine, you had to use a COM EXE server. A DLL sur-
rogate, I read, could now allow you to run a DLL remotely by loading the
DLL for you and relaying the object pointers to you. At the time, I thought
it was an oddity developed just for completeness sake. Microsoft themselves
recommended it to be used on flaky DLLs so that they couldn’t crash the
main application. Little did I know it would be the basis for a whole new
way to program.
Somewhere along the line someone realized that rather than have this
dumb DLL surrogate just sit there relaying object pointers, it could also
actually add functionality to your COM DLL without you changing a single
line of code.
In this chapter, we’ll review how the design of client/server applications
evolved over the years and how COM DLL surrogates were used to help
with that evolution. We’ll watch surrogates evolve from the super dumb
DLLHOST application, to the transaction savvy Microsoft Transaction
Server, to the full service COM+.
75
76 Chapter 4: COM+
We’ll watch as the systems programming (e.g., thread safety, load balanc-
ing, scaling) that an application programmer used to have to worry about
be reassigned back to the system where an application programmer can acti-
vate it by setting the right option.
New terminology was required of course. Because the client was getting
stripped of everything but an application’s interface, it now became “thin
client”. And the stuff that was getting stripped out of the client was called
its “business logic”.
As server computers get even faster, the communication between applica-
tion server and database server becomes a bottleneck. This is a particular
problem when an application does a lot of cumulative database access,
where it can’t just grab all the information it needs at once, but has to
sequentially grab data based on a previous action. Network communica-
tions can start to burn up the lines. Sure, stored procedures help by keeping
some sequential accesses on the database server, but you don’t want to write
all of your application in stored procedures. So when the server is fast
enough to handle the load, the application and database servers are being
consolidated onto one machine in what’s called a two-tiered architecture —
or as someone from the seventies would call it, a classic client/server archi-
tecture. Please see Figure 4.2.
78 Chapter 4: COM+
DLL Surrogates
A DLL Surrogate is a special EXE application that can load COM DLLs on
behalf of a client — on the same machine or another machine. If you were
to write a DLL Surrogate application yourself, you would have to imple-
ment the ISurrogate class and call CoRegisterSurrogate() to tell COM
you’re a surrogate. But more than likely, you can just use the DLL Surrogate
supplied by Windows, DLLHOST.EXE.
To set your client up to use DLLHOST.EXE, you use OLE/COM Object
Viewer again. This time you configure the Class ID that identifies the DLL
as if it were a remote EXE. Please see Figure 4.5.
To set up the DLL server on the same or remote machine, you set the
DLL up as usual using the OLE/COM Object Viewer, but then you also
check the “Use Surrogate Process” box and locate the DLLHOST.EXE applica-
tion on the machine. Please see Figure 4.6.
When the client goes to create the DLL, COM redirects the call to the
DLLHOST.EXE on the same machine or some remote machine and passes it
the Class ID of the DLL the client wants to create.
DLL Surrogates 81
And voilà, your DLLs are running out-of-process from your client. But
beware. Now that your DLLs are out-of-process, you must make sure to
either only use argument types that the COM DLL can transmit between cli-
ent and server (see Chapter 2) or create and register your own proxy/stub
DLL. Otherwise, your method calls won’t get all the arguments it asked for.
On the other hand, a couple of interesting wrinkles pop up when using
DLLHOST.EXE. First of all, your COM DLL servers can themselves create
other DLL servers that will run inside of DLLHOST.EXE and therefore don’t
need a proxy/stub, etc. Another interesting wrinkle occurs with multitask-
ing. As mentioned in Chapter 3, when using a COM EXE server, your mul-
titasking choices were rather limited. But now, courtesy of DLLHOST, you
suddenly get all of those choices back for your DLLs. Of course, this phe-
nomenon is limited to the interaction between DLLs in DLLHOST.
Note: With COM objects calling other COM objects, the normal cli-
ent/server terminology starts to get vague. After all, one COM object
can be another object’s server while at the same time be someone else’s
client. Therefore, the “base” client is the first client to start the pro-
ceedings. And the entire architecture is considered “multi-tiered”.
82 Chapter 4: COM+
objects fail a transaction, MTS will rollback every transaction those objects
performed. Please see Figure 4.7. I
But MTS won’t work with just any COM object. First of all, it obviously
must reside in a COM DLL server because, at its heart, it’s just a DLL Sur-
rogate application.
To take full advantage of MTS, the object must also be written with a
single database transaction in mind. Because MTS will be committing or
rolling back everything its COM object does, it won’t do for that COM
object to sit there for hours in a loop handling client requests. Instead, the
object should be written to handle a single request and the client should cre-
ate a new COM object for each request. Please see Figure 4.8 (page 84).
Stateless Programming
Because an object should be written for a single transaction, it also doesn’t
make sense for it to have any long-term member variables or properties.
After all, whatever is stored in the object will be gone soon anyway. Instead,
the COM object should make a point of saving long-term values either in a
database or some other data store. COM and other technologies call this
“stateless” programming. Ironically, classes were originally developed to
84 Chapter 4: COM+
encapsulate data with its functionality. With MTS, there’s no data, just func-
tions.
IObjectContext
MTS also needs a confederate object in your COM object to operate. This
object, created from IObjectContext, is what keeps track of your object’s
transactions and is actually created first. Your object actually becomes the
aggregate of IObjectContext (i.e., your COM class is the “derived” class
and IObjectContext is the “base” class). Your object might not even exist
for the entire length of a transaction.
You can also implement IObjectControl in your COM class. IObject-
Context calls the functions in IObjectControl class when your object is
Microsoft Transaction Server (MTS) 85
Thread Safety
MTS also keeps total control over the threads running in it. You can’t create
your own thread from an MTS object. Nor should you want to because
every object that MTS creates gets executed in its very own thread to start
with.
To prevent its objects from stepping on each other’s shared data, MTS 4
relies on COM’s standard threading models as shown in the tables in Chap-
ter 3. When writing an MTS COM class using ATL, you aren’t even pre-
sented with a choice for a threading model — “Both” is picked for you.
That means that if this MTS object then creates an “Apartment” or “Both”
object, it will run in the same thread; a “Free” will get its very own thread
but with synchronization; and a “Single” will wind up running with the
MTS process itself, again with synchronization.
where m_spObjectContext is a smart pointer that ATL creates for you that
points to the IObjectContext class. To rollback, you use:
m_spObjectContext->SetAbort();
If this object itself has called other COM objects, they too can either call
SetAbort() or SetComplete(). All it takes is one object to call SetAbort()
and the entire transaction is voided, even if SetComplete() is called later.
Therefore, once it’s called, you should return to the original client immedi-
ately.
You should also check to make sure m_spObjectContext isn’t null before
calling it. Although m_spObjectContext will never be null when running
under MTS, it will be when you debug the application in the Studio.
For a complete example of writing an MTS friendly object, please refer
to Example 36 (page 276).
What is COM+?
Contrary to what the name implies, COM+ is not an enhancement to all of
COM — although at one point, there was hope that it would be. Early pre-
sentations on COM+ showed that its developers were trying to make COM
What is COM+? 87
just as easy to use as any non-COM class by completely hiding its inner
workings. Even developers writing early-binding COM servers would sim- I
ply declare a class differently. There would be no IDL file and COM classes
would support traditional inheritance.
But a year later, each month seemed to be marked by what else COM+
wouldn’t be doing. Today, COM+ is more of an enhancement to MTS than
any change to COM. But what they did to MTS more then compensates for
what they didn’t do. In fact, if you do a lot of database client/server, you will
probably rarely use regular COM again.
4
Note: COM+ might still eventually evolve into all of its dreamed func-
tionality, but by then, it’ll probably be called COM++ or Mary’s
Object Emporium.
So what did they do? First, they integrated MTS into the operating sys-
tem starting with Windows 2000. You can’t just install COM+ by itself on a
system, but by being part of the operating system, the surrogate is made
much more powerful and reliable.
Then they added all of the features we supposed a really smart DLL sur-
rogate could support plus a few more:
• Just-in-Time Activation, where COM+ doesn’t create an object until the
client actually calls one of its methods.
• Object Pooling, where COM+ creates a bunch of objects ahead of time.
• Load Balancing, where COM+ redirects a method call to the least busy
machine.
• A New Threading Model, where COM+ provides a faster threading
model that still allows it to protect your COM object.
• Event Service, where COM+ provides a much more robust solution to
having a server callback its client (in other words, a better connection
point and sink).
• Queued Components, where COM+ allows a client to use a COM server
when it isn’t even connected to the server’s computer.
• Finer-grained Security, where COM+ allows you to specify security at the
method level.
Although all of the other features we discussed in the first three chapters of
this book are still available in Windows 2000, the features listed here are
only available to your COM object if you configure it to use COM+. And
88 Chapter 4: COM+
Just-in-Time Activation
To save on space in the server machine, COM+ can be optionally set so that
it doesn’t actually create an instance of your COM object for a client until
the client calls one of its methods. In other words, the client asks COM+ to
create an object to which COM+ replies, “I got your object right here!”
If nothing else forced you to write stateless code, just-in-time (JIT) acti-
vation does. You can store fancy data in your COM class’s member vari-
ables all you want — they’ll just be destroyed the minute your method
returns. Please see Figure 4.9.
Object Pooling I
As you can image, just-in-time activation may save on memory, but it uses
up precious CPU cycles in the process of creating and destroying COM
objects. As a remedy for frequently-called objects, you can ask COM+ to
create a few objects when the system first comes up. Then, when a client
calls a method, COM+ tries to locate that object in the pool and passes the
request to a match. Please see Figure 4.10.
Unlike JIT, which requires no programming on your part, you need to set
up your object to support object pooling. You start by implementing the
IObjectControl mentioned above in your COM class. Just select the appro-
priate option when using the ATL Object Wizard and three methods will be
added to your COM class:
• CanBePooled() which you implement by returning TRUE;
• Activate() which you implement as if it were your COM class’s con-
structor, initializing member variables;
• Deactivate() which you implement as if it were your COM class’s
destructor.
You also must enable object pooling for your COM class when you regis-
ter it with COM+ using the Component Services snap-in. For a more spe-
cific example, please see Example 38 (page 287).
90 Chapter 4: COM+
Load Balancing
Load balancing is another service you get from COM+ without changing
anything in your COM class. This time when a client calls a method, it calls
a “router” machine. The router machine itself can handle the call, but it
also has a response-time tracker service which, as the name implies, keeps
track of which machine is the least busy and can reroute the request to that
machine. All of the machines eligible to handle the call are considered to be
in an “Application Cluster”. Please see Figure 4.11.
To use load balancing, you must first configure what machines are eligi-
ble to play (the cluster). To do this, you go into the router machine’s proper-
ties and add the machine names to the CLBS tab.
Next, you register your COM DLL with COM+ on every machine in the
cluster, making sure to also check the “Component supports dynamic load
balancing” option for each. Checking this option just tells COM+ your
object was written to support load balancing.
What do you do special to support load balancing? You don’t use any-
thing in your code that depended on the location of the machine that the
object will be running on. In other words, you don’t use a complete file path
What is COM+? 91
like “C:\test” and you don’t even use the system time (it could be off by a
minute). I
Not only do you need to give your object a “neutral” threading model,
you also need to use the Component Services snap-in to configure your
object to require this traveling synchronization lock. For more specifics,
please refer to Example 38 (page 287).
sense to use this traveling lock as well — unless a neutral object will at
some time be created along the line. All new COM+ development I
should use this new neutral model to provide thread security.
Event Service
In Chapter 2, we reviewed two options for a COM server to call a client
back when an event occurred. These included adding a connection point to
the server and a sink to the client, or using ActiveX’s standard event firing
4
through a Windows message. COM+ has a new service that allows a server
to talk to a client, but it’s not what you might think.
First of all, this service is again only available to objects running under
COM+. You start by creating a dummy “event” class that defines the class
and methods a COM server will be calling when an event occurs — not
unlike the sink interface we had to define in Chapter 2 to tell the COM
server with the connection point what method to call. This time however,
you register this class with COM+ as an event class.
The dummy class you created doesn’t actually implement anything — its
method calls simply return to the caller. But to write the actual COM+ client
objects, you would use this dummy class’s interface and actually implement
it like any other COM+ class. You then register these client classes with
COM+, only this time you tell COM+ that this object should be created and
called when the previously registered event class is created and called.
And that’s it. For a COM server to activate these client objects when an
event occurs (“fire an event”) now, it just creates the event class and calls its
methods. Please see Figure 4.13 for an overview or Example 39 (page 294)
for more details.
94 Chapter 4: COM+
The COM+ team celebrated the creation of this service with two new
terms. The COM+ server is now called a “publisher” and the clients are
called “subscribers”. And perhaps it’s just as well they did rename these
players because the application of this technology isn’t quite targeted to the
same set of problems — a sink and connection points. Instead of solving the
problem of informing some external client when an event occurs (such as an
administrator application), the clients in this case are COM+ objects too.
Therefore, they can only do what COM+ objects can do: handle a transac-
tion and return as quickly as possible.
So what this technology really provides is a way for one COM+ object to
create and call a bunch of other COM+ objects with just one call.
Queued Components
For all of the last three and a half chapters, we assumed that when a client
wanted to create and call the methods of a COM server, that that server was
available. But what about the case of a client on a laptop computer that
wants to talk to a COM server on some central computer? Do we forbid the
client application from even running until the laptop is on the network?
With COM+, you don’t have to. Instead, you can use what’s called a queued
component.
Once again, you can only use queued components with COM+. This
time it’s because COM+ provides three COM servers of its own that can
What is COM+? 95
record and playback method calls. The client uses the record server
(QC.Recorder) to record its method calls and the server uses the playback I
object (QC.Player). These two COM+ objects in the middle talk to each
other — not with COM this time, but with the Microsoft Message Queue
(MSMQ), which is a relatively new API (12/97) available to any application
that wants to queue messages to an application on the same machine or
another. I highly recommend it for your queuing needs.
To take advantage of COM+’s queued components, you start by creating
your COM server as always except this time you don’t specify any method
arguments as returning data back (i.e., as being an [out] or [in,out]). 4
After all, it wouldn’t make sense for the client to hang around after it’s made
a call to see what the server has to return.
Next, when you register the server with the Component Services snap-in,
you tell it that the object in this server is eligible to process messages straight
from the MSMQ and that its interface is queued. And finally, you also regis-
ter the server on the client machine telling it what machine the server is on
— as you would with any COM server using Regsvr32 and the OLE/COM
Object Viewer.
The client at this point could create an instance of the server and use its
methods as always but the communication would not be queued. Instead,
the client must create an instance of the COM+ server that records method
calls (QC.Recorder). When the client creates the recording object, it lets it
know for which real COM server its recording method calls.
The recording object then exposes those methods to the client as if it
were the real COM server, and the client proceeds as if it were talking to the
real server. Creating and talking to the recording server might look some-
thing like this:
LPUNKNOWN lpUnknown=NULL;
::CoGetObject("queue:/new:Server.Wzd",NULL,IWzd,&lpUnknown);
IWzd *pWzd=(IWzd*)lpUnknown;
pWzd->Method1(1234);
At the point where the client finally releases the recording object, the
recording object formats a message and sticks it into an MSMQ queue.
MSMQ takes over and attempts to send the message to a queue on the des-
ignated machine. How does it know what machine to send it to? It gets it
from the machine address you specified when registering the COM server
on the client machine.
96 Chapter 4: COM+
If the server machine is up and running, the message flies across to it.
Otherwise, MSMQ holds onto the message until the target machine does
appear on the network.
On the server machine, yet another COM+ server object is listening to
MSMQ to see if any messages have come in for the objects in its package.
When one does come in, this object (QC.ListenerHelper) creates the play-
back object (QC.Player) to create and playback the method calls.
For an overview of this process, please see Figure 4.14. For more details,
please refer to Example 40 (page 296).
On the positive side, MSMQ will make a Herculean effort to deliver the
message, even storing it in a dead letter queue if it never succeeds. But if the I
laptop falls off a bridge, what can you do?
Attribute Programming
COM+ represents a shift in programming philosophy. Although application
programmers weren’t suppose to know much about system programming,
as the requirements for applications evolved, they required more and more
systems programming to meet those requirements. Application program-
mers had to be aware of thread safety and client/server protocols and how
best to scale their application up to handle thousands of users.
With COM+, application programmers no longer need to worry about
adding code to their application for thread safety or protocols or scalability
or security. Instead, they get this functionality from COM+, just by activat-
ing the right setting.
In COM+ terminology, setting options to get thread safety, security, etc.
— instead of writing code — is called “attribute programming”.
The Component Catalog 99
Note: Not everyone would consider CORBA dead but Sun did retire
their CORBA product (NEO) when they brought out EJB. As with all
technologies, CORBA will be around until everything that uses it is
retired. More like Betamax then eight-track cassettes.
This time, however, EJB has the backing of another powerhouse vendor,
Sun Micro, the same people that brought you Java. As you might expect,
one of the downsides to EJB is that objects can only be written in Java.
Another is the lack of features you might expect with any new product. But
right now, Sun Micro is a bigger name on Unix machines and EJB will be a
force to reckon with there.
100 Chapter 4: COM+
Summary
In this chapter, we saw COM+ evolve from the simple DLLHOST applica-
tion that could host a COM DLL on another machine into a new program-
ming philosophy with COM+. We saw MTS develop to take over the chores
of keeping track of transactions over several objects or systems. And we saw
MTS evolve into COM+, allowing you to pool your objects or divide their
labor over several machines or secure them against intrusion, all without
writing one line of code ourselves.
COM+ and EJB represent the next step in the evolution of software pro-
gramming. First there was spaghetti programming where your application
was just page after page of subroutines and goto’s. Then there was
object-oriented programming where your application was split up into
classes that could be independently tested and reused. And now there is
attribute programming where programming is more a matter of setting the
right option and writing the actual object is almost an afterthought. And as
with past evolutionary steps, it will take time to get used to not having to
worry about threading, security, etc. But to paraphrase one popular COM+
author, “Don’t worry, be COM”. (Get it? COM sounds like “calm”. It also
has that “Don’t worry, be happy” kind of thing going for it.)
After all, COM+ is just the next logical step while we’re all just waiting
around for computers to be fast and smart enough to know what we want
without us having to program them.
This ends the basics portion of this book. The remaining chapters take
an example-centric approach to COM, presenting common client/server
problems and their COM solutions. I tried to cover all the various incarna-
tions of COM because even though COM+ represents an exciting new way
to program, it really doesn’t offer the whole package yet.
Section II
COM Examples
The examples in this book concentrate on getting you writing COM objects
for a majority of applications as quickly as possible. We start with creating
someone else’s COM object, progress to writing your own using ATL, MFC,
Visual Basic and Visual J++, review the argument types you can use to talk
to these objects, and graduate with writing your own COM+ objects.
Towards the end, we also review one of Microsoft’s prewritten COM object
libraries called Active Data Objects (ADO) to access data sources (ex: data-
bases).
The topics covered in this section by chapter include:
101
102 Section II: COM Examples
COM Communications
Chapter 9 has several examples of how to use COM to automatically send
data between your client and COM server. Included is an example of all the
data types that COM supports as well as Visual Basic and Visual J++.
COM+ Examples
In Chapter 10, we show some of the things you can do with the latest
enhancements to COM. Included is an example of writing a COM+ object,
using the ability for a client to queue its call to a COM server until
the COM server is available, and using the event server provided by
Windows 2000.
Potpourri
The examples in this last chapter (12) are an eclectic bunch — from how to
add licensing support to your COM server, to processing COM errors, to
using the free threaded marshaller to prevent COM from ever trying to
make your COM object thread safe.
5
Chapter 5
Creating and
Accessing COM Objects
Before we get into all the complications involved with writing a COM class
ourselves, I thought a review of how easy it is to use a COM object would
help prove to you that it’s all worth while. We will look at several of the
most common permutations of creating a COM object, from using the API
directly to being totally oblivious to the whole procedure in Visual Basic.
And from creating totally custom COM classes to creating ActiveX con-
trols.
The examples in this chapter include:
Example 1 Creating a COM Object Using C++ and the COM API
where we roll up our sleeves and revert to the API level to create a COM
object.
103
104 Chapter 5: Creating and Accessing COM Objects
Strategy
We will look at three common ways to create a COM object using the COM
API directly. The API functions include, in order of importance:
::CoCreateInstance(), CoCreateInstanceEx(), and ::CoGetObject(). The
last function is usually only of importance as a diagnostic tool. Both
::CoCreateInstance() functions call ::CoGetObject() internally and by
calling it ourselves, we can help troubleshoot an object creation problem.
This example is included for the rare case of when you need to create a
COM class directly with the API. For a simpler approach, please refer to the
next example.
Example 1 Creating a COM Object Using C++ and the COM API 105
Steps
Include the Identifiers for the COM Server in Your Project
The COM class you want to create is identified in the system by guids,
which are 128 bit arrays containing a set of numbers theoretically unique
throughout the universe. Both the DLL or EXE file the class lives in and the
class itself have their very own ids. The identifier for the DLL/EXE file is
called a Class ID and the id for the COM class itself is called an Interface ID
(confused yet?). Because the ids are arrays, they must be included in your
project as data items as opposed to simple defines or constants. In other
words, you have to create a static global array per id in one source file, and
if these ids are required in another file they must be declared externally II 5
there.
1. Create a guids.h file.
2. Add the class and interface ids for all of the COM classes that you’ll be
creating in your project. If the COM class was created using ATL, just
cut and paste the ids you’ll find in the xxx_i.c file created by that
project. For MFC COM classes, locate and copy the files from the
xxx_i.c file located in the separate Interface Server project (see Example
9, page 143). This, unfortunately, only contains the interface ids. You
will need to dig into the .cpp file in the MFC project for the
IMPLEMENT_OLECREATE macro to find the class id. If there are multiple
COM classes in this MFC project, you still only need one class id. The
final guids.h file should look something like this:
#if !defined guids_h
#define guids_h
#endif
you can make to do this as shown in the next two steps. You must make this
call not only when your application first starts, preferably in the InitIn-
stance() of an MFC application, but also at the start of any thread your
application creates.
1. Your application can call:
::CoCreateInstance(
NULL //reserved
);
OR
2. Your application can call:
::CoInitializeEx(
NULL, //always NULL
COINIT_APARTMENTTHREADED //or COINIT_MULTITHREADED
);
You also need to add _WIN32_DCOM to your project settings under “Pre-
processor definitions” in order to get the prototype definition for ::CoIni-
tializeEx() included in your compile.
The second API function allows for better control of your COM objects
in a multitasking environment. Please refer to Chapter 3 for more on how a
COM object behaves in a multitasking environment.
Remember — only include the guids.h file once. The variables it defines
are global.
Example 1 Creating a COM Object Using C++ and the COM API 107
{
_com_error err(hr);
AfxMessageBox(err.ErrorMessage());
return;
}
// ask class factory to create the class
hr = pCF->CreateInstance(
NULL, // aggregated COM object (none)
IID_IWzd, // class to create and object of
(LPVOID*) &iWzd); // returned object pointer
if (FAILED(hr))
{
_com_error err(hr);
AfxMessageBox(err.ErrorMessage());
return;
}
pCF->Release();
iWzd->Release();
results
(MULTI_QI*)&results); // array of MULTI_QI structures
if (FAILED(hr))
{
_com_error err(hr);
AfxMessageBox(err.ErrorMessage());
return;
}
for (int i=0;i<3;i++)
{
if (FAILED(results[i].hr))
{ II 5
_com_error err(hr);
AfxMessageBox(err.ErrorMessage());
}
}
6. You’ll then need to get the pointers to the created objects from the
MULTI_QI structure:
IWzd *iWzd1=(IWzd*)results[0].pItf;
IWzd *iWzd2=(IWzd*)results[1].pItf;
IWzd *iWzd3=(IWzd*)results[2].pItf;
// release all objects when done
iWzd1->Release();
iWzd2->Release();
iWzd3->Release();
Notes
• You’ll find that almost all COM API function calls start with a ::Co…
prefix for “Com Object”. When writing your own COM object, you
might consider naming your COM class this way (e.g., CoAccessDB) in
order to differentiate COM classes from regular C++ or Java classes.
• We used CLSCTX_ALL in the API class above. The other options allow you
to specify exactly what type of file the COM class lives in: DLL or EXE.
However, CLSCTX_ALL gives you the flexibility of allowing someone at
configuration time to determine whether to use a version of the class that
110 Chapter 5: Creating and Accessing COM Objects
lives in an EXE file or a DLL file. When given the choice, COM checks
for DLLs first, then EXEs that use the exact same Class ID.
CD Notes
• When executing the project on the accompanying CD, put a breakpoint
in the OnTest() button handler and step through the API calls.
Strategy
Unlike the direct API approach shown in the last example, smart pointers
bring all of the niceties of object-oriented programming to the creation of
your COM objects. Specifically, the #import compiler directive takes care of
adding those messy guids to your project. And the API calls themselves are
encapsulated in custom classes that the #import directive also creates. These
classes not only make creating an object easier, but also helps ensure the
COM class is destroyed since the Release() method is called from its
destructor.
Steps
Import the COM Class Definitions
1. Import the COM class’s definitions into a project source file using the
#import directive. The file you include can be a DLL, EXE, TLB, or OCX
file. The #import directive has several keywords you can use. Without
any keywords, the classes that #import creates require you to specify a
scope for every reference to the COM class (i.e., SERVERLib::IWzdPtr)
Example 2 Creating a COM Object Using C++ and Smart Pointers 111
where the scope name is the name of the Library statement in the IDL
file:
#import "server\server.tlb"
2. When none of the values you’re importing interfere with any definitions
already in that source file, you can avoid having to use the scope opera-
tor by using this keyword with the #import directive:
#import "server\server.tlb" no_namespace
3. Using the following keyword keeps the compiler from adding extra error
checking code around every method call you make to the imported COM
object. If you want to customize the way you handle errors, use this:
II 5
#import "server\server.tlb" raw_interfaces_only
Also see Example 46 (page 333) for more on automated COM error han-
dling.
OR
2. Your application can call:
::CoInitializeEx(
NULL, //always NULL
COINIT_APARTMENTTHREADED //or COINIT_MULTITHREADED
);
112 Chapter 5: Creating and Accessing COM Objects
You also need to add _WIN32_DCOM to your project settings under “Pre-
processor definitions” in order to get the prototype definition for ::CoIni-
tializeEx() included in your compile.
The second API function allows for better control of your COM objects
in a multitasking environment. Please refer to Chapter 3 for more on how a
COM object behaves in a multitasking environment.
where the class name, IWzdPtr, is the original interface name (i.e., IWzd)
with a “Ptr” suffix. Looking at this syntax another way, you want to create
the COM class IWzd which is located in the Wzd DLL or EXE file.
2. To create the object using a smart pointer’s method instead of in its con-
structor so that you can get the error code, use:
IWzdPtr pPtr2;
HRESULT hr=pPtr2.CreateInstance(
__uuidof(Wzd) //class id of DLL or EXE that contains class
);
Note that even though the class is created on the stack, the pointer oper-
ator is used. Smart pointers override the pointer operator to denote you are
accessing the methods of the created object. To access the methods of the
smart pointer class itself (i.e., QueryInterface()), use the dot syntax
(pPtr.QueryInterface()).
4. To catch COM errors, use:
try
{
…
}
Example 2 Creating a COM Object Using C++ and Smart Pointers 113
Please refer to Example 46 (page 333) for more on COM error handling.
Notes
• If you look in your Debug or Release directory of your client project
after you’ve used the #import directive, you’ll notice it created a .tli and
a .tlh file for every file it imported. Inside you’ll find the custom classes
that #import created along with the guids you no longer need to worry
about. Please refer to the following #Import Generated File Listings for
an example.
• The scope operator name (i.e., SERVERLib) comes from the type library’s
name (the Library statement in the IDL file). The class id name comes
from the coclass name also found in the type library or IDL file.
CD Notes
• When executing the project on the accompanying CD, put a breakpoint
in the OnTest() button handler and step through the API calls.
114 Chapter 5: Creating and Accessing COM Objects
#pragma once
#pragma pack(push, 8)
#include <comdef.h>
//
// Forward references and typedefs
//
//
// Smart pointer typedef declarations
//
_COM_SMARTPTR_TYPEDEF(IWzd, __uuidof(IWzd));
//
// Type library items
//
struct __declspec(uuid("351495d1-aa6d-11d3-807e-000000000000"))
Wzd;
// [ default ] interface IWzd
Example 2 Creating a COM Object Using C++ and Smart Pointers 115
struct __declspec(uuid("351495d0-aa6d-11d3-807e-000000000000"))
IWzd : IDispatch
{
//
// Wrapper methods for error-handling
//
HRESULT Method1 (
long lArg );
//
// Raw methods provided by interface II 5
//
//
// Wrapper method implementations
//
#pragma pack(pop)
#pragma once
//
// interface IWzd wrapper method implementations
//
Strategy
In the last two examples, we created COM objects using what is called their
“custom interface”. You can also use those examples to create a COM
object that supports both a custom interface and a late binding or automa-
tion interface (a “dual” interface). Few and far between COM objects exists
that only support a late-binding interface. Those types of COM objects only
allow access to its functionality through an IDispatch class implementation
and represent the first types of COM objects. Please refer to Chapter 1 for
more on types of COM objects.
We will be using the ClassWizard to create a C++ class that will wrap a
COM object that supports late binding. We will also look at an MFC class
that helps handler errors.
Example 3 Creating a COM Object Using MFC and Late Binding 117
Steps
Initialize the COM DLL
Unlike other Win32 APIs, you need to tell the COM API — specifically
OLE32.DLL — that you’re going to be using it today. There is one of two calls
you can make to do this shown in the next two steps. You must make this
call not only when your application first starts, preferably in the
InitInstance() of an MFC application, but also at the start of any thread
your application creates.
OR
2. Your application can call:
::CoInitializeEx(
NULL, //always NULL
COINIT_APARTMENTTHREADED //or COINIT_MULTITHREADED
);
You also need to add _WIN32_DCOM to your project settings under “Pre-
processor definitions” in order to get the prototype definition for ::CoIni-
tializeEx() included in your compile.
The second API function allows for better control of your COM objects
in a multitasking environment. Please refer to Chapter 3 for more on how a
COM object behaves in a multitasking environment.
3. You can now access its methods as with any C++ class:
wzd.Method1(1234);
wzd.SetProperty1(4321);
long lVal=wzd.GetProperty1();
4. To catch any COM errors using MFC’s COleException class, you can
use:
COleException *e=new COleException;
try
{
}
catch (COleException *e)
{
_com_error err(e->m_sc);
AfxMessageBox(err.ErrorMessage());
e->Delete();
}
Notes
• The C++ class that the ClassWizard generates simply converts your
method calls into the call configuration required by the IDispatch class.
Please see the following example in the Generated C++ Class Listings.
• As mentioned in Chapter 1 and 2, this type of interface is less efficient
than a custom interface because a custom interface is a direct call to the
server (when in a DLL) while a late bound call has the additional over-
head required by IDispatch to get to the right function. When the server
is in an EXE however, the overhead of interprocess communications
overshadows this inefficiency.
Example 3 Creating a COM Object Using MFC and Late Binding 119
• When creating a COM object from Visual Basic or Visual J++, the
late-binding interface is used exclusively. However, this connection can
be optimized by querying the object at compile-time for its functionality
as it’s done here with MFC and the class generated by the ClassWizard.
CD Notes
• Build the project on the CD is debug mode. Put a breakpoint in the
OnTest() button handler and step through as the object is created and
called.
// Attributes
public:
long GetProperty1();
void SetProperty1(long);
// Operations
public:
void Method1(long lArg);
};
120 Chapter 5: Creating and Accessing COM Objects
#include "stdafx.h"
#include "server.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////
////////
// IWzd properties
long IWzd::GetProperty1()
{
long result;
GetProperty(0x1, VT_I4, (void*)&result);
return result;
}
/////////////////////////////////////////////////////////////////////
////////
// IWzd operations
{
static BYTE parms[] =
VTS_I4;
InvokeHelper(0x2, DISPATCH_METHOD, VT_EMPTY, NULL, parms,
lArg);
}
You would like to create and access a COM object that only supports late
binding using smart pointers.
Strategy
In the last example, we created a late-binding COM object using MFC’s
classes. But just as smart pointers made creating an object that supports
early binding easier, so does it make creating a late-bound object. In fact,
unless you looked at the .tlh and .tli files generated by the #import direc-
tive, you wouldn’t know the object was any different.
Steps
Initialize the COM DLL
1. Your application must call:
::CoCreateInstance(
NULL //reserved
);
OR
2. Your application must call:
::CoInitializeEx(
NULL, //always NULL
COINIT_APARTMENTTHREADED //or COINIT_MULTITHREADED
);
122 Chapter 5: Creating and Accessing COM Objects
You also need to add _WIN32_DCOM to your project settings under “Pre-
processor definitions” in order to get the prototype definition for ::CoIni-
tializeEx() included in your compile.
OR
#import "server\debug\server.tlb" no_namespace // don't need to use
a SERVERLib:: scope in
// front of every
IWzdPtr, etc.
OR
#import "server\server.tlb" raw_interfaces_only // doesn't create
wrappers around each method
Notes II 5
• Check what class name to use from the .tli file that the #import direc-
tive extracts from the type library. For example, in the case of VJ++, the
name extracted is actually XxxDispatchPtr, where Xxx is the class name
you use inside of VJ++.
CD Notes
• Build the project on the CD is debug mode. Put a breakpoint in the
OnTest() button handler and step through as the object is created and
called. Notice that the debugger won’t let you step through the dispatch
call, so you’ll have to put a breakpoint in the server methods to catch
them there.
Strategy
The term ActiveX started out as referring strictly to COM objects that cre-
ated controls (custom list boxes, buttons, etc.) in your application — more
124 Chapter 5: Creating and Accessing COM Objects
specifically, controls written in MFC and C++ that could be used in Visual
Basic applications. But as time progressed, ActiveX began to apply to
non-controls as well — when you create a COM DLL or EXE server using
Visual Basic you can only create it using the ActiveX standard. Both the
Dialog Editor and ClassWizard make adding an ActiveX control to your
dialog box templates fairly easy.
Steps
Initialize the COM DLL
Unlike other Win32 APIs, you need to tell the COM API — specifically
OLE32.DLL — that you’re going to be using it today. There is one of two calls
you can make to do this shown in the next two steps. You must make this
call not only when your application first starts, preferably in the
InitInstance() of an MFC application, but also at the start of any thread
your application creates.
OR
2. Your application can call:
::CoInitializeEx(
NULL, //always NULL
COINIT_APARTMENTTHREADED //or COINIT_MULTITHREADED
);
You also need to add _WIN32_DCOM to your project settings under “Pre-
processor definitions” in order to get the prototype definition for ::CoIni-
tializeEx() included in your compile.
The second API function allows for better control of your COM objects
in a multitasking environment. Please refer to Chapter 3 for more on how a
COM object behaves in a multitasking environment.
Example 5 Creating an ActiveX Control Using MFC 125
2. After embedding the class, you will also need to call its Create() method
which has the effect of going out and creating the COM object:
virtual BOOL Create(LPCTSTR lpszClassName,
LPCTSTR lpszWindowName, DWORD dwStyle,
const RECT& rect,
CWnd* pParentWnd, UINT nID,
CCreateContext* pContext = NULL)
You’ll notice this is the exact same syntax of creating a window. How-
ever, you should create an invisible window since it’s only used in this case
to communicate between the ActiveX object and your application using
window messages.
3. Call the methods and properties of this class as usual.
4. To handle events from this object, you will need to add an event sink
map to the window you specified as the parent window when creating
the object. You can do this manually, however, you can also cheat by
adding this object to a dummy dialog template and performing the same
steps above to add an event handler to that dialog’s class and then cut
and paste the resulting map into your real parent window. An event sink
looks like this:
BEGIN_EVENTSINK_MAP(CTesterDlg, CDialog)
//{{AFX_EVENTSINK_MAP(CTesterDlg)
ON_EVENT(CTesterDlg, IDC_SERVERCTRL1, 1 /* Event1 */,
OnEvent1Serverctrl1, VTS_I4)
ON_EVENT(CTesterDlg, IDC_SERVERCTRL1, -600 /* Click */,
OnClickServerctrl1, VTS_NONE)
//}}AFX_EVENTSINK_MAP
END_EVENTSINK_MAP()
Note: Make sure to substitute your class’s name and its base class for
the dummy dialog class’s name and base class.
Notes
• An ActiveX control is typically in a DLL, however, for marketing rea-
sons, the suffix to this file is .OCX rather than .DLL.
Example 5 Creating an ActiveX Control Using MFC 127
• If you look inside of the class created for you by the Wizards, you’ll
notice that ActiveX objects use late binding. Perhaps the biggest differ-
ence between ActiveX and late bound objects is the standard functional-
ity required of an ActiveX object. And an ActiveX control always creates
a window of some sort. Please see the following listing for an example
wrapper class.
• An ActiveX control is a great way to create a custom control once and
share it amongst your project team without worrying about supplying
them with the current source files or even whether or not they’re pro-
gramming in the same language as you.
• Events are a way for an ActiveX control to call back its client whenever
something happens. This is done using windows messages and sink II 5
maps. Outside of the ActiveX standard, an entirely different approach is
used where the server calls the client directly, which you will see in
Examples 13 and 14 (beginning on page 160).
CD Notes
• You will need to create the ActiveX project first so that it registers the
ActiveX control. Otherwise the Dialog Editor will complain that it can’t
find the control that was inserted into it and display a blank area where
the control should be. Note that even before the application is created,
its ActiveX controls are in use because some of its functionality is used
by the Dialog Editor to configure the look and feel of the control (what-
ever settings the ActiveX author provides for their control.)
128 Chapter 5: Creating and Accessing COM Objects
/////////////////////////////////////////////////////////////////////
////////
// CServer wrapper class
// Operations
public:
long Method1(long lArg);
void Refresh();
void AboutBox();
};
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations
immediately before the previous line.
#endif //
!defined(AFX_SERVER_H__A039CB7C_AE33_11D3_8088_000000000000__INCLUDED_)
130 Chapter 5: Creating and Accessing COM Objects
#include "stdafx.h"
#include "server.h"
/////////////////////////////////////////////////////////////////////
////////
// CServer
IMPLEMENT_DYNCREATE(CServer, CWnd)
/////////////////////////////////////////////////////////////////////
////////
// CServer properties
long CServer::GetProperty1()
{
long result;
GetProperty(0x2, VT_I4, (void*)&result);
return result;
}
short CServer::GetAppearance()
{
Example 5 Creating an ActiveX Control Using MFC 131
short result;
GetProperty(DISPID_APPEARANCE, VT_I2, (void*)&result);
return result;
}
float CServer::GetProperty2()
{ II 5
float result;
GetProperty(0x1, VT_R4, (void*)&result);
return result;
}
/////////////////////////////////////////////////////////////////////
////////
// CServer operations
void CServer::Refresh()
132 Chapter 5: Creating and Accessing COM Objects
{
InvokeHelper(DISPID_REFRESH, DISPATCH_METHOD, VT_EMPTY, NULL, NULL);
}
void CServer::AboutBox()
{
InvokeHelper(0xfffffdd8, DISPATCH_METHOD, VT_EMPTY, NULL, NULL);
}
Strategy
This is the classic example — one of the original forces behind the creation
of COM, which is allowing Visual Basic programmers to access nifty con-
trols created using MFC and C++. And as you might expect, as the oldest
application of COM, it’s very easy to accomplish using the Visual Basic stu-
dio.
Steps
Add an ActiveX Control to your VB Project
1. From the Visual Basic Studio, click on the “Project” then “Compo-
nents…” menu commands to open the Components dialog box. Select
the registered ActiveX control you require and click “OK”. This control
will then appear in the Studio’s selection of controls.
2. Add the control to your form(s) and change its properties and handle its
events as you would any regular VB control.
Example 7 Creating a COM Object from Visual Basic 133
Notes
• If the control you desire doesn’t appear in the Components dialog box,
chances are it hasn’t been registered on your system. To register a con-
trol, you will first obviously need to locate the ActiveX file. Then, type:
Regsvr32 activex.ocx
CD Notes
• Open the MFC application (under the Server subdirectory) using the
Developer’s Studio. Create the ActiveX control using the Developer Stu- II 5
dio — the ActiveX control will automatically register itself as the last
step of the project build. Open the VB application using the VB Studio
and notice that this ActiveX control is now part of Form1.
Strategy
Visual Basic can also create COM objects that don’t use the ActiveX stan-
dard and don’t appear in its forms, however they still must support late
binding. To do this, we will be using several additions to the Basic language
lexicon, including New, CreateObject(), and even the class.method() syn-
tax you only used to find in object-oriented languages.
Steps
Add the COM Class to Your Project
1. Add the COM object to your Visual Basic project by clicking on the
“Project” and then “References…” menu commands to open the
134 Chapter 5: Creating and Accessing COM Objects
Notice that you call the methods of the COM object as if it were a class
method.
2. When an object type isn’t known at compile time, such as when a user
action determines what type of object to use, you need to use the follow-
ing syntax. At runtime, VB is forced to query the method IDs of a COM
object as required (hey, get a faster computer):
Dim IWzdSrv2 As Object
Set IWzdSrv2 = New SERVERLib.Wzd
IWzdSrv2.Method1 (4321)
3. You can even create a COM object without having first included it in
your project by specifying its ProgID directly as seen here:
Dim IWzdSrv3 As Object
Set IWzdSrv3 = CreateObject("Server.Wzd")
IWzdSrv3.Method1 (9876)
4. In all cases, you don’t have to worry about releasing a COM object in VB
— it’s done for you automatically.
Notes
• As you can see, COM objects are accessed just like the methods of any
class in an object-oriented language. Later, in Example 27 (page 234), we
will see that you can also create your own COM object using VB and the
class nomenclature. So, in addition to the other benefits of COM, it also
Example 8 Creating a COM Object from Visual J++ 135
CD Notes
• Create both the VB project and the ATL project (for debug). Unlike the
situation where both the client and the COM object are both written in
the same language, you cannot step from one language into another
when debugging. You therefore have two choices, the first of which is to
step through the VB client and watch as the objects are created and used. II 5
To test from the other side using C++, create the object as usual for
debug. Also, create the VB client as an EXE file. Then, go into your C++
project settings and locate the “Debug” tab. On that page is an edit box
called, “Executable for Debug Session”. Click the button next to it and
then click “Browse”. Now locate the VB .EXE file you just created and
select it. You can now start debugging in the Developer Studio which will
automatically bring up the VB .EXE application.
Strategy
Accessing a COM object from J++ is almost as simple as accessing it from
Visual Basic — which is saying something. The Visual J++ Studio creates a
Java class wrapper that takes all the headache out of creating, accessing,
and destroying the object. Unfortunately, as with VB, you are again forced
to use COM classes that support late binding.
136 Chapter 5: Creating and Accessing COM Objects
Steps
Create the Class Wrapper
1. From within the VJ++ Developer Studio, click on the “Project” and
“Add COM Wrapper...” menu items to open the COM Wrappers dialog
box. You’ll notice that the Studio creates two wrapper classes right in
the COM class’s subdirectory. The first class wraps the COM class’s
interface, the second wraps the COM class’s implementation. Please see
the following listings for an example of this wrapper class.
2. In the parlance of Java, the name of the DLL or EXE file that contains
the COM class becomes the package name, but the class name is still the
class name. Therefore, to create a COM class where the COM class
“Wzd” exists in the DLL project “server”, you would use:
server.Wzd wzd=new server.Wzd();
4. As with Visual Basic, you don’t have to worry about releasing a COM
object — it’s done for you automatically by VJ++.
Notes
The AppWizard will automatically add these menu items along with some
context-sensitive help, so it’s better to just manually add these menu items
yourself than to add all that dead weight to your project.
CD Notes
Bring up both projects, the first in the VJ++ Studio, the second (located in
the Server subdirectory) in the Developer Studio. Build both. Then, refer to
the CD Notes for the last example.
Example 8 Creating a COM Object from Visual J++ 137
package server;
import com.ms.com.*;
import com.ms.com.IUnknown;
import com.ms.com.Variant;
package server;
import com.ms.com.*;
import com.ms.com.IUnknown;
import com.ms.com.Variant;
/** @com.class(classid=C177116F-9AAA-11D3-805D-000000000000,DynamicCasts)
@com.interface(iid=C177116E-9AAA-11D3-805D-000000000000,
thread=AUTO, type=DUAL) */
public class Wzd implements IUnknown,com.ms.com.NoAutoScripting,server.IWzd
{
/** @com.method(vtoffset=4, dispid=1, type=METHOD,
name="Method1", addFlagsVtable=4)
@com.parameters([in,type=I4] lArg) */
public native void Method1(int lArg);
Example 8 Creating a COM Object from Visual J++ 139
141
142 Chapter 6: Writing COM Servers with MFC
Example 10 Writing a COM DLL Server with MFC which shows you
how to create a COM class that lives in a DLL.
Example 11 Writing a COM EXE Server with MFC which shows you
how to write a COM class that lives in an executable.
Strategy
MFC COM classes that will only support late binding (aka, Automation),
which is all that Visual Basic and VJ++ can use, don’t require an Interface
Server. However, to write an MFC COM class that supports early binding
(aka, a custom interface), you need an Interface Server. Why does the inter- II
face between a client and a server itself need a server? Because the job of
sending and receiving data between client and server has been moved to this
6
interface server. We will find in the next chapter that this separate project in
MFC has been incorporated into the server project itself in ATL.
Because there aren’t any wizards to create this project and it involves sev-
eral intricate files, we will be simply modifying the Interface Server project
found on the CD accompanying this book.
Steps
Add the Interface Server Project to Your MFC Server
1. Create a subdirectory off of your MFC COM server project and copy the
following Interface Server project files from the CD. If you use the Sam-
pleWizard to do this, the “Wzd” mnemonic will be replaced with what-
ever is more appropriate for your application:
IWZD.DEF // a DLL definition file
IWZD.DSP // a project file
IWZD.DSW // a workspace file
IWZD.IDL // a blank IDL file
IWZD.MAK // a make file that creates the .c, .h and .dll files
Note: You can find a sample listing for most of these files at the end of
this example.
Notice that there are no method attributes here — they usually come into
play when defining a late-bound COM class.
To edit this file then:
1. Change the guid that defines this class. Use the GUIDGEN.EXE application
that comes with your VC++ distribution kit to generate a new guid.
2. Change the class (aka, interface) name to your own class name.
3. Add your own methods and arguments to this class. To determine what II
attributes to add to these arguments, please refer to Example 31
(page 246).
4. If you need to define more than one COM class, simply cut and paste this 6
definition as many times as needed and repeat steps 1 to 3.
Notes
• We will find in Example 18 (page 192) that this DLL registration isn’t
necessary with some COM objects written using ATL — even when
they’re out-of-process or using multitasking. That’s because the COM
146 Chapter 6: Writing COM Servers with MFC
DLL is configured to do all the work itself as it does with objects written
in VB and VJ++.
CD Notes
• Please refer to the next example for a demo of this project.
EXPORTS
DllGetClassObject @1 PRIVATE
DllCanUnloadNow @2 PRIVATE
GetProxyDllInfo @3 PRIVATE
DllRegisterServer @4 PRIVATE
DllUnregisterServer @5 PRIVATE
.c.obj: 6
cl /c /Ox /DWIN32 /D_WIN32_WINNT=0x0400 /DREGISTER_PROXY_DLL $<
clean:
@del $(IFACE).dll
@del $(IFACE).lib
@del $(IFACE).exp
@del dlldata.obj
@del $(IFACE)_p.obj
@del $(IFACE)_i.obj
Strategy
The AppWizard, ClassWizard, and MFC are all stream-lined to easily create
a late-bound (automation) COM class for you. There isn’t, however, an
148 Chapter 6: Writing COM Servers with MFC
ounce of help available to add a custom interface to that. So we will use the
wizards as far as they can take us and then manually edit the rest of the way.
We will be using the AppWizard to create the DLL server for us and the
ClassWizard to create the COM classes themselves. We will also need the
Interface Server found in Example 9 to define our COM classes for the
world.
Steps
Create the Server Project
1. Use the AppWizard to create a “Regular DLL” using MFC statically or
dynamically. Make sure to also include “Automation” support.
2. To a subdirectory of the create project, add the Interface Server project
found in Example 9. Insert this project into your server project’s work-
space for convenience. Follow the steps in Example 9 to add your COM
class’s methods to the IDL file and build the project.
where IWzd is the name you gave your COM class’s interface and Wzd-
Class is the name of the class that will actually implement the methods
Example 10 Writing a COM DLL Server with MFC 149
of your COM class. Within this map, declare each of the methods you
will be implementing.
Notice in this line that you are associating the Interface ID with the class
name you specified in the .h file as the one that will be implementing the
COM class.
3. You will now need to implement all of the methods of IUnknown for your
COM class. Fortunately, there aren’t many and other than their names,
they’ll look exactly like this:
//////////////////////////////////////////////////////////////
/// XWzdClass Implementation
ULONG FAR EXPORT CWzdSrv::XWzdClass::AddRef()
{
METHOD_PROLOGUE(CWzdSrv, WzdClass);
return pThis->ExternalAddRef();//pThis accesses enclosing
class's this pointer
}
return pThis->ExternalRelease();
}
You’ll notice that the name you pick for the COM class implementation
(in this example WzdClass) gets an “X” prefixed to it by the macros and then
is used to create a class that’s embedded in your CCmdTarget class.
4. Similarly, add your COM class’s methods to this class using the same
naming convention and syntax as seen here:
STDMETHODIMP CWzdSrv::XWzdClass::Method1(long lArg1, long *plArg2)
{
METHOD_PROLOGUE(CWzdSrv, WzdClass);
return S_OK;
}
5. After building the project, you will need to manually register it using:
Regsvr32 xxx.dll
For a full listing of the .cpp and .h files, please refer to the listings at the
end of this example.
Notes
• You can also modify your project settings to automatically register your
COM object, however some prefer to always manually register their
objects for more control over their development (you know exactly what
COM object is registered).
• Don’t mess with the other maps you find in the generated COM class
files. Although they aren’t used for a COM class that uses a custom inter-
face, you might eventually decide to add late-binding support so that VB
and VJ++ applications can use your creation. And these maps are where
Example 10 Writing a COM DLL Server with MFC 151
your definitions will go. The same applies to the .ODL file you’ll find in
your project directory.
• You create a Regular DLL project for the basic reason that a Regular
DLL can be used by any application — not just an MFC application —
which is one of the major tenants of COM: interoperability.
• Please see the notes under Example 18 (page 192) on how to debug your
DLL server.
CD Notes
• Open the test.dsw project — all three projects will be loaded (the client,
server and interface server). Build the debug version and place a break
point in the OnTest() handler of the dialog class of the tester. Then, step II
through as the MFC client calls the MFC server.
6
Listings — COM Class .H File
#if
!defined(AFX_WZDSRV_H__4487D433_A6FF_11D3_A398_00C04F570E2C__INCLUDED_)
#define
AFX_WZDSRV_H__4487D433_A6FF_11D3_A398_00C04F570E2C__INCLUDED_
#include "..\IServer\IWzd.h"
/////////////////////////////////////////////////////////////////////
////////
// CWzdSrv command target
// Attributes
public:
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CWzdSrv)
public:
virtual void OnFinalRelease();
//}}AFX_VIRTUAL
// Implementation
protected:
virtual ~CWzdSrv();
DECLARE_MESSAGE_MAP()
DECLARE_OLECREATE(CWzdSrv)
/////////////////////////////////////////////////////////////////////
////////
#endif //
6
0
// WzdSrv.cpp : implementation file
//
#include "stdafx.h"
#include "Server.h"
#include "WzdSrv.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////
////////
// CWzdSrv
IMPLEMENT_DYNCREATE(CWzdSrv, CCmdTarget)
CWzdSrv::CWzdSrv()
{
154 Chapter 6: Writing COM Servers with MFC
EnableAutomation();
AfxOleLockApp();
}
CWzdSrv::~CWzdSrv()
{
// To terminate the application when all objects created with
// with OLE automation, the destructor calls AfxOleUnlockApp.
AfxOleUnlockApp();
}
void CWzdSrv::OnFinalRelease()
{
// When the last reference for an automation object is released
// OnFinalRelease is called. The base class will automatically
// deletes the object. Add additional cleanup required for your
// object before calling the base class.
CCmdTarget::OnFinalRelease();
}
BEGIN_MESSAGE_MAP(CWzdSrv, CCmdTarget)
//{{AFX_MSG_MAP(CWzdSrv)
// NOTE - the ClassWizard will add and remove mapping macros here.
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
BEGIN_DISPATCH_MAP(CWzdSrv, CCmdTarget)
//{{AFX_DISPATCH_MAP(CWzdSrv)
// NOTE - the ClassWizard will add and remove mapping macros here.
Example 10 Writing a COM DLL Server with MFC 155
//}}AFX_DISPATCH_MAP
END_DISPATCH_MAP()
// {4487D431-A6FF-11D3-A398-00C04F570E2C}
static const IID IID_IWzdSrv =
{ 0x4487d431, 0xa6ff, 0x11d3, { 0xa3, 0x98, 0x0, 0xc0, 0x4f, 0x57,
0xe, 0x2c } };
II
// add this line from idl project (iwzd_i.c)
const IID IID_IWzd = 6
{0xC177116E,0x9AAA,0x11D3,{0x80,0x5D,0x00,0x00,0x00,0x00,0x00,
0x00}};
BEGIN_INTERFACE_MAP(CWzdSrv, CCmdTarget)
INTERFACE_PART(CWzdSrv, IID_IWzdSrv, Dispatch)
INTERFACE_PART(CWzdSrv, IID_IWzd, WzdClass) // add this line
END_INTERFACE_MAP()
// {4487D432-A6FF-11D3-A398-00C04F570E2C}
IMPLEMENT_OLECREATE(CWzdSrv, "Server.WzdSrv", 0x4487d432, 0xa6ff,
0x11d3, 0xa3, 0x98, 0x0, 0xc0, 0x4f, 0x57, 0xe, 0x2c)
/////////////////////////////////////////////////////////////////////
////////
// CWzdSrv message handlers
//////////////////////////////////////////////////////////////
/// XWzdClass Implementation
ULONG FAR EXPORT CWzdSrv::XWzdClass::AddRef()
{
METHOD_PROLOGUE(CWzdSrv, WzdClass);
return pThis->ExternalAddRef();//pThis accesses enclosing
class's this pointer
156 Chapter 6: Writing COM Servers with MFC
return S_OK;
}
return S_OK;
}
Strategy
As you might expect, the strategy here is not much different than it was for
the last example. This time, however, rather than creating a DLL using the
AppWizard, you create an EXE project. The ClassWizard creates the COM
class as shown in the last example.
Steps
Create the COM EXE Server
1. Use the AppWizard to create an EXE project (MDI, SDI, Dialog — it
doesn’t matter). Make sure to specify “Automation” support. II
2. To a subdirectory of the create project, add the Interface Server project
found in Example 9. Insert this project into your server project’s work- 6
space for convenience. Follow the steps in Example 9 to add your COM
class’s methods to the IDL file and build the project.
Notes
• If running a COM EXE is inconvenient as a method to registering your
COM class, you can also write your own .reg file that will do the same.
A template of such a .reg file might look something like this:
REGEDIT4
[HKEY_CLASSES_ROOT\CLSID\{000209FF-0000-0000-C000-000000000046}]
@="Microsoft Word Application"
[HKEY_CLASSES_ROOT\CLSID\{000209FF-0000-0000-C000-
000000000046}\LocalServer32]
@="c:\\PROGRA~1\\MICROS~2\\OFFICE\\WINWORD.EXE"
158 Chapter 6: Writing COM Servers with MFC
CD Notes
• Debugging a COM EXE is a difficult proposition. Because the debugger
within the Developer’s Studio can only debug one process at a time, you
will need to open a second Studio for the COM server. Create debug ver-
sions of both client and server and then start the server. You can now
select break points in the server. Now, start the client and put a break
point on the OnTest() button handler. Click “Test” and step into the
server. You’ll notice that you will automatically switch between debug-
gers as you go from client to server and back.
• You can also just start the client because when you step into the server,
you automatically switch in a Studio that was executed just for the occa-
sion. In fact, unless you start the COM server in the second debugger, a
third debugger will be started — just loading the server up into a debug-
ger isn’t enough to make COM recognize it as the server you want to
run.
• Make sure to kill each of these COM EXE servers as you go along —
otherwise you won’t be able to rebuild the server (you’ll get a share pro-
tection error because the EXE is still using its program image).
Strategy
As mentioned in Example 3 (page 116), the AppWizard, ClassWizard, and
MFC are all setup to make the creation of a late bound COM class easy.
Therefore, we will be using the AppWizard to create the server — either
DLL or EXE — then, we will be using the ClassWizard to create the COM
Example 12 Writing a COM Server that Supports Late Binding with MFC 159
classes. And instead of using the editor to add methods to our class, we will
use the ClassWizard yet again.
Steps
Create the COM Server
1. Use the AppWizard to create a DLL or EXE project. The DLL must be a
Regular DLL. The EXE can be MDI, SDI, or Dialog. If you choose SDI
or dialog, every time you create a new COM object, a new instance of
the EXE file is executed. With an MDI application, only one instance is
executed. Also make sure to choose “Automation” support. II
Create the COM Class
6
1. Use the ClassWizard to add a COM class to your project derived from
CCmdTarget or any class that is itself derived from CCmdTarget (i.e., CWnd,
CDialog). At this point, you’ll notice that the controls at the bottom of
this dialog become enabled. For the first class, you can pick “Createable
by Type ID” because this adds a ClassID to your project. For other
COM classes in the same server, you can pick “Automation”.
With “Member variable” you are only notified when the variable in
question changes (the “Notification function”).
Notes
• The AppWizard will automatically add these menu items along with
some context-sensitive help. It’s better to just manually add these menu
items yourself than to add all that dead weight to your project.
CD Notes
• Please see Chapter 1 and 2 for more on the difference in efficiency
between early binding and late binding. As previously mentioned, you
may not have a choice — if you want a VB or VJ++ programmer to be
able to use your MFC COM object, you may be forced to use late bind-
ing.
Strategy
Rather than tie up a client waiting for an event to occur at the server, it’s
much more efficient to give the server a function to call in the client when
something happens. In Win32 parlance, this is called a callback address.
Since COM is brand new and someone was bored, new terms were
required. And since one of the past analogies for COM was that objects
were like hardware components, hardware terms were used — thus, the
new terms connection point and sink. What those terms really mean is that
one or more clients can give a server object a callback address that the
server will call when something happens. The address the server calls in a
client is considered a sink, and a server that can do this is considered to have
a connection point.
Unfortunately, a client can’t just pass a callback address to a server for all
the same reasons why we weren’t calling the server directly ourselves.
Example 13 Writing a COM Server with a Connection Point with MFC 161
Instead, the client itself must implement a COM class and pass that address
to the server. Fortunately, we’ll leave that implementation to another exam-
ple (Example 14, page 163).
For now, we just have to add a connection point to our MFC server.
Although MFC has no wizards for automatically adding a connection point
to a server, it does supply a couple of macros you can manually add to the
server yourself.
Steps
Add a Connection Point to a COM Class
II
1. Create the COM server and class as usual with the AppWizard and
ClassWizard as shown in the earlier examples in this chapter.
2. Add an include to the top of the new COM class’s .h file: 6
#include "afxctl.h"
3. To the bottom of the .h file of this new class, add a connection map using
the following macros and syntax:
DECLARE_CONNECTION_MAP()
BEGIN_CONNECTION_PART (CWzdSrv, CallBackCP)
CONNECTION_IID(IID_IWzdSink)
END_CONNECTION_PART(CallBackCP)
5. To the .cpp and .h file, add a method that you can call from anywhere
that will handle the actual call to the client. In this example, we’re calling
it “CallBackClients()”. Since the CConnectionPoint class allows multi-
ple clients to connect to this object for events, we will need to call every
client in a loop like so:
void CWzdSrv::CallBackClients(long data)
{
const CPtrArray *pConnections=m_xCallBackCP.GetConnections();
int nConnections=pConnections->GetSize();
for (int i=0;i<nConnections;i++)
{
IWzdSink *pWzdSink=(IWzdSink*)(pConnections->GetAt(i));
pWzdSink->Callback(data);
}
}
Notes
• The connection point we just implemented only supports early binding.
Since both Visual Basic and Visual J++ are both clients that require late
binding, you can only use this solution with an MFC client. Please see
the next example for an MFC client that uses this connection point. VB
and VJ++ can connect to a connection point, however it must be imple-
mented using IDispatch. The ATL Wizards allows you to automatically
add a connection point that supports late binding — ironically, it’s an
early-binding connection point that you must manually add (see Exam-
ple 25, page 220). If you can, use ATL when you need to support a
late-binding connection point. Otherwise, please refer to the code auto-
matically generated by Example 25 as a guide for adding this functional-
ity to your MFC COM server.
• The routine CallBackClients() shown previously is intended to be used
by any other class in the server when the client needs to be informed of
some event. In addition, the parameter list can be just as extensive as any
method call.
Example 14 Writing a COM Client with a Sink Using MFC 163
CD Notes
• Build the debug version of test.dsw. Then, stick a breakpoint in the
OnTest() button handler and watch as the client creates the server, con-
nects its sink to it, calls it and then gets called back.
6
Strategy
This is the client side of the previous example using MFC. What we will be
doing is setting up our own mini-server within this client, getting a COM
pointer to it and sending it to the server to register and call whenever some
event occurs. We will be creating our mini-server manually and then use
MFC’s AfxConnectionAdvise() static function to make the actual connec-
tion. Please refer to the previous example as to why this mini-server is called
a sink in COM.
Steps
Create the Mini-Server’s Interface Project
1. Referring back to Example 9 (page 143), create an interface that will
define the COM class with which the server will use to call you back.
This can be like any server interface with multiple arguments, etc.
2. Create a guids.h file containing the Interface ID of the Interface Project
you created. In this example, IWzdSink is the interface’s name:
#if !defined guids_h
#define guids_h
{0x20050FE0,0xA719,0x11d3,{0xA3,0x98,0x00,0xC0,0x4F,0x57,0x0E,
0x2C}};
#endif
return S_OK;
}
IWzdSink *iWzdSink=NULL;
hr = m_xWzdSinkClass.QueryInterface (IID_IWzdSink, (void**)
&iWzdSink);
m_xWzdSinkClass.Release(); // undo AddRef() performed by QI,
unneeded when server is us
The server can now call the client’s Callback() method at will.
166 Chapter 6: Writing COM Servers with MFC
Notes
• Even though the client is implementing a small server within itself, it
must still follow all the rules of COM (i.e., write and register a
proxy/stub for non-automation argument types, etc.). After all, if the
server is in Detroit, Detroit must still somehow get back to your client in
Denver.
• ActiveX servers use an entirely different approach to calling back a client
which involves a windows message sent to the client which has a sink
map to process it. Please see Example 17 (page 184) for more.
CD Notes
• Build the debug version of test.dsw. Then, stick a breakpoint in the
OnTest() button handler and watch as the client creates the server, con-
nects its sink to it, calls it, and then gets called back.
Strategy
Classes that only create one of themselves are called singletons. But single-
tons break at least one rule of COM objects: when a client creates a COM
object it will receive a fresh copy every time. Unfortunately, there are times
when you want the same object referred to each time, such as when you
want several COM objects to keep track of some state.
Writing a COM singleton server with MFC is a matter of overriding the
class factory that MFC uses and perverting it for our own gains. A class fac-
tory is the actual part of a COM server that creates an instance of the COM
object and MFC holds this functionality in a class called COleObjectFac-
tory. In order to get at it, we also need to rewrite a couple of MFC macros
because the name COleObjectFactory is actually burned into the standard II
macros.
6
Steps
Write a Singleton Class
1. Write your MFC COM class as usual. See the examples earlier in this
chapter.
2. To the top of the .h file of this COM class, add your own definitions for
the MFC OLE macros DECLARE_OLECREATE and IMPLEMENT_OLECREATE.
We do this because the class name it uses for COleObjectFactory is hard-
coded and we can’t get to it otherwise to specify our own class name
(which, in this example, is CWzdOleObjectFactory):
/////////////////////////////////////////////////////////////////////
////////
// Implement our own DECLARE_OLECREATE macros
class CWzdOleObjectFactory;
#define DECLARE_OLECREATE_WZD(class_name) \
public: \
static AFX_DATA CWzdOleObjectFactory factory; \
static AFX_DATA const GUID guid; \
AFX_DATADEF CWzdOleObjectFactory
class_name::factory(class_name::guid, \
RUNTIME_CLASS(class_name), FALSE, _T(external_name)); \
AFX_COMDAT const AFX_DATADEF GUID class_name::guid = \
{ l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }; \
3. Substitute these new macros for the current macros. In the .h file, find
the DECLARE_OLECREATE macro and rename it to
DECLARE_OLECREATE_WZD. In the .cpp file, it’s the IMPLEMENT_OLECREATE
macro for the IMPLEMENT_OLECREATE_WZD macro.
4. Implement this new derivation of COleObjectFactory at the bottom of
this .h file:
/////////////////////////////////////////////////////////////////////
////////
// Implement our own derivation of COleObjectFactory class factory
class CWzdOleObjectFactory : public COleObjectFactory
{
public:
CWzdOleObjectFactory( REFCLSID clsid, CRuntimeClass* pRuntimeClass,
BOOL bMultiInstance, LPCTSTR lpszProgID ) :
COleObjectFactory(clsid,pRuntimeClass,bMultiInstance,lpszProgID)
{};
Notes
• We couldn’t define the new CWzdOleObjectFactory class at the top of the
.h file for compiler reasons. CWzdSrv couldn’t be used in our implementa-
tion until it was defined.
CD Notes
• Open the test.dsw project into the Developer Studio and build the
debug version of the project. Place a breakpoint in the OnTest() button
handler and run the application. Then, step through and notice that no
matter how many times the COM class is created using ::CoCreateIn-
stance(), the same COM object instance is returned.
II
Listings — COM Class .H File
6
#if
!defined(AFX_WZDSRV_H__4487D433_A6FF_11D3_A398_00C04F570E2C__INCLUDED_)
#define
AFX_WZDSRV_H__4487D433_A6FF_11D3_A398_00C04F570E2C__INCLUDED_
/////////////////////////////////////////////////////////////////////
////////
// Implement our own DECLARE_OLECREATE macros
class CWzdOleObjectFactory;
#define DECLARE_OLECREATE_WZD(class_name) \
public: \
static AFX_DATA CWzdOleObjectFactory factory; \
static AFX_DATA const GUID guid; \
170 Chapter 6: Writing COM Servers with MFC
/////////////////////////////////////////////////////////////////////
////////
// CWzdSrv command target
// Attributes
public:
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CWzdSrv)
public:
virtual void OnFinalRelease();
//}}AFX_VIRTUAL
// Implementation
virtual ~CWzdSrv();
protected:
/////////////////////////////////////////////////////////////////////
////////
// CWzdSrv command target
II
class CWzdSrv : public CCmdTarget
{ 6
DECLARE_DYNCREATE(CWzdSrv)
// Attributes
public:
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CWzdSrv)
public:
virtual void OnFinalRelease();
//}}AFX_VIRTUAL
// Implementation
virtual ~CWzdSrv();
protected:
DECLARE_MESSAGE_MAP()
DECLARE_OLECREATE_WZD(CWzdSrv)
};
/////////////////////////////////////////////////////////////////////
////////
// Implement our own derivation of COleObjectFactory class factory
class CWzdOleObjectFactory : public COleObjectFactory
{
public:
CWzdOleObjectFactory( REFCLSID clsid, CRuntimeClass*
pRuntimeClass,
BOOL bMultiInstance, LPCTSTR lpszProgID ) :
COleObjectFactory(clsid,pRuntimeClass,bMultiInstance,lpszProgID)
{};
{
// return static singleton object
static CWzdSrv wzd;
return &wzd;
}
};
/////////////////////////////////////////////////////////////////////
////////
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations II
immediately before the previous line.
6
#endif //
!defined(AFX_WZDSRV_H__4487D433_A6FF_11D3_A398_00C04F570E2C__INCLUDED_)
Strategy
You can derive one COM class interface from another (just use an existing
interface for your interface’s base class.) But you can’t derive one fully
implemented COM class from another. Why not? The biggest problem is
that what is traditionally considered inheritance is achieved at compile-time
when a big table of pointers is created to all of the methods in the class.
COM classes can’t interact at compile-time because they might be written in
six different languages. Instead, they can only interact at runtime when any
call to a base class method would require a runtime solution. Also consider
that your base class might be in Cleveland while your derived class could be
in Cincinnati. A runtime solution would possible go along these lines: You
174 Chapter 6: Writing COM Servers with MFC
create a COM object IWzd2 with a base class IWzd. When you go to call a
method on IWzd2, COM must first check to see if the method you’re calling
is actually in IWzd2 or if it’s, in fact, a method in IWzd and then make the call
there. Microsoft has been promising to eventually add this functionality, but
it probably won’t be any time soon based on how long they’ve promised it.
Another solution is encapsulation — where you just instantiate the base
class from within your “derived” class and pass any method calls directly to
the base class. However, the problem with this approach is that you must
manually add every method you want from your base class to your derived
class.
The most commonly used solution is called aggregation, where the
derived class again instantiates the base class. The difference is that the cli-
ent itself has to use QueryInterface() to get a pointer to the base class so
that it can use its methods. In fact, all that aggregation gets you other than
the automatic creation of two COM objects at once is that you are assured
that both objects are in the same state. (Aggregation is used for transactions
in COM+ for this reason.)
Again, there is no wizard to allow you to aggregate your MFC COM
object to another COM object. But to be fair, ATL has no wizard either.
Instead, aggregation is a sophisticated dance of manual editing, most of
which involves creating and releasing one object from within another.
Steps
Write the COM Class That Will be Aggregated
1. Create the MFC COM class as usual using the ClassWizard, then add the
following to the COM class’s constructor:
// allow this object to be aggregated by another
EnableAggregation();
Example 16 Aggregating a COM Object with MFC 175
Write the COM Class That Will Aggregate The “Base” Class
1. Create a guids.h file containing the class and interface IDs of the object
you wish to aggregate:
#if !defined guids_h
#define guids_h
2. Create the second MFC COM class as usual using the ClassWizard. 6
3. To the .h file of this second class, add a member variable that will hold
the pointer to the aggregated class’s object:
private:
IUnknown* m_punkWzdAggSvr;
5. To the .cpp file of the second class, include the guids.h file:
#include "guids.h"
GetControllingUnknown(),
CLSCTX_INPROC_SERVER,
IID_IUnknown,
(LPVOID*)&m_punkWzdAggSvr);
if (FAILED(hr))
{
m_punkWzdAggSvr = NULL;
return FALSE;
}
return TRUE;
}
10. For a listing of this “derived” class, please refer to the end of this exam-
ple.
_com_error err(hr);
AfxMessageBox(err.ErrorMessage());
return;
}
where iWzd is the pointer to the “derived” class and iWzdAgg is a pointer
to the “base” class.
3. You can then access the methods of these two classes as before:
iWzdAgg->Method1(1234,&lArg);
iWzd->Method2(lArg2, ulArg);
4. You must still release both objects — aggregation doesn’t even take that
burden away from you: II
iWzd->Release();
iWzdAgg->Release(); // must still release aggregated interface 6
Notes
• Accessing the “base” class can be done much cleaner with smart point-
ers. Please refer to Example 2 (page 110) for an example of this.
• As mentioned previously, all aggregation gets you is the assurance that
whatever data is in one object’s member variables will match the state of
the other object. For instance, aggregation is used in COM+ to keep
track of transactions — the outer “derived” object keeps track of the
transaction while the inner “base” class does the actual data source
manipulation.
CD Notes
• Open the test.dsw project and build the debug version. Place a break-
point in the OnTest() button handler and run the application. Then, step
through as both the aggregated and aggregating COM objects are cre-
ated and used.
178 Chapter 6: Writing COM Servers with MFC
#include "..\IServer\IWzd.h"
/////////////////////////////////////////////////////////////////////
////////
// CWzdSrv command target
// Attributes
public:
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CWzdSrv)
public:
virtual void OnFinalRelease();
//}}AFX_VIRTUAL
Example 16 Aggregating a COM Object with MFC 179
// Implementation
protected:
virtual ~CWzdSrv();
/////////////////////////////////////////////////////////////////////
////////
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations
180 Chapter 6: Writing COM Servers with MFC
#endif //
!defined(AFX_WZDSRV_H__4487D433_A6FF_11D3_A398_00C04F570E2C__INCLUDED_)
#include "stdafx.h"
#include "Server.h"
#include "WzdSrv.h"
#include "guids.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////
////////
// CWzdSrv
IMPLEMENT_DYNCREATE(CWzdSrv, CCmdTarget)
CWzdSrv::CWzdSrv()
{
EnableAutomation();
// aggregated object(s)
m_punkWzdAggSvr=NULL;
AfxOleLockApp();
}
CWzdSrv::~CWzdSrv()
{
// To terminate the application when all objects created with
// with OLE automation, the destructor calls AfxOleUnlockApp.
AfxOleUnlockApp();
}
II
void CWzdSrv::OnFinalRelease() 6
{
// release aggregate(s) we created
m_punkWzdAggSvr->Release();
CCmdTarget::OnFinalRelease();
}
BEGIN_MESSAGE_MAP(CWzdSrv, CCmdTarget)
//{{AFX_MSG_MAP(CWzdSrv)
// NOTE - the ClassWizard will add and remove mapping macros
here.
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
BEGIN_DISPATCH_MAP(CWzdSrv, CCmdTarget)
//{{AFX_DISPATCH_MAP(CWzdSrv)
// NOTE - the ClassWizard will add and remove mapping macros
182 Chapter 6: Writing COM Servers with MFC
here.
//}}AFX_DISPATCH_MAP
END_DISPATCH_MAP()
// {4487D431-A6FF-11D3-A398-00C04F570E2C}
static const IID IID_IWzdSrv =
{ 0x4487d431, 0xa6ff, 0x11d3, { 0xa3, 0x98, 0x0, 0xc0, 0x4f, 0x57,
0xe, 0x2c } };
BEGIN_INTERFACE_MAP(CWzdSrv, CCmdTarget)
INTERFACE_PART(CWzdSrv, IID_IWzdSrv, Dispatch)
INTERFACE_PART(CWzdSrv, IID_IWzd, WzdClass)
INTERFACE_AGGREGATE(CWzdSrv, m_punkWzdAggSvr) //add this line
so that MFC knows to look for additional methods in aggregated
object
END_INTERFACE_MAP()
// {4487D432-A6FF-11D3-A398-00C04F570E2C}
IMPLEMENT_OLECREATE(CWzdSrv, "Server.WzdSrv", 0x4487d432, 0xa6ff,
0x11d3, 0xa3, 0x98, 0x0, 0xc0, 0x4f, 0x57, 0xe, 0x2c)
/////////////////////////////////////////////////////////////////////
////////
// CWzdSrv message handlers
//////////////////////////////////////////////////////////////
/// Aggregate Implementation
BOOL CWzdSrv::OnCreateAggregates()
Example 16 Aggregating a COM Object with MFC 183
{
// create the aggregate object(s)
HRESULT hr = ::CoCreateInstance(CLSID_IWzdAggSvr,
GetControllingUnknown(),
CLSCTX_INPROC_SERVER,
IID_IUnknown,
(LPVOID*)&m_punkWzdAggSvr);
if (FAILED(hr))
{
m_punkWzdAggSvr = NULL;
return FALSE;
} II
return TRUE; 6
}
//////////////////////////////////////////////////////////////
/// XWzdClass Implementation
ULONG FAR EXPORT CWzdSrv::XWzdClass::AddRef()
{
METHOD_PROLOGUE(CWzdSrv, WzdClass);
return pThis->ExternalAddRef();//pThis accesses enclosing
class's this pointer
}
return S_OK;
}
Strategy
Controls are child window-like buttons and list boxes. Using MFC, you can
add your own functionality to the standard system supplied control win-
dows, but the classes you create would only work in an MFC application.
ActiveX controls are COM objects that support a standard set of function-
ality so that you can add your custom controls to any other application that
supports ActiveX controls.
What this example hopes to show is how to create a basic ActiveX con-
trol using MFC’s ActiveX ControlWizard. But because ActiveX represents a
whole standard for a server, including dozens of classes, it must support in
order to be considered ActiveX, if you will be doing serious ActiveX devel-
opment, I would advise you to get a book that can devote all of its pages to
ActiveX.
Example 17 Writing an ActiveX Control Using MFC 185
Steps
Create the ActiveX Project
1. Click on Developer Studio’s File and New menu items to open the New
dialog box. Then, enter a project name and directory and select “MFC
ActiveX ControlWizard”.
2. The ControlWizard has two steps. In the first step, pick how many con-
trols this server will contain. If you have two or more related controls,
pick that many. The final DLL will be that much bigger, but there won’t
be any wasted space. You can also have the wizard provide everything
you need so that your customers must buy a license from you in order to
run this control. Please see Figure 6.1 for step 1. II
3. In the next step, you pick the name to give this control’s classes. You can
also decide whether your control will be based on a standard system con- 6
trol, such as a button or list box (recommended), or whether you’re
going to create your own control from scratch. Also, in step 2 you can
decide on several features to add to your control. Those shown in this
page are related to visual features you can add. Press the “Advanced…”
button to pick from a selection of options that can optimize your control
when used in a web browser. Please see Figures 6.2 and 6.3 respectively
for step 2 and the Advanced options. Please refer to Table 6.1 for the
effect of each of these options.
Notes
• Please refer to the examples in Chapter 5 for how to use an ActiveX con-
trol from MFC or Visual Basic.
• While perfecting your control, you may find it too cumbersome to create
and use another application to be your client. Instead, you can use the
“ActiveX Control Test Container”. To invoke this utility, just build the
debug version of your control and then press the “Debug”/“Go” menu
commands or toolbar button to start debugging. The debugger will
immediately realize you need an executable to run your control (because
it’s a DLL) and will prompt you for one. Click on the arrow button next
to the edit box and select the “ActiveX Control Test Container”.
• In ActiveX parlance, a container is a client that also usually supplies the
parent window on which your control will appear. Internet Explorer,
Visual Basic forms, and even your dialog boxes can be containers.
CD Notes
• The project on the accompanying CD contains a simple ActiveX control.
Try building it and inserting it in a Visual Basic form or VC++ dialog
template.
7
Chapter 7
191
192 Chapter 7: Writing COM Servers with ATL
Strategy
We will be using the ATL COM AppWizard to create the DLL project itself,
but to create the COM classes that will live in this server, we will use the
ATL Object Wizard. We will also look at adding methods and properties to
a COM class using the ClassView tab of the Workspace View of the Studio.
Steps
Create the DLL Project
1. Use the Developer Studio’s “File” then “New” command to open the
New dialog box. Select the “Projects” tab and pick a name for your
II
server. Remember this will be the name of the server and not the COM
class that will live in it, and for the purposes of file naming you can’t
name both the same. Click on the “ATL COM Wizard” list box item to
open the ATL COM Wizard. The DLL option is already selected, but you
also have a choice of three optional types of functionality you can add to 7
your server:
• Allow merging of proxy/stub code — if your COM class’s methods
will be using non-standard argument types as seen in Example 31
(page 246), you will need to write and register a proxy/stub DLL
to support those method arguments. This normally means you
need to keep track of two DLLs, one for the COM server and one
for the proxy/stub server. Clicking this option consolidates the
COM DLL with the proxy/stub DLL so you have only one DLL to
worry about.
• Support MFC — if you would like to use MFC from your COM
server (and who doesn’t), click this option. Seriously though, don’t
click this option unless you will be doing some extensive user inter-
face work in your server because it adds overhead to your object. If
you need MFC’s data collection support, why not use the Standard
Template Library (STL) instead? That’s what it’s there for.
• Support MTS/COM+ — if you would like to add support in your
server for the ability to work in a COM+ environment allowing
your object to work with several databases while COM worries
about committing or rolling back the transaction then pick this
option.
Please refer to Figure 7.1 for a look at these options.
194 Chapter 7: Writing COM Servers with ATL
Note: There are several other types of objects to choose here; however,
90% of the time you’ll either picking this one or MS Transaction
which is covered in Example 36 (page 276).
2. Fill in a name to give your COM class, which you will find will create
several other names as well.
3. Click on the “Attributes” tab to view the options you can give your
class.:
• Threading model — allows you to decide whether or not to depend
on COM to make sure your object is thread safe. Please refer to
Chapter 3 for much more on what these options mean.
• Interface — allows you to pick whether or not your object will
support early or late binding. If you pick “Custom interface”,
Visual Basic and Visual J++ will not be able to use your object.
Picking “Dual” allows other C++ objects and clients to use the
Example 18 Writing a COM DLL Server Using ATL 195
1. Click on the ClassView tab of your Workspace View and locate the inter-
face symbol for your COM class. Then, right-click on this symbol. You
will then be given a choice of adding methods or properties to this class.
Please refer to Figure 7.3.
Note: If you pick the symbol above the interface symbol in Figure 7.3,
you will also be given an opportunity to add methods but you will be
adding them to the DLL shell that your COM class sits in and not to
the COM class itself.
2. To add a method, pick the “Method” menu item to open the “Add
Method to Interface” dialog box. Enter the method’s name and argu-
ments. Note that you must enter how those arguments will appear in the
IDL file, that is, with the argument attributes before the argument and
type (i.e., [in] long lArg).
Example 18 Writing a COM DLL Server Using ATL 197
3. When you click “OK”, the Studio will edit three files for you: the IDL
file, and the .CPP and .H files of the COM class itself. Unfortunately, it II
will use the same argument types you entered in the IDL file, which may
not work in the .CPP and .H files. In other words, you may be required to
go back and fix the types so that your source files will compile. This most
notably happens with Safearray arguments. Please refer to Chapter 2
for what arguments to use in the IDL and the C++ source files.
7
• You can now go to the .CPP file and implement you COM class’s
methods.
code to make your server smaller. To force ATL to return this code, go
into your project settings and remove the symbol “_ATL_MIN_CRT” from
your preprocessor definitions (you will only find this symbol in your
Release configurations).
4. If your methods will be using argument types that aren’t supported by
the OLE dll (i.e., structures) and you want COM to either provide thread
protection to your COM server or your COM server will be out-of-pro-
cess as with an MTS/COM+ server, you will need to build your server’s
proxy/stub dll too. To build this dll, execute this line:
nmake -f XXXps.mk
Where XXXps.mk is the make file that the ATL COM AppWizard auto-
matically created for your project. Because nmake requires thousands of
utilities and dlls to be in your environments PATH, the easiest way to
execute this line is to temporally add it to the Post Build Step of your
project settings and rebuild the project. Once built, you need to register
it, now and wherever your server is installed, using regsvr32.exe:
Regsvr32 XXXps.dll
If you don’t create this DLL, COM is notorious for not telling you where
it hurts and will try to muddle through without it. In the case of an array,
for instance, it will send the first element over and ignore the rest.
5. An ATL project will automatically register itself when you build it. Some
feel that this is too much automation on the project’s part and go into the
project settings (the “Custom Build” tab) and remove this step. However,
I have found that 75% of my persistent COM problems when developing
are from incorrectly registering the right COM server. Unless you feel
you’re an advanced user, I would leave this step alone.
Notes
• We were able to automatically add methods to our COM class using the
ClassView tab of the Workspace View — however, if you make a mistake
and want to add or change an argument, or if you want to delete a
method or property entirely, you will have to manually edit the IDL,
.CPP and .H files yourself. Also be aware that if the definition for a
method doesn’t match in all three files, you don’t get a friendly compiler
error telling you the problem. Instead you get an obscure error that the
COM class you are writing couldn’t be instantiated — at compile time.
Before you waste your time pondering why the compiler was trying to
Example 19 Writing a COM EXE Server Using ATL 199
instantiate your class, just recheck your prototypes against the IDL file to
make sure they match both for argument types as well as number of
arguments.
• A common scenario is to have an MFC client with one or more ATL
server dependencies in the same workspace. But when you build the cli-
ent using its sole “Release” build configuration, which build configura-
tion does the Studio use for your ATL servers? Minimum size. If you
prefer minimum dependencies instead, you can add a plain old “Release”
configuration to your project which the Studio will use instead. Start by
clicking on the “Build” then “Configuration” menu items to open the
Configuration dialog box. Then, click “Add”. Enter the name “Release”
and copy it from the minimum dependencies configuration.
II
• It is a little trickier to debug a COM DLL than it is to debug a regular
DLL. The problem is in setting breakpoints. Since a COM object doesn’t
officially become part of your application until runtime, the debugger
will refuse to allow you to set a breakpoint in a COM DLL until runtime,
and then when your application terminates, those breakpoints get
thrown out. To make your COM DLL accept breakpoints as readily and 7
dependably as a regular dll, go into your project settings under the
“Debug” tab, and open the “Category” combo box at the top. Select
“Additional DLLs” and browse for and select the .DLL files that contain
the COM servers you want to debug. Now you can go into that server’s
source files and set breakpoints whether you’re running or not.
Strategy
As you might expect, writing the executable version of a COM server isn’t
that much different than writing the dll version which we did in the last
example. However, there are a few things to keep in mind which we will dis-
cuss below.
200 Chapter 7: Writing COM Servers with ATL
Steps
Create the EXE Project
1. As in the last example, bring up the ATL COM AppWizard, only this
time select “Executable (EXE)” for the server type. You will notice that
all of the options listed at the bottom of the dialog box now become dis-
abled. Why can’t you merge the proxy/stub with your executable?
Because the proxy/stub must be in-process with its client and if its sitting
in your server’s executable, it will be out-of-process. Why can’t your exe-
cutable server support MTS/COM+? Because those technologies depend
on your server being run by their DLL host, which pretty much rules out
an EXE. And why can’t your server support MFC? Actually it can, and if
you’re willing to hunt around the MSDN documentation for it, they’ll
give you some tedious steps to manually add MFC support to your COM
executable server. But I think it would be much simpler to refer to Exam-
ple 11 (page 156) and create your COM executable server using MFC
instead.
will need to use the OLE/COM Object Viewer to configure the system
registry on the client’s machine to tell it what machine the server is on.
Start by loading up the OLE/COM Object Viewer, which comes with
your copy of VC++, on the client machine. Expand the tree view of
objects at the “All Objects” branch as seen here in Figure 7.4.
II
2. Next, locate your COM class in this list (it obviously must have already
been registered once by the Studio or by using regsvr32.exe), and click
once on it. This will open a raw view of the registry in the right-hand
window as seen in Figure 7.5. Click on the “Implementation” tab of this
property sheet to view yet another property sheet. Click first on the
“Inproc Server” tab and clear the “Path to Implementation” edit box.
Do the same for the “Local Server” tab. Then, click on the “Activation”
tab of the main property sheet and enter the name of the machine that
your server is on. Click on the “Registry” tab to save your settings.
3. Register your EXE server on its machine as usual or with OLE/COM
Object Viewer, except this time leave the Activation tab alone and fill in
the Local Server path.
4. If you are running Windows NT or Windows 2000 on the server
machine, make sure you have an account on that machine. In other
words, if you signed into the machine that the client is running on using
the account and password “Bill”/”Pass”, then you should have the exact
same account and password on the server machine. COM just uses that
account and password to run that application on that machine.
202 Chapter 7: Writing COM Servers with ATL
Notes
• Debugging your COM EXE server can be a tedious problem. The debug-
ger will only run one process at a time — which is okay for a dll server
that does, in fact, run in the process of the client. However, for an exe-
cutable, the Studio actually invokes another Studio when you step into
the server to run the server. This doesn’t allow you to set up any break-
points in the server. However, what you can do is bring up the other Stu-
dio yourself, put your breakpoints on and then execute the Server. Then,
bring up the client, which will now attach to your running server rather
than start its own. Don’t reverse the steps, however, and bring up the cli-
ent first or it will spawn its own server again and the server in your Stu-
dio will be ignored.
• Sometimes when you build your EXE server, the Studio will complain
that it can’t write to the EXE file due to sharing problems. This means
one or more of your servers are still running. And because an EXE server
Example 20 Writing a COM EXE Service Using ATL 203
doesn’t have an interface, you‘ll have to kill it using the task list of your
operating system (press ctrl-alt-delete to invoke it). Unfortunately, this
doesn’t always work. On NT/2000 especially, when you go to kill the
server, it will respond that you don’t have the privilege. To kill a stub-
born server like this, just right click on it in the task list to attach a
debugger to it. Once it has come up into the Studio’s debugger, just click
the stop-debugging toolbar button and exit the Studio.
Strategy 7
An NT Service is an application that starts at boot up time and is controlled
thorough the Control Panel’s “Service’s” applet. Windows 9x doesn’t sup-
port services, however, you can get the same effect by writing an EXE pro-
gram and sticking it in the autoexec.bat file in the root directory. All you
miss out on is the ability to pause the application through the Control
Panel.
Writing a ATL service is again not unlike a DLL or regular EXE server.
However, an ATL service is typically used to also perform some function
other than just house one or more COM classes. In fact, the usual scenario
is that the service starts a thread that performs some function, such as log-
ging errors or generating reports and the COM object is simply used as a
way for any other application to talk to this function (i.e., send it data). We
will therefore look at this scenario.
Steps
Create the EXE Project and its COM Classes
1. Create the project using ATL COM AppWizard as shown in Example 18
(page 192). Pick EXE service and notice that the options at the bottom
204 Chapter 7: Writing COM Servers with ATL
of the dialog box are disabled. To find out why, please refer to the last
example.
1. At the top of your project’s main .cpp file, the one with CServiceModule
_Module; defined at the top, add your event flags and thread definitions:
// thread stuff
#include <process.h> /* _beginthread, _endthread */
long glArg;
HANDLE ghNow, ghDie, ghDone, ghThread;
void WzdThread(LPVOID param);
2. Down below, just before the service goes into its message loop, create the
events and the thread:
// create thread events and start thread
ghNow = CreateEvent (NULL, TRUE, FALSE, NULL);
ghDie = CreateEvent (NULL, TRUE, FALSE, NULL);
ghDone = CreateEvent (NULL, TRUE, FALSE, NULL);
ghThread = (HANDLE)_beginthread(WzdThread, 0, NULL);
// the message loop that was put here by the ATL COM AppWizard
MSG msg;
while (GetMessage(&msg, 0, 0, 0))
DispatchMessage(&msg);
• The service will leave the message loop when it receives a WM_QUIT
message. At this point, you want to tell the thread to also termi-
nate and then deallocate the event flags. Rather than kill the
Example 20 Writing a COM EXE Service Using ATL 205
thread, which can be messy, we set an event flag created just for the
occasion that tells the thread to leave its wait loop:
// service had terminated, tell thread to die
SetEvent(ghDie);
if (WaitForSingleObject(ghDone, 1000) == WAIT_TIMEOUT)
{
// thread didn't terminate, kill it...
DWORD dwCode=0;
TerminateThread(ghThread, dwCode);
}
CloseHandle(ghDone);
CloseHandle(ghDie); II
CloseHandle(ghNow);
• Now create the thread that listens to these event flags, perhaps
times out to perform some chore, but definitely returns when the
ghDie event is set. Such a thread might be written like this: 7
// WzdThread.cpp: Implementation of WzdThread thread
#include "stdafx.h"
#include <process.h> /* _beginthread, _endthread */
/////////////////////////////////////////////////////////////////////
////////
// event stuff
extern long lArg;
extern HANDLE ghNow, ghDie, ghDone;
// the thread
void WzdThread(LPVOID param)
{
// thread will work in background
::SetThreadPriority(::GetCurrentThread(),THREAD_PRIORITY_BELOW_NORMAL);
// forever
while (true)
206 Chapter 7: Writing COM Servers with ATL
{
// wait for termination, timeout or a now event
int rVal;
HANDLE hEventArray[2] = {ghDie, ghNow};
if ((rVal=WaitForMultipleObjects(2, hEventArray, FALSE,
30000))==WAIT_OBJECT_0)
break; // terminate now!
switch (rVal)
{
///////////////////////////////////////////////////////////////////
//////
//////////////////// NOW EVENT //////////////////////////////////
///////////////////////////////////////////////////////////////////
//////
case WAIT_OBJECT_0+1:
ResetEvent(ghNow);
break;
}
}
// end thread
SetEvent(ghDone);
_endthread();
}
Strategy
One of the tenants of COM is that you should never ever change an inter-
face. Of course, in real life within a company, it happens all the time. But if
you want to follow by the rules, we will review how to add a new extension
to an old COM class using ATL. Mostly what is involved is just adding a
new COM class to your existing project using the ATL Object Wizard and
then manually editing both to be aware of each other.
208 Chapter 7: Writing COM Servers with ATL
Steps
Edit the Old COM Class
We will be modifying the old COM class’s .h file to split up its class into a
base class and a derived class (this is to prevent link errors later):
1. Change the class name from, as an example, CWzd to CWzdBase.
2. Create a new class called CWzd at the bottom of the .h file that looks like
this:
// new class
class CWzd :
public CWzdBase,
public CComCoClass<CWzd, &CLSID_Wzd>
{
public:
DECLARE_REGISTRY_RESOURCEID(IDR_WZD)
};
3. In the old class definition, comment out the CComCoClass<> in the deriva-
tions.
4. In the old class definition, comment out the DECLARE_REGISTRY_RESOURCE
ID(IDR_WZD) macro.
5. To get a general idea of what the final class would look like, please refer
to the listing at the bottom of this example.
5. Because this new class is derived from the old class, you can now use any
or all of the old class’s methods in your new class, perhaps even passing
an entire method to the base class.
3. Note that you cannot go backwards — upon creating the old class, you
can’t get a pointer to the interface of the new class.
Notes
• Note that even though the new class is derived from the old class, that
you cannot access the old class’s methods from the new class. This gets
into the issue of COM classes and why they can’t be derived from one
another. For more on why, please refer to Chapter 3 and Example 26
(page 226). If you want, you can simply add the old class’s methods to
your new class and call the old class’s method inside.
CD Notes
• Build the project on the CD and put a breakpoint in the OnTest() button
handler and then step through the old and the new implementations.
210 Chapter 7: Writing COM Servers with ATL
#ifndef __WZD_H_
#define __WZD_H_
/////////////////////////////////////////////////////////////////////
////////
// CWzd
class ATL_NO_VTABLE CWzdBase :
public CComObjectRootEx<CComSingleThreadModel>,
// public CComCoClass<CWzd, &CLSID_Wzd>, //moved down
public IDispatchImpl<IWzd, &IID_IWzd, &LIBID_SERVERLib>
{
public:
CWzdBase()
{
}
DECLARE_PROTECT_FINAL_CONSTRUCT()
BEGIN_COM_MAP(CWzdBase)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(IWzd)
END_COM_MAP()
// IWzd
public:
STDMETHOD(Method1)(/*[in]*/ long lArg);
};
// new class
Example 21 Extending Your ATL COM Class 211
class CWzd :
public CWzdBase,
public CComCoClass<CWzd, &CLSID_Wzd>
{
public:
DECLARE_REGISTRY_RESOURCEID(IDR_WZD)
};
#endif //__WZD_H_
II
Listings — Edited New Class’s .H File
// WzdEx.h : Declaration of the CWzdEx
#ifndef __WZDEX_H_ 7
#define __WZDEX_H_
DECLARE_REGISTRY_RESOURCEID(IDR_WZDEX)
212 Chapter 7: Writing COM Servers with ATL
DECLARE_PROTECT_FINAL_CONSTRUCT()
BEGIN_COM_MAP(CWzdEx)
COM_INTERFACE_ENTRY2(IDispatch, IWzdEx)
COM_INTERFACE_ENTRY(IWzdEx)
COM_INTERFACE_ENTRY_CHAIN(CWzdBase)
END_COM_MAP()
// IWzdEx
public:
STDMETHOD(Method2)(/*[in]*/long lArg);
};
#endif //__WZDEX_H_
Strategy
Actually, we did this in Examples 18 and 19 — you just didn’t know it.
Unlike MFC, which makes you very aware of what type of interface you’re
supporting, as long as you don’t put any late-binding hostile argument types
into your methods, the ATL support classes take care of both interfaces
invisible to you.
Steps
Create a Late Binding COM Class
1. Follow all the steps in Example 18 or 19. Just make sure to specify
“Dual” when selecting the attributes for your COM class in the ATL
Object Wizard. And if the MIDL compiler complains about an argument
Example 23 Writing an ATL Singleton Server 213
type not being compatible with OLE Automation, this time you defi-
nitely have to change the type to something it will support.
Notes
• Although COM objects are suppose to work with any client, each client
has its very own selection of argument types you should be working
with. Please see Chapter 2 and Examples 31, 32, and 33 (beginning on
page 246) for more.
Steps
Make a COM Class a Singleton
1. To make a COM class into a singleton just add the following macro to its
.h file just about the DECLARE_REGISTRY_RESOURCEID macro:
DECLARE_CLASSFACTORY_SINGLETON(CWzd)
Notes
• You’ve probably noticed by now if you’re reading this chapter from start
to finish, that a lot of the configuration of an ATL COM class is done in
its .h file leaving just method implementations in its .cpp file.
214 Chapter 7: Writing COM Servers with ATL
CD Notes
• When executing the project on the accompanying CD, you will notice
that the same object pointer is returned every time CreateInstance() is
called.
Strategy
A tearoff interface gives you a way to create only a portion of your COM
class at any one time. Why would you need that kind of functionality? Usu-
ally, it’s in the situation where you have a COM class that has lots of func-
tionality but it’s only infrequently or optionally used based on the hardware
you have or some other factor.
To add these interfaces, we will be doing lots of manual editing to the
IDL and .h files of our COM “base” class and making liberal use of ATL’s
helpful macros.
Steps
Create the COM Server and Class
1. Create the ATL server and COM class as usual using the wizards as seen
in Example 18 (page 192).
public:
};
3. Don’t worry, you don’t have to edit the .cpp file too. After you’ve saved
your work, you can use the ClassView tab of the Workspace View to add
methods and properties to tearoff interfaces.
4. Please see an example .H file below under Listings.
216 Chapter 7: Writing COM Servers with ATL
2. To create one of the tearoff interfaces using smart pointers, use the fol-
lowing:
IWzdTearPtr pTear(pPtr);
pTear->Method2(1234);
3. When the smart pointer releases this object, the object is destroyed
immediately!
Notes
• Only use a tearoff interface if the classes are related. If you just have a
whole bunch of unrelated and seldom used COM classes, just leave them
in separate files.
CD Notes
• When executing the project on the accompanying CD, you will notice
that the tearoff interface is created using QueryInterface() and
destroyed immediately when the object is released.
Example 24 Writing an ATL COM Tearoff Server 217
//////////////////////////////////////////////////////
//////////////////////////////////////////////////////
II
import "oaidl.idl";
import "ocidl.idl";
[
object,
uuid(C177116E-9AAA-11D3-805D-000000000000), 7
dual,
helpstring("IWzd Interface"),
pointer_default(unique)
]
interface IWzd : IDispatch
{
[id(1), helpstring("method Method1")] HRESULT Method1([in]
long lArg);
};
[
object,
uuid(C177118E-9AAA-11D3-805D-000000000000),
dual,
helpstring("IWzdTear Interface"),
pointer_default(unique)
]
interface IWzdTear : IDispatch
{
[id(1), helpstring("method Method2")] HRESULT Method2([in]
long lArg);
218 Chapter 7: Writing COM Servers with ATL
};
[
uuid(C1771162-9AAA-11D3-805D-000000000000),
version(1.0),
helpstring("Server 1.0 Type Library")
]
library SERVERLib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");
[
uuid(C177116F-9AAA-11D3-805D-000000000000),
helpstring("Wzd Class")
]
coclass Wzd
{
[default] interface IWzd;
interface IWzdTear;
};
};
#ifndef __WZD_H_
#define __WZD_H_
/////////////////////////////////////////////////////////////////////
////////
// CWzdTear
class CWzd;
class ATL_NO_VTABLE CWzdTear :
public IDispatchImpl<IWzdTear, &IID_IWzdTear, &LIBID_SERVERLib>,
Example 24 Writing an ATL COM Tearoff Server 219
public CComTearOffObjectBase<CWzd>
{
public:
CWzdTear()
{
}
//DECLARE_REGISTRY_RESOURCEID(IDR_WZD)
//DECLARE_PROTECT_FINAL_CONSTRUCT()
BEGIN_COM_MAP(CWzdTear) II
COM_INTERFACE_ENTRY(IWzdTear)
// COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()
// IWzd 7
public:
STDMETHOD(Method2)(long lArg);
};
/////////////////////////////////////////////////////////////////////
////////
// CWzd
class ATL_NO_VTABLE CWzd :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CWzd, &CLSID_Wzd>,
public IDispatchImpl<IWzd, &IID_IWzd, &LIBID_SERVERLib>
{
public:
CWzd()
{
}
DECLARE_REGISTRY_RESOURCEID(IDR_WZD)
DECLARE_PROTECT_FINAL_CONSTRUCT()
220 Chapter 7: Writing COM Servers with ATL
BEGIN_COM_MAP(CWzd)
COM_INTERFACE_ENTRY(IWzd)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY_TEAR_OFF(IID_IWzdTear, CWzdTear)
END_COM_MAP()
// IWzd
public:
STDMETHOD(Method1)(long lArg);
};
#endif //__WZD_H_
Strategy
Not all communication between client and server is one-way. Sometimes
the client might want the server to call it when some event occurs. To do
this in COM, the client temporarily becomes a server itself implement-
ing its own COM object with a method that the server calls. The client
then registers a pointer to this object with the server for the server to call.
In the parlance of COM, a server that allows a client to register a “call-
back” object with it, is said to have a “connection point”.
Just as a COM server itself might have to support an early or late-bind-
ing interface, so must a connection point. Therefore, first we will show how
to add a connection point that supports late binding for Visual Basic and
Visual J++ clients using just the ClassView. After that, we’ll review how to
add a connection point that supports early binding for MFC clients, which
atypically for ATL requires a lot of manual editing using the ATL Object
Wizard, the Connection Point map, and a function you have to write your-
self.
Example 25 Writing an ATL COM Server that has a Connection Point 221
Steps
Add a Late-Binding Connection Point
When a sink interface is added to an MFC client, you have to create an
Interface Project to define just what methods the server can call in the
client (as shown in Example 9, page 143). But you can’t add an IDL file to a
VB application. So how can VB not only know the name of the interface
method to call but also the type of the calling arguments? Because you
define it in the COM server’s IDL file and VB or VJ++ accesses this defini-
tion through the server’s type library.
1. Create the COM Class as usual using the ATL Object Wizard, how- II
ever make sure to select the “Support Connection Points” property.
This causes the wizard to add a Connection Point map to the .h file of
your class and to also add an event interface to your IDL file that will
be defining the callback method(s) in the VB client.
2. Open the ClassView and right-click on this event interface and add
7
the definition of the method or methods that your VB or VJ++ client
will be supporting.
3. A new class will be created for your COM server project that will
contain the method(s) you added in the last step, although each name
will be prefixed with “Fire_”. You can now call this method(s) from
anywhere in this ATL COM class.
Note: If you look in the IDL file, you’ll notice that ATL only adds
this event interface to the type library and uses the “source”
attribute in the coclass declaration. The “source” attribute tells the
MIDL compiler that whatever COM server implements this inter-
face doesn’t have to implement this particular class. Instead, this
definition resides in the type library for VB to access when imple-
menting the sink in the client.
222 Chapter 7: Writing COM Servers with ATL
1. Create the COM Class as usual using the ATL Object Wizard, however
make sure to select the “Support Connection Points” property. This
causes the wizard to add a Connection Point map to the .h file of your
class. It also adds a late-binding interface for events that we will ignore.
2. Somehow include the Interface ID of the client’s sink COM class in this
.h file (please see Example 9, page 143, on how to create this class). In
other examples, we’ve included guids by copying them from the interface
server project and putting them in their own guids.h file like so:
#if !defined guids_h
#define guids_h
#endif
3. Locate the Connection Point map that was created in your ATL COM
class’s generated .h file and add a macro that will reference the client’s
Interface ID:
BEGIN_CONNECTION_POINT_MAP(CWzd)
CONNECTION_POINT_ENTRY(IID_IWzdSink)
END_CONNECTION_POINT_MAP()
4. At the top of this .h file, you will also need to add a class as seen below.
This class contains one method that can be called by any method of your
COM class and will loop through all clients that have registered them-
selves with your class:
template <class T>
class CProxyIWzdSinkEvents : public IConnectionPointImpl<T,
&IID_IWzdSink, CComDynamicUnkArray>
{
public:
Example 25 Writing an ATL COM Server that has a Connection Point 223
7. You can now call your client in any of your COM class’s methods with:
HRESULT hr=Fire_WzdSink(1234);
224 Chapter 7: Writing COM Servers with ATL
8. You may notice that the code you just added manually was added auto-
matically for you when creating a late-binding connection point. For a
complete listing of an example COM class that supports an early-binding
connection point, please refer to the listing at the end of this example.
Notes
• To connect to this class using VB or J++, see Chapter 8.
• The return trip from server back to client must follow all the same rules
as any COM object when implementing an early-binding connection
point. As an example, if you use argument types that aren’t supported by
the COM dll and you’re going across process boundaries, you must cre-
ate and register the proxy/stub dll as with any COM class. As usual,
late-binding interfaces take care of themselves.
CD Notes
• Please refer to the CD Notes under the clients that implement sinks in
Chapter 8.
#ifndef __WZD_H_
#define __WZD_H_
/////////////////////////////////////////////////////////////////////
////////
template <class T>
class CProxyIWzdSinkEvents : public IConnectionPointImpl<T,
&IID_IWzdSink, CComDynamicUnkArray>
{
public:
HRESULT Fire_WzdSink( long lArg )
Example 25 Writing an ATL COM Server that has a Connection Point 225
{
T* pT = (T*)this;
pT->Lock();
HRESULT ret;
IUnknown** pp = m_vec.begin();
while (pp < m_vec.end())
{
if (*pp != NULL)
{
IWzdSink *pWzdSinks =
reinterpret_cast<IWzdSink*>(*pp);
ret = pWzdSinks->Callback( lArg ); II
}
pp++;
}
pT->Unlock();
return ret; 7
}
};
/////////////////////////////////////////////////////////////////////
////////
// CWzd
class ATL_NO_VTABLE CWzd :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CWzd, &CLSID_Wzd>,
public IConnectionPointContainerImpl<CWzd>,
public IDispatchImpl<IWzd, &IID_IWzd, &LIBID_SERVERLib>,
public CProxyIWzdSinkEvents<CWzd>
{
public:
CWzd()
{
}
DECLARE_REGISTRY_RESOURCEID(IDR_WZD)
226 Chapter 7: Writing COM Servers with ATL
DECLARE_PROTECT_FINAL_CONSTRUCT()
BEGIN_COM_MAP(CWzd)
COM_INTERFACE_ENTRY(IWzd)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(IConnectionPointContainer)
END_COM_MAP()
BEGIN_CONNECTION_POINT_MAP(CWzd)
CONNECTION_POINT_ENTRY(IID_IWzdSink)
END_CONNECTION_POINT_MAP()
// IWzd
public:
STDMETHOD(Method1)(/*[in]*/ long lArg);
};
#endif //__WZD_H_
Strategy
As mentioned in the last chapter and in Chapter 3, aggregation is COM’s
weak version of class inheritance. Your COM object can’t access the “base”
class’s functionality as easily as in the classic sense, but it can use Query-
Interface() to get a pointer to the other object and access its functionality
that way.
As seen in the last chapter with MFC, aggregation is a matter of the
“derived” class automatically creating and releasing the “base” class.
Example 26 Aggregating a COM Object Using ATL 227
But you’ll find even with ATL’s ubiquitous macros that this job isn’t any eas-
ier than it was with MFC.
Steps
Create the “Base” COM Class
1. When using the ATL Object Wizard to create the COM class you’ll be
aggregating, make sure to specify “Yes” (the default) or “Only” when
picking an Aggregation option.
#endif
5. Add a final constructor to the class where you will create an instance of
the aggregated object:
HRESULT FinalConstruct( )
{
HRESULT hr=::CoCreateInstance(CLSID_WzdAggSvr,
GetControllingUnknown(), CLSCTX_ALL,
IID_IUnknown, (LPVOID*)&m_pUnknown);
return hr;
}
6. Add a final release to the class where you will release the aggregated
object:
void FinalRelease()
{
m_pUnknown->Release();
}
Note: You can also use the following aggregation macro and avoid step
5 altogether:
COM_INTERFACE_ENTRY_AGGREGATE_AUTO(CLSID_WzdAggSvr,IID_IWzdAggSvr,
m_pUnknown)
9. For an example listing of the aggregating class’s .h file, please refer to the
end of this example.
where iWzd is the pointer to the “derived” class and iWzdAgg is a pointer
to the “base” class.
3. You can then access the methods of these two classes as before:
iWzdAgg->Method1(1234,&lArg);
iWzd->Method2(lArg2, ulArg);
4. You must still release both objects — aggregation doesn’t even take that
burden away from you:
iWzd->Release();
iWzdAgg->Release(); // must still release aggregated interface
230 Chapter 7: Writing COM Servers with ATL
Notes
• Please refer to Example 16 (page 173) for other notes on aggregation.
CD Notes
• Open the test.dsw project and build the debug version. Place a break-
point in the OnTest() button handler and run the application. Then, step
through as both the aggregated and aggregating COM objects are cre-
ated and used.
#ifndef __WZDSRV_H_
#define __WZDSRV_H_
#include "guids.h"
/////////////////////////////////////////////////////////////////////
////////
// CWzdSrv
class ATL_NO_VTABLE CWzdSrv :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CWzdSrv, &CLSID_WzdSrv>,
public IDispatchImpl<IWzdSrv, &IID_IWzdSrv, &LIBID_SERVERLib>
{
public:
CWzdSrv()
{
m_pUnknown=NULL;
}
HRESULT FinalConstruct( )
{
HRESULT hr=::CoCreateInstance(CLSID_WzdAggSvr,
Example 26 Aggregating a COM Object Using ATL 231
GetControllingUnknown(), CLSCTX_ALL,
IID_IUnknown, (LPVOID*)&m_pUnknown);
return hr;
}
void FinalRelease()
{
m_pUnknown->Release();
}
DECLARE_GET_CONTROLLING_UNKNOWN()
II
DECLARE_REGISTRY_RESOURCEID(IDR_WZDSRV)
DECLARE_PROTECT_FINAL_CONSTRUCT()
BEGIN_COM_MAP(CWzdSrv) 7
COM_INTERFACE_ENTRY(IWzdSrv)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY_AGGREGATE(IID_IWzdAggSvr,m_pUnknown)
END_COM_MAP()
// IWzdSrv
public:
STDMETHOD(Method2)(/*[in]*/ long lArg, /*[in]*/ unsigned
long ulArg);
private:
LPUNKNOWN m_pUnknown;
};
#endif //__WZDSRV_H_
232 Chapter 7: Writing COM Servers with ATL
233
234 Chapter 8: Writing COM Servers with VB and VJ++
Strategy
Visual Basic is fully automated to create a COM server for you using the
ActiveX standard. You use the Studio to create the project for DLL or EXE
or a DLL that supports a control. You use the Visual Basic Class Builder to
add the COM classes, methods and events.
Steps
Create the Project
1. In the Visual Basic Studio, click on the “File” and “New” menu items to
open the New Project dialog box. Then, pick ActiveX EXE, DLL, or
Control.
that the first class added to your project won’t work with the VB Class
Builder — just click “OK”.
2. In the VB Class Builder, right-click on the new class and add methods,
properties, events, other classes, etc. as shown in Figure 8.1.
II
Notes
• The collateral advantage to creating COM classes from VB is that it adds
object-oriented programming to the Basic lexicon.
CD Notes
• To test the project on the CD, start the server first and tell it to start
when some client has created an instance of it. Then, start the client and
step through the server.
Strategy
Connection points and sinks are how non-ActiveX COM servers inform a
client when some event happens. Although COM servers written in VB can’t
themselves have a connection point (you can only create an ActiveX COM
server with VB), you do have the ability to “listen” to some other server’s
connection point using VB’s WithEvents attribute.
Steps
Add a Sink to Your VB Application
1. First, add the COM class to your VB project as usual using “Project/Ref-
erences...”
2. Then, declare this COM class globally (above all subroutines) as follows:
Dim WithEvents IWzdEvent As SERVERLib.Wzd
Example 28 Adding a Sink to a Visual Basic Client 237
4. Add the actual sink function (the function that the COM server will be
calling) by locating “(General)” in the Object combo box and the han-
dler name in the Procedure combo box. The handler name will be the
variable name you assigned the class and the callback’s method name.
The arguments are automatically determined from the type library:
II
Private Sub IWzdEvent_Callback(ByVal lArg As Long)
MsgBox "sink called"
End Sub
5. For a full listing of this, please refer to the end of this example.
Notes 8
• Because VB can only use COM objects that support late binding, this
sink also requires a COM server that supports a late-binding connection
point. To create an ATL connection point that supports late binding,
please refer to Example 25 (page 220).
CD Notes
• Create and register the server project first using the Developer’s Studio.
Then, bring the client up into the VB Studio and put a breakpoint on the
IWzdEvent_Callback function and click “Test” and notice that your VB
application was notified of an event by the COM server.
238 Chapter 8: Writing COM Servers with VB and VJ++
MsgBox "done"
End Sub
Private Sub Command2_Click()
End
End Sub
Private Sub IWzdEvent_Callback(ByVal lArg As Long)
MsgBox "sink called"
End Sub
Strategy
Just as with Visual Basic, the VJ++ Developer Studio makes it easy to create
a COM DLL server. We will be creating the project using the Studio’s “File”
menu command and we will be adding methods using the Class Outline
view.
Example 29 Writing a COM DLL Server Using Visual J++ 239
Steps
Create the COM DLL
1. Click on the “File” then “New Projects” to open the “New Project” dia-
log box. Click on the “New” tab and expand the tree view at the “Visual
J++ Projects” branch. Then, click on “Components”. Pick the “COM
DLL” in the right list control and click “Open”. The Studio will now cre-
ate your COM DLL project and add a class to it. All of the public meth-
ods of this class will automatically be exposed to a COM client that
creates this server.
2. To easily add methods to the created class, expand the project in the II
Project Explorer and double-click on the .java file. A Class Outline will
then appear in the left view which will contain a tree view that will list
this class’s name as one of its branches. Right-click on this name to open
a menu that will allow you to automatically add methods to your class as
shown in Figure 8.2.
3. Build the project as usual which will automatically register the DLL. Use
regsvr32.exe or the OLE/COM Object Viewer as usual to register the
DLL on another machine.
240 Chapter 8: Writing COM Servers with VB and VJ++
Notes
• As per usual, creating this COM server was a snap but at the price of
performance. This late-bound object will typically run 40% slower than
the equivalent COM server written in C++ with ATL.
Strategy
Connection points and sinks are how non-ActiveX COM servers inform a
client when some event happens. To add a sink to a J++ client, we start by
wrapping the COM server in a J++ class. Because you can’t stick an IDL file
in a J++ project, we get our sink interface from that class wrapper (which in
turn, got it from the COM server’s type library). We then use Java’s “imple-
ment” syntax to implement that interface in our client. And finally we use a
special Java/VJ++ helper class called ConnectionPointCookie to take it from
there.
Steps
Add a Sink to Your VJ++ Application
1. Include the COM package in your VJ++ class:
import com.ms.com.*;
2. Wrap the COM server class with the connection point as usual using the
“Project/Add COM Wrapper” menu commands.
3. One of the items extracted when VJ++ was wrapping the COM server
was the interface for sink we must implement. Add this implementation
Example 30 Adding a Sink to a Visual J++ Client 241
syntax to your J++ class like so (examine the generated wrapper to get
the exact name of the interface to implement):
public class Form1 extends Form implements server._IWzdEvents
{
: : :
6. At some point, create the COM server as usual and then create an 8
instance of the ConnectionPointCookie class. You can’t do this back-
wards because the ConnectionPointCookie class needs to refer to the
COM server:
// create server
server.Wzd wzd=new server.Wzd();
// connect to server's connection point
cookie = new ConnectionPointCookie(
wzd, //source of callbacks
this, //destination of callbacks
server._IWzdEvents.class); //interface class
7. The COM server can now call this J++ class’s “Callback()” method.
8. To see this class in its entirety, please refer to the end of this example.
Notes
• This example will only work with a COM server that supports late
binding, not only for regular method calls, but also for its connection
point. As seen in Example 25 (page 220), you can easily add a
242 Chapter 8: Writing COM Servers with VB and VJ++
late-bound connection point to an ATL COM server. I’m afraid I’ve left
you on your own for MFC.
CD Notes
• When executing the project on the accompanying CD, you will notice
that the “Callback” method is called right after “wzd.Method1” is called.
/**
* This class can take a variable number of parameters on the command
* line. Program execution begins with the main() method. The class
* constructor is not invoked unless an object of type 'Form1' is
* created in the main() method.
*/
/**
* Form1 overrides dispose so it can clean up the
* component list.
*/
Example 30 Adding a Sink to a Visual J++ Client 243
COM Communications
In a perfect world, the communication that goes on between a client and its
COM server would be no different than any class method call, and a client
written in any language could freely exchange its data in its native format
with a COM server written in any other language. In reality, COM does
take care of the brunt of the work it used to take to communicate between
applications and machines. However, it depends on you to carefully config-
ure your IDL file to tell it what to do. And if you go outside of its repertoire
of data types it knows how to send and receive, you also need to provide it
with your own proxy/stub DLL to help it out. It also can’t really get over the
hurdle of transparently converting a data type that’s native to one language
if it just doesn’t have an exact equivalent in another.
The examples in this chapter include:
245
246 Chapter 9: COM Communications
Strategy
In this example, we will simply be reviewing the different dances you must
perform in order to get your data to your COM server, from IDL syntax to
argument types to how to extract the data at both ends.
Steps
Pass Simple Variable Data
To pass chars, shorts, unsigned shorts, longs, unsigned longs, enumerations,
floats, and doubles:
1. In the IDL file, use:
// enumerator (above any interface definitions)
typedef enum {Monday=2, Tuesday, Wednesday, Thursday, Friday} workday;
: : :
method([in] unsigned char cArg, [in] short sArg, [in] unsigned
short usArg, [in] workday enumArg, [in] long lArg, [in]
unsigned long ulArg,[in] float fArg, [in] double dArg);
Example 31 Passing Data to a COM Object Using C++ 247
Note: To use this section, just find the argument type you need and its
different incarnation between .idl, .cpp, and .h files.
1. In the IDL file for in’s and out’s and the use of pointers, use:
[in] long lArg1,[out] long *lArg2,[in,out] long *lArg3
2. In client, use:
long lArg1=123;// 123 will be passed to method
// but lArg1 will not receive anything back
long lArg2=456; // 456 will be not be passed to method,
// but will be overwritten by method on return
long lArg3=789; // 789 will be passed to method
// and will also be overwritten by value returned by method
pPtr->Simple5(lArg1,&lArg2,&lArg3);
//note that returned arguments must pass their addresses
Note: Even if you are ignoring an argument for a particular call, you
must still NULL out that argument if it’s been declared in the IDL file
as a pointer ([out] or [in,out]). If there’s garbage in an argument,
COM will attempt to transmit whatever that garbage is pointing to.
OR
Array4([in] long lFirst, [in] long lLength, [in] long lSize,
[in,first_is(lFirst),length_is(lLength),size_is(lSize)] long *aArg);
where lFirst is the first element to transmit, lSize is the size of the
entire array and you can use either lLast to specify the last element to
send or lLength to specify how many actual elements to send starting at
lFirst.
OR
pPtr->Array4(lFirst, lLength, lSize, aArg);
OR
STDMETHODIMP CWzd::Array4(long lFirst, long lLength, long lSize, long
*aArg)
{
for (int i=lFirst;i<(lFirst+lLength);i++)
{
int j=aArg[i];
}
return S_OK;
}
1. To pass a structure using COM, you need to define that structure at the
top of the IDL file as seen here, then use the declaration below. As for
COM classes, because all COM classes are derived from the IUnknown
class, use that argument type:
// define structure above any interface definitions:
typedef struct 9
{
long lElement;
long *pPointer;
float fElement;
} MYSTRUCT;
: : :
[in] MYSTRUCT myStruct, [in] IUnknown *myClass
2. The structure you defined in your IDL file will be added to the .h file or
type library along with the definitions of the methods in the COM server,
so you can just start using it in your client like so:
MYSTRUCT myStruct;
myStruct.pPointer=new long[12];
252 Chapter 9: COM Communications
Please see the next example for how to pass your COM pointer in a mul-
titasking environment.
float fFloat[2];
[case(25)]
double dDouble;
[case(27)]
MYSTRUCT myStruct;
[default]
long lLong;
} MYEUNION;
: : :
[in] long lType, [in,switch_is(lType)] MYEUNION myEUnion
pPtr->EUnions1(lType,myEUnion);
return S_OK;
}
There are three attributes you can apply to a memory pointer argument:
ref, unique, and ptr:
1. “Ref” is the least overhead to COM. It just passes the memory pointer
and any data it points to and the server promises not to change or deallo-
cate the memory.
2. “Unique” is the next least overhead and the default to COM. This time
the server can change the memory or even deallocate it, but the client and
server promise that this is the only pointer to this particular chunk of
memory so that COM doesn’t have to worry about reconciling what
multiple pointers do to that chunk of memory while in the server before
it sends it back to the client.
3. “Ptr” is the most overhead to COM, but the most transparent to you.
You don’t have to worry much about anything except making sure this
argument is NULL if you’re not using it.
4. In the IDL file, use one of the following attribute combinations:
HRESULT MemPtr1([in,ref] MYSTRUCT *pStruct);
HRESULT MemPtr2([in,out,unique] MYSTRUCT **pPtr);
HRESULT MemPtr3([in,out,ptr] MYSTRUCT **pPtr1,[in,out,ptr]
MYSTRUCT **pPtr2);
HRESULT MemPtr4([in,out] MYSTRUCT **pPtr1,[in,out] MYSTRUCT
**pPtr2);
Note: You can’t use LPVOID to define your memory pointer because
COM needs to know exactly how big an argument you’re sending.
5. In the client, use one of these matching argument types. In this example,
we’re sending a structure allocated using ::CoTaskMemAlloc():
MYSTRUCT *pMyStruct=
(MYSTRUCT*)::CoTaskMemAlloc(sizeof(MYSTRUCT)
pPtr->MemPtr1(pMyStruct);
pPtr->MemPtr2(&pMyStruct);
Example 31 Passing Data to a COM Object Using C++ 255
MYSTRUCT *pMyStruct2=pMyStruct;
pPtr->MemPtr3(&pMyStruct,&pMyStruct2);
MYSTRUCT *pReturned;
pPtr->MemPtr4(NULL, &pReturned);
return S_OK;
}
STDMETHODIMP CWzd::MemPtr3(MYSTRUCT **pStruct1,MYSTRUCT **pStruct2)
{ 9
(*pStruct1)->lElement=123;
(*pStruct2)->lElement=456;
return S_OK;
}
STDMETHODIMP CWzd::MemPtr4(MYSTRUCT **pStruct1,MYSTRUCT **pStruct2)
{
return S_OK;
}
VB variables are stored in variants. And when your COM object is return-
ing a value as seen here, you need to specify it as a retval:
Myval = server.method()
• SafeArrays
HRESULT VBVars2([in] SAFEARRAY(BYTE) *pSA1, [out]
SAFEARRAY(BYTE) *pSA2);
• Variants
HRESULT VBVars3([in] VARIANT VAR1, [out] VARIANT *pVAR2);
• Retvals
"myval=server.method()"
HRESULT VBVars4([out,retval] long *pVal);
• SafeArrays
// create safearray and initialize with data
SAFEARRAY *saMySafeArray=
::SafeArrayCreateVector(
VT_UI1, // type (VT_UI1 == one byte unsigned
integers (bytes)
0, // lower index of array (can be negative) II
100); // size
LPBYTE lpByte=NULL;
::SafeArrayAccessData(
saMySafeArray, //safearray vector from above
(LPVOID*)&lpByte); //pointer
lpByte[0]=12;
lpByte[1]=34;
::SafeArrayUnaccessData(
saMySafeArray); //safearray vector from above
pPtr->VBVars2(&saMySafeArray); 9
// retrieve the data
::SafeArrayAccessData(
saMySafeArray, //safearray vector from above
(LPVOID*)&lpByte); //pointer
BYTE by=lpByte[0];
::SafeArrayUnaccessData(
saMySafeArray); //safearray vector from above
• Variants
// convert a variable to a variant using the C++ class "_variant_t"
long lLong=14;
bstrtMyString1=L"test"; //reusing a _bstr_t class
_variant_t vartMyVariant1(lLong);
_variant_t vartMyVariant2(bstrtMyString1);
// convert a safearray to a variant using V_xxx macros (other
types can be found in OLEAUTO.H)
saMySafeArray=::SafeArrayCreateVector( VT_UI1,0,100);
VARIANT varMyVariant3;
VariantInit(&varMyVariant3); //initialize to no type
V_VT(&varMyVariant3)= VT_ARRAY | VT_UI1;
V_ARRAY(&varMyVariant3)=saMySafeArray;
pPtr->VBVars3(&vartMyVariant1,&vartMyVariant2,&varMyVariant3);
// convert a variant back to a variable using "_variant_t"
lLong=long(vartMyVariant1);
bstrtMyString1=_bstr_t(vartMyVariant2);
// convert safearrays using V_xxx macros (other types can be
found in OLEAUTO.H)
if (V_VT(&varMyVariant3)==(VT_ARRAY | VT_UI1))
saMySafeArray=V_ARRAY(&varMyVariant3);
::SafeArrayDestroy(saMySafeArray);
• Retvals
long val=pPtr->VBVars4();
// same exact method call without the IWzdPtr wrapper method
pPtr->raw_VBVars4(&val);
• SafeArrays
SAFEARRAY** pSA
for both input and output — completely different from IDL syntax.
Example 31 Passing Data to a COM Object Using C++ 259
• Variants
VARIANT VAR1, VARIANT* pVAR2
• Retvals
long *pRetVal
Notes
• If the MIDL compiler balks at your selection of argument types because
they aren’t supported by OLE Automation, you can still use those types
but you must build and register the proxy/stub DLL for your server. That II
is unless your COM server lives in a DLL that will be used directly by
your client (no MTS/COM+, no DLLHost) and your server won’t be using
COM to keep it thread safe in a multitasking application. Of course, if
your client is using OLE Automation (late binding), you better pick argu-
ment types it likes.
• If you forget and don’t build the proxy/stub DLL and your server does
need it, this is the error message you’ll get, “nothing”. In fact, COM will
just try to muddle along without. As an example, in the case of a simple
array, COM will send over the first element and then call it a day. You’ll
spend hours of fun playing around with your array attributes until you
finally decide to build the proxy/stub DLL and register it on the client 9
and server machines.
• The _bstr_t and _variant_t classes used above require you to include
comdef.h in your source. The USES_CONVERSION macro requires atlconv.h.
• For more on this, please refer to Chapter 2.
CD Notes
• When executing the project on the accompanying CD, watch as the argu-
ments are sent between client and server. There’ll be a lot of MIDL warn-
ings when building this project — that’s because we present all IDL
argument types, not just those types supported by Automation.
260 Chapter 9: COM Communications
Strategy
As discussed in Chapter 3, COM can be used in some situations to provide
thread-safety for your COM objects. In other words, you don’t need to use
critical sections, etc. to protect your data against simultaneous access by
multiple threads. COM does this by using marshalling your method calls if
you try to access a COM object outside of your thread. If, however, you
create a COM object in your own thread, your method calls will not be
marshalled. But what happens if you pass that COM object pointer to
another thread? That un-marshalled pointer which won’t keep your data
thread safe? If you want to pass such a pointer you can use two COM API
calls to add marshalling to that pointer. The API calls are:
CoMarshalInterThreadInterfaceInStream() to pack up the pointer before
the call, and CoGetInterfaceAndReleaseStream() to extract the pointer on
the other side. Actually, COM doesn’t necessarily add marshalling to the
pointer — even though you bothered to type in that whole API call. COM
first determines whether or not marshalling is necessary and if not, the
pointer may pass through unmodified.
Steps
Pack the Pointer up for the Call
1. To pass a COM object pointer, use :CoMarshalInterThreadInterface-
InStream() to first put it in stream format like so:
LPSTREAM lpStream=NULL;
hr=::CoMarshalInterThreadInterfaceInStream(
__uuidof(IWzd),//interface ID of pointer we’re passing
pPtr.GetInterfacePtr(),//pointer to be passed
&lpStream //returned stream variable
Example 32 Passing Interface Pointers Between Threads Using C++ 261
);
if (FAILED(hr))
{
_com_error err(hr);
AfxMessageBox(err.ErrorMessage());
return;
}
Notes
• Please see Chapter 3 for much more on how COM can be used to auto-
matically make your COM objects thread safe.
CD Notes
• When executing the project on the accompanying CD, you will notice
that the pointer is unmarshalled in the main application, but marshalled
in the thread.
Strategy
In this example, we will simply be reviewing the subset of COM argument
types you can safely use with a Visual Basic client, from IDL syntax in the
COM server to the VB argument types they correspond with.
Steps
Pass Simple Data Types
To pass bytes, integers, longs, singles (floats), and doubles:
1. In VB, use:
Dim cArg As Byte
Dim sArg As Integer
Dim lArg As Long
Dim fArg As Single
Dim dArg As Double
cls.method(cArg, sArg, lArg, fArg, dArg)
Example 33 Passing Data Between C++ and Visual Basic 263
2. In the IDL file of the server for in’s and out’s, use:
[in] BSTR bstr
9
[out] BSTR *pBSTR
Pass Arrays
To pass a VB array, which in C++ is a SafeArray, use:
1. In VB use:
Dim aArg(1 To 25) As Byte
cls.method(aArg)
2. In the IDL file of the C++ server for in’s and out’s, use:
[in] SAFEARRAY(BYTE) sa
[out] SAFEARRAY(BYTE) *pSA
264 Chapter 9: COM Communications
Pass Variants
To pass variants, which is a VB variable that you don’t type or type as a
Variant:
1. In VB, use:
Dim vArg As Variant 'or any untyped argument
cls.method(vArg)
Return Values
To get a returned variable from the method call (you’ll see below):
1. In VB, use:
Dim lArg as Long
lArg = cls.method()
2. In the IDL file, use (at the end of the line of arguments!):
[out,retval] long *plArg
Notes
• You don’t need a proxy/stub DLL to talk to VB or VJ++ because the
COM DLL that implements the COM API knows how to send a stan-
dard set of argument types and it finds out what arguments are in your
method calls from your COM server’s type library.
• Don’t worry about deallocating binary strings or SafeArrays passed to
your COM object from VB. In most cases, VB will clean these up itself
and will, in fact, get upset if you do it first.
• For more notes, please refer to the end of Example 31 (page 246).
Strategy
A data collection in this case is the VB variable type that allows you to do
this in VB: 9
For Each MyObject In MyCollection
' use MyObject, which could be a safearray, BSTR, etc.
Next
Steps
Write the Data Collection Server
A data collection server will generally have five methods to which you can
add other custom methods. The Item() method return the data object at an
index. The Add() method adds a new object to the collection. The Init()
method can initialize the maximum size of the collection — or you can just
make Add() smart enough to keep appending the list. The Count() method
returns the current number of elements in the collection. And the
_NewEnum() method is used by VB in the For…Next loop we saw above.
The IDL file for this server looks like this:
HRESULT Item([in] VARIANT Index, [out, retval] LPVARIANT pItem);
HRESULT Add([in]SAFEARRAY(unsigned char)* NewObj);
HRESULT Count([out, retval] long *pVal);
HRESULT Init([in]long numObjs);
HRESULT _NewEnum( [out, retval] LPUNKNOWN* pVal);
1. Copy the Data Collection Server from the CD and modify it for your
application. A sample implementation can be found in the listing at the
end of this example.
2. Create the collection server inside of your real server and initialize it to
the number of objects it will contain:
IWzdCollectionPtr pPtr;
pPtr.CreateInstance(__uuidof(WzdCollection));
pPtr->Init(
3 // number of objects to add
);
Example 34 Passing Data Collections Between Visual C++ & Visual Basic 267
3. Add those objects to the collection. In this case, we’re adding SafeArrays:
for (int i=0;i<3;i++)
{
// create object (safearray, BSTR, etc.)
LPBYTE lpByte;
SAFEARRAY *pSA=::SafeArrayCreateVector(VT_UI1,0,10);
::SafeArrayAccessData(pSA,(LPVOID*)&lpByte);
lpByte[0]=(BYTE)i;
::SafeArrayUnaccessData(pSA);
pPtr->Add(&pSA);
}
II
4. Before returning the collection, don’t forget to detach it from the smart
pointer — otherwise the smart pointer will release it before it even gets
back to your VB client:
*pCollection=pPtr.Detach();
3. Define the collection server just before you call the real server:
Dim MyCollection As WzdCollection
4. And also define a variable that will contain each object as it comes out of
the collection:
Dim MyObject
5. Use your real server to get the collection server (and its contents):
rVal = IWzdSrv.GetCollection(MyCollection)
268 Chapter 9: COM Communications
6. To access individual items in the collection, you can use the Item()
method:
MyObject = MyCollection.Item(0)
/////////////////////////////////////////////////////////////////////
////////
// CWzdCollection
typedef
CComObject<CComEnum<IEnumVARIANT,&IID_IEnumVARIANT,VARIANT,
_Copy<VARIANT> > > EnumVar;
if (m_inx<m_numObjs)
{
m_var[m_inx].vt = VT_ARRAY | VT_UI1;
m_var[m_inx].parray = *ppNewObj;
m_inx++;
return S_OK;
}
return E_FAIL;
}
// return count
STDMETHODIMP CWzdCollection::get_Count(long *pVal) II
{
*pVal=m_numObjs;
return S_OK;
}
// return enum
STDMETHODIMP CWzdCollection::get__NewEnum(LPUNKNOWN *pEnumObjs)
{
EnumVar *pVar=new EnumVar;
pVar->Init(&m_var[0],&m_var[m_numObjs],NULL,AtlFlagCopy); 9
pVar->QueryInterface(IID_IUnknown,(void**)pEnumObjs);
return S_OK;
}
// cleanup on destroy
void CWzdCollection::FinalRelease()
{
270 Chapter 9: COM Communications
if (m_var)
{
for (int i=0; i<m_inx; ++i)
{
SafeArrayDestroyData(m_var[i].parray);
m_var[i].vt = VT_EMPTY;
}
delete []m_var;
}
}
Strategy
In this example, we will simply be reviewing the subset of COM argument
types you can safely use with a Visual J++ client, from IDL syntax in the
COM server to the VJ++ argument types they correspond with.
Steps
Pass Simple Data Types
To pass chars, shorts, integers, floats, and doubles into C++:
1. In VJ++, use these argument types:
char byArg='a';
short sArg=1234;
int lArg=1234567890;
float fArg=1.234f;
double dArg=1.23456789;
wzd.method(byArg,sArg,lArg,fArg, dArg);
Example 35 Passing Data Between C++ and Visual J++ 271
2. In the C++ server’s IDL file, use the matching argument from here:
9
[in] long lArg1,[out] long *lArg2,[in,out] long *lArg3
OR
int lLength=3;
wzd.method(lFirst, lLength, lSize, aArg);
OR
[in] long lFirst, [in] long lLength, [in] long lSize,
[in,first_is(lFirst),length_is(lLength),size_is(lSize)] long
*aArg);
OR
long lFirst, long lLength, long lSize, long *aArg
where the elements from lFirst to lLast are sent and the array, aArg, is
lSize elements big. Or in the second case, lFirst is the first element sent
and lLength is the number of elements after that are sent.
Pass BSTRs
A BSTR argument type is the binary string type that’s native to Visual Basic.
However it’s also implemented in VJ++ as a String class:
1. In VJ++, use:
String bstr="Test";
String[] pBSTR=new String[1];
wzd.method(bstr, pBSTR);
Pass SafeArrays
SafeArrays are VB’s native format for arrays. In order to pass a SafeArray to
VJ++, we need to include the Microsoft COM package for a special SafeAr-
ray class:
1. In VJ++, use:
import com.ms.com.SafeArray;
: : :
SafeArray psa = new SafeArray(Variant.VariantByte,100);
psa.setByte(0,(byte)12);
wzd.method(psa);
Pass Variants
The Variant class is also included with the Microsoft Java package, which
allows VJ++ to accept a variant argument from a COM server:
1. In VJ++, use:
9
import com.ms.com.Variant;
Variant pVAR1=new Variant(1234);
Variant pVAR2=new Variant(bstr);
Variant pVAR3=new Variant(psa);
wzd.method(pVAR1, pVAR2, pVAR3);
Notes
• By comparing this example with the Visual Basic example earlier in this
chapter, you can also figure out how to pass data between a VB client
and a VJ++ COM object. Some pundits suggest this as the ideal configu-
ration — a simple to program front end using VB using robust VJ++
COM objects.
CD Notes
• Create both the VJ++ and VC++ projects. Tell the VC++ project, which is
an ATL COM DLL, to use the VJ++ exe to run it when debugging. You
can configure this in the “Debug” tab of the project settings in the “Exe-
cutable for debug” edit box. Then, set a breakpoint in each method of
the ATL COM object and watch as the VJ++ application sends it data.
10
Chapter 10
COM+ Examples
COM+ represents the frontier of COM development. Contrary to what its
name implies, COM+ isn’t an overall enhancement of COM but rather an
enhancement to a certain area of COM development. On the other hand,
some would insist it makes the rest of COM irrelevant, but even its stron-
gest supporters will be out there writing COM NT Services or regular COM
objects when needed.
What area does COM+ enhance? For the complete story, please turn
back to Chapter 4, but in general, COM+ is targeted towards database
applications that require lots of scalability (read: can support ten thousand
clients as easily as one). In fact, some would consider COM+, which is only
available on Windows 2000, to simply be the next version of the Microsoft
Transaction Server (MTS), a service available from the NT Options pack
that runs on NT. Because we’re in that delicate transition period between
NT and Windows 2000, I have included examples for both in this chapter.
One of the more striking characteristics you will find in the following
examples is that they contain little or no code you need to write yourself.
Instead you are simply activating functionality that COM+ has available to
your object. When the options outnumber the lines of code, you’re talking
about “attribute” programming. Soon 99% of every book on computers
275
276 Chapter 10: COM+ Examples
will be on how to setup your options and your code will be size lines of
Basic.
The examples in this chapter include:
Strategy
In order for your COM DLL server to take full advantage of the MTS or
COM+ surrogate environment, you need to add support for two objects to
your server: IObjectContext and IObjectControl. By using the methods of
IObjectContext, you are able to take advantage of MTS/COM+’s transac-
tion support that allows you to commit or rollback transactions to multiple
databases over multiple objects. And by implementing IObjectControl in
your own server, you’re able to take advantage of COM+’s object pooling
functionality.
Example 36 Writing an MTS or COM+ Server Using ATL 277
Steps
Creating the Project and Class
1. Click on the Studio’s “File” and “New” menu commands to open the
New dialog. Then, pick “ATL COM AppWizard” and enter a project
name to open the ATL AppWizard.
2. You can only create a dll project. You must also pick “Support MTS”. If II
you plan on having any non-standard argument types in your methods
(e.g., structures, or see list in Chapter 2), then also select “Allow merging
of proxy/stub code” to make your life a lot easier. (You won’t have to
register your MTS/COM+ dll server and your proxy/stub dll on each
machine.) You can also add MFC support, but MTS/COM+ objects
don’t tend to have any user interface involvement and the other MFC
classes like CList can substituted for their smaller Standard Template
Library (STL) equivalents.
3. To add a class to this project, click on the Studio’s “Insert” and “New
ATL Object” menu commands to open the ATL Object Wizard. From
the list of objects, pick the “MTS Transaction Server”. (This is with v6.0
of the Studio. Version 7.0 will mention COM+.)
4. You can now name your MTS/COM+ class (remember it can’t be the 10
same name as the project). On the “MTS” tab, you can add support to
your server to be usable by early- and late-binding clients. Just leave it as
“Dual”. Another option is to support “IObjectControl”. This option
adds an implementation to your DLL server for the IObjectControl class
which is what COM+ uses to determine and control your server in a
pooled object environment. This option is meaningless in MTS because
MTS doesn’t support object pooling.
2. When dividing your functionality up into methods, try to put all related
activity into individual methods. Methods should be written like really
smart stored procedures that get in, do a couple of database transactions,
and leave. Don’t save anything in a member variable for next time,
because your object will be destroyed immediately upon return to the cli-
ent anyway. And at the end call SetAbort() or SetComplete() to respec-
tively rollback or commit your changes to the database. Your method
should look something like this:
STDMETHODIMP CWzd::Method1(long lArg)
{
HRESULT hr=S_OK;
try
{
// use ADO to access database
}
catch(_com_error& e)
{
hr=e.Error();
}
catch (...)
{
hr=E_FAIL;
}
m_spObjectContext->SetComplete();
}
return hr;
}
Notes
• You should also add some type of error reporting capability to your class
along the lines of Example 46 (page 333). When you have objects flying
around in a release environment, you could have sporadic bugs happen-
ing that could never be traced otherwise.
• Notice in the listing above that we first check if m_spObjectContext is
null before using it. This member variable of your class is assigned to 10
your object’s IObjectContext (IObjectContext actually aggregates your
object). But outside of MTS or COM+, such as when debugging your
class, there won’t be any IObjectContext and your class will blow up.
• Don’t worry about adding critical sections to your method calls for
thread-safety if you’re using ADO or COM+. If you’re using ADO to
access your database(s), ADO will synchronize your updates. And
COM+ pretty much forces you to use their synchronization support
which is equivalent to putting your entire transaction into one big critical
section.
• To use this object, first register it with the correct environment for your
operating system (MTS for Windows NT or COM+ for Windows 2000).
Then, create and use the object from any client the same way you would
280 Chapter 10: COM+ Examples
use any COM object. Please see Example 2 (page 110) for how to create
the object using smart pointers.
/////////////////////////////////////////////////////////////////////
////////
// CWzd
HRESULT CWzd::Activate()
{
HRESULT hr = GetObjectContext(&m_spObjectContext);
if (SUCCEEDED(hr))
return S_OK;
return hr;
}
BOOL CWzd::CanBePooled()
{
return TRUE;
}
void CWzd::Deactivate()
{
m_spObjectContext.Release();
}
try
{
// use ADO to access database
}
catch(_com_error& e) II
{
hr=e.Error();
}
catch (...)
{
hr=E_FAIL;
}
Strategy
We will be using the Microsoft Management Console to register the COM
DLL server we created above with MTS. MTS comes with the Windows NT
Option pack for Windows NT. If you will be using your COM server with
COM+, please refer to the next example. For Windows 9x, forget about it.
Steps
Open the Microsoft Management Console
1. The Windows NT Option pack installation puts a new menu item in
your system’s start menu. Underneath this item you will find the
“Microsoft Transaction Explorer” which opens the “Microsoft Manage-
ment Console” or MMC. The reason they’re not called the same thing is
that the MMC was intended to support several management applications
that “snap-in” to the MMC.
Note: The MMC has a user-hostile interface, perhaps due to its snap-in
nature. You must fully select a branch with the left cursor before you
can right-click on it to get the correct popup menu.
2. Once the package has been added, locate it in the list of packages and II
right-click on it. Then, from the popup menu, select “Properties”. From
the “Security” tab of this property sheet, you can change the Package
Identity you set above. From the “Activation” tab, you can pick what
type of package this is: Server or Library. Please see Figure 10.1 for a
review of these steps.
3. Next we will add our COM DLL server to the package we just created.
Locate the “Component” branch under your new package and
right-click on it. Select “New” to open the Component Wizard, which
will lead you through the intuitive process of adding your COM DLL.
Along the way, you are given the choice of adding a new component or
one that is already registered on your system. If you used ATL to create
your object on a machine, it will have already been registered using
regsvr32. However, in most cases, you should just locate the actual DLL
10
filename on the system so that there’s no mistake as to which one you
install.
284 Chapter 10: COM+ Examples
4. After you’ve added your COM DLL to a package, you must still config-
ure how you want MTS to support its transactions. Locate your new
COM DLL under the package’s components and right-click on it. Select
Example 37 Registering a Server with MTS 285
Strategy
We will be using COM+’s Component Services snap-in to register the COM
DLL server we created above. COM+ only exists on Windows 2000. For
Windows NT, you will need to use the Microsoft Transaction Server (MTS)
discussed in the last example. For Windows 9x, you will need to write your
own DLL surrogate. II
Steps
Open the Component Services snap-in
1. Unlike MTS, COM+ is part of the operating system. And its administra-
tor application is now located in your system’s control panel settings
under “Component Services”.
2. If you’re at all familiar with MTS registration, COM+ isn’t much differ-
ent. The biggest difference is that you can specify whether an application
is a Server or a Library in the creation process rather than through its
properties later.
3. To add your COM DLL to an application, locate the “Components”
branch under your new application and right-click on it. (Note: You
must fully select a branch with the left mouse button before you can
right-click on it to get the correct popup menu.) Then, click on “New
Component” to browse your machine for the DLL filename. See
Figure 10.3.
4. If you’re familiar with MTS, you’ll notice a new button that allows you
to register an “Event Class”. Please see Example 39 (page 294) for more
on this.
5. For an overview of adding a DLL server to COM+, please refer to
Figure 10.4.
6. Once added to an application, you can endow your object with optional
functionality from COM+ by editing its properties. To edit a dll’s proper-
ties, locate it in the Component Services’ tree view and right-click on it.
Then, select “Properties”.
7. You have the same transaction support as MTS (see the last example),
plus the ability to set a timeout so that your server application doesn’t
hang the system. You can also enable your object to be pooled, provide it
with arguments when it’s created by COM+, and tell COM+ to create it
when the client asks for it to be created.
8. You also have the ability to ask COM+ to make sure your object is
thread safe under the “Concurrency” tab. Because there’s very little over-
head for this feature and it has the effect of placing your object and any
object it calls into one big critical section, you should always leave it
enabled.
9. For a review of the steps to configure your COM+ object, please see
Figure 10.5.
Example 38 Registering a Server with COM+ 289
II
10
290 Chapter 10: COM+ Examples
II
10
doctor role and a nurse role, such that a doctor who logs in will be able
to access more functionality than a nurse.
2. Once a role is created, locate the “Users” branch underneath it and
right-click on it to start adding users to it. The users you will be adding
come straight out of your system’s User Manager.
3. After a role has been added to an application, you can go back to each
server and even each method of a server and use this role to protect that
server or method. To protect a method, locate it from under a server
component and right-click on it. Then, select “Properties” and click on
the “Security” tab. You will find your roles listed there. Check mark all
roles that are authorized to call that method.
4. For a review of making a method secure, please see Figure 10.6.
Notes
• Actually, the security we implemented above is the last line of defense. In
a well-designed system, an unauthorized user wouldn’t even get to this
point because the user interface would forbid it (e.g., a login system con-
nected to an alarm system). COM+’s protection is intended to prevent
unauthorized access to a more savvy user.
• With COM+ comes the new threading model: “Neutral”. For more on
this new model, please refer to Chapter 4. However, Neutral is not in the
vocabulary of v6.0 of the Studio. Until v7.0 of the Studio makes it possi-
ble otherwise, you will need to manually edit your project’s .rgs file and
replace “Both” with “Neutral”. Then, rebuild and register.
Example 38 Registering a Server with COM+ 293
II
10
294 Chapter 10: COM+ Examples
Strategy
Although this functionality is related to connection points and sinks as laid
out in Chapter 2, the event server support in COM+ is more like an auto-
mated way to create and call several other COM+ objects when some event
occurs. We start by creating a dummy event class which has the interface
and methods that will be called but nothing else. We then create some real
COM classes that pattern their interface and methods after this event class,
but this time, actually implement some functionality. We tell COM+ about
the event class and about the other classes that are based on it. And when-
ever someone creates and calls the event class, COM+ takes over and creates
and calls the rest.
In COM+ terminology, the classes that pattern themselves after the event
class are called “subscribers” and the entity that creates and calls the event
class is called the “publisher”.
Steps
Create and Register the Event Class
1. Use ATL Wizards to create an MTS/COM+ class as laid out in the first
example in this chapter. Add methods to this class that you would like to
be able to call when some event happens.
2. Register this class with COM+, but this time we use the new “Event
Class” button. You will then be prompted to browse for the event class’s
DLL.
3. After the class has been registered, open its properties by right-clicking
on it. Under the “Advanced” tab of the property sheet, you are given the
option to allow all subscribers to be informed simultaneously or sequen-
tially waiting for each method call to complete before the next one is
called. You are also given the chance to make this event class available to
other objects in the same application. For this, you will also need to spec-
ify an id to use by the subscribers.
Example 39 Using the COM+ Event Server 295
II
10
3. Locate the “Subscription” branch under this new server, right-click it,
and ask to create a new subscription. Tell COM+ you want to use all
interfaces for this event, select the ProgID of the event class, and give the
subscription a name.
4. For a review of these steps, please refer to Figure 10.8.
Notes
• Anyone from a VB client to another COM+ object can now create and
call the event class’s methods and COM+ will do the rest.
Strategy
All along we’ve assumed that when a client wants to talk to a COM server,
the machine it’s sitting on is available. But what about the case of a laptop
computer that might go days without being connected to any network that
would have a COM server available. You could write your own queuing
facility to store up data and then use COM to send it in when connected.
However, objects running in the COM+ environment can use COM+ to
store up its data instead.
Essentially, the client in COM+, rather than creating the COM server
directly, creates a special COM+ object instead called QC.Recorder. The cli-
ent then talks to this object as if it were the real thing until it’s done. Once
released, this special object unloads every method call the client made to it
into a special message that’s stored in a queue supported by Microsoft Mes-
sage Queue (MSMQ), a service that’s independent of COM. When the lap-
top is finally connected to a network, MSMQ automatically locates the
machine the real server is on and sends the message there. The COM+ envi-
ronment on the target machine can be setup to listen for new messages and
will grab this one when it comes in. COM+ then uses QC.Player to create
and replay everything the original client did on the laptop.
And you get all of this functionality with little change to your client code
and no change to your server.
Example 40 Writing and Using a COM+ Queued COM Server 297
II
10
298 Chapter 10: COM+ Examples
Steps
Configure the Queued Server
1. Within the Component Services, right-click on the application that con-
tains the queued server (the real server the client wants to talk to). In the
“Queuing” tab, checkmark both items. This causes COM+ to create the
QC.ListenerHelper to monitor the MSMQ for any new queued mes-
sages.
2. You will also need to tell COM+ that this interface by opening its prop-
erties sheet and selecting “Queued” on the “Queuing” tab.
3. COM+’s QC.ListenerHelper will now monitor MSMQ for this applica-
tion. To review, please see Figure 10.9.
II
10
300 Chapter 10: COM+ Examples
3. COM+ gets the destination computer’s name from how you registered
the dll on the client system. However, you can also specify the computer
name in the CoGetObject() call’s binary string like so:
L"queue:ComputerName=mycomputer/new:Server.Wzd
Notes
• Please see Chapter 4 for more on queued components. You may feel a lit-
tle leery about storing up your data as meaningless messages in MSMQ.
However, COM+ makes a tremendous effort to get this data to the real
server even making use of a dead-letter office. And using COM+ is a lot
easier than the alternative — such as maintaining a mini-database on the
laptop and what that entails.
11
Chapter 11
Accessing Database
Objects
Eventually Microsoft hopes to replace the entire Win32 API with COM
objects. Instead of creating a window by calling the global function
::CreateWindow(), you would create a COM object which has a method
called CreateWindow() among others that you would call. (Of course, to
create the COM object you would still have to call the static function
::CoCreateInstance(), but you’ve got to start somewhere.) One of the first
API functions to be replaced by COM has been the ODBC API that allows
universal access to data sources available to your application. The COM
objects that were written to take over this functionality are collectively
called OLE DB. And these objects were so versatile, so COM-like, and so
basic that it was immediately realized that they were just too complicated to
use by anyone other than their original programmers. So a new layer of
COM classes was written to help simplify and encapsulate the functionality
of OLE DB and these classes were collectively called Active Data Objects or
ADO.
What you immediately find with ADO is that there’s about a hundred
ways to do anything. You can open a database, then create a command
301
302 Chapter 11: Accessing Database Objects
object to open a recordset object, or you can just open a command object
and then open a recordset or you can just create a recordset object and spec-
ify the database and table right in its argument list. In this chapter, I give but
one fairly comprehensive way to open a database and examine its records.
And since ADO also supports late binding, I try to duplicate this compre-
hensive approach in Visual C++, Visual Basic, and Visual J++.
The examples in this chapter therefore include:
Strategy
Although there are dozens of ADO classes and methods, 10% of those
classes and methods are used 90% of the time. We will therefore review just
the basic everyday functionality you need — open the database, get a
recordset and scroll through it, etc. In fact, if you have my previous book
(VC++ MFC Extensions by Example, 1999, R&D Books), the example here
is just the ADO version of the ODBC and DAO examples I show there.
Example 41 Accessing a Database Using C++ and ADO 303
Steps
Import ADO’s Definitions
1. Import the type library for ADO into the source file in which you will be
using ADO:
#import "c:\Program files\Common files\System\ADO\MSADO15.dll"
rename("EOF", "_EOF")
using namespace ADODB;
Note: You also need to add _WIN32_DCOM to your project settings under
“Preprocessor definitions” in order to get the prototype definition for
::CoInitializeEx() included in your compile.
rs->PutCollect(L"CustomerID",L"BONCO");
rs->PutCollect(L"CompanyName",L"Franks");
rs->PutCollect(L"ContactName",L"Runk");
rs->Update();
3. Retrieve any output parameters back from this stored procedure call if
the parameter direction is adParamOutput, adParamInputOutput, or
adParamReturnValue. In this example, we will retrieve from the zeroth
parameter in the stored procedure call: 11
long vt=command->GetParameters()->GetItem(_variant_t(0L))
->GetValue().vt;
or retrieve as a variant
_variant_t vart=command->GetParameters()->GetItem(_variant_t(0L))
->GetValue().Detach();
308 Chapter 11: Accessing Database Objects
2. Add input parameters only, one for each “?” in the stored procedure
argument list:
command->GetParameters()->Append(command->CreateParameter(
_bstr_t(""),// a user assigned parameter name (optional)
adInteger, // data type. other common ones:
: : :
Use Transactions
If your COM server will be used in an MTS/COM+ application, please refer
to Example 36 (page 276) for how to use transactions. However, outside of
that framework, ADO itself supports transactions as seen in this section.
1. To start a transaction, use:
connection->BeginTrans();
each error, because ADO stores each and every error in a data collection.
(Of course, each error will probably be enigmatic, still….)
1. To catch and scroll through ADO errors:
try
{
...using ADO COM objects...
}
catch(_com_error& e)
{
ErrorsPtr pErrors=connection->GetErrors();
if (pErrors->GetCount()==0)
{
II
AfxMessageBox(e.ErrorMessage());
}
else
{
for (int i=0;i<pErrors->GetCount();i++)
{
_bstr_t desc=pErrors->GetItem((long)i)
->GetDescription();
AfxMessageBox(desc);
}
}
}
catch(...)
{
} 11
Notes
• This example depends on you configuring your database on your
machine using ODBC32 in the Control Panel. You can also use the “ Pro-
vider=” keyword to cause ADO to bypass ODBC entirely. In the “Open
a Database” section previously in step 2, you would use “Provider=xxx;
Data source = xxx.mdb”, where xxx.mdb is the actual mdb filename.
310 Chapter 11: Accessing Database Objects
However, in order for this syntax to work, xxx must a valid ProjID of a
COM object that can take the place of ODBC. Microsoft provides such a
COM object for both Access databases and SQL Server databases. (For
Access, it is: Provider=Microsoft.JET.OLEDB.4.0; Data source =
xxx.mdb.) Please refer to your database vendor for information on any
Provider COM objects they might supply.
• Let’s talk about blobs for a second. For a binary blob, use an ADO data
type of adLongVarBinary, use an Access database type of OLE Object,
and use a SafeArray in your application to contain it. For a string blob,
which can be as big as a binary blob but can only contain alphanumeric
characters (no zeros), use an ADO data type of adLongVarWChar, an
Access database type of Memo, and a BSTR data type in your applica-
tion. And don’t forget to destroy the SafeArray or BSTR when you’re
done with them.
• As mentioned, there are thousands of permutations of how to accom-
plish anything with ADO. Microsoft’s ADO web site is loaded with a few
of these: http://www.microsoft.com/data/ado.
CD Notes
• First, use ODBC to setup the dummy Access database that comes with
this example. When executing the project, set a breakpoint in the
OnTest() button handler and step through as ADO accesses that data-
base.
Strategy
Step-by-step we will almost duplicate everything we did in the last example
using C++, only now with a Visual Basic spin. That means no need to worry
about the fact that you’re using a COM object.
Example 42 Accessing a Database Using Visual Basic and ADO 311
Steps
Define the ADO Classes
1. As with any COM server, use the “Project/References” menu commands
to add ADO to your project. The server itself is called: Microsoft
ActiveX Data Objects Library.
2. Define the ADO classes to be used:
Dim cn As New ADODB.Connection
Dim cmd As New ADODB.Command
Dim rs As New ADODB.Recordset
Dim prm As ADODB.Parameter
II
Access a Recordset
11
1. To get a recordset from the data source using SQL, use:
rs.Open "SELECT * FROM CUSTOMERS", cn, adOpenForwardOnly,
adLockPessimistic
CompanyName = rs("CompanyName")
ContactName = rs("ContactName")
rs.MoveNext
Wend
5. Retrieve an out parameter after the stored procedure is executed (if the
parameter direction is adParamOutput, adParamInputOutput, or adParam-
ReturnValue):
vt = rs(0) ‘returns parameter 0
2. Add input parameters only, one for each “?” in the stored procedure call
(see above):
Set prm = cmd.CreateParameter("", adInteger, adParamInput, 5, 1234)
cmd.Parameters.Append prm
Use Transactions
1. To start a transaction, use:
cn.BeginTrans
Notes
• Please see the notes under the last example.
CD Notes
• First, use the ODBC32 Control Panel Applet to setup the example’s mdb
file with a DSN of “WZDDB”. Then, set a breakpoint at the top of the form
routine and step through as it accesses the database.
Example 43 Accessing a Database Using Visual J++ and ADO 315
Strategy
Step-by-step we will almost duplicate everything we did in the last example
using C++ only now with a Visual J++ spin. That means no need to worry II
about the fact that you’re using a COM object.
Steps
Define the ADO Classes
1. Import the ADO class definitions into any source file that will be using
ADO:
import com.ms.com.*;
import com.ms.wfc.data.*;
2. Create the ADO COM objects for connecting and issuing commands to
the data source:
com.ms.wfc.data.Command command=new com.ms.wfc.data.Command();
com.ms.wfc.data.Connection connection=
new com.ms.wfc.data.Connection();
11
//(other ex:"MSOracle8")
"", // user id
"" // password
);
// initialize command object
command.setActiveConnection(connection);
Access a Recordset
1. To get a recordset from the data source using SQL, use:
com.ms.wfc.data.Recordset rs =new Recordset();
// specify SQL command
command.setCommandText("SELECT * FROM CUSTOMERS");
command.setCommandType(AdoEnums.CommandType.TEXT);
// what text in command specifies. also:
// adCmdStoredProc - stored procedure
// adCmdTable - table name
// adCmdFile - file name
// adCmdTableDirect -
// adCmdUnknown - unknown
// open record set
rs.open(command, // source of record set--can be
// a SQL statement, etc.--see last param
null, // active connection object (optional)
AdoEnums.CursorType.FORWARDONLY,// cursor type. also:
// adOpenForwardOnly
// adOpenKeyset Opens a keyset-type cursor.
// adOpenDynamic Opens a dynamic-type cursor.
// adOpenStatic Opens a static type cursor.
AdoEnums.LockType.OPTIMISTIC,
// record set locking type. also:
// adLockReadOnly -- read only
// adLockPessimistic --lock records at the
// data source when editing
Example 43 Accessing a Database Using Visual J++ and ADO 317
2. Add input parameters only, one for each “?” in stored procedure call
above:
command.getParameters().append(command.createParameter(
"", // a user assigned parameter name (optional)
AdoEnums.DataType.INTEGER, // data type.
AdoEnums.ParameterDirection.INPUT,// direction.
5, // database column size for this parameter
// required by SP (must be a long)
new Variant(1234) //if adParamInput or
// adParamInputOutput, the input 11
// value as a variant
));
Use Transactions
1. To open a transaction, use:
connection.beginTrans();
3. To rollback a transaction:
connection.rollbackTrans();
Note: Sometime in the future, you will hopefully be able to write VJ++
COM servers for MTS/COM+. At that point, you would also be able
to use their transaction services instead of these.
Notes
• Please see the notes for the first example in this chapter.
CD Notes
• First use the ODBC32 Control Panel Applet to setup the example’s mdb
file with a DSN of “WZDDB”. Then, set a breakpoint at the top of the form
routine and step through as it accesses the database.
II
11
322 Chapter 11: Accessing Database Objects
Potpourri
This is the “everything else” chapter and COM has a lot of those. Because
your work can be potentially a part of some other application, at what
point would you check to see if that application has a license to run your
server? There’s also the whole subject of reporting errors back to your cli-
ent, especially when there might be several layers of COM servers between
where the error occurred and where the client made the first call. And
finally, there’s the arcane subject of the “free-threaded marshaller” — the
most talked about subject you’ll never use.
The examples in this chapter include:
323
324 Chapter 12: Potpourri
Strategy
The MFC class COleObjectFactory makes it very easy to add licensing logic
to your COM server. You just override its VerifyUserLicense() member
function and return false if the client doesn’t have a license. The major prob-
lem, however, is being able to derive a class from COleObjectFactory. The
name is declared statically inside of an MFC macro forcing you to rewrite
that macro. Fortunately, it’s a small macro.
Steps
Write the COM Server
1. Create your MFC COM class as usual using the ClassWizard.
2. To the top of the generated .h file of this COM class, add your own deri-
vation of the MFC COleObjectFactory class as follows:
class CWzdOleObjectFactory : public COleObjectFactory
{
public:
CWzdOleObjectFactory( REFCLSID clsid, CRuntimeClass*
pRuntimeClass,
BOOL bMultiInstance, LPCTSTR lpszProgID ) :
Example 44 Licensing Your COM Object Using MFC 325
COleObjectFactory(clsid,pRuntimeClass,bMultiInstance,
lpszProgID)
{};
}
5. Substitute these macros for the current macros. In this same .h file, find
the DECLARE_OLECREATE macro and rename it to DECLARE_OLECREATE_WZD.
In the .cpp class file, replace the IMPLEMENT_OLECREATE macro with
IMPLEMENT_OLECREATE_WZD.
326 Chapter 12: Potpourri
6. For a complete listing of this .h file, please refer to the end of this exam-
ple.
Notes
• You might think this strategy a bit lame considering someone could just
copy the license file with the COM DLL or EXE file. So you might make
it more secure by adding a lot more logic to VerifyUserLicense(). Per-
haps make the license file expire or base the license on some facet of the
machine it’s installed on.
CD Notes
• Build the project and put a breakpoint in VerifyUserLicense() and
watch as the license is checked before the object is created.
/////////////////////////////////////////////////////////////////////
////////
// Implement our own derivation of COleObjectFactory class factory
#define DECLARE_OLECREATE_WZD(class_name) \
public: \
static AFX_DATA CWzdOleObjectFactory factory; \
static AFX_DATA const GUID guid; \
/////////////////////////////////////////////////////////////////////
////////
// CWzdSrv command target 12
// Attributes
public:
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CWzdSrv)
public:
virtual void OnFinalRelease();
//}}AFX_VIRTUAL
BOOL VerifyUserLicense();
// Implementation
protected:
virtual ~CWzdSrv();
DECLARE_MESSAGE_MAP()
DECLARE_OLECREATE_WZD(CWzdSrv)
END_INTERFACE_PART(WzdClass)
};
/////////////////////////////////////////////////////////////////////
////////
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations
immediately before the previous line.
#endif // II
!defined(AFX_WZDSRV_H__4487D433_A6FF_11D3_A398_00C04F570E2C__IN
CLUDED_)
Strategy
ATL makes it a lot easier to add licensing support to your COM server
using one of its ubiquitous macros DECLARE_CLASSFACTORY2(). However,
you still need to write your own licensing class.
Steps 12
2. Manually add a new class to the top of the created object’s .h file that
looks like this:
class CWzdLicense
{
protected:
static BOOL VerifyLicenseKey(BSTR bstr)
{
// compare bstr with embedded license info
return TRUE; // valid license
}
7. Please refer to the end of this example for a complete listing of this .h
file.
Example 45 Licensing Your COM Object Using ATL 331
Notes
• Please see the notes under the last example.
CD Notes
• Build the project and put a breakpoint in VerifyUserLicense() and
watch as the license is checked before the object is created.
II
#ifndef __WZD_H_
#define __WZD_H_
/////////////////////////////////////////////////////////////////////
////////
// CWzdLicense-- a class that checks on licensing
class CWzdLicense
{
protected:
static BOOL VerifyLicenseKey(BSTR bstr)
{
// compare bstr with embedded license info
return TRUE; // valid license
}
12
static BOOL GetLicenseKey(DWORD dwReserved, BSTR* pBstr)
{
*pBstr = L"licence";// embedded license info
return TRUE;
}
{
// open license file and compare with license key info
embedded in this class
return TRUE; //TRUE==licensed
}
};
/////////////////////////////////////////////////////////////////////
////////
// CWzd
class ATL_NO_VTABLE CWzd :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CWzd, &CLSID_Wzd>,
public IDispatchImpl<IWzd, &IID_IWzd, &LIBID_SERVERLib>
{
public:
CWzd()
{
}
DECLARE_CLASSFACTORY2(CWzdLicense)
DECLARE_REGISTRY_RESOURCEID(IDR_WZD)
DECLARE_PROTECT_FINAL_CONSTRUCT()
BEGIN_COM_MAP(CWzd)
COM_INTERFACE_ENTRY(IWzd)
COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()
// IWzd
public:
STDMETHOD(Method1)(long lArg);
};
#endif //__WZD_H_
Example 46 Processing COM Errors 333
Strategy
This example shows a little bit of everything. We catch the COM error class
_com_error, which is thrown by smart pointer classes and ADO. We write
our own user-defined COM error codes using a COM macro. And we use
two COM API calls, SetErrorInfo() and GetErrorInfo(), to pass a IEr- II
rorInfo COM object from our server to our client for much more robust
error reporting.
Steps
Catch Thrown Errors
1. To catch COM errors thrown by smart pointers and other COM classes
that use the _com_error class, use:
try
{
retval=pPtr->Method1(1234);
}
catch (_com_error &err)
{
AfxMessageBox(err.ErrorMessage());
}
2. When using a smart pointer to access a COM server, the server methods 12
you call are actually smart pointer methods which check for a COM fail-
ure. When a COM failure is found (FAILED()), this method wrapper then
throws an exception using the _com_error class. To avoid this check and
potential throw, you can access the unadulterated server method directly
by calling it directly with:
hr=pPtr->raw_Method1(1234,&retval);
3. Or you could prevent any method from being wrapped by using the
raw_interfaces_only keyword when you use the #import directive at
the top of the listing.
If you make the first argument to this macro a zero (0), and return it to your
client, it will pass the FAILED() test, but still inform the client of something.
A classic use of this is to tell a client when the server has reached the end of
a recordset or a file.
1. When an error occurs, create your own error message like so:
ICreateErrorInfo *cei;
HRESULT hr = CreateErrorInfo(&cei);
IErrorInfo *eip;
hr = cei->QueryInterface(IID_IErrorInfo, (LPVOID*) &eip);
if (!FAILED(hr))
{
cei->SetDescription(L"Something bad happened.");
// the error in human form
cei->SetSource(_bstr_t(__FILE__));
// where it happened
cei->SetHelpContext(23);
II
// plug into help file to summon a
cei->SetHelpFile(L"help.hlp");
// particular passage
SetErrorInfo(0, eip);
eip->Release();
}
cei->Release();
2. Then return a failure to let the client know that something bad happened
so it knows to look for this message:
return WZD_ERROR3;
Even though you’ve returned an error, how does a client know that you
have also created an IErrorInfo object for it? Unless you’re writing both
server and client, you need to provide some way for the client to ask the
COM server if it has an IErrorInfo object for it. The official way to do this
is to include an ISupportErrorInfo COM class in your COM server. This
class has but one method: InterfaceSupportsErrorInfo() that returns a
COM failure if your COM server doesn’t. You can automatically add this
class to your COM server in ATL:
1. When creating the COM class using the ATL Object Wizard, specify
“Support ISupportErrorInfo”. This simply adds the ISupportErrorInfo
COM class to your server with an implementation that says, “Yes”. It
does nothing else for you.
2. In the client, when you receive an error, create a new safe pointer from
the object’s pointer for ISupportErrorInfo like so:
ISupportErrorInfoPtr sei(pPtr);
where pPtr is the smart pointer for the COM object that had the error.
This is the first chance for the client to determine whether the COM
server has created an IErrorInfo — if the QueryInterface() that this
smart pointer performs fails, the COM server doesn’t support it.
3. Then, just before using GetErrorInfo() to retrieve the IErrorInfo
object, check ISupportErrorInfo’s one method:
hr=sei->InterfaceSupportsErrorInfo(__uuidof(IWzd));
if (hr==S_OK)
{
// retrieve IErrorInfo
: : :
}
where IWzd is the interface of the COM server your using. Even if the
COM server does create the IErrorInfo object, it might not have done it
for this error. The InterfaceSupportsErrorInfo() checks to see if the
server used SetErrorInfo() and if not, a failure is returned. And GetEr-
roInfo() can only be called once.
Example 47 Turning off Marshalling for a “Both” COM Object Using MFC 337
Notes
• Some clients such as a web browser will automatically display the error
message in your IErrorInfo object.
• Using GetErrorInfo() and SetErrorInfo() only works for the current
thread. That means your client won’t be able to grab an IErrorInfo
object created in an out-of-process EXE or DLL, and if it was created by
a COM object in another thread of your application. So rather than use
GetErrorInfo() and SetErrorInfo(), you might consider passing the
IErrorInfo object along yourself in your method argument list. Or you
might send it to some error handler service, such as an NT service, that
will log your errors. Or you might even consider replacing IErrorInfo
with your own custom error information object. II
CD Notes
• When executing the project on the accompanying CD, notice that the
error message created in the server is passed to the client.
Strategy
As mentioned in Chapter 3, COM can be used to automatically make your
COM servers thread safe. One way that COM does this is by marshalling
your COM server’s methods, even though the server is a DLL in the same 12
process space as its client. Because marshalling is the exact same process
that goes on when COM sends data to a COM server that’s in another
application or another country, this thread safety comes at the price of some
additional processing overhead. So if you’ve already made your COM
server thread safe (e.g., by adding critical sections to your code), you don’t
need to incur this performance hit. Why would COM ever marshal your
server when it doesn’t need it? If you configure it as a “Both” threading
338 Chapter 12: Potpourri
Note: Using the free-threaded marshaller with a COM server that will
be out-of-process makes no sense — your COM server’s methods must
be marshalled for the original purpose of marshalling, just to get your
data from the client to the server.
Steps
Use the Free-Threaded Marshaller
1. Follow Example 16 (page 173) for MFC aggregation — except the COM
class you’ll be aggregating will be a system COM class.
2. At the point where you would use ::CoCreateInstance() to create the
object to aggregate, use the following instead:
::CoCreateFreeThreadedMarshaler(GetControllingUnknown,&pUnknown);
Notes
• As neat as this one is, you’ll probably never use it if you do things right.
Example 48 Turning off Marshalling for a “Both” COM Object Using ATL 339
Strategy
Refer to the last example and Chapter 3 for more on the “free-threaded
marshaller”. Adding one to your ATL COM server is just a setting you need II
to make when you use the ATL Object Wizard.
Steps
Use the Free-Threaded Marshaller
1. Create the ATL server as usual with the ATL AppWizard.
2. Create the ATL COM class as usual with the ATL Object Wizard, except
on the second tab make sure to specify “Free Threaded Marshaller”.
3. That’s it — this object will only be marshalled if it’s out-of-process.
Notes
• It’s been a pleasure. I’ll see you all at the beach.
12
340 Chapter 12: Potpourri
COM Tables
Table A.1 Sending Simple Message Types
What to send IDL type C++ type VB type VJ++ type
Unsigned 8 bits char char Byte char
Unsigned 8 bits boolean bool (1) (1)
Unsigned 8 bits byte BYTE (1) (1)
Unsigned 16 bits unsigned unsigned (1) (1)
short short
Signed 16 bits short short Integer short
Unsigned 32 bits unsigned unsigned (1) (1)
long long
Signed 32 bits long long Long int
Unsigned 64 bits unsigned unsigned (1) (1)
hyper hyper
Signed 64 bits hyper hyper (1) (1)
32-bit floating point float float Single float
341
342 Appendix A: COM Tables
A
346 Appendix A: COM Tables
COM Troubleshooting
Guide
347
348 Appendix B: COM Troubleshooting Guide
Table B.1
Symptom Possible Problem Solutions
HRESULT returned is The client isn’t calling Call CoInitInstance()
0x800401f0 CoInitInstance(), which from all of your threads,
must be called from an John.
application and any thread
created by the application
before it can use a COM
function (Co…) except for
the CoTaskMemAlloc()
family of calls.
HRESULT returned is The COM server’s DLL or If the server is a DLL, use
0x80040154 EXE hasn’t been registered “regsvr32 xxx.dll” to
on your system. Even when register the DLL, where xxx
the DLL or EXE you want to is the name of the DLL. If an
access is running on another EXE, usually just executing
machine, you must still reg- the EXE once is enough to
ister it on your machine and register it. Whenever possi-
then use OLEVIEW to redirect ble, let the system automati-
your system to the other sys- cally register your COM
tem. When first working servers for you.
with COM, this will be the
root of most of your prob-
lems.
HRESULT returned is The COM class you’re try- If you’re not using smart
0x80004002 ing to create doesn’t exist in pointers, double check the
this DLL or EXE or it’s Interface ID against the
being identified with the value in the IDL file of the
wrong Interface ID. COM server. If you’re using
smart pointers, make sure
the type library you’re
importing isn’t out-of-date,
or make a point of import-
ing from the dll or exe file
directly.
You’re debugging and when The number of arguments in Make sure the type library
you try to step into a method the method that your client you’re importing isn’t
call you get this error: “The is calling is different then out-of-date and that the dll
value of ESP was not prop- what the server is expecting. or exe file that is registered
erly saved across a function on your system is the one
call.” your client application is
importing.
COM Troubleshooting Guide 349
B
350 Appendix B: COM Troubleshooting Guide
Your particular application may present you with unique problems. One
of the worst feelings in the world is remembering you ran across a problem
before but forget how you fixed it, or you wrote it down but forget where
you put it. Here’s a place to write it down — just don’t lose the book.
B
352 Appendix B: COM Troubleshooting Guide
353
354 Index
L
H Late Binding 25
HKEY_CLASSES_ROOT 10 late binding 21Ð22, 25Ð26, 32, 34, 158, 212,
221
Launch Permissions 59
I Licensing 60, 324, 329
load balancing 76, 90Ð91
IClassFactory 24, 107
LoadLibrary() 5Ð6, 11
IDispatch 21, 24, 34Ð36, 41, 115Ð116, 118Ð
120, 128, 130, 149, 162, 208, 210, 212, 217,
219Ð220, 226, 228, 231, 332
IDL file 18Ð19, 31Ð32, 87, 144, 214
M
basic formats 32 marshalling 64, 69, 200, 217, 260, 337Ð339
33, 35, 38, 41Ð42, 145Ð146, 196, 210, 212, MDI application 159
217, 226, 231, 246Ð251, 253, 256, 263Ð Memory Pointers 42, 253
264, 266, 271Ð272, 342 Microsoft Foundation Class (MFC) 24Ð25, 46,
60, 143, 147, 324
356 Index
N
NDR macros 32 R
Network Data Representation (NDR) 32 reference count 7, 15
regsvr32.exe 25, 198, 201, 239
Release() 15, 17, 20, 26, 107Ð110, 149, 156,
O 164Ð165, 176Ð177, 181, 183, 228Ð229,
Object Linking and Embedding (OLE) 4Ð5, 231, 280, 335
58Ð59, 80, 152, 154, 167, 172, 179Ð181, Remote Procedure Call (RPC) 3, 6, 12, 32, 64
195, 198, 201, 213, 239, 259, 301, 310, 325, remote server 107Ð108
328 router machines 90
Object Pooling 87, 89 RPC API 32
Object Security 58 RPCNSS.EXE 32
ODBC API 301 RPCSS.EXE 29
OLE Controls 5 Running Object Table (ROT) 23
OLE DB 301
OLE/COM Object Viewer 58Ð59, 80, 201,
239 S
OLE2 4 SafeArrays 39, 44, 255, 265, 273
OLE32.DLL 4, 14Ð15, 20Ð21, 29, 31Ð32, 36, scaling 76
38, 105, 111, 117, 124, 342 SDI application 23, 25
oleview.exe 58 Security 57Ð60, 87, 97, 290, 293
OnCreateAggregates() 56, 175, 179, 182 Service Control Manager (SCM) 29
33, 35, 38, 41, 95, 146, 248, 256, 263Ð264, SetAbort() 86, 98, 278, 281
266, 271Ð272 Single Threaded Apartment (STA) 71
out-of-process 31, 60, 64, 66, 69, 71, 73, 81, Single Use class 23
145, 198, 200, 337Ð339, 351 Singleton 22, 166Ð167, 213
Sink 163, 165, 236, 238, 240, 242
smart pointers 16, 18Ð19, 57, 104, 110, 121,
P 177, 209, 216, 280, 298, 333, 348
packages 282Ð283, 287 sockets 3
pipes 3 Software Foundation (OSF) 8
Process Boundaries 14 Stateless Programming 83
Index 357
stored procedures 77, 278 W
subscribers 294
Window Sockets 76
Synchronization 65, 69, 92
windows messaging 3
system registry 6, 9Ð10, 23, 60, 69, 99, 157,
Windows NT/2000 58, 73, 207
201, 207
T
tearoff
interface 214
tearoff server 214
Thread Safety 23, 61Ð65, 70Ð71, 85, 343Ð345
Threading Models
Apartment 71
Both 104
Thread-safety 62
Three Tier Architecture 77
Transactions 83, 285, 308, 313, 320
Two Tier Architecture 78
type library 16Ð17, 20, 31, 34, 36, 50Ð51,
113Ð115, 117, 122Ð123, 214, 217, 221,
237, 240, 251, 265, 303, 348
U
Universally Unique Identifier (UUID) 8
UNIX 4
V
VARIANT 37, 44, 256, 258Ð259, 264, 266,
268Ð269, 273, 343
Variant 255
Variants 44
VerifyUserLicense() 60Ð61, 324Ð328, 331
virtual table (VTBL) 35, 54, 64, 66
Visual Basic 5, 25, 37, 43, 234, 262, 310
argument types 43
Visual C++ 5, 128Ð130, 153, 173, 179, 265,
302, 329
Visual J++ 5, 31, 37, 46, 49, 51, 119, 135,
137Ð138, 141, 162, 194, 212, 220, 233,
238, 240, 242, 270, 302, 315
This Page Intentionally Left Blank
_______ / _______
Visual C++ MFC Programming
by Example
by John E. Swanke
Programming Industrial
Strength Windows
by Petter Hesselberg
Supercharge MFC
by Jeffrey Galbraith