Q I'm definitely hooked on COM. I've spent some time doing low-level COM coding, but recently I have been doing most of my COM coding using ATL. A number of my coworkers, though, have an MFC background. To further complicate matters, I keep hearing how well Visual Basic® supports COM.
As a result of the Se different backgrounds, my coworkers and I have been debating the merits of using different environments to develop our COM-based software. When should we consider using either ATL or MFC in COM development? When should we consider using a mixture of both ATL and MFC, and what should the mixture look like? In addition, I know many developers who want to write COM-based software in Visual Basic. When should we use Visual Basic instead of C++?
Craig Randall
A You're not the Only one posing this question. We've wanted to address this issue for some time, since doing so provides an opportunity to explore the fundamental
philosophies underlying each technology. Let's start with pure COM.
COM is the Foundation
COM is primarily an integration technology that enables developers to get disparate pieces of binary software to work together at runtime. As long as the client and the Object can agree on some intermediate communication standard, then both sides are happy. Of course, in COM the communication protocol is the interfacea group of semantically related functions. What makes COM interfaces special is that they all start with the same three functions: QueryInterface, AddRef, and Releasecollectively known as the IUnknown interface. Having QueryInterface at the top of every COM interface allows developers to discover new interfaces at runtime and arbitrarily expand a client's connection to an object. This provides a smooth, forward-versioning mechanism. Software vendors can update their software without breaking clients. For example, if a vendor ships a new version of a component, it will maintain the Old interface as well as provide a new one. Clients are happy using the Old interface until they're ready to adopt the new one. This also makes it possible for the different parts of a software system to be developed independently of each other.
Designing and developing COM-based software is a discipline that requires you to focus on the interface between binary components first and on the implementation second. When you set out to write a COM class with any tool, the first thing to do is set forth the interface using Interface Description Language (IDL) like that shown in Figure 1.
Once this textual description of your interfaces is set up, you run the IDL through the MIDL compiler. The byproducts emitted from the MIDL compiler include a header file useful for developing C/C++ clients and objects, a header file that includes the GUIDs mentioned in the IDL file, and a type library (a .tlb file) that includes most of the information described in the IDL file. The type library is suitable for Visual Basic, Java, and any other language or development tool that supports COM.
It's good to hear you've spent some time developing COM code using raw C++. This is the Only way to really understand how COM works. Once you have COM under your belt, there are a wide variety of tools available so you can spend less time writing QueryInterface and more time reading Dilbert. the Se tools include raw C++, MFC, ATL, and Visual Basic. (Java is also a great language for developing COM-based software. However, the enhanced COM support in Microsoft® Visual J++ was still in flux at the time of this writing.) Let's take each language in turn and explore their relative merits and limitations.
C++ and COM
C++ is COM's assembly language. When you develop COM software in C++, you cannot escape any of the issues facing a COM developer; you're basically programming to the metal. Here are some of the Se considerations:
- Writing the COM class and implementing IUnknown correctly
- Adding IDispatch or dual interfaces if the Object is to be available to a scripting environment
- Writing a class object (and probably implementing IClassFactory)
- Adding a reference-counting mechanism to the server
- Adding self-registration code to the server
- If the server is a DLL, adding the correct entry points: DllGetClassObject, DllCanUnloadNow, DllRegister-Server, and DllUnregisterServer
- If the server is an EXE, adding calls to CoRegisterClassObject
- Selecting the appropriate threading model
For example, if you want to compose a COM class from the interfaces described above, you'd probably write a C++ class like the One shown in Figure 2. This COM class implements the ILoveCOM and IUseAnyTool interfaces through multiple inheritance of the interfaces. The C++ compiler looks at the inheritance list, sees base classes with virtual functions, and adds two vptrs to the CoTaoOfCPP class. Each vptr points to a function table matching the respective interface signatureswhich is exactly what the client wants to see.
Implementing IUnknown for the CoTaoOfCPP class is shown in Figure 3. the Other domain-specific functions (the functions that come along with ILoveCOM and IUseAnyTool) are omitted for clarity.
The most important thing to notice about this code is how QueryInterface works. The code examines the GUID requested by the client and then peels off the appropriate vptr (using static_cast) and hands the vptr back to the client. The AddRef and Release methods assume that this is a heap-based object. AddRef bumps up the reference count for each copy of a pointer to the Object. Release decrements the reference count and checks to see if the count is zero. If it is zero, Release gets rid of the C++ class.
One of the most interesting things about the task list required to write a COM server is the boilerplate nature
of the code involved. The changes needed between one
COM server and another are minor. When using C++ without the aid of a library, you are responsible for getting everything right.
Use C++ to write your COM software if: you're learning COM and you want to understand exactly how COM works, or you need the finest-grained control possible in your software. Again, the Only way to fully understand COM is to write some COM software using C++. You get to see exactly what's going on. In addition, when you write COM software using C++ you have absolute control over the behavior of your class. You lose some of that control when you employ a framework like MFC or ATL or runtime support à la Visual Basic or Java. You get all the flexibility when you use ATLyou just need to understand COM.
MFC and COM
MFC is for Windows®-based application developers. Once you understand the Windows programming model (that is, how WndProcs and window handles work), MFC saves you from having to write those big switch statements. In addition, MFC has a wide variety of useful abstractions like the document/view architecture, and user interface gadgets like dockable toolbars and status bars.
MFC came along in 1992 during the heyday of classic Petzold-style Windows development. In 1993 Microsoft released Visual C++ and a host of visually oriented development tools like AppWizard and ClassWizard. That same year COM was released to the general public under the guise of OLE2. Then MFC 1.5 included support for OLE, making it much easier to develop compound document applications as well as automation-based applications (as opposed to adding the Se features from scratch). However, COM and MFC were basically developed independently.
The key to understanding MFC's support for COM is to realize that MFC was a working framework before OLE and COM became a big deal. Microsoft had to figure out how to add COM support to the MFC framework, to which a large number of developers had already started programming. Microsoft added COM to the framework by inserting IUnknown support into CCmdTarget and tacking on a few more macros. For example, implementing the same COM class in MFC (ILoveCOM and IUseAnyTool) is shown in Figure 4 and Figure 5. Figure 4 shows that declaration of the class, while Figure 5 shows its implementation.
MFC uses the BEGIN_INTERFACE_PART and END_
INTERFACE_PART macros to add nested classes to the CCmdTarget-derived class. Then MFC implements IUnknown within the CCmdTarget class. MFC also has macros to define the class object (DECLARE_OLECREATE) and macros to declare the interface lookup mechanism used in CCmdTarget's implementation of QueryInterface.
Because MFC uses nested classes to implement COM classes, each nested class declared in the header file must have its own implementation of IUnknown. But that means each object needs to have a way of maintaining its own identity. Notice how the implementations of IUnknown forward back to the CCmdTarget-derived class. Figure 5
also shows the interface map (used for QueryInterface)
and the second half of the class object macro, IMPLEMENT_OLECREATE.
MFC is a useful framework for getting a large-scale application up and running very quickly. The AppWizard pumps out a full-blown application in just a few seconds, and the ClassWizard makes handling messages in the application a breeze.
Use MFC whenever you want to implement the Se large-scale, document-based applications (or even smaller dialog-based utilities). You'll also want to use MFC if you decide that OLE Document support is important to your applicationthe OLE Document stuff is a mess when done by hand. To complement MFC's support for OLE Documents, MFC has excellent support for OLE drag and drop. MFC also provides the hands-down easiest way to implement IDispatch dispatch maps in the ClassWizard's Automation tab. In addition, MFC provides the most convenient way to write ActiveX® controls using C++. However, be aware that the MFC DLL imposes a 1MB penalty at runtime since all of the nice Windows SDK support provided by MFC is found in that DLL. Plus, your application needs to haul that MFC DLL around wherever it goes.
So as far as COM-based development is concerned, buying into MFC gives you a bunch of high-level features right off the bat. However, embellishing or adding your own custom COM interfaces to the Se features is a rather tedious matter involving the usual smattering of MFC-based macros and the typing that goes along with the M. For example, if you want to add a custom interface to your MFC-based ActiveX control, you need to add the BEGIN_INTER-FACE_PART and END_INTERFACE_PART macros to define the nested class for the interface. You also need to add an entry in the interface map. All this is in addition to the actual functions in the interface. Moreover, adding other COM-based features in MFC ends up being difficultmainly because MFC was designed first as a Windows framework, with COM tacked on afterward.
ATL and COM
The Active Template Library framework is for developers who get tired of writing QueryInterface over and over again. ATL materialized during Microsoft's big Web-technology push (late 1995 and early 1996). Web pages provide a good opportunity to write script-based code, and COM objects are very useful within script-based code (or any code for that matter). It seems as though many ActiveX controls should have been able to find their homes on Web pages.
However, during that time, using MFC was the Only efficient means of writing an ActiveX control. Unfortunately, the size cost imposed by the MFC DLL made the MFC-based approach unsuitable for Web-based applications where distribution of actual bits is very important. Microsoft needed to provide developers with a way to write COM classes and ActiveX controls with a smaller footprint. ATL was born out of that need.
ATL was originally just four header files, one of which was empty. the Original ATL provided suitable implementations of IUnknown, IClassFactory, IDispatch, COM
Connections, and some COM enumeration support. Later on, ATL included higher-level support for implementing ActiveX controls.
Whereas MFC tacked COM interfaces onto a C++ class via nested classes and macros, ATL tacks interfaces onto a C++ class by mixing in template-based implementations of interfaces. Figure 6 shows the ILoveCOM and the IUseAnyTool interfaces applied to an ATL-based class.
ATL is great for writing smaller COM classes and ActiveX controls. The ATL wizards can get you up and running very quickly. Once you have a COM server built, adding COM classes and controls to the server is easy. The wizards allow you to select options like implementing connection points and ISupportErrorInfo. If you choose the multithreading option for your COM object, ATL can only automatically provide synchronization support for the class factory. You'll need to protect global and member data. For more information, see the documentation for CComAutoCriticalSection.
Applying new interfaces to your COM class is a matter of adding the interface to the IDL and including the inheritance list of your ATL-based C++ class. If you do it right, the wizards will even keep the interface and the implementation in synch as you add methods.
Writing an ActiveX control using ATL is also possible. Just create an ATL-based DLL and add a control using the wizard. ATL provides a reasonable implementation for ActiveX controls. Creating a full control pins some 12 interfaces (above and beyond IUnknown and IDispatch) onto the control's C++ class implementation.
ATL is useful for creating components (as opposed to writing larger-scale applications). Use it when you want to build a large application out of smaller, more manageable pieces. That's what COM is all about, and ATL reflects that philosophy in its design and implementation. ATL also includes some basic support for SDK-style programming.
ATL Versus MFC
Unfortunately, many folks look at the two frameworks and make the following conclusion: ATL and MFC both support COM in some way, so they must be competitors. Generally, this is not true. ATL and MFC share the most ground in their implementation of ActiveX controls. Both frameworks make it easier to create ActiveX controls (as opposed to writing the M from scratch). However, MFC's approach is much more wizard-based and relies on the MFC infrastructure. In some ways, MFC is beneficial because it provides some nice wrappers around the Windows SDK. However, it also locks you into the MFC architecture.
The ATL approach also involves using some wizards. But developing an ActiveX control in ATL requires you to be much more COM-awareespecially when you decide to move beyond simple COM classes with single interfaces. Using ATL involves using the raw Windows API. When you decide to use the Windows API, you're back to dealing with handles and so forth, and ATL just doesn't have the same type of wrapping around the Windows SDK. That's not all bad (especially if you're used to the Windows SDK), but be aware of this issue as you choose your tools.
|