Building ActiveX Controls for Internet Explorer
This article covers features of Windows Internet Explorer that a developer writing Microsoft ActiveX Controls should take into account when targeting Internet Explorer as a container. Some of the material in this article discusses existing technologies that have increased relevance with Internet Explorer; other sections discuss new technologies and services that are available only to a control in Internet Explorer. For an in-depth description of these technologies and reference documentation, the Internet Client SDK is the best source of information. To get the most out of this article, the reader should already be familiar ActiveX Controls and associated Component Object Model (COM) technologies.
- Justification for Targeting Internet Explorer as an ActiveX Control Container
- Apartment-Model Awareness
- OC96 and Windowless Controls
- Accessing Dynamic HTML
- Authoring Controls for the Active Desktop
- Additional Guidelines
Justification for Targeting Internet Explorer as an ActiveX Control Container
Internet Explorer is built on a foundation of reusable and extensible components; two of the main components—the WebBrowser Control (SHDOCVW) and MSHTML—are available for reuse as part of other applications. An increasing number of Microsoft and non-Microsoft applications will be using the same underlying control container. Current examples of this component reuse in the Internet Explorer suite include the Microsoft Active Desktop and Microsoft Outlook Express. Both use the same HTML rendering engine as Internet Explorer. For more information about reusing browser components, see the About the Browser overview.
Designers of any binary extension for Internet Explorer should ensure that their components are as secure as possible. For more information on designing secure ActiveX controls, see Designing Secure ActiveX Controls.
Apartment-model awareness is an important issue because of architectural changes in the multithreaded nature of containers such as Internet Explorer, the Active Desktop, and Outlook Express. Multiple instances of these containers are now quite common, so ActiveX Controls should be developed to conform to apartment-model rules to ensure that they behave correctly.
If a control is not apartment-model aware, the control might not function correctly. At the very least, it will suffer from degraded performance because calls to the control will be marshaled. This is due to the creation of the control being handled differently by the system if the control marks itself as being apartment-model-aware. Internet Explorer will also reject improper calls across thread boundaries.
There are two relatively simple steps involved in making a control apartment-model-aware:
Protect global resources. When data that is shared among instances of the control is being accessed, it should be protected so that other instances of the control do not attempt to access it at the same time. For example:
::EnterCriticalSection(&g_cs); // Access global data // .... ::LeaveCriticalSection( &g_cs );
Mark the control in the registry as "Apartment". This indicates to the system that the control understands and conforms to apartment-model rules and causes COM to create the control in its own apartment.
For further details about single and multi-threaded apartment models, see INFO: Descriptions and Workings of OLE Threading Models in the Microsoft Knowledge Base.
OC96 and Windowless Controls
Full support of the OC96 (or OLE Controls '96) specification began appearing in common containers such as Microsoft Office 97 and Microsoft Internet Explorer 4.0. One of the primary driving forces behind this specification was to improve the performance of controls for use on the Internet. An additional key feature is support for windowless controls. Windowless controls not only provide better performance; they also participate in overlapping 2-D layout, can maintain a specific z-order, and can exhibit transparency as well as irregular shapes. Visual Filter effects will function only on components that support the windowless control specification, because traditional windowed controls don't provide the hooks required for filters to affect painting. All the enhancements are designed to be compatible with existing OLE Controls technology, so a control can use these new features while maintaining backward compatibility with down-level containers.
Key features of OC96 include:
- Delayed activation. Delaying the activation of a control means that a page can instantiate and be ready for interaction with the user faster, particularly if there are multiple controls on the page. A new interface, IPointerInactive, allows a control to remain inactive but still interact minimally with the mouse and drag-and-drop operations. Although the control still activates, it does so only as the result of some interaction with the user.
- Windowless. By supporting the windowless control specification, a control can make efficient use of its container's window rather than having a window of its own. This results in using fewer system resources and improves performance through faster activation/deactivation. The new interfaces introduced to support windowless controls include IOleInPlaceSiteWindowless and IOleInPlaceObjectWindowless. When drawing itself, a control asks the container-provided IOleInPlaceObjectWindowless interface for a device context and returns the device context to the container after drawing into it. Providing windowless support not only provides transparency for interesting visual effects, but also further improves the performance of the control.
- Hit detection. Hit detection is provided so that a windowless control can have an irregular shape and to complete the visual benefits of transparency. If the container detects that the rectangular extent occupied by a windowless control has been hit, it calls IViewObjectEx::QueryHitPoint (another new interface for windowless controls). The control then determines if it has been hit and returns an indication to the container.
- Quick activation. A new, optional interface, IQuickActivate, has been introduced to further speed activation of a control. If a container such as Internet Explorer 4.0 determines that this interface is supported by a control it is instantiating, the container calls IQuickActivate, passing two structures, one that the container uses to provide ambient properties and interface pointers to the control and the other for data that the control returns to the container. Replacing multiple calls with a single call increases the efficiency of instantiating a control.
Accessing Dynamic HTML
Internet Explorer allows components to access the Dynamic HTML (DHTML) object model of the document in which the control is hosted. All the capabilities of the object model are available to the control, allowing it to both read and modify the document content. If a control supports either the IObjectWithSite or IOleObject interface, accessing the object model is very simple and consists of the following two steps:
- Call IOleClientSite::GetContainer. This returns the IOleContainer interface of the host of the control.
- Call QueryInterface on the IOleContainer interface for the IHTMLDocument2 interface.
The IHTMLDocument2 interface provides access to the document in which the control is hosted. Given this interface, it is possible to traverse the entire HTML object model. The following bit of code, written using Active Template Library (ATL) 2.0 smart pointers, accesses the object model and sets the background color of the document to pink. In script this is equivalent to document.bgColor = "pink".
// Get the document. CComPtr<IOleContainer> spContainer; m_spClientSite->GetContainer(&spContainer); CComQIPtr<IHTMLDocument2, &IID_IHTMLDocument2> spDoc(spContainer); if (spDoc) spDoc->put_bgColor(CComBSTR(_T("pink")));
Controls can perform any of the manipulations available to script in Internet Explorer 4.0. They can also perform functions that aren't possible from script due to security restrictions that do not apply to ActiveX Controls. To aid in the development process, use script to prove a concept; then, translate that script to interface-level manipulation.
Authoring Controls for the Active Desktop
Internet Explorer 4.0 introduced several key innovations to the user's browsing experience. Among these are offline browsing and the Active Desktop. The former uses a caching mechanism to allow the user to read content from the Web without requiring them to be connected to it. The latter turns the desktop into a Web page by enabling the user to add any content viewable in the browser as an item to the Active Desktop, including ActiveX Controls. Network-aware components should understand the issues involved in authoring components for the Active Desktop that behave well in the offline state.
In Internet Explorer Integrated Browser Mode, the Windows Shell and the browser run in the same process space. This poses a challenge to developers who are attempting to debug their controls. Testing controls in Integrated Browser Mode is recommended because it helps to reveal apartment-model issues.
When deploying controls, use cabinet (.cab) files, and sign your controls. Issues regarding security have received great attention recently. End users are advised not to download executable content unless it is signed. If you do not sign your control, you cannot expect users to download your product. Cabinet files also create a better end-user experience because only one certificate is presented to the user, and a single, compressed download brings multiple files to the client computer. Details of this technology are in the Internet Client SDK.
Do not assume that nonsystem DLLs are already installed on a client computer. Although some releases of Internet Explorer ship with certain DLLs, such as Microsoft Foundation Classes (MFC) and Ctl3d32, this is not a reasonable assumption for all cases. In circumstances where a control is dependent on such DLLs, the main cabinet file should reference additional cabinets that contain the dependencies so they will be downloaded if necessary.
This section presents additional guidelines for creating controls for Internet Explorer. The caveats for control authors include:
- If a control fires an event from a thread that it creates, rather than from the thread in which it was created, the connection point interface through which the event is fired must be marshaled to the secondary thread.
- Internet Explorer calls FindConnectionPoint on a control to hook up events even if the control exposes no events. A control should be written to expect this call.
- As previously mentioned, controls should be authored to respect apartment-model rules.
- The VBScript engine passes parameters to controls by reference (ByRef) and not by value (ByVal). Controls should check the VARTYPE field of parameters of type VARIANT explicitly.
- Internet Explorer no longer sends the show and hide verbs (OLEIVERB_SHOW and OLEIVERB_HIDE) to controls. A control should rely only on receiving the OLEIVERB_INPLACEACTIVATE verb and the IOleInPlaceObject::InPlaceDeactivate call.
- Internet Explorer does not activate all controls immediately on initialization of the control. Instead, it implements an optimization, only activating a control when it is visible (OLEMISC_ACTIVATEWHENVISIBLE). If a control needs to process window messages at initialization, it should create a hidden window for that purpose.
- In Internet Explorer, if a control implements a ReadyState property (DISPID_READYSTATE), events will remain frozen until the control notifies the container that the ready state has changed to READYSTATE_COMPLETE. Internet Explorer expects this notification through its IPropertyNotifySink::OnChanged implementation. All controls should implement a ReadyState property to communicate their initialization status to scripts.
- A control should listen to IOleControl::FreezeEvents calls from the container. If a control fires an event that is important when events are frozen, the event will not be received by scripts. While it is not typically harmful to fire an event when events are frozen, if events are critical, the control should maintain a freeze count and implement a queuing mechanism, firing those queued events when IOleControl::FreezeEvents is called with FALSE and the freeze count reaches 0.
- Internet Explorer does not unload controls when the browser is closed. Internet Explorer relies on proper COM reference counting. When the last reference to a control is released, the control will unload. A control should take care when caching interface pointers such as those returned by IOleClientSite::GetContainer and IOleControlSite::GetExtendedControl.
- A control should use the WM_CONTEXTMENU message to trigger a context menu and not utilize the WM_RBUTTONUP message.
- A control should not forcefully UI activate itself, or assume itself to be UI active. This is an area in which the page author should be in control, and a control that needs the focus should have it set through script. In the case of multiple controls on a page, a UI activation battle could cause strange results. Take as another example a control at the bottom of a large page that forcefully scrolls into view were it to UI activate itself on instantiation.
- A control should bring up modal dialog boxes only when requiring immediate response to some user interaction. On the contrary, a control asynchronously downloading data in the background should not display a modal dialog box if the link fails. Instead, it should fire an error event that a script could handle in its own fashion. Always keep the user in mind, as well as the effect a control's behavior will have on applications hosting your control directly or indirectly through Internet Explorer components.
- Controls should take advantage of the Internet Explorer implementation of the IBindHost interface when performing data download. IBindHost can be obtained from Internet Explorer through the IServiceProvider interface, which is available by querying the IOleClientSite interface. For details, see the ATL 2.0 implementation of IBindStatusCallback, specifically
- Internet Explorer eliminated the four-page in-memory cache. Controls no longer remain alive even though the page in which they were embedded was no longer in view. Assuming proper reference counting, Internet Explorer destroys all controls embedded on the page when the user navigates away from the page. To keep a control alive, consider implementing a frameSet and placing the object tag in the head object of the page containing the frameSet. While this instance of the control is not visible, it could provide global data for use by other objects contained within other frames within the frameset.
- Internet Explorer prints documents by instantiating a second instance of the document on a background thread, by restoring the state of the document, and by requesting that the document print itself. To provide proper support for printing in Internet Explorer, controls should support one of the persistence interfaces (IPersist*). In addition, controls should make no assumptions about the type of device context (DC) they receive: controls should provide device independent rendering support. Finally, because controls are not put into the UI Active state when printed in the background, they should not rely on window handles (HWND).