This page is a collection of informationabout programming COM using C, C++, and maybe Java (later, but not now). It seeks to present a step-by-step tutorial on creating simple COM objects using Visual C++ (Version 4+). It's intended to be somewhat sparse on the conceptual end of things, but heavy on the practical examples of how to go about COM Programming. I wrote this after trying to build a COMponent and found it difficult not only to get the thing actually built, but even simply finding decent documentation about it. Hopefully, this tutorial will help make it easier for others to figure this stuff out, too.
Please note that while I"ve tried to make this information understandable to everyone who can program in C/C++, it is (at this point) mostly a collection of chunks of info about COM/OLE/ActiveX, without a lot of effort spent on making transitions between them. If I revise this, I'll put more work into providing a smoother read.
Table Of Contents | |
Overview of COM, and some of it's basic concepts | |
The tutorials themselves (in C, C++ at this point) | |
Further reading and resources | |
An overview of COM | |
A COM FAQ |
(Or, stuff that I wish I had found out earlier, and should have, since they're not difficult)
What is COM?
COM stands for Component Object Model, and is a binary standard for getting pieces of code (components) to interact with each other, it's layered on top of DCE RPC. Since COM is a binary standard, it's language-independent, and we can write COM objects and clients in C, C++, Java, Visual Basic, and anything else that supports COM. Essentially it's a form of object-oriented programming, since one creates objects, then invokes methods on them. It's built it into Windows (which is where most people do COM programming), but will soon be available on other platforms (Software AG has already released a port of DCOM to Solaris and Linux, with (many) more on the way).
COM = OLE = ActiveX. (more or less)
COM (Component Object Model), OLE ( formerly Object Linking and Embedding, now it's no longer an acronym), and ActiveX are the same thing. The only substantial difference that I'm aware of is that ActiveX objects are intended to be transfered across the networks (ie, the Internet), and so are optimized to be small in size.OLE is what grew out of inter-application data transfer (ie, how to get an Excel spreadsheet into a Word document), and tends to deal with those issues. COM is the architecture that underlies both ActiveX and OLE.
COM transparantly handles method invocation
Note that one uses COM to handle the details of getting method calls from clients to servers. Because COM handles these details, it's possible for the method calls (messages) to be in the same address space (in process, or "in-proc"), different address spaces on the same computer ("local"), or different address spaces on different computers ("remote")(this last case is handled by Distributed COM, or DCOM). The nice thing is that COM does this (pretty) transparantly, so that we could create an object with intention of running it as a local object, but move it to a remote computer later without having to change the code. This might not be optimal, but it'll work.
Reference Counting
Since COM was originally intended to be used in C++, we don't have garbage collection (yet -- COM+ is a new runtime component that may offer garbage collection), but we still need a way to keep track of when objects should be deallocated. Ideally, we'd like the objects to delete themselves, because multiple clients can use the same object, simultaneously, without knowing that the others are using it. If one client were to delete the object while the other clients were still trying to use the object, there would be massive problems. We solve this problem with reference counting -- the object itself keeps a count of the number of clients using it (who are refering to it, thus the name), and when that count is decremented to 0, it deletes itself. Each time a client starts using an object, it increments a private, internal reference count within the object (by calling the public AddRef method on that object). When the client is finished using on object, it decrements the private, internal count on the object ( by calling the public Release method on that object). When the reference count is decremented to 0, the object calls delete on itself, and is removed from memory. It's a pretty snazzy way to solve the problem of trying to figure out when to delete the object, but can be tough to debug. Also, once the object has executed the delete this statement, the object should be careful to not access any member variables.
Calling Convention
Once you look at the code, you'll note that all the methods have the __stdcall declaration. The __stdcall declaration specifies how arguments are pushed onto the stack when a method is invoked. Since you want your COM object to interoperate with other COM objects, you want to make sure that both your object and other COM objects use the same calling convention. Declaring them to use the __stdcall calling convention insures that your components call functions in the way that other components expect you call them. If you're components won't interact with anything else, then this doesn't matter (this is why everything will work fine if you compile a component and a client, and forget to specify __stdcall with both of them).
Interfaces
We'll ask COM to create a certain object for us, and then manipulate the object by invoking the object's methods. Related
methods are grouped together to form an "interface', which serves to allow clients access to the object's functionality without
exposing implementation details. Clients are able to ask an object whch interfaces it offers for use by calling the
QueryInterface method that every object has. Once an interface is released to the public, it should NEVER be changed,
becuase it's impossible to predict who is depending on the interface to be the way it is. And if the interface changes, some
existing code somewhere could be broken. If you wanted a previously released interface to behave differently in the next
version of your program you'd have to create a new interface, though the new interface could simply be an extension of the
old one, with new methods tacked on to do whatever it is you left out of the first version.
Interfaces are not objects! It's very easy to get these two concepts confused, especially when you're first starting at this stuff.
Remember to keep the concept of interface ( which is essentially a specification of how one object interacts with another) separate
from the concept of object (the code that actually implements the interfaces)
IUnknown
Next, we're going to briefly look at some specific interfaces that every object will have to worry about:: IUnknown and IClassFactory. First, IUnknown. Every object must support IUnknown, as it provides the interface for reference counting (AddRef and Release methods), as well as a mechanism for asking an object if it supports an arbitrary interface. The interface looks like the following:
HRESULT QueryInterface( REFIID iid, //Identifier of the requested interface
void ** ppvObject //Indirect pointer to the object );ULONG AddRef(void);
ULONG Release(void);
Once you've written the code for your object, you'll need something to control the creation of it. While this might sound redundant, it's actually very useful. If you want to create a single object that will service all requests, you can have the class factory create only a single instance of the object. If you want to do load-balancing among objects, the class factory may be the place to pick which object to use. Also, since COM is language independent, this allows one to create an object without knowing anything about the specifics of how a particular language wants to allocate objects (for example, when one creates COM objects in C, the class factory has to fill in the vtable manually, fake a constructor by initializing variables, etc). Note that class factories are in 1-to-1 correspondence with COMponents: one (and only one) class factory creates a certain type of COM object. The actual IClassFactory interface looks like:
IClassFactory: IUnknown
{
HRESULT QueryInterface( REFIID iid, void ** ppvObject );
ULONG AddRef(void);
ULONG Release(void);
HRESULT CreateInstance( IUnknown * pUnkOuter, REFIID riid, void ** ppvObject );
HRESULT LockServer( BOOL fLock );
};The first three methods are IUnknown, while the last two are what actually do work. CreateInstance asks the classfactory to create a new instance of the object that the class factory is responsible for creating. pUnkOuter is used when aggregating objects (the COM equivalent of inheritance, which will be covered elsewhere). For simple stuff, this should be set to NULL. riid is a reference to an IID (a reference variable in C++, a pointer in C), which is the Interface that is requested on the created object. ppvObject is a pointer to a pointer that we'll fill in with the interface if the call to CreateInstance is successful, and that we'll zero out if it fails. Thus, we could create an instance of CMike, and ask to have the class factory return the interface for IFoo on the object with the following line:
pClassFactory->CreateInstance( NULL, IFoo, ppvObj );
Class factories are unusual in that they can be unloaded from memory even when it's reference count is nonzero. I don't know why, but that's how it is. If we want to make sure that the server isn't unloaded until we want (eg, if we know that we'll be creating a number of objects), we call the LockServer method. Calling LockServer( TRUE ) indicates that the object shouldn't be unloaded until a corresponding LockServer(FALSE) is called. Since these calls can be nested, this generally means that a reference count is kept, with LockServer(TRUE) incrementing the count and LockServer(FALSE) decrementing it.
UUIDs, GUIDs, IIDs, CLSIDs
At this point, these are all structurally identical, but semantically different. The format is:
typedef struct _UUID {
unsigned long Data1;
unsigned short Data2;
unsigned short Data3;
unsigned char Data4[8];
} UUID;
(as detailed in Microsoft Visual C++ 5.0 on-line help)
Abbreviation | Name | Use |
UUID | Universally Unique Identifier | Uniquely Identify something. ID is unique not just on your machine, but on all machines. In order to avoid swamping Microsoft with requests for UUIDs, a decentralized way of generating must be used -- GuidGen.exe is a utility which will generate a UUID/GUID for you. This idea of a UUID is similiar to / the same as the idea of a UUID found in OSF DCE RPC |
IID | Interface ID | Uniquely identifies an interface. Also used to locate the proxy/stub for a given interface. |
CLSID | CLaSs ID | Uniquely identifies
a class of objects. It's used to figure out where a class's class factory
is, so that one can instantiate the class Because these aren't easily human readable (or rememberable), ProgIDs are also provided, which map a string to an CLSID, thus allowing people to indicate which interface they're talking about without having to deal with a 128 bit identifier. ProgIDs aren't guaranteed to be unique, but do provide an easily rememberable human-readable name for a class. |
GUID | Globally Unique Identifier | A GUID is the same thing as a UUID. |
All Rights Reserved 1997 by Michael William Panitz (mpanitz@cascadia.ctc.edu)
Home Page
Please note that OLE, COM, ActiveX, book titles, etc are NOT in any way reserved or trademarked by Mike, but instead
belong to their resepective owners. The author would also like to gratefully acknowledge the MS Visual C++ help files as
being a much appreciated source of information, particularly for function/interface prototypes, data type definitions, etc.