t's hard to believe that five years have passed since the COM specification was drafted. And while a significant number of Visual C++® developers are using COM to improve their applications, there are still many who are not. Since nearly every developer I've talked to is interested in the benefits of COMlanguage independence, distributed deployment, and runtime polymorphism, to name a fewI have concluded that the reason for the slow migration is that converting to COM is unnecessarily difficult.
It's one thing to think of COM as a great application model, it's another thing entirely to have the resources and know-how to develop COM-centric C++ applications. On the other hand, if you're programming with Visual Basic®, the paradigm shift probably hasn't been all that difficult because the runtime services provided by that language take care of most of the COM details. Obviously, there is a very wide range
of applications that don't fit the Visual Basic bailiwick.
Indeed, none of the
off-the-shelf applications I use were written with Visual Basic. From what I can tell, they were all written with
Visual C++.
Let's review some of the difficulties facing developers who employ C++ and COM today and discuss several ways in which COM+a not-yet-released set of operating system services and runtime DLLspromises to overcome those difficulties. Although COM+ will surely benefit all developers, whatever language they're using, I will focus on those benefits from the point of view of a C++ developer.
Developing COM Objects in C++
Arguably, programmers using Visual C++ haven't really had much help developing applications using COM. By and large, the handful of COM wrapper frameworks (like Active Template Library, or ATL) that exist are perceived as being either too difficult for beginning and intermediate developers or too cumbersome for the experts (MFC). Even though some developers blame the lack of quality COM support in C++ on shortcomings in ATL or MFC, I do not. In my opinion, the single greatest obstacle is simply that a C++ object is not inherently a COM object. I'm not saying that there should be one-to-one correlation between the two, because COM is a language-independent binary specification and C++ is not. But if you're using C++ to develop COM components that represent various elements in your application's problem domain, you'll likely find that a significant portion of your
code has nothing to do with the problem at all. Instead,
much of it is boilerplate code you must provide so that the COM subsystem knows how to create and manage instances of your objects.
Take a look at the source code for a bare-bones Time Server component I wrote some time ago (see Figure 1). This object implements a single custom interfaceITimeOfDaythat does nothing more than return the current time and date via two methods, GetTimeString and GetDateString. As you can see, even though this simple object exposes only one interface (in addition to IUnknown, of course), I had to write over 350 lines of code. Even if you don't count the 375 lines of code generated by the MIDL compiler when it processes the timesvr.idl file, the actual implementation of the GetTimeString and GetDateString interface methods still only represents one-fifth of the total source code!
The chief advantage of using raw C++ when developing COM objects is that you have complete control over every nuance of their implementation. If you want to alter the way the IClassFactory2 interface implementation creates your objects, for example, you are free to do so without restriction because you "own" all of the code. Furthermore, despite the relatively high boilerplate-to-innovative source code ratio, COM objects written in raw C++ do not need to be linked with bulky third-party source libraries or runtime DLLs, so they can be pedal-to-the-metal lean and mean. The downside of developing COM objects from scratch is that you
sacrifice some of the fringe benefits of traditional object-oriented development in the processnamely, source code reuse and abstraction.
The Effects of Language
It has been said that creating COM objects using raw C++ is akin to writing traditional software using assembly language. That statement reminds me of a discussion I recently had with a dyed-in-the-wool assembly language programmer who is quite upset about all of the ballyhoo surrounding C++ and Java. Her gripe is that people have the mistaken notion that you can't do object-oriented development unless you are using one of those languages. In her words, there is nothing you can do in C++ or Java that you haven't been able to do for the last 15 years using assembly language. While I don't disagreeall code must be converted to CPU instructions at some point, after allI do believe that the constructs of a programming language affect the characteristics of the programs it is used to create. In other words, a well-versed programmer who sets out to build a particular application will arrive at a different result if she uses C++ than if she uses Visual Basicor Java, Fortran, or assembly language for that matter. As evidence to support that idea, consider that semantics affect composition in spoken languages as well. In Spanish, for example, you can often place "-ito" or "-ita" at the end of a noun in a way that adds richness to its meaning. Changing "abuela" to "abuelita" transforms the meaning of the word from "grandma" to something akin to "dear, sweet granny." Using the word "casita" instead of "casa" to describe the place where you live invokes a difference in connotation comparable to using the word "home" instead of "house."
Just as spoken languages provide building blocks that allow rich expression, good programming languages provide constructs that unleash developer creativity, empowering you to solve difficult problems and build efficient systems. Java aficionados talk about how the garbage collection and native thread support provided by that language allow developers to create much more sophisticated applications than they could using languages encumbered by the problems
associated with traditional thread synchronization and memory management techniques.
So what does any of this have to do with developing and using COM objects in C++? Because the language of COMits data types, definitions, and mechanismsis different from C++, developers must write code to translate between the two languages. In other words, although it makes sense to implement COM objects as C++ objects, there are a number of tedious COM-specific tasks to worry about that are only loosely related to the functionality provided by the component. These tasks include class factories, stock interfaces (IUnknown, IDispatch), component registration, lifetime management (CoCreateInstance, AddRef, Release), IDL scripting, connection points and events, data type conversion (such as char* to BSTR), and implementation inheritance.
ATL to the Rescue?
Even if nothing is lost in the translation, it often takes more work than it should to create full-featured COM objects using C++. Although the principles of COM are sound, simple, and elegant, learning the myriad interfaces most commonly associated with real-world COM objectsIDispatch, IClassFactory2, IOleControl, and so forthcan be an overwhelming task. Fortunately, you can use the ATL to significantly reduce the amount of time you spend implementing and reimplementing those "stock" interfaces.
To illustrate this point, I took the Time Server sample from Figure 1 and reimplemented it using ATL (see Figure 2). Since I used the ATL COM AppWizard and ATL Object Wizard, I hardly had to write any code at all. Even if I'd written all of the code by hand, I would only have had to write a third as many lines of code as the raw C++ sample. Furthermore, using the helper templates and macros provided by ATL, I was able to expose ITimeOfDay as a dual interfaceso that I could use it as an Active Server Pages componentwith only a few extra lines of code.
I'm a big fan of ATL because it simplifies COM development considerably with almost no overhead. It removes the drudgery from COM development by encapsulating most of the problematic tasks I outlined above. But ATL isn't perfect. It requires you to use cryptic macros and complicated templates, thereby reducing the readability of your C++ classes. And because the ATL COM AppWizard superficially simplifies things by automatically placing many of those constructs in the code it generates, many C++ developers begin using ATL without taking the time to learn how COM actually works. Without a basic understanding of COM and the way it is implemented in ATL, developers find themselves hopelessly lost as soon as they stray from the narrow path of the wizard-generated code.
The Promise of COM+
Simply stated, the goal of COM+ is to make developing COM-based components and applications easier. That's a rather general and broad-reaching goal, but much of it will be achieved by supplying runtime services that provide a default implementation of each of the problems I outlined above. Unlike ATL or MFC, which require developers to derive classes from a particular framework or sprinkle their code with macros, the COM+ runtime will provide services directly to the compiler.
In other words, you'll be able to simply create a C++ classperhaps it will be called a coclass to distinguish it from a vanilla C++ classand the compiler will plumb the calls to the COM+ runtime behind the scenes. To make life easier, you won't be required to provide a class factory, write special code to register the class, or manage its lifetime (à la AddRef and Release). You'll be able to concentrate your energies on finding solutions to the problems you're being paid to solve using the language of your choice (C++ in this case, right?). To illustrate this point, here's the entire header file for the
Time Server sample as it might look if it were implemented in COM+: