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.
- ActiveX Controls
- Overloading IPropertyNotifySink
- Container-Specific Private Interfaces
- Multithreaded Issues
- Event Freezing
- Container Controls
- WS_GROUP and WS_TABSTOP Flags in Controls
- Multiple Controls in One DLL
- Enhanced Metafiles
- Dual Interfaces
- IPropertyBag and IPersistPropertyBag
An ActiveX control is essentially a simple OLE object that supports the IUnknown interface. It usually supports many more interfaces in order to offer functionality, but all additional interfaces can be viewed as optional and, as such, a container should not rely on any additional interfaces being supported. By not specifying additional interfaces that a control must support, a control can efficiently target a particular area of functionality without having to support particular interfaces to qualify as a control. As always with OLE, whether in a control or a container, it should never be assumed that an interface is available, and standard return-checking conventions should always be followed. It is important for a control or container to degrade gracefully and offer alternative functionality if a required interface is not available.
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 or other methods to obtain pointers to these interfaces. A container should not expect a control to support anything more than the IUnknown interface. These guidelines identify what interfaces a control can support and what the presence of a particular interface means.
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. It is important to note that this document does not cover detailed semantics of the OLE interfaces; this is covered by the Windows Software Development Kit (SDK) documentation.
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.
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.
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.
ActiveX Controls must support self-registration by implementing the DllRegisterServer and DllUnregisterServer functions. ActiveX Controls must register all the standard registry entries for embeddable objects and automation servers.
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.
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), IOleObject is a necessity. When implementing this interface, the control must also support the semantics of the following methods:
IOleObject::SetHostNames, IOleObject::Close, IOleObject::EnumVerbs, IOleObject::Update, IOleObject::IsUpToDate, IOleObject::GetUserClassID, IOleObject::GetUserType, IOleObject::GetMiscStatus, and the IOleObject::Advise, IOleObject::Unadvise, and IOleObject::EnumAdvise methods that work in conjunction with a container's IAdviseSink implementation.
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 is supported as well. Required methods are IOleInPlaceObject::InPlaceDeactivate, IOleInPlaceObject::UIDeactivate, IOleInPlaceObject::SetObjectRects, and IOleInPlaceObject::ReactivateAndUndo. Support for this interface requires support for IOleObject.|
|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, (IOleControl::OnMnemonic methods); ambient properties (IOleControl::OnAmbientPropertyChange); and/or events that the control requires the container to handle (IOleControl::FreezeEvents). Note that mnemonics are different from accelerators that are handled through IOleInPlaceActiveObject: mnemonics have an associated UI and are active even when the control is not UI active. A control's support for mnemonics means that the control also knows how to use the container's IOleControlSite::OnControlInfoChanged method. Because this requires the control to know the container's site, support for mnemonics also means support for IOleObject. In addition, knowledge of mnemonics requires in-place support and thus IOleInPlaceObject.
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 interface, ambient property support means that the control supports IOleObject (to get the site) as well as being able to generate IDispatch calls.
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, IDataObject::QueryGetData, IDataObject::EnumFormatEtc, IDataObject::DAdvise, IDataObject::DUnadvise, and IDataObject::EnumDAdvise methods are required. Support for graphical formats other than CF_METAFILEPICT is optional.|
|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, IViewObject::GetAdvise, IViewObject::SetAdvise, and IViewObject2::GetExtent methods.|
|IDispatch||Indicates that the control has either custom methods or custom properties that are both available by late-binding through IDispatch::Invoke. This also requires that the control provides type information through other IDispatch methods. A control can support multiple IDispatch implementations where only one is associated with IID_IDispatch—the others must have their own unique dispinterface identifiers.
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, which requires a separate object with IEnumConnectionPoints.
Support for IConnectionPointContainer means that the object also supports one or more related objects with IConnectionPoint that are available through IConnectionPointContainer methods. Each "connection point" object itself must implement the full IConnectionPoint interface, including IConnectionPoint::EnumConnections, which requires another enumerator object with the IEnumConnections interface.
|IProvideClassInfo2||Indicates that the object can provide its own coclass type information directly through IProvideClassInfo::GetClassInfo. If the control supports the later variation, IProvideClassInfo2, it also indicates its ability to provide its primary source IID through IProvideClassInfo2::GetGUID. All methods of this interface must be implemented.|
|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 themselves implement IPropertyPage2, as opposed to the basic IPropertyPage interface.|
|IPersistStream||See Persistence Interfaces section.|
|IPersistStreamInit||See Persistence Interfaces section.|
|IPersistMemory||See Persistence Interfaces section.|
|IPersistStorage||See Persistence Interfaces section.|
|IPersistMoniker||See Persistence Interfaces section.|
|IPersistPropertyBag||See Persistence Interfaces section.|
|IOleCache2||Indicates support for container caching of control visuals. A control generally obtains caching support itself through the OLE CreateDataCache function. Only controls with meaningful static content should choose to do this (although it is not required). If a control supports caching at all, it should simply aggregate the data cache and expose both IOleCache and IOleCache2 interfaces from the data cache. [Note: IOleCacheControl is only important if the control has an external out-of-process data source that itself implements IDataObject such that the control could directly connect the data source to the cache without any intervening layers. This is exceptionally rare.]|
|IExternalConnection||Indicates that the control supports external links to itself; that is, the control is not marked with OLEMISC_CANTLINKINSIDE and supports IOleObject::SetMoniker and IOleObject::GetMoniker. A container never queries for this interface itself nor calls it directly, because calls are generated from inside OLE's remoting layer.|
|IRunnableObject||Indicates that the control differentiates being "loaded" from being "running," as some in-process objects do.|
Objects that have a "persistent state" of any kind must implement at least one IPersist* interface, and preferably multiple interfaces, in order to provide the container with the most flexible choice of how it wants to save a control's state.
If a control has any 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.
|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 instance. Controls that want to be marked "Insertable" as other compound document objects (for insertion into non-control-aware containers) must support this interface.|
|IPersistPropertyBag||The object can save and load its state as individual properties written to IPropertyBag, which the container implements. This is used for "save as text" functionality in some containers.|
|IPersistMoniker||The object can save and load its state to a location named by a moniker. The control calls IMoniker::BindToStorage to retrieve the storage interface it requires, such as IStorage, IStream, ILockBytes, IDataObject, and so on.|
While support for IPersistPropertyBag is optional, it is strongly recommended as an optimization for containers with "save as text" features, such as Visual Basic.
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 Windows 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.
|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::SetMoniker||Mandatory if the control is not marked with OLEMISC_CANTLINKINSIDE.|
|IOleObject::GetMoniker||Mandatory if the control is not marked with OLEMISC_CANTLINKINSIDE.|
|IOleObject::SetExtent||Mandatory only for DVASPECT_CONTENT.|
|IOleObject::GetExtent||Mandatory only for DVASPECT_CONTENT.|
|IOleObject::DoVerb||See Note 1.|
|IPersistStreamInit::GetSizeMax||See Note 2.|
- 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.
An ActiveX control, by virtue of being a COM object, must have associated server code that supports control creation through IClassFactory as a minimum.
It is optional, not required, that this class object also supports IClassFactory2 for licensing management. Only those vendors that are concerned about licensing need to support COM's licensing mechanism. In other words, because IClassFactory2 is the only way to achieve COM-level licensing, this interface is required on the class object for those controls that want to be licensed.
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.
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.
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.
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).
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 and IOleObject::GetClientSite).
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.
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.
|IOleClientSite||Site||Controls that implement IOleObject call IOleClientSite methods as part of the standard OLE embedding protocol, specifically the methods IOleClientSite::SaveObject, IOleClientSite::ShowObject, IOleClientSite::OnShowWindow (only if a separate window activation state is supported), IOleClientSite::RequestNewObjectLayout, and IOleClientSite::GetContainer (if communication with other controls is desired). The IOleClientSite::GetMoniker method is only used when the control can be linked to externally, that is, the control is not marked with OLEMISC_CANTLINKINSIDE.|
|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) as part of the standard OLE in-place activation protocol.|
|IAdviseSink||Site||Control calls IAdviseSink::OnDataChange if the control supports IDataObject, IAdviseSink::OnViewChange if the control supports IViewObject2, and IAdviseSink::OnClose, IAdviseSink::OnSave, and IAdviseSink::OnRename if the control supports IOleObject.|
|IOleControlSite||Site||If supported, control calls IOleControlSite::OnControlInfoChanged when mnemonics change, IOleControlSite::LockInPlaceActive and IOleControlSite::TransformCoords if events are fired (the latter method is only used if coordinates are passed as event arguments), IOleControlSite::OnFocus and IPropertyPage::TranslateAccelerator if the control has a UI active state, and IOleControlSite::GetExtendedControl if the control wants to look at extended-control (container-owned) properties.|
|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 implementations (which are usually found in subobjects of the control). A control always knows how to call its own event interfaces, since the control defines those interfaces.|
|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.|
This section describes various features, hints, and tips for ActiveX control developers to help ensure good interoperability between controls and containers.
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.
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.
Starting with 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 and CoUnmarshalInterface) when passed between threads. For more information about apartment model threading, refer to the Windows SDK documentation and the OLEAPT sample (in the Windows SDK).
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.
As described previously, container controls are ActiveX Controls that visually contain other controls. The ActiveX Controls architecture specifies the ISimpleFrameSite interface to enable container controls. Containers can also support container controls without supporting ISimpleFrameSite, although the behavior cannot be guaranteed. For this reason, a component category exists for ISimpleFrameSite controls where the full functionality of this interface is required.
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.
A control should not use the WS_GROUP and WS_TABSTOP flags internally; some containers rely on these flags to manage keyboard handling.
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
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.
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.
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 and CoCreateInstance) explicitly call IClassFactory and not IClassFactory2, and therefore cannot be used to create or load licensed ActiveX Controls. ActiveX control containers should explicitly create and load ActiveX Controls using IClassFactory2.
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 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.