April 1999
Code for this article: April99VP.exe (53KB)
George Shepherd is a Senior Software Engineer at Stingray Software where he develops Visual CASE. He co-wrote MFC Internals (Addison-Wesley, 1996).
|
Q I'm really comfortable writing single COM classes using ATL. I'm also a seasoned MFC developer and have spent a lot of time writing ActiveX® controls using MFC. ATL lets me generate all sorts of controls, whereas MFC lets me generate only one kind of control. What's the difference between each of the types of controls I can create using ATL? Many readers A Back in 1996 when ATL first came along, there were no wizards to generate objectsyou had to do it all by hand. When the next version of ATL came out in 1997, Visual Studio® provided a number of different wizards for generating controls. Now Visual C++ 6.0® produces even more controls. ATL basically produces six kinds of controls: a Full control, a Composite control, an HTML control, and Lite versions of these controls. The Visual C++ documentation describes how these controls differ in very general terms. For example, the documentation says a Full control is an object that supports the interfaces for all containers. A Lite control supports the interfaces needed by Microsoft® Internet Explorer and includes support for a user interface. A Composite control consists of a dialog resource hosting a group of regular Windows® controls. An HTML control contains DHTML functionality. Its interface is generated by an HTML Web page. A Lite Composite control is a Composite control that supports only the interfaces needed by Internet Explorer. Finally, a Lite HTML control is a stripped-down HTML control supporting interfaces required by Internet Explorer. Unfortunately, this description doesn't tell the whole story. So let's take a deeper look at exactly what's at the heart of each control. A Full Control
In the old days of COM (1994), Microsoft defined a technology named OLE controls. OLE controls were COM classes that implemented a host of interfacesmany of them for supporting the OLE document protocol. In 1996, the OC96 protocol emerged. OC96 changed the definition of a COM-based control from one of those gargantuan COM classes implementing a ton of interfaces to a COM class implementing only IUnknown. Microsoft redefined the terms of a control because the size of these executable objects had become a real issue in a browser-centric world. All the classic OLE control interfaces were still validthey had simply become options. Unfortunately, such a broad definition began confusing developers. In addition to the standard OLE control features (embedding, connection points, incoming dispatch interfaces, and so on), the OC96 specification defines such features as delayed activation, windowless controls, hit detection for windowless controls, and quick activation. Most of these features represent optimizations to increase the efficiency and reduce the size of a control. For example, by employing delayed activation a control may be instantiated by its container and wait to execute the expensive OLE embedding protocol until the container asks the control to activate. ActiveX controls have historically had their own windows, adding to the instantiation overhead. (The control has to go through the trouble of creating its own window.) A windowless ActiveX control doesn't have to create its own window, instead relying on the client's window by communicating with the client through the IOleInPlaceObjectWindowless and IOleInPlaceSiteWindowless interfaces. Finally, the 1996 control specification defines a means for clients to quickly activate controls through the IQuickActivate interface. Instead of making several round-trips to activate a control, clients can activate a control in one round-trip. A Full control implements the entire OLE document protocol as well as all the interfaces necessary for the object to be a control. When you select Full control from the ATL Control Wizard, ATL gives you a control with the entire activation and embedding protocol added. Figure 1 shows the code generated by the Control Wizard when asked to create a Full control. This full-blown control implements all of the interfaces necessary to house the control in pretty much any container you can find. Scanning the control's inheritance list and interface map reveals that the control implements IFullATLControl, IDispatch, IViewObject, IViewObject2, IViewObjectEx, IPersistStreamInit, IOleInPlaceObjectWindowless, IOleInPlaceObject, IOleWindow, IOleInPlaceActiveObject, IOleControl, IOleObject, IPersistStream, ISpecifyPropertyPages, IQuickActivate, IPersistStorage, IDataObject, IProvideClassInfo, and IProvideClassInfo2. (Notice that connection point support is not in this listthat's something you can add easily.) The IViewObjectxxx, IOleObject, IOleInPlaceObject, IOleInPlaceActiveObject, IPersistStorage, and IDataObject interfaces represent the OLE document-ness of the control. This control implements the entire OLE embedding protocol, supports the full OLE control protocol, supports the container in querying for property pages, can persist to streams and storages, and, finally, can provide type information to the client upon demand. That's a pretty tall order. Let's take a look at what gets left out of a control when you create a Lite control. The Lite Control
Figure 2 shows the code generated by the ATL Control Wizard when you select Lite Control. Notice that the Lite control's interface is substantially shorter than the Full control. It supports all the control-oriented interface implementations, but not all the OLE document interface implementations.
Figure 3 shows the difference between a Full control and a Lite control. You can see that Internet Explorer doesn't require ISpecifyPropertyPages, IQuickActivate, IPersistStorage, IDataObject, IProvideClassInfo, or IProvideClassInfo2 in the Lite control. ISpecifyPropertyPages allows a client (like Visual Basic®) to interrogate the control for a list of property page GUIDs so the client can host those pages in its own property dialog. IQuickActivate allows clients to load and initialize controls in one round-trip. IPersistStorage is part of the whole OLE document protocol and allows objects to persist themselves to a structured storage provided by the client. IDataObject is also part of the OLE document protocol proper and is used mainly to transfer presentations (often in the form of a metafile) between a client and an object living in different process spaces. Finally, IProvideClassInfo2 gives clients a way to easily get to the type information of a control. As it turns out, these interfaces are really there to support a separate design mode that's often required by developers using Visual Basic. But when developers write controls to send over the Internet, they rarely require a separate design mode, and can make do with the Lite control. The size of a bare-bones Full control using a Unicode release build is 36864KB. A bare-bones Lite control using a Unicode release build is 32768KB. Composite and Lite Composite Controls
An ATL Composite control is a relatively straightforward concept. It's an ActiveX control whose presentation space is represented by a dialog box. Over the years, many folks have used this idea to collect several controls together and make it easy to drop the functionality into an application. However, up until now they've had to roll everything by hand. The ATL Composite control is a COM object supporting all the COM control interfaces, and it manages a dialog box full of controls. Figure 4 shows the code generated to create a Composite control. The Composite control implements the same interfaces as the Full control. The Composite control manages a bunch of regular Windows controls as well. Notice that the Composite control derives from CComCompositeControl (instead of the plain-vanilla CComControl). CComComposite-
Control has all the extra code for managing the control's dialog box. A Lite Composite control is a Composite control on a diet. As with a regular Composite control, the Lite version derives from CComCompositeControl, so that it can collect some regular Windows controls in a dialog box and manage the dialog box. Figure 5 shows the inheritance list and interface map for an ATL Lite Composite control. The difference between a regular Composite control and a Lite Composite control is similar to the difference between a Full control and a Lite control. The Full version supports the design-time interface for property pages and the OLE embedding support, while the Lite version leaves out those interfaces and that functionality. The regular bare-bones Composite control compiles to 45056KB, while the bare-bones Lite version compiles to 40960KB. HTML and Lite HTML Controls
An HTML control is an ActiveX control that hosts the standard WebBrowser control, specifies its user interface using HTML, and can access the browser programmatically using IWebBrowser2 (implemented by the WebBrowser control). You'll find the inheritance list and interface map generated by the ATL Control Wizard for a Full HTML control in Figure 6. The presentation and UI for this control is driven by HTML. The HTML source is included in the control's resource. The HTML control loads the HTML into the browser when the control's window is created. As with the other types of controls, the HTML control also has a stripped-down version. The Lite HTML control performs the same functionality as the Full HTML control. Figure 7 shows the inheritance list and interface map generated by the ATL Control Wizard for a default Lite HTML control. The Lite version of the HTML control simply omits the design-time support. A bare-bones Full HTML clocks in at 40960KB, while a Lite HTML control clocks in at 36864KB.
Q I recently heard that connection points exist entirely for type-less scripting languages. I need to implement bidirectional communication using Visual Basic. How can I do this without connection points?
A You heard incorrectly. Connection points offer a very general, albeit slower, means of notification than some privately defined event mechanism. COM's connection point interfaces represent an interesting part of COM. They were invented a number of years ago as part of the OLE control architecture. Rather than enabling bidirectional communication, COM's connection point architecture simply represents a means of establishing a bidirectional communication connection once the client and the object agree on the contract. COM has supported bidirectional communication since day one. All you really need to establish bidirectional communication is to have the client and the object agree on an interface that the object can use to call back to the client. Connection Points
The idea behind connection points is that they support a well-known way for clients and objects to establish a connection. There are two interfaces involved in establishing this connection: IConnectionPointContainer and IConnectionPoint. Clients find out if objects support connections by first asking for IConnectionPointContainer (through a normal call to QueryInterface). If an object supports IConnectionPointContainer for that sink, then the client can ask to establish a connection using a certain interface using IConnectionPoint. A client can use IConnectionPoint to get a callback interface over to the object. Once the object has the client's interface, the object can call back to the client. There are advantages and disadvantages in using connection points to establish a connection between a client and an object. One advantage is that they provide a way for scripting clients to receive events from the objects they host. For example, if you're writing some VBScript code, you don't have to do very much to get events back from an object if the object supports connection points. All you have to do is create an object and mention that it supports callbacks using the WithEvents keyword. Another advantage of connection points (if you're programming using C++) is that they provide a way for clients to negotiate callback interfaces, very much like QueryInterface. That is, a client can choose to receive callbacks dynamically at runtime. However, the client would need to implement these event methods also at runtime. A downside of connection points is that they are complex to understand and implement. While MFC automatically supports connection points in an ActiveX control, adding more than the stock connection points requires understanding how connection points work and how MFC implements them using macros. If you're programming in raw C++, you can always implement them yourself, but that requires a lot of coding as well. Another drawback is that connection points are very inefficient. The original Microsoft connection point interfaces were obviously designed to be used when the client and object lived in the same apartment. Establishing a connection using connection points requires five round-trips to set it up and four round-trips to tear it down. If you're positive that your client and object will always live in the same apartment, it's not a big deal. However, if your object and client ever happen to live in a different apartment, crossing that apartment boundary five times to connect a client and an object is an unfortunate waste of clock cycles. Let's look at a more efficient way to hook a client and an object together using Visual Basic without connection points through a sample control. The Control
This sample contains a simple ATL-based control that tracks message traffic. It installs a window hook to trap window messages and keeps a count of them during regular intervals. The default case is 400 milliseconds, although that interval may be changed easily. The message traffic monitor is a Full control that supports the entire control specification, so it may be used easily within Visual Basic. The control presents a simple graph showing the message traffic over regular intervals. The control supports both regular COM connections as well as its own custom bidirectional communication mechanism, so you can compare the two methods of establishing bidirectional communication. Even though the tool you're using is Visual Basic, you're really looking at how to get COM objects and clients into a conversation. All COM development (regardless of the tool) begins with some IDL. ATL's COM Wizard and Control Wizard easily generate some source code to start off with. Part of the source code generated by the wizards includes some IDL. The bare-bones IDL includes an empty incoming interface definition, a library block, a default outgoing interface, and a COM class block. Figure 8 shows the IDL for the control. Notice there are two callback interfaces. One is a dispatch interface that's to be used with connection points (_IATLMsgTrafficCtlEvents). The second is a custom COM interface (IATLMsgTrafficCtlEventsCustom). The ATLMsgTrafficCtl's coclass statement lists both callback interfaces using the [source] attribute. This designates the interfaces as outgoing interfaces. The [default] attribute on the _IATLMsgTraffic CtlEvents interface designates _IATLMsgTrafficCtlEvents as the default outgoing interface that Visual Basic will use to establish the connection using connection points. |
Figure 9 Hosting ATLMsgTrafficCtl |
The client application is a simple form-based app that holds the control and provides two listboxes to monitor the two event sets. Figure 9 shows the Visual Basic form.
Implementing the Callback Interfaces
There are two callback interfaces to implement in this application. One is the normal callback interface _IATLMsgTrafficCtlEvents. This one is a no-brainer because it's the interface defined in the IDL as the default outgoing interface. To implement this interface, all you need to do is select ATLMsgTrafficCtl1 from the combobox on the top-left side of the form while editing. The callback functions appear in the combobox on the right-hand side of the form editor. Selecting the functions listed in the right-hand combobox causes Visual Basic to stub out the functions as subroutines within the form. Visual Basic understands the signatures of the callback interfaces because it consults
the control's type library. _IATLMsgTrafficCtlEvents is the default outgoing interface for the control, thus Visual Basic will automatically use connection points to hook up the two objects.
The second outgoing interface needs to be set up manually. First, you need to make sure your form implements that interface. To make this happen, simply use the Implements keyword at the top of the form's source code. Tell Visual Basic you want the form to implement IATLMsgTrafficCtlEventsCustom. Once you've added the interface to the form, it will appear in the combobox on the left-hand side of the form editor. Select IATLMsgTrafficCtlEventsCustom from the combobox on the left-hand side of the screen. IATLMsgTrafficCtlEventsCustom's two functions will appear in the combobox on the right-hand side of the form editor. Selecting the functions from the combobox causes Visual Basic to stub out the functions.
Once the interface is implemented on the form, you need to hook up the client and the object. ATLMsgTrafficCtl's main incoming interface has a function to do this. (Go back and take another peek at the IDL for the control.) That function is named Advise. It takes an IATLMsgTrafficCtlEventsCustom interface pointer as an in parameter and a long integer as an out parameter. The long integer is a cookie that the form uses later to disconnect. Connecting the client and the object is simply a matter of passing the form's IUnknown pointer and a long variable to receive the cookie. A good place to do this is when the form loads.
You may pass the Visual Basic form's IUnknown pointer over to the control by referring to the form itself in the parameter list. The keyword for doing this is Me. Figure 10 shows the entire source code for the form hosting the message traffic control.
As you can see, even with Visual Basic it's not strictly necessary that you use connection points to establish bidirectional communication. You can easily set up bidirectional communication as long as you provide a way for the client to get an interface pointer over to the object.
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 70023.1000@compuserve.com.
From the April 1999 issue of Microsoft Systems Journal