COM Frameworks
Some tools are already available to help reduce the frustration induced by OLE and COM. For example, the Microsoft Foundation Class (MFC) framework is designed to make it as easy as possible for C++ developers to write applications and components that take advantage of OLE by providing a set of classes that encapsulate its use. Microsoft Visual Basic, the most popular Microsoft development tool, distances developers even further from the complexity of OLE. However, only with the release of the Visual Basic Custom Control Edition was it possible to develop COM objects using Visual Basic. Until then, Visual Basic had been a tool only for COM consumers, not for COM developers.
Certainly, developing COM objects using MFC and Visual Basic is much easier than trying to create them using raw C or C++. In fact, for all but the most advanced developers, attempting to face the wrath of COM without the shelter of a framework can be tantamount to developer suicide; those frameworks have been the only feasible approach. Along with the benevolent level of abstraction they provide, however, MFC and Visual Basic carry a fairly stiff penalty: they both saddle applications and components with a large run-time dynamic-link library (DLL). This extra freight inherent in MFC and Visual Basic has created a chasm between "lean and mean" COM development using straight C and C++ and the "fat, dumb, and happy" approach taken by those frameworks.
Galileo
A few years ago, in an attempt to provide solutions to the problems facing COM developers within Microsoft, several members of the Visual C++ development team were formed into a new team created to design the Enterprise edition of Visual C++, code-named Galileo. The new team determined that internal corporate developers were adopting a multitiered strategy wherein application logic is divided into roughly three layers: user services, business services, and data services, as described in the following sidebar.
Naturally, the Galileo team decided that COM would be the most instinctive method of communication between the layers of logic. However, they concluded that the COM objects developed for the business services layer—the middle tier—would need to be built with size and speed (rather than ease of development) in mind, even if it meant an increase in the time required to develop those objects. Furthermore, the objects would need to be capable of supporting access from many clients simultaneously. Keep in mind that at that time the Microsoft tools for COM development were entirely focused on simplicity rather than performance. In other words, when it came to addressing the COM needs of multitiered, multithreaded business applications (to say nothing of the even stricter needs of Internet applications), MFC and Visual Basic simply couldn't cut it.
To provide a middle-ground solution between lethargic OLE-centric frameworks and no framework at all, the Galileo team designed and created a small C++ library that would simplify COM development without sacrificing component size and speed.
The BaseCtl Framework
As tends to happen whenever developers on different teams strive to find better ways to solve the same sets of problems, another Microsoft team was simultaneously making similar inroads toward a lightweight C++-based COM framework. Specifically, members of the Visual Basic team were developing an in-house set of C++ classes for creating tight, quick OCXs (now known as ActiveX controls). Although their framework—eventually known as the ActiveX BaseCtl framework—was quite a bit more difficult to use than MFC (partially because BaseCtl wasn't really designed and documented for external use), it was still much easier to use than raw C++. Furthermore, its performance gains over MFC were impressive enough to warrant a great deal of excitement. Indeed, in early 1996, the BaseCtl framework was considered significant enough that it was released to the world as an undocumented (and unsupported) set of sample code.
The Active Template Library
The Galileo team incorporated many of the aspects of the BaseCtl framework into their own C++ library and named the resulting code the Active Template Library (ATL). Actually, at one time ATL was called the ActiveX Template Library, but rumor has it that the "X" rating was removed to make ATL suitable for young developers!
At first, ATL was available only to a lucky few within Microsoft. After a short time, however, so many internal developers were asking for permission to use the code and providing suggestions for future enhancements that Microsoft decided to make ATL available to everyone. ATL 1.0 was released to the Visual C++ Web site at the end of April 1996, and ATL 1.1 (which included a number of bug fixes and an improved set of documentation and sample code) was made available shortly thereafter. Eventually, after further enhancements—including support for developing various flavors of ActiveX controls—ATL became the tool of choice for developing lightweight COM objects. In fact, ATL 2.1 was made an officially supported part of Visual C++ 5.0, and wizard support for it was added to Microsoft Developer Studio. As of this writing, the most recent release of ATL is version 3.0, which ships with Visual C++ 6.0.
The Future of ATL and MFC
Although ATL certainly represents a significant milestone in the history of COM development, it doesn't signal the end of the line for MFC, despite the paranoia of some panic-stricken developers. When ATL was first released, nervous developers everywhere immediately began to ask some pointed questions: What is the future of MFC? Is Microsoft going to continue to improve and support MFC, or will ATL be positioned as its replacement? Because MFC has without question become the framework of choice for Windows development under C++, these are good questions. Indeed, because of the sheer number of developer-hours invested in MFC, any attempt to discontinue support for it would be met with a great deal of resistance, to say the least. Well, MFC developers can rest assured that Microsoft has no plans to abandon MFC anytime soon. The goals of MFC and ATL are different but complementary, so both should be around for a long time.
The Benefits and Drawbacks of MFC
The goal of MFC is to make it as easy as possible for C++ developers to create robust Windows applications without sacrificing application performance. For that reason, the MFC class wrappers around the Windows API are, by design, wide and shallow. In other words, although it encapsulates many different APIs, the abstraction layer between the MFC classes and the native Win32 functions under the hood is noticeably thin. For example, the CWnd::SendMessage function does nothing more than call the SendMessage function exposed by the Windows API. Although this approach is frustrating to some developers—especially those who want to develop Windows-based applications without having to invest the time to understand the ins and outs of the Windows programming model—it is most often a good trade-off. It simplifies application development without selling out on speed. Of course, applications built using MFC will suffer some performance penalty compared to those built with the raw Windows API, but for robust, large-scale applications, the maintainability of MFC code more than makes up for any slowdown in speed.
Unfortunately, MFC support for OLE and COM doesn't follow the same model as its support for the Windows API. Although MFC's window classes contain a substantial number of inline wrappers (à la CWnd ::SendMessage), its OLE classes aren't nearly as lightweight. As a result, MFC is less suited for middle-tier COM object development than it is for Windows development or even OLE development. Until recently, the COM limitations of MFC have been largely forgivable—ActiveX controls developed with MFC are perfectly adequate for use in end-user applications written in Visual Basic and MFC. And when it comes to traditional OLE support—OLE document servers, compound documents, containers, and so on—MFC is the best choice. However, the recent trend toward client/server Internet applications and the three-tier architecture described earlier has exposed two problems. First, even the smallest components created using MFC are generally considered too large for use within a Web browser, especially considering the time it takes to download the associated DLLs. Second, MFC is not well suited for creating server components that provide no user interface but must simultaneously support multiple threads of execution. Thus, whereas MFC still has something to offer future Windows applications, it falls short when it comes to developing COM objects in the business services tier.
ATL vs. MFC
In a way, ATL is to COM what MFC is to the Windows API. The goal of ATL is to provide a thin but effective wrapper around the most common COM interfaces without sacrificing component performance. Despite this similarity, however, the designs of MFC and ATL differ in several key ways:
- MFC contains an interconnected hierarchy of classes, whereas ATL is a set of disjoint templates. This difference means that with ATL you don't pay the size/speed penalty for a given feature unless your component actually uses it.
- MFC is linked to a project as a static library or a DLL, but ATL is compiled as source code. Because there are no OBJ files to link to, ATL requires no run-time DLL redistribution.1
- MFC supports a single-inheritance model, whereas the functionality of an ATL component depends entirely on the use of multiple inheritance. Specifically, a component that supports several different COM interfaces will inherit from several different associated ATL templates.
- Over time, MFC has grown considerably. As the expectations placed on Windows applications have increased, so have the size and feature set of MFC. Although a similar progression is likely as the use of ATL becomes more prevalent, ATL's use of templates rather than regular inheritance will almost assuredly prevent class proliferation.
A Framework by Any Other Name
If you were to study the scenery in the offices and cubicles of a thousand different Windows developers, you'd likely notice two recurring themes. First, you would see a shocking number of empty soda cans—after all, if the periodic table had been developed by programmers, it would have contained only three elements: carbonated water, sugar, and caffeine. Second, you would notice an abundance of MFC class hierarchy posters on the walls. These posters are likely as much status symbols as reference materials. MFC follows a very regimented, hierarchical, singly inherited class model. It doesn't take very long to see that just about everything inherits from CWnd, CObject, or CCmdTarget.
In stark contrast, ATL is composed almost entirely of independent base classes that are "templatized."(You hear that word a lot in the C++ community, even though our spelling checker doesn't like it.) It has been said that the best thing about the ATL framework is that it isn't a framework at all. Indeed, if an ATL poster is ever published, the class layout will be very, very flat.
The Component Object Model
You're reading this book because you've decided to learn about the Microsoft Active Template Library (ATL). Maybe your project is headed in that direction. Perhaps you've heard the acronym and have decided that ATL is the next technology in which to bury yourself. Or maybe you just want to make sure you and your development team are buzzword-compliant. Whatever your reason, before you perch yourself in front of this or any other ATL book to learn how to work with the framework, you need to be sure that you understand exactly how the Microsoft Component Object Model (COM) works.
Developers sometimes misconstrue the purpose of ATL and hope that it will save them from having to learn COM. Nothing could be further from the truth. It's true that once you learn the core concepts of COM, ATL will save you from a lot of typing while letting you retain control over the COM classes you write. Under the hood, however, ATL is just another way to wire COM interfaces to their implementations. You can get by just pumping out COM classes using the ATL COM Object Wizard and adding interface methods and attributes using the ClassView pane. But you'll be able to go only so far before you start bumping into the important hardcore issues related to COM. For example, you'll need to understand the importance of separating implementations from their interfaces. And if you avoid working through the issue of COM apartments, you won't be able to work effectively with the different threading models in COM (and don't even think of touching the Free Threaded Marshaler check box in the ATL Object Wizard property page).
In this chapter, we'll explore Microsoft's binary object model (COM and Distributed COM, or DCOM), looking at the fundamental concepts behind the technology and examining how COM works in a distributed environment. Our focus is on how COM and DCOM work with Microsoft Windows NT 4.0. We'll go over COM interfaces, COM classes, class objects, and apartments. We'll cover COM from the C++ perspective because that's the only way to really understand how COM works. To begin, we'll take a look at why COM exists in the first place and then work through each of COM's atoms.
The Software Game
The game of software has changed since the late 1980s and early 1990s. These days, robust software distribution (that is, objects everywhere—even on a network) is extremely important. For the past decade, developers have been writing applications using object-oriented languages and tools such as C++ and Delphi , among others. Using object-oriented programming languages has been a successful venture for most participants, giving rise to useful application frameworks and code libraries. These languages and tools are great for developing applications that exist on the desktop and that distribute information and functionality in limited ways.
Until now, sharing objects has traditionally been a source code proposition; that is, developing object-oriented software has involved confining the entire development team to a single development environment so that they could share the source code. With such a wide choice of languages and tools available to the contemporary developer, however, it doesn't make sense to use only one language to implement a large development project. For example, although tools such as C++ and the Microsoft Foundation Class (MFC) library are adequate for building user interfaces, they might not be the best tools for building infrastructure-type software. Microsoft Visual Basic is even better for writing user interfaces, but at the cost of losing some control. Raw C++ is a great language for writing infrastructure and lower-level software, but it makes writing user interfaces tedious.
Object models such as COM define binary standards so that different parts of an application can be developed independently. That way, an entire software project can be assembled from parts built using C, C++, Java, Visual Basic, or whatever language is popular at the moment.
In addition to software integration, COM is all about writing software that is easy to distribute—that's COM's primary function. COM basically takes the best parts of C++ and leaves out all the compiler-specific garbage imposed by the different compilers. In essence, COM formalizes the notion of interface-based programming.
C++ Templates
Recall that COM is really just Microsoft's brand of interface-based programming. The primary distinction between interface-based programming and class-based programming (the kind C++ programmers have been doing for the last decade) is that in interface-based programming a class's implementation is separated from its interface.
One downside of COM is that getting a COM class up and running requires substantial amounts of boilerplate code. To write a COM class, you need to perform the following tasks:
- Write the COM class, implementing IUnknown correctly.
- Add IDispatch (or a dual interface) if the object is to be available to a scripting environment.
- Write a class object (which implements IClassFactory in most cases).
- Add a reference-counting mechanism to the server.
- Add self-registration code to the server.
- Add the correct entry points if the server is a DLL: DllGetClassObject, DllCanUnloadNow, DllRegisterServer, and DllUnregisterServer.
- Add calls to CoRegisterClassObject if the server is an EXE.
As you can see, just as writing a regular Win32 SDK_type application requires reams of infrastructure code, so does writing a COM DLL or EXE involve a huge amount of infrastructure code that stays mostly the same from one COM class to another.
Code reuse has been the mantra of programmers since time began. Why should we have to retype lots of code for lots of different classes when most of the code is going to be the same in every class? With C++ templates, we don't have to.
Developers are always looking for ways to reduce the amount of typing they need to do. C and C++ developers have been using preprocessor macros to abstract and generalize code for years. If you write a macro, the preprocessor simply substitutes the code the macro defines whenever the macro is used in the code. Macros basically save you a whole bunch of typing. The biggest problem with macros is that they are often difficult to get right and to debug. C++ templates are like macros in that they also facilitate abstracting and generalizing C++ code. However, templates are C++ language-specific and are handled by the compiler (rather than the preprocessor) and so are type-safe.
In this chapter, we'll cover the philosophy behind C++ templates and explain how to use them. This information is important for you to know because Microsoft Active Template Library (ATL) is based heavily on C++ templates.
Conclusion
Templates are making their way into the programming world in a big way. Many developers restrict their use of templates to implementing collections and smart pointers, but templates are often useful for other purposes as well. In this chapter, we examined a way to employ templates that allow clients to change the behavior of a C++ class simply by passing in a template parameter when the template-based object is created. This capability is a testament to the power and flexibility of C++ when it's used correctly. Now that you're sufficiently grounded in templates, all the ATL source code that we'll sift through in the remainder of the book will make much more sense.