Introduction to ActiveX Controls
The purpose of this topic is to provide guidelines for implementing Microsoft® ActiveX® Controls that interoperate well with containers and other controls. This article defines the minimum set of interfaces, methods, and features that are required of ActiveX Controls to accomplish seamless and useful interoperability.
Overview
An ActiveX control is essentially a simple OLE object that supports the IUnknown
It is important for controls that require optional features, or features specific to a certain container, to be clearly packaged and marketed with those requirements. Similarly, containers that offer certain features or component categories must be marketed and packaged as offering those levels of support when hosting ActiveX Controls. It is recommended that controls target and test with as many containers as possible, and degrade gracefully to offer less or alternative functionality if interfaces or methods are not available. In a situation where a control cannot perform its designated job function without the support of a component category, that category should be entered as a requirement in the registry to prevent the control being inserted in an inappropriate container.
These guidelines define those interfaces and methods that a control can expect a container to support, although as always a control should check the return values when using QueryInterface
Why are ActiveX Controls Important?
ActiveX Controls have become the primary architecture for developing programmable software components for use in a variety of different containers, ranging from software development tools to end-user productivity tools. For a control to operate well in a variety of containers, the control must be able to assume some minimum level of functionality that it can rely on in all containers.
By following these guidelines, control developers make their controls more reliable and interoperable, and, ultimately, better and more usable components for building component-based solutions.
This article provides guidelines toward good interoperability. It is expected that new interfaces and component categories will develop over time; future versions of this document reflecting these changes will be made readily available through Microsoft. It is important to note that this document does not cover detailed semantics of the OLE interfaces; this is covered by the Platform SDK documentation.
What If an Interface You Need Is Not Available?
This section states some fundamental rules that apply to all OLE programming. OLE programs should use QueryInterface to acquire interface pointers, and must check the return value. OLE applications cannot safely assume that QueryInterface will succeed. This requirement applies to all OLE applications. If the requested interface is not available (that is, QueryInterface returns E_NOINTERFACE), the control or container must degrade gracefully, even if that means it cannot perform its designated job function.
What's New?
This release of the guidelines embraces the concept of component categories, which are a part of the OLE specification. In previous versions of this document, component categories were loosely referred to as "function groups" and were used to identify areas of functionality that a container can optionally support. For this version there has been a definition of how component categories work for ActiveX Controls, and some fundamental categories are identified. The use of component categories allows the relaxing of some of the previous rules that identified interfaces as being mandatory, and allows greater flexibility for controls to efficiently target certain areas of functionality without having to provide superfluous additional support in order to qualify as a control. This edition of the guidelines also discusses what the presence or absence of an interface means and what to do in that situation.
The remainder of this article is divided into four sections. The first discusses guidelines for implementing controls, the second discusses guidelines for implementing containers, the third discusses component categories, and the fourth discusses general guidelines, relevant to both control and container developers.
ActiveX Controls
An ActiveX control is really just another term for "OLE Object" or, more specifically, "Component Object Model (COM) Object." In other words, a control, at the very least, is some COM object that supports the IUnknown interface and is also self-registering. Through QueryInterface a container can manage the lifetime of the control, as well as dynamically discover the full extent of a control's functionality based on the available interfaces. This allows a control to implement as little functionality as it needs to, instead of supporting a large number of interfaces that actually don't do anything. In short, this minimal requirement for nothing more than IUnknown allows any control to be as lightweight as it can.
Other than IUnknown and self-registration, there are no other requirements for a control. There are, however, conventions that should be followed about what the support of an interface means in terms of functionality provided to the container by the control. This section describes what it means for a control to actually support an interface, as well as methods, properties, and events that a control should provide as a baseline if it has occasion to support methods, properties, and events.
Self-Registration
ActiveX Controls must support self-registration by implementing the DllRegisterServer
ActiveX Controls must use the Component Categories API to register themselves as a control and register the component categories that they require a host to support, and any categories that the control implements. In addition, an ActiveX control might want to register the "control" keyword in order to allow older containers, such as Microsoft Visual Basic® 4, to host them.
ActiveX Controls should also register the ToolBoxBitmap32 registry key, although this is not mandatory.
The Insertable component category should only be registered if the control is suitable for use as a compound document object. It is important to note that a compound document object must support certain interfaces beyond the minimum IUnknown required for an ActiveX control. Although an ActiveX control might qualify as a compound document object, the control's documentation should clearly state what behavior to expect under these circumstances.
What Support for an Interface Means
In addition to the IUnknown interface, an ActiveX control—or COM Object for that matter—expresses whatever optional functionality it supports through additional interfaces. This is to say that no other interfaces are required above IUnknown. To that end, the following table lists the interfaces that an ActiveX control might support, and what it means to support that interface. Further details about the methods of these interfaces are given in a later section.
Interface | Comments/What it means to support the interface |
---|---|
IOleObject | If the control requires communication with its client site for anything other than events (see IConnectionPointContainer
Note A control implementing IOleObject must be able to handle IAdviseSink if the container provides one; a container might not, in which case a control ensures, of course, that it does not attempt to call a nonexistent sink. |
IOleInPlaceObject | Expresses the control's ability to be in-place activated and possibly user-interface (UI) activated. This interface means that the control has a user interface of some kind that can be activated, and
IOleInPlaceActiveObject |
IOleInPlaceActiveObject | An in-place capable object that supports IOleInPlaceObject must also provide this interface as well, though the control itself doesn't necessarily implement the interface directly. |
IOleControl | Expresses the control's ability and desire to deal with mnemonics (IOleControl::GetControlInfo If a control uses any container-ambient properties, it must also implement this interface to receive change notifications, because following the semantics of changes is required. Because ambient properties are available only through the container site's IDispatch The IOleControl::FreezeEvents method is necessary for controls that must know when a container is not going to handle an event—this is the only way for a control to know this condition. If IOleControl::FreezeEvents is only necessary in isolation, such that other IOleControl methods are not implemented, then IOleControl can stand alone without IOleObject or IOleInPlaceObject. |
IDataObject | Indicates that the control can supply at least graphical renderings of the control (CF_METAFILEPICT is the minimum if the control has any visuals at all) and/or property sets, if the control has any properties to provide. The IDataObject::GetData |
IViewObject2 | Indicates that the control has some interesting visuals when it is not in-place active. If implemented, a control must support the IViewObject::Draw |
IDispatch | Indicates that the control has either custom methods or custom properties that are both available by late-binding through IDispatch::Invoke A control is encouraged to supply dual interfaces for custom method and property access, but this is optional if methods and properties exist. |
IConnectionPointContainer | Indicates that a control supports at least one "outgoing" interface, such as events or property change notifications. All methods of this interface must be implemented if this interface is available at all, including IConnectionPointContainer::EnumConnectionPoints Support for IConnectionPointContainer means that the object also supports one or more related objects with IConnectionPoint |
IProvideClassInfo2 | Indicates that the object can provide its own coclass type information directly through IProvideClassInfo2::GetClassInfo |
ISpecifyPropertyPages | Indicates that the control has property pages that it can display so that a container can coordinate this control's property pages with other controls' pages when property pages are to be shown for a multicontrol selection. All methods of this interface must be implemented when support exists. |
IPerPropertyBrowsing | Indicates the control's ability to provide a display string for a property, provide predefined strings and values for its properties, and/or map a property DISPID to a specific property page. Support for this interface means that support for properties through IDispatch as above is provided. If mapping a property DISPID to a specific property page is supported, it also means that the object's property pages mapped through IPerPropertyBrowsing::MapPropertyToPage |
IPersistStream | See Persistence Interfaces section. |
IPersistStreamInit | See Persistence Interfaces section. |
See Persistence Interfaces section. | |
IPersistStorage | See Persistence Interfaces section. |
See Persistence Interfaces section. | |
See Persistence Interfaces section. | |
IOleCache2 | Indicates support for container caching of control visuals. A control generally obtains caching support itself through the OLE CreateDataCache |
IExternalConnection | Indicates that the control supports external links to itself; that is, the control is not marked with OLEMISC_CANTLINKINSIDE and supports IOleObject::SetMoniker |
IRunnableObject | Indicates that the control differentiates being "loaded" from being "running," as some in-process objects do. |
Persistence Interfaces
Objects that have a "persistent state" of any kind must implement at least one IPersist*
If a control hasany persistent state whatsoever, it must, as a minimum, implement either IPersistStream or IPersistStreamInit (the two are mutually exclusive and shouldn't be implemented together for the most part). The latter is used when a control wants to know when it is created new as opposed to reloaded from an existing persistent state (IPersistStream does not have the "created new" capability). The existence of either interface indicates that the control can save and load its persistent state into a stream, that is, an instance of IStream
Beyond these two stream-based interfaces, the IPersist* interfaces listed in the following table can be optionally provided in order to support persistence to locations other than an expandable IStream.
Interface | Usage |
---|---|
IPersistMemory | The object can save and load its state into a fixed-length sequential byte array (in memory). |
IPersistStorage | The object can save and load its state into an IStorage |
IPersistPropertyBag | The object can save and load its state as individual properties written to |
IPersistMoniker | The object can save and load its state to a location named by a moniker. The control calls IMoniker::BindToStorage |
While support for IPersistPropertyBag is optional, it is strongly recommended as an optimization for containers with "save as text" features, such as Visual Basic.
With the exception of IPersistStreamInit::GetSizeMax
Optional Methods
Implementing an interface doesn't necessarily mean implementing all methods of that interface to do anything more than return E_NOTIMPL or S_OK as appropriate. The following table identifies the methods of the interfaces that a control can implement in this manner. Check with the Platform SDK OLE Reference documentation for full syntax and valid return values from these methods. Any method not listed here must be fully implemented if the interface is supported.
Method | Comments |
---|---|
IOleControl | |
IOleControl::GetControlInfo, IOleControl::OnMnemonic | Mandatory for controls with mnemonics. |
IOleControl::OnAmbientPropertyChange | Mandatory for controls that use ambient properties. |
IOleControl::FreezeEvents | See "Event Freezing" in the General Guidelines section. |
IOleObject | |
IOleObject::SetMoniker | Mandatory if the control is not marked with OLEMISC_CANTLINKINSIDE. |
IOleObject::GetMoniker | Mandatory if the control is not marked with OLEMISC_CANTLINKINSIDE. |
IOleObject::InitFromData | Optional. |
GetClipboardData | Optional. |
IOleObject::SetExtent | Mandatory only for DVASPECT_CONTENT. |
IOleObject::GetExtent | Mandatory only for DVASPECT_CONTENT. |
IOleObject::SetColorScheme | Optional. |
IOleObject::DoVerb | See Note 1. |
IOleInPlaceObject | |
IOleWindow::ContextSensitiveHelp | Optional. |
IOleInPlaceObject::ReactivateAndUndo | Optional. |
IOleInPlaceActiveObject | |
IOleWindow::ContextSensitiveHelp | Optional. |
IViewObject2 | |
IViewObject::Freeze | Optional. |
IViewObject::Unfreeze | Optional. |
IViewObject::GetColorSet | Optional. |
IPersistStreamInit, IPersistMemory | |
IPersistStreamInit::GetSizeMax | See Note 2. |
Notes:
- A control with property pages must support IOleObject::DoVerb for the OLEIVERB_PROPERTIES and OLEIVERB_PRIMARY verbs. A control that can be active must support IOleObject::DoVerb for the OLEIVERB_INPLACEACTIVATE verb. A control that can be UI active must also support IOleObject::DoVerb for the OLEIVERB_UIACTIVATE verb.
- If a control supports IPersistStreamInit and can return an accurate value, it should do so.
Class Factory Options
An ActiveX control, by virtue of being a COM object, must have associated server code that supports control creation through IClassFactory
It is optional, not required, that this class object also supports IClassFactory2
Properties
Although most controls do have properties, controls are not required to expose any properties and thus the control does not require IDispatch. If the control does have properties, there are no requirements for which properties a control must expose.
Methods (through IDispatch and other dispinterfaces)
Although most controls do expose and support several methods, controls are not required to expose or support any methods; thus the control does not require IDispatch. If the control does have any methods, there are no requirements for which methods a control must expose.
Events
Although most controls do expose and fire several events, controls are not required to expose or fire any events; thus the control does not require IConnectionPointContainer. If the control does have any events, there are no requirements for which events a control must expose.
Property Pages
Support for property pages and per-property browsing is strongly recommended, but not required. If a control does implement property pages, those pages should conform to one of the standard sizes: 250 x 62 or 250 x 110 dialog window units (DLUs).
Ambient Properties
If a control supports any ambient properties at all, it must at least respect the values of the following ambient properties under the conditions stated in the following table using the standard DISPIDs.
Ambient property | DISPID | Comment/Conditions for use |
---|---|---|
LocaleID | -705 | If locale is significant to the control, for example, for text output. |
UserMode | -709 | If the control behaves differently in user (design) mode and run mode. |
UIDead | -710 | If the control reacts to UI events, it should honor this ambient property. |
ShowGrabHandles | -711 | If the control supports in-place resizing of itself. |
ShowHatching | -712 | If the control supports in-place activation and UI activation. |
DisplayAsDefault | -713 | Only if the control is marked OLEMISC_ACTSLIKEBUTTON (which means that support for keyboard mnemonics is provided, thus IOleControl::GetControlInfo and IOleControl::OnMnemonic must be implemented). |
As described previously, use of ambients requires both IOleControl (for IOleControl::OnAmbientPropertyChange as a minimum) as well as IOleObject (for IOleObject::SetClientSite
The OLEMISC_SETCLIENTSITEFIRST bit might not necessarily be supported by a container. In these circumstances, a control must resort to default values for the ambient properties that it requires.
Using the Container's Functionality
The previous sections have described some of the necessary caller-side support that an ActiveX control must have in order to access certain features of its container. The following table describes a control's usage of container-side interfaces and when such usage would occur.
Interface | Container object | Usage |
---|---|---|
IOleClientSite | Site | Controls that implement IOleObject call IOleClientSite methods as part of the standard OLE embedding protocol, specifically the methods IOleClientSite::SaveObject |
IOleInPlaceSite | Site | Controls that have an in-place activate and possibly a UI active state call IOleInPlaceSite methods (generally all of them, with the exception of IOleInPlaceActiveObjectImpl::ContextSensitiveHelp |
IAdviseSink | Site | Control calls IAdviseSink::OnDataChange |
IOleControlSite | Site | If supported, control calls IOleControlSite::OnControlInfoChanged when mnemonics change, IOleControlSite::LockInPlaceActive |
IDispatch (ambient properties) | Site | Used to access ambient properties. |
IPropertyNotifySink | Varies | A control must generate OnChanged and OnRequestEdit for any control properties that are marked as [bindable] and [request], respectively. |
Other event sink interfaces | Varies | A control that has outgoing interfaces other than IPropertyNotifySink will be handed other interface pointers of the correct IID to the control's IConnectionPoint::Advise |
IOleInPlaceFrame | Frame | Used when a control has an in-place UI active state that requires frame-level tools or menu items. |
IOleInPlaceUIWindow | Document | Used only when a control has an in-place UI active state that requires document-level or pane-level UI tools. This is rare. |
General Guidelines
This section describes various features, hints, and tips for ActiveX control developers to help ensure good interoperability between controls and containers.
Overloading IPropertyNotifySink
Many ActiveX control containers implement a modeless property browsing window. If a control's properties are altered through the control's property pages, the control's properties can get out of sync with the container's view of those properties (the control is always right, of course). To ensure that it always has the current values for a control's properties, an ActiveX control container can overload the IPropertyNotifySink interface (data binding) and also use it to be notified that a control property has changed. This technique is optional, and is not required of ActiveX control containers or ActiveX Controls.
Container-Specific Private Interfaces
Some containers provide container-specific private interfaces for additional functionality or improved performance. Controls that rely on those container-specific interfaces should, if possible, work without those container-specific interfaces present so that the control functions in different containers. For example, Visual Basic implements private interfaces that provide string formatting functionality to controls. If a control makes use of the Visual Basic private formatting interfaces, it should be able to run with default formatting support if these interfaces are not available. If the control can function without the private interfaces, it should take appropriate action (such as warn the user of reduced functionality) but continue to work. If this is not an option, a component category should be registered as required to ensure that only containers supporting this functionality can host these controls.
Multithreaded Issues
Starting with Microsoft Windows® 95 and Microsoft Windows NT® 3.51, OLE provides support for multithreaded applications, allowing applications to make OLE calls from multiple threads. This multithreaded support is called the "apartment model"; it is important that all OLE components using multiple threads follow this model. The apartment model requires that interface pointers are marshaled (using CoMarshalInterface
Event Freezing
A container can notify a control that it is not ready to respond to events by calling IOleControl::FreezeEvents(TRUE). It can unfreeze the events by calling IOleControl::FreezeEvents(FALSE). When a container freezes events, it is freezing event processing, not event receiving; that is, a container can still receive events while events are frozen. If a container receives an event notification while its events are frozen, the container should ignore the event. No other action is appropriate.
A control should take note of a container's call to IOleControl::FreezeEvents(TRUE) if it is important to the control that an event is not missed. While a container's event processing is frozen, a control should implement one of the following techniques:
- Fire the events in the full knowledge that the container will take no action.
- Discard all events that the control would have fired.
- Queue up all pending events and fire them after the container has called IOleControl::FreezeEvents(FALSE).
- Queue up only relevant or important events and fire them after the container has called IOleControl::FreezeEvents(FALSE).
Each technique is acceptable and appropriate in different circumstances. The control developer is responsible for determining and implementing the appropriate technique for the control's functionality.
Container Controls
As described previously, container controls are ActiveX Controls that visually contain other controls. The ActiveX Controls architecture specifies the ISimpleFrameSite
To support container controls without implementing ISimpleFrameSite, an ActiveX control container must:
- Activate all controls at all times.
- Reparent the contained controls to the hWnd of the containing control.
- Remain the parent of the container control.
WS_GROUP and WS_TABSTOP Flags in Controls
A control should not use the WS_GROUP and WS_TABSTOP flags internally; some containers rely on these flags to manage keyboard handling.
Multiple Controls in One DLL
A single .ocx DLL can container any number of ActiveX Controls, thus simplifying the distribution and use of a set of related controls.
If you ship multiple controls in a single DLL, be sure to include the vendor name in each control name in the package. Including the vendors' names in each control name enables users to easily identify controls within a package. For example, if you ship a DLL that implements three controls, Con1, Con2, and Con3, the control names should be:
<Your company name> Con1 Control <Your company name> Con2 Control <Your company name> Con3 Control
IOleContainer::EnumObjects
This method is used to enumerate over all the OLE objects contained in a document or form, returning an interface pointer for each OLE object. The container must return pointers to each OLE object that shares the same container. Nested forms or nested controls must also be enumerated.
Some containers implement "extender controls," which wrap non-ActiveX controls, and then return pointers to these extender controls as a form is enumerated. This behavior enables ActiveX Controls and ActiveX control containers to integrate well with non-ActiveX controls, and is thus recommended but not required.
Enhanced Metafiles
Not surprisingly, enhanced metafiles provide more functionality than standard metafiles; using enhanced metafiles generally simplifies rendering code. An enhanced metafile device context (DC) is used in exactly the same way as a standard metafile DC. Enhanced metafiles are not available in 16-bit OLE. OLE supports enhanced metafiles, and includes backward compatibility with standard metafiles and 16-bit applications.
A 32-bit ActiveX control container should use enhanced metafiles instead of standard metafiles.
Licensing
In order to embed licensed controls successfully, ActiveX control containers must use IClassFactory2 instead of IClassFactory. Several OLE creation and loading helper functions (for example, OleLoad
Dual Interfaces
OLE Automation enables an object to expose a set of methods in two ways: through the IDispatch interface, and through direct OLE vtable binding. IDispatch is used by most tools available today, and offers support for late binding to properties and methods. Vtable binding offers much higher performance because this method is called directly instead of through IDispatch::Invoke. IDispatch offers late bound support, where direct vtable binding offers a significant performance gain; both techniques are valuable and important in different scenarios. By labeling an interface as "dual" in the type library, an OLE Automation interface can be used through IDispatch, or it can be bound to directly. Containers can thus choose the most appropriate technique. Support for dual interfaces is strongly recommended for both controls and containers.
IPropertyBag and IPersistPropertyBag
IPropertyBag and IPersistPropertyBag optimize "save as text" mechanisms, and therefore are recommended for ActiveX control containers that implement a "save as text" mechanism. IPropertyBag is implemented by a container, and is roughly analogous to IStream. IPersistPropertyBag is implemented by controls, and is roughly analogous to IPersistStream.
<Return>
|