Dr. GUI on Components, COM, and ATL

Dr. GUI Online
Part 1: February 2, 1998
Part 2: February 9, 1998
Part 3: February 23, 1998
Part 4: March 2, 1998

Editor's Note   Only parts 1 through 4 of this column were available at the time of release of Visual Studio version 6.0. This column will be reprinted in its entirety in the July 1998 release of the MSDN Library. You can also visit the weekly column, "Dr. GUI Online," at MSDN Online (http://www.microsoft.com/msdn/), and view the archives section for this and previous columns.

Contents

Part 1: You're gonna do COM? Hasn't it been done already?
Part 2: Basics of COM
Part 3: Getting Objects and Interfaces
Part 4: The Object Class and Object Library

Part 1: You're Gonna Do COM? Hasn't It Been Done Already?

The good doctor can hear you now: "You're gonna do what? Explain COM? Books have been written on that subject!"

Indeed they have. Dr. GUI can recommend a number of good ones. He really likes both Dale Rogerson's Inside COM (Microsoft Press, 1997) and Don Box's Essential COM (Addison-Wesley, 1998). Books like Adam Denning's ActiveX Controls Inside Out (Microsoft Press, 1996) and Professional Visual C++ 5 ActiveX/COM Control Programming by Sing Li and Panos Economopoulos (WROX Press, 1997) are good, too. And of course, you'll want Kraig Brockschmidt's Inside OLE, 2nd Edition (Microsoft Press, 1995)—the ultimate printed OLE reference. (MSDN is the ultimate reference, of course—not that the good doctor has an opinion.) And if you want a formal, nonpartisan explanation of component software, check out Clemens Szyperski's new book, Component Software: Beyond Object-Oriented Programming (Addison-Wesley, 1998).

"So what," you ask, "could Dr. GUI possibly add to all this?"

"Nothing," the good doctor would respond. These authors (and others) plus the documentation truly say it all. If you can find the time to read them, that is.

"So that's what you're up to!" you exclaim. "You're going to give us a gentle introduction to COM and ATL—just a few screenfuls each week!"

And Dr. GUI replies, "You're smart. This is going to be easier than I thought."

Components? Am I programming a stereo?

If you've used Visual Basic much, you're very familiar with using components to do your programming: you use both visual ActiveX controls (such as spin buttons) and nonvisual ActiveX components (such as database access objects). It's hard to find a significant Visual Basic program that doesn't make heavy use of premade reusable components. But although you reuse plenty of components, most folks don't yet write a whole lot of reusable components of their own.

If you're programming in C++, you likely have a different experience of reuse. C++ and object-oriented programming claim to make reuse easy, but what has your experience been? Have you been able to create a library of reusable objects? A few of you no doubt have, but most of us have not. And if we have such a library, do we routinely make good use of it? It's not just a lack of discipline that keeps us from reusing our code: the fact is that it's hard to reuse code (it never seems to do quite what we need) and it's even harder to write reusable code (it's very hard to be general enough yet useful enough).

On top of that, what C++ makes easy is not creation of reusable binary components; rather, C++ makes it relatively easy to reuse source code. Note that most major C++ libraries are shipped in source form, not compiled form. It's all too often necessary to look at that source in order to inherit correctly from an object—and it's all too easy (and often necessary) to rely on implementation details of the original library when you reuse it. As if that isn't bad enough, it's often tempting (or necessary) to modify the original source and do a private build of the library. (How many private builds of MFC are there? The world will never know . . . .)

So let's reuse binary objects, not source code

So how can you reuse binary objects? Well, the answer that first comes to a Windows programmer's mind is simple: use dynamic-link libraries (DLLs). Using DLLs does work—Windows is itself, after all, primarily a set of DLLs. But there are some problems.

First, DLLs are not necessarily programming-language independent. Even for DLLs written in C, it's all too easy to change the calling convention (which parameters are pushed in which order?) such that the DLL is only usable from C programs. Granted, the calling convention used by Windows is pretty well established as the standard for Windows systems, but the good doctor has seen DLLs fail because of mismatched calling conventions.

Writing a C-style interface for your DLL has some major limitations. First off, it restricts you from doing object-oriented programming because the object-oriented features of C++ require name decoration of function names. There is no standard for name decoration; in some cases, different versions of the same compiler decorate names differently. Second, implementing polymorphism is difficult. You can work around these problems by creating wrapper classes for both sides, but doing so is painful. And Dr. GUI's not into pain. (Not too much, anyway.)

Even if you did resolve the name decoration problems so you could link successfully to the DLL, other problems arise when it comes time to update your objects.

First off, you're hosed (that's a medical term) if you add any virtual functions to your object when you update it. You might think you're okay if you add the new functions to the end, but you're not: you've just shifted the vtable entries for all objects that inherit from you. And since a virtual function call needs a fixed offset into the vtable in order to call the correct function, you can't make any changes to the vtable—at least not without recompiling every program that uses your object or any object derived from it. Clearly, recompiling the world every time you update your object is not an option.

Second, if you're using new in your client to allocate objects, you won't be able to change the size of the object (that is, add any data) without recompiling the world.

Lastly (and most importantly), updating DLLs is a nightmare because you're stuck between a rock and a hard place with two unappetizing options: either try to update the DLL "in place" by overwriting it or rename the new version. Updating the DLL in place is very bad: the chances that even if you keep the interface consistent you'll break some user of your DLL are very high. Dr. GUI doesn't need to tell you of all the problems the industry, including Microsoft, has run into because of this issue.

The alternative—using a new DLL name—will at least keep your working systems working. But there's a cost in disk space (perhaps not a big deal when typical hard disks are 3 gigabytes or so), and a second cost: increased memory usage. If your user is using both versions of the DLL, there will be two copies of very similar code in the user's working set. It's not unusual when you examine a user's memory usage to find two or three versions of the Visual Basic runtime or the Microsoft Foundation Class (MFC) DLL, for instance. Given that almost all Windows systems typically use more virtual memory than they have physical memory, increasing the working set size has serious performance implications in the form of increased virtual memory swapping to disk. (That's why, to paraphrase a counter-example to Brook's law, adding more memory to a slow system makes it faster.)

Ideally, you'd like to allow your user (or application) to be able to choose what version to use. This is VERY hard with statically linked DLLs, but very easy if you dynamically load your DLL.

To be fair to C++, we should note that it was never intended to solve this set of problems. C++ was intended to allow you to reuse code in programs that reside in one file, so all of the objects are compiled at the same time. C++ was not intended to provide a way to build reusable binary components that can be mixed and matched over versions and years. By the way, the creators of Java noticed these problems—these deficiencies were a major motivation for developing Oak, which later became Java.

So what about Java?

Java does solve some of these problems, but it also adds some of its own. The biggest problem is that Java components (JavaBeans, usually) are only intended to be used by programs written in Java. Now it's true that the Microsoft virtual machine (VM) allows you to use JavaBeans as COM objects. You can therefore use them from any language. And it's true that Sun has a Java/ActiveX bridge. But in general, unless you're running on Windows, Java is a single-language system: Java components can only be used in Java programs. And, for the most part, you have to rewrite your systems from scratch to use Java. (Yes, you can make native calls—but it's a big hassle to do using Java Native Interface (JNI)—and your programs will no longer be at all portable.) Dr. GUI finds this pretty unacceptable, so he's glad that the Microsoft virtual machine (VM) is more flexible—for Windows, at least. No language, not even C++, Visual Basic, or Java, is right for every programmer and every problem.

Java also makes you decide when you write your program whether the component you're using is local (on your machine) or remote (on another machine)—and the methods for using local and remote components are quite different.

Java has a couple of other issues that make it a less-than-ideal answer for all component needs. First, it has no really solid way to deal with versioning. (The package manager in the Microsoft VM helps a lot with this issue.) Second, Java will be to one degree or another slower than C++. Dr. GUI notes that recent "it's as fast as C++" benchmarks published in an online Java magazine omitted tests where Java would do poorly. Two examples that come to mind are string and array manipulation (Java has to bounds-check each access), and initial method calls (Java has to look up the method by signature in a table in the class for the first call, although subsequent calls, which the magazine did test, can be fast). Finally, Java's "one-class-at-a-time" loading scheme can be considerably slower than loading all the code at once (even if there's less code!) because it requires so many more file or HTTP transactions, both of which have a high overhead.

Even if you're using Java in a way that gives good performance, your performance will suffer when you use Java components from another language because of the translation layer that needs to be there to link the dissimilar languages and object models.

Where Java shines is in the possibility that you can use your compiled components on dissimilar machines without needing to recompile for each machine's processor and operating system. But this often doesn't "just work"—you'll need to test and debug on every platform you intend to support.

So what's the alternative?

As it turns out, it is possible to use C++ to build DLLs and other binary components that are reusable. Both Dale Rogerson's book, Inside COM, and Don Box's book, Essential COM, start with a C++ class they want to reuse and solve each of the problems I've listed above (and a few more) with some slick tricks. And both of them end up with, not surprisingly, COM. In other words, each of the solutions to the problem of binary reuse is an important feature of COM. (If you'd like to check this progression out now, check out Markus Horstmann's article, "From CPP to COM".

While COM's "native language" is C++, it's relatively easy to use COM from C—the headers even support this. And, with a few language tweaks, it's possible to have two-way COM support from any language—Visual Basic, Java, Delphi, and so on. (By two-way COM support, I mean that it's possible both to use COM objects from that language, and to write COM objects in that language.) Implementing COM compatibility in your language run time isn't trivial, but the benefits are great: once you do, you open up a whole world of already written and debugged COM objects for your use. And there's a wide market for the COM components you write—the Giga Information Group estimates the current market at $400 million a year and expects it to be $3 billion in three years. (The COM-component market is growing faster than Microsoft!) Note that these market estimates are for third-party COM objects: they exclude COM components provided by Microsoft.

Another key feature of COM is that three types of objects are supported: in process (DLL), local (EXE in separate process on the same machine), and remote (DLL or EXE on a different machine via Distributed COM, or DCOM). You write code that uses COM components without worrying (or even knowing) what type of COM object you'll end up using, so you use the exact same code to hook up to an in-process, local, or remote object. How does COM hook you up to the right object? Well, it looks for the object's Class ID in the registry—and the registry entries tell COM which type or types of objects are available. COM does the rest, including starting processes and communicating over the network. (Note: there are performance differences between the different types of COM objects that you'll need to think about—but at least the code for connecting to and using the objects is exactly the same no matter what type of object you eventually use.)

COM doesn't solve all of the world's problems, however. For instance, it's still possible to break programs that use your component when you update a component. (Perhaps the fact that COM enforces a "black box" view of the component where it's not possible to find implementation details makes such breakage less common, but they still occur.) So you still have to choose between updating components in place and risking breakage, and using new Class IDs for new components. But COM does make it somewhat easier to write code that allows the user (or application) to choose what version it will use without recompilation.

Recall that COM components can be written in and used from most any language, and they can reside on any machine. That's nice. But what about cross-platform support?

Well, the cross-platform story is a good-news/bad-news story. The bad news is that there isn't very much COM on any platform besides Win32 right now. There are a couple of ports of COM to some non-Windows platforms, but not many. But that's not all the news.

The good news is that many more ports—to most common UNIX versions and to MVS—are coming, and soon. Further, Microsoft is doing some of the porting work itself. So it won't be long until COM and DCOM will be available on your favorite mainframes and UNIX boxes—availability for UNIX is scheduled to be announced in February. And think of how cool it'll be to have remote COM objects written in any language running on some fast mainframe that you can access from any language (Visual Basic, Java, Delphi, C++) on your machine. Check out the latest information on the Microsoft COM Web site (http://www.microsoft.com/com/).

So if you're developing for Windows, you'll certainly want to consider writing COM objects, no matter whether you develop in Visual Basic, Java, C++, Delphi, or some other COM-compatible language. The objects you write will be usable on the local machine or remotely without rebuilding your component or the component's clients, thanks to the magic of COM and DCOM. And if you need your solutions to run on platforms other than Windows, COM is looking better and better, so it's still certainly worth looking into and seriously considering.

Coming up next: Basic COM concepts you should know

Next week, we'll talk about the basics of COM: objects, interfaces, and modules. And we'll start to dig into the C++ code for a simple COM object, if we have time. (If not, we'll do it the week after next.)

Part 2: Basics of COM

Where we've been; where we're going

In Part 1, the good doctor talked about why a language like C++ doesn't solve the problem of allowing you to build software out of binary components. The gist of that discussion was that C++ wasn't intended to solve this problem; rather, it was intended to make reuse of source code easy for single-executable programs. C++ meets that goal quite well. But we want to be able to mix and match components from different vendors without rebuilding parts (or all) of the system each time a component changes. We went into a lot of reasons why the C++ model doesn't work in this scenario.

So then what? Do you have to abandon C++? Nope—but you have to use it in a way that's a little different than what you're used to. And that's what we'll discuss next—how to use COM from C++.

Does that mean that you should stop reading if you're not a C++ programmer? No, because whatever COM-compatible language you're using (Visual Basic, Visual J++, Delphi, and others) will be doing the things we discuss under the hood (or is that in the operating room?). So if you read on, you'll gain valuable insights as to how those ActiveX controls and COM components work.

Okay, so what is COM?

The Component Object Model (COM) is a way for software components to communicate with each other. It's a binary and network standard that allows any two components to communicate regardless of what machine they're running on (as long as the machines are connected), what operating systems the machines are running (as long as it supports COM), and what language the components are written in. COM further provides location transparency: it doesn't matter to you when you write your components whether the other components are in-process DLLs, local EXEs, or components located on some other machine. (There are performance implications, of course, but you don't have to rewrite a thing to change the other components' locations—that's key.)

Objects

COM is based on objects—but the objects aren't quite the objects you're used to in C++ or Visual Basic. (By the way, an object and a component are pretty much the same thing. Dr. GUI will tend to say "component" when he's talking about application architecture and "object" when he's talking about implementations.)

First off, COM objects are well encapsulated. You cannot gain access to the internal implementation of the object; you have no way of knowing what data structures the object might be using. In fact, the objects are so well encapsulated that COM objects are usually just drawn as boxes. Figure 1 is a drawing of an utterly encapsulated object. Note how the implementation details are hidden from you.

Figure 1. An utterly encapsulated, non-COM object.

That's all well and good for encapsulation, but what about communication? As it stands, we have no way to communicate with the component in that box. Clearly, this won't do.

Interfaces: Communications with an object

That's where interfaces come in. The only way to access a COM object is through an interface. We can draw an interface called IFoo on an object like the one shown in Figure 2.

Figure 2. Object with interface—still not COM.

The thing that looks like a lollipop sticking out of the side of our object is the interface—in this case, the IFoo interface. This interface is the only way to communicate with this object. The good doctor finds it more useful to think of an interface as being like a plug-in connector than a lollipop. It's how you plug into the functionality of the object. Think of it as being like the antenna input to your VCR or TV.

An interface is two things. First, it's a set of functions that you can call to get the object to do something. In C++, interfaces are represented as abstract base classes. For instance, the definition of IFoo might be:

class IFoo {
   virtual void Func1(void) = 0;
   virtual void Func2(int nCount) = 0;
};

We'll ignore the return types and inheritance for now . . . but do note that there can be more than one function in the interface and that all of the functions are pure virtual functions: they do not have implementations in class IFoo. We're not defining behavior here—we're only defining what functions are in the interface. (A real object has to have implementation, of course—more on that later.)

Second—and more importantly—an interface is a contract between the component and its clients. In other words, an interface not only defines what functions are available, it also defines what the object does when the functions are called. This semantic definition is not in terms of the specific implementation of the object, so there's no way to represent it in C++ code (although we can provide a specific implementation in C++). Rather, the definition is in terms of the object's behavior, so that revisions to the object and/or new objects that also implement the interface (contract) are possible. In fact, the object is free to implement the contract in any way it chooses (as long as it honors the contract). In other words, the contract has to be (gasp!) documented outside of the source code. This is especially important since clients won't get (and don't need) the source code.

This notion of a specific contract is crucial to COM and to component software in general. Without "ironclad" contracts, it would be impossible to interchange components.

Interface contracts, like diamonds, are forever

In COM, once you "publish" an interface contract by shipping a component, the contract is immutable—it cannot be changed in any way. You can not add. You can not delete. You can not modify. Why? Because other components are depending on the contract. If you change the contract, you'll break that software. You can improve your internal implementation as long as you still honor the contract.

What if you've forgotten something? What if requirements change? How does the world improve?

The answer's easy: you write a new contract. The standard OLE interface list has many of these: IClassFactory and IClassFactory2, IViewObject and IViewObject2, and so on. And you can, of course, provide an IFoo2. (I'm sure you've noticed by now that interface names, by convention, begin with an uppercase I.)

So if I write a new contract, how does software that only knows about the old contract still use my components? Won't that mess old components up?

COM objects can support multiple interfaces—they can implement multiple contracts

The answer to that is also no—and the reason is simple: in COM, an object can support multiple interfaces. In fact, all useful COM objects support at least two interfaces. (At least the standard IUnknown interface—more on this later—and an interface that does what you want the object to do.) Visual ActiveX controls support about a dozen interfaces, most of them standard interfaces. In order for a component to support an interface, it has to implement each and every method in that interface, so this is a very substantial task. That's why tools like the Active Template Library (ATL) and so forth are popular: they provide implementation for all of the interfaces.

So in order to support the new IFoo2 functionality, we add our IFoo2 to this object as well.

Figure 3. Version 2.0, which supports both IFoo and IFoo2—but still is not a COM object.

If you're still thinking of plugs, think of IFoo as the antenna input to your TV and IFoo2 as the composite video input. Note that you can't plug the antenna cable into the composite video input—or vice versa. In other words, each interface is logically unique.

On the other hand, these interfaces do have something in common. Do I have to rewrite the whole implementation just to add a new interface that's almost exactly the same as the old one? No, because COM supports inheritance of interfaces. As long as we don't change the functions already in IFoo, we can define IFoo2 as follows:

class IFoo2 : public IFoo {
   // Inherited Func1, Func2
   virtual void Func2Ex(double nCount) = 0;
};

Interface review

So let's review what we've seen. First off, COM is a binary standard for software object interaction. Since it's a binary standard, objects don't—and can't—know of the implementation details of the objects they use. Objects, therefore, are black boxes.

We manipulate these black box objects only through the interfaces the objects expose. Finally, a given object can expose as many interfaces as it chooses.

Simple, right?

Well, we've ignored a lot of details. How do those objects get created? How do you gain access to an interface? How do you call an interface's methods? And where's the implementation of these objects, anyway? And when will that pesky object finally be destroyed?

These are great questions—but unfortunately Dr. GUI's almost late for surgery, so we'll have to postpone the answers until Part 3. But the good doctor will deal with one issue now: How are interface methods called?

Calling interface methods

You may have seen this coming already, but this ends up being very simple: COM method calls are merely C++ virtual function calls. We will somehow (more on this in Part 3) get a pointer to an object that implements an interface, then we'll just call the methods of the interface.

First, let's assume that we have a C++ class called CFoo that implements the IFoo interface. Notice that we inherit from IFoo to insure that we implement the proper interface in the proper order.

class CFoo : public IFoo {
   void Func1() { /* ... */ }
   void Func2(int nCount) { /* ... */ }
};

The pointer we use will be called an interface pointer. Assuming we can get one, our code might look something like this:

#include <IFOO.H // Don't need CFoo, just the interface
void DoFoo() {
  IFoo *pFoo = Fn_That_Gets_An_IFoo_Pointer_To_A_CFoo_Object();

  // Call the methods.
  pFoo -> Func1();
  pFoo -> Func2(5);
};

It really is that simple.

But what really goes on underneath? Well, as it turns out, the COM binary standard applies to method calls, too—so COM defines what happens in order to call the function. Specifically, the same thing happens that happens for a virtual function call:

  1. pFoo is dereferenced to find the vtable pointer in the object.

  2. The vtable pointer is dereferenced and indexed to find the address of the function to be called.

  3. The function is called.

See Figure 4 for the numbered steps:

Figure 4. C++ virtual function calls through interface pointer

Remember that in C++ every time you have virtual functions you'll have a vtable that points to those functions—and that the calls are always done by indexing into the vtable.

"Aha!" you say. "I knew it. COM is really tied to C++! It's not a binary standard at all!"

To which Dr. GUI replies, "Nonsense." After all, you can implement this call in any language that supports arrays of function pointers. It's easy to do it in C, for instance—a call to Func2 through the pointer p would look like:

(*((*p)+1))(p, 5); // passing 5 to the 2nd function in the array

Note that we have to pass p as the first parameter—this simulates the C++ this pointer. (*p) is the first indirection (step 1), *((*p) + 1) is the index into the vtable (step 2), and then we call the function with p and 5 (step 3). It's easy, but it's ugly—Dr. GUI showed it so you'd know you could do it (and to make you appreciate C++). In x86 assembly language, it might look something like this:

MOV EAX, [pFoo]      ; step 1
MOV EAX, [EAX + 4]   ; step 2, indexed to 2nd pointer
CALL [EAX]          ; step 3

Dr. GUI is aware that the second and third instructions could be combined into CALL [EAX + 4] if you didn't need to keep the address of the function in EAX.

Why did the good doctor show all this detail? Well, if you can do it in assembly or C, you can do it in any language! Other languages (Visual Basic, Visual J++, Delphi) build the support for doing these calls into their run times or virtual machines—often by using assembly or C code similar to the above.

The point is that any COM method call must use the data structures shown in order to do a call, regardless of what the original language is—or regardless of where the COM object is. We'll discuss how COM achieves location transparency in a future column.

Where we've been; where we're going

Okay . . . so we've discussed interfaces, objects, and how interface methods are called.

An object is the basic unit of COM—it's the thing that COM creates. An object implements some set of interfaces. An interface is a set of methods and a contract for what those methods do. Interface methods are called in the same manner a C++ virtual function is called.

In Part 3, we'll talk about how to create these objects, how to get interface pointers, and how objects are destroyed.

Part 3: Getting Objects and Interfaces

In Part 2, we discussed two really basic concepts in COM: objects and interfaces. We also showed how an object could implement more than one interface. Finally, we discussed the nitty-gritty of how COM method calls are made—and noticed that they're the same as C++ virtual function calls. (But we also noted that you can do them in any language that supports—or can call assembly language to support—pointers to arrays of function pointers.)

More on COM: Creation and destruction of objects; getting interface pointers

But you might have found the discussion somewhat unsatisfying: we never talked about how to create an object, nor did we discuss how to get an interface pointer so we could call methods on the object's interface. And we never talked about how to get rid of objects you no longer need, or how to switch interfaces.

The good doctor will shed some light on these topics in this week's discussion. But first, we need to take care of a little housekeeping. You know how sometimes you go to explain something and you suddenly remember that you forgot a few important things? That's what's happened here—the good doctor forgot that in order to create objects, you've gotta be able to have a way to refer to them—and a way to clearly define your interfaces. So we'll talk about the forgotten pieces, then cover the real fun stuff. (And that's why this column is long—and late.)

Identifiers in COM

If you're thinking ahead, you've noticed that we're going to need some identifiers for various entities in the COM world. First, we're going to need an identifier for the object type (or class). Second, we'll need an identifier for each interface. But what should we use for an identifier? A 32-bit int? A 64-bit int? Well, perhaps we could. But there's a problem: the identifiers will have to be unique across all machines, since there's no way of knowing what machines you'll want to install a component on. Your objects and interfaces will need the same identifier on all machines so that any client can use your component. Further, no other object or interface may use that identifier, no matter where it came from. In other words, these identifiers must be globally unique.

Fortunately, algorithms and data formats exist for creating such identifiers. By using your machine's unique network card ID, the current time, and other data, these identifiers, called GUIDs (globally unique identifiers) are created by a program called GUIDGEN.EXE. GUIDs are stored in 16-byte (128 bit) structures, giving 2128 possible GUIDs. But don't worry about running out of GUIDs: although the good doctor was unable to find the exact number of atoms in the universe, even after searching the Web, he believes it's significantly less than 2128 So there's no shortage of GUIDs and no need for GUID conservation, despite the in-jokes of COM practitioners.

In C++, there are data types defined by COM header files for GUID, CLSID (class identifier GUID), and IID (interface identifier GUID). Since these 16-byte structures are kind of large to be passing around by value, you'll usually use REFCLSID and REFIID as the data types of parameters when passing GUIDs. You will have to create a CLSID for each object type you write and an IID for each custom interface you create.

Standard interfaces

COM defines a large set of standard interfaces and their associated IIDs. For instance, the mother of all interfaces, IUnknown, has an IID of "00000000-0000-0000-c000-000000000046" (the hyphens are part of the standard way of writing GUIDs). This IID is defined by COM and you need never refer to it directly; instead, use the symbol IID_IUnknown, which is defined for you in the headers.

The IUnknown interface has three functions:

HRESULT QueryInterface(REFIID riid, void **ppvObject);
ULONG AddRef();
ULONG Release();

We'll discuss exactly what these functions do later.

Much of your COM programming will be done using standard interfaces—a lot of it will be providing implementations for standard interfaces so that other COM clients and objects can use your object.

By the way, there are some macros used in COM to describe the return types and calling conventions of functions. Almost all COM methods return an HRESULT, so the macro STDMETHODIMP assumes that. The macro STDMETHODIMP_() takes a parameter—the return type of the method. (You only use the STDMETHOD macro when you're defining an interface with pure virtual functions—and the IDL compiler writes that code for you—more on that later.)

Using these macros, the declarations above would look like this:

STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();

We'll use these macros from now on out. Doing so makes it easy to port your code to different COM platforms (such as Macintosh and Solaris).

Custom interfaces

Custom interfaces are interfaces you create. You'll create your own IID for these interfaces, and define your own list of functions. Our IFoo interface is a custom interface. I've defined an IID called IID_IFoo (which has the value "13C0205C-A753-11d1-A52D-0000F8751BA7") by running the GUID generator on my machine.

Recall that the class declaration was originally:

class IFoo {
   virtual void Func1(void) = 0;
   virtual void Func2(int nCount) = 0;
};

We'll modify this to be COM-compliant by changing it around a bit:

interface IFoo : IUnknown {
virtual HRESULT STDMETHODCALLTYPE Func1(void) = 0;
virtual HRESULT STDMETHODCALLTYPE Func2(int nCount) = 0;
};

Using the macros as described above, this becomes:

interface IFoo : IUnknown {
STDMETHOD Func1(void) PURE;
STDMETHOD Func2(int nCount) PURE;
};

The word "interface" is not a keyword in C++, rather it is #defined in the appropriate COM header as "struct". (Recall that in C++ classes and structs are the same except that structs use public inheritance and access by default rather than private.) STDMETHOD uisesSTDMETHODCALLTYPE, which is defined as __stdcall, indicating that the compiler should generate the standard function calling sequence for these functions. Remember, we use these macros because the definitions of them will change as we port our code to different platforms.

All COM functions (with almost no exceptions) return an HRESULT error code. This HRESULT is a 32-bit number that uses the sign bit to represent success or failure and fields in the remaining 31 bits to indicate the "facility" and the error code (which is specific to the facility), and some reserved bits. Normally you'll want to return the success code S_OK, but you can return an error code—either a standard one or one you make up—if you encounter a problem in your method.

Finally, note that I'm deriving IFoo from the standard COM interface IUnknown. That means that any class that implements IFoo will also need to implement the three functions AddRef, Release, and QueryInterface. Also, the IFoo vtable will have pointers to these three functions before the pointers to Func1 and Func2. That's a total of five functions in the vtable, and five functions to implement. All COM interfaces are derived from IUnknown, so all COM interfaces contain these three functions in addition to any other functions.

What's that thing in the MIDL?

You won't actually write the declaration above yourself—it'll be generated for you by the MIDL compiler. Why? Well, as it turns out, C++ can't express everything that needs to be expressed in an interface. Recall that COM objects can be DLLs to be used in-process, meaning in the same address space. So if you pass a pointer to some data to an in-process server, the server can dereference the pointer directly.

But also recall that your COM object can be a local (out-of-process) server in separate EXE address spaces, or even accessed remotely. Any time you need to pass a pointer to a COM method in such an object, you're got a problem: the pointer is meaningless in any other address space. What's meaningful is the data to which the pointer points. This data has to be copied into the other address space—and perhaps back. This process of copying the right data is called marshalling. Thankfully, COM does the marshalling for you in most cases. But in order for it to do so, you need to tell it more than just the type the pointer points to—you need to tell COM how the pointer is used. For instance, does the pointer point to an array? A string? Is the parameter an input parameter only? An output parameter? Both? You can see that there's no way to express this in C++.

So we'll need another language, called IDL (Interface Definition Language) to define the interface. IDL looks a lot like C++, but it has "attributes" in square brackets added in to the C++-like code. MIDL.EXE compiles the IDL file you (or Visual Studio) write to produce a variety of outputs. For now, the only output we care about is the header file for our interface, which we'll include in our code.

In our example, it doesn't make any difference since we're only passing by value, so the IDL code looks very similar—the main difference is that the word "virtual" is missing. But if we created a new interface IFoo2 which adds a method Func3(int *) to the other two methods, the IDL would look something like this:

[ uuid(E312522F-A7B7-11D1-A52E-0000F8751BA7) ]
interface IFoo2 : IUnknown
{
   HRESULT Func1();
   HRESULT Func2(int in_only);
   HRESULT Func3([in, out] int *inout);
};

Note a few things. First, there are various attributes in the IDL, enclosed in square brackets. The attributes always apply to the thing immediately after, so the uuid attribute above applies to the interface—it's the IID for the interface. (UUID, or universally unique identifier, is a synonym for GUID.) The [in, out] attribute applies to the pointer and tells COM that it will have to marshal a single int both in and out when it calls Func3 (if marshalling is required). If the int pointer referred to an array, it would have an additional attribute (size_is with a parameter). There is IDL code to define the object as well. For instance, the code fragment to define our object might look like this:

[ uuid(E312522E-A7B7-11D1-A52E-0000F8751BA7) ]
coclass Foo
{
   [default] interface IFoo;
};

This is how the CLSID is associated with the class—and how the set of interfaces the class implements is defined. Note that although this looks a lot like C++ with a couple of additional attributes, it doesn't correspond exactly to C++ code like the interface definition does.

Creation of objects

Once our CLSID is associated with an object type (more on this later), we can create an object. As it turns out, this is very simple—just one function call:

IFoo *pFoo = NULL;
HRESULT hr = CoCreateInstance(CLSID_Foo, NULL, CLSCTX_ALL,
               IID_IFoo, (void **)&pFoo);

If CoCreateInstance succeeds, it creates an instance of the object identified by the CLSID GUID, CLSID_Foo. Note that there's no such thing as a "pointer to the object"; instead, we always refer to the object through an interface pointer. So we have to specify which interface we want (IID_IFoo) and pass a pointer to a place for CoCreateInstance to store the interface pointer.

The two parameters we've not discussed yet are not important at the moment.

Once we make the call, we check to make sure that the call succeeded and go ahead and use the object:

if (SUCCEEDED(hr)) {
   pFoo->Func1();   // Call methods.
   pFoo->Func2(5);
   pFoo->Release();   // MUST release interface when done.
}
else // Creation failed...

CoCreateInstance returns an HRESULT to indicate whether it succeeded or not. Always use the SUCCEEDED macro to check the result since nonnegative values mean success. In fact the most common success code, S_OK, is zero—so a check like "if (hr) // Success" won't work at all. Once the object has been created successfully, you can use the interface pointer to call the methods of the interface, as shown above.

It is vitally important that you release the interface pointer when you're done with it by calling Release. Note that since all interfaces are derived from IUnknown, all interfaces support Release. The COM object is responsible for freeing itself when you tell it you're done with it, but it relies on you to tell it when you're done. If we'd forgotten to call Release, the object would be leaked (and locked in memory at least until our application closed—and perhaps until the system is rebooted). Messing up object lifetime is a very common COM programming problem—and it's often difficult to find. So be careful, starting now. Note that we only release the interface if we were actually able to create it.

Figure 5 is a diagram of our newly created object. By convention, IUnknown is not labeled; it's always drawn attached to the upper right hand corner of the object. All of the other interfaces are drawn on the left.

Figure 5. Our first simple COM object, with unlabeled IUnknown.

Now that we have IUnknown implemented, we really have a COM object. (If only it was as simple as drawing a connector!)

If we'd added an IFoo2 interface to the object, we'd have a total of three interfaces, drawn as in Figure 6.

Figure 6. Theoretical version 2.0, which supports both IFoo and IFoo2.

GUIDs and the registry

So how did COM find the object's code in order to create it? Simple: it looked in the registry. When a COM component is installed, it has to have entries made in the registry. For our Foo class, the entry might look something like this:

HKEY_CLASSES_ROOT
  CLSID
    {E312522E-A7B7-11D1-A52E-0000F8751BA7}="Foo Class"
      InprocServer32="D:\\ATL Examples\Foo\\Debug\\Foo.dll"

Most objects will have some additional entries, but we can ignore these for now.

At HKEY_CLASSES_ROOT\CLSID, there's an entry for the CLSID of our class. That is how CoCreateInstance looks up the DLL name for the component. When you provide CoCreateInstance with the CLSID, finds the DLL name, loads the DLL, and creates the component (more on this later).

The registry entries would look somewhat different if the server was out-of-process or remote, but the main point is that the information would be there so that COM could start the server and create the object.

If you know the name (ProgID) of the object but not its CLSID, you can look up the CLSID in the registry. For our object, there's an entry at:

HKEY_CLASSES_ROOT
  Foo.Foo="Foo Class"
    CURVER="Foo.Foo.1"
    CLSID="{E312522E-A7B7-11D1-A52E-0000F8751BA7}"
  Foo.Foo.1="Foo Class"
    CLSID="{E312522E-A7B7-11D1-A52E-0000F8751BA7}"

"Foo.Foo" is the version-independent ProgID, and Foo.Foo.1 is the ProgID. If you create a Foo object from Visual Basic, the CLSID is looked up using one of these ProgIDs. (Note that the ATL wizards don't create the registry entries quite correctly in current versions: they leave out the first of the two CLSID keys shown above. Don't forget to make a copy of the CLSID for the version-independent ProgID.)

Modules, component classes, and interfaces

Note that it's possible—in fact common—for one module (DLL or EXE) to implement more than one COM component class. When this happens, there will be more than one CLSID entry that refers to the same module.

So now we can define the relationship between modules, classes, and interfaces. A module (the basic unit that you build and install) can implement one or more components. Each component will have its own CLSID and entry in the registry that points to the module's file name. And each component implements at least two interfaces: IUnknown, and an interface that exposes the component's functionality. Figure 7 shows this.

Figure 7. Module Oo.DLL contains implementations for three objects: Foo, Goo, and Hoo. Each object implements IUnknown and one or more additional interfaces.

Getting to the other interfaces with QueryInterface

Let's say that we have a new and improved Foo2 object that implements two custom interfaces: IFoo and IFoo2. We already know how to create such an object using CoCreateInstance and how to get a pointer to one of its three (don't forget IUnknown) interfaces.

Once we've got that interface pointer, how do we get an interface pointer to one of the object's other interfaces? We can't call CoCreateInstance again—that would create a new object. We don't want that—we only want a different interface on the existing object.

That's the problem that IUnknown::QueryInterface solves. Remember, because all interfaces inherit from IUnknown, all interfaces implement QueryInterface. So we'll just use the first interface pointer to call QueryInterface to get the second interface pointer:

IFoo *pFoo = NULL;
HRESULT hr = CoCreateInstance(CLSID_Foo2, NULL, CLSCTX_ALL,
               IID_IFoo, (void **)&pFoo);
if (SUCCEEDED(hr)) {
   pFoo->Func1();   // call IFoo::Func1
   IFoo2 *pFoo2 = NULL;
   hr = pFoo->QueryInterface(IID_IFoo2, (void **)&pFoo2);
   if (SUCCEEDED(hr)) {
      int inoutval = 5;
      pFoo2->Func3(&inoutval);   // IFoo2::Func3
      pFoo2->Release();
   }
   pFoo->Release();
}

We pass QueryInterface the IID of the interface we want and a pointer to the place for QueryInterface to store the new interface pointer. Once QueryInterface returns successfully, we can use the interface pointer to call that interface's function.

Note well that we must release both interface pointers when we're done with them. Failure to release either of them will leak the object. Since we never refer to the object except through interface pointers, we have to release each and every interface pointer we get in order for the object as a whole to be released.

IUnknown's other functions

IUnknown has two other functions, AddRef and Release. We've seen already that you use Release to tell an object that you're done with an interface pointer. So when do you use AddRef?

Reference counts and when an object can be freed

Most COM objects keep a reference count—in other words, they're required to keep track of how many interface pointers to the object are in use. When the reference counts on all of the object's interfaces goes to zero, it can be freed. We don't explicitly free the object; we just release all of the object's interface pointers and the object frees itself at the appropriate time.

AddRef increments the reference count, and Release decrements it. So if we didn't call AddRef, why did we have to call Release?

Whenever QueryInterface hands out a new pointer to an object, it's responsibility of QueryInterface to call AddRef before returning the pointer. That's why we didn't have to call AddRef for the pointers we got—QueryInterface did it for us. (Note that CoCreateInstance calls QueryInterface—which calls AddRef—so this is true even for our first interface pointer to an object.)

Note that you're required to call Release on the same interface pointer on which you called AddRef. Objects can, if they want, keep track of references on an interface-by-interface basis. The code above carefully did this, correctly pairing implicit AddRef calls with the appropriate Release calls—one Release call for each interface pointer.

You need to call AddRef if you're making a copy of an interface pointer so that the reference count for the interface is accurate. When you need to do this or not is a bit complex, but it's well covered by various COM references. Check them out for the details.

Various smart pointer classes can make dealing with the great IUnknown much easier (in fact, automatic). There are several such classes in ATL and Visual C++ 5.0. And if you're using another language such as Visual Basic or Java, the language's implementation of COM deals with the IUnknown methods correctly for you.

Where we've been; where we're going

So we've talked about how to create objects and destroy them (we don't—we just release all of our interface pointers to them), and how to call interface methods and switch interfaces. Along the way, we also introduced the concepts of various GUIDs used to identify the objects and interfaces and the registry entries needed so that COM can figure out how to create your object.

In Part 4, we'll talk more specifically about how in-process objects are created, and ways to make that creation more efficient. If there's time, we'll also talk about the guts of implementing an object, including the code you need for creating an object and IUnknown.

Part 4: The Class Object and Class Factory

In Part 3, we talked about how to create and destroy objects (we don't destroy them—we just release all interface pointers to them) and how to call interface methods and switch interfaces. Along the way, we also introduced the concepts of the various GUIDs used to identify the objects and interfaces and the registry entries needed so that COM can figure out how to create your object.

This time, we'll talk more specifically about how in-process objects are created, and ways of making that creation more efficient. We'll also talk about the class object (also known as class factory) and how to implement one. There's not time this week to talk about implementing your actual object, but that'll be at the top of the agenda next week.

What happens when you call CoCreateInstance?

We've talked already about how, when you call CoCreateInstance, COM searches the registry to find the CLSID so it can find the DLL (or EXE) that implements the object. But we didn't explain the details of how that happens. CoCreateInstance encapsulates the following functionality:

IClassFactory *pCF;
CoGetClassObject(rclsid, dwClsContext, NULL,
      IID_IClassFactory, (void **)&pCF);
hresult = pCF->CreateInstance(pUnkOuter, riid, ppvObj)
pCF->Release();

As we can see, there are three steps. The first step is to get a class object via its IID_IClassFactory interface. Next, we call IClassFactory::CreateInstance on this class object. (The parameters for this call are passed in from the CoCreateInstance call.) The parameter pUnkOuter is used for a reuse method called aggregation, which we'll talk about later. Assume that it's NULL for now. We now have a pointer to an instance of our object in *ppvObj. Finally, we release the class object.

So what is this class object? Why do we have to bother with it?

The class object

The class object is a special COM object whose main purpose is to implement the IClassFactory interface. (You'll often hear this object referred to as a "class factory" or even "class factory object," but it's more accurate to refer to it as the class object.)

If you've used Java extensively, you can think of the COM class object as being roughly like a Java object of class "Class." And you'll note that Java's Class.newInstance is analogous to IClassFactory::CreateInstance just as COM's CoGetClassObject is analogous to the Class.forName static method.

This object is special because it's not created by calling CoCreateInstance or IClassFactory::CreateInstance, as are most COM objects. Rather, it's always created by calling CoGetClassObject. We'll see other examples of special COM objects at the end of this article. (As it turns out, CoGetClassObject doesn't always create a class object. If COM has a class object for the right class available, it can just return an interface pointer to it.)

After the call to CoGetClassObject, note that the code doesn't have to worry about what kind of object it's creating—it doesn't matter whether it's an in-process or local server, for instance. The class object handles all those differences. CoGetClassObject, however, does need to do the work of digging through the registry (and list of existing registered class objects) in order to figure out how to create or find a class object for the CLSID requested.

The class object is a great example of the power of polymorphism. We call a COM API in order to get the object. But once we've got it, we can determine that it supports the standard interface we need (IClassFactory) and we can then call the methods of that interface—in this case, IClassFactory::CreateInstance. Note that we have no idea how the class object's CreateInstance works. All we know is that if it succeeds, it will return an interface pointer that refers to the object. We don't have to and don't want to know anything else (that's encapsulation) and we get the right behavior for the particular class object by making the exact same function call (that's polymorphism)—it's the identity of the class object that determines the exact behavior.

Each class object instance is associated with a particular CLSID—note that IClassFactory::CreateInstance does not have a CLSID as one of its parameters. Rather, the class object knows what CLSID it is to create. That means that you'll need at least one class object for each separate CLSID you want to be able to create.

In addition to IClassFactory, the class object can implement whatever interfaces it likes. For instance, you could define an interface that allows you to set defaults for object instances created from a particular class object. But note that you're not guaranteed that there is only one class object for a given CLSID, so if you call CoGetClassObject more than once, you may well get interface pointers to different class objects. (Since you control the creation of the class object, you can define this in your implementations.)

Why there's a class object

As we discussed, the most important reason for COM to require implementation of a class object is so that COM can have a standard polymorphic way of creating objects of any type without requiring the client to know the exact details of creation. The class object encapsulates that knowledge, so the client doesn't have to. This implies that the class object and the "real" object have a very close relationship—and often lot of knowledge of each other.

But why not a simpler scheme? For instance, you could imagine a function in your COM DLL called, say, DLLCreateInstance that would accept a CLSID and create a new instance. A function such as this is certainly simpler than a COM object and IClassFactory.

But it wouldn't work for EXEs. You don't just export functions from EXEs. And it certainly wouldn't work well for remote objects. So when we make the class object a COM object, COM takes care of all of the in-process and out-of-process issues for us. That's a good deal.

Since the class object is a COM object that knows how to "do the right thing" to create an instance of the target object, note that once the class object is created, COM is out of the picture in terms of creation of instances. So for the first object of a particular type that's created, COM has to do a lot of work. First, it has to look up the CLSID in the list of registered class objects (or in the registry if the class object doesn't exist). If the class object needs to be created, COM creates it, perhaps including loading a DLL or starting an EXE. Finally, COM calls IClassFactory::CreateInstance on the correct class object to create your instance. Phew!

But if you keep the class object around, you can skip most of the work for subsequent instances: just call IClassFactory::CreateInstance yourself to make additional objects. This can be almost as fast as calling operator new directly—and far faster than having COM create objects.

Important   If you keep a class object around, you must call IClassFactory::LockServer to tell COM to keep the server in memory. The reference to the class object will not keep the server in memory automatically. This behavior is an exception to the normal COM behavior. If you fail to lock the server, you may cause a protection violation if you try to access the class object after the server's been unloaded. Don't forget to unlock the server when you're done with the class object.

Finally, the class object can support additional ways to create objects, such as the IClassFactory2 interface that's used instead of IClassFactory to create licensed controls. Licensed controls are controls that require the user to have the correct license ID before the control can be created.

Another way to create objects and when to use it

If you're creating only one instance of an object and if you can use IClassFactory to create the object, you may as well use CoCreateInstance (or CoCreateInstanceEx, which can create remote objects). But if you're creating more than one instance of an object, or if you need to use an interface besides IClassFactory to create the object, you'll need to get (and probably hold) a class object.

Getting the class object is easy—just do as CoCreateInstance does: call CoGetClassObject. Once you have the interface pointer to the class object, call IClassFactory::LockServer(TRUE) to lock the server in memory. Then you can hold on to the interface pointer to the class object and call IClassFactory::CreateInstance whenever you need a new instance. Finally, when you're done creating objects, release the server by calling IClassFactory::LockServer(FALSE) and release the interface pointer by calling Release on it. Remember that releasing the interface has to be the last thing you do to the interface.

Implementing the class object

So what does this class object look like? Well, it's a simple COM object. That means that it implements at least one interface: IUnknown. Almost all class objects also implement IClassFactory so they can create instances.

We might have a class object declared like this:

class CMyClassObject : public IClassFactory
{
protected:
   ULONG m_cRef;
public:
   CMyClassObject() : m_cRef(0) { };
      //IUnknown members
      STDMETHODIMP QueryInterface(REFIID, void **);
      STDMETHODIMP_(ULONG) AddRef(void);
      STDMETHODIMP_(ULONG) Release(void);

      //IClassFactory members
      STDMETHODIMP CreateInstance(IUnknown *, REFIID iid, void **ppv);
      STDMETHODIMP LockServer(BOOL);
};

Of course, this class contains declarations for each of the functions in IclassFactory::CreateInstance and LockServer. Plus (surprise!), there are the IUnknown functions. (Remember that IClassFactory is derived from IUnknown, as are all COM interfaces.) Note that we have a member that holds the reference count for this object and that we've initialized it to zero in the constructor. Note also that we're using the official COM macros for declaring method impmentations.

How this class object is created

There are a variety of ways to create a class object, none of which involve CoCreateInstance. Since we really only need one instance of this object and since it's a small object with no constructor, I decided to just declare a global object in my code:

CMyClassObject g_cfMyClassObject;

That means that this object will always exist when the DLL is loaded.

In order to implement IClassFactory::LockServer, we'll also need a global count of all of the nonclass object instances and the number of times LockServer was called:

LONG g_cObjectsAndLocks = 0;

How CoGetClassObject gets the class object

For in-process DLL servers, this is simple: COM calls a function called DllGetClassObject in your DLL. You must export this function if your DLL contains COM-creatable COM objects. DllGetClassObject has the following prototype:

STDAPI DllGetClassObject(const CLSID &rclsid, const IID &riid,
void ** ppv);

COM passes in a CLSID and an IID; DllGetClassObject returns a pointer to the requested interface in *ppv. If the class object can't be created or the requested interface doesn't exist, an error is returned in the HRESULT return value (note that STDAPI is #defined to return an HRESULT).

For EXE servers, the process is different: You register a class object for each class COM can create by calling CoRegisterClassObject for each class object. This puts the class objects in the list of registered class objects. When the EXE process ends, it calls CoRevokeClassObject once per class object to remove the objects from the registered list. If you need more details on this, check the COM docs or various COM books—I'd like to focus on in-process (DLL) servers here.

Note, then, that how COM actually gets the class object when you call CoGetClassObject depends on whether the object is implemented by a DLL or an EXE. If a DLL, it loads the DLL (if not already loaded) and calls DllGetClassObject. For an EXE, it loads the EXE (if not already loaded) and waits until the EXE registers the class object it's looking for or until a timeout occurs.

Our DllGetClassObject might look like this:

STDAPI DllGetClassObject(REFCLSID clsid, REFIID iid, void **ppv) {
   if (clsid != CLSID_MyObject) // Right CLSID?
      return CLASS_E_CLASSNOTAVAILABLE;

   // Get the interface from the global object.
   HRESULT hr = g_cfMyClassObject.QueryInterface(iid, ppv);
   if (FAILED(hr))
      *ppv = NULL;
   return hr;
}

We must check to see that the CLSID requested is the one we support. If not, we return E_FAIL. Next, we QueryInterface for the interface requested. If this fails, we set the output pointer to NULL and return E_NOINTERFACE. If it succeeds, we return S_OK and the interface pointer.

Implementing the methods of the class object

IUnknown::AddRef and IUnknown::Release

Our class object is global. It always exists and it can't be destroyed (at least not until the DLL is unloaded). Since we'll never delete this object and since references to class objects don't keep a server loaded, we almost don't need to implement reference counting. However, reference counting can be handy for debugging, so we'll implement it in this object anyway.

AddRef and Release are responsible for maintaining the reference count on the object. Note that we have an instance variable m_cRef that's been initialized to zero. AddRef and Release will just increment and decrement the reference counter and return the new value of the reference counter.

If the object was dynamically created, it would be the responsibility of Release to delete the object when the reference count went to zero. Since our object is globally allocated, we can't do that.

STDMETHODIMP_(ULONG) CMyClassObject::AddRef() {
   return InterlockedIncrement(&m_cRef);
}

STDMETHODIMP_(ULONG) CMyClassObject::Release() {
   return InterlockedDecrement(&m_cRef);
}

I used the thread-safe increment and decrement functions rather than just using ++m_cRef and --m_cRef to get into the habit of thinking about multithreaded operation.

If you wanted to make AddRef and Release real simple, you could simply have them return a nonzero value—you could also eliminate the member variable for the reference count for the class object (but not the objects and locks count global variable!).

IUnknown::QueryInterface

The QueryInterface implementation is 100 percent standard for this object—no special stuff because it's a class object. All we do is see whether the interface requested is one of the two interfaces (IUnknown and IClassFactory) that we support. If it is, we return an interface pointer, cast appropriately, to our object—and we call AddRef on that exact pointer to reference count. If not, we return the appropriate error code, E_NOINTERFACE.

STDMETHODIMP CMyClassObject::QueryInterface(REFIID iid, void ** ppv) {
   *ppv = NULL;
   if (iid == IID_IUnknown==iid || iid == IID_IClassFactory) {
      *ppv = static_castthis;
      (static_cast*ppv)->AddRef();
      return S_OK;
   else {
      *ppv = NULL; // COM Spec requires NULL if failure
      return E_NOINTERFACE;
   }
}

Note the funky new static_cast operator. In ANSI C++, you can distinguish between the three different semantic uses of casts by using different operators. The static_cast operator does the appropriate cast between pointers to different class types, changing the value of the pointer if necessary (it's not in this case because I'm not using multiple inheritance).

IClassFactory::CreateInstance

Here is the heart of our class object—the function that creates instances.

STDMETHODIMP CMyClassObject::CreateInstance (IUnknown *pUnkOuter,
   REFIID iid, void ** ppv)
{
   *ppv=NULL;

// Just say no to aggregation.
   if (pUnkOuter != NULL)
      return CLASS_E_NOAGGREGATION;

   //Create the object.
   CMyObject *pObj = new CMyObject();
   if (pObj == NULL)
      return E_OUTOFMEMORY;

   //Obtain the first interface pointer (which does an AddRef).
   HRESULT hr = pObj->QueryInterface(iid, ppv);

   // Delete the object if the interface is not available.
   //Assume the initial reference count was zero.
   if (FAILED(hr))
      delete pObj;
  
   return hr;
}

First off, we don't support aggregation, so if the pointer is non-NULL, we can't create the object because we're being asked to support aggregation. Next, we allocate the object and return E_OUTOFMEMORY if we can't allocate the object.

Next, we call QueryInterface on the newly created object to get an interface pointer to return. If this fails, we delete the object and return the error code. If it succeeds, we return the success code from QueryInterface. Note that QueryInterface will call AddRef if it succeeds, giving us a correct reference count for the object.

Note also that we did not increment the object and lock counter, g_cObjectsAndLocks. We could have done this if creation succeeded, but we would have to decrement it either in the instance object's Release or in its destructor. We'll put it in the destructor of the object itself—in Part 5. But if the decrement is in the destructor, then the increment should be in the constructor, not here.

There are a number of different patterns for doing the initial QueryInterface on the object, depending on how the object itself does its initial reference counting. An issue that comes up is that in some cases, an object will do something in the process of doing QueryInterface that causes the a pair of AddRef and Release calls to be executed. If the initial reference count of the object is zero, the call to Release will cause the object to free itself—even before CreateInstance returns. Not good.

One common technique is to set the object's initial reference count to one rather than zero. It's easy to do this in the object's constructor (see Part 5). If you do that, though, you have to modify CreateInstance to call Release after it calls QueryInterface so that the reference count will be set right.

If you do this, you omit deleting the object. If QueryInterface fails, it won't call AddRef—so the object's reference count will be one, not two. If you call Release at that point, the object's reference count will go to zero and the object will delete itself. If QueryInterface succeeds, it will increment the reference count to two and then the Release will decrement the reference count at one, where it should be for a healthy object.

If you assume that the initial reference count is one, you end up with CreateInstance code for the QueryInterface to the end that looks like this:

// ...

//Obtain the first interface pointer (which does an AddRef).
   HRESULT hr = pObj->QueryInterface(iid, ppv);

   // Delete object if interface not available.
   // Assume the initial referece count was one, not zero.
    pObj->Release(); // Back to one if QI OK, deletes if not

   return hr;
}

We'll use this code for our object in Part 5: it's simple, and it always works. The good doctor doesn't consider it a disadvantage that CreateInstance has to know implementation details about the object—after all, that's what CreateInstance is for: to encapsulate such details so the client doesn't have to worry about them.

IClassFactory::LockServer

LockServer merely increments and decrements the global lock and object count. It does not attempt to release the DLL when the count goes to zero. (If this was an EXE server, the server would be shut down when the count went to zero, provided there weren't any interactive users.)

STDMETHODIMP CMyClassObject::LockServer(BOOL fLock) {
   if (fLock)
      InterlockedIncrement(&g_cObjectsAndLocks);
   else
      InterlockedDecrement(&g_cObjectsAndLocks);
   return NOERROR;
}

Again, I've chosen to make this code thread safe. When the count goes to zero, the object can be deleted.

DllCanUnloadNow

COM will call DllCanUnloadNow to decide whether or not to unload a DLL. We simply return S_OK if it's okay to unload, S_FALSE if not. It's okay to unload if there are no objects or locks on the server.

STDAPI DllCanUnloadNow() {
   if (g_cObjectsAndLocks == 0)
     return S_OK;
   else
      return S_FALSE;
}

Where we've been; where we're going

So we've discussed part of how in-process objects are created and ways of making that creation more efficient. We also talked about the class object (also known as class factories) and how to implement one. But we didn't get around to actually implementing an object.

Next time, we'll talk about the guts of implementing an instance object, including the code you need for IUnknown and your own custom interfaces—and maybe even talk about special high-efficiency COM objects that don't use COM to be created.

A note: We've been using C++ to do these implementations, but you can also use C. The good doctor doesn't see any reason to (especially since mixed C and C++ programs are fine). But if you really want to for some reason, there are examples on MSDN, including the topic, "RectEnumerator in C: ENUMC.C," in Chapter Two of Inside OLE.

Your turn

Do you have any topics you'd like to see the good doctor address? You can write the Doc at drgui@microsoft.com. Although the good doctor's surgery schedule precludes individual replies, Dr. GUI does read and consider all mail.