George Shepherd is a Senior Software Engineer at Stingray Software where he develops Visual CASE. Scot Wingo is cofounder of Stingray Software (www.stingsoft.com), an MFC/Java class library company. George and Scot co-wrote MFC Internals (Addison-Wesley, 1996).|
|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 these 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++?
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. These 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 these considerations:
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.
- 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
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 these 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 these 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 these features is a rather tedious matter involving the usual smattering of MFC-based macros and the typing that goes along with them. 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 them 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.
Figure 7 MFC in ATL
One of the checkboxes that is available in the ATL COM AppWizard lets you add MFC support to ATL (see Figure 7). Choose this option with care. By bringing MFC support into your ATL-based project, you defeat many of the reasons for choosing ATL in the first place. Checking this box causes the AppWizard to add AFXWIN.H and AFXDISP.H to the STDAFX.H file.
This option also links your project to the MFC library.
Why would you want to mix MFC with ATL? Well, MFC provides an easy-to-use dialog box mechanism. MFC makes handling messages a breeze via ClassWizard, and has nice wrapper classes for the Windows API. In addition, MFC has that really useful CString class. You need to decide whether these conveniences are worth linking your ATL-based server to the MFC DLL. In general, we recommend most developers skip this option and find some other way to implement these MFC-style conveniences.
While ATL takes care of much of the grunge for you, it doesn't relieve you of understanding how COM works.
For example, if you decide you want to play with the threading models or want to start composing COM classes in any advanced way (such as using tearoffs or aggregation), you need to understand the principles behind COM.
Visual Basic and COM
For developers who want to spend even less time coding, there's Visual Basic. Of course, we say this facetiously. Visual Basic often gets a bum rap around the C++ camp. Originally developed as a quick way to give developers a foothold into the Windows-based development world without having to read Charles Petzold's book, Visual Basic hides the nuances of Windows development behind a powerful, easy-to-use graphical interface.
Visual Basic has evolved considerably since its inception. These days, it is a viable tool for creating ActiveX controls, documents, and generally useful Windows-based applications. You can even use it to create plain vanilla COM classes like the ones we've been looking at in this column.
For example, if you want to implement the ILoveCOM and IUseAnyTool interfaces in a Visual Basic class, just choose either ActiveX DLL or ActiveX EXE from the Project menu. This automatically creates a COM server with the correct infrastructure. Select References from the Project menu to read in the type information produced by the MIDL compiler after compiling the IDL found in Figure 1.
If the project doesn't already have a class module in it, add a class to the project. Then add the interfaces to the class by using the Implements keyword in the General section of the class code:
This tells Visual Basic to add the ILoveCOM and IUseAnyTool interfaces to the Visual Basic class. Then all you need to do is implement each of the member functions. Just add the subroutines shown in Figure 8 to your class.
At this point, Visual Basic handles the grunge work like QueryInterface, reference counting, and server lifetime issues. Visual Basic provides great client-side support too, although most of it happens behind the scenes. For example, to use COM classes in a Visual Basic-based application, first read in a class's type information. (Create a reference to the type library using the Project | Reference command.) Once Visual Basic has read in the type information, it becomes aware of any coclasses and interfaces in the library. When you start typing
Visual Basic immediately begins to fill out the rest of the statement using IntelliSense®. For example, if the type library you read in has a COM class named TaoOfVB, Visual Basic will fill out the statement for you:
Typing the activation function call
causes Visual Basic to perform a CoCreateInstance and ask for the class's default interface.
If the COM class implements other interfaces (as advertised in the type information), the Visual Basic client code can perform a QueryInterface by simply using this assignment symbol:
Dim loveCOM as ILoveCOM
Set foo = new TaoOfVB
Set loveCOM = foo
At this point you are free to invoke the methods on the ILoveCOM interface.
Use Visual Basic if getting your software into production in a short time is your biggest concern. Visual Basic lets you bypass the oddities of Windows and COM development, allowing you to pay closer attention to the big problems. The downside of Visual Basic is that you lose a degree of flexibility when developing applications and components. For example, when developing and using COM classes, Visual Basic tends to blur the distinction between interface and implementation. In contrast, distinguishing these two elements lies at the heart of COM.
Choosing the COM lifestyle is a good decision for most developers. Once you start using COM and understand interface-based development, you won't be able to go back. When it comes to writing COM classes in C++, you have several paths to choose from. You may choose to meet COM head-on with C++. This is greatespecially if you want complete control over the behavior of your COM class.
There are two major frameworks that support COM: MFC and ATL. MFC is good for developing Windows-based applications that have high-level COM features. MFC provides support for higher-level features like OLE Documents, OLE Drag and Drop, and ActiveX controls. However, if you want to do anything more with COM, you need to seriously bend the framework (or at least end up typing in those hideous macros).
In contrast, ATL was built with COM support in mind. ATL handles IUnknown, IClassFactory, IDispatch, and the other common COM idioms really well. New interfaces are added to an ATL-based class via inheritance. Consequently, an ATL-based class is often much easier on the eyes than an MFC-based COM class. In addition, ATL provides full support for different class composition techniques like aggregation, containment, and tearoff interfaces.
Visual Basic and Java offer higher-level means of writing and using COM classes. Unlike C++ and frameworks like MFC and ATL, using Visual Basic does not require a deep understanding of the principles behind COM. In fact, it encourages the melding of interface and implementation.
Have a question about programming in
Visual Basic, Visual FoxPro, Microsoft
Access, Office, or stuff like that? Send
your questions via email to George Shepherd at firstname.lastname@example.org.
© 1998 Microsoft Corporation. All rights reserved. Legal Notices.