Component Development

The documentation in this section contains the information you'll need if you are developing ActiveX™ Controls, ActiveX Control containers, Active Document servers, Active Document hosts, ActiveX Scripting hosts, or ActiveX Scripting engines.

COM Objects for the Internet

ActiveX Controls

ActiveX Control Containers

Active Documents

ActiveX Scripting

Microsoft Scriptlet Technology


Internet Awareness for Objects, Controls, and Containers

The following documents, which are referenced in text using shortened names, contain information relevant to COM Objects:
Full name | Referenced as
Asynchronous Moniker Design Specification | Asynchronous Monikers
Component Categories | Component Categories
Compound Files on the Internet | Internet Files
OLE Control and Control Container Guidelines v2.0 | Control Guidelines
URL Monikers | URL Monikers


Introduction and Requirements

There is no question that OLE Controls, or COM objects in general, are useful as part of a document or page on an Internet site. However, some problems exist with regards to how a control is specified within a document, and how a control can behave well in a "slow-link" environment such as the Internet (including how the control retrieves its data in an incremental or progressive fashion, working well with other controls that may also be retrieving their data in the same manner).

Many controls, such as small buttons and label controls, which do not have significant amounts of persistent data, are already suitable for use in the Internet environment. Such controls, even those that already exist, need virtually no changes to work well inside browsers. This document focuses primarily on those controls that have significant amounts of persistent data.

The topics under consideration are listed here:

  • What is the exact instantiation mechanism for a control? That is, how does a container get the class code bits into memory?
  • What options are available for the storage of a control's persistent data, both properties and large data BLOBs (such as a bitmap image or video data)?
  • When a control's data includes potentially huge binary streams (bitmap, video, sound, and so on), how does the control access this data asynchronously and incrementally in cooperation with the container (including the ability for the container to display a progress UI)?
  • How does a control notify the container that the control has finished retrieving asynchronous data and is ready for interaction with the user?

Other topics include miscellaneous subjects, including component categories; "Internet-awareness" requirements and options, including new dispIDs and GUIDs; standard Internet-aware picture, sound, and video objects; and reference material for interfaces described in this document.

In addressing the problem above, one must recognize that there are three different points in the lifetime of a Web document when these concerns arise:

  1. Author Time: the author of the document typically saves the contents of all the controls directly in the document so that the working document may be entirely self-contained. This is the same idea as "design time" in the original OLE Controls specification, and a container's UserMode ambient property is set to FALSE at this time.
  2. Publish Time: the author has completed creation of the document and wishes to finalize it for publication. This is not so much an operational mode because it is a transition between design and "ready-to-run" states. Anyway, at this point the author can break the data for any and all controls out into separate document/storage locations, assigning to each control the name of its storage location.
  3. Run Time: a user is viewing the document with controls that must become interactive at some point. The controls whose data exists at some other location must progressively retrieve their data (using the names assigned at publish time, if needed) without blocking the entire process—that is, cooperating with the container. In this state, a container's UserMode property is set to TRUE.

It is also a requirement of this document to preserve as much of the existing Windows, COM, and OLE programming model as possible—that is, to avoid creating new technology whenever possible, especially to avoid adding additional properties or interfaces that all controls would have to support regardless of their feature set. This ensures that this document does not significantly raise the amount of baseline technology for controls, meaning that only controls that need Internet features implement support for those features. It also leverages a great deal of work that has already been done on OLE Controls, both in design and implementation.

Note: Everything in this document applies to generic COM objects as well as controls, although the term "control" is used most often. In fact, the distinction between controls and generic COM objects is gradually becoming non-existent. Controls are, at the time of writing, now considered to be nothing more than a COM object with support of the IUnknown interface at the minimum. Numerous optional interfaces provide the specific features that the control requires (such controls no longer use the "Control" registry key for identity, but use instead a new component category). This allows a control vendor to implement only as much code as is absolutely necessary for the control's functionality, placing the small added burden on the container to degenerate gracefully when a certain interface or feature is not available.

"Controls," as the term is used in this document, refers to any generic usage of OLE technologies in creating component software. The solutions presented here are extensions to the totality of OLE (not just extensions for a complete OLE Control that implements all possible control features). It should also be noted that this document describes only minimal extensions to the OLE model that has succeeded in the marketplace for several years now. As such, this document does not contain a substantial amount of "new" technology, merely extensions to make the existing technology work well in a new environment.

The extensions in this document also apply identically, without modification to the new additions to the OLE Controls model to support windowless controls.

Note: For more information on this view of controls, see the Control Guidelines document listed in the references.

Non-Requirements

In any situation where the source and consumer of any data are physically separate, there is the possibility for "broken links" where the consumer loses track of the source. This is a problem when it comes to standard OLE compound documents where a user may move a source or consumer document independent of the other, possibly breaking any links between the two. This problem exists because file systems are read-write as far as the user is concerned.

There is ongoing work to solve the link-tracking problem for OLE compound documents where monikers are involved. As solutions become available in that context, they will also become available to any other architecture that employs monikers. As moniker usage is part of this document, some degree of link tracking automatically comes into play without any extra work on the part of controls.

Furthermore, for Internet browsing purposes, link tracking is generally not required because it is assumed that only authors/designers of Web pages and Web sites will have the capability to rearrange the relative positions between source and consumer documents. In other words, the user sees the Web as a read-only file system. Because of that, it is the author's/designer's responsibility—with the help of an authoring tool built on the system-level link tracking services—to ensure that a document with external links contains accurate specifications of those external links. The same issues apply to hyperlinks, but no solutions are readily available given the widely distributed and heterogeneous nature of the Web. This document simply doesn't attempt to address link tracking.

This document does offer some assistance to authoring tools for managing external references made within controls. At the present time, however, it doesn't offer any solutions for nested controls where external references may exist at any level. This document handles a large majority of cases, but does not provide a complete solution.

It is also an author's/designer's responsibility to manage data stored in external locations so that the data can be removed when no document container links to those sources. For example, someone could author a document with a picture control whose data comes from a source specified with a URL moniker. However, at a later time, someone (the author or another person) could delete that picture control from the document. The author would then be responsible to determine whether the data source itself should be deleted if there are no controls referencing that data any longer.

This document does not offer any solutions to this resource management/reference counting issue. Solutions are the domain of content-management and authoring tools.


Control Instantiation

How does a container bring control class code (binary executable code) into memory and instantiate a control of that class? There are two relevant scenarios:

  1. The control's class code is on the client machine already (or somewhere in the name space addressable through the registry on that machine). Therefore, the document only needs the CLSID of that class code because COM/OLE provides mapping from CLSID to code bits using the registry. A container generally creates a control directly using the CLSID with CoCreateInstance.
  2. The control's class code is located somewhere other than on the client machine, possibly on another Internet site, and must be brought to the client machine before a control of that class can be instantiated. The exact details of this "Code Download" scenario will be described in a future specification.

This document only concerns itself with scenario 1 above, because the purpose of "code download" described in scenario 2 is to install a control on the client machine so that scenario 1 works.

The next three sections describe the issues for instantiation at author, publish, and run time.

Author Time Instantiation

A designer of a document or page will use the user interface functionality of the authoring tool to decide which object or control to insert into a document, ultimately ending up with a CLSID for the object or control. The authoring tool is responsible for providing the browsing capabilities to locate objects and controls wherever they may be, in the local name space or on some other Web location. For more information on standards related to browsing available object and control classes, see the user interface guidelines.

Once a control's CLSID is known, the authoring tool simply uses standard COM/OLE instantiation (using CoCreateInstance, OleCreate, and so on) to instantiate the control. In this case, the authoring tool will need to include the control's CLSID when saving the document persistently. Handling other possibilities is still under consideration.

In other respects, the authoring tool treats each control instance in exactly the same manner: calling methods, setting properties, attaching event handlers, specifying layout, and so on.

Saving a Control at Publish Time

At publish time, the authoring tool must create a document, in HTML or another format, that describes the exact source of each control's class code, because it will be referenced at run time, in addition to each control's instance data.

If the code is assumed to be on the client machine along with the browser, the authoring tool need only store the CLSID for the control class (the CLASSID attribute in HTML) in addition to the control's properties and persistent data, as described below. Such would be the assumption for standard system-provided control classes as well as implementations that the document will manage in its own code repository (part of a future specification).

If the control's class code will be found at another Internet site at run time, the authoring tool must save the URL for that site. This can be done by saving the URL text directly (as the CODE attribute in HTML).

If the document incorporates custom control classes that are on the author's machine at publish time, but may not be on each client machine at run time, the authoring tool must facilitate the placement of the class code (according to license restrictions) on an appropriate Internet site, converting the CLSIDs of the control instances in the document into the appropriate monikers and then saving the monikers in the document.

In addition to each control instance (CLSID/moniker and the control's instance data), the authoring tool, at publish time, also saves its own "extended properties," which a control instance itself never deals with, such as Name, Width, Height, Align, and so on. Documents stored in their own formats use whatever data formats they want; documents saved in HTML must follow the guidelines described in HTML Standards.

Run-Time Instantiation

An Internet browsing tool, when it wants to view a document, will generally activate the necessary viewer for that document type, which may be itself (in HTML cases, for instance) or some other view through DocObjects. The viewer then loads the document, recreating the control instances therein according to the prioritization scheme determined at author time.

"Recreating a control" means creating a previously saved instance of a control so that some persistent data for that control exists in the document or somewhere else. In the OLE model, instance data always has an associated CLSID—thus, the container knows which class code it must have to instantiate an uninitialized control using the CLSID directly ( CoCreateInstance, OleLoadFromStream, and so on). Once the container creates that instance, it initializes the instance with whatever persistent data was previously saved through the Load member of some IPersist* interface. A section below covers exactly where this persistent data comes from and which interfaces are involved.

In some cases, the class code for a control and its instance data resides in other locations named with monikers (or URLs, which can be turned into URL monikers as is the case with the CODE attribute in HTML). In this case, the container must first retrieve the control's class code and then proceed with creation and initialization as described above.

As with initial creation, the container now has an interface pointer to an initialized control that is generally ready for use (depending on the internal state of the control as discussed in a later section). At this time, a container may have other properties in its storage that lay outside the control's own persistent image (such as PARAM fields in HTML). In that case, it sends these property values to the control after the control has returned from IPersist*::Load.


Storage of Control Persistent Data

Note This section is primarily background material to set the stage for a later section. Those interested only in the details of writing good Internet-aware controls and containers can skip directly to that section.

For the purposes of this discussion, any given control may potentially have the following types of persistent data:

  • CLSID (16 bytes): The CLSID identifies the class code that can read other data that follows.
  • Properties (usually less than 10K-20K bytes): The control has a set of named values, accessible generally through OLE Automation (that is, vtable interfaces with type information, IDispatch, or dual interfaces). These may be standard or custom properties, of course.
  • BLOBs (arbitrary sizes): The control has any number of large binary data blocks, each of which exists in any format (standard or custom) and can be arbitrarily large. Bitmaps, videos, sounds, and other sizable data fall into this category where controls are concerned. "Native data," as used to describe the persistent data of a "compound document object" in OLE Documents, is another example.

Note The description of these three elements is for conceptual purposes only. These elements do not describe any kind of actual stream, file, or data format.

The following illustration shows the CLSID, properties, and BLOBs.

Figure 1: Possible elements of a control's persistent data

There may be controls that have no persistent data; none of these elements exist in a document. In that case, the container has the CLSID of the control (directly from the CLSID attribute in HTML or indirectly from CODE) and the control needs no other initialization after creation.

When a control has any persistent data at all, the CLSID element always exists along with one or both of the other two elements. How these elements are stored in relation to the container document involves the concepts of "embedding" and "linking," which originated in OLE Documents (the OLE compound document architecture), described in a following section. Because of certain limitations inherent in the compound document model, that section extends the "embedding" and "linking" concepts as persistence mechanisms only to allow more flexibility in control implementations. Additional sections provide notes regarding the use of IPersistMoniker, HTML, and progressive property retrieval.

Many readers will already be asking themselves how a control might be able to split its properties element from its BLOB elements so that the CLSID and properties are stored in one location and the BLOBs in other locations. This topic is described further in a later section.

A Review of Embedding and Linking: OLE Documents

"Object Linking and Embedding" as OLE was originally called in its version 1.0 days, introduced the idea of "embedded compound document objects" and "linked compound document objects." These concepts carried forward into OLE 2:

  1. Embedding: All of the object's native data (and CLSID) is completely stored in-line—that is, embedded, within the document itself. The container also stores any container-owned information, such as a presentation cache, with the object's native data.
  2. Linking: The container still stores any container-owned information and the presentation cache in the document but the object's native data (and CLSID) is stored in an outside location. The location is named with some moniker which is itself serialized in the document (usually the object's CLSID is cached here as well for various optimization purposes). Linked objects also have the user interface restriction that they cannot be in-place activated.

The following illustration illustrates embedding and linking.

Figure 2: Embedding and Linking as persistence mechanisms in OLE Documents

Because of the demands of the presentation cache, a container in OLE Documents must always provide an instance of IStorage to each embedded or linked objects. In the embedding case, the class code for that object must itself implement IPersistStorage. In the linking case, OLE provides the "default handler" that handles the moniker and the cache in the container document (through IPersistStorage), but the class code itself has to provide whatever interfaces are appropriate for the binding behavior of the moniker. For example, if a File moniker is used, the class code must implement IPersistFile to learn from the moniker where the native data resides on the file system.

Note also that an embedded (or even linked) object may internally, in its own native data, store references to external data sources, essentially doing a simpler form of "linking" unbeknownst to the container. The system-provided "Package" object is a perfect example of this. A Package can contain an "embedded" file or a "command line" along with an icon and label (which make up its presentation in the document). When a file is embedded in the package, the file image itself makes up the package's "native" data. When the package contains a command line, the command line is embedded along with the icon and label, but the "native" data is ultimately in some other file in the name space as described by the command line. This can be described as a "link" to that other file, although the link is entirely internal to the package object and is entirely hidden from the container.

While this architecture works fine within the confines of a single high-speed file system, where storage space is cheap and network latency and transfer speed are usually not an issue, it does not apply all that well to an environment like the Internet. In particular there are the following problems:

  • There is almost always some sizable data stored inside the document, usually the presentation cache. Unless the container chooses to eliminate the cache entirely, the document size becomes significant, which is undesirable in a slow-speed network.
  • An embedded object can only use IPersistStorage as a persistence mechanism, which is simply too "heavyweight" for many types of controls that store all their properties in only use a few hundred bytes or less (such as simple labels, buttons, checkboxes, group boxes, scrolling marquees, and so on). This leads to wasted storage space.
  • A link source must generally implement IPersistFile to support moniker binding, which works only in the domain of UNC path names whereas the Internet requires the ability to use URLs.
  • Any external references in the object's native data are known only to the object, which precludes any container participation in managing or assigning those locations and in managing the data stored there.

OLE Controls as first introduced in 1994 gave controls the ability to implement the IPersistStreamInit persistence mechanisms for the "embedding" case, eliminating the caching issue in the process and providing a lightweight alternative to IPersistStorage. However, OLE Controls was not originally concerned with complete flexibility in persistence mechanisms nor alternate forms of "linking" that addressed all of these issues.

Persistent Embedding and Linking (IPersist* Interfaces)

This document extends the concepts of embedding and linking to work outside the particulars of OLE Documents ( IPersistStorage, caching, IPersistFile), specifically to work with all available persistence mechanisms as well as with URL monikers and the possibility of asynchronous retrieval of linked data. The concepts of embedding and linking are described as follows:

  1. Persistent Embedding: The object's/control's CLSID, properties, and BLOBs are completely stored in-line—that is, embedded, within the document itself.
  2. Persistent Linking: The object's/control's CLSID, properties, and BLOBs are stored in another location identified with some single moniker.

Note The "linking" architecture of OLE Documents is itself more than just a persistence mechanism because it also involves various user interface standards, such as the stipulation that a linked object cannot be in-place activated. Any user interface guidelines concerned with OLE Documents are not of interest to what this section calls "Persistent Embedding" and "Persistent Linking" because the mechanisms here are solely concerned with the location of data and have nothing to do with user interface models. A control can, thus, work with persistently linked data while still being in-place activated.

In addition, in OLE documents the idea of "linking" generally means that the source itself (of the linked object) supplies the exact moniker to name the object. In "persistent linking" the container provides the moniker to the object and the object binds that moniker to some piece of storage in which the object reads or writes its data.

The following illustration shows persistent embedding and persistent linking.

Figure 3: Persistent Embedding and Persistent Linking

Again, a later section covers the case where one or more of the control's properties name other external storage locations so that the BLOBs do not have to be stored with the control's properties in either the embedding or linking case. For the purposes of this discussion, we assume that any in-line BLOBs in the embedding case are still small so that the byte count of the properties and the BLOBs still fall within a reasonable size. One can simply see these small portions of binary data as additional control properties.

The extensions that enable these generic persistent mechanisms are as follows:

  1. A control may use any of IPersistStreamInit, IPersistStorage, IPersistMemory, and IPersistPropertyBag as a Persistent Embedding mechanism. A control can choose to support as many of these as it wants.
  2. A container that wants to use Persistent Embedding must be able to provide controls with storage space appropriate for the preferred interfaces on the control: IStream for IPersistStreamInit, IStorage for IPersistStorage, memory (void *) for IPersistMemory, IPropertyBag for IPersistPropertyBag, and IPropertyBag2 for IPersistPropertyBag2. Containers will determine what method is used to save a control initially (according to its own preference for these methods) and expect that the control can be reinitialized through the same means.
  3. A control may use any of IPersistStreamInit, IPersistStorage, IPersistMemory, and IPersistFile as a Persistent Linking mechanism, again choosing to support as many interfaces as wanted ( IPersistMoniker is also an option although the feasibility of this interface is questionable at this time). It is strongly recommended that Internet-aware controls implement at least one other interface than IPersistMemory and IPersistFile because both of these require that all the data exists locally, which is unsuitable for use with asynchronous data transfers.
  4. A container that wants to use Persistent Linking must use a moniker that supports the possible persistence mechanisms that a control might support. In the immediate term, this is limited to the URL moniker.
  5. A container is allowed to make a copy of a control's persistent data at any time through any IPersist*::Save member functions regardless of which IPersist* interface was used to initialize the control. When doing so, the container must not pass TRUE as the fRemember argument of IPersistStorage::Save, IPersistMoniker::Save, and IPersistFile::Save and must pass FALSE as the fClearDirty argument to IPersistStreamInit::Save, IPersistPropertyBag::Save, and IPersistMemory::Save. The container must also call the SaveCompleted members of IPersistStorage and IPersistFile (and perhaps IPersistMoniker) with NULL arguments after calling their Save members.

The following table summarizes all these IPersist* interfaces:
Storage Location | Persistence Interface | Comments
Storage Element | IPersistStorage | Standard in OLE Documents; the container provides an IStorage pointer to the storage element in which the control can create any structure it wants. This will be potentially common for author-time scenarios, typically rare for publish/run-time scenarios.
Expandable Stream | IPersistStreamInit, IPersistStream | Most suitable for small and fully embedded controls; all the data including paths goes into one stream which can be easily placed inline in the document. IPersistStreamInit is a superset and replacement for IPersistStream. IPersistStreamInit should be used preferentially.
Fixed-Size Memory Block | IPersistMemory | Alternative for IPersistStreamInit, but allows the container to specify a fixed-size memory allocation as the storage medium with the restriction that the control does not attempt to access data outside that boundary.
"Property Bag" (container-supplied) | IPersistPropertyBag | Alternative for IPersistStreamInit in which the control tells the container to save and load individual properties (described in a VARIANT) through IPropertyBag. The implementor of the property bag can deal with each property in any way it wants.
"Property Bag 2" (container-supplied) | IPersistPropertyBag2 | Alternative for IPersistStreamInit in which the control tells the container to save and load individual properties (described in a VARIANT) through IPropertyBag2. The implementor of the property bag can deal with each property in any way it wants.
File | IPersistFile | The object is given a UNC path name and is told to read or write its data to that file.
External: named with a moniker | IPersistMoniker | The object is given a moniker and told to read and write its data to whatever storage mechanism ( IStorage, IStream, ILockBytes, or IDataObject) it wants when dealing with that external data. The storage mechanism may also be asynchronous; in that case, the IPersistMoniker implementation understands the necessary considerations.

Details about IPersistMemory, IPersistPropertyBag, and related interfaces are provided in a later section. For information on IPersistMoniker, see Asynchronous Monikers.

These extensions are basically concerned with the protocol through which a container communicates persistence intentions ( IPersist*::Load and so on) to controls in its document, which also addresses the needs of author, publish, and run-time scenarios. At author time, the designer may want to keep all the data for all controls in-line so there is only one document to manage, or some controls may be told to save and load their data in another site already. At publish time, the author might want to store the bulky portions of data (controls with large BLOBs) in other locations, thus breaking the document up into its final distribution on the Internet. At run time, then, the controls in the document will access their data as instructed to do so by the container.

An Internet-aware object or control is one that understands the options available here and implements support for whatever mechanisms it needs and can support best. Such a control need not support all storage cases and can choose to support only those it sees fit to support or can support reasonably well. At a minimum, controls that have any persistent data should implement at least IPersistStreamInit or IPersistStorage (whichever is most suitable), adding other interfaces as features demand them. For example, controls whose persistent data is made up entirely of name/value property pairs will likely implement IPersistPropertyBag as well). Information about why a control might choose to implement IPersistMoniker is provided in a later section.

If a control for some reason only implements one persistence interface out of IPersistStreamInit, IPersistStorage, IPersistMemory, and IPersistPropertyBag, it must mark itself with the appropriate component category to say that support for that interface in a container is mandatory. These categories are described in a later section. Controls that implement IPersistStorage and can work in down-level containers as simple compound document embeddings can mark themselves with the "Insertable" registry key so that they appear in the Insert Object UI of such containers (a category is not used because down-level containers don't see categories).

Note that it takes less than 200 bytes to persistently save values for all of the standard properties defined for a full-featured OLE Control, such as text, caption, colors, drawing styles, and font; if the control wants to include a small metafile for immediate rendering, adding a few hundred bytes more bytes should not be a big problem.

On the other hand, containers should support—for Persistent Embedding—as many different persistence interfaces as reasonable with IPersistStreamInit and IPersistStorage as a baseline, adding support for IPersistMemory for optimization purposes and IPersistPropertyBag for Save-As-Text capabilities. Authoring tools and containers must also pay attention to component categories when it does not implement support for any one interface so that it can avoid inserting a control into a document when its persistence needs cannot be met.

In this Persistent Embedding case as well, the container chooses which interface it will attempt to use first with any given control. Some containers may look for IPersistStorage before IPersistStreamInit; others will try IPersistPropertyBag before all others, or will place IPersistMemory before IPersistStreamInit as the former might be more efficient for the container. Because the size of embedded data is of high concern for Internet-aware containers, IPersistStreamInit and IPersistMemory should generally be given priority over IPersistStorage and IPersistPropertyBag as they generally produce the smallest amount of data.

Note that it is perfectly reasonable for a container to have a control save its data into any location specified in any of these interfaces after which the container copies the resulting binary data to another location altogether. The container then saves a reference to that other location so that when loading the control, it can recreate the necessary storage structure and hand it back to the control. This option, by the way, has been available in OLE since version 1.0.

For Persistent Linking, containers need not be concerned with the interfaces directly but must understand its own usage and storage of monikers, specifically URL and other asynchronous monikers. Because the monikers themselves internally query for and call the various IPersist* interfaces, the container does not have to understand those persistence interfaces directly. Such intelligence is encapsulated in the monikers.

In this case, the moniker itself chooses the priority as the container merely needs only to choose how it will save the moniker. As described in Asynchronous Monikers, such monikers will look for interfaces in the order of IPersistMoniker, IPersistStreamInit, IPersistStorage, IPersistMemory, and IPersistFile, the latter two requiring that all the data is available before any members of either interface can be called.

The use of IPersistMoniker is questionable and has limitations. This issue is described in a later section.

In both embedding and linking cases, the container is responsible for handling the details of asynchronous storage as described in "Compound Files on the Internet" and "Asynchronous Monikers Design Specification." In short, all persistence interfaces other than IPersistMoniker are considered synchronous in that the control expects all the data to be available when it gets a call to IPersist*::Load. In the case of IPersistFile and IPersistMemory, this is explicit because you can't create the file or the memory block unless you have the data.

In the case of IPersistStream[Init] and IPersistStorage, a container can pass either a "synchronous" storage object or a "blocking" storage object. In the "synchronous" case, the container will retrieve all the data before calling IPersist*::Load so nothing has changed from all historical uses of these interfaces. In the "blocking" case, the data might not actually be available, but any call that the control makes to IStorage::OpenStorage, IStorage::OpenStream, or IStream::Read (and so on) will simply not return until the data is actually available. From the control's point of view, the storage or stream objects are simply slow—the control won't usually care because it just waits for the call to return. But from the container's point of view, simultaneously loading multiple controls using asynchronous "blocking" storage may be a perfect way to manage multiple data transfers.

Use of IPersistMoniker

As described in a previous section, the IPersistMoniker interface is the primary interface through which an asynchronous moniker will attempt to have a control initialize in the Persistent Linking case. In general, IPersistMoniker is the successor of IPersistFile that allows persistence to and from any abstract location that can be named with a moniker as opposed to only a file name. The interface is defined as follows (see Asynchronous Monikers for complete information):

interface IPersistMoniker : public IPersist
   {
    HRESULT IsDirty(void);
    HRESULT InitNew([in] IMoniker* pmkStore, [in] DWORD grfMode
        ,[in] IBindCtx* pbindctx);
    HRESULT Load([in] IMoniker *pmkStore, [in] DWORD grfMode
        , [in] IBindCtx *pBindCtx);
    HRESULT Save([in] IMoniker *pmkStore, [in] BOOL fRemember
        , [in] IBindCtx *pBindCtx);
    HRESULT SaveCompleted([in] IMoniker *pmkStore);
    HRESULT GetCurMoniker([out] IMoniker **ppmkStore);
    }

In short, this interface is just like IPersistFile (plus the InitNew member) except that where IPersistFile takes a filename string, IPersistMoniker takes a moniker and a bind context. The semantics of the Load and Save members are as follows:

pmkStore->BindToStorage(pBindCtx, ..., IID_<xxx>, &pInterface);

if (load)
    pInterface->[Read](...)   //May be asynchronous
else if (save)
    pInterface->[Write](...)

That is, the control is asked to save or load itself from a moniker using the supplied bind context, and the control is responsible to call IMoniker::BindToStorage to get the appropriate storage-related interface to which it reads (or writes) its data. This may involve asynchronous considerations as described in the Internet Files and Asynchronous Monikers documents. When the control obtains an asynchronous IStorage or IStream, it has to ensure that it can handle IMoniker::BindToObject, returning a NULL interface pointer that will be passed to IBindStatusCallback::OnDataAvailable when it becomes available; IStream::Read and other reading calls that might return E_PENDING; and data that becomes available in segments through IBindStatusCallback::OnDataAvailable. The considerations here are the same as for any client of an asynchronous monikers, so again, see Asynchronous Monikers for more information. A later section also discusses this to some extent in terms of a control's use of "data paths."

Thus, implementing IPersistMoniker is not a requirement, even for asynchronous transfer of persistently linked data that goes on outside the control before any of its other IPersist*::Load members are called. This is because asynchronous monikers themselves (that is, the URL moniker presently) will take whatever data it sees at the named location and package that data in some storage object appropriate for other IPersist* interfaces. That is, if the moniker does not find IPersistMoniker on the object, it will query for IPersistStreamInit. If that interface exists, the moniker will wrap up the data in an asynchronous-blocking IStream and pass it to the control. If the interface is not there, the moniker will try other interfaces like IPersistStorage, IPersistMemory, and IPersistFile, in turn, wrapping the data in an asynchronous-blocking IStorage if needed, or retrieving all the data and placing it in memory or a file before handing it to the control.

Relationship to the HTML OBJECT Tag

It is useful at this point to describe the relationships between the persistent embedding and persistent linking scenarios described above and the attributes described in HTML Standards, namely the CLASSID, DATA, and PARAM attributes within an OBJECT tag.

In the persistent embedding case, the authoring tool that is writing the HTML document first asks the control to save all of its data through some persistence interface. After the IPersist*::Save call, the authoring tool has some bucket of control-created bits and the CLSID for the control itself. When writing the HTML OBJECT tag, the authoring tool always writes a CLASSID attribute and then includes a DATA attribute describing the control's data in some way. In all cases, the data must have a CLSID in the first 16 bytes to identify the exact format of the data.

In some cases, the stream data might be too large. In that case, the authoring tool might use IPersistPropertyBag to be handed each property in turn, which it would then write into the HTML document as individual PARAM attributes. Other PARAM attributes might also be later added manually to this set by someone editing the HTML directly.


Data Path Properties

The impetus for much of the work in this document finds its origins in the concept of "progressive rendering" ("progressive disclosure") for something like a picture or a picture control. In such cases, the picture should be able to display an initial low-detail picture, followed by mid-detail and high-detail renderings.

This document is, however, concerned with the more general problem of retrieving any significant amounts of data in a cooperative fashion from possibly many distributed locations after the control has already been instantiated and is possibly interacting with the container and the user in other ways. This capability has been called "Progressive Rendering," "Progressive Property Disclosure," and "Progressive Downloading." The term "Progressive Data Retrieval" will be used to refer to all of these more special cases at once, because it makes no assumptions about the exact type of data that is being retrieved—for example, it might be properties, might be images, might be anything.

In all the following sections, it is very important to keep in mind that the control is ultimately responsible for specifying the exact format and structure of any linked data and decides exactly how it will be pulled from the named source. The container has nothing to do with this other than being able to control the relative priority of the retrieval and being notified when the loading state of a control changes. So, for example, a container doesn't have to care about how a picture control chooses to progressively render low-, mid-, and high-detail images (from a GIF, for instance). The picture control would internally know where and how each image is stored in its data, and retrieve those in turn. The container would then be notified through IAdviseSink::OnViewChange when the control has enough data to do something meaningful in IViewObject2::Draw (which is a moot point for an in-place active control whose window is visible because the control would just update itself, but this will be important later on for windowless controls).

The point of all this is that the container chooses where to tell controls to store and retrieve their data; the controls choose how to store and retrieve that data in a cooperative, asynchronous, and progressive manner. The asynchronous aspect may apply to none, some, or all of a control's data.

This section is, thus, about "Data Path Properties," and describes the architecture for having a control save and load pieces of its data (BLOBs especially) in locations other than where the control's properties are kept. That is, for any number of reasons, it may be inappropriate or impossible for a control to store absolutely all of its data in the same location as its properties. Here are some examples:

  • The control is being embedded and its BLOB data is too large to store in an embedded manner, such as in an HTML document.
  • The control wants to retrieve BLOBs in an asynchronous manner regardless of where the control's properties are stored. A video control is a good example where this would be important.
  • The control may be sharing BLOBs with other controls so it is inappropriate and/or wasteful to duplicate the BLOB in each control's persistent state (this is similar to the original linking concept in OLE Documents). Sharing a company logo across many Web pages is a good example, and excellent performance can be achieved in this case when a URL cache is in operation—the BLOB itself may have already been downloaded from another page and can simply be pulled from the cache immediately.
  • The control may want to allow BLOBs to be modified so that whenever the control retrieves the data, it can retrieve the most recent information. A weather-map control that always shows the latest satellite image is a good example.
  • The control may simply want to separate properties from BLOBs for its own reasons.

To address these particular concerns, a control may use one or more "Data Path Properties" to reference external sources of data. As shown in the following illustration, each "path" (as they're called for short) is a value of some kind that is stored like any other properties in the control's persistent data. When the control needs to retrieve data from a source, it has the container create a moniker from the string representation of the property value and calls IMoniker::BindToStorage to retrieve some sort of storage pointer (usually IStream, but not limited to that). In binding a URL moniker, the control can choose whether the binding itself will be synchronous or asynchronous, and whether or not the data transfer itself will be synchronous or asynchronous.

Figure 4: Data Path Properties are values which can be parsed into monikers that reference sources of data external to the control's embedded properties

In short, each path property is a control-managed "link" to an outside data source. As described in an earlier section, this capability has always been available in OLE Documents, except that such links were always internal to the control. Such hidden use of external resources is not acceptable in the Internet environment where authoring tools need to manage the interconnections between many Internet sites.

Data Path Properties address the following issues:

  • How to expose the existence of path properties from a control.
  • How to specify exactly what type of data a particular path should refer to so that an authoring tool can provide appropriate browsing UI or automatically assign a path property referring to the correct format of data.
  • How to assign values to path properties, which may occur through either the authoring tool (automatically or manually) or the control itself (manually through its own property pages).
  • How to save each path property in the control's persistent data.
  • How to cooperatively retrieve data referred to with a path property ,which might include container participation.
  • How to communicate a control's "readiness" to a container at run time so that the container can manage dependencies on the control's internal state.

Linking capabilities using path properties are equally usable in both the Persistent Embedding and Persistent Linking cases as described in an earlier section. For clarity, the combinations of Persistent Embedding and Persistent Linking with Data Path Properties are shown in the following illustrations.

Figure 5: A control that uses data paths is embedded in a document, but each path references an external source of data

Figure 6: A control that uses data paths is linked to a document and itself contains addition paths that reference external sources of data

With data paths, a control can allow its persistent data to be distributed around a file system or the Internet. For example, a sophisticated motion-picture control may separately reference its AVI data, its audio data, and a lengthy text transcript of the dialogue. The control could then have MoviePath, AudioPath, and TranscriptPath properties. In a similar manner, a picture control might store three separate detail images and use the properties LowDetailImagePath, MediumDetailImagePath, and HighDetailImagePath (and maybe the motion picture control uses these three as well to provide progressive rendering of a static image—anything is possible). This document places no restrictions on the exact data formats stored in locations given to path properties—the control ultimately determines the formats it can employ.

The following sections provide information about data path properties, starting with how a control exposes its path properties and the data formats associated with those paths. This is followed with a discussion of how path values are assigned, how a control saves a path property, and how a control uses a property to retrieve data asynchronously. A later section describes how the control communicates its "readiness" to a container and how the container handles different readiness states.

Exposing Data Path Properties and Corresponding Data Format

The following characteristics apply to publicly exposed path properties and controls that provide them:

  1. A path property can be a property of any type, most commonly a BSTR (allowing easy manipulation through Microsoft®Visual Basic® code such as MyObj.TranscriptPath), but other types are allowed, such as IUnknown or IDispatch. Choice of the exact property type is the decision of the control developer.
  2. All path properties regardless of type are tagged with a special custom attribute GUID that identifies the property as being a "path." This simplifies the process for an authoring tool to find path properties with a quick check through the control's type information. This standard GUID is defined as GUID_PathProperty with the value of {0002DE80-0000-0000-C000-000000000046}.
  3. The custom attribute described in 2 can express one or more MIME type values to describe the actual data type that this path should reference. The syntax is the same as that for the Accept: fields of HTTP headers (see below, and see the HTTP specification for complete information).
  4. A path property can be assigned any dispID that the control developer sees fit.
  5. A control marks each path property with the [bindable] attribute. This is because a container may allow path assignment through its own user interface and may be displaying path properties in its own property browser. [bindable] is, thus, necessary to keep the property browser synchronized with the control's state. With this attribute, the control must call IPropertyNotifySink methods if the container has connected an implementation of that sink interface through the control's connection points. Controls can choose to mark path properties as [requestedit] and [displaybind], if desired, but these are not requirements.
  6. The control's coclass entry in its type information is tagged with another special custom attribute GUID that specifies "this control has one or more path properties." This standard GUID is defined as GUID_HasPathProperties with the value {0002DE81-0000-0000-C000-000000000046}. The value of this attribute is the number of path properties that exist in the control. This attribute thus allows authoring tools to quickly and efficiently determine if a control has path properties at all and quickly describes how many path properties exist.

The HTTP Accept: syntax is summarized as follows:

    <field>  =    Accept: <entry> *[ , <entry>
    <entry>  =    <content type> *[ ; <param> ]
    <param>  =    <attr> = <float>
    <attr>   =    q / mxs / mxb
    <float>  =    <ANSI-C floating point text representation>

as in:

Accept: text/plain, text/html, image/*
Accept: text/x-dvi; q=.8; mxb=100000; mxt=5.0, text/x-c

A "custom attribute" as now supported in type information is defined using the keyword custom with a (GUID, value) pair as arguments for the attribute. This is shown in the following example:

#define GUID_PathProperty 0002DE80-0000-0000-C000-000000000046

[id(1), bindable, displaybind, propget, custom(GUID_PathProperty
    , "text/plain; q=0.5, text/html, text/*")]
    BSTR TranscriptPath(void);

This defines the BSTR property TranscriptPath as a path property with the types of plain text, HTML text, and other text (using HTTP Accept: syntax). This information is retrieved from the type information through the new type library interface ITypeInfo2 as described in the next section.

As an example of defining path properties in this way, consider a control that has an AVI MoviePath property, a RIFF AudioPath property, and an ANSI text TranscriptPath property that would include them in its type library this way (in ODL syntax; exact MIME types have been omitted because the code below is demonstrating the definition of multiple properties):

#define GUID_PathProperty      0002DE80-0000-0000-C000-000000000046
#define GUID_HasPathProperties 0002DE81-0000-0000-C000-000000000046

[<attributes>]
library
    {
    importlib <path to header that includes GUID_PropertyPath and GUID_HasPathProperties>

    [<attributes>]
    interface IMyObjectProperties
        {
        //Other stuff here...

        [id(1), bindable, displaybind, propget, custom(GUID_PathProperty, "...")]
            BSTR MoviePath(void);

        [id(1), bindable, displaybind, propput, custom(GUID_PathProperty, "...")]
            void MoviePath([in] BSTR pathVideo);

        [id(2), bindable, propget, custom(GUID_PathProperty, "...")]
            BSTR AudioPath(void);

        [id(2), bindable, propput, custom(GUID_PathProperty, "...")]
            void AudioPath([in] BSTR pathAudio);

        [id(3), bindable, propget, custom(GUID_PathProperty, "...")]
            BSTR TranscriptPath(void);

        [id(3), bindable, propput, custom(GUID_PathProperty, "...")]
            void TranscriptPath([in] BSTR pathText);

        //Other stuff here...
        }

    [<other attributes>, custom(GUID_HasPathProperties, "3")]
    coclass MyObject
        {
        interface IMyObjectProperties;
        ...
        }

    }

Author-Time Discovery of Path Properties

Authoring tools and other containers will want to know exactly which control properties are, in fact, data path properties as well as the type of data that should be referenced through each property. This allows the authoring tool to effectively enumerate data paths and retrieve their current values, which is useful when the authoring tool needs to update any such properties to perform link management.

First, to determine if any particular control has any path properties, the authoring tool should perform the following actions:

  1. Retrieve the control's type information for its coclass either through IProvideClassInfo::GetClassInfo or from the type library directly. These methods result in an ITypeInfo interface for that part of the type library.
  2. Call ITypeInfo::QueryInterface(IID_ITypeInfo2, &pITypeInfo2) to retrieve the interface necessary to read custom attributes. If this interface does not exist, the container has to assume that no path properties exist.
  3. Call pITypeInfo2->GetCustData(GUID_HasPathProperties, &va) to retrieve the value of the custom attribute into the VARIANT in va. If this function fails, or if the value in va is empty or zero, the control has no path properties.

Having this knowledge in hand, the authoring tool can then enumerate the properties in any of the control's incoming interfaces and check if those properties are data paths using the following procedure. These steps assume that pTI2 is the ITypeinfo2 pointer to one of the control's interface or dispinterface entries (available through the ITypeInfo of the control's coclass):

  1. Get the count of the functions in the interface using pTI2->GetTypeAttr which returns a TYPEATTR whose cFuncs field has the number of function. Call pTI2->ReleaseTypeAttr when finished.
  2. Iterate in a for loop where the index counter ( index) begins at zero and counts to cFuncs-1 (cFuncs is from step 1) through the functions.
  3. For each function, call pTI2->GetFuncCustData(index, GUID_PathProperty, &va) to attempt to read the custom path property attribute's value into the VARIANT va.
  4. If the GetFuncCustData call fails, or if the contents of va are empty, this property is not a data path. On success, the property is a data path and the value in va will be the description of the MIME type.
  5. To determine the dispID of the property, call pTI2->GetFuncDesc and look in the FUNCDESC structure for the memid field, which is the dispID. Be sure to call pTI2->ReleaseFuncDesc when done with the FUNCDESC structure.
  6. For optimization purposes, one can count the number of path properties found so far and exit the loop early if that number matches the value that was read from the GUID_HasPathProperties in the steps used to determine if the control had any paths.
  7. Once the loop is complete, the authoring tool will have a list of dispIDs (which may have some duplicates) for all the control's data path properties. The authoring tool can use pTI2->GetNames with these dispIDs to retrieve text names for those properties.

What then does a tool do with such knowledge of paths and their data types? There are three general scenarios:

  1. If the authoring tool is interactive, it might be able to display the data browsing UI based on the data format associated with any given path property. That is, if the tool knows that a path property uses the MIME type given in the value of the GUID_PathProperty attribute and that tool has a UI that can browse for that type, the tool can enable a "..." button in its property browser UI that invokes a File/Open type of dialog box when pressed. The user then selects a file and the name of that data file is assigned to the path property.
  2. If the authoring tool is automatic, it uses other available information (for example, a data repository) to determine the source of any particular piece of data. Knowing the format that the control needs for a data path allows such a tool to find some appropriate data, create a path name for it, and assign that name to the control.
  3. When the authoring tool publishes a document to an Internet site, it will want to check for any dependencies on outside data—that is, it will want to check for any control properties that reference data outside the document. The authoring tool can then check whether that path will be valid at run time and if not, it can choose to relocate the data to an accessible location and assign a new path to the control before saving that control. This is necessary, for example, when a control references a file on the local file system when the document is being placed on an Internet site.

Of course, the sophistication of the authoring tool is almost limitless. With the tagging and MIME-typing of data path properties, this document guarantees that any such tool can always determine the data format associated with a path. It's then a matter of whether the tool recognizes that GUID.

Assigning Path Properties

The values of path properties—the strings that identify storage locations—are assumed to be assigned only at author-time. In this case there are two possibilities:

  1. The authoring tool obtains the name from the user or creates it automatically, assigning it to the control through a "set property" operation (through a dispinterface or dual interface depending on the nature of the control). Note that if a transfer is already underway for this property, the control must abort the transfer as described below.
  2. The control obtains the name itself from its own property page UI; in that case it makes the change unilaterally. Any path properties marked with [requestedit] means the control is required to call any connected IPropertyNotifySink interface's OnRequestEdit function to decide whether the change is actually allowed. If OnRequestEdit returns S_FALSE, the control must not change its path in this way (the authoring tool is essentially saying that the document or Internet site state is read-only as far as document/page contents are concerned).

In both cases, a change in the value of any path property will generate a call to any connected IPropertyNotifySink interface's OnChanged member, because path properties are marked with [bindable]. In both cases, an authoring tool that displays its own property browsing UI would update itself to reflect the current state of the control. This also gives the authoring tool the chance to modify the path when the user changes it through the control's property pages directly. When the user makes the change, the control will notify the authoring tool through OnChanged. The authoring tool then reads the new value of the path property, modifies it as necessary, and sends the modified value back to the control. Therefore, in both the first and second cases above, the authoring tool has the final say in the exact value assigned to any path property.

The most preferable case is where the value is simply a container-relative name. Relative file names and URLs are identical in form: "../pictures/tree.bmp" serves equally well in both the local file system and Internet environments. Relative item names, like "!Picture 6", identifies the path as pointing to some other part of the same container document. In URL cases, the name may be absolute, which indicates that there's no relative path between the container document and the location described in the data path.

In any case, a control must always obtain some moniker from a data path name to bind to the storage so referenced. Because the name may only be relative, the container is always given the chance to create the actual moniker itself. The details of this are covered in the next section.

Use of relative names allows an authoring tool to move the root document—and possibly an entire site—around, without invalidating any external links stored in data paths. For example, consider the author-time case where the document being created is stored in "c:\pages\mypage.doc" and in that document is a bitmap control with an ImagePath property. The authoring tool assigns the name "frog.bmp" to this property. When the control wishes to access the bitmap named in the ImagePath property, it passes "frog.bmp" to the authoring tool and asks for a moniker in return. The authoring tool sees two file names and can combine them into one "c:\pages\frog.bmp" name. The authoring tool then returns this name in a moniker so that the control can bind that moniker and retrieve the data.

Now when the author saves the document to an Internet site at Publish time, the location of the document changes to something like "http://www.bogons.com/bogazoid/mypage.htm". In addition, the authoring tool copies "frog.bmp" to that same site where it becomes "http://www.bogons.com/bogazoid/frog.bmp". At run time, the control reloads the path property name "frog.bmp" and asks the container to make it a moniker. The container simply combines the document URL with the relative name "frog.bmp" to generate the exact URL for the bitmap.

In some cases, the control may have obtained an absolute filename (or URL) from its own property pages at author-time. So, for instance, the user types in "c:\pages\frog.bmp" instead of just "frog.bmp." Initially, the control will internally save the absolute string, but because it sends an OnChanged notification (data path is [bindable]), the authoring tool retrieves this new value and computes the relative path to that same file from the document's current location (using the file moniker's RelativePathTo member, for example). The relative path in this case will end up as just "frog.bmp", which the authoring tool simply assigns back to the control.

In the case where the user gives a control an absolute URL, there's nothing the authoring tool can do at author-time when the document is stored on a local file system. At publish time, however, the authoring tool first determines the new URL of the document. It can then cycle through all known path properties of each control before it saves that control. If the authoring tool finds an absolute URL in a property, that tool can attempt to compute the relative path between the document's URL and the path's URL. Once it has computed this relative path, it can assign the relative name to the path property and proceed with saving the control.

When the authoring tool supports relative path names that are not either file names or URLs, but are instead item names that identify a piece of storage in the document itself, such as "#Picture 6", the authoring tool must specify, in its own documentation, what prefix character marks the name as an item of some kind. This is a note to the container's moniker creation mechanism in IBindHost::CreateMoniker as described in the next section.

Storing and Comparing Path Properties

Controls should always ensure that they can produce a string for any path property. This is because all interfaces that deal with path properties (either IDispatch for the property itself or IBindHost, in a section below, for converting the string into a moniker) deal with paths as strings.

However, controls should not compare two path properties as strings. Instead, the comparison should be carried out using two monikers created from those strings through IBindHost::CreateMoniker (see below). With monikers, the comparison between pmk1 and pmk2 is the call pmk1->IsEqual(pmk2) or pmk2->IsEqual(pmk1).

Integrated Author Tool Browsing and Control Property Pages

A sophisticated container or authoring tool will likely provide its own browsing UI for various types of data, typically the most common types of data that would be assigned to path properties (images, sound, and video). To maintain as much of a consistent user interface as possible, a container should make such browsing UI available to a control's property pages so that the property page can invoke that UI instead of showing its own, which would likely be inconsistent.

This integration is achieved through a container-side interface called IOleBuilderManager. An exact specification for this interface is not, however, available at this time and will be provided at a later date. The basic idea, however, is that through this interface a control's property page can test for the availability of container-side browsing UI and invoke that UI if it is available. This allows the page to enable a "Browse..." button for a particular path property, invoking the container's UI when that button is pressed.

Container Supply of Monikers Through IBindHost

Working with data paths requires, of course, the ability of the control to access data referenced by its path properties. The key part of such data access is providing some means by which the control turns a data path name into a moniker that it can bind. Because the container/authoring tool is the one that generally knows the context that modifies any relative path name, the container, in this relationship, is responsible for generating an appropriate moniker when the control asks for one.

This step happens through a container's (or authoring tool's) implementation of the interface IBindHost, which is implemented as a service available through a container site's implementation of IServiceProvider, and is the topic of the next section. The control accesses the bind host implementation by calling QueryInterface(IID_IServiceProvider...) on whatever site pointer it already has and calling IServiceProvider::QueryService(SID_SBindHost). Use of the service provider means that the bind host is generally a separate object from the site; although as an implementation convenience, it could be implemented on the site directly.

Of course, in order for a control to have a site through which it can access these services, the control must provide some means through which the container can pass its site pointer to the control in the first place. Many controls implement IOleObject whose SetClientSite member accomplishes exactly this. However, not all controls need to implement IOleObject, so a section below describes a simple siting mechanism using IObjectWithSite to achieve the same ends.

This document requires that a control using data paths supports a siting mechanism. If the control doesn't support IOleObject, it must support IObjectWithSite. A container that supports data paths must provide a site through one of these mechanisms and must generally implement IBindHost and provide access to it through the site's IServiceProvider. If a control that uses paths finds itself without a container site, it can still degenerate gracefully by calling MkParseDisplayNameEx itself, as necessary.

Overview of IBindHost

The creation of a moniker from a potentially relative path name is what is considered a container-side "service" that a control accesses through IBindHost::CreateMoniker. In addition, a control asks its container to download data through IBindHost::MonikerBindToStorage as described in a later section.

The IBindHost interface is defined as follows:

interface IBindHost : IUnknown
    {
    HRESULT CreateMoniker([in] LPCOLESTR pszName, [in] IBindCtx *pBC
        , [out] IMoniker** ppmk, [in] DWORD dwReserved);

    HRESULT MonikerBindToStorage([in] IMoniker *pMk, [in] IBindCtx *pBC
        , [in] IBindStatusCallback *pBSC, [in] REFIID riid
        , [out, iid_is(riid)] void **ppvObj);

    HRESULT MonikerBindToObject([in] IMoniker *pMk, [in] IBindCtx *pBC
        , [in] IBindStatusCallback *pBSC, [in] REFIID riid
        , [out, iid_is(riid)] void **ppvObj);
    }

The CreateMoniker member takes a text string of some path—which may be absolute or relative—and returns a moniker that references the actual absolute location. That is, the container combines its known document location with the path supplied in pszName to create the resulting moniker. How the container does this depends on the document path and the type of name found in pszName, which is either something that the URL moniker can handle (including file names) or an item name referring to some portion of the container document itself.

In the item name case, a container can specify a prefix character, like "#" or ">", that it will use to differentiate an item name from some other kind of name. This fact should be known to users of the container at author-time so that those users can enter the correct names in whatever UI is used to assign path values. When the container detects its prefix character, it knows that it has to parse the name according to its own rules.

In any other case, the container creates a moniker from the text string directly using MkParseDisplayNameEx This handles the cases where URLs are given as well as display names of File Item monikers and other similar composites. If for some reason IBindHost::CreateMoniker fails, the calling control can degenerate to calling MkParseDisplayNameEx itself, but this should only be used as a last resort.

Either way, the container now has a moniker for the data path as well as a moniker naming its own document. To return a full moniker from ParseDisplayName it calls pmkDocument->ComposeWith(pmkPath, kReturn) where pmkReturn receives the moniker to return to the caller.

This rule assumes that the document moniker class implements ComposeWith in an intelligent manner. If the document moniker is a File moniker, for example, and the path moniker is a File moniker, ComposeWith will return one File moniker with the combination. The same applies for any case where the path moniker is a URL and the document is either a File or a URL. It is even possible to mix a URL document moniker with a File moniker for the path, provided that the File moniker is not absolute (where the combination would make no sense).

When the path moniker is some item or composite, the File or URL moniker ComposeWith implementations simply create another composite.

The bottom line is that IBindHost::CreateMoniker will create a precise moniker that is the combination of the document's own moniker (obtainable through IOleClientSite::GetMoniker(OLEWHICHMK_CONTAINER) and a moniker created from the path name. The control can use this moniker to bind, but this is not the moniker that the control should save. See below for more information on persistence.

Generic Siting with IObjectWithSite

A control that does not need IOleObject, but still requires access to its container site, specifically for the purposes of accessing container-side services, must instead implement IObjectWithSite:

interface IObjectWithSite : IUnknown
    {
    HRESULT SetSite([in] IUnknown *pUnkSite);
    HRESULT GetSite([in] REFIID riid, [out, iid_is(riid)] void **ppvSite);
    }

In the absence of IOleObject a container can attempt to provide the IUnknown pointer to its site through IObjectWithSite::SetSite. IObjectWithSite::GetSite is included as a hooking mechanism, which allows a third party to intercept calls from the control to the site.

When a control needs to create a moniker from a data path, it obtains the bind host through the site's IServiceProvider interface and proceeds from there.


Cooperative and Asynchronous Data Retrieval

Once a control has a moniker for a data path property, it will eventually want to call that moniker's IMoniker::BindToStorage to retrieve an interface pointer through which the control can retrieve data (or write it as described in the next section).

To deliver optimal quality of service to browser users, a control must read data from an external source in a cooperative manner as much as possible. That is to say, the control should support reading linked data in an asynchronous manner, paying attention to container prioritization and allowing the container to participate in the transfer as it wants. To provide the container with authority over the binding operation, the control should use the IBindHost::MonikerBindToStorage and IBindHost::MonikerBindToObject member functions to bind to any moniker.

A control will first, of course, check whether the moniker it obtained from the container is, in fact, an asynchronous moniker at all by querying it for IMonikerAsync (see Asynchronous Monikers, which specified this interface as an alias for IUnknown). If this interface is not present, the moniker is synchronous and the control does not need to create an IBindStatusCallback callback interface to receive asynchronous notifications from the moniker bind operaion.

If the moniker is asynchronous, the control operates as any other client of such a moniker does, by creating an IBindStatusCallback callback interface for asynchronous download, as described in the document Asynchronous Moniker. The specific requirement for a control is that instead of binding to its moniker directly, it should bind to the moniker through its container, through IBindHost::MonikerBindToStorage or IBindHost::MonikerBindToObject. This call gives the container a chance to register any callbacks or other bits in the bind operation as desired. For example, a container that wishes to watch the progress of the transfer through IBindStatusCallback::OnProgress will register its own callback for the bind operation, paying attention to OnProgress callbacks and delegating all other callbacks to the control that initiated the bind operation.

If the container does not implement IBindHost, the control can always bind to its moniker(s) directly through IMoniker::BindToStorage and IMoniker::BindToObject. These should only be used in degenerate cases when there is no IBindHost present.

A control passes its IBindStatusCallback to its container directly through IBindHost::BindToXXX. However, if the control wishes to participate in format negotiation (for example, HTTP), it must create a bind context and register its FORMATETC enumerator on this bind context before calling IBindHost::BindToXXX. When a control initiates an asynchronous transfer for a data path, usually within IPersist*::Load, it should always obtain the data through IBindStatusCallback::OnDataAvailable exclusively. This is so the control can immediately release the moniker and the bind context and return immediately from IPersist*::Load so that the container can continue processing. Such code inside IPersistStreamInit::Load where another asynchronous stream is obtained, for example, would appear as follows:

HRESULT CImpIPersistStream::Load(IStream *pstm)
   {
    IMoniker *pmkPath;
    IStream  *pstmAsync;
    IBindCtx *pbc;

    /*
     * Load properties with pstm->Read, which is either blocking or synchronous.
     * Assume that one property is a path moniker, which we re-create with
     * OleLoadFromStream into pmkPath.
     *
     * Also, assume that m_pbsc is our IBindStatusCallback implementation, and
     * m_pEnumFE is our FORMATETC enumerator.
     */

    //pIBindHost is a pointer to the container's IbindHost.
    CreateAsyncBindCtx(0, NULL, m_pEnumFE, &pbc);
    pIBindHost->MonikerBindToStorage(pmkPath, pbc, m_pbsc, IID_IStream, &pstmAsync);

    //Do this only for async monikers.
    if (NULL!=pstm)
        pstmAsync->Release();

    pbc->Release();
    pmkPath->Release();

    //Data shows up in IBindStatusCallback::OnDataAvailable.

    return S_OK;
    }

When retrieving data, the path moniker that names the external storage location is either synchronous or asynchronous. If the moniker used in binding is a synchronous moniker, read calls from the control will effectively block the execution thread as is expected—this is how the OLE programming model already works with regards to moniker binding.

Of course, an object that does not want to block and can return E_PENDING as necessary from some of its interface members (see below) may elect to perform the otherwise blocking read operations in another thread, thereby allowing the original thread to continue. This still allows IPersist*::Load to return right away without waiting for the data to arrive.

The tricky part in handling asynchronous data is communicating the control's "readiness" state (including E_PENDING return codes). These topics are covered in later in this document.

Aborting a Data Transfer

Any control that is currently reading or writing data in an asynchronous manner must pay attention to IBindStatusCallback::OnStopBinding. If the control has all its data already, this notification merely says that the transfer is complete. If the control has not received all its data, the transfer was aborted for some other reason—perhaps the container scrolled the object out of view and stopped the transfers itself.

The control itself can call IBinding::Abort if it needs to stop the transfer for some reason. The control receives the IBinding pointer through IBindStatusCallback::OnStartBinding and must save it for later use. One such use is for handling the case where a container destroys a control while a transfer is underway—the control has to stop the transfer as part of its destruction procedure.

Also, if a container or authoring tool assigns a new value to a path property while the control is currently retrieving data asynchronously for that path, the control must stop the transfer through IBinding::Abort as soon as is reasonable and save the new path value.

Because a control cannot predict exactly how and when one or more data transfers will be aborted, it must be careful where reentrancy is concerned, especially inside the implementation of IBindStatusCallback::OnStopBinding (but in all other members as well). That is, the control should maintain an internal state machine to protect itself from operations that occur during or after an aborted transfer.

Transfer Priority

As the container owns the "document" as a whole, the container establishes which control will be given the right to transfer data first, second, and so on. When multiple transfers are going on at the same time, a container can control the relative priority of each transfer through its own implementations of IBindStatusCallback, which are used to control each bind operation (as managed through IBindHost::MonikerBindToXXX). A container can establish its desired relative priority in IBindStatusCallback::GetPriority. Once the transfer begins and the callbacks receive IBindStatusCallback::OnStartBinding, the container will have the IBinding interface through which it can set the priority there as well. Controls should not interfere with this process.

Controls that use WinINet or Sockets Directly

There are a certain class of controls that may want to bypass the mechanisms of the URL moniker to retrieve bits for some data path directly from the network through a transport layer like WinInet or Sockets. In such cases, the control is not willing to call IBindHost::MonikerBindToStorage, but instead wants to use the absolute URL directly.

To accomplish this, the control still asks the container for the full moniker in the first place because this step is necessary to resolve any relative path names. Once the control has the moniker, it can retrieve the full URL simply by calling IMoniker::GetDisplayName.

Object Persistence and Path Properties

When a control is asked to save its persistent data through some IPersist*::Save call, it is being asked to save its properties, including its current path properties and the BLOBs in the locations specified with those path properties. That is, once the control has saved its properties into the storage appropriate for the IPersist* interface in use, it also synchronously saves its BLOBs by obtaining the moniker for the path property from the container as described in a later section, calling IMoniker::BindToStorage with the container's bind context, and writing data to the storage obtained earlier. When IPersist*::Save returns, the object has completely written any data it needed to save.

A control should save its path property values as strings, not as monikers. In short, a control should simply save whatever values are assigned to its data path properties (as opposed to serializing any monikers that might have been created using these strings).

Container Flexibility in Moniker Choice

The discussion in the previous section made no stipulations as to what class of moniker must be assigned to any path property. This is intentional: a container can instruct a control to store its persistent data in any location of the container's choosing, as long as the container can create a path name that, when turned into a moniker, references the exact piece of storage that the control needs to load. In the case of creating a complex Web site, most of these monikers will be URL monikers. But there are many other moniker classes that can be used effectively, such as File monikers, generic composite monikers, or even custom monikers.

This design, therefore, allows the container full flexibility in choosing storage locations, depending on the authoring state of the document. At author time, the container (generally at the author's request) may choose to keep the persistent data of all controls embedded in the document. In this case, the container merely passes each control a name identifying some location in the document or on the local file system, such as a single Item moniker or a File moniker. The container then provides the necessary binding support for whatever moniker it supplies through IBindHost::CreateMoniker. In the case where the authoring tool has the data stored in the document itself, supplying a File Item moniker to the control means that the control's call to IBindHost::MonikerBindToStorage returns very quickly.

At publish time, the container may then choose to break whatever portions of control data it desires out of the document, saving that data in other locations and reassigning paths. Because the container will have some idea of the size of each control's persistent data, it may choose to leave some of that data embedded in the document anyway, leaving the controls concerned with simple File Item monikers (and such). If the container moves some data to other Web sites, it would create URL monikers for those sites and assign them the path properties of the appropriate controls. As far as the control is concerned, the data has moved, but the data is still named with some moniker. The control continues to use the same IBindHost::MonikerBindToStorage code that it did before.

What is important to understand here is that the control never has to distinguish between author-time and publish/run-time differences as far as its data path storage is concerned. The control always has some moniker naming its persistent data store(s), and always calls IBindHost::MonikerBindToStorage in all circumstances. This maintains a single programming model across all document modes, while still giving the container complete flexibility in choose storage locations and how the data in the document is distributed.

Exposing Path Properties from Nested Controls

Some controls, regardless of whether they themselves use any path properties, may contain other nested controls that do use such paths. Nevertheless, an authoring tool will want to construct a list of all paths used by all controls, regardless of the level of nesting involved. This facilitates site management—the authoring tool can examine all external data references from the top-level document and correct the paths involved if the site is being relocated.

Any control that itself uses one or more path properties need only expose those properties as described in earlier sections. Those controls that themselves contain other controls must expose those nested controls so that an authoring tool can check the path properties in those controls as well.

For this reason, a containing control must implement IOleContainer [note that the containing control might also choose to implement IOleItemContainer as well (which is derived from IOleContainer) to allow easier navigation to a particular control given the control's name] with the following behavior:
Member Function | Behavior
Iunknown members | Implemented as usual.
ParseDisplayName | Returns E_NOTIMPL.
LockContainer | Returns E_NOTIMPL.
EnumObjects | Returns an IEnumUnknown enumerator through which the caller can retrieve the IUnknown pointers for each nested control.

Given this behavior, the top-level container or authoring tool that wishes to examine all path properties in all controls, regardless of the nesting level, uses IProvideClassInfo::GetClassInfo to retrieve the control's type information and then uses the same procedures described earlier to build a list of the control's dispIDs that each correspond to some data path property. The caller can retrieve this property value, examine it, and modify it as necessary. This procedure is summarized in the following pseudo-code:

//In top-level container
construct IEnumUnknown for all top-level controls in pEnum
FindAllPaths(pEnum)
//Done

function FindAllPaths(IEnumUnknown *pEnum)
    }
    IUnknown *pObj;

    while (SUCCEEDED(pEnum->Next(&pObj)))
        {
        call IProvideClassInfo::GetClassInfo or obtain coclass type information via type lib

        if (SUCCEEDED(ITypeInfo2::GetCustData) and value is greater than zero)
            {
            iterate over properties
                {
                for each path property
                    {
                    examine the path
                    update/modify the path as necessary
                    }
                }

            ITypeInfo2::Release();
            }

        if (SUCCEEDED(pObj->QueryInterface(IID_IOleContainer, &pCont)))
            {
            pCont->EnumObjects(&pEnumNested)

            if (NULL!=pEnumNested)
                FindAllPaths(pEnumNested)

            pCont->Release();
            }

        pObj->Release();
        }
    }

Through this process, the top-level container can examine all path properties in use in the document. It is unlikely that this will be a seriously expensive operation in all except the most complex documents. Typically, there will only be a handful of controls on a page with any path properties at all, and even fewer containing controls with a significant number of nested controls. Second, third, fourth, and deeper levels of nesting will be exceptionally rare, so implementing this scheme with a recursive code structure will not be expensive in probably ninety percent of all situations.

What is described here for a containing control does not apply to controls that internally use some kind of helper object as a supporting component. In such cases, the helper object is not a "nested control"—it is merely a provider of a useful service. The control that is using the helper should not consider itself a "containing control" because it does not implement the necessary container-side services that would be necessary to nest an actual control.


Communicating Control "Readiness"

For all persistence interfaces other than IPersistMoniker, the authoring tool or container assumes that once IPersist*::Load returns, the control has loaded all of its properties. However, controls that use data paths may not actually have all of their data at this time. That is, within IPersistStreamInit::Load, for instance, a control might load a data path property for an AVI file and begin an asynchronous retrieval of that AVI data before returning from Load as shown in an earlier section.

When data is coming in asynchronously like this, the control may not necessarily be ready to handle all requests the container might make of it. Accordingly, the control may return E_PENDING from some interface member functions. The next section describes the use of E_PENDING.

In addition, certain user-supplied code, in a container like Visual Basic for example, may want to take action on the current "readiness" state of a control, such as enabling or disabling one control based on the ability of another control to accept certain calls. The second section below describes a standard property, ReadyState, that reflects this aspect of a control, and a standard event, OnReadyStateChange, that informs the container that the state has changed. Because the ReadyState property is primarily of interest to user-supplied code, this standard event is used instead of IPropertyNotifySink::OnChanged to make the event visible in programming tools, and to pass the new ReadyState value along with the event.

Use of E_PENDING

In the lifetime of any particular control that uses data paths, there are generally four (possibly five) distinct states of readiness that a control may have:
State | Description
Uninitialized | The control is waiting to be initialized through. IPersist*::Load.
Loading | The control is initializing itself through one or more asynchronous properties. Some property values may not be available.
Loaded/Can-Render | The control has returned from IPersist*::Load so that all its properties are available and it has started any asynchronous data transfers. The control's properties are available and it is ready to draw at least something through IViewObject2::Draw. Making properties available doesn't necessarily require the control's type information.
Interactive | The control is capable of interacting with the user in at least some limited sense and can supply its type information. Full interaction may not be available until all asynchronous data arrives.
Complete | The control is completely ready for all requests.

These states are listed in order of priority, which is to say that a control should strive to makes its properties and some basic rendering capabilities available as soon as it can (even if the rendering is to just draw a rectangle with text in it) before going on to become interactive and before being able to fulfill requests that depend on asynchronous data.

From the point in time when a control is first instantiated, certain interface members may return E_PENDING to indicate that the control has not retrieved enough data yet to carry out the request (note that all interfaces must be available through QueryInterface although none of their members may do anything yet). To understand the implications, it is useful to step through the moments in time during the loading of a document and the creation and initialization of the controls in that document.

  1. (NonExistent!): The container has loaded the document but has not yet instantiated any control. At this point, the container can display the document with nothing but rectangle placeholders for each visible control. That is, because no controls are yet loaded, the container cannot call IViewObject2::Draw for any of them, and must rely on cached information (extent, label, and so on) to render anything for a control.
  2. Unintiailized: The container has loaded a control, but the control is completely uninitialized—that is, the container has not yet called IPersist*::Load. The control must be loaded first before the container can expect to call any other interface member. If the container wishes to draw an image for the control, it must do so itself using any cached information (like a caption string).
  3. Loading: A control has returned from IPersist*::Load and is making properties available as they arrive. The control must be capable of rendering something minimal (like a rectangle and text) through IViewObject2::Draw and should give priority to retrieving its Caption and Text properties first, followed by rendering-related properties ( BackColor, ForeColor, DrawStyle, and so on)
  4. Loaded/Can-Render: The control has returned from a call to IPersist*::Load so that it has loaded its properties (or a control that was Loading has now retrieved all its immediate properties). At this point, the control must be prepared to at least render something minimal (like a rectangle and text) through IViewObject2::Draw. In simple cases, like a button, the control will already have all its data necessary for rendering such as colors, text, font, and styles.
  5. Interactive: The control has enough information to be mostly interactive: it can be in-place activated (and perform layout negotiation), handle all property operations, accept user input, and so on. The control also has its type information available. In addition, progressively higher detailed renderings (or other streamed data) may be coming into the control at this point so that the control sends IAdviseSink::OnViewChange notifications to tell the container that the control should be redrawn (that is, if the control doesn't have a window; if it has a window, it just redraws when it wants to).
  6. Complete: The control has loaded all of its data including that obtained through paths so that it can do more sophisticated renderings using possibly medium- and high-detail graphics.

During each state other than Complete, various interface member functions may not be operative; that is, they will return E_PENDING, and certain properties may not be available. The following table describes which interfaces and methods must be ready in each of these states, if the control supports those interfaces at all.
State | Required Interfaces/Members/Properties
Uninitialized | IPersist*::Load and IPersist*::InitNew
Loading | IViewObject2::Draw, Idispatch::Invoke for some properties.
Loaded/Can-Render | All of IviewObject2, Idispatch::Invoke for all properties that don't depend on extra data.
Interactive | All of Idispatch (methods and type information), all of IRunnableObject, IOleObject, IOleInPlaceObject (and IOleInPlaceActiveObject), IProvideClassInfo2, IspecifyPropertyPages, IDataObject, IOleControl, IconnectionPointContainer, and IConnectionPoint, with the exception of any operation that depends on asynchronous data that is not yet available, such as IDataObject::GetData for certain formats.
Complete | Everything is ready including other members of rest of IPersist*.

Obviously, this puts a some burden on a control, which needs to maintain one or more "readiness" flags that are checked on entry to various interface members so that the function returns E_PENDING if the flag says "not ready." The more distinct states the control chooses to support, the more complex this will become internally; however, the container doesn't need to differentiate these states: it simply handles E_PENDING by providing some default action of its own where appropriate, then trying the operation again later.

In other words, these states are not something that the container has to internally maintain—they are simply a guide to how a control implementation should be structured to work best in the Internet environment.

Many controls will never have occasion to differentiate between Loaded and Interactive because nearly all simple controls have data dependencies for becoming interactive. The differentiation between the states is made here because some controls with data paths may not be ready for Interactive until at least some data is received asynchronously. Nevertheless, a control should attempt to become interactive as soon as possible.

The OnReadyStateChange Event and ReadyState Property

How does a container know when any given control is ready to render itself, can be interactive with the user, or has completely retrieved all its necessary data for full operation? That is, how does a container know the "readiness" state of a control?

A simple scenario demonstrates the need for the container to have such knowledge. Imagine that you have a form on which there is a video control and a "Play" button that calls the Play method in the video control. The video control has a VideoPath property set to some URL. When this form is first opened, the video control will not have any data that it will play, so the container will want to disable the "Play" button immediately. When the video control is asked to load itself, it will load its embedded and begin an asynchronous transfer of its AVI file. When the transfer is complete, the container will want to enable the Play button.

More precisely, user code will pick up some event from the video control and use that event to enable the button (the container would not generally be hardcoded to do this). In addition, user code outside of a change event may need to know the current readiness state of a control.

For the purposes described here, readiness is defined as one of the following values, each of which corresponds to one of the states described in the previous section:

enum
    {
    READYSTATE_UNINITIALIZED=0, //Never used except as default initialization. state
    READYSTATE_LOADING=1,       //Control is currently loading its properties
    READYSTATE_LOADED=2,        //Control has been initialized via IPersist*::Load.
    READYSTATE_INTERACTIVE=3,   //Control is interactive but not all data is available.
    READYSTATE_COMPLETE=4       //Control has all its data.
    }

Again, the "loading" state is only used for controls that are initializing themselves asynchronously through IPeristMoniker::Load. Otherwise, controls begin with READYSTATE_LOADED.

A control makes its current state available through the standard ReadyState property (DISPID_READYSTATE, of type long). Until a control is initialized with IPersist*::Load, the value of this property is undefined (and unavailable, because the control is not ready at all). When a control has loaded its properties, the property must be at least READYSTATE_LOADED. In many cases, the control will not differentiate between loaded and interactive; in that case, the control is interactive immediately and this property reflects at least READYSTATE_INTERACTIVE. In addition, some controls do not differentiate between "loaded" and any other state (such as buttons that load all their properties and have no external data to retrieve); in that case, the property should always be READYSTATE_COMPLETE. A control need not support the ReadyState property at all; in that case, the container always assumes READYSTATE_COMPLETE.

When a control differentiates at least two of these states, it must notify the container of the state transition with the standard control event called OnReadyStateChange (DISPID_ONREADYSTATECHANGE), which has the following prototype:

[id(DISPID_ONREADYCHANGE)]
        void OnReadyStateChange(long lReadyState);

The state flag passed with this event is one of the READYSTATE_* flags shown above. The general meaning of these states was described in the last section, but to make use of the event, the programmer must understand—from the control's documentation—what methods and properties or other features of the control are available in any given state. In our example, the video control might not say it's "interactive" until it has at least some of its data, and its definition of "complete" might mean that it's ready to play. On the other hand, a graphical hyperlink control might describe itself as "interactive" as soon as it's loaded, because it can receive user input and then only send the "complete" state when it has actually retrieved all of its graphics.

Because this event is designed for use in user code, the programmer is responsible for understanding what each particular state means for whatever control might be sending it. In the video example, the programmer has read that the video's Play method is not available until the ready state is "complete"; in that case, the Visual Basic code enables the "Play" button on READYSTATE_COMPLETE only:

Private Sub video1.OnReadyStateChange(ReadyState as Long)

    if ReadyState=READYSTATE_COMPLETE
        buttonPlay.Enabled=TRUE

End Sub

Controls that do not differentiate between all the states can simply choose to send only the appropriate ones, such as READYSTATE_COMPLETE, if the control is completely ready as soon as it's loaded. Sending "complete" implies that the control has already passed through "loaded" and "interactive." Sending "interactive" implies that "loaded" has already passed but "complete" has not been reached. Again, because user code is what's generally interested in this event, the programmer is assumed to understand any particular control's behavior with this event. User code should not depend on all three states being sent explicitly.

Of course, any control that may not be ready to receive certain requests still has to protect itself in case a container makes some request that the control cannot yet fulfill. In that case the control returns E_PENDING from the interface member function.


Other Considerations

Component Categories for Describing Internet-Aware Objects

In all the various features defined in this document, there are various useful categories for describing what a object requires from its container as well as what features an object supports, which is useful in a container's browsing UI. The following tables describe the meaning for various categories as both "implemented" and "required" categories:
"Required" Categories | Description
CATID_PersistsToMoniker, CATID_PersistsToStreamInit, CATID_PersistsToStream, CATID_PersistsToStorage, CATID_PersistsToMemory, CATID_PersistsToFile, CATID_PersistsToPropertyBag | Each of these categories are mutually exclusive and are only used when an object supports only one persistence mechanism at all (hence, the mutual exclusion). Containers that do not support the persistence mechanism described by one of these categories should prevent themselves from creating an object of classes so marked.
CATID_RequiresDataPathHost | The object requires the ability to save data to one or more paths and requires container involvement, therefore requiring container support for IbindHost.
"Implemented" Categories | Description
CATID_PersistsToMoniker, CATID_PersistsToStreamInit, CATID_PersistsToStream, CATID_PersistsToStorage, CATID_PersistsToMemory, CATID_PersistsToFile, CATID_PersistsToPropertyBag | Object supports the corresponding IPersist*mmechanism for the category.
CATID_InternetAware | The object is "Internet-aware" meaning that the object needs no special behavior to work well in the Internet environment, or the object uses large data sets but is written to retrieve that data asynchronously. In addition, all "Internet-aware" objects are marked with OLEMISC_SETCLIENTSITEFIRST. This category is defined to aid authoring tools differentiate between objects that may or may not understand the special implications of the Internet.

The following table provides the exact CATIDs assigned to each category:
Category | CATID
CATID_RequiresDataPathHost | 0de86a50-2baa-11cf-a229-00aa003d7352
CATID_PersistsToMoniker | 0de86a51-2baa-11cf-a229-00aa003d7352
CATID_PersistsToStorage | 0de86a52-2baa-11cf-a229-00aa003d7352
CATID_PersistsToStreamInit | 0de86a53-2baa-11cf-a229-00aa003d7352
CATID_PersistsToStream | 0de86a54-2baa-11cf-a229-00aa003d7352
CATID_PersistsToMemory | 0de86a55-2baa-11cf-a229-00aa003d7352
CATID_PersistsToFile | 0de86a56-2baa-11cf-a229-00aa003d7352
CATID_PersistsToPropertyBag | 0de86a57-2baa-11cf-a229-00aa003d7352
CATID_InternetAware | 0de86a58-2baa-11cf-a229-00aa003d7352

For more information on categories, see the Component Categories document.


Summary

Added Standard Properties, Methods, Events, and Interfaces

The following table summarizes the added standard dispIDs described in this document:
Control Property | Value
DISPID_READYSTATE | (-525)
Control Methods | Value
none
Control Events | Value
DISPID_READYSTATECHANGE | (-609)

The following table summarizes the new interfaces and other GUID values described in this document, as well as those that are described here with the exception of category IDs described in a later section:
Symbol | Value
IID_IBindHost | FC4801A1-2BA9-11CF-A229-00AA003D7352
IID_IServiceProvider | 6D5140C1-7436-11CE-8034-00AA006009FA
IID_IObjectWithSite | FC4801A3-2BA9-11CF-A229-00AA003D7352
IID_IPersistMemory | BD1AE5E0-A6AE-11CE-BD37-504200C10000
IID_IPersistPropertyBag | 37D84F60-42CB-11CE-8135-00AA004BB851
IID_IPropertyBag | 55272A00-42CB-11CE-8135-00AA004BB851
IID_IPersistPropertyBag2 | 22F55881-280B-11d0-A8A9-00A0C90C2004
IID_IPropertyBag2 | 22F55882-280B-11d0-A8A9-00A0C90C2004
IID_IErrorLog | 3127CA40-446E-11CE-8135-00AA004BB851
GUID_PathProperty | 0002DE80-0000-0000-C000-000000000046
GUID_HasPathProperties | 0002DE81-0000-0000-C000-000000000046
SID_SBindHost | FC4801A1-2BA9-11CF-A229-00AA003D7352 (same as IID_IBindHost)

IPersistMoniker and its IID is documented in Asynchronous Monikers.

Requirements for Internet-Aware Controls and Objects

If a control does not use any data paths for BLOB data, working well within Internet-aware containers mostly means that a control supports as many IPersist* interfaces as is reasonable for it, giving the container and asynchronous monikers a great deal of flexibility in how they initialize the control in both persistent embedding and persistent linking cases.

Controls that only support one persistence interface must mark themselves as mandatory users of the appropriate category.

Controls with one or more data paths have these requirements:

  1. Must support either IOleObject or IObjectWithSite as a siting mechanism.
  2. Must mark data path properties in type information with the [bindable] attribute as well as the appropriate custom attribute using GUID_PathProperty.
  3. Must mark the coclass entry in the control's type information with the custom attribute using GUID_HasPathProperties.
  4. Must follow the rules of moniker creation and persistence, using the container's IBindHost as necessary and as IBindHost is available.
  5. Must understand how to bind with an asynchronous moniker using a container-provided bind context from IBindHost if available and how to receive data through IBindStatusCallback.
  6. Must prioritize initialization sequences and data retrieval to render and interact as soon as possible, returning E_PENDING from member functions as appropriate for the control's initialization state.
  7. Should supply a ReadyState property, if applicable, and fire OnReadyStateChange events as needed.
  8. Should support IPersistPropertyBag for supporting HTML PARAM attributes.

This document also recommends that controls be OLE Accessibility-aware for the benefit of users requiring accessibility services.

Requirements for Internet-Aware Containers/Clients/Controllers

Whereas much of a control's functionality is optional (with the exceptions of necessary support for data paths), a container, on the other hand, generally wants to support as many features as possible. As guidelines, a container that wishes to host Internet-aware controls at run time should meet these requirements:

  1. Must expect and handle E_PENDING, especially from IViewObject2::Draw.
  2. Must provide a site to a control through either IOleObject or IObjectWithSite mechanisms.
  3. Must supply initialized bind contexts and name parsing through IBindHost.
  4. Must support at least IPersistStreamInit as a control persistence mechanism.
  5. Must support the HTML Extensions for COM Objects if the client is HTML-oriented.
  6. Should manage relative creation/initialization priorities for each control in the document.
  7. Should supply browsers for common data types required by data paths (authoring tools).
  8. Should support as many IPersist* interfaces as possible to allow maximum control flexibility.

This document also recommends that controls be OLE Accessibility-aware for the benefit of users requiring accessibility services.


Standard Internet-Aware Objects

As a benefit to control developers, Microsoft will supply Internet-Aware picture, sound, and video objects that will make dealing with these data formats in the Internet environment a non-issue for many controls. The existing picture object, for example, makes a control's manipulation of a bitmap, metafile, or icon nearly effortless. The additional objects described here aim to do the same for their respective data types, where all standard objects will support the ability to retrieve large data BLOBs in an asynchronous manner.

A function called OleLoadPicturePath became available in the final version of Microsoft Internet Explorer 3.0. OLELoadPicturePath takes a moniker and a bind context (which the control retrieves from IBindHost) and will take care of the details of downloading the picture. In return, OLELoadPicturePath provides an IPicture interface pointer that is identical to the standard picture object that already exists. The control can simply call IPicture::Render to draw the image.

The exact specifications for other picture object enhancements and other standard objects are not available at this time. The design may be done along the lines of a "data type" architecture so that new objects supporting other data types can be added in the same manner. In addition, the architecture may support plug-in "player" objects that extend the usable formats for each data type object. For example, a standard picture object would probably support formats like BMP, DIB, Metafile, Icon, GIF, and JPEG natively. Individual "players" could be then plugged into this picture object for supporting other formats like TIFF, PCX, or other proprietary formats. As each player would be a COM object in itself, controls could take advantage of any "code download" technology for these players.


Interface Reference

The following interfaces are relevant to components hosted by Internet Explorer. This section describes these interfaces.

IBindHost

IServiceProvider

IObjectWithSite

IPersistMemory

IPersistPropertyBag

IPropertyBag

IPersistPropertyBag2

IPropertyBag2

IErrorLog

IPersistHistory

For information on the following interfaces, see the Asynchronous and URL Monikers Reference:

IBinding

IBindStatusCallback

IPersistMoniker


IBindHost

This interface is implemented as a service available from a site object through which a container supplies services and information that objects require when performing an asynchronous data transfer, specifically name-to-moniker parsing and a container-initialized bind context.

IDL:

[
uuid(fc4801a1-2ba9-11cf-a229-00aa003d7352)
    , object,pointer_default(unique)
]

interface IBindHost : IUnknown
    {
    HRESULT CreateMoniker([in] LPCOLESTR pszName, [in] IBindCtx *pBC
        , [out] IMoniker** ppmk, [in] DWORD dwReserved);

    HRESULT MonikerBindToStorage([in] IMoniker *pMk, [in] IBindCtx *pBC
        , [in] IBindStatusCallback *pBSC, [in] REFIID riid
        , [out, iid_is(riid)] void **ppvObj);

    HRESULT MonikerBindToObject([in] IMoniker *pMk, [in] IBindCtx *pBC
        , [in] IBindStatusCallback *pBSC, [in] REFIID riid
        , [out, iid_is(riid)] void **ppvObj);
    }

IBindHost::CreateMoniker

IBindHost::MonikerBindToStorage

IBindHost::MonikerBindToObject


IBindHost::CreateMoniker

HRESULT CreateMoniker(
    LPCOLESTR pszName,
    IBindCtx *pBC,
    IMoniker **ppmk,
    DWORD dwReserved);

Provides the caller with a means to turn some sort of text name into a moniker so that the caller does not have to interpret the name in any way itself. In many cases, the implementation of CreateMoniker will simply call MkParseDisplayNameEx, but this method gives the implementor of IBindHost a chance to catch host-specific strings that MkParseDisplayNameEx would not otherwise recognize.

  • Returns one of the following values:
    S_OK | The moniker was successfully obtained and the caller is responsible for the interface pointer.
    E_OUTOFMEMORY | There is insufficient memory to create the moniker.
    E_UNEXPECTED | An unknown error occurred.
    MK_E_SYNTAX | The bind host was unable to parse the string into a moniker because the information in the name was unrecognizable.
pszName
[in] Address of the string containing the name to parse.
pBC
[in] Optional bind context to be used when creating the moniker. This parameter is currently ignored, but may be used in the future for passing additional information.
ppmk
[out] Address of the caller's IMoniker * variable in which to store the moniker created from pszName. The caller is responsible for calling IMoniker::Release when the moniker is no longer needed.
dwReserved
[in] Reserved for future use, must be zero.

E_NOTIMPL is disallowed—a bind host is responsible for providing moniker parsing services.


IBindHost::MonikerBindToObject

HRESULT MonikerBindToObject(
    IMoniker *pMk,
    IBindCtx *pBC,
    IBindStatusCallback *pBSC,
    REFIID riid,
    void **ppvObj);

Should be the single mechanism controls use when trying to bind to the object addressed by a moniker.

  • Returns one of the following values:
    S_OK | The bind operation completed synchronously and successfully. The result of the bind operation is available in ppvObj.
    MK_S_ASYNCHRONOUS | The bind operation will complete asynchronously. Behavior matches that of IMoniker::BindToObject.
    E_OUTOFMEMORY | There is insufficient memory to create the moniker.
    E_UNEXPECTED | An unknown error occurred.
pMk
[in] Moniker to bind to.
pBC
[in] Optional bind context to be used when binding the moniker. This parameter can be used by the control for passing in additional bind options, such as a format enumerator (IEnumFormatETC).
pBSC
[in] Callback interface whereby the control receives asynchronous callbacks pertaining to the bind operation. The control must provide its callback interface separately, not registered on the pBC parameter.
riid
[in] Identifier of the desired storage interface. Serves the same purpose as the analogous parameter in IMoniker::BindToObject.
ppvObj
[out] Result of the bind operation (if it is synchronous). This parameter serves the same purpose as the analogous parameter in IMoniker::BindToObject.

This method behaves exactly the same as IMoniker::BindToObject, except that it provides the control container (implementor of IBindHost) enough authority over the bind operation so that the control container can take charge of setting bind options and priority, while delegating all results and callbacks for the bind operation back to the control.


IBindHost::MonikerBindToStorage

HRESULT MonikerBindToStorage(
    IMoniker *pMk,
    IBindCtx *pBC,
    IBindStatusCallback *pBSC,
    REFIID riid,
    void **ppvObj);

Should be the single mechanism controls use when trying to bind to the data addressed by a moniker.

  • Returns one of the following values:
    S_OK | The bind operation completed synchronously and successfully. The result of the bind operation is available in ppvObj.
    MK_S_ASYNCHRONOUS | The bind operation will complete asynchronously. Behavior matches that of IMoniker::BindToStorage.
    E_OUTOFMEMORY | There is insufficient memory to create the moniker.
    E_UNEXPECTED | An unknown error occurred.
pMk
[in] Moniker to bind to.
pBC
[in] Optional bind context to be used when binding the moniker. This parameter can be used by the control for passing in additional bind options, such as a format enumerator (IEnumFormatETC).
pBSC
[in] Callback interface whereby the control receives asynchronous callbacks pertaining to the bind operation. The control must provide its callback interface separately, not registered on the pBC parameter.
riid
[in] Identifier of the desired storage interface. Serves the same purpose as the analogous parameter in IMoniker::BindToStorage.
ppvObj
[out] Result of the bind operation (if it is synchronous). This parameter serves the same purpose as the analogous parameter in IMoniker::BindToStorage.

This method behaves exactly the same as IMoniker::BindToStorage, except that it provides the control container (implementor of IBindHost) enough authority over the bind operation so that the control container can take charge of setting bind options and priority, while delegating all results and callbacks for the bind operation back to the control.


The IServiceProvider Interface

IServiceProvider is a generic access mechanism to locate a GUID-identified "service" that is provided through a control or any other objects that it can communicate with. For example, an embedded object (such as an OLE control) normally only communicates with its associated "client site" object in the container through the IOleClientSite interface supplied through IOleObject::SetClientSite. Such an embedded object is required to ask the client site for some other service that the container supports when that service may not necessarily be implemented in the site itself.

In this regard, the site must provide a means through which the control managed by that site can access the service when necessary. A specific example of this necessity can be found in the function IOleInPlaceSite::GetWindowContext, through which an in-place object or control can access interface pointers for the document object that contains the site and the frame object that contains the document. Because these interface pointers exists on separate objects, the control cannot call the site's QueryInterface to obtain those pointers.

The generic architecture for achieving the same ends, without requiring ad hoc solutions where the need arises, is the interface IServiceProvider whose existence says "somewhere in the code of which I'm part exists some set of services that can be accessed through this interface."

The interface itself has only one member, QueryService, through which a caller specifies the service ID (SID, a GUID), the IID of the interface desired in return, and the address of the caller's interface pointer variable.

IDL:

[
uuid(6d5140c1-7436-11ce-8034-00aa006009fa)
    , object,pointer_default(unique)
]
interface IServiceProvider : IUnknown
    {
    HRESULT QueryService([in] REFGUID guidService
, [in] REFIID riid, [out, iid_is(riid)] void **ppv);
    }

IServiceProvider::QueryService


IServiceProvider::QueryService

HRESULT QueryService([in] REFGUID guidService, [in] REFIID riid, [out, iid_is(riid)] void **ppv);

QueryService is the factory method for any services exposed through an implementation of IServiceProvider. It creates or accesses the implementation the service identified with guidService, returning in *ppv the address of the interface specified by riid.

  • Returns:
    S_OK | The service was successfully created or retrieved. The caller is responsible for calling ((IUnknown *)*ppv)->Release();.
    E_OUTOFMEMORY | There is insufficient memory to create the service.
    E_UNEXPECTED | An unknown error occurred.
    E_NOINTERFACE | The service exists, but the interface requested does not exist on that service.
    SVC_E_UNKNOWNSERVICE | The service identified with guidService is not recognized.
guidService
[in] The unique identifier of the service (a SID)
riid
[in] The unique identifier of the interface the caller wishes to receive for the service.
Ppv
[out] The address of the caller-allocated variable to receive the interface pointer of the service on successful return from this function. The caller becomes responsible for calling Release through this interface pointer when the service is no longer needed.

Because there is only one member function in this interface, E_NOTIMPL is not a valid return code—if the function is not implemented, the interface has no reason to exist.


IObjectWithSite

Often, an object needs to communicate directly with a "container site" that is managing the object itself. Outside of IOleObject::SetClientSite, there is no generic means through which an object becomes aware of its site. IObjectWithSite provides simple objects with a lightweight means (lighter than IOleObject) with a siting mechanism. This interface should only be used when IOleObject is not already in use.

Through IObjectWithSite, a container can pass the IUnknown pointer of its site to the object through SetSite. Callers can also retrieve the latest site passed to SetSite through GetSite. This latter function is included as a hooking mechanism, allowing a third party to intercept calls from the object to the site.

IDL:

[
uuid(fc4801a3-2ba9-11cf-a229-00aa003d7352)
    , object,pointer_default(unique)
]
interface IObjectWithSite : IUnknown
    {
    HRESULT SetSite([in] IUnknown *pUnkSite);
    HRESULT GetSite([in] REFIID riid, [out, iid_is(riid)] void **ppvSite);
    }

IObjectWithSite::GetSite

IObjectWithSite::SetSite


IObjectWithSite::GetSite

HRESULT GetSite([in] REFIID riid, [out, iid_is(riid)] void **ppvSite);

Retrieves the last site set with IObjectWithSite::SetSite. If there's no known site, the object returns a failure code.

  • Returns:
    S_OK | The site was returned successfully and the caller is responsible for calling ((IUnknown *)(*ppvSite))->Release() when the site is no longer needed.
    E_FAIL | There is no site; in that case, *ppvSite contains NULL on return.
    E_NOINTERFACE | There is a site, but it does not support the interface requested by riid.
riid
[in] The IID of the interface pointer that should be returned in ppvSite.
ppvSite
[out] The address of the caller's void* variable in which the object stores the interface pointer of the site last seen in IObjectWithSite::SetSite. The specific interface returned depends in the riid argument—in essence, the two arguments act identically to those in QueryInterface. If the appropriate interface pointer is available, the object must call AddRef on that pointer before returning successfully. If no site is available, or the requested interface is not supported, the object sets this argument to NULL and returns a failure code.

E_NOTIMPL is disallowed—any object implementing this interface must be able to return the last site seen in SetSite.


IObjectWithSite::SetSite

HRESULT SetSite(
    IUnknown *pUnkSite);

Provides the site's IUnknown pointer to the object. The object should hold onto this pointer, calling AddRef in doing so. If the object already has a site, it should first call pUnkSite->AddRef to secure the new site, call IUnknown::Release on the existing site, then save pUnkSite.

  • Returns:
    S_OK Returned in all circumstances
pUnkSite
[in] Interface pointer of the site managing this object. If NULL, the object should call IUnknown::Release on any existing site at which point the object no longer knows its site.

E_NOTIMPL is disallowed—without implementation of SetSite, the IObjectWithSite interface is unnecessary.


The IPersistMemory Interface

IPersistMemory operates exactly as IPersistStreamInit, except that it allows the caller to provide a fixed-size memory block (identified with a void *) as opposed to IPersistStreamInit, which involves an arbitrarily expandable IStream.

IDL:

[
uuid(BD1AE5E0-A6AE-11CE-BD37-504200C10000)
    , object, pointer_default(unique)
]
interface IPersistMemory : IPersist
    {
    HRESULT IsDirty(void);
    HRESULT Load([in, size_is(cbSize)] void *pvMem, [in] ULONG cbSize);
    HRESULT Save([in,out, size_is(cbSize)] void *pvMem, [in] BOOL fClearDirty, [in] ULONG cbSize);
    HRESULT GetSizeMax([out] ULONG* pcbSize);
    HRESULT InitNew(void);
    };

The cbSize argument to Load and Save indicate the amount of memory accessible through pvMem.

The IsDirty, GetSizeMax, and InitNew members are semantically and syntactically identical to those in IPersistStreamInit. Only Load and Save differ as described below.

IPersistMemory::Load

IPersistMemory::Save


IPersistMemory::Load

HRESULT IPersistMemory::Load([in] void *pvMem,
        [in] ULONG cbSize);

Instructs the object to load its persistent data from the memory pointed to by pvMem where cbSize indicates the amount of memory at pvMem. The object must not read past the address (BYTE*)((BYTE *)pvMem+cbSize).

  • Returns:
    S_OK | The object successfully loaded its data.
    E_UNEXPECTED | This member was called after the object was already initialized with IPersistMemory::Load. Only one initialization is allowed per instance.
    E_POINTER | The pointer in pvMem is NULL.
pvMem
[in] The address of the memory from which the object can read up to cbSize bytes of its data.
cbSize
[in] The amount of memory available at pvMem form which the object can read its data.

Any object that implements IPersistMemory has some information to load persistently; therefore, E_NOTIMPL is not a valid return code.


IPersistMemory::Save

HRESULT IPersistMemory::Save([in] void *pvMem,
        [in] BOOL fClearDirty,
        [in] ULONG cbSize);

Instructs the object to save its persistent data to the memory pointed to by pvMem where cbSize indicates the amount of memory available at pvMem. The object must not write past the address (BYTE*)((BYTE *)pvMem+cbSize). The fClearDirty flag determines whether the object is to clear its dirty state after the save is complete.

  • Returns:
    S_OK | The object successfully initialized itself.
    E_UNEXPECTED | This member was called before the object was initialized with IPersistMemory::InitNew or IPersistMemory::Load.
    E_INVALIDARG | The number of bytes indicated by cbSize is too small to allow the object to save itself completely.
    E_POINTER | The pointer in pvMem is NULL.
pvMem
[in] The address of the memory in which the object should save up to cbSize bytes of its data.
fClearDirty
[in] A flag indicating whether the object should clear its dirty state on return from Save or leave that state as is.
cbSize
[in] The amount of memory available at pvMem to which the object can write its data.

Any object that implements IPersistMemory has some information to save persistently; therefore, E_NOTIMPL is not a valid return code.

The caller should ideally allocate as many bytes as the object returns from IPersistMemory::GetSizeMax.


The IPersistPropertyBag Interface

The IPersistPropertyBag interface works in conjunction with IPropertyBag (see below) and IErrorLog (see below) to define an individual property-based persistence mechanism. Whereas a mechanism like IPersistStream gives an object an IStream in which to store its binary data, IPersistPropertyBag provides an object with an IPropertyBag interface through which it can save and load individual properties. The implementor of IPropertyBag can then save those properties in whatever way it chooses, such as name/value pairs in a text file. Errors encountered in the process (on either side) are recorded in an "error log" through IErrorLog. This error reporting mechanism works on a per-property basis instead of an "all properties as a whole" basis through just the return value of IPersist*::Load or IPersist*::Save.

IDL:

[
uuid(37D84F60-42CB-11CE-8135-00AA004BB851)
    , object, pointer_default(unique)
]
interface IPersistPropertyBag : IPersist
    {
    HRESULT InitNew(void);
    HRESULT Load([in] IPropertyBag *pPropBag, [in] IErrorLog *pErrorLog);
    HRESULT Save([in] IPropertyBag *pPropBag, [in] BOOL fClearDirty
        , [in] BOOL fSaveAllProperties);
    };

The basic mechanism is that a container tells the object to save or load its properties through IPersistPropertyBag. For each property, the object calls the container's IPropertyBag interface passed to the IPersistPropertyBag members. IPropertyBag::Write obviously saves a property in whatever place the container wants to put it, and IPropertyBag::Read retrieves a property.

This protocol is essentially a means of sequentially communicating individual property values from the object to the container, which is useful for doing save-as-text operations and the like. The object gives the container the choice of the format in which each property is saved, while retaining itself the decision as to which properties are saved or loaded.

IPersistPropertyBag::InitNew

IPersistPropertyBag::Load

IPersistPropertyBag::Save


IPersistPropertyBag::InitNew

HRESULT InitNew(void);

Informs the object that it is being initialized as a newly created object.

  • Returns:
    S_OK | The object successfully initialized itself. This should be returned even if the object doesn't do anything in the function.
    E_UNEXPECTED | This member was called after IPersistPropertyBag::Load or IPersistPropertyBag::Save.
none

E_NOTIMPL should not be returned—use S_OK when the object has nothing to do in the function.


IPersistPropertyBag::Load

HRESULT Load([in] IPropertyBag *pPropBag,
        [in] IErrorLog *pErrorLog);

Instructs the object to initialize itself using the properties available in the property bag, notifying the provided error log object when errors occur. All property loading must take place within this function call because the object cannot hold the IPropertyBag pointer.

  • Returns:
    S_OK | The object successfully initialized itself.
    E_UNEXPECTED | This member was called after IPersistPropertyBag::InitNew has already been called. They two initialization methods are mutually exclusive.
    E_POINTER | The address in pPropBag is not valid (such as NULL,) and, therefore, the object cannot initialize itself.
    E_FAIL | The object was unable to retrieve a critical property that is necessary for the object's successful operation. The object was, therefore, unable to initialize itself completely.
pPropBag
[in] Address of the caller's property bag through which the object can read properties. Cannot be NULL.
pErrorLog
[in] Address of the caller's error log in which the object stores any errors that occur during initialization. Can be NULL; in that case, the caller is not interested in errors.

E_NOTIMPL is not a valid return code because any object implementing this interface must support the entire functionality of the interface.


IPersistPropertyBag::Save

HRESULT Save(
    IPropertyBag *pPropBag,
    BOOL fClearDirty,
    BOOL fSaveAllProperties);

Instructs the object to save its properties to the given property bag, optionally clearing the object's "dirty" flag. The caller can request that the object save all properties or that the object save only those that are known to have changed.

  • Returns one of the following values:
    S_OK | The object successfully saved the requested properties itself.
    E_FAIL | There was a problem saving one of the properties. The object can choose to fail only if a necessary property could not be saved, meaning that the object can assume default property values if a given property is not seen through Load at some later time.
    E_POINTER | The address in pPropBag is not valid (such as NULL) and therefore the object cannot initialize itself.
pPropBag
[in] Address of the caller's property bag through which the object can write properties. Cannot be NULL.
fClearDirty
[in] Flag indicating whether the object should clear its dirty flag when saving is complete. TRUE means clear the flag, FALSE means leave the flag unaffected. FALSE is used when the caller wants to do a "Save Copy As" operation.
fSaveAllProperties
[in] Flag indicating whether the object should save all its properties (TRUE) or only those that have changed from the default value (FALSE).

E_NOTIMPL is not a valid return code because any object implementing this interface must support the entire functionality of the interface.


The IPropertyBag Interface

When a client wants to have exact control over how individually named properties of an object are saved, it would attempt to use an object's IPersistPropertyBag interface as a persistence mechanism. In that case, the client supplies a "property bag" to the object in the form of an IPropertyBag interface.

IDL:

[
uuid(55272A00-42CB-11CE-8135-00AA004BB851)
    , object, pointer_default(unique)
]
interface IPropertyBag : IUnknown
    {
    HRESULT Read([in] LPCOLESTR pszPropName, [in,out] VARIANT *pVar
        , [in,out] IErrorLog *pErrorLog);
    HRESULT Write([in] LPCOLESTR pszPropName, [in] VARIANT *pVar);
    };

When the object wishes to read a property in IPersistPropertyBag::Load, it will call IPropertyBag::Read. When the object is saving properties in IPersistPropertyBag::Save, it will call IPropertyBag::Write. Each property is described with a name in pszPropName whose value is exchanged in a VARIANT. This information allows a client to save the property values as text, for instance, which is the primary reason why a client might choose to support IPersistPropertyBag.

The client records errors that occur during Read into the supplied "error log."

IPropertyBag::Read

IPropertyBag::Write


IPropertyBag::Read

HRESULT Read([in] LPCOLESTR pszPropName,
        [in,out] VARIANT *pVar,
        [in,out] IErrorLog *pErrorLog);

Asks the property bag to read the property named with pszPropName into the caller-initialized VARIANT in pVar. Errors that occur are logged in the error log pointed to by pErrorLog. When pVar->vt specifies another object pointer (VT_UNKNOWN), the property bag is responsible for creating and initializing the object described by pszPropName. See IPropertyBag::Write for more information.

  • Returns:
    S_OK | The property was read successfully. The caller becomes responsible for any allocations that are contained in the VARIANT in pVar.
    E_POINTER | The address in pszPropName is not valid (such as NULL).
    E_INVALIDARG | The property named with pszPropName does not exist in the property bag.
    E_FAIL | The property bag was unable to read the specified property, such as if the caller specified a data type to which the property bag could not coerce the known value. If the caller supplied an error log, a more descriptive error was sent there.
pszPropName
[in] The name of the property to read. Cannot be NULL.
pVar
[in, out] The address of the caller-initialized VARIANT that is to receive the property value on output. The function must set both type and value fields in the VARIANT before returning. If the caller initialized the pVar->vt field on entry, the property bag should attempt to coerce the value it knows into this type. If the caller sets pVar->vt to VT_EMPTY, the property bag can use whatever type is convenient.
pErrorLog
[in] Address of the caller's error log in which the property bag stores any errors that occur during reads. Can be NULL; in that case, the caller is not interested in errors.

E_NOTIMPL is not a valid return code because any object implementing this interface must support the entire functionality of the interface.


IPropertyBag::Write

HRESULT Write([in] LPCOLESTR pszPropName,
        [in] VARIANT *pVar);

Asks the property bag to save the property named with pszPropName using the type and value in the caller-initialized VARIANT in pVar. In some cases, the caller may be asking the property bag to save another object—that is, when pVar->vt is VT_UNKNOWN. In such cases, the property bag queries this object pointer for some persistence interface, like IPersistStream or even IPersistPropertyBag again, and has that object save its data as well. Usually this results in the property bag having some byte array for this object which can be saved as encoded text (hex string, MIME, and so on). When the property bag is later used to reinitialize a control, the client that owns the property bag must recreate the object when the caller asks for it, initializing that object with the previously saved bits.

This allows very efficient persistence operations for large BLOB properties like a picture, where the owner of the property bag itself directly asks the picture object (managed as a property in the control being saved) to save into a specific location. This avoids potential extra copy operations that would be involved with other property-based persistence mechanisms.

  • Returns:
    S_OK | The property bag successfully saved the requested property.
    E_FAIL | There was a problem writing the property. It is possible that the property bag does not understand how to save a particular VARIANT type.
    E_POINTER | The address in pszPropName or pVar is not valid (such as NULL). The caller must supply both.
pszPropName
[in] The name of the property to write. Cannot be NULL.
pVar
[in] The address of the caller-initialized VARIANT that holds the property value to save. The caller owns this VARIANT and is responsible for all allocations therein. That is, the property bag itself does not attempt to free data in the VARIANT.

E_NOTIMPL is not a valid return code because any object implementing this interface must support the entire functionality of the interface.


The IPersistPropertyBag2 Interface

The IPersistPropertyBag2 interface works in conjunction with the IPropertyBag2 and IErrorLog interfaces to define an individual property-based persistence mechanism. This interface is similar to IPersistPropertyBag, but allows the object to support property bags that implement IPropertyBag2.

Whereas a mechanism like IPersistStream gives an object an IStream in which to store its binary data, IPersistPropertyBag2 provides an object with an IPropertyBag2 interface through which it can save and load individual properties. The implementor of IPropertyBag2 can then save those properties in whatever way it chooses, such as name/value pairs in a text file. Errors encountered in the process (on either side) are recorded in an "error log" through IErrorLog. This error reporting mechanism work on a per-property basis instead of an "all properties as a whole" basis through just the return value of IPersist*::Load or IPersist*::Save.

When requesting a persistent property bag interface, a property bag that supports IPropertyBag2 will attempt to obtain the object's IPersistPropertyBag2 interface before attempting to obtain the object's IPersistPropertyBag interface. This allows objects that support either interface to communicate with the property bag.

IDL:

[object, uuid(22F55881-280B-11d0-A8A9-00A0C90C2004), pointer_default(unique)]
interface IPersistPropertyBag2 : IPersist
    {
    typedef [unique] IPersistPropertyBag2 *LPPERSISTPROPERTYBAG2;

    HRESULT InitNew();

    HRESULT Load(
        [in]IPropertyBag2 * pPropBag,
        [in]IErrorLog * pErrLog);

    HRESULT Save(
        [in]IPropertyBag2 * pPropBag,
        [in]BOOL fClearDirty,
        [in]BOOL fSaveAllProperties);

    HRESULT IsDirty();
    }

The basic mechanism is that a container tells the object to save or load its properties through IPersistPropertyBag2. For each property, the object calls the container's IPropertyBag2 interface passed to the IPersistPropertyBag members. IPropertyBag2::Write saves a property in whatever place the container wants to put it, and IPropertyBag2::Read retrieves a property.

This protocol is essentially a means of sequentially communicating individual property values from the object to the container, which is useful for doing save-as-text operations and the like. The object gives the container the choice of the format in which each property is saved, while retaining itself the decision as to which properties are saved or loaded.

IPersistPropertyBag2::InitNew

IPersistPropertyBag2::IsDirty

IPersistPropertyBag2::Load

IPersistPropertyBag2::Save


IPersistPropertyBag2::InitNew

HRESULT InitNew(void);

Informs the object that it is being initialized as a newly created object.

  • Returns one of the following values:
    S_OK | The object successfully initialized itself. This should be returned even if the object doesn't do anything in the function.
    E_UNEXPECTED | This member was called after IPersistPropertyBag2::Load or IPersistPropertyBag2::Save.

E_NOTIMPL should not be returned. Return S_OK even if the object does not perform any function in this method.


IPersistPropertyBag2::IsDirty

HRESULT IsDirty(void);

Indicates if one or more properties have changed since the last initialization or save operation.

  • Returns:
    S_OK | One or more properties has changed.
    S_FALSE | None of the properties has changed.

E_NOTIMPL is not a valid return code because any object implementing this interface must support the entire functionality of the interface.


IPersistPropertyBag2::Load

HRESULT Load(
    [in] IPropertyBag2 *pPropBag,
    [in] IErrorLog *pErrorLog
);

Instructs the object to initialize itself using the properties available in the property bag, notifying the provided error log object when errors occur. All property storage must take place within this function call as the object cannot keep the IPropertyBag2 interface pointer passed in pPropBag.

  • Returns one of the following values:
    S_OK | The object successfully initialized itself.
    E_UNEXPECTED | This member was called after IPersistPropertyBag2::InitNew has already been called. The two initialization methods are mutually exclusive.
    E_POINTER | The address in pPropBag is not valid (such as NULL) and therefore the object cannot initialize itself.
    E_FAIL | The object was unable to retrieve a critical property that is necessary for the object's successful operation. The object was, therefore, unable to initialize itself completely.
pPropBag
[in] Address of the caller's IPropertyBag2 interface through which the object can read properties. This argument cannot be NULL.
pErrorLog
[in] Address of the caller's IErrorLog interface in which the object stores any errors that occur during initialization. This argument can be NULL; in that case, the caller is not interested in errors.

E_NOTIMPL is not a valid return code because any object implementing this interface must support the entire functionality of the interface.


IPersistPropertyBag2::Save

HRESULT Save(
    IPropertyBag *pPropBag,
    BOOL fClearDirty,
    BOOL fSaveAllProperties
);

Instructs the object to save its properties to the given property bag, optionally clearing the object's "dirty" flag. The caller can request that the object save all properties or that the object save only those known to have changed.

  • Returns one of the following values:
    S_OK | The object successfully saved the requested properties itself.
    E_FAIL | One or more essential properties could not be saved. The object can choose to succeed (return S_OK) if all essential properties could be saved but one or more non-essential properties could not be saved.
    E_POINTER | The address in pPropBag is not valid (such as NULL) and, therefore, the object cannot initialize itself.
pPropBag
[in] Address of the caller's IPropertyBag2 interface through which the object can write properties. This argument cannot be NULL.
fClearDirty
[in] Flag indicating whether the object should clear its dirty flag when saving is complete. If this argument is nonzero, the dirty flag should be cleared. If this argument is zero, the dirty flag should not be changed. Zero is used when the caller wants to do a "Save Copy As" operation.
fSaveAllProperties
[in] Flag indicating whether the object should save all its properties or only those that have changed from their default values. If this argument is nonzero, all properties should be saved. If this argument is zero, only those properties that have changed from their default values should be saved.

E_NOTIMPL is not a valid return code because any object implementing this interface must support the entire functionality of the interface.


The IPropertyBag2 Interface

When a client wants to have exact control over how individually named properties of an object are saved, it will attempt to use an object's IPersistPropertyBag2 interface as a persistence mechanism. In that case, the client supplies a "property bag" to the object in the form of an IPropertyBag2 interface.

IPropertyBag2 is an enhancement of the IPropertyBag interface. IPropertyBag2 allows the object to obtain type information for each property through the use of the CountProperties and GetPropertyInfo methods. A property bag that implements IPropertyBag2 must also support IPropertyBag so that objects that only support IPropertyBag can access their properties. Likewise, an object that supports IPropertyBag2 must also support IPropertyBag so that it can communicate with property bags that only support IPropertyBag.

IDL:

[object, uuid(22F55882-280B-11d0-A8A9-00A0C90C2004), pointer_default(unique)]
interface IPropertyBag2 : IUnknown
{
    typedef [unique]IPropertyBag2 *LPPROPERTYBAG2;

    HRESULT Read(
        [in]	ULONG cProperties,
        [in]	PROPBAG2 * pPropBag,
        [in]  	IErrorLog * pErrLog,
        [out] 	VARIANT * pvarValue,
        [out]   HRESULT * phrError);

    HRESULT Write(
        [in]	ULONG cProperties,
        [in] 	PROPBAG2 * pPropBag,
        [in] 	VARIANT * pvarValue);

    HRESULT CountProperties(
        [out] 	ULONG * pcProperties);

    HRESULT GetPropertyInfo(
        [in]  	ULONG iProperty,
        [in]  	ULONG cProperties,
        [out] 	PROPBAG2 * pPropBag,
        [out] 	ULONG * pcProperties);

    HRESULT LoadObject(
        [in]	LPCOLESTR pstrName,
        [in]	DWORD dwHint,
        [in]	IUnknown * pUnkObject,
        [in]	IErrorLog * pErrLog);
}

When the object wants to read a property in IPersistPropertyBag2::Load, it will call IPropertyBag2::Read. When the object is saving properties in IPersistPropertyBag2::Save, it will call IPropertyBag2::Write.

The client records errors that occur during Read with the supplied IErrorLog interface.

IPropertyBag2::CountProperties

IPropertyBag2::GetPropertyInfo

IPropertyBag2::LoadObject

IPropertyBag2::Read

IPropertyBag2::Write

PROPBAG2

PROPBAG2_TYPE


IPropertyBag2::CountProperties

 HRESULT CountProperties(
    [out] ULONG *pcProperties
);

Retrieves the number of properties in the property bag.

  • Returns one of the following values:
    S_OK | The method was successful.
    E_POINTER | pcProperties is not valid.
pcProperties
[out] Address of a ULONG that receives the number of properties in the property bag.


IPropertyBag2::GetPropertyInfo

HRESULT GetPropertyInfo(
    [in] ULONG iProperty,
    [in] ULONG cProperties,
    [out] PROPBAG2 * pPropBag,
    [out] ULONG * pcProperties
);

Retrieves information for properties in a property bag without actually retrieving the properties themselves.

  • Returns one of the following values:
    S_OK | Information was successfully retrieved for all of the requested properties.
    E_INVALIDARG | iProperty is greater than the number of properties in the property bag.
    E_OUTOFMEMORY | A memory allocation error occurred.
iProperty
[in] Zero-based index of the first property that information is being requested for. This argument must be less than the number of properties retrieved by IPropertyBag2::CountProperties.
cProperties
[in] Number of properties to retrieve information for. This argument specifies the number of array elements in pPropBag.
pPropBag
[out] Address of an array of PROPBAG2 structures that receive the information for the properties. There must be at least cProperties elements in this array. This argument cannot be NULL.
pcProperties
[out] Address of a ULONG that receives the number of properties that information was retrieved for. This argument cannot be NULL.


IPropertyBag2::LoadObject

HRESULT LoadObject(
    [in] LPCOLESTR pstrName,
    [in] DWORD dwHint,
    [in] IUnknown * pUnkObject,
    [in] IErrorLog * pErrLog);

Causes the property bag to instruct a previously created and initialized property object to read its persistent properties.

  • Returns one of the following values or any of the IPersist*::Read method return values:
    S_OK | The object successfully retrieved its properties.
    E_INVALIDARG | One or more of the arguments is not valid.
pstrName
[in] Name of the property object.
dwHint
[in] Hint value that was retrieved using GetPropertyInfo. This argument is optional and must be zero if the hint is not known or used.
pUnkObject
[in] Address of the object's IUnknown interface. This argument cannot be NULL.
pErrLog
[in] Address of an IErrorLog interface in which the property bag stores any errors that occur during the load. This argument can be NULL; in that case, the caller is not interested in logging errors.

LoadObject allows the calling application to participate in the creation and initialization of a property object. When using the IPropertyBag interface, it is possible to load a property object using the IPropertyBag::Read method with VT_UNKNOWN. The IPropertyBag method does not, however, allow the property object to be initialized by the calling application before the property object reads its own persistent data. LoadObject allows the property object to be created and initialized by the calling application instead of having the property bag create and initialize it.

To use LoadObject, the property object's name and CLSID are retrieved using GetPropertyInfo. The property object is then created and initialized. LoadObject is then used in lieu of IPropertyBag2::Read to cause the property object to read its persistent data.


IPropertyBag2::Read

HRESULT Read(
    [in] ULONG cProperties,
    [in] PROPBAG2 * pPropBag,
    [in] IErrorLog * pErrLog,
    [out] VARIANT * pvarValue,
    [out] HRESULT * phrError
);

Causes one or more properties to be read from the property bag.

  • Returns one of the following values:
    S_OK | All of the properties were successfully read.
    E_FAIL | One or more of the properties was not successfully read. It is possible that one or more properties were successfully read. The array at phrError will contain a success or error code for each property. If the caller supplied an IErrorLog for pErrLog, a more descriptive error will be added.
cProperties
[in] Number of properties to read. This argument specifies the number of elements in the arrays at pPropBag, pvarValue and phrError.
pPropBag
[in] Address of an array of PROPBAG2 structures that specify the properties being requested. The vt and pstrName members of these structures must be filled in before calling this method. The dwHint member of these structures is optional. There must be at least cProperties elements in this array. This argument cannot be NULL.
pErrLog
[in] Address of an IErrorLog interface in which the property bag stores any errors that occur during the reads. This argument can be NULL; in that case, the caller is not interested in logging errors.
pvarValue
[out] Address of an array of VARIANT structures that receive the property values. The caller does not need to initialize these structures before calling Read. The Read method will fill both type and value fields in these structures before returning. There must be at least cProperties elements in this array. The calling application is responsible for freeing any allocations that are contained in these structures. This argument cannot be NULL.
phrError
[out] Address of an array of HRESULT values that will receive the result of each property read. There must be at least cProperties elements in this array. This argument cannot be NULL.


IPropertyBag2::Write

 HRESULT Write(
    [in] ULONG cProperties,
    [in] PROPBAG2 * pPropBag,
    [in] VARIANT * pvarValue
);

Causes one or more properties to be saved into the property bag.

  • Returns one of the following values:
    S_OK | The property bag successfully saved all of the properties.
    E_FAIL | One or more of the properties could not be saved.
cProperties
[in] Number of properties to save. This argument specifies the number of elements in the arrays at pPropBag and pvarValue.
pPropBag
[in] Address of an array of PROPBAG2 structures that specify the properties being saved. The pstrName member of these structures must be filled in before calling this method. The dwHint member of these structures is optional. There must be at least cProperties elements in this array. This argument cannot be NULL.
pvarValue
[out] Address of an array of VARIANT structures that contain the property values to be saved. There must be at least cProperties elements in this array. This argument cannot be NULL.


PROPBAG2

typedef struct _tagPROPBAG2{
    DWORD dwType;
    VARTYPE vt;
    CLIPFORMAT cfType;
    DWORD dwHint;
    LPOLESTR pstrName;
    CLSID clsid;
} PROPBAG2;

Contains or receives property information. This structure is used with the IPropertyBag2::GetPropertyInfo, IPropertyBag2::Read, and IPropertyBag2::Write methods.

dwType
Type of property. This will be one of the PROPBAG2_TYPE values.
vt
VARIANT type of the property.
cfType
Clipboard format or MIME type of the property.
dwHint
Property name hint. If possible, this member will be filled by IPropertyBag2::GetPropertyInfo and can be used with IPropertyBag2::Read and IPropertyBag2::Write to accelerate the read or write operation. These values are not valid outside of the property bag that created them.
pstrName
Address of an OLESTR that specifies the property name.
clsid
CLSID of the object. This member is only valid if dwType is PROPBAG2_TYPE_OBJECT.


PROPBAG2_TYPE

typedef enum _tagPROPBAG2_TYPE{
    PROPBAG2_TYPE_UNDEFINED = 0,
    PROPBAG2_TYPE_DATA = 1,
    PROPBAG2_TYPE_URL = 2,
    PROPBAG2_TYPE_OBJECT = 3,
    PROPBAG2_TYPE_STREAM = 4,
    PROPBAG2_TYPE_STORAGE = 5,
    PROPBAG2_TYPE_MONIKER = 6
} PROPBAG2_TYPE;

Identifies the type of data contained in a PROPBAG2 structure.

PROPBAG2_TYPE_UNDEFINED
The value type is unknown or undefined.
PROPBAG2_TYPE_DATA
The value type is simple data.
PROPBAG2_TYPE_URL
The value type is a URL reference.
PROPBAG2_TYPE_OBJECT
The value type is an object.
PROPBAG2_TYPE_STREAM
The value type is a stream.
PROPBAG2_TYPE_STORAGE
The value type is a storage.
PROPBAG2_TYPE_MONIKER
The value type is a moniker.


IErrorLog

The IErrorLog interface is an abstraction for an "error log" that is used to communicate detailed error information between a client and an object. The caller of the single interface member, AddError, simply "logs" an error where the error is an EXCEPINFO structure attached to a specific property. The implementor of the interface is responsible for handling the error in whatever way it desires.

IErrorLog is used in the protocol between a client that implements IPropertyBag and an object that implements IPersistPropertyBag.

IDL:

[
uuid(3127CA40-446E-11CE-8135-00AA004BB851)
    , object, pointer_default(unique)
]
interface IErrorLog : IUnknown
    {
    HRESULT AddError([in] LPCOLESTR pszPropName, [in] LPEXCEPINFO pExcepInfo);
    };

IErrorLog::AddError


IErrorLog::AddError

HRESULT AddError(
    [in] LPCOLESTR pszPropName,
    [in] LPEXCEPINFO pExcepInfo);

Logs an error (an EXCEPINFO structure) in the error log for a named property.

  • Returns one of the following values:
    S_OK | The error was logged successfully.
    E_FAIL | There was a problem logging the error.
    E_OUTOFMEMORY | There was not enough memory to log the error.
    E_POINTER | The address in pszPropName or pExcepInfo is not valid (such as NULL). The caller must supply both.
pszPropName
[in] Name of the property involved with the error. Cannot be NULL.
pExcepInfo
[in] Address of the caller-initialized EXCEPINFO structure that describes the error to log. Cannot be NULL.

E_NOTIMPL is not a valid return code because the member function is the only one in the entire interface.


The IPersistHistory Interface

The IPersistHistory interface provides a mechanism for a component to persist its state within its containing application during the current session.

IDL:

[
    uuid(91A565C1-E38F-11d0-94BF-00A0C9055CBF),
    object, pointer_default(unique)
]
interface IPersistHistory : IPersist
{
    typedef [unique] IPersistHistory *LPPERSISTHISTORY;

    HRESULT LoadHistory(
            [in]IStream *pStream,
            [in]IBindCtx *pbc);

    HRESULT SaveHistory(
            [in]IStream *pStream);

    HRESULT SetPositionCookie(
            [in]DWORD dwPositioncookie);

    HRESULT GetPositionCookie(
            [out]DWORD *pdwPositioncookie);
}

When to Implement

This interface is implemented by a component that wishes to maintain its state across navigations in a Web browser. Take the following example: a user enters data into a Web-based form containing intrinsic controls as well as a custom edit control. The user navigates away from the page and later returns during the current browser session to find that the data entered into the custom component is lost while the intrinsic controls have maintained their state.

In Internet Explorer 4.0, when a user navigates away from a page, all controls on the page are queried for IPersistHistory. Those that implement the interface are handed a stream through the SaveHistory method into which they can store their current state. If the page is loaded again during the current browser session, each control is handed a stream through the LoadHistory method from which they can reload their previous state.

Warning

Internet Explorer 4.0 caches up to 2 MB of persistent data for all controls. When this limit is exceeded data is discarded on a least recently used basis. Component developers should respect the needs of other controls and limit their use of the history cache.

Historical Note

This interface replaces the four-page cache implemented by Internet Explorer 3.0x; Internet Explorer 4.0 does not implement the four-page cache.

IPersistHistory::SaveHistory

IPersistHistory::LoadHistory

IPersistHistory::SetPositionCookie

IPersistHistory::GetPositionCookie


IPersistHistory::SaveHistory

HRESULT SaveHistory(
    [in]IStream *pStream);

The container calls this method to request that the component save its state.

  • Returns:
    S_OK | The component successfully saved its state to the supplied stream.
    E_FAIL | The component was unable to save its state.
    E_POINTER | The stream pointer was invalid.
pStream
[in] Pointer to the stream into which the object should save its persistent state.

The component should under no circumstances add a reference to the supplied stream. The stream should not be cached by the component.


IPersistHistory::LoadHistory

HRESULT LoadHistory(
    [in]IStream *pStream,
    [in]IBindCtx *pbc);

The container calls this method to request that the component restore its state.

  • Returns:
    S_OK | The component was able to reload its persistent state.
    E_FAIL | The component was unable to reload its persistent state.
    E_OUTOFMEMORY | There was not enough memory to reload the persistent state of the component.
    E_POINTER | The stream or the bind context supplied by the container were invalid.
pStream
[in] Pointer to an interface on the stream from which the object should loaded its persistent state
pbc
[in] Pointer to an interface on a bind context. The bind context can be used in binding operations to restore the component's state and should be passed along to any objects contained by the component if a binding is to be performed by the contained component.

The component should under no circumstances add a reference to the supplied stream. The stream should not be cached by the component.


IPersistHistory::SetPositionCookie

HRESULT SetPositionCookie(
    [in]DWORD dwPositioncookie);

Reserved for future use. Components are not required to implement this method.

  • Returns:
    E_NOTIMPL The component does not implement this method.


IPersistHistory::GetPositionCookie

HRESULT GetPositionCookie(
    [out]DWORD *pdwPositioncookie);

Reserved for future use. Components are not required to implement this method.

  • Returns:
    E_NOTIMPL The component does not implement this method.


ActiveX Controls

This section introduces ActiveX™ Controls and provides general guidelines for control developers. It includes information about run-time licensing for an ActiveX control, identifying an ActiveX control as safe for scripting or safe for code download, packaging an ActiveX control in a .cab file, and applying a test certificate to an ActiveX control.

In addition, this section includes information describing the differences between ActiveX Controls written for Internet Explorer 3.0 and ActiveX Controls written for Internet Explorer 4.0.

Introduction to ActiveX Controls

Building ActiveX Controls for Internet Explorer 4.0

ActiveX Control Tutorial

Safe Initialization and Scripting for ActiveX Controls

Licensing ActiveX Controls

Signing Code with Microsoft Authenticode Technology

Packaging ActiveX Controls

Palette Management for ActiveX Objects

Registering an ActiveX Object as the Player for a Media Type

Related Topics

Asynchronous and URL Monikers

Hyperlinks


Introduction to ActiveX Controls

The purpose of this topic is to provide guidelines for implementing ActiveX™ Controls that interoperate well with containers and other controls. This document defines the minimum set of interfaces, methods, and features that are required of ActiveX Controls to accomplish seamless and useful interoperability.

Overview

ActiveX Controls

General Guidelines

Overview

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.

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 document 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 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 document 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, "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 IUnknown::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 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 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), IOleObject is a necessity. When implementing this interface, the control must also support the semantics of the following methods: SetHostNames, Close, EnumVerbs, Update, IsUpToDate, GetUserClassID, GetUserType, GetMiscStatus, and the Advise, Unadvise, and 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 GetWindow, InPlaceActivate, UIDeactivate, SetObjectRects, and 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 (GetControlInfo, OnMnemonic methods); ambient properties (OnAmbientPropertyChange); and/or events that the control requires the container to handle (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, ambient property support means that the control supports IOleObject (to get the site) as well as being able to generate IDispatch::Invoke calls.

The 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 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 GetData, QueryGetData, EnumFormatEtc, DAdvise, DUnadvise, and 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 Draw, GetAdvise, SetAdvise, and 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 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 EnumConnections, which requires another enumerator object with the IEnumConnections interface.

IProvideClassInfo[2] | 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.
IOleCache[2] | 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.

Persistence Interfaces

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.
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 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.

With the exception of IPersistStream[Init]::GetSizeMax and IPersistMemory::GetSizeMax, all methods of each interface must be fully implemented.

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
GetControlInfo, OnMnemonic | Mandatory for controls with mnemonics.
OnAmbientPropertyChange | Mandatory for controls that use ambient properties.
FreezeEvents | See "Event Freezing" in the General Guidelines section.
IOleObject
SetMoniker | Mandatory if the control is not marked with OLEMISC_CANTLINKINSIDE.
GetMoniker | Mandatory if the control is not marked with OLEMISC_CANTLINKINSIDE.
InitFromData | Optional.
GetClipboardData | Optional.
SetExtent | Mandatory only for DVASPECT_CONTENT.
GetExtent | Mandatory only for DVASPECT_CONTENT.
SetColorScheme | Optional.
DoVerbs | See Note 1.
IoleInPlaceObject
ContextSensitiveHelp | Optional.
ReactivateAndUndo | Optional.
IOleInPlaceActiveObject
ContextSensitiveHelp | Optional.
IViewObject2
Freeze | Optional.
Unfreeze | Optional.
GetColorSet | Optional.
IPersistStream[Init], IPersistMemory
GetSizeMax | See Note 2.

Notes:

  1. A control with property pages must support IOleObject::DoVerbs for the OLEIVERB_PROPERTIES and OLEIVERB_PRIMARY verbs. A control that can be active must support IOleObject::DoVerbs for the OLEIVERB_INPLACEACTIVATE verb. A control that can be UI active must also support IOleObject::DoVerbs for the OLEIVERB_UIACTIVATE verb.
  2. If a control supports IPersistStream[Init] 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 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.

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 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 support 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 OnAmbientPropertyChange as a minimum) as well as IOleObject (for SetClientSite and 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.

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 SaveObject, ShowObject, OnShowWindow (only if a separate window activation state is supported), RequestNewObjectLayout, and GetContainer (if communication with other controls is desired). The 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 ContextSensitiveHelp) as part of the standard OLE in-place activation protocol.
IAdviseSink | Site | Control calls OnDataChange if the control supports IDataObject, OnViewChange if the control supports IViewObject2, and OnClose, OnSave, and OnRename if the control supports IOleObject.
IOleControlSite | Site | If supported, control calls OnControlInfoChanged when mnemonics change, LockInPlaceActive and TransformCoords if events are fired (the latter method is only used if coordinates are passed as event arguments), OnFocus and TranslateAccelerator if the control has a UI active state, and 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.

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.

Note A control should use IPropertyNotifySink::OnRequestEdit only for data binding; it is free to use OnChanged for either or both purposes.

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 CoMarshallInterface and CoUnmarshalInterface) when passed between threads. For more information about apartment model threading, refer to the Platform SDK documentation and the OLEAPT sample (in the Platform SDK).

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 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 SimpleFrameSite 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.

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 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. In the future, Microsoft will update these standard APIs to use both IClassFactory and IClassFactory2, as appropriate.

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.


Building ActiveX Controls for Internet Explorer 4.0

This article covers features of Microsoft® Internet Explorer 4.0 that a developer writing ActiveX™ Controls should take into account when targeting Internet Explorer 4.0 as a container. Some of the material in this article discusses existing technologies that have increased relevance with Internet Explorer 4.0; other sections discuss new technologies and services that are only available to a control in Internet Explorer 4.0. 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 COM technologies.

Justification for Targeting Internet Explorer as an ActiveX Control Container

Apartment-Model Awareness

OC96 and Windowless Controls

Accessing Dynamic HTML

Multimedia Services

Security

Authoring Controls for the Active Desktop

Debugging

Deployment

Appendix: Additional Guidelines and Version Differences

Justification for Targeting Internet Explorer as an ActiveX Control Container

Internet Explorer is built upon a foundation of reusable and extensible components; two of the main components—the WebBrowser Control (SHDOCVW) and the HTML parsing and rendering engine (MSHTML)—are available for reuse as part of other applications. An increasing number of Microsoft and third-party applications will be using the same underlying control container; thus, if a control works well in Internet Explorer 4.0 it will also work well in future applications. Current examples of this component reuse in the Internet Explorer suite include the Active Desktop and Outlook Express. Both use the same HTML rendering engine as Internet Explorer.

Apartment-Model Awareness

Apartment-model awareness is not a new issue with Internet Explorer 4.0, but it is an important one 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 4.0 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 OLE threading models, see article Q150777, "Descriptions and Workings of OLE Threading Models," in the Microsoft Knowledge Base.

OC96 and Windowless Controls

While the OC96 (or OLE Controls '96) specification is not new, full support is only now appearing in common containers such as Office 97 and Internet Explorer 4.0; thus, the specification deserves further attention. 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, but they also can participate in overlapping 2D layout, can maintain a specific z-order, and can exhibit transparency as well as irregular shapes. Visual Filter effects will only function on components that support the windowless control specification since 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 leverage 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. 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 IOleInPlaceObjectWindowless and IOleInPlaceSiteWindowless. When drawing itself, a control asks the container-provided IOleInPlaceSiteWindowless 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 4.0 allows components to access the Dynamic HTML 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:

  1. Call IOleClientSite::GetContainer. This returns the IOleContainer interface of the host of the control.
  2. 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.

Note Any of the new interfaces introduced with Internet Explorer 4.0 are subject to change until the product is officially released. It is recommended that you maintain a copy of the header files you used to develop your code. When you upgrade to a later build of Internet Explorer 4.0, upgrade to the corresponding build of the Internet Client SDK, and use a file comparison tool such as Windiff to compare headers. Windiff is a utility that ships with the Microsoft Platform SDK as well as Microsoft Visual C++®.

Multimedia Services

Internet Explorer 4.0 ships with a number of multimedia controls; however, the services these controls use are also available to third-party controls. These include pixel-level manipulation, soon-to-be-introduced DirectDraw® support, timer and synchronization services, and visual filters and transitions. The full specifications and samples are included in the Internet Client SDK.

Security

Any ActiveX control to be deployed and used with Internet Explorer should be conceived and designed with object safety in mind from the start. Object safety is a promise by the control author about the potential for the control to be used in a manner that might compromise security. There are two variations of object safety that usually, but not always, go together:

  • Safe for untrusted data asserts that the control is safe for any possible arguments (< PARAM> tags of an < OBJECT>).
  • Safe for scripting asserts that the control is safe for any possible use of its properties, methods, and events.

ActiveX Control Safety Checklist

Safety of the control is ultimately a subjective judgment, but as a general rule "safe" means that none of the following undesirable effects will result for any conceivable use of the control:

  • Exposure of private information on the local computer or network
  • Modification or destruction of information on the local computer or network
  • Faulting of the control and the potential crashing of the browser
  • Consumption of excessive time or resources such as memory
  • Execution of potentially damaging system calls, including execution of files
  • Use of the control in a deceptive manner and causing unexpected results

To help check these abstract requirements, here are some questions to consider carefully before marking a control as safe. The same questions apply to both untrusted data and scripting.

  • What files can the control potentially access?
    • The ability to create files can be used to put content into the trusted local computer file system, possibly overwriting valuable data.
    • Any arguments that allow arbitrary file paths to be specified are particularly suspect as potentially allowing access to private information.
    • Writing excessive amounts of data even to restricted sets of files can use up disk space.
  • Is registry access possible?
    • Access to even limited parts of the registry could reveal information that should be kept private.
    • Writing large amounts of data to the registry could waste resources or possibly corrupt the system.
  • What system calls can the control be made to do?
    • Executing code or arbitrary shell commands (such as FORMAT) is particularly dangerous.
    • Think about how any system calls might be used by a malicious page, especially those calls with arguments that can be influenced by an untrusted data (<PARAM>) or script invocation.
  • Other issues:
    • Since any control can be used by any page that happens to know its CLSID, think about how the control might be reused maliciously in a completely unrelated page. Would the user possibly be tricked by seeing the familiar control out of context?
    • Be sure the control won't loop infinitely or crash when given bad data or arguments.
    • Can the control be made to call other objects on the page, including Java™ applets? The Java virtual machine (VM) called from native code in the control could attribute greater permissions to the control than script on the page has. If the script can manipulate the control to call the Java VM for it, an indirect security attack is possible.
    • Can the control tunnel out of the frame in which it is hosted and access content in another frame? The data accessed could potentially violate the privacy of the user.

Using ActiveX Controls Safely in an Enterprise Intranet Environment

Using Internet Explorer 4.0 Security Zones, it is possible to configure the browser to only download or run ActiveX Controls located with the enterprise, a.k.a. the intranet. When a control is downloaded and installed on the local system, however, the location of the original download is no longer available, and the control is available for use on any page that might reference its CLSID. Thus, take heed to the possibility that an Internet page might reference a control intended only for the intranet. There are several approaches to handling this issue. If the ActiveX Controls are safe for untrusted data and for scripting, supporting IObjectSafety and requiring only safe controls is the simplest solution. Controls that are truly safe should not be usable to break security, even by an untrusted Internet page. To prevent an ActiveX control from being used on pages other than the one on which it is intended, the control can check the context in which it is instantiated. That is, a control can get access to the DHTML object model as described previously to get the URL of the page instantiating it. The location properties of the document object return the URL of the page in which the control is embedded. For instance, an ActiveX control could be designed to run only in the "http://admin" intranet area. When the URL of the containing document begins with a different protocol and host name, it could run with reduced functionality or otherwise completely disable itself. Be aware that some important controls might be inherently unsafe for scripting. They include tools to aid system configuration or installation. To safely use these ActiveX Controls in the intranet environment, it is important to guarantee:

  • That these controls be used only by intranet pages and cannot be reused by other untrusted security zones;
  • That any intranet pages that use these controls do so in the intended manner and with appropriate safeguards.

The first restriction can be enforced because, by default, ActiveX Controls are not safe for scripting. A system administrator can configure a custom security policy to override object safety for a specific intranet zone. Controls that are not marked as safe will run only in pages from this zone. In fact, it is possible to restrict a control to run only in a designated secure zone by explicitly not marking the control as safe. Of course the Internet zone should never be set to override object safety.

Authoring Controls for the Active Desktop

Internet Explorer 4.0 introduces 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. For more information see the article on Supporting Offline Browsing in Applications and Components.

Debugging

In Internet Explorer 4.0 Integrated Browser Mode, the Windows® shell and the Web 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. For specific information on debugging ActiveX Controls in Internet Explorer 4.0, see Q167715, "Debugging ActiveX Controls in IE 4.0 Integrated Browser," in the Microsoft Knowledge Base.

Deployment

When deploying controls, use cabinet (.cab) files, and sign your controls. Issues regarding security have received great attention recently. End users are not advised 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.

Don't assume that non-system DLLs are already installed on a client computer. Although some releases of Internet Explorer ship with certain DLLs, such as MFC and Ctl3d32, this is not a reasonable assumption for minimal installations as well as future versions of Internet Explorer. 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.

Appendix: Additional Guidelines and Version Differences

This section presents additional guidelines for creating controls as well as known differences in Internet Explorer's control hosting behavior between version 3.0 and 4.0. Internet Explorer 3.0 hosted controls in a slightly more relaxed fashion than Internet Explorer 4.0; Internet Explorer 4.0 follows the control container specification more stringently. This allows control authors to better anticipate the behavior of Internet Explorer 4.0 as a control container; however, it also means that controls which made assumptions based on Internet Explorer 3.0 behavior might no longer function as desired in Internet Explorer 4.0. Here we present the caveats we have discovered to control authors.

  • 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. For more information, see Q157437, "FIREV.EXE Fires Events from a Second Thread," in the Microsoft Knowledge Base.
  • Internet Explorer 4.0 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 no longer passes parameters to controls by value (ByVal); they are now passed by reference (ByRef). Controls should check the VARTYPE field of parameters of type VARIANT explicitly.
  • Internet Explorer 3.0 sent DoVerb with the verbs OLEIVERB_SHOW and OLEIVERB_HIDE to controls in addition to the verb OLEIVERB_INPLACEACTIVATE and IOleInPlaceObject::InPlaceDeactivate. Internet Explorer 4.0 no longer sends the show and hide verbs. A control should rely only on receiving the OLEIVERB_INPLACEACTIVATE verb and the IOleInPlaceObject::InPlaceDeactivate call.
  • Internet Explorer 3.0 activated all controls immediately upon initialization of the control. Internet Explorer 4.0 does not. 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 4.0, 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. Internet Explorer 3.0 would unfreeze events even if the control never reached the completed state. 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 FreezeEvents is called with FALSE and the freeze count reaches 0.
  • In Internet Explorer 3.0, controls were unloaded when the browser was closed. Internet Explorer 4.0 relies upon proper COM reference counting. When the last reference to a control is released, the control will unload. Likewise, when the last reference to Internet Explorer is released, the browser will unload. A control should take care when caching interface pointers such as those returned by IOleClientSite::GetContainer and IOleControlSite::GetExtendedObject.
  • 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 upon instantiation.
  • A control should only bring up modal dialog boxes 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 upon applications hosting your control directly or indirectly through Internet Explorer 4.0 components.
  • Controls should take advantage of the Internet Explorer 4.0 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 IBindStatusCallbackImpl::StartAsyncDownload.
  • Internet Explorer 4.0 eliminates the four-page in-memory cache. In Internet Explorer 3.0, controls would remain alive even though the page in which they were embedded was no longer in view. Assuming proper reference counting, Internet Explorer 4.0 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 element 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 4.0 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 4.0, 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, since controls are not put into the UI Active state when printed in the background, they should not rely upon window handles (HWND).


ActiveX Control Tutorial

This article describes how a developer can add ActiveX™ support to an existing OLE Automation control.

Introduction

Prerequisites

Document Roadmap

What Is an ActiveX Control?

Porting the Timer Control

Introduction

An ActiveX control is an object that supports a customizable, programmatic interface. Using the methods, events, and properties exposed by a control, Web authors can automate their HTML pages. Examples of ActiveX Controls include text boxes, command buttons, audio players, video players, stock tickers, and so on.

You can develop ActiveX Controls using Microsoft® Visual Basic® 5.0, Java™, and Microsoft Visual C++®. This article focuses on controls developed with Microsoft Visual C++. (For more information about developing controls with Microsoft Visual Basic, refer to http://www.microsoft.com/vbasic/prodinfo/cce/; for more information about developing controls with Java, refer to http://splash.javasoft.com/beans/javadoc/packages.html.)

Because ActiveX Controls are complex, Microsoft offers several tools that help a C++ developer create an ActiveX control. The following table describes these tools.
Tool | Description | Ships with
Microsoft Foundation Class Library (MFC) | A set of C++ classes that support COM, OLE, and ActiveX (among other things). MFC provides the simplest means of creating ActiveX Controls. | Microsoft Visual C++ version 4.2 or later. (MFC ships with earlier versions of Visual C++; however, these versions do not support ActiveX.)
ActiveX Controls Framework | A set of C++ classes designed specifically to support control development. The framework requires more knowledge of OLE Automation and dual interfaces than does MFC. | Internet Client SDK.
Microsoft ActiveX Template Library | A set of C++ templates designed to create small and fast COM objects. | ATL 2.1 ships with Microsoft Visual C++ version 5.0. (ATL 2.0 is a web release that relies on the Microsoft Visual C++ 4.2 IDE).

This tutorial focuses on controls developed with MFC. It demonstrates how you can convert an existing OLE control into an ActiveX control. The tutorial is based on the sample MFC timer control that ships with Microsoft Visual C++ version 4.2. The timer control supported a single event and a single property: It fired a Timer event at a user-specified interval (which corresponded to the control's Interval property). For the purpose of this tutorial, the timer control was converted into a digital alarm clock (which can be inserted into an HTML page using the OBJECT tag).

Digital-Clock ActiveX Control

The modifications to the sample timer control include:

  • Overriding the COleControl::OnDraw member function so that the control renders current time.
  • Adding support for stock font, foreground color, and background color properties.
  • Adding support for the alarm's hour and minute properties.
  • Identifying the control as safe for scripting.
  • Adding support for run-time licensing.
  • Packaging the control for efficient download on the Internet.

Prerequisites

To benefit from this tutorial, you should be familiar with COM (the OLE Component Object Model) and OLE Automation; in addition, you should know C++ and be familiar with the Microsoft Foundation Class Library (MFC).

If you need an introduction to COM, you should read the second edition of Inside OLE by Kraig Brockschmidt (published by Microsoft Press). If you need an introduction to OLE Automation, you should read ActiveX Controls Inside Out by Adam Denning (also published by Microsoft Press).

If you need an introduction to C++ or the Microsoft Foundation Class Library, you should read Inside Visual C++ by David Kruglinski (and published by Microsoft Press).

Document Roadmap

This document contains the following sections.
Section | Description
What Is an ActiveX Control? | Reviews the parts of an OLE control; in addition, it describes the new features that differentiate an ActiveX control from an OLE control. This section includes descriptions of topics such as security, run-time licensing, and packaging.
Porting the Timer Control | Describes the work required to convert the sample MFC timer control into a digital clock (with alarm functionality). It includes information about modifying the MFC OnDraw function, adding support for stock properties, adding support for custom properties, adding support for security, and so on.

What Is an ActiveX Control?

An ActiveX control is an OLE control that supports additional ActiveX features. This section reviews the architecture of an OLE control and then describes those additional ActiveX features (such as safety, run-time licensing, digital certificates, packaging the control, and so on).

Description of an OLE Control

At a minimum, an OLE control is a COM object that supports the IUnknown and IClassFactory (or IClassFactory2) interfaces. As stated, support for these interfaces is the minimal requirement for an OLE control; however, in order to do any meaningful work, an OLE control also supports a number of other interfaces that provide features such as writing persistent data to disk, supporting automation (methods, events, and properties), and supporting a user interface for the control.

The following table describes the interfaces that can be supported by an OLE control.
Interface | Description
IOleObject | Supports communication between container and the control.
IOleInPlaceObject | Supports in-place activation.
IOleControl | Supports mnemonics and ambient properties.
IDataObject | Supports data transfer (emulates DDE and the Windows® clipboard).
IViewObject2 | Supports functions that a container uses to render the control.
IDispatch | Supports a control's methods and properties.
IConnectionPointContainer | Supports a control's events or property-change notifications.
IProvideClassInfo2 | Supports functions that a container calls in order to obtain a pointer to the control's type information.
ISpecifyPropertyPages | Supports functions that a container calls to determine the extent of a control's property-page support.
IPerPropertyBrowsing | Supports functions that allow containers to retrieve individual control properties.
IPersist* | Includes six interfaces that support functions that enable a control to read or write its persistent data to storage, stream, or file.
IOleCache2 | Supports the functions that a container calls in order to cache a control's data.
IExternalConnection | Supports functions that a control uses to track external connections.
IRunnableObject | Supports functions that a container uses to determine whether a control differentiates between a "loaded" and a "running" state.

As the previous list notes, the IPersist* interfaces support functions that allow a control to read or write its persistent data to storage, stream, or file. The following list identifies these interfaces and their purpose.
Interface | Purpose
IPersistMemory | Saves and loads the control's data into a fixed-length sequential byte array (in memory).
IPersistStorage | Saves and loads the control's data 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 | Saves and loads the control's data as individual properties written to IPropertyBag, which the container implements. This is used for "Save As Text" functionality in some containers.
IPersistMoniker | Saves and loads the control's data 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.

For more information about any of these interfaces, refer to the OLE core documentation that ships with the Platform SDK.

Description of an ActiveX Control

An ActiveX control is an OLE control that supports several additional features. These features include:

  • Initialization security
  • Scripting security
  • Run-time licensing
  • Reduced footprint (for quick downloading)
  • Digital certification

Initialization security

When a control is initialized, it can receive data from an arbitrary IPersist* interface (from either a local or a remote URL) for initializing its state. This is a potential security hazard because the data could come from an untrusted source. Controls that guarantee no security breach regardless of the data source are considered safe for initialization.

There are two methods for indicating that your control is safe for initialization. The first method uses the Component Categories Manager to create the appropriate entries in the system registry. Internet Explorer examines the registry prior to loading your control to determine whether these entries appear. The second method implements an interface named IObjectSafety on your control. If Internet Explorer determines that your control supports IObjectSafety, it calls the IObjectSafety::SetInterfaceSafetyOptions method prior to loading your control in order to determine whether your control is safe for initialization.

For more information about identifying a control as safe for initialization using the Component Categories Manager, see Using the Component Categories Manager. For more information about identifying a control as safe for initialization using IObjectSafety, see Supporting the IObjectSafety Interface.

Scripting security

Code signing can guarantee a user that code is trusted. However, allowing ActiveX Controls to be accessed from scripts raises several new security issues. Even if a control is known to be safe in the hands of a user, it is not necessarily safe when automated by an untrusted script. For example, Microsoft Word is a trusted tool from a reputable source, but a malicious script can use its automation model to delete files on the user's computer, install macro viruses, and worse.

Just as there are two methods for indicating that your control is safe for initialization, so there are two methods for identifying a control as safe for scripting. The first method uses the Component Categories Manager to create the appropriate entries in the system registry (when your control is loaded). Internet Explorer 3.0 examines the registry prior to loading your control to determine whether these entries appear. The second method implements the IObjectSafety interface on your control. If Internet Explorer 3.0 determines that your control supports IObjectSafety, it calls the IObjectSafety::SetInterfaceSafetyOptions method prior to loading your control in order to determine whether your control is safe for scripting.

For more information about identifying a control as safe for scripting using the Component Categories Manager, see Using the Component Categories Manager. For more information about identifying a control as safe for scripting using IObjectSafety, see Supporting the IObjectSafety Interface.

Run-time licensing

Most Microsoft ActiveX Controls should support design-time licensing and run-time licensing. (The exception is the control that is distributed free of charge.) Design-time licensing ensures that a developer is building his or her application or Web page with a legally purchased control; run-time licensing ensures that a user is running an application or displaying a Web page that contains a legally purchased control.

Design-time licensing is verified by control containers such as Microsoft Visual Basic, Microsoft Access, or Microsoft Visual InterDev. Before these containers allow a developer to place a control on a form or Web page, they first verify that the control is licensed by the developer or content creator. These containers verify that a control is licensed by calling certain functions in the control: If the license is verified, the developer can add it.

Run-time licensing is also an issue for these containers (which are sometimes bundled as part of the final application); the containers again call functions in the control to validate the license that was embedded at design time.

Microsoft Internet Explorer 4.0 is another type of container. Like the other containers, Internet Explorer 4.0 also calls functions in the control to verify that it is licensed. However, unlike Visual Basic or Microsoft Access, which embed ActiveX Controls within the binary code of their application's executable files, Internet Explorer 4.0 uses a different model. This unique model is a necessary response due to:

  • The openness of the browser (the ability to view the HTML source file).
  • Client/server issues imposed by the Internet (or corporate intranet).

Because any Internet Explorer 4.0 user can view the HTML source code for a given Web page, and because an ActiveX control is copied to a user's computer before it is displayed, a level of indirection is required to "hide" the license for an ActiveX control from the user. This prevents an Internet Explorer 4.0 user from pirating and reusing a control that he or she has not purchased. Microsoft addresses these run-time licensing issues with a new file called the license package file (or LPK file). This file can be included in any HTML page by using the OBJECT tag. For more information about .lpk files, their use, and their format, refer to The License Package File.

The functions that support run-time licensing are members of the IClassFactory2 interface. Any ActiveX control that supports run-time licensing must support and implement this interface (an extension of the IClassFactory interface).

For more detailed information about implementing run-time licensing for an ActiveX control, see Run-Time Licensing.

Reduced footprint

Microsoft supports a data-compression technology and associated toolset that you can use to package your ActiveX control for faster, more efficient downloading over the Internet or an intranet. (This same technology and toolset can be applied to Microsoft Win32® applications, Java classes, and Java libraries.)

Cabinet files

For a number of years, Microsoft used cabinet (.cab) files to compress software that was distributed on disks. Originally, these files were used to minimize the number of disks shipped with packaged product; today, .cab files are used to reduce the file size and the associated download time for Web content that is found on the Internet or corporate intranet servers.

The cabinet file format

The .cab file format is a nonproprietary compression format, also known as MSZIP, that is based on the Lempel-Ziv data-compression algorithm. (At a future date, other compression formats might also be supported.)

For information about Cabarc.exe, the tool you can use to compress your ActiveX control, see Cabarc.exe.

For a description of the HTML tags required to access a control contained in a cabinet file, see Accessing Controls Stored in Cabinet Files.

Digital certification

The default security level for Internet Explorer 4.0 is High. When a user runs at this level, any object identified by the OBJECT tag on an HTML page must be digitally signed. Digital signatures are created using the Authenticode™ technology that is included in the Internet Client SDK. A digital signature associates a software vendor's name and a unique public key with a file that contains an ActiveX object (ensuring some sort of accountability on the part of the object's developer).

Before you purchase a certificate for your control's .cab file from a vendor, you can use the test certificate provided by Microsoft for verification purposes.

For a description of how you can add a digital certificate to your control's .cab file, see Signing Code with Microsoft Authenticode Technology.

Porting the Timer Control

The original OLE timer control was an ideal starting point for a digital clock example: existing code was already in place to handle the firing of an event at a user-specified interval. The remaining work required the addition of:

  • Drawing code (which retrieves the current system time and then renders it within the control's real estate).
  • Stock properties (which let the user customize the appearance of the clock's output).
  • Custom properties (which let the user set the clock's alarm).
  • Support for safety (required by Internet Explorer).
  • Support for run-time licensing (required by Internet Explorer).
  • Packaging into a .cab file (required for efficient downloads).

The following sections describe how clock-like features were added to the timer control.

Modifying the OnDraw Function

As noted earlier in the Description of an OLE Control section, IViewObject is one of the interfaces commonly supported by an OLE control. This interface supports functions that a container calls to draw a control, to retrieve the logical palette used by a control, and to temporarily freeze the current view of the control.

MFC wraps the IViewObject functions in the COleControl class. These functions are defined in the Ctlview.cpp file. The COleControl::XViewObject::Draw function contains most of the functionality typically found in an implementation of IViewObject::Draw; this functionality includes:

  • Checking for optimized drawing.
  • Saving the DC state.
  • Switching to MM_TEXT mode.
  • Drawing the control.

To draw the control, COleControl::XViewObject::Draw calls the COleControl::DrawContent member function, which is found in Ctlcore.cpp. This function maps logical coordinates into device coordinates. Once the coordinates are converted, COleControl::DrawContent calls COleControl::OnDraw to actually render the control. COleControl::OnDraw must be overridden by the control developer in the source code, which is generated by the OLE Control wizard. The default version of COleControl::OnDraw simply draws an ellipse. The following excerpt shows the code provided by the OLE Control wizard.

/////////////////////////////////////////////////////////////////////////////
// CTimeCtrl::OnDraw - Drawing function

void CTimeCtrl::OnDraw(
            CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
{
    // TODO: Replace the following code with your own drawing code.
    pdc->FillRect(rcBounds, CBrush::FromHandle((HBRUSH)GetStockObject(WHITE_BRUSH)));
    pdc->Ellipse(rcBounds);
}

Most of the code required to convert the sample timer control into a digital clock is actually found in the OnDraw function. This includes code that:

  • Sets the user's requested foreground color, background color, and font.
  • Sets the background mode to transparent for drawing.
  • Paints the control's background using the background color.
  • Retrieves the current system time.
  • Compares the current time to the user's requested alarm time.
  • Sounds the alarm if necessary.
  • Packages the time for rendering.
  • Renders the string containing the time.
  • Reselects the original font and brush into the device context.

The entire OnDraw function from the Timectl.cpp file appears below.

/////////////////////////////////////////////////////////////////////////////
// CTimeCtrl::OnDraw - Drawing function

void CTimeCtrl::OnDraw(
            CDC* pdc, const CRect& rcBounds, const CRect&)
{

    //Variables required for text output
    CFont* pOldFont;
    TEXTMETRIC tm;
    char tText[30];
    CString strTime;
    CRect rc = rcBounds;
    CBrush bkBrush(TranslateColor(GetBackColor()));

    //Variables required for alarm handler
    time_t aclock;     // Elapsed time in seconds since 00:00:00 on 1/1/1970
    struct tm *now;    // Time structure (contains useful time data)
    BOOL bTest;

    //Set the foreground color property and the transparent bk mode
    pdc->SetTextColor(TranslateColor(GetForeColor()));
    pdc->SetBkMode(TRANSPARENT);

    //Paint the background using the BackColor property
    pdc->FillRect(rcBounds, &bkBrush);

    //Retrieve the time, check the alarm, package a string specifying the time
    time(&aclock);    //Retrieve elapsed time in seconds
    now = localtime(&aclock);    //Convert elapsed seconds to meaningful data

    //Examine the alarm properties
    if ((now->tm_hour == m_alarmHour) && (now->tm_min == m_alarmMinute))
        if (bAlarm == TRUE)
    {
        bTest = PlaySound((const char*)IDR_WAVE3, AfxGetResourceHandle(), SND_RESOURCE);
        bAlarm = FALSE;
    }

    //Package time for rendering
    sprintf(tText, "Current Time: %.8s\n", asctime(now)+11);
    strTime = tText;

    //Draw the time string
    pOldFont = SelectStockFont(pdc);
    pdc->GetTextMetrics(&tm);
    pdc->SetTextAlign(TA_CENTER | TA_TOP);
    pdc->ExtTextOut((rc.left + rc.right) / 2, (rc.top + rc.bottom - tm.tmHeight) / 2, ETO_CLIPPED, rc, strTime, (strTime.GetLength())-1, NULL);

    //Reselect the old font and brush
    pdc->SelectObject(pOldFont);

}

Adding Support for Color and Font Properties

As noted in the previous section that describes the OnDraw function, the current time is rendered by using the background color, foreground color, and font properties that the user selected. These properties are three of the nine stock properties supported by the COleControl class. The following list identifies the nine supported stock properties:

  • Appearance
  • BackColor
  • BorderStyle
  • Caption
  • Enabled
  • Font
  • ForeColor
  • hWnd
  • Text

Support for the three stock properties was added using the ClassWizard. For example, support for the background color property was added through the following steps when the control's project was open in Microsoft Visual C++:

  1. Select ClassWizard from the View menu.
  2. Select the OLE Automation tab.
  3. Select the control's class from the Class Name combo box.
  4. Select the Add Property button.
  5. Select the BackColor property in the External Name box.
  6. Select the OK button to close the Add Property dialog box.
  7. Select the OK button to confirm choices and close the ClassWizard.

When support for a stock property is added using the ClassWizard, the control's implementation file (Timectl.cpp in the case of the sample) and the control's object description (.odl) file (Timectl.odl) are updated. In the case of the sample control's implementation file, the following line was added to the control's dispatch map:

DISP_STOCKPROP_BACKCOLOR()

In the case of the sample control's object description file, the following line was added:

[id(DISPID_BACKCOLOR), bindable, requestedit] OLE_COLOR BackColor;

After support for the stock properties was added to the sample control, these properties were available for drawing operations. The background color was retrieved by calling the GetBackColor function and applying the result to a background brush variable (bkBrush) as follows:

CBrush bkBrush(TranslateColor(GetBackColor()));

Adding Support for the Alarm (Properties and Resource)

As noted in the Adding Support for Color and Font Properties section, COleControl supports nine stock properties that are common to most ActiveX Controls. However, invariably a control requires custom properties such as the Alarm properties that are supported by the digital clock control.

There are four types of custom properties: member variables, member variables with notification, get/set methods, and parameterized. The sample digital-clock control supports three custom properties (each of which falls into the category of member variables with notification). These properties are the AlarmHour, AlarmMinute, and Interval. The AlarmHour property specifies the hour at which the alarm should trigger; the AlarmMinute property specifies the minute at which the alarm should trigger; and the Interval property specifies the interval (in milliseconds) for updating the clock that's displayed by the control. When the user resets any of these custom properties, a notification function is called to handle any work necessitated by those changes.

The MFC framework supports COleControl::DoPropExchange to load or store persistent data for a control. This function normally makes calls to the PX_ family of functions to load or store specific user-defined properties. The sample control takes advantage of the MFC property-exchange functionality to save and load the custom interval and alarm properties.

/////////////////////////////////////////////////////////////////////////////
// CTimeCtrl::DoPropExchange - Persistence support

void CTimeCtrl::DoPropExchange(CPropExchange* pPX)
{
    ExchangeVersion(pPX, MAKELONG(_wVerMinor, _wVerMajor));
    COleControl::DoPropExchange(pPX);

    long nInterval = m_interval;
    short sAlarmHr = m_alarmHour;
    short sAlarmMin = m_alarmMinute;

    PX_Long(pPX, _T("Interval"), m_interval, DEFAULT_INTERVAL);
    if (pPX->IsLoading())
    {
        if (nInterval != m_interval)
            OnIntervalChanged();
    }

    PX_Short(pPX, _T("AlarmHour"), m_alarmHour, DEFAULT_HOUR);
    if (pPX->IsLoading())
    {
        if (sAlarmHr != m_alarmHour)
            OnAlarmHourChanged();
    }

    PX_Short(pPX, _T("AlarmMinute"), m_alarmMinute, DEFAULT_MINUTE);
    if (pPX->IsLoading())
    {
        if (sAlarmMin != m_alarmMinute)
            OnAlarmMinuteChanged();
    }
}

In the case of the AlarmHour property, when the OnAlarmHourChanged function is called, a global Boolean variable (bAlarm) is set to TRUE and the COleControl::SetModifiedFlag function is called to notify the container that the control's persistent state has changed.

void CTimeCtrl::OnAlarmHourChanged()
{
    bAlarm = TRUE;
    SetModifiedFlag(TRUE);
}

The global variable (bAlarm) is examined within the COleControl::OnDraw function to ensure that the alarm should be triggered at the specified time.

//Examine the alarm properties
    if ((now->tm_hour == m_alarmHour) && (now->tm_min == m_alarmMinute))
        if (bAlarm == TRUE)
    {
        bTest = PlaySound((const char*)IDR_WAVE3, AfxGetResourceHandle(), SND_RESOURCE);
        bAlarm = FALSE;
    }

As this code excerpt shows, the alarm sounds when the PlaySound function is called. This function takes three arguments: the first (IDR_WAVE3) is a resource identifier; the second is an instance handle for the control's resources; and the third is a flag specifying that the sound is a resource.

The current alarm sound (a ringing phone) was inserted into the sample control's DLL to ensure that the alarm would activate on any system that supported audio output. This .wav file was inserted using the Insert Resource dialog box.

Adding Support for Safety

The sample control uses the Component Categories Manager to identify its safety features. For sample code demonstrating how this support was implemented, see Registering a Control as Safe.

Adding Support for Run-Time Licensing

The sample control supports run-time licensing for most containers through the IClassFactory2 interface. However, when Internet Explorer is the container, it's also necessary to generate an .lpk file as part of the run-time licensing scheme.

For more information about run-time licensing and .lpk files, see Run-Time Licensing for Internet Explorer 4.0.

For more information about using LPKTool.exe to create an .lpk file for a control, see Appendix A - Generating a License Package with LPK_TOOL.

Reducing a Control's Footprint

The sample control was compressed using Cabarc.exe, a tool provided as part of the Internet Client SDK. For a description of this process, see Packaging the Sample ActiveX Control.

Creating a Test Certificate for an ActiveX Control

For testing purposes, a digital certificate was added to the sample control's .cab file using several tools shipped with the Internet Client SDK. For a description of this process, see Signing the .cab File.


Safe Initialization and Scripting for ActiveX Controls

This document describes the code that a control developer should implement to ensure safe initialization and safe scripting for a Microsoft® ActiveX™ control.

Introduction

Internet Explorer 3.0 Security Levels

Initialization Security

Scripting Security

Using the Component Categories Manager

Supporting the IObjectSafety Interface

Reference

Introduction

Many Microsoft® ActiveX™ Controls are initialized with persistent data, which is either local or remote, and most ActiveX Controls are scriptable (they support a set of methods, events, and properties). Both initialization (with persistent data) and scripting require certain safeguards to ensure that security is not violated.

An example of a control that poses a security risk at initialization time would be a data compression/decompression control. If the user pointed this control to a remote, compressed file that contained a Trojan-horse copy of a system file (such as Kernel.dll) and requested that the control decompress this file, system security could be breached.

An example of a control that poses a security risk at scripting time would be a control that relies on certain system settings before a script can be safely executed. It would be up to the control developer to provide the necessary code that retrieves the system settings before allowing the script to execute.

Internet Explorer 3.0 Security Levels

Microsoft Internet Explorer 3.0 has three security levels: low, medium, and high. When the user attempts to display a page containing a control that does not guarantee safe initialization or scripting, Internet Explorer 3.0 does one of the following based on the current security level.
Security level | Internet Explorer 3.0 notification
Low | No warnings. Controls can be initialized or scripted regardless of data source or scripts.
Medium | User is warned of potential safety violation prior to loading the page. User can accept or reject initialization or scripting. If user disables scripting, scripting errors occur when user views the page and attempts to execute the script.
High | User is warned of potential safety violation prior to loading the page. User cannot accept or reject initialization or scripting. Scripting errors occur if user attempts to view page and execute script.

Note Most control developers set their Internet Explorer 3.0 security level to low during the early stages of control development. However, prior to implementing the safe initialization and scripting code, security should be reset to high (the default setting) to ensure adequate testing of the control.

Initialization Security

When a control is initialized, it can receive data from an arbitrary IPersist* interface (from either a local or a remote URL) for initializing its state. This is a potential security hazard because the data could come from an untrusted source. Controls that guarantee no security breach regardless of the data source are considered safe for initialization.

There are two methods for indicating that your control is safe for initialization. The first method uses the Component Categories Manager to create the appropriate entries in the system registry. Internet Explorer 3.0 examines the registry prior to loading your control to determine whether these entries appear. The second method implements an interface named IObjectSafety on your control. If Internet Explorer 3.0 determines that your control supports IObjectSafety, it calls the IObjectSafety::SetInterfaceSafetyOptions method prior to loading your control in order to determine whether your control is safe for initialization.

Scripting Security

Code signing can guarantee a user that code is trusted. However, allowing ActiveX Controls to be accessed from scripts raises several new security issues. Even if a control is known to be safe in the hands of a user, it is not necessarily safe when automated by an untrusted script. For example, Microsoft Word is a trusted tool from a reputable source, but a malicious script can use its automation model to delete files on the user's computer, install macro viruses, and worse.

There are two methods for indicating that your control is safe for scripting. The first method uses the Component Categories Manager to create the appropriate entries in the system registry (when your control is loaded). Internet Explorer 3.0 examines the registry prior to loading your control to determine whether these entries appear. The second method implements the IObjectSafety interface on your control. If Internet Explorer 3.0 determines that your control supports IObjectSafety, it calls the IObjectSafety::SetInterfaceSafetyOptions method prior to loading your control in order to determine whether your control is safe for scripting.

Using the Component Categories Manager

As mentioned previously, Internet Explorer 3.0 examines the system registry to determine whether a control is safe for initialization and/or scripting. Internet Explorer 3.0 examines the registry by calling the ICatInformation::IsClassOfCategories method to determine if the control supports the given category (safe for initializing or safe for scripting).

If a control uses the Component Categories Manager to register itself as being safe, the registry entry for that control contains an Implemented Categories key, which contains one or two subkeys. One subkey is set if the control supports safe initialization, and the other subkey is set if the control supports safe scripting. The safe initialization subkey corresponds to CATID_SafeForInitializing; the safe scripting subkey corresponds to CATID_SafeForScripting. (Unlike the other subkeys for the component categories that are defined in the Comcat.h file, the subkeys for safe initialization and scripting are defined in Objsafe.h.)

The following illustration shows a snapshot of the registry entry for the Tabular Data Control, an ActiveX™ control that ships with Internet Explorer and allows authors to create data-driven Web pages. Because the control is safe for scripting and initialization, it marks itself in the registry as safe for scripting (7DD95801-9882-11CF-9FA9-00AA006C42C4)and safe for initializing from persistent data (7DD95802-9882-11CF-9FA9-00AA006C42C4).

Registry entry for a control

Registering a Control as Safe

The system registry contains a Component Categories key that lists subkeys for each of the categories of functionality that are implemented or required by components and applications installed on the system. The following illustration shows a snapshot of the Component Categories key. Observe that the CATID_SafeForScripting (7DD95801-9882-11CF-9FA9-00AA006C42C4) and the CATID_SafeForInitializing (7DD95802-9882-11CF-9FA9-00AA006C42C4) are included in the list:

Component Categories key and subkeys

To create the subkey for the appropriate component category, your control must complete the following steps:

  1. Create an instance of the Component Categories Manager and retrieve the address of the ICatRegister interface.
  2. Set the appropriate members of a CATEGORYINFO structure.
  3. Call the ICatRegister::RegisterCategories method, passing the address of the initialized CATEGORYINFO structure.

The following example shows how these steps were accomplished and combined into a single function named CreateComponentCategory in the sample control.

#include "comcat.h"
HRESULT CreateComponentCategory(CATID catid, WCHAR* catDescription)
    {

    ICatRegister* pcr = NULL ;
    HRESULT hr = S_OK ;

    hr = CoCreateInstance(CLSID_StdComponentCategoriesMgr,
            NULL, CLSCTX_INPROC_SERVER, IID_ICatRegister, (void**)&pcr);
    if (FAILED(hr))
        return hr;

    // Make sure the HKCR\Component Categories\{..catid...}
    // key is registered.
    CATEGORYINFO catinfo;
    catinfo.catid = catid;
    catinfo.lcid = 0x0409 ; // english

    // Make sure the provided description is not too long.
    // Only copy the first 127 characters if it is.
    int len = wcslen(catDescription);
    if (len>127)
        len = 127;
    wcsncpy(catinfo.szDescription, catDescription, len);
        // Make sure the description is null terminated.
        catinfo.szDescription[len] = '\0';

    hr = pcr->RegisterCategories(1, &catinfo);
        pcr->Release();

        return hr;
    }

When a subkey has been created for the requisite category, the control should register the category as being implemented by the control. To register the category as being implemented, your control should complete these tasks:

  1. Create an instance of the Component Categories Manager and retrieve the address of the ICatRegister interface.
  2. Call the ICatRegister::RegisterClassImplCategories method, passing the control's CLSID and the requisite category ID as arguments.

The following example shows how these steps were accomplished and combined into a single function named RegisterCLSIDInCategory in the sample control.

#include "comcat.h"
HRESULT RegisterCLSIDInCategory(REFCLSID clsid, CATID catid)
    {
// Register your component categories information.
    ICatRegister* pcr = NULL ;
    HRESULT hr = S_OK ;
    hr = CoCreateInstance(CLSID_StdComponentCategoriesMgr,
                NULL, CLSCTX_INPROC_SERVER, IID_ICatRegister, (void**)&pcr);
    if (SUCCEEDED(hr))
    {
       // Register this category as being "implemented" by the class.
       CATID rgcatid[1] ;
       rgcatid[0] = catid;
       hr = pcr->RegisterClassImplCategories(clsid, 1, rgcatid);
    }

    if (pcr != NULL)
        pcr->Release();

        return hr;
        }

A control should register the safe initialization and scripting categories when its DLLRegisterServer function is called. (DLLRegisterServer is called by the Component Object Model [COM] to create the registry entries for all classes supported by the control.) In the sample control, the DLLRegisterServer function contains calls to the CreateComponentCategory and RegisterCLSIDInCategory functions (which register the control as safe for initialization and scripting). The following implementation of DLLRegisterServer was taken from the sample control.

STDAPI DllRegisterServer(void)
{
    HRESULT  hr;    // return for safety functions

    AFX_MANAGE_STATE(_afxModuleAddrThis);

    if (!AfxOleRegisterTypeLib(AfxGetInstanceHandle(), _tlid))
        return ResultFromScode(SELFREG_E_TYPELIB);

    if (!COleObjectFactoryEx::UpdateRegistryAll(TRUE))
        return ResultFromScode(SELFREG_E_CLASS);

    // Mark the control as safe for initializing.

    hr = CreateComponentCategory(CATID_SafeForInitializing, L"Controls
        safely initializable from persistent data!");
    if (FAILED(hr))
        return hr;

    hr = RegisterCLSIDInCategory(CLSID_SafeItem, CATID_SafeForInitializing);
    if (FAILED(hr))
        return hr;

    // Mark the control as safe for scripting.

    hr = CreateComponentCategory(CATID_SafeForScripting, L"Controls
        safely scriptable!");
    if (FAILED(hr))
        return hr;

    hr = RegisterCLSIDInCategory(CLSID_SafeItem, CATID_SafeForScripting);
    if (FAILED(hr))
        return hr;

    return NOERROR;
}

Just as a control is responsible for creating the registry entries for all safety categories it supports, a control is also responsible for unregistering its categories. The COM calls a control's DLLUnRegisterServer function to remove appropriate registry entries before unloading the control.

To unregister the safe initialization and/or safe scripting categories, the control should do the following:

  1. Create an instance of the Component Categories Manager and retrieve the address of the ICatRegister interface.
  2. Call the ICatRegister::UnRegisterClassImplCategories method, passing the control's CLSID and the requisite category ID as arguments.

The following example shows how these steps were accomplished and combined into a single function named UnRegisterCLSIDInCategory in a sample control.

HRESULT UnRegisterCLSIDInCategory(REFCLSID clsid, CATID catid)
    {
    ICatRegister* pcr = NULL ;
    HRESULT hr = S_OK ;

    hr = CoCreateInstance(CLSID_StdComponentCategoriesMgr,
            NULL, CLSCTX_INPROC_SERVER, IID_ICatRegister, (void**)&pcr);
    if (SUCCEEDED(hr))
    {
       // Unregister this category as being "implemented" by the class.
       CATID rgcatid[1] ;
       rgcatid[0] = catid;
       hr = pcr->UnRegisterClassImplCategories(clsid, 1, rgcatid);
    }

    if (pcr != NULL)
        pcr->Release();

        return hr;
        }

As mentioned previously, a control is responsible for removing the safe initialization and scripting category entries when the DLLUnRegisterServer function is called. The following excerpt is taken from a sample control that supports both safe initialization and safe scripting.

STDAPI DllUnregisterServer(void)
{
    HRESULT hr;    // HResult used by Safety Functions

    AFX_MANAGE_STATE(_afxModuleAddrThis);

    if (!AfxOleUnregisterTypeLib(_tlid))
        return ResultFromScode(SELFREG_E_TYPELIB);

    if (!COleObjectFactoryEx::UpdateRegistryAll(FALSE))
        return ResultFromScode(SELFREG_E_CLASS);

    // Remove entries from the registry.

    hr=UnRegisterCLSIDInCategory(CLSID_SafeItem, CATID_SafeForInitializing);
    if (FAILED(hr))
        return hr;

    hr=UnRegisterCLSIDInCategory(CLSID_SafeItem, CATID_SafeForScripting);
    if (FAILED(hr))
        return hr;

    return NOERROR;
}

Supporting the IObjectSafety Interface

The IObjectSafety interface allows a container to ask a control to make itself safe, or to retrieve the current initialization or scripting capabilities for the control. This interface is defined in the Objsafe.h file. Currently two capabilities are supported: safe for scripting and safe for initialization. These capabilities correspond to the following bit flags, which are defined in Objsafe.h:
INTERFACESAFE_FOR_UNTRUSTED_DATA | Specifies that the interface is safe for initialization.
INTERFACESAFE_FOR_UNTRUSTED_CALLER | Specifies that the interface is safe for scripting.

(Additional capabilities may be defined at a future date.)

The IObjectSafety interface supports two methods: IObjectSafety::GetInterfaceSafetyOptions and IObjectSafety::SetInterfaceSafetyOptions. The first method returns the control's security capabilities (as specified by either of the two bit flags listed above). The second method allows a container to request that a control configure itself for safe initialization or scripting. The following listing from Objsafe.h contains the interface and method definitions.

// Option bit definitions for IObjectSafety:
#define    INTERFACESAFE_FOR_UNTRUSTED_CALLER    0x00000001
        // Caller of interface may be untrusted.
#define    INTERFACESAFE_FOR_UNTRUSTED_DATA    0x00000002
        // Data passed into interface may be untrusted.

// {CB5BDC81-93C1-11cf-8F20-00805F2CD064}
DEFINE_GUID(IID_IObjectSafety, 0xcb5bdc81, 0x93c1, 0x11cf, 0x8f, 0x20,
    0x0, 0x80, 0x5f, 0x2c, 0xd0, 0x64);

interface IObjectSafety : public IUnknown
    {
    public:
        virtual HRESULT __stdcall GetInterfaceSafetyOptions(
            /* [in]  */ REFIID riid,
            /* [out] */ DWORD __RPC_FAR *pdwSupportedOptions,
            /* [out] */ DWORD __RPC_FAR *pdwEnabledOptions) = 0;

        virtual HRESULT __stdcall SetInterfaceSafetyOptions(
            /* [in] */ REFIID riid,
            /* [in] */ DWORD dwOptionSetMask,
            /* [in] */ DWORD dwEnabledOptions) = 0;

    };

Although some containers call IObjectSafety::GetInterfaceSafetyOptions to retrieve the default capabilities for a control, Internet Explorer 3.0 does not. Instead, it calls IObjectSafety::SetInterfaceSafetyOptions prior to initializing a control. And, if the control supports scripting, it calls IObjectSafety::SetInterfaceSafetyOptions a second time. This function has three arguments: the first specifies the interface ID for an interface on the control that the container wants to access; the second specifies the capabilities that the container would like to set; the third specifies how the container would like to set these options.


Reference

This section describes the methods of the IObjectSafety interface.

IObjectSafety::GetInterfaceSafetyOptions

IObjectSafety::SetInterfaceSafetyOptions


IObjectSafety::GetInterfaceSafetyOptions

HRESULT  GetInterfaceSafetyOptions(
    /* [in]  */ REFIID riid,
    /* [out] */ DWORD __RPC_FAR *pdwSupportedOptions,
    /* [out] */ DWORD __RPC_FAR *pdwEnabledOptions);

Retrieves the safety options supported by an object as well as the safety options that are currently set for that object.

  • Returns S_OK if successful, or E_NOINTERFACE if riid specifies an interface that is unknown to the object.
riid
Specifies the interface ID for a given object.
pdwSupportedOptions
Specifies the options supported for the interface identified by riid.
pdwEnabledOptions
Specifies the options currently enabled for the interface identified by riid.

This method returns a set of bits in pdwSupportedOptions for each capability that the control knows about, and a set of bits in pdwEnabledOptions for each capability for which the control is currently safe.

For example, a control might say that it knows about INTERFACESAFE_FOR_UNTRUSTED_DATA and INTERFACESAFE_FOR_UNTRUSTED_CALLER, and is currently safe for just INTERFACESAFE_FOR_UNTRUSTED_DATA.


IObjectSafety::SetInterfaceSafetyOptions

HRESULT SetInterfaceSafetyOptions(
    /* [in] */ REFIID riid,
    /* [in] */ DWORD dwOptionSetMask,
    /* [in] */ DWORD dwEnabledOptions);

Makes an object safe for initialization or scripting.

  • Returns one of the following values:
    S_OK | Success,
    E_NOINTERFACE | riid specifies an interface that is unknown to the object.
    E_FAIL | dwOptionSetMask specifies an option that is not supported by the object.
riid
Specifies the interface ID for the object to be made safe.
dwOptionSetMask
Specifies the options to be changed.
dwEnabledOptions
Specifies the settings for the options that are to be changed. This can be one of the following values:
INTERFACESAFE_FOR_UNTRUSTED_CALLER | The interface identified by riid should be made safe for scripting.
INTERFACESAFE_FOR_UNTRUSTED_DATA | The interface identified by riid should be made safe for untrusted data during initialization.

Typically, a control client would call this method before attempting to script or initialize the object, in an attempt to require that the object is safe for the desired action. If SetInterfaceSafetyOptions returns failure, its client displays the user interface to the user to confirm whether the action should be allowed.

This method takes an interface (either IDispatch for scripting or IPersist* for initialization), a bitmask of options to change (dwOptionSetMask), and the new settings for those options (dwEnabledOptions). The currently supported capabilities are, as described above, INTERFACESAFE_FOR_UNTRUSTED_CALLER and INTERFACESAFE_FOR_UNTRUSTED_DATA.


Licensing ActiveX Controls

This article explains the licensing strategy for Microsoft® ActiveX™ Controls. In addition, it describes the functionality that must be implemented on an ActiveX control in order to support this strategy.

Introduction

Design-Time Licensing

Run-Time Licensing

Appendix A - Generating a License Package with LPK_TOOL

Appendix B - Authoring Tools

Introduction

Most Microsoft® ActiveX™ Controls should support design-time licensing and run-time licensing. (The exception is the control that is distributed free of charge.) Design-time licensing ensures that a developer is building his or her application or Web page with a legally purchased control; run-time licensing ensures that a user is running an application or displaying a Web page that contains a legally purchased control.

Design-time licensing is verified by control containers such as Microsoft Visual Basic®, Microsoft Access, or Microsoft Visual InterDev. Before these containers allow a developer to place a control on a form or Web page, they first verify that the control is licensed by the developer or content creator. These containers verify that a control is licensed by calling certain functions in the control: If the license is verified, the developer can add it.

Run-time licensing is also an issue for these containers (which are sometimes bundled as part of the final application); the containers again call functions in the control to validate the license that was embedded at design time.

Microsoft Internet Explorer 4.0 is another type of container. Like the other containers, Internet Explorer 4.0 also calls functions in the control to verify that it is licensed. However, unlike Visual Basic or Microsoft Access, which embed ActiveX Controls within the binary code of their application's executable files, Internet Explorer 4.0 uses a different model. This unique model is a necessary response due to:

  • The openness of the browser (the ability to view the HTML source file).
  • Client/server issues imposed by the Internet (or corporate intranet).

Because any Internet Explorer 4.0 user can view the HTML source code for a given Web page, and because an ActiveX control is copied to a user's computer before it is displayed, a level of indirection is required to "hide" the license for an ActiveX control from the user. This prevents an Internet Explorer 4.0 user from pirating and reusing a control that they have not purchased. Microsoft addresses these run-time licensing issues with a new file called the license package file (or LPK file). This file can be included in any HTML page by using the OBJECT tag. For more information about .lpk files, their use, and their format, refer to The License Package File.

The functions that support design-time and run-time licensing are members of the IClassFactory2 interface. Any ActiveX control that supports licensing must support and implement this interface (an extension of the IClassFactory interface). The IClassFactory interface supports the CreateInstance and LockServer methods. ( IClassFactory::CreateInstance is called by a container to instantiate a control; IClassFactory::LockServer is called by a container to lock the control into memory once it is instantiated). The IClassFactory2 interface built upon IClassFactory by adding three new methods: GetLicInfo, RequestLicKey, and CreateInstanceLic. IClassFactory2::GetLicInfo retrieves a structure that specifies the licensing capabilities for an ActiveX control. IClassFactory2::RequestLicKey retrieves a license key. IClassFactory2::CreateInstanceLic instantiates the ActiveX control using the license key (which was retrieved by calling IClassFactory2::RequestLicKey).

If you've created your control with the Microsoft Foundation Class Library (MFC), the IClassFactory2 interface is wrapped by the COleObjectFactory class. In addition to handling licensing issues, COleObjectFactory handles object registration as well.

This article provides general information about design-time and run-time licensing for ActiveX Controls. In addition, it provides specific details about run-time licensing for Internet Explorer 4.0. The samples in this article are based on a sample MFC control. For more information about non-MFC controls, refer to the OLE documentation that is part of the Microsoft Platform SDK.

Design-Time Licensing

Design-time licensing is verified by ActiveX control containers such as Microsoft Visual Basic, Microsoft Access, and Microsoft Visual InterDev. These containers verify that a control is licensed to the developer by calling IClassFactory2::CreateInstanceLic and passing a NULL value for the bstrKey parameter. The corresponding code found in the control's implementation of CreateInstanceLic verifies that the control is licensed. In most cases, this is done by checking for a license (.lic) file and examining the contents of the first line that appears in this file (the contents of this line are sometimes called the license key).

Implementing Design-Time Licensing with MFC

In the sample control, which was created with MFC, the IClassFactory2 interface's functions are wrapped by the COleObjectFactory class. The implementation of the COleObjectFactory::IClassFactory2 interface is found in the MFC Olefact.cpp source file. The following excerpt from this file shows the MFC implementation of the IClassFactory2::CreateInstanceLic method.

STDMETHODIMP COleObjectFactory::XClassFactory::CreateInstanceLic(
    LPUNKNOWN pUnkOuter, LPUNKNOWN /* pUnkReserved */, REFIID riid,
    BSTR bstrKey, LPVOID* ppvObject)
{
    METHOD_PROLOGUE_EX(COleObjectFactory, ClassFactory)
    ASSERT_VALID(pThis);

    *ppvObject = NULL;

    if (((bstrKey != NULL) && !pThis->VerifyLicenseKey(bstrKey)) ||
        ((bstrKey == NULL) && !pThis->IsLicenseValid()))
        return CLASS_E_NOTLICENSED;

    // outer objects must ask for IUnknown only
    ASSERT(pUnkOuter == NULL || riid == IID_IUnknown);

    // attempt to create the object
    CCmdTarget* pTarget = NULL;
    SCODE sc = E_OUTOFMEMORY;
    TRY
    {
        // attempt to create the object
        pTarget = pThis->OnCreateObject();
        if (pTarget != NULL)
        {
            // check for aggregation on object not supporting it
            sc = CLASS_E_NOAGGREGATION;
            if (pUnkOuter == NULL || pTarget->m_xInnerUnknown != 0)
            {
                // create aggregates used by the object
                pTarget->m_pOuterUnknown = pUnkOuter;
                sc = E_OUTOFMEMORY;
                if (pTarget->OnCreateAggregates())
                    sc = S_OK;
            }
        }
    }
    END_TRY

    // finish creation
    if (sc == S_OK)
    {
        DWORD dwRef = 1;
        if (pUnkOuter != NULL)
        {
            // return inner unknown instead of IUnknown
            *ppvObject = &pTarget->m_xInnerUnknown;
        }
        else
        {
            // query for requested interface
            sc = pTarget->InternalQueryInterface(&riid, ppvObject);
            if (sc == S_OK)
            {
                dwRef = pTarget->InternalRelease();
                ASSERT(dwRef != 0);
            }
        }
        if (dwRef != 1)
            TRACE1("Warning: object created with reference of %ld\n", dwRef);
    }

    // cleanup in case of errors
    if (sc != S_OK)
        delete pTarget;

    return sc;
}

When the license key (bstrKey) is NULL, this function calls the COleObjectFactory::IsLicenseValid member function to determine whether a valid design-time license exists for the control. The implementation of COleObjectFactory::IsLicenseValid is also found in the MFC Olefact.cpp source file. The following excerpt from this file shows the MFC implementation of this function.

BOOL COleObjectFactory::IsLicenseValid()
{
    if (!m_bLicenseChecked)
    {
        m_bLicenseValid = (BYTE)VerifyUserLicense();
        m_bLicenseChecked = TRUE;
    }
    return m_bLicenseValid;
}

Initially, the member variable, m_bLicenseChecked, is false. As a result, the COleObjectFactory::VerifyUserLicense member function is called. This function is one of three COleObjectFactory functions that are usually overridden by the ActiveX control developer. In the case of the sample control, this function was overridden and implemented in the Timectrl.cpp module. It was implemented as follows:

BOOL CTimeCtrl::CTimeCtrlFactory::VerifyUserLicense()
{
    return AfxVerifyLicFile(AfxGetInstanceHandle(), _szLicFileName,
        _szLicString);
}

The AfxVerifyLicFile function returns a Boolean value if the license file identified by the _szLicFileName parameter exists and if the first line of this file contains the license string (or key) specified by the _szLicString parameter. These parameters are static variables that appear at the beginning of the Timectrl.cpp file.

// Licensing strings

static const TCHAR BASED_CODE _szLicFileName[] = _T("time.lic");

static const WCHAR BASED_CODE _szLicString[] =
    L"Copyright (c) 1996 ";

Note that the sample control uses the default implementation of COleObjectFactory::VerifyUserLicense, which was generated by the ControlWizard. It is possible that a control developer may want to implement a more secure licensing scheme. For example, a developer might want to embed the license key at a location other than the first line in the licensing file. In this case, the implementation of VerifyUserLicense would not call AfxVerifyLicFile; instead, it would contain code that parsed the file as required to extract the embedded key.

Run-Time Licensing

Run-time licensing is similar for containers such as Microsoft Visual Basic or Microsoft Access; however, as stated in the introduction, run-time licensing is handled differently by Internet Explorer 4.0. This section will first describe run-time licensing for general containers (such as Visual Basic or Microsoft Access) and will conclude with a description of run-time licensing for Internet Explorer 4.0.

General Run-Time Licensing Considerations

There are two points in time when run-time licensing is an issue for a container such as Microsoft Visual Basic: the first is during the creation of the application's executable file; the second is immediately prior to running the application. In both instances, the container calls one or more of the IClassFactory2 methods found in the control in order to determine whether a valid license exists.

Embedding the License Key

When you build an executable file with Visual Basic for an application that uses ActiveX Controls, Visual Basic attempts to embed copies of each control's license keys within the executable file. The container retrieves a license key by calling the IClassFactory2::GetLicInfo method found in each control. This function returns the address of a structure, LICINFO, whose members specify whether a run-time key is available and whether a full machine license exists.

typedef struct tagLICINFO
    {
    ULONG cbLicInfo;
    BOOL  fRuntimeKeyAvail;
    BOOL  fLicVerified;
    } LICINFO;

If Visual Basic determines that the container does support run-time licensing (that is, if the LICINFO.fRuntimeKeyAvail member was set to TRUE), it then calls the IClassFactory2::RequestLicKey method to retrieve a copy of the key, which it then embeds in the executable file.

Using the embedded key

At run time, a container calls IClassFactory2::CreateInstanceLic for each control that is part of the application or Web page that it is displaying. Unlike the earlier call to CreateInstanceLic (at design time, when the container passed a NULL value as the fourth parameter, bstrKey), the container passes a valid string that contains the embedded license key. This allows the control to run on any user's computer (whether or not they are licensed) as long as the embedded key matches the key found in the control.

Embedding the License Key with MFC

In the sample ActiveX control, the IClassFactory2 interface's functions are wrapped by the COleObjectFactory class. The implementation of the COleObjectFactory::IClassFactory2 interface is found in the MFC Olefact.cpp source file. The following excerpt from this file shows the MFC implementation of the IClassFactory2::GetLicInfo method.

STDMETHODIMP COleObjectFactory::XClassFactory::GetLicInfo(
    LPLICINFO pLicInfo)
{
    METHOD_PROLOGUE_EX(COleObjectFactory, ClassFactory)
    ASSERT_VALID(pThis);

    BSTR bstr = NULL;
    pLicInfo->fLicVerified = pThis->IsLicenseValid();
    pLicInfo->fRuntimeKeyAvail = pThis->GetLicenseKey(0, &bstr);
    if (bstr != NULL)
        SysFreeString(bstr);

    return S_OK;
}

COleObjectFactory::GetLicInfo calls two other member functions: COleObjectFactory::IsLicenseValid and COleObjectFactory::GetLicenseKey. The first function is part of the class definition found in Olefact.cpp; the second function was overridden by the sample control and appears in the Timectl.cpp file. The code for COleObjectFactory::IsLicenseValid appears as follows:

BOOL COleObjectFactory::IsLicenseValid()
{
    if (!m_bLicenseChecked)
    {
        m_bLicenseValid = (BYTE)VerifyUserLicense();
        m_bLicenseChecked = TRUE;
    }
    return m_bLicenseValid;
}

The COleObjectFactory::IsLicenseValid member function, in turn, calls COleObjectFactory::VerifyUserLicense, another function that was overridden by the control. (The Implementing Design-Time Licensing with MFC section contains a listing for VerifyUserLicense.) In the sample control, this function simply checks the first line of the .lic file and verifies that it contains the specified license key.

After the sample control calls COleObjectFactory::VerifyUserLicense, it then calls the COleObjectFactory::GetLicenseKey member function. This function is overridden by the sample control and is found in the Timectl.cpp file. The code for COleObjectFactory::GetLicenseKey appears as follows:

BOOL CTimeCtrl::CTimeCtrlFactory::GetLicenseKey(DWORD dwReserved,
    BSTR FAR* pbstrKey)
{
    if (pbstrKey == NULL)
        return FALSE;

    *pbstrKey = SysAllocString(_szLicString);
    return (*pbstrKey != NULL);
}

The CTimeCtrl::CTimeCtrlFactory function retrieves a copy of the license key, which the container can store in the appropriate location. (In the case of Visual Basic or Microsoft Access, the key is embedded in the application's executable file.)

Run-Time Licensing for Internet Explorer 4.0

Like the other containers, Internet Explorer 4.0 also calls the IClassFactory2 methods to verify that an ActiveX control is licensed. However, unlike Visual Basic or Microsoft Access, which embed ActiveX Controls within the binary code of their application's executable files, Internet Explorer 4.0 uses a different model. This unique model is a necessary response to the following factors:

  • The openness of the browser (the ability to view the HTML source file).
  • Client/server issues imposed by the Internet (or corporate intranet).

Because any Internet Explorer 4.0 user can view the HTML source code for a given Web page, and because an ActiveX control is copied to a user's computer before it is displayed, a level of indirection is required to "hide" the license for an ActiveX control from the user. This prevents an Internet Explorer 4.0 user from pirating and reusing a control that they have not purchased. Microsoft addresses these run-time licensing issues with a new file called the license package file (or LPK file).

The License Package File

A license package is stored as a license package (.lpk) file. From a very simplistic perspective, this file can be thought of as an array of ActiveX control CLSIDs and license keys. The file has the following format.
Element | Description
.lpk header | This header identifies the file type: "LPK License Package"
Copyright text or other legal statement | "Legalese" to dissuade casual copying of .lpk files
LPK version GUID | In plain-text on a line by itself. This GUID is used to mark the beginning of the real license package data; it is also used to identify the LPK file format version.
Uuencoded(Base64) license package
struct {
    UUID uuidLPKFile;   // unique per LPK
    DWORD dwLicenses;   // number of licenses in the file
    LICENSEPAIR aLicenses[]; // array of license pairs
} LICENSEPACKAGE;

struct {
    CLSID clsid;        // clsid of object
    DWORD cchLic;       // number of characters in the license
    WCHAR ach[];        // license (saved as UNICODE characters)
} LICENSEPAIR;

The ActiveX License Manager is a component of Internet Explorer 4.0 found in the Licmgr10.dll file. This component parses an .lpk file and extracts the license key for each corresponding CLSID in the file.

At run time, when Internet Explorer 4.0 is rendering an HTML page that contains ActiveX Controls, the License Manager calls the IClassFactory2::CreateInstanceLic method, passing the license key that it extracted from the .lpk file in order to verify that a control is licensed. If the license key matches the control's license, the control will be rendered on the page.

Embedding a License Package in an HTML file

An HTML author can embed a license package in any HTML page by using the OBJECT tag, a CLSID that identifies the object as a license package, and a PARAM tag that specifies the relative location of the license package file with respect to the HTML page.

<OBJECT CLASSID="clsid:5220CB21-C88D-11cf-B347-00AA00A28331">
    <PARAM NAME="LPKPath" VALUE="time.lpk">
</OBJECT>

The contents of the .lpk file are parsed by the license manager object. Only one .lpk file can be included in a given HTML page. (If more than one .lpk file is included in a page, Internet Explorer 4.0 ignores all but the first.)

Appendix A - Generating a License Package with LPK_TOOL

The Internet Client SDK ships with an executable file named Lpk_Tool.exe that you can use to generate license package files. This file is copied to the \bin subdirectory found in the SDK.

The application is actually simple to use. When you start the application, it displays a combo box that lists all of the ActiveX Controls currently running on your computer.

Combo box showing all ActiveX Controls running on your computer

The next step requires that you choose the controls that you want to display on a given HTML page. You choose a control by highlighting it in the combo box and then clicking the Add button.

Combo box with selected control

Once you've selected all the controls that you want to display on a given page, you can create and save the actual .lpk file by clicking the Save & Exit button. (This causes the application to display the File Save dialog box, which allows you to specify the path and file name.)

Appendix B - Authoring Tools

HTML authoring tools will make it easy to create HTML pages with embedded ActiveX Controls. Such tools should be responsible for creating the .lpk license package for licensed controls used on a page or on a Web site. Since there may be a one-to-many mapping between LPKs and HTMs, this may be more difficult for page-based authoring tools as opposed to Web-based authoring tools (for example, Microsoft FrontPage™).

Clearly, tool support is necessary for Notepad HTML authors as well. The solution is a simple GUI tool that lists all controls that are installed on a computer with design-time licenses. The tool allows a user to create an .lpk license package by selecting which controls should be included in the package. A second tool could parse HTML pages and create an .lpk file for all the controls that require licensing.


Signing Code with Microsoft Authenticode Technology

This document explains how to digitally sign files with Microsoft(r) Authenticode(tm) technology included in the Microsoft Internet Client SDK. These digital signatures associate a software vendor's name and unique public key with a file, thus assuring some level of accountability.

Currently, Authenticode allows software vendors to sign:

  • .exe files
  • .cab files
  • .ocx and .dll files
  • .ctl files

Important Release Information

Introduction to Code Signing

Signing and Checking Code with Authenticode

Glossary

Appendix A: Required Files

Appendix B: The X.509 Certificate

Appendix C: Suggested Reading


Important Release Information

This release of Authenticode version 2.0 is coupled with Microsoft Internet Explorer version 4.0, and contains changes and enhancements over the previous version that was released with Internet Explorer version 3.02 UPD.

The version of Authenticode that was released with Internet Explorer 3.02 UPD added several new important code-signing features that improve on the initial implementation of Authenticode. Both the code-signing tools and browsers were updated with a new infrastructure that provides for these new features. The two most important features are:

  • The addition of a verifiable signature timestamp. When a software publisher's certificate expires, it is impossible to determine if the software was signed during the valid period of the certificate without incorporation of a verifiable signature timestamp. Authenticode version 2.0 incorporates timestamping support in both the signing and verification tools. In addition, VeriSign will be supporting a verifiable timestamping service for Authenticode signing purposes.
  • Inclusion of certificates in the certificate authority (CA) verification hierarchy which expire on June 30, 1997. Earlier versions of Internet Explorer will be unable to verify Authenticode signatures after that date. Internet Explorer version 3.02 UPD and future versions resolve this by eliminating these 'short lived' certificates. Signatures on certificates issued by VeriSign will properly verify until expiration of the VeriSign root certificate.

This release (with Internet Explorer 4.0) contains the same infrastructure and features as the Internet Explorer 3.02 UPD release. However, to provide a more consistent user interface, many of the command line option flags have been re-named or changed, and a few have been added.

As a result of these Authenticode improvements, the following steps need to be taken:

  • Software publishers need to re-sign their code using the Authenticode version 2.0 tools for Internet Explorer 3.02 UPD or Internet Explorer 4.0 in order for users to be able to verify their signed files after June 30, 1997.
  • Users need to upgrade to Internet Explorer version 3.02 UPD or Internet Explorer 4.0 in order to verify signed files after June 30, 1997.

Note that once files are re-signed, users of Internet Explorer versions earlier than 3.02 UPD will not be able to verify the re-signed files unless they upgrade to version 3.02 UPD or version 4.0. But after July 1, 1997, users of Internet Explorer versions earlier than 3.02 UPD will not be able to verify any signed files, whether the files have been re-signed with the new tools or not. It is clearly in the users' best interest to upgrade to Internet Explorer 3.02 UPD or version 4.0 to be able to continue to verify signed files. So software publishers should be able to re-sign their code using the new tools with confidence that users will be able to verify the files.

Additionally, by using the VeriSign service to timestamp the new signatures, software publishers gain the added benefit that the digital signatures will not need to be re-signed when their own software publishing certificate expires.


Introduction to Code Signing

One of the larger questions facing the software industry is this: How can users trust code that is published on the Internet? Currently, most Web pages contain only static information, but soon they will be filled with controls and applications that are downloaded and run locally, on the user's computer.

Packaged software uses branding and trusted sales outlets to assure users of its integrity, but these are not available when code is transmitted on the Internet. Additionally, there is no guarantee that the code hasn't been altered while being downloaded. Browsers typically exhibit a warning message explaining the possible dangers of downloading data, but do nothing to actually see whether the code is what it claims to be. A more active approach must be taken to make the Internet a reliable medium for distributing software.

Ensuring Integrity and Authenticity

There are two issues that must be addressed to make the Internet a reliable source for software:
Ensuring authenticity | Assures users that they know where the code came from.
Ensuring integrity | Verifies that the code hasn't been tampered with since its publication.

Microsoft's solution to these issues is Authenticode coupled with an infrastructure of trusted entities. A discussion of the infrastructure is included in the explanation of certification authorities later in this section. Authenticode, which is based on industry standards, allows developers to include information about themselves and their code with their programs through the use of digital signatures.

While Authenticode itself cannot guarantee that signed code is safe to run, Authenticode is the mechanism by which users can be informed of whether the software publisher is participating in the infrastructure of trusted entities. Thus, Authenticode serves the needs of both software publishers and users who rely upon the Internet for the downloading of software.

Digital Signatures

Use digital signatures when you want to distribute data, and you want to assure recipients that it does indeed come from you. Signing data does not alter it; it simply generates a digital signature string you can bundle with the data.

Digital signatures are created using a public-key signature algorithm such as the RSA public-key cipher. A public-key algorithm actually uses two different keys: the public key and the private key (called a key pair). The private key is known only to its owner, while the public key can be available to anyone. Public-key algorithms are designed so that if one key is used for encryption, the other is necessary for decryption. Furthermore, the decryption key cannot be reasonably calculated from the encryption key. In digital signatures, the private key generates the signature, and the corresponding public key validates it.

In practice, public-key algorithms are often too inefficient for signing long documents. To save time, digital signature protocols use a cryptographic digest, which is a one-way hash of the document. The hash is signed instead of the document itself. Both the hashing and digital signature algorithms are agreed upon beforehand. Here is a summary of the process:

  1. A one-way hash of the document is produced.
  2. The hash is encrypted with the private key, thereby signing the document.
  3. The document and the signed hash are transmitted.
  4. The recipient produces a one-way hash of the document.
  5. Using the digital signature algorithm, the recipient decrypts the signed hash with the sender's public key.

If the signed hash matches the recipient's hash, the signature is valid and the document is intact.

When software (code) is associated with a publisher's unique signature, distributing software on the Internet is no longer an anonymous activity. Digital signatures assure accountability, just as a manufacturer's brand name does on packaged software. If an organization or individual wants to use the Internet to distribute software, they should be willing to take responsibility for that software. This is based on the premise that accountability is a deterrent to the distribution of harmful code.

Digital Certificates

A certificate is a set of data that completely identifies an entity, and is issued by a certification authority (CA) only after that authority has verified the entity's identity. The data set includes the entity's public cryptographic key. When the sender of a message signs the message with its private key, the recipient of the message can use the sender's public key (retrieved from the certificate either sent with the message or possibly available elsewhere in the directory service) to verify the sender's identity.

Certificate Store Technology

In order to perform a code signing operation, both private key and signer identification information must be supplied. The digital certificate used in the signature usually supplies the signer identification information, however. Thus, the private key must be supplied through some other means. Additionally, the signature must include the certificate chain for the cryptographic service provider (CSP), up to a root certificate trusted by the user, in order for the signed file to be authenticated. So in all, there are several items that need to be provided in order to generate a digital signature.

Microsoft has developed a certificate store technology to reduce the above complexity. Using this technology, when a user enrolls to obtain a certificate, they specify the private key information, the CSP information, and the certificate store name for the certificate. The certificate will then be stored in the certificate store and be associated with the other items. When the user wants to sign a document, they only need to identify the certificate in the certificate store. The code signing tool will retrieve the certificate, the private key, and the certificate chain for the CSP, all based on the specified certificate.

Using Microsoft's certificate store technology, only one certificate is necessary to perform a digital code signing operation. This relieves users from having to manage private key and CSP information.

Digital Certification

One of the primary goals of a digital certificate is to confirm that the public key contained in a certificate is, in fact, the public key belonging to the person or entity to whom the certificate is issued. For example, a Certification authority (CA) might digitally sign a special message (the certificate information) containing the name of a user, Alice, and her public key in such a way that anyone can verify that the certificate information message was signed by no one other than the CA and thereby convey trust in Alice's public key.

The typical implementation of digital certification involves a signature algorithm for signing the certificate. The process goes something like this:

  1. Alice sends a certification request containing her name and her public key to a CA.
  2. The CA creates a special message (m) from Alice's request, which constitutes most of the data in the certificate. The CA signs the message with its private key, obtaining a separate signature (sig) in the process. Then the CA returns the message m and the signature sig to Alice; the two parts together form a certificate.
  3. Alice sends the certificate to Bob to convey trust in her public key.
  4. Bob verifies the signature sig using the CA's public key. If the signature is verified, he accepts Alice's public key.

As with any digital signature, anyone can verify, at any time, that the certificate was signed by the CA, without access to any secret information. Bob needs only to get a copy of the CA's certificate in order to access the CA's public key.

A certificate is valid only for the period of time specified by the CA that issued it. The certificate contains information about its beginning and expiration dates. The CA can also revoke any certificate it has issued and maintains a list of revoked certificates. This list is called a certificate revocation list (CRL), and is published by the CA so that anyone can determine the validity of any given certificate.

Certification Authorities

Certification authorities (CAs) are trustworthy persons or organizations that issue certificates to applicants whose identity has in some way been verified by the CA. Certificates are verified through a hierarchy of these CAs. Each certificate is linked to the certificate of the CA that signed it. By following this hierarchy, or verification path, to a known, trusted CA, you can be assured that a certificate is valid. An example of this is illustrated in the following diagram.

Sample certification hierarchy

In this example, Netwerks' certificate is certified by CA1, while Bob's is certified by CA3. Netwerks knows CA1's public key. CA2 has a certificate signed by CA1, so Netwerks can verify the CA2 certificate. The root also has a certificate signed by CA1. CA3 (Bob's CA) has a certificate signed by the root. By moving up the verification chain to a common point (in this case, the root), Netwerks can verify Bob's certificate.

Duties of Certification Authorities

Certification authorities have two main duties:

  • Publishing the criteria for granting certificates.
  • Granting certificates to applicants that meet the published criteria.

Other duties might include:

  • Managing certificates (for example, enrolling, renewing, and revoking them).
  • Storing root keys.
  • Verifying evidence submitted by applicants.
  • Providing tools for enrollment.
  • Accepting the liability associated with these responsibilities.

Obtaining Certification

To obtain a certificate from a CA, a software publisher must meet the criteria for either a commercial or an individual publishing certificate and submit these credentials to either a CA or a Local Registration Authority (LRA). The criteria discussed below have been proposed by Microsoft. Note that standards bodies, such as the World Wide Web Consortium (W3C), are reviewing these criteria and they are subject to change. A description of the overall process of obtaining a certificate for code signing ends this section of the document.

Criteria for Commercial Certification

Applicants for a commercial software publishing certificate must meet the following criteria:
Identification | Applicants must submit their name, address, and other material that proves their identity as a corporate representative. Proof of identify requires either personal presence or registered credentials.
The Pledge | Applicants must pledge that they will not distribute software that they know, or should have known, contains viruses or would otherwise harm a user's computer or code.
Dun & Bradstreet Rating | Applicants must achieve a level of financial standing as indicated by a D-U-N-S number (which indicates a company's financial stability) and any additional information provided by this service. This rating identifies the applicant as a corporation that is still in business. (Other financial rating services are being investigated.) Corporations that do not have a D-U-N-S number at the time of application (usually because of recent incorporation) can apply for one and expect a response in less than two weeks.

It is recommended that applicants generate and store their private key using a dedicated hardware solution. For example, this can be a magnetic stripe card, a plastic key with an embedded ROM chip (called a ROM key), or a smart card. For more information about storing keys, see Section 8.7 of Bruce Schneier's book, Applied Cryptography.

How do large software publishers determine who should apply for certificates and who should sign code? The answers depend on how the software publisher wants to control distribution of their software on the Internet.

In a centralized approach, where the company wants total control of what code is published, there may be only one certificate, and strict guidelines for releasing code through one source. In a decentralized approach, software publishers might allow each division, or even smaller groups or individuals within the company, to sign their own code using the corporate name. The point is that the software publisher must decide who can apply for a certificate and sign code, and who takes responsibility for any code signed using certificates that bear the corporate name.

Using the Dun & Bradstreet rating as a criterion draws a line between "commercial" and "individual" developers. The intended distinction is between commercial persons or entities (that is, sole proprietors, partnerships, corporations, or other organizations that develop software as a business) and noncommercial persons or entities (that is, individuals or nonprofit corporations).

Criteria for Individual Certification

Applicants for an individual software publishing certificate must meet the following criteria:
Identification | Applicants must submit their name, address, and other material that will be checked against an independent consumer database to validate their credentials.
The Pledge | Applicants must pledge that they cannot and will not distribute software that they know, or should have known, contains viruses or would otherwise maliciously harm the user's computer or code.

The value of an individual software publishing certificate is in the information it provides to users so they can decide whether or not to download the code. Knowing who authored the code, and that the bits have not been altered from the time the code was signed to the present, is reassuring information. Additionally, a browser could be used to access a publisher's Web pages so the user can obtain detailed information about the signed code, the author, and the certificate authority. After learning about this code and the author, the user might decide to run the code, or all future code, coming from this particular individual.

The Application Process

These are the steps to apply for and grant a certificate:

  1. Apply for a software publishing certificate.

    A software publisher's request for certification is sent to the local registration agency (LRA). (In a simpler model, it is sent to the CA.) It is expected that CAs and LRAs will have Web sites that walk the applicant through the application process. Applicants will be able to look at the entire policy and practices statements of the CA or LRA. The utilities an applicant needs to generate signatures, such as Microsoft Authenticode, should also be available.

    The applicant must generate a key pair using either hardware or software encryption technology. The public key is sent to the LRA during the application process. For individuals, all of the necessary information can be transferred online. For commercial publishers, because of the identity requirements, proof of identification must be sent by mail or courier.

  2. Verify the applicant's credentials.

    Depending on the contract between the CA and the LRA, these companies will examine the evidence to verify an applicant's credentials. To do this, they may employ external contractors such as Dun & Bradstreet.

  3. Generate and issue the software publisher X.509 certificate.

    After the CA has decided that the applicant meets the policy criteria, it generates a Software Publisher Certificate (SPC). The SPC contains multiple certificates conforming to the industry standard X.509 certificate format with Version 3 extensions. The SPC is distributed in a digital signature with the publisher's software file to identify the publisher and provide the publisher's public key. The digital signature is also used by the receiver of the file to verify that the file has not been modified since it was signed.

    The SPC is stored by the CA for reference, and a copy is returned to the applicant via electronic mail.

    The publisher should review the contents of the certificate and verify that the public key works with the private key. After accepting the certificate, the publisher should include a copy in all published software signed with the private key.

    Commercial developers can expect a response to their application in less than two weeks. While there is no limit to the number of certificates commercial software publishers can obtain, it is up to the publisher to determine who gets a certificate, and how code is signed and distributed.

  4. Distribute signed software.

    The publisher can now begin signing and distributing software on the Internet. Publishers use utility programs to sign the software they intend to publish. The utility programs use the private key to generate a digital signature on a digest of the binary file and create a signature file containing the signed content of a PKCS #7 signed-data object. (For more information about PKCS #7, see the RSA specification listed in Suggested Reading.) The PKCS #7 signed-data object also contains a copy of the SPC. For portable executable (PE) image format files, the PKCS #7 signature file contents are stored in the binary file itself, in an additional section.


Signing and Checking Code with Authenticode

This section demonstrates how to sign code by creating digital signatures and associating them with files using Authenticode, which is provided with the Internet Client SDK. Creating a fully verifiable certificate might assume the existence of a complex hierarchy of CAs. A root certificate and a root private key are provided for testing purposes only. Independent software vendors must obtain a certificate from GTE, VeriSign, Inc., or another CA for signing code that will be distributed to the public.

Authenticode consists of programs to digitally sign files and programs to check that the files were, indeed, successfully signed. The programs are:

  • MakeCert, which creates a test X.509 certificate.
  • Cert2SPC, which creates a test SPC.
  • SignCode, which signs a file.
  • ChkTrust, which checks the validity of the file.
  • MakeCTL, which creates a certificate trust list.
  • CertMgr, which manages certificates, certificate trust lists, and certificate revocation lists.
  • SetReg, which sets registry keys that control the certificate verification process.

All user input to these programs is now case insensitive. Additionally, separate options now exist for the key-pair name and the private-key file.

MakeCert

Use the MakeCert test program to generate a test X.509 certificate. MakeCert performs the following tasks:

  1. Creates a public/private key pair for digital signatures and associates it with a name that you choose.
  2. Associates the key pair with a publisher's name that you choose.
  3. Creates an X.509 certificate, signed by the test root key or one you specify, that binds your name to the public part of the key pair. The certificate is output to a file, a system certificate store, or both.

The syntax for invoking MakeCert is:

MakeCert [basic options | extended options] outputFile

MakeCert's options consist of basic options and extended options. Basic options are the most commonly used ones to create a certificate. Extended options provide more flexibility. The options for MakeCert are also divided into three functional groups:

  1. Options applicable to SPC-file, private-key, and certificate store technology.
  2. Options specific to SPC-file and private-key technology only.
  3. Options specific to certificate store technology only.

Options in Groups 2 and 3 cannot be mixed, with the exception of the –ic option.

MakeCert Group 1 Options

The following Group 1 options may be applied in all cases.

Options are sorted alphabetically in the left (IE 4.0) column, and the corresponding IE 3.02 options are given in the middle column. IE 3.02 options that have no direct IE 4.0 equivalent will say either "N/A" or "(See -option)" in the IE 4.0 column, and are sorted alphabetically into the order of IE 4.0 options. For example, the IE 3.02 -C option follows the IE 4.0 -b option.
For IE 4.0 | For IE 3.02 UPD | Description
-a algorithm | -a | For IE3.02: Indicates the SHA1 hash algorithm should be used. MD5 is the default For IE4.0: The hash algorithm. Must be set to either SHA1 or MD5. The default is MD5.
-b dateStart | -B:dateStart | The date the certificate first becomes valid. The default is when the certificate is created. The format of dateStart is mm/dd/yyyy.
(See -$) | -C | The certificate will be used by commercial software publishers.
(See -$) | -C:f | The certificate will be used by commercial software publishers who have met the minimum financial criteria.
-cy certificateTypes | -t:types | For IE3.02: The certificate type. This parameter can be E for end-entity, C for certificate authority, or both. For IE4.0: The certificate type. Can be end, authority, or both. End stands for end-entity and authority stands for certificate authority.
-d displayName | -d:displayName | The display name of the subject.
-e dateEnd | -E:dateEnd | The date when the validity period ends. The default is the year 2039.
-eku OID1,OID2... | N/A | Inserts a list of one or more comma-separated enhanced key usage Object Identifiers (OIDs) into the certificate. For example, -eku 1.3.6.1.5.5.7.3.2 inserts the client authentication OID. See wincrypt.h in CryptoAPI 2.0 for definitions of allowable OIDs.
N/A | -g | Creates a glue certificate (IE 3.02 only.)
-h numChildren | -h:numChildren | The maximum height of the tree below this certificate.
(See -$) | -I | The certificate will be used by individual software publishers.
-l policyLink | -l:policyLink | A link to SPC agency policy information (for example, a URL).
-m nMonths | -D:nMonths | The duration of the validity period.
(See -a) | -m | The MD5 hash algorithm should be used. This is the default.
-n name | -n:name | The name for the publisher's certificate. This name must conform to the X.500 standard. The simplest method is to use "CN=MyName" format. For example: -n "CN=Test".
-nscp | -N | The Netscape client authentication extension should be included.
-r | -r | Creates a self-signed certificate.
-sc subjectCertFile -U:subjectCertFile | The certificate file name with the existing subject public key to be used.
-sk subjectKey | -u:subjectKey | The location of the subject's key container which holds the private key. If a key container does not exist, one is created. If neither of the -sk or -sv options are used, the JoeSoft key container is created and used by default.
-sky subjectKeySpec N/A The subject's key specification, which must be one of three possible values:

1. Signature, which stands for AT_SIGNATURE key specification.

2. Exchange, which stands for AT_KEYEXCHANGE key specification.

3. An integer, such as 3.

See notes on key specifications below.

-sp subjectProviderName | N/A | CryptoAPI provider for subject. The default is the user's provider. See the CryptoAPI 2.0 documentation for details on CryptoAPI providers.
-sr subjectCertStoreLocation | N/A | The registry location of the subject's certificate store. Must be either localMachine or currentUser. currentUser is the default. currentUser means that the certificate store is under the registry key HKEY_CURRENT_USER and localMachine means HKEY_LOCAL_MACHINE.
-ss subjectCertStoreName | N/A | The name of the subject's certificate store where the generated certificate will be stored.
-sv subjectKeyFile | -k:subjectKeyFile | The location of the subject's .pvk file. If neither of the -sk or -sv options are used, the JoeSoft key container is created and used by default.
-sy nSubjectProviderType | N/A | CryptoAPI provider type for subject. The default is PROV_RSA_FULL. See the CryptoAPI 2.0 documentation for details on CryptoAPI provider types.
-# serialNumber | -#:serialNumber | The serial number of the certificate. The maximum value is 2^31. The default is a value generated by the program that is guaranteed to be unique.
-$ certificateAuthority | (See -I, -C, -C:f) | The type of Certificate Authority. Must be set to either commercial or individual. Commercial means the certificate will be used by commercial software publishers. Individual means the certificate will be used by individual software publishers.
-? -? Displays the basic options.
-! N/A Displays the extended options.

If the -sky key specification option is used in IE 4.0, the specification must match the key specification indicated by the private key file or private key container. If the key specification option is not used, then the key specification indicated by the private key file or private key container will be used. If there is more than one key specification in the key container, MakeCert will first attempt to use the AT_SIGNATURE key specification. If that fails, MakeCert will then try to use AT_KEYEXCHANGE. Since most users have either an AT_SIGNATURE key or AT_KEYEXCHANGE key, this option does not need to be used in most cases.

MakeCert SPC-file and Private-key Technology Options

The following are Group 2 options for SPC-file and private-key technology only:
For IE 4.0 | For IE 3.02 UPD | Description
-ic issuerCertFile | -i:issuerCertFile | The location of the issuer's certificate.
-ik issuerKey | -s issuerKeyFile | The location of the issuer's key container. The default is the test root key.
-iky IssuerKeySpec -K:keySpec For IE3.02: The issuer's key specification. This parameter can be either S for a signature key (this is the default) or E for a key-exchange key.

For IE4.0: The issuer's key specification, which must be one of three possible values:

1. Signature, which stands for AT_SIGNATURE key specification.

2. Exchange, which stands for AT_KEYEXCHANGE key specification.

3. An integer, such as 3.

See notes on key specifications below.

-ip IssuerProviderName | -x:providerName | CryptoAPI provider for issuer. The default is the user's provider. See the CryptoAPI 2.0 documentation for details on CryptoAPI providers.
-iv issuerKeyFile | -s:issuerKeyFile | The issuer's private-key file. The default is the test root.
-iy nIssuerProviderType | -y:nProviderType | CryptoAPI provider type for issuer. The default is PROV_RSA_FULL. See the CryptoAPI 2.0 documentation for details on CryptoAPI provider types.

If the -iky key specification option is used in IE 4.0, the specification must match the key specification indicated by the private key file or private key container. If the key specification option is not used, then the key specification indicated by the private key file or private key container will be used. If there is more than one key specification in the key container, MakeCert will first attempt to use the AT_SIGNATURE key specification. If that fails, MakeCert will then try to use AT_KEYEXCHANGE. Since most users have either an AT_SIGNATURE key or AT_KEYEXCHANGE key, this option does not need to be used in most cases.

MakeCert Certificate Store Technology Options

The following are Group 3 options for certificate store technology only:
For IE 4.0 | For IE 3.02 UPD | Description
-ic issuerCertFile | NA | The file containing the issuer's certificate. For IE 4.0 only, MakeCert will search in the certificate store for a certificate with an exact match.
-in issuerNameString | N/A | The common name of the issuer's certificate. MakeCert will search in the certificate store for a certificate whose common name includes issuerNameString.
-ir IssuerCertStoreLocation | N/A | The registry location of the issuer's certificate store. Must be either localMachine or currentUser. currentUser is the default. currentUser means that the certificate store is under the registry key HKEY_CURRENT_USER and localMachine means HKEY_LOCAL_MACHINE.
-is issuerCertStoreName | N/A | The issuer's certificate store that includes the issuer's certificate and its associated private key information. If there is more than one certificate in the store, the user must uniquely identify it with the -ic or -in option. If the certificate in the certificate store is not uniquely identified, MakeCert will fail.

MakeCert Internet Explorer 3.02 UPD Example

The following is an example that creates a certificate using the Internet Explorer 3.02 UPD options:

MakeCert -k:c:\KeyStore\MyKey.pvk -n:CN=MySoftwareCompany Cert.cer

In this example, a certificate file called Cert.cer is created. The public part of the key pair called MyKey is bound to the publisher, MySoftwareCompany.

6.4 MakeCert Internet Explorer 4.0 Examples

The following are a series of examples for creating certificates with MakeCert using the Internet Explorer 4.0 options.

Make a certificate issued by the default test root. Save the certificate to a file.

MakeCert myNew.cer

Make a certificate issued by the default test root. Save it to a certificate store.

MakeCert -ss myNewStore

Make a certificate issued by the default test root. Create a .pvk file and output the certificate to both a store and a file.

MakeCert -sv myNew.pvk -ss myNewStore myNew.cer

Make a certificate issued by the default test root. Create a key container and output the certificate to both a store and a file.

MakeCert -sk myNewKey -ss myNewStore myNew.cer

Make a certificate using the default test root. Save the certificate to a store. Then make another certificate using the newly created certificate. Save the second certficate to another store.

MakeCert -sk myNewKey -ss myNewStore
MakeCert -is myNewStore -ss anotherStore

Make a certificate using the default test root. Save the certificate to my store. Then make another certificate using the newly created certificate. Since there is more than one certificate in my store, identify the first certificate using its common name.

MakeCert -sk myNewKey -n "CN=XXZZYY" -ss my
MakeCert -is my -in "XXZZYY" -ss anotherStore

Make a certificate using the default test root. Save the certificate to my store and to a file. Then make another certificate using the newly created myNew certificate. Since there is more than one certificate in my store, uniquely identify the first certificate using the certificate filename.

MakeCert -sk myNewKey -n "CN=XXZZYY" -ss my myNew.cer
MakeCert -is my -ic myNew.cer -ss anotherStore

Create a self-signed certificate named myNewRoot using the default test root. Then use SignCode with the certificate to sign a file.

MakeCert -sk myNewRootKey -r -ss myNewRoot
SignCode -s myNewRoot myControl.exe

Create a self-signed certificate named myNewRoot using the default test root. Save myNewRoot into a system certificate store ca . Then make a certificate from myNewRoot and sign some code with the certificate.

When SignCode uses a certificate in a store for signing, it will also build a certificate chain from the signing certificate to a root. All of the certificates in the certificate chain must be in any of the my, ca, spc, or root certificate stores. In this case, myNewRoot is in the ca certificate store.

MakeCert -sk myNewRootKey -r -ss ca myNewRoot.cer
MakeCert -is ca -ic myNewRoot.cer -ss myNewSign
SignCode -s myNewSign myControl.exe

Cert2SPC

After you have generated a certificate, you can create an SPC (Software Publisher Certificate) with the Cert2SPC program. This program wraps multiple X.509 certificates into a PKCS #7 signed-data object. Note that this program is for test purposes only. A valid SPC is obtained from a CA.

The syntax for Cert2SPC is:

Cert2SPC cert1.cer cert2.cer. . .certN.cer output.spc

where:

  • cert1. . .certN are the names of the X.509 certificates to include in the SPC.
  • output is the name of the PKCS #7 object containing the X.509 certificates.

Here is an example:

Cert2SPC MyCert.cer MyCert.spc

This wraps an X.509 certificate, MyCert.cer into a PKCS #7 SPC called MyCert.spc.

SignCode

The final step is to actually sign a file using the SignCode program. This program will:

  1. Create a cryptographic digest of the file.
  2. Sign the digest with your private key.
  3. Copy the X.509 certificates from the SPC into a new PKCS #7 signed-data object. The PKCS #7 object contains the serial numbers and issuers of the certificates used to create the signature, the certificates, and the signed digest information.
  4. Embed the object into the file.
  5. Optionally, it can add a timestamp to the file. A timestamp should always be added when signing a file. However, SignCode also has the ability to add a timestamp to a previously signed file subject to some restrictions (see the examples that follow the options table).

The syntax for signing is:

SignCode [options] [fileName]

where fileName is the name of the output file (Internet Explorer 4.0 only) and options are as described below.

The options for SignCode are divided into three groups:

  1. Basic options applicable in all cases
  2. Options specific to SPC-file and private-key technology
  3. Options specific to certificate store technology

    Options in groups 2 and 3 cannot be mixed in the same operation.

SignCode Basic Options

The following basic options (Group 1) may be applied in all cases:

Options are sorted alphabetically in the left (IE 4.0) column, and the corresponding IE 3.02 options are given in the middle column. IE 3.02 options that have no direct IE 4.0 equivalent will say either "N/A" or "(See -option)" in the IE 4.0 column, and are sorted alphabetically into the order of IE 4.0 options. For example, the IE 3.02 -individual option follows the IE 4.0 -i option.
For IE 4.0 | For IE 3.02 UPD | Description
-a algorithm | -sha, -md5 | The hashing algorithm to use. Must be set to either SHA1 or MD5. The default is MD5.
(See -$) -commercial | The code being signed was created by a commercial software publisher. The action SignCode will take depends upon both this setting and the SPC actually used. See table following these options for possible actions.
-i opusInfo | -info opusInfo | A location, such as a URL, for obtaining information about your program.
(See -$) | -individual | The code being signed was created by an individual software publisher. The action SignCode will take depends upon both this setting and the SPC actually used. See table following these options for possible actions.
-j dllName | N/A | The name of a DLL that returns an array of authenticated attributes for signing files. The user can specify more than one DLL by repeating the -j option. For example: -j dll1 -j dll2 -j dll3
-jp dllParam N/A A parameter to be passed for the preceding DLL. For example: -j DLL1 -jp DLL1Param.

Only one parameter is allowed per DLL. For a list of pre-defined DLLs and their parameters, see "-j and -jp SignCode Options".

(See -a) | -md5 | Specifies that the MD5 hashing algorithm should be used. This is the default hashing algorithm.
-n opusName | -name opusName | The friendly name given your program.
N/A | -nocerts | Specifies that no X.509 certificates should be embedded in the PKCS #7 signed-data object. In this case, the relevant certificates must already be stored on the client computer. In IE 4.0, all certificates will always be included in the message.
N/A | -prog filename | The output file name in IE 3.02 UPD. In IE 4.0 the file name is always placed as the last item on the command line.
(See -a) | -sha | Specifies that the SHA1 hashing algorithm should be used.
-t httpAddress | timestamper | Indicates that the file is to be timestamped. A URL specifying an address of a timestamping server must be provided (see -x for timestamping previously signed files).
-tr times | N/A | The maximum number of timestamping trials. The default is 1. SignCode will repeat the timestamping operation until it is successful or the limit on number of trials is reached.
-tw seconds | N/A | The number of seconds between each timestamping trial. The default is 0.
-x | -nosigning | Used when just timestamping a previously signed file. No signing is performed. Must be used in conjunction with -t.
-$ signingAuthority | -commercial, -individual | The type of publisher that created the code being signed. Must be either commercial or individual. The action SignCode will take depends upon both this setting and the signing certificate actually used. See table following these options for possible actions.
-? -? Displays all of the options.

The signature type (commercial or individual) used by SignCode depends on the signing option and certificate used. In some cases SignCode will fail the requested operation if there is a conflict. The possible signing actions are as follows:
Certificate Type | -$ commercial | -$ individual | no option
Commercial Only | commercial | fail | commercial
Individual Only | fail | individual | individual
Both | commercial | individual | commercial
None | fail | individual | individual

SignCode SPC-file and Private-key Technology Options

The following options are specific to SPC-file and private-key technology (group 2) only:

When upgrading from IE3.02 UPD to IE 4.0, the -pvk option should be mapped to either the -k option or to the -v option, depending on the storage location of the private key. If the private key is stored in a key container, use the -k option. If the private key is stored in a private key file, use the -v option.
For IE 4.0 | For IE 3.02 UPD | Description
-k privateKey | -pvk privateKey | The key container that holds the private key. Cannot be used with -v.
-ky keySpec N/A Indicates the key specification, which must be one of three possible values:

1. Signature, which stands for AT_SIGNATURE key specification.

2. Exchange, which stands for AT_KEYEXCHANGE key specification.

3. An integer, such as 3.

See notes on key specifications below.

-p providerName | -provider providerName | The CryptoAPI provider to use. The default is the user's default provider. (For more details, see the CryptoAPI 2.0 documentation.)
-spc credentials | -spc credentials | The file that contains the credentials. This is usually an .spc file.
-v privateKeyFile | -pvk privateKeyFile | The file containing the private key of the publisher (subject). This is usually a .pvk file. The -v option for IE 4.0 cannot be used with -k.
-y providerType | -providerType providerType | The CryptoAPI provider type to use. The default is PROV_RSA_FULL. (For more details, see the CryptoAPI 2.0 documentation.)

If the -ky key specification option is used in IE 4.0, the specification must match the key specification indicated by the private key file or private key container. If the key specification option is not used, then the key specification indicated by the private key file or private key container will be used. If there is more than one key specification in the key container, SignCode will first attempt to use the AT_SIGNATURE key specification. If that fails, SignCode will then try to use AT_KEYEXCHANGE. Since most users have either an AT_SIGNATURE key or AT_KEYEXCHANGE key, this option does not need to be used in most cases.

SignCode Certificate Technology Options

The following options are specific to certificate technology (group 3) only:
For IE 4.0 | For IE 3.02 UPD | Description
-c certificateFile | N/A | The file containing the encoded signing certificate. SignCode will search the certificate store for a matching certificate. Either -c or -cn can be used to identify the signing certificate in the certificate store to use.
-cn commonNameString | N/A | The common name of the encoded signing certificate. SignCode will search in the certificate store for a certificate whose common name includes commonNameString. If the common name of more than one certificate in the certificate store includes commonNameString, SignCode will fail and issue an error.
-r storeLocation | N/A | The registry location of the certificate store. Must be set to either currentUser or localMachine. The default is currentUser. CurrentUser means the certificate store is under the HKEY_CURRENT_USER registry key and localMachine means it is under HKEY_LOCAL_MACHINE.
-s storeName | N/A | The certificate store that includes the signing certificate and associated private key. The default is my. If there is more than one certificate in the store, the desired certificate must be uniquely identified using the -c or -cn options, or SignCode will fail.
-sp storePolicy N/A The certificate store policy. Must be set to either chain or spcStore. The default is spcStore. chain means to add to the signature all of the certificates in the chain located in the following certificate stores: spc, my, ca, and root.

spcStore means to add to the signature all of the certificates in the chain located in the following certificate stores: spc, my, ca, and root. Stop when the certificate is located in the spc store. This is the default.

The spc store is under the HKEY_LOCAL_MACHINE registry key. The my, ca, and root stores are all under the HKEY_CURRENT_USER registry key.

Once the file has been signed (assuming you have a valid certificate) and timestamped, the file can be distributed to your customers. Note that certificates generated with the test programs MakeCert and Cert2SPC are NOT valid for signing code that will be distributed to the public. Independent software vendors must obtain a certificate from GTE, VeriSign, Inc., or another CA for signing code that will be distributed to the public.

SignCode Examples for Internet Explorer 3.02 UPD

Here are two examples of how to sign and timestamp a file using the Internet Explorer 3.02 UPD options. The first uses a private-key name MyKey and the second uses a private-key file My.pvk:

SignCode -prog MyControl.exe -spc Cert.spc -pvk MyKey -timeStamper http://timestamp.verisign.com/scripts/timstamp.dll
SignCode -prog MyControl.exe -spc Cert.spc -pvk My.pvk -timeStamper http://timestamp.verisign.com/scripts/timstamp.dll

In both cases a PKCS #7 object, Cert.spc, is embedded into the digest of the file, MyControl.exe. In the first example, the digest is signed with the private key of the MyKey key pair, and a timestamp is added. In the second example, the digest is signed with the private-key file My.pvk, and a timestamp is added.

SignCode Examples for Internet Explorer 4.0

Here are two examples of how to sign and timestamp a file using the Internet Explorer 4.0 options. The first uses the MyKey key container and the second uses a private-key file My.pvk:

SignCode -spc Cert.spc -k MyKey -n "My control" -i http://www.my.com -$ commercial -t http://timestamp.verisign.com/scripts/timstamp.dll MyControl.exe

SignCode -spc Cert.spc -v My.pvk -n "My control" -i http://www.my.com -$ commercial -t http://timestamp.verisign.com/scripts/timstamp.dll MyControl.exe

In both cases a PKCS #7 object, Cert.spc, is embedded into the digest of the file, MyControl.exe. In the first example, the digest is signed with the private key of the MyKey key container, and a timestamp is added. In the second example, the digest is signed with the private-key file My.pvk, and a timestamp is added.

The following example shows how to sign and timestamp a file using a certificate in my store (the default certificate store). The certificate's common name is MyCert, and the program being signed has the friendly name My Control.

SignCode -cn "myCert" -n "My control" -i http://www.my.com -$ commercial -t http://timestamp.verisign.com/scripts/timstamp.dll MyControl.exe

The following example shows how to sign and timestamp a file using a certificate in theCert store. There is assumed to be only one certificate in the certificate store, so it is not necessary to identify the certificate. The certificate store is at the default registry location, HKEY_CURRENT_USER. My control is the friendly name for the program being signed.

SignCode -s "theCert" -n "My control" -i http://www.my.com -$ commercial -t http://timestamp.verisign.com/scripts/timstamp.dll MyControl.exe

Adding a Timestamp to a Previously Signed File

A timestamp should always be added when a file is signed. However, if a file was signed without a timestamp, a timestamp can be added to that file. Here is an example of how to add a timestamp to a previously signed file:

For Internet Explorer 3.02 UPD:

SignCode -prog MyControl.exe -nosigning -timeStamper http://timestamp.verisign.com/scripts/timstamp.dll

For Internet Explorer 4.0:

SignCode -t http://timestamp.verisign.com/scripts/timstamp.dll -x MyControl.exe

Notice the -x option is used in conjunction with the -t option to indicate that the file to be timestamped has already been signed. If you try to timestamp a file previously signed by Internet Explorer 3.0 tools, the timestamp may fail with an error saying you must re-sign. If you timestamp a previously signed file using the SignCode tool for Internet Explorer version 3.02 UPD or 4.0, it should successfully add the timestamp. The signing and timestamping concurrent operation should always work, even if the file was previously signed by Internet Explorer 3.0 tools, because if there is an old signature it is replaced.

-j and -jp SignCode Options

The -j and –jp options are provided for more advanced users of SignCode and Internet Explorer 4.0 only. The user can write a Dynamic Link Library (DLL) with predefined entry points that returns an array of authenticated or unauthenticated attributes for signing files. SignCode will take the following actions using these options:

  1. Load the DLL specified by the –j option.
  2. Pass the parameters specified by –jp option to the DLL.
  3. Retrieve an array of authenticated or unauthenticated attributes for signing Java files from the DLL.
  4. Add the attributes to the signature.

There are four pre-defined entry points for the DLL:

HRESULT WINAPI InitAttr(

LPWSTR pInitString); // IN: the initialization string

HRESULT WINAPI GetAttr(

PCRYPT_ATTRIBUTES *ppsAuthenticated, // OUT: Authenticated attributes added to signature

PCRYPT_ATTRIBUTES *ppsUnauthenticated); // OUT: Unauthenticated attributes added to signature

HRESULT WINAPI ReleaseAttr(

PCRYPT_ATTRIBUTES psAuthenticated, // OUT: Authenticated attributes to be released

PCRYPT_ATTRIBUTES psUnauthenticated); // OUT: Unauthenticated attributes to be released

HRESULT WINAPI ExitAttr( );

SignCode calls these entry points in the following order:

  1. InitAttr(initString); // (parameter specified by the –jp option)
  2. GetAttr();
  3. ExitAttr();
  4. InitAttr(initString); // (parameter specified by the –jp option)
  5. ReleaseAttr();
  6. ExitAttr();

ChkTrust

The ChkTrust program checks the validity of a signed file by:

  1. Extracting the PKCS #7 signed-data object.
  2. Extracting the X.509 certificates from the PKCS #7 signed-data object.
  3. Computing a new hash of the file and comparing it with the signed hash in the PKCS #7 object.

If the hashes agree, ChkTrust then verifies that the signer's X.509 certificate is traceable back to the root certificate and that the correct root key was used.

If all these steps are successful, it means that the file has not been tampered with, and that the vendor who signed the file was authenticated by the root authority.

Here is the syntax:

ChkTrust [options] signedFile

where the options are:
For IE 4.0 | For IE 3.02 UPD | Description
N/A | -c | Indicates that the signed file is a cabinet file.
N/A | -i | Indicates that the signed file is a PE image file. Includes .exe, .dll, and .ocx files.
-q | -n | Do not display user interface.
-? N/A Displays all of the options.

In the IE 4.0 version, ChkTrust can distinguish file types automatically, so the -c and -i switches used in the IE 3.02 UPD version are obsolete. Here is an example for the IE 3.02 UPD version:

ChkTrust -i MyProgram.exe

A successful response would be:

Result: 0

Here is an example for the IE 4.0 version:

ChkTrust MyProgram.exe

Note that versions of ChkTrust that are older than the one released with Internet Explorer 3.02 UPD cannot validate files signed with this release of Authenticode.

MakeCTL

The MakeCTL utility creates a certificate trust list (CTL) and outputs the encoded CTL to a file. MakeCTL is supported in IE 4.0 only.

The input to MakeCTL is an array of certificate stores. MakeCTL will build a CTL which includes the SHA1 hash of all of the certificates in the certificate stores. A certificate store can be one of the following:

  • A serialized store file
  • A PKCS#7
  • An encoded certificate file
  • A system store

The syntax for invoking MakeCTL is:

MakeCTL [-u subjectUsageID] [-s [-r registryLocation]] store1 [-s [-r registryLocation]] store2 ... [-s [-r registryLocation]] storeN output.ctl

where:

  • store1 store2 ... .storeN are the names of the certificate stores to make the certificate trust list for.
  • output.ctl is the name of the output file containing the certificate trust list.

The options are as follows:
-u subjectUsageID | The CTL subject usage identifier. The default identifier is szOID_TRUSTED_CODESIGNING_CA_LIST, which specifies that the CTL consists of root CAs for code signing.
-s | Indicates that the certificate store is a system store.
-r registryLocation The registry location of the system certificate store. Meaningful only when –s is set. Must be set to either currentUser or localMachine. The default setting is currentUser.

currentUser means that the certificate store is under the registry key HKEY_CURRENT_USER. localMachine means that the certificate store is under the registry key HKEY_LOCAL_MACHINE.

-? Lists command syntax and options.

An encoded CTL file must be signed before using. CTL files can be signed using the SignCode utility. Once the CTL file is signed, it can be moved to the trust system store by CertMgr. CertMgr can also move the CTL's signer certificate to the root store. If the subject usage identifier of the CTL is szOID_TRUSTED_CODESIGNING_CA_LIST (the default), all the files signed by certificates in the CTL will be trusted by ChkTrust and Authenticode.

The following example generates a CTL that includes all the certificates in a system certificate store named root:

MakeCTL -s root output.ctl

The following example generates a CTL that includes an array of certificates:

MakeCTL one.cer two.cer three.cer output.ctl

CertMgr

The CertMgr utility is a replacement for DumpCert and adds many new capabilities. CertMgr can manage certificates, certificate trust lists (CTLs), and certificate revocation lists (CRLs). The functionality of CertMgr has three parts:

  1. Display certificates, CTLs, and CRLs.
  2. Add certificates, CTLs, and CRLs from one certificate store to another.
  3. Delete certificates, CTLs, and CRLs from a certificate store.

Each functional part of CertMgr has its own syntax and options group. The syntax and options for different functional parts cannot be mixed.

CertMgr works with two kinds of certificate stores, StoreFile and system store. A StoreFile can be one of the following kinds of files:

  • Encoded CTL/CRL/certificate file (could be base64 encoded)
  • PKCS#7 file
  • Signed document
  • Serialized StoreFile

It is not necessary to specify the type of the StoreFile. CertMgr can determine the StoreFile type and take the appropriate actions.

A system store is located in the user's registry. The user can refer to a system store by providing just its name. It is not necessary to specify the certificate store provider type. Depending on the type of the StoreFile or system store, CertMgr will choose the corresponding store provider type.

Displaying Certificates, CTLs, and CRLs with CertMgr

The syntax for invoking CertMgr to display certificates, CTLs, and CRLs is as follows:

CertMgr [options] [-s [–r registryLocation]] StoreName

StoreName is the name of the certificate store containing the items to display. The store can be a serialized store (StoreFile) or a system store (in the registry). By default, CertMgr will display all the certificates, CTLs, or CRLs in the store, unless otherwise specified in the options.

The options are:
-v | Verbose mode. Displays detailed information about certificates, CTLs, and CRLs. The default is to display brief information.
-c | Only display certificates.
-CTL | Only display CTLs.
-CRL | Only display CRLs.
-e encodingType | Certificate encoding type.
-y storeProviderType | Store provider type.
-f dwFlags | Store open flag. This is the dwFlags parameter passed to CertOpenStore. The default value is CERT_SYSTEM_STORE_CURRENT_USER. Meaningful only if –y is set. For more information see the CryptoAPI 2.0 documentation.
-s | Indicates the store is a system store. If this option is not set, the store is a StoreFile.
-r registryLocation | Identifies the registry location of the system certificate store. Meaningful only when –s is set. Must be set to either currentUser or localMachine. CurrentUser is the default. currentUser means that the certificate store is under the registry key HKEY_CURRENT_USER, localMachine means HKEY_LOCAL_MACHINE.
-? Displays all the options.

Adding Certificates, CTLs, and CRLs with CertMgr

The syntax for invoking CertMgr to add certificates, CTLs, and CRLs is as follows:

CertMgr –add [options] [-s [–r registryLocation]] SourceStore [-s [–r registryLocation]] DestinationStore

SourceStore is the source certificate store that contains the existing certificates, CTLs, and CRLs. DestinationStore is the destination certificate store to which the certificates, CTLs, and CRLs will be added to. The destination store will be saved as a serialized store, unless the –7 option is used, which saves the store as a PKCS#7. Note that the –7 option can not be used when the destination store is a system store.

CertMgr can add either certificates, CTLs, or CRLs. If there is more than one item in one of these categories, the user has three options:

  1. Use the –all option to add all the items in the category.
  2. Use the –n and -sha1 options to uniquely identify the item.
  3. Do not specify any options, and CertMgr will prompt the user with a list of items to add. The user responds by entering the index of the item to add.

This functionality of CertMgr can be used to copy and move certificates, CTLs, and CRLs. See the examples following the options.

The options are:
-c | Add certificates.
-CTL | Add CTLs.
-CRL | Add CRLs.
-all | Add all entries.
-7 | Save the destination store as a PKCS#7.
-e encodingType | Certificate encoding type.
-y storeProviderType | Store provider type.
-f dwFlags | Store open flag. This is the dwFlags parameter passed to CertOpenStore. The default value is CERT_SYSTEM_STORE_CURRENT_USER. Meaningful only if –y is set. For more information see the CryptoAPI 2.0 documentation.
-n commonNameString | The common name of the certificate to add. Can be used only with certificates.
-s | Indicates the store is a system store. If this option is not set, the store is a StoreFile.
-sha1 sha1Hash | The SHA1 hash of the certificate, CTL, or CRL to add.
-r registryLocation | Identifies the registry location of the system certificate store. Meaningful only when –s is set. Must be set to either currentUser or localMachine. CurrentUser is the default. currentUser means that the certificate store is under the registry key HKEY_CURRENT_USER, localMachine means HKEY_LOCAL_MACHINE.
-? Displays all the options.

Deleting Certificates, CTLs, and CRLs with CertMgr

The syntax for invoking CertMgr to delete certificates, CTLs, and CRLs is as follows:

CertMgr –del [options] [-s [–r registryLocation]] SourceStore [-s [–r registryLocation] DestinationStore]

SourceStore is the source certificate store that contains the existing certificates, CTLs, and CRLs. DestinationStore is the destination certificate store which will contain copies of the remaining certificates, CTLs, and CRLs after the specified items have been deleted. If DestinationStore is not specified, SourceStore will also serve as the destination store (it will be modified.) The destination store will be saved as a serialized store, unless –7 option is used, which saves the store as a PKCS#7. Note that the –7 option cannot be used when the destination store is a system store.

CertMgr can delete either certificates, CTLs, or CRLs. If there is more than one item in one of these categories, the user has three options:

  1. Use the –all option to delete all the items in the category.
  2. Use the –n and -sha1 options to uniquely identify the item.
  3. Do not specify any options, and CertMgr will prompt the user with a list of items to delete. The user responds by entering the index of the item to delete.

The options are:
-c | Delete certificates.
-CTL | Delete CTLs.
-CRL | Delete CRLs.
-all | Delete all entries.
-7 | Save the destination store as a PKCS#7.
-n commonNameString | The common name of the certificate to delete. Can be used only with certificates.
-sha1 sha1Hash | The SHA1 hash of the certificate, CTL, or CRL to delete.
-e encodingType | Certificate encoding type.
-y storeProviderType | Store provider type.
-f dwFlags | Store open flag. This is the dwFlags parameter passed to CertOpenStore. The default value is CERT_SYSTEM_STORE_CURRENT_USER. Meaningful only if –y is set. For more information see the CryptoAPI 2.0 documentation.
-s | Indicates the store is a system store. If this option is not set, the store is a StoreFile.
-r registryLocation | Identifies the registry location of the system certificate store. Meaningful only when –s is set. Must be set to either currentUser or localMachine. CurrentUser is the default. currentUser means that the certificate store is under the registry key HKEY_CURRENT_USER, localMachine means HKEY_LOCAL_MACHINE.
-? Displays all the options.

CertMgr Examples

View certificates, CRLs, and CTLs from a file called myFile.ext. myFile can be one of the following files: an encoded CTL, CRL, or certificate file (could be base64 encoded), a PKCS#7 file, an SPC file, a signed document, or a serialized storeFile.

CertMgr myFile.ext

View a system store called my store:

CertMgr -s my

Add all the certificates in a file called myFile.ext to a new file, called newFile.ext:

CertMgr -add -all -c myFile.ext newFile.ext

Copy all the certificates in a system store to a file called newMy.ext:

CertMgr -add -all -c -s my newMy.ext

Move a certificate with common name myCert in my system store to a file called newCert.cer:

CertMgr -add -c -n myCert -s my newCert.cer

Delete all the certificates in my system store:

CertMgr -del -all -c  -s my

Delete all the CTLs in my system store and save the resulting store to a file called newStore.str:

CertMgr -del -all -ctl -s my newStore.str

The following example demonstrates how to build a CTL and move it to the root store:

// 1. Make a self-signed certificate called sign.cer.
MakeCert -sv sign.pvk -r -n "CN=THIS IS A TEST OF MAKECTL" sign.cer
// Make an SPC file using Cert2SPC.
Cert2SPC sign.cer sign.spc

// 2. Make another self-signed certificate called test.cer.
MakeCert -sv test.pvk -r -n "CN=THIS IS MY TEST CERT" test.cer
// Make an SPC file using Cert2SPC.
Cert2SPC test.cer test.spc

// 3. Make a test.ctl from test.cer.
MakeCTL test.cer test.ctl

// 4. Sign test.ctl with the sign.pvk and sign.spc made in step 1.
SignCode -v sign.pvk -spc sign.spc test.ctl

// 5. Move test.ctl to the trust system store.
CertMgr -add -ctl test.ctl -s trust

// 6. Move sign.cer to the root system store.
CertMgr -add -c sign.cer -s root

// 7. Sign something (test.exe) with test.pvk, and test.spc.
SignCode -v test.pvk -spc test.spc test.exe

// 8. Since test.cer is in the test.ctl, ChkTrust will succeed.
ChkTrust test.exe

SetReg

The SetReg utility is used to set the value of the registry keys controlling the behavior of the Authenticode certificate verification process. These keys are called the Software Publishing State Keys. When SetReg has completed the requested action, the current state of the Software Publishing State Keys is displayed.

The syntax for invoking SetReg is:

SetReg [options] [Choice # <True | False>]

The options can be one of the following values:
For IE 4.0 | For IE 3.02 UPD | Description
-q | -q | Suppresses the display of the Software Publishing State Key values after SetReg has completed the requested action. The values are displayed by default.
-? -h Lists command syntax and options.

Choice # must be one of the following values (valid for both IE 3.02 and IE 4.0, except 9 and 10, which are for IE 4.0 only):
1 | Trust the Test Root. Causes the test root to be a trusted root if set to TRUE. This is equivalent to running "regedit wvtston.reg" in Internet Explorer 3.x. The default is FALSE. Any code signed with a test root will not verify unless this flag is turned TRUE.
2 | Use expiration date on certificates. Causes the certificate expiration date to be checked if set to TRUE. To ignore expiration dates, set this flag to FALSE. The default is TRUE.
3 | Check the revocation list. Causes the revocation check to be performed if set to TRUE. To bypass revocation check, set this flag to FALSE. The default is FALSE in Internet Explorer 3.x and TRUE in Internet Explorer 4.x.
4 | Offline revocation server OK (Individual). Allows off-line approval for individual certificates if set to TRUE. The default is FALSE.
5 | Offline revocation server OK (Commercial). Allows off-line approval for commercial certificates if set to TRUE. The default is FALSE.
6 | Java offline revocation server OK (Individual). Allows off-line approval for individual certificates and does not display the user interface for bad certificates if set to TRUE. The default is FALSE.
7 | Java offline revocation server OK (Commercial). Allows off-line approval for commercial certificates and does not display the user interface for bad certificates if set to TRUE. The default is FALSE.
8 | Invalidate version 1 signed objects. Invalidates version 1 signed objects if set to TRUE. The default is FALSE.
9 | Perform the revocation check on the timestamp server's certificate if set to TRUE. The default is FALSE. For IE 4.0 only.
10 | Only allow downloads from publishers that are contained in the Personal Trust Database if set to TRUE. The default is FALSE. For IE 4.0 only.

Here is an example:

SetReg 1 TRUE

This turns on the capability to trust the test root.


Glossary

X.509 Certificate

A cryptographic certificate that contains a vendor's unique name and the vendor's public key.

PKCS #7 Signed Data

A Public Key Certificate Standard #7 (PKCS #7) signed-data object encapsulates the information used to sign a file. Typically, it includes the signer's certificate and the root certificate.

Certification Authority (CA)

A trusted entity that makes a statement (represented by an X.509 certificate) about the authenticity of another certificate.

Cryptographic Digest

A one-way hash function that takes a variable-length input string and converts it to a fixed-length output string (called a cryptographic digest.) This fixed-length output string is probabilistically unique for every different input string and thus can act as a fingerprint of a file. When a file with a cryptographic digest is downloaded, the receiver recomputes the digest. If the output string matches the digest contained in the file, the receiver has proof that the received file was not tampered with and is identical to the file originally sent.

Local Registration Authority (LRA)

An intermediary between a publisher and a CA. The LRA can, for example, verify a publisher's credentials before sending them to the CA.

Portable Executable (PE) Image

The standard Win32(r) executable format.

Software Publishing Certificate (SPC)

A PKCS #7 signed-data object containing X.509 certificates.

Trust Provider

The software that decides whether or not a given file is trusted. This decision is based on the certificate associated with the file.

WIN_CERTIFICATE

A Win32 data structure that contains either a PKCS #7 signed-data object or an X.509 certificate.


Appendix A: Required Files

To use Authenticode, a set of client files, publishing tools, and a signing DLL are required.

Client files are installed in the \WinNT\System32 directory on Microsoft(r) Windows NT(r), or in the \Windows\System directory on Microsoft Windows(r) 95, and include the following:

  • Wintrust.dll
  • Softpub.dll
  • Mssip32.dll
  • Vsrevoke.dll
  • Crypt32.dll

Publishing tools and the signing DLL are installed in the \INetSDK\Bin directory and include the following:

  • MakeCert.exe (creates an X.509 certificate for testing purposes only)
  • Cert2SPC.exe (creates an SPC for testing purposes only)
  • SignCode.exe (signs and timestamps a file)
  • ChkTrust.exe (checks the validity of the file)
  • MakeCTL.exe (creates a certificate trust list)
  • CertMgr.exe (manages certificates, CTLs, and CRLs)
  • SetReg.exe (sets registry keys controlling certificate verification)
  • Signer.dll (performs signing)

Appendix B: The X.509 Certificate

The X.509 protocols include a structure for public-key certificates. A CA assigns a unique name to each user and issues a signed certificate containing this name and the user's public key. The following diagram shows an X.509 certificate.

These are the meanings for each field.
Field | Meaning
Version | Identifies the certificate format.
Serial Number | A value unique to the CA.
Algorithm Identifier | Identifies the algorithm used to sign the certificate, together with any necessary parameters.
Issuer | The name of the CA.
Period of Validity | A pair of dates. The certificate is valid during the time period between the two.
Subject | The name of the user.
Subject's Public Key | Contains the public key algorithm name, any necessary parameters, and the public key.
Signature | The CA's signature.

Appendix C: Suggested Reading

The topic of digital signing is discussed more fully in the following documents.

CCITT, Recommendation X.509, The Directory-Authentication Framework, Consultation Committee, International Telephone and Telegraph, International Telecommunications Union, Geneva, 1989.

Microsoft Cryptographic Service Provider Programmer's Guide, Microsoft, 1995.

Microsoft Application Programmer's Guide, Microsoft, 1995

RSA Laboratories, PKCS #7: Cryptographic Message Syntax Standard. Version 1.5, November, 1993.

Schneier, Bruce, Applied Cryptography, 2d ed. New York: John Wiley & Sons, 1996.

http://www.microsoft.com/security

http://www.rsa.com External Link


Packaging ActiveX Controls

This article introduces a data-compression technology and associated toolset that you can use to package your Microsoft® ActiveX™ control for faster, more efficient downloading over the Internet or an intranet. (This same technology and toolset can be applied to Microsoft Win32® applications, Java™ classes, and Java libraries.)

Cabinet Files

Packaging the Sample ActiveX Control

Cabarc.exe

.INF Files

Cabinet Files

For a number of years, Microsoft used cabinet (.cab) files to compress software that was distributed on disks. Originally, these files were used to minimize the number of disks shipped with packaged product; today, .cab files are used to reduce the file size and the associated download time for Web content that is found on the Internet or corporate intranet servers.

The .cab file format is a nonproprietary compression format, also known as MSZIP, that is based on the Lempel-Ziv data-compression algorithm. (At a future date, other compression formats might also be supported.)

Accessing Controls Stored in Cabinet Files

An ActiveX control is identified by the OBJECT tag in an HTML file. If the control has been stored in a .cab file, the OBJECT tag must include a CODEBASE= attribute that specifies the URL for this .cab file. The following excerpt from a sample HTML file demonstrates how the OBJECT tag and CODEBASE= attribute are used.

<OBJECT CLASSID="clsid:dcf0768D-ba7a-101a-b57a-0000c0c3ed5f" codebase="http://webserver/time.cab"
    ALIGN="CENTER" WIDTH=270 HEIGHT=26 ID="T1"><PARAM NAME="Interval" VALUE=1000>
    <PARAM NAME="Enabled" VALUE=1></OBJECT>

Microsoft Internet Explorer version 4.0 handles the OBJECT tag and the CODEBASE= attribute in the following manner:

  1. Internet Explorer 4.0 parses the OBJECT tag and searches for the CODEBASE= attribute. If the CODEBASE= attribute is absent or is preceded by a URL-to-object index server in the CodeBaseSearchPath, this index is used to retrieve the file.
  2. Internet Explorer 4.0 locates the .cab file identified by the CODEBASE= attribute.
  3. Internet Explorer 4.0 expands the files found in the .cab file.
  4. Internet Explorer 4.0 copies the expanded files to the user's computer (the default directory is \windows\occache).
  5. Internet Explorer 4.0 registers the objects and/or files that require registration.
  6. Internet Explorer 4.0 calls the Component Object Model (COM) function CoCreateInstance to create an instance of the specified object.

Signing Cabinet Files

A .cab file can be digitally signed like an ActiveX control. A digital signature provides accountability for software developers: The signature associates a software vendor's name with a given file. A signature is applied to a .cab file (or control) using the new Authenticode™ technology.

The .cab toolset assists software developers in applying digital signatures to .cab files by allowing a developer to allocate space in the .cab file for the signature.

CABinet Resource Kit

Microsoft supplies a CABinet resource kit that contains tools that a developer can use to build .cab files. You access this technology by executing a self-extracting executable, Cab-sdk.exe, that installs from the ..\inetsdk\bin subdirectory.

One of the tools installed by Cab-sdk.exe is Cabarc.exe. This command-line application lets you compress an ActiveX control and store it in a .cab file. In addition to creating .cab files, you can use Cabarc.exe to list the contents of or extract files from an existing .cab file.

For an example that demonstrates how you can use Cabarc.exe to package an ActiveX control, see Packaging the Sample ActiveX Control.

For more information about Cabarc.exe and its options, see Cabarc.exe.

Packaging Strategies for Multiple Platforms

Internet Explorer 4.0 and ActiveX Controls can run on the Apple Macintosh® and on Intel® x86 computers, as well as on any of several RISC machines. As a result, it's important that control developers create, test, and bundle their control's binaries for all platforms.

This section outlines three packaging strategies for supporting multiple platforms:

  • Multiple .cab files with the .inf saved in a separate .cab file
  • Multiple .cab files with the .inf and x86 binary bundled in a single .cab file
  • A single .cab file

Multiple .cab files with .inf in separate .cab

The first strategy requires that one .cab file contain a Windows® 95 .inf file, the second contain the x86 binary, the third contain the Apple MacIntosh binary, and so on. Using this strategy, the CODEBASE= attribute of the OBJECT tag specifies the relative path for the .cab file that contains the Windows 95 .inf file.

The primary benefit of this strategy is reduced file size at download time—only the first .cab file containing the .inf file and a second .cab file with the appropriate binary for a given platform are downloaded.

The one drawback of this strategy is that two digital certificates will appear (one certificate per .cab file).

Multiple .cab files with .inf and x86 binary bundled in single .cab

The second strategy requires that one .cab file contain both the Windows 95 .inf file and the x86 binary. The remaining .cab files contain the Apple MacIntosh and RISC binaries (one binary per .cab file). Using this strategy, the CODEBASE= attribute of the OBJECT tag will specify the relative path for the .cab file that contains the Windows 95 .inf file and the x86 binary.

This strategy is most beneficial for controls that are primarily targeted at the x86 platform. Another benefit of this strategy is reduced file size at download time.

The one drawback of this strategy is that Apple MacIntosh or RISC users will have to wait for the x86 binaries to download.

Single .cab file

The third strategy requires that a single .cab file contain all binaries.

This strategy is most beneficial for small controls that have no dependent DLLs and that are primarily targeted at an intranet.

The drawbacks of this solution are that it does not optimize for dependent DLLs that might already be on the user's computer, and this solution might not be acceptable on some intranets.

Packaging the Sample ActiveX Control

This section describes how a single .cab file was created for the sample digital clock control. This .cab file contains a Windows 95 .inf file as well as the x86 binary for the control.

This example is based on the second packaging format described in the previous section of this article. However, the sample package is incomplete: a couple of final steps, which were omitted, require that a separate .cab file be created for the Apple Macintosh binary as well as for each of the supported RISC platforms. In addition to creating the separate .cab files, the .inf file would need to be modified to point to these additional .cab files. These steps were omitted to simplify the example.

Installing the CABinet Development Kit

To package an ActiveX control, it's necessary to install Cabarc.exe in the ..inetsdk\bin subdirectory. When the kit is installed, you can begin using the tools it contains to build the necessary .cab files for your control.

Creating the .inf File

As previously stated, the sample .cab file bundles a Windows 95 .inf file with an x86 binary. The bundled .inf file contains information about the control, such as its name, its class identifier (CLSID), the name and location of required DLLs, whether the control requires registration, and so on. This .inf file has the following format:

 [version]
    signature="$CHICAGO$"
    AdvancedINF=2.0
 [Add.Code]
    time.ocx=time.ocx
    msvcrt.dll=msvcrt.dll
    mfc42.dll=mfc42.dll
    olepro32.dll=olepro32.dll
 [time.ocx]
    file-win32-x86=thiscab
    clsid={DCF0768D-BA7A-101A-B57A-0000C0C3ED5F}
    FileVersion=1,0,0,0
    RegisterServer=yes
 [msvcrt.dll]
    FileVersion=4,20,0,6164
    hook=mfc42installer
 [mfc42.dll]
    FileVersion=4,2,0,6256
    hook=mfc42installer
 [olepro32.dll]
    FileVersion=4,2,0,6068
    hook=mfc42installer
 [mfc42installer]
    file-win32-x86=http://activex.microsoft.com/controls/vc/mfc42.cab
    run=%EXTRACT_DIR%\mfc42.exe

If you're bundling the .inf file and your control's x86 binary in a single .cab file, and if your control was written with Microsoft Foundation Class Library (MFC) 4.2, you can use the previous example after you replace all occurrences of the sample control name (time.ocx) with the name of your control and replace the sample control's CLSID with the CLSID for your control.

If you're organizing your files differently, or if your control requires DLLs other than the Microsoft Visual C++® 4.2 DLLs, you'll need to modify the .inf file to accommodate your unique requirements. Note that if your control requires that a DLL be registered before the control, its corresponding entry in the .inf file must precede the control's entry in the file. Also note that the order of the source files in the command line that is passed to Cabarc.exe must match the order of the file references in the .inf file.

For more information about .inf files and the section tags found within them, see .INF Files.

Building the .cab File

Once you've installed Cabarc.exe and built the .inf file for your package, you can build the .cab file. In the case of the sample .cab file, this entailed running Cabarc.exe with the following arguments:

cabarc.exe -s 6144 N time.cab time.ocx time.inf

This command created a .cab file named Time.cab, which contains a compressed x86 binary for the sample control as well as a compressed version of the corresponding .inf file. The -s 6144 switch specifies that Cabarc.exe should allocate 6144 bytes for a digital certificate. (Code signing with digital certificates is described briefly in the next section).

For more information about Cabarc.exe and its use, see Cabarc.exe.

Signing the .cab File

The default security level for Internet Explorer 4.0 is High. When a user runs at this level, any object identified by the OBJECT tag on an HTML page must be digitally signed. Digital signatures are created using the Authenticode technology that is included in the Internet Client SDK. A digital signature associates a software vendor's name and a unique public key with a file that contains an ActiveX object (ensuring some sort of accountability on the part of the object's developer).

Before you purchase a certificate for your control's .cab file from a vendor, you can use the test certificate provided by Microsoft for verification purposes. The following steps were required to add the SDK test certificate to the sample control's .cab file:

  1. Use Setreg.exe, which is found in the \inetsdk\bin directory, to validate the test root in the trust hierarchy.
    setreg 1 true

    Setreg.exe replaces the Wvtston.reg and Wvtstoff.reg files that were shipped as part of the ActiveX SDK. Validation of the test root means that Internet Explorer 4.0 will treat a .cab file signed under the test root as though it included credentials.

  2. Use the Makecert.exe utility, which is found in the \inetsdk\bin directory, to create a key pair (the pair consists of a public and a private key).
    makecert -u:mykey -n:CN=mysoftwarecompany cert.cer

    In addition to creating the key pair, Makecert.exe associates the key pair with an X.500 distinguished name and creates an X.509 certificate that is signed by the root key (and that binds your name to the public part of the new key pair).

  3. Use the Cert2SPC.exe utility, which is found in the \inetsdk\bin directory, to generate a software publishing certificate (.spc file).
    cert2spc root.cer cert.cer cert.spc

  4. Use the Signcode.exe utility (and accompanying wizard), which is found in the \inetsdk\bin directory, to sign the .cab file.
    signcode -prog mycab.cab -spc cert.spc -pvk mykey

    If Signcode.exe is successful, it will have embedded a PKCS7 certificate in the .cab file.

If you package your control in one or more .cab files, each file must be digitally signed.

For information about X.500 distinguished names, X.509 certificates, software publishing certificates, or PKCS7 certificates, see the article titled Signing Code with Microsoft Authenticode Technology.

Cabarc.exe

Cabarc.exe is a utility that creates, lists, and extracts the contents of cabinet (.cab) files. Cabarc uses a command-line interface similar to that of popular archiving tools. Cabarc supports wildcards and recursive directory searches.

Cabarc is used as follows:

cabarc [<options>] <command> [<file list>]

Three commands are currently supported:

  • n (create a new .cab file)
  • l (list contents of an existing .cab file)
  • x (extract files from a .cab file)

Creating New .cab Files (n Command)

Cabinets are created by using the n command, followed by the name of the cabinet to create, followed by a file name list, as shown in this example:

cabarc n mycab.cab prog.c prog.h prog.exe readme.txt

This command creates the cabinet Mycab.cab, containing the files Prog.c, Prog.h, Prog.exe, and Readme.txt, in a single folder, using the default compression mode, MSZIP.

Cabarc supports wildcards in the file name list, as shown in the following example:

cabarc n mycab.cab prog.* readme.txt

Options (-p, -P, -r, -s, -i, -m, -l, +, @)

Directory names are not preserved in the cabinet (by default); only the file name component is stored. For example, the following command stores Prog.c in the cabinet:

cabarc n mycab.cab c:\source\myproj\prog.c

Path preservation (-p)

To preserve paths, use the -p option as shown in the following example:

cabarc -p n mycab.cab c:\mysource\myproj\prog.c

This command puts mysource\myproj\prog.c in the cabinet. Be aware that the c:\ prefix is still removed from the file name: Cabarc cannot store absolute paths in the cabinet nor extract such absolute paths.

Path stripping (-P)

The path stripping option, -P, is used for preserving part (but not all) of a path. The following example shows how this option can archive everything in the c:\mysource\myproj\ directory, but store only the myproj\ component of the path.

cabarc -p -P mysource\ n mycab.cab c:\mysource\myproj\prog.c

The -P option strips any strings that begin with the provided string. Wildcards are not supported by the -P option; it is a simple text match. Absolute path prefixes, such as c:\ or \, are stripped before the comparison takes place. Do not include absolute path prefixes when using the -P option.

The -P option can be reused to strip out multiple paths. Cabarc builds a list of all paths to be stripped and applies only the first one that matches. Consider the following example:

cabarc -p -P mysrc\ -P yoursrc\ n mycab.cab c:\mysrc\myproj\*.* d:\yoursrc\yourproj\*.c

The trailing slash at the end of the path is important: Entering -P mysrc instead of -P mysrc\ adds files as \myproj\file name.

Recursive directory search (-r)

Cabarc has an option, -r, for a recursive directory search. Using this option archives files in a directory and all of its subdirectories. For example, the following command example archives all files ending in .h that are in c:\msdev\include\, c:\msdev\include\sys, and c:\msdev\include\gl (assuming these directories exist on your system).

cabarc -r -p n mycab.cab c:\msdev\include\*.h

The -p option is used here to preserve the path information when the files are added to the cabinet. Without this option, only the file names would be stored.

Reserving space for digital signatures (-s)

Cabarc can reserve space in the cabinet for a code signature. This is done with the -s option, which reserves a specified amount of empty space in the cabinet. For code signatures, reserve 6144 bytes. Consider the following example for doing so:

cabarc -s 6144 n mycab.cab test.exe

The -s option does not actually write the code signature; it reserves space for it in the cabinet. Use the code signature utility to fill out the code signature.

Setting the cabinet ID (-i)

Cabinet files have a 16-bit cabinet identification field that is designed for application use. The default value of this field is zero; however, use the -i option of Cabarc to set this field to any 16-bit value, as shown in the following example:

cabarc -i 12345 n mycab.cab test.exe

Setting the compression type (-m)

Set the compression type with the -m option. The default compression type for a cabinet is MSZIP. Only MSZIP compression (-m MSZIP) and no compression (-m NONE) are supported. The following command stores files in the cabinet without compression.

cabarc -m NONE n mycab.c *.*

Creating a file list from a file (-l)

Cabarc can create a file list from a file. Cabarc compiles a list of file inputs from a text file (instead of from the command line), using the -l option, followed by the name of the file from which to take the inputs. Consider the following example:

cabarc -l filelist.txt n mycab.cab

The -l option can be reused to retrieve file lists from multiple files. Cabarc does not check for duplicate files. If the same physical file appears in multiple file lists, it gets added to the cabinet multiple times.

The -l option can be combined with file names on the command line. The files from the -l option are added first. Consider the following example:

cabarc -l filelist1.txt -l filelist2.txt n mycab.cab *.c *.h

Beginning new folders (+)

All files are put in one folder in the cabinet (by default). It is possible to instruct Cabarc to begin a new folder by using the plus sign (+) for the file to be added, as shown in the following example:

cabarc n mycab.cab test.c main.c + test.exe *.obj

This command creates the cabinet Mycab.cab with one folder containing Test.c and Main.c, and a second folder containing Test.exe and all files matching *.obj.

Generating a file listing from an existing file list (@)

Cabarc can input its list of files from a text file, instead of from the command line, by using @files ("at files"). This is done by using the @ symbol to prefix the name of the file that contains the file list. For example:

cabarc n mycab.cab @filelist.txt

The text file must list the physical file names of the files to be added, one per line. As is the case when specifying file names on the command line, the plus (+) symbol can be used as a file name to specify the beginning of a new folder. If a file name contains any embedded spaces, it must be enclosed in quotes, as shown here:

test.c
myapp.exe
"output file.exe"

The reason for requiring quotes is that each physical file name might be followed on the same line by an optional logical file name, which specifies the name under which the file will be stored in the cabinet.

test.c myapp.c
myapp.exe
"output file.exe" foobar.exe

If the logical file name contains spaces, it must also be enclosed in quotes. Note that the logical file name overrides the -p (preserve path names) and -P (strip path name) options—the file will be added to the cabinet exactly as indicated. Wildcards are allowed in the physical file name, but in this situation a logical file name is not allowed.

The @ feature can be used multiple times to retrieve file lists from multiple files. Cabarc does not check for the presence of duplicate files, so if the same physical file appears in multiple file lists, it will be added to the cabinet multiple times.

The @ feature can be combined with file names on the command line. Files are added in the order in which they are parsed on the command line. For example:

cabarc n mycab.cab @filelist1.txt *.c @filelist2.txt *.h

The @ feature is available only when creating cabinets, not when extracting or listing cabinets.

Listing Contents of Existing Files (l Command)

The list cabinet contents command, l, makes it possible to view the contents of a cabinet. Consider the following example:

cabarc l mycab.cab

Cabarc displays the Set ID in the cabinet (see the -s option for cabinet creation). Cabarc also displays the following file information: size, date, time, and attributes.

Extracting Files from Cabinet Files (x Command)

The extracting cabinets command, x, extracts files from a cabinet. The simplest use of the x command is shown in the following example, which extracts all files from the cabinet.

cabarc x mycab.cab

Alternatively, it is possible to selectively extract files by providing a list of file names or a list of file names and wildcards. Consider the following example:

cabarc x mycab.cab readme.txt *.exe *.c

Full paths (if they are present in the cabinet), by default, are not preserved upon extraction. For example, if mysrc\myproj\test.c is in the cabinet, the command cabarc x mycab.cab causes Test.c to be extracted into the current directory. To preserve file names upon extraction, the -p option must be used. This option causes any required directories to be created, if necessary.

By default, the extracted files are stored in the current directory (and its subdirectories, if -p is used). However, it is possible to specify a destination directory for the extracted files. This is accomplished by appending a directory name to the command line. The directory name must end in a backslash ( \ ). For example:

cabarc x mycab.cab c:\somedir\

cabarc x mycab.cab *.exe c:\somedir\

The extracting cabinets option considers only the file name component in the matching process; the path name is ignored. For example, cabarc x mycab.cab test.c causes mysrc\myproj\test.c to be extracted to the current directory as Test.c, as will cabarc x mycab.cab *.c (which also extracts any other files matching *.c).

.INF Files

An .inf file contains data that Microsoft Windows NT® and Windows® 95 use to load and register a bundled control (as well as any files required by that control). Each section of the .inf file is described below.

The [version] Section of the .inf File

The first section in the .inf file is the [version] section. In the sample .inf, this section has the following format:

[version]
    signature="$CHICAGO$"
    AdvancedINF=2.0

The signature= tag specifies that the .inf version is compatible with both Windows NT and Windows 95.

The AdvancedINF= tag identifies the version of Advpack.dll that Internet Explorer 4.0 must load in order to parse this .inf file. In this case, version 2.0 is required.

The [Add.Code] Section of the .inf File

The second section of the .inf file is the largest section. In addition to the section immediately following the [Add.Code] tag, this section includes the subsections that are identified by the [time.ocx], [msvcrt.dll], [mfc42.dll], and [olepro32.dll] tags.

The [Add.Code] section maps files required by a given .ocx file to subsections in the .inf file. In the sample .inf file, this section has the following format:

[Add.Code]
    time.ocx=time.ocx
    msvcrt.dll=msvcrt.dll
    mfc42.dll=mfc42.dll
    olepro32.dll=olepro32.dll

The first line in this section maps time.ocx (the file that contains the sample control) to a subsection of the same name. The second, third, and fourth lines map DLLs that are required by the control to corresponding subsections in the .inf file. (The names of the .inf subsections are not restricted to the mapped file name; these names were chosen strictly for convenience in the sample.)

Since the sample control was created with MFC 4.2, the three named DLLs are required on a user's computer before the control will successfully run.

The [time.ocx] subsection of the [Add.Code] section

The [time.ocx] subsection has the following format:

[time.ocx]
    file-win32-x86=thiscab
    clsid={DCF0768D-BA7A-101A-B57A-0000C0C3ED5F}
    FileVersion=1,0,0,0
    RegisterServer=yes
  • The first line in this subsection specifies that the x86 binary is found in the same .cab file as the .inf file.
  • The second line specifies the control's CLSID.
  • The third line specifies the control's version.
  • The fourth line specifies that Internet Explorer 4.0 should register the control (using the specified CLSID and version number).

The [msvcrt.dll] subsection of the [Add.Code] section

The [msvcrt.dll] subsection has the following format:

[msvcrt.dll]
    FileVersion=4,20,0,6164
    hook=mfc42installer

The first line specifies the file version from the fixedfileinfo section of the DLL's version resource.

Note that Windows 95 Explorer shows only a partial version number in the Properties dialog box. For example, in the case of Msvcrt.dll, the Properties dialog box will return 4.20.6164 as the version number (ignoring the third section [0] in the 4,20,0,6164 series).

The second line in this subsection, hook=mfc42installer, identifies a section in the .inf file that points to a .cab file that contains the required DLL (Msvcrt.dll).

The [mfc42.dll] subsection of the [Add.Code] section

The [mfc42.dll] subsection has the following format:

[mfc42.dll]
    FileVersion=4,2,0,6256
    hook=mfc42installer

For more information about these entries, see the description of the [msvcrt.dll] subsection.

The [olepro32] subsection of the [Add.Code] section

The [olepro32.dll] subsection has the following format:

[olepro32.dll]
    FileVersion=4,2,0,6068
    hook=mfc42installer

For more information about these entries, see the description of the [msvcrt.dll] subsection.

The [mfc42installer] Section of the .inf File

The final section in the .inf file is the [mfc42installer] section. In the sample .inf file, this section has the following format:

[mfc42installer]
    file-win32-x86=http://activex.microsoft.com/controls/vc/mfc42.cab
    run=%EXTRACT_DIR%\mfc42.exe

The file-win32-x86= tag specifies that the .cab file containing the MFC 4.2 DLLs is located at the specified site.

The run= tag specifies that the .cab file contains a self-extracting executable named Mfc42.exe.


Palette Management for ActiveX Objects

This document describes palette management for ActiveX objects, specifically ActiveX Controls and Active Document Objects. The palette management rules described herein allow multiple controls inside a form or an HTML page to display correctly, while still leaving enough flexibility to allow a control to demand palette control. In general, containers are responsible for palette management, and contained objects (for example, controls) should only realize their palettes in the background.

Palette Management for ActiveX Controls and Containers

Palette Management for Active Document Objects

Palette Management for ActiveX Controls and Containers

This section describes how ActiveX controls and containers should manage the palette.

Palette selection

Because there may be more than one control on a page or form, it is up to the container to choose a common palette, and every control should realize its palette in the background. Anything else would result in chaos as each control realized its palette while painting. The display would flicker madly and most controls would look awful.

Ultimately it is the container's responsibility to choose the common palette. Some containers may use the UI active object's palette; others may compute the palette at run-time.

Ambient palette property

The container makes its palette available to contained controls through an ambient property: DISPID_AMBIENT_PALETTE. This property is defined as part of the OLE Controls 96 specification and is implemented by Microsoft Internet Explorer. Controls can receive the value of this ambient initially by implementing IOleObject::SetClientSite or IObjectWithSite::SetSite, then querying the container client site for IDispatch, and finally calling IDispatch::Invoke on the property to obtain its value. Afterwards, controls can receive notification of changes to this property by implementing: IOleControl::OnAmbientPropertyChange().

Containers should realize the ambient palette before calling this function or IViewObject2::Draw(). Older containers that do not implement this property will typically iterate through their controls, forwarding the WM_QUERYNEWPALETTE message until a control returns TRUE (indicating it has realized a palette). Containers that implement DISPID_AMBIENT_PALETTE should never send WM_QUERYNEWPALETTE to their controls.

Palette change notification

Palette-aware controls that need to be notified of changes to the ambient palette should implement IOleControl::OnAmbientPropertyChange and handle DISPID_AMBIENT_PALETTE and DISPID_UNKNOWN. DISPID_UNKNOWN is sent when the values of more than one ambient property changes. When this happens, controls should explicitly check for a changed palette.

Older containers that do not define an ambient palette will send WM_PALETTECHANGED messages. Controls are encouraged to handle this message as well.

Note that controls can still realize a different palette (in the background). The ambient palette property is useful only if a control needs to optimize its display to the palette of the container. If this is not necessary, the control should ignore the ambient palette property.

IViewObject2::GetColorSet

All palette-aware controls should implement IViewObject2::GetColorSet if they want to specify a palette preference. Containers can use the return value to determine if a control is palette-aware. Containers can also use the color information returned to choose what palette to realize.
E_NOTIMPL | The control is not palette-aware.
S_FALSE | The control is palette-aware but does not have a palette at this time.
S_OK | The control is palette-aware and has returned its palette to the container.

It should be noted that the container does not have to call IViewObject2::GetColorSet. The container can determine its palette independently of its controls.

Realizing the palette

Older OLE guidelines allowed the UI active object to realize its own palette in the foreground. Revised guidelines disallow this practice.

Controls should realize their palette in the foreground only when receiving a WM_QUERYNEWPALETTE message. In every other situation, the control must realize its palette in the background. Containers that implement DISPID_AMBIENT_PALETTE will never send this message.

Summary:

  • Containers determine the palette.
  • Containers can choose to implement DISPID_AMBIENT_PALETTE.
  • Containers must realize the ambient palette before calling IViewObject2::Draw or IOleControl::OnAmbientPropertyChange.
  • Controls must always realize their palette in the background. The only exception is when receiving a WM_QUERYNEWPALETTE message.
  • Controls should implement IViewObject2::GetColorSet if they want to tell the container about their palette preferences.
  • Controls should implement IOleControl::OnAmbientPropertyChange if they want to be notified about changes in the ambient palette property in order to optimize their display to the container's palette.

Palette Management for Active Document Objects

The following sections describe issues related to palette management by OLE Document Objects. In general, the palette management scheme for document objects is the same as the scheme used for controls, except that document objects do not receive ambient properties from their container.

Responsibilities of the document object

Palette management for document objects is similar to palette management for controls. The following are the responsibilities of the document object:

  • Document objects should implement IViewObject2::GetColorSet if interested in notifying the container about the palette they want to display. This is exactly the same as the corresponding responsibility for controls (as previously discussed).
  • Document objects should realize their palette in the background except when handling WM_QUERYNEWPALETTE messages (forwarded from the container). Again, this is exactly the same as the corresponding responsibility for controls.

The only difference between the responsibilities of controls and document objects is that document objects do not handle the DISPID_AMBIENT_PALETTE property.

Single document case

In the most common cases, a DocObject container will activate only one DocObject at a time. In such a situation, it is acceptable that the activated DocObject should have complete control over the palette, which includes managing palette issues for any controls or content within that document.

The DocObject container in this case has nothing to do with palettes and need not be concerned with palette issues. It should leave palette handling up to the DocObject— in other words, it should forward all Windows® palette management messages on to the DocObject.

The following is example code for the window procedure of the document object host:

case WM_PALETTECHANGED:
case WM_QUERYNEWPALETTE:
{
    HWND hwnd;
    IOleWindow *pOleWindow;
    if (pDocObj && SUCCEEDED(pDocObj->QueryInterface(IID_IOleWindow,
            (LPVOID*)&pOleWindow))) {
        LRESULT lres = 0;
        if (SUCCEEDED(pOleWindow->GetWindow(&hwnd))) {
            lres = SendMessage(hwnd, uMsg, wParam, lParam);
        }
        pOleWindow->Release();
        return lres;
    }
    break;
}

Multiple DocObject case

In rare cases, a DocObject container may be able to activate more than one DocObject at a time within multiple container frames. While this user interface is discouraged at this time, it can be achieved.

However, no palette-management solution exists for this scenario because there is currently no protocol for communicating palettes between a DocObject and its container. Therefore, the container cannot create a palette suitable to all DocObjects that it has activated.

Because of this, the activated DocObject in the foreground has control over the palette and should use foreground palette rendering. Other activated DocObjects in the background use background palette rendering. The DocObject container itself does not participate in palette management at all.


Registering an ActiveX Object as the Player for a Media Type

This topic describes how to register an ActiveX™ object as the viewer or player for a particular media type (MIME). This registration is essential for Microsoft® Internet Explorer to launch the correct player when interpreting the standard HTML A HREF tag or the Netscape-introduced EMBED tag. Without this registration, it is impossible to guess what application to use to display or play an unknown media file. The registration process associates an ActiveX object with a given MIME type or file-name extension so that Internet Explorer or any other ActiveX control container can launch the correct player for files of that type. Internet Explorer first tries to find a match for the correct media type; if that is not possible, it uses the file-name extension.

What Information Needs To Be Registered

An ActiveX object has four pieces of identifying information:

  • The class identifier (CLSID) of the object. This is a globally unique identifier (GUID). The examples below use this CLSID:
    {01234567-89AB-CDEF-0123-456789ABCDEF}
  • The ProgId of the object. This is a textual program identifier. The examples use FooCorp.BarControl.
  • The MIME type for media types the object understands. Note that not all ActiveX objects are viewers or players for media types, so not every object needs to be associated with a MIME type. The examples use the MIME type application/foobar.
  • The file extension for media types the object understands. Note that not all ActiveX objects are viewers or players for media types, so not every object needs to be associated with a file extension. The examples use the extension .foo.

Step 1: Registering the CLSID for the ActiveX Object

All ActiveX/COM objects must be entered in the registry so COM (Component Object Model) knows how to instantiate the object servers from the appropriate executable file. For full details on this registration process, see the COM Specification or OLE Reference (in the Platform SDK). A simple in-proc server would enter information such as the following in the registry.

HKEY_CLASSES_ROOT\CLSID\
    {01234567-89AB-CDEF-0123-456789ABCDEF}\
        InProcServer32        DLL File location
        ProgId                FooCorp.BarControl
        ......

To see an example, run Regedit.exe and look under HKEY_CLASSES_ROOT\CLSID.

Note For an ActiveX control to act as a full-frame viewer (for HTML A HREF tags) or as an embedded viewer (for EMBED tags), it is also necessary to include the CONTROL keyword in the registry settings for the control.

HKEY_CLASSES_ROOT\CLSID\
    {01234567-89AB-CDEF-0123-456789ABCDEF}\
        Control               (value not set)

It is also possible to register an ActiveX control as a full-frame viewer so it is used as the viewer/player when following A HREF links to a particular media type. To mark an ActiveX control as capable of full-frame display, use the following registry setting:

HKEY_CLASSES_ROOT\CLSID\
    {01234567-89AB-CDEF-0123-456789ABCDEF}\
        EnableFullPane\
           .foo

Note A requirement of this EnableFullPane registration process is to register the control as the viewer for the .foo file extension as described below.

Step 2: Associating the Object's CLSID with a ProgId

To associate a given ActiveX object with data types, it is first necessary to associate the object CLSID with a ProgId (program identifier). This is explained fully in the COM Specification or OLE Reference pages (Platform SDK), and is illustrated in the following simple example for defining the new ProgId, FooCorp.BarControl.

HKEY_CLASSES_ROOT\
    FooCorp.BarControl\
        CLSID                 {01234567-89AB-CDEF-0123-456789ABCDEF}

Also under the ProgId key it is possible to register shell commands to be used to edit or open files for this ProgId, as in the following example:

HKEY_CLASSES_ROOT\FooCorp.BarControl\
    Shell\Open\
        Command               c:\Program Files\foobar.exe %1

Step 3: Associating the Object with a MIME Type

It is possible to associate an ActiveX object as a viewer for various media types. The following keys must be entered in the registry to associate a given ActiveX object with a given media type, application/foobar.

HKEY_CLASSES_ROOT\
    MIME\Database\Content Type\
        application/foobar\
            Extension         .foo
            CLSID             {01234567-89AB-CDEF-0123-456789ABCDEF}

Note Neither the Extension nor the CLSID key is necessary. One key associates the MIME type with a file extension; the other associates it with the CLSID of an ActiveX object.

Step 4: Associating the File Extension with a ProgId and Optionally with a MIME Type

If an ActiveX control container such as Microsoft Internet Explorer does not have a (server-provided) MIME type to choose a viewer for a new data type, the data file-name extension is used to find the appropriate ActiveX object to understand the new media format. If the file-name extension is associated with a MIME type, the MIME-CLSID mapping is used to pick the object. Otherwise, the extension-ProgId mapping is used. The example below shows how to associate a three-letter file extension with a MIME type and a ProgId, although the MIME association is optional.

HKEY_CLASSES_ROOT\
    .foo\
        (Default)             FooCorp.BarControl
        Content Type          application/foobar


ActiveX Control Containers

The purpose of this topic is to provide guidelines for implementing containers that will interoperate well with other controls and containers. This document defines the minimum set of interfaces, methods, and features that are required of ActiveX™ containers to accomplish seamless and useful interoperability.

Overview

ActiveX Control Containers

General Guidelines

Related Topics

Overview

An ActiveX control container must be able to host a minimal ActiveX control as specified in this document. It will also support a number of additional interfaces. A container can optionally support a number of interfaces and methods, which are grouped into functional areas known as component categories. A container can support any combination of component categories. For example, a component category exists for Databinding; a container may or may not support the data-binding functionality, depending on the market needs of the container. If a control needs data-binding support from a container to function, the control will enter this requirement in the registry. This allows a container to offer for insertion only those controls it knows it can successfully host. It is important to note that component categories are specified as part of OLE and are not specific to ActiveX Controls. The controls architecture uses component categories to identify areas of functionality that an OLE component might support. Component categories are not cumulative or exclusive, so a container can support one category without necessarily supporting another.

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.

Why Are the ActiveX Control Container Guidelines 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 and container developers make their controls and containers more reliable and interoperable, and, ultimately, better and more usable components for building component-based solutions.

This document 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 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.

ActiveX Control Containers

ActiveX control containers must provide support for the following features:

  • Embedded objects from in-process servers
  • In-place activation
  • OLEMISC_ACTIVATEWHENVISIBLE
  • Event handling

The following sections describe the specific interfaces, methods, and other features that are required of ActiveX control containers. The last section describes how to gracefully degrade when a particular control interface is not supported.

Required Interfaces

The following table lists the ActiveX control container interfaces, denoting which interfaces are optional, and which are mandatory and must be implemented by containers.
Interface | Support mandatory? | Comments
IOleClientSite | Yes
IAdviseSink | No | Only when the container wants data change notifications (controls with IDataObject), view change notification (controls that are not active and have IViewObject[2]), and other notifications from controls acting as standard embedded objects.
IOleInPlaceSite | Yes
IOleControlSite | Yes
IOleInPlaceFrame | Yes
IOleContainer | Yes | See Note 1.
IDispatch for ambient properties | Yes | See Note 2 and "Ambient Properties" section
Control Event Sets | Yes | See Note 2.
ISimpleFrameSite | No | This interface and support for nested simple frames is optional.
IPropertyNotifySink | No | Only needed for containers that have their own property-editing UI, which would require updating whenever a control changed a property itself, or containers that want to control [requestedit] property changes and other such data-binding features.
IErrorInfo | Yes | Mandatory if container supports dual interfaces. See Note 2.
IClassFactory2 | No | Support is strongly recommended.

Notes:

  1. IOleContainer is implemented on the document or form object (or appropriate analog) that holds the container sites. Controls use IOleContainer to navigate to other controls in the same document or form.
  2. Support for dual interfaces is not mandatory, but is strongly recommended. Writing ActiveX control containers to take advantage of dual interfaces will afford better performance with controls that offer dual interface support.

ActiveX control containers must support OLE Automation exceptions. If a container supports dual interfaces, it must capture automation exceptions through IErrorInfo.

Optional Methods

An OLE component can implement an interface without implementing all the semantics of every method in the interface, instead returning E_NOTIMPL or S_OK as appropriate. The following table describes those methods that an ActiveX control container is not required to implement (that is, the container can return E_NOTIMPL).

The table also describes optional methods; note that the method must still exist, but can simply return E_NOTIMPL instead of implementing "real" semantics. Note that any method from a mandatory interface that is not listed in the table must be considered mandatory and cannot return E_NOTIMPL.
Method | Comments
IOleClientSite
SaveObject | Necessary for persistence to be successfully supported.
GetMoniker | Necessary only if the container supports linking to controls within its own form or document.
IOleInPlaceSite
ContextSensitiveHelp | Optional.
Scroll | Can return S_FALSE with no action.
DiscardUndoState | Can return S_OK with no action.
DeactivateAndUndo | Deactivation is mandatory; Undo is optional.
IOleControlSite
GetExtendedControl | Necessary for containers that support extended controls.
ShowPropertyFrame | Necessary for containers that want to include their own property pages to handle extended control properties in addition to those provided by a control.
TranslateAccelerator | Can return S_FALSE with no action.
LockInPlaceActive | Optional.
IDispatch (ambient properties)
GetTypeInfoCount | Necessary for containers that support nonstandard ambient properties.
GetTypeInfo | Necessary for containers that support nonstandard ambient properties.
GetIDsOfNames | Necessary for containers that support nonstandard ambient properties.
IDispatch (event sink)
GetTypeInfoCount | The control knows its own type information, so it has no need to call this.
GetTypeInfo | The control knows its own type information, so it has no need to call this.
GetIDsOfNames | The control knows its own type information, so it has no need to call this.
IOleInPlaceFrame
ContextSensitiveHelp
GetBorder | Necessary for containers with toolbar UI (which is optional).
RequestBorderSpace | Necessary for containers with toolbar UI (which is optional).
SetBorderSpace | Necessary for containers with toolbar UI (which is optional).
InsertMenus | Necessary for containers with menu UI (which is optional).
SetMenu | Necessary for containers with menu UI (which is optional).
RemoveMenus | Necessary for containers with menu UI (which is optional).
SetStatusText | Necessary only for containers that have a status line.
EnableModeless | Optional.
TranslateAccelerator | Optional.
IOleContainer
ParseDisplayName | Only if linking to controls or other embeddings in the container is supported, as this is necessary for moniker binding.
LockContainer | As for ParseDisplayName.
EnumObjects | Returns all ActiveX Controls through an enumerator with IEnumUnknown, but not necessarily all objects (since there's no guarantee that all objects are ActiveX Controls; some might be regular Windows® controls).

Miscellaneous Status Bits Support

ActiveX control containers must recognize and support the following OLEMISC_ status bits.
Status bit | Support mandatory? | Comments
ACTIVATEWHENVISIBLE | Yes
IGNOREACTIVATEWHENVISIBLE | No | Needed for inactive and windowless control support. See Note 1.
INSIDEOUT | No | Not generally used with ActiveX Controls but rather with compound document embeddings. Note this is contrary to some SDK documentation that says this bit must be set for the ACTIVATEWHENVISIBLE bit to be set.
INVISIBLEATRUNTIME | Yes | Designates a control that should be visible at design time, but invisible at run time.
ALWAYSRUN | Yes
ACTSLIKEBUTTON | No | Support is normally mandatory, although it is not necessary for document-style containers.
ACTSLIKELABEL | No | Support is normally mandatory, although it is not necessary for document-style containers.
NOUIACTIVATE | Yes
ALIGNABLE | No
SIMPLEFRAME | No
SETCLIENTSITEFIRST | No | Support for this bit is recommended but not mandatory.
IMEMODE | No

Notes:

  1. The IGNOREACTIVATEWHENVISIBLE bit is for containers hosting inactive and windowless controls. The bit is introduced as part of the ActiveX Controls 96 specification; see this documentation for more details.

Keyboard Handling

Keyboard handling support for the following functionality is strongly recommended, although it is recognized that it is not applicable to all containers:

  • Support for OLEMISC_ACTSLIKELABEL and OLEMISC_ACTSLIKEBUTTON status bits.
  • Implementing the DisplayAsDefault ambient property (if it exists, it can return FALSE).
  • Implementing tab handling, including tab order.

Some containers use ActiveX Controls in traditional compound document scenarios. For example, a spreadsheet might allow a user to embed an ActiveX control into a worksheet. In such scenarios, the container would do keyboard handling differently, because the keyboard interface should remain consistent with the user's expectations of a spreadsheet. Documentation for such products should inform users of differences in control handling in these different scenarios. Other containers should endeavor to honor the above functionality correctly, including mnemonic handling.

Storage Interfaces

Containers must be able to support controls that implement IPersistStorage, IPersistStream, or IPersistStreamInit. Optionally, a container can support any other persistence interfaces, such as IPersistMemory, IPersistPropertyBag, and IPersistMoniker, for those controls that provide support.

After an ActiveX control container has chosen and initialized a storage interface to use ( IPersistStorage, IPersistStream, IPersistStreamInit, and so on), that storage interface will remain the primary storage interface for the lifetime of the control; that is, the control will remain in possession of the storage. This does not preclude the container from saving to other storage interfaces.

ActiveX control containers do not need to support a "save as text" mechanism, thus using IPersistPropertyBag and the associated container-side interface IPropertyBag are optional.

Ambient Properties

At a minimum, ActiveX control containers must support the following ambient properties using the standard DISPIDs.
Ambient property | DISPID | Comments/Conditions
LocaleID | -705
UserMode | -709 | For containers that have different user and run environments.
DisplayAsDefault | -713 | For those containers where a default button is relevant.

Extended Properties, Events, and Methods

ActiveX control containers are not required to support extended controls. However, if the container does support extended properties, it must support the following minimal set:
Visible
Parent
Default
Cancel

Currently, extended properties, events, and methods do not have standard DISPIDs.

Message Reflection

It is strongly recommended that an ActiveX control container support message reflection. This will result in more efficient operation for subclassed controls. If message reflection is supported, the MessageReflect ambient property must be supported and have a value of TRUE. If a container does not implement message reflection, the OLE CDK creates two windows for every subclassed control, to provide message reflection on behalf on the container.

Automatic Clipping

It is strongly recommended that an ActiveX control container support automatic clipping of its controls. This will result in more efficient operation for most controls. If automatic clipping is supported, the AutoClip ambient property must be supported and have a value of TRUE.

Automatic clipping is the ability of a container to ensure that a control's drawn output goes only to the container's current clipping region. In a container that supports automatic clipping, a control can paint without regard to its clipping region, because the container will automatically clip any painting that occurs outside the control's area. If a container does not support automatic clipping, CDK-generated controls will create an extra parent window if a nonnull clipping region is passed.

Degrading Gracefully in the Absence of an Interface

Because a control might not support any interface other than IUnknown, a container has to degrade gracefully when it encounters the absence of any particular interface.

One might question the usefulness of a "control" with nothing more than IUnknown. But consider the advantages that a control receives from a container's visual programming environment (such as Microsoft Visual Basic®) when the container recognizes the object as a "control":

  1. A button for the object appears in a toolbox.
  2. One can create an object by dragging it from the toolbox onto a form.
  3. One can give the object a name that is recognized in the visual programming environment.
  4. The same name in (3) above can be used immediately in writing any other code for controls on the same form (or even a different form).
  5. The container can automatically provide code entry points for any events available from that object.
  6. The container provides its own property browsing UI for any available properties.

When an object isn't recognized as a "control," it potentially loses all of these very powerful and beneficial integration features. For example, in Visual Basic 4.0 it is very difficult to really integrate some random object that is not a "control" in the complete sense, but may still have properties and events. Because Visual Basic 4.0's idea of a control is very restrictive, the object does not gain any of the integration features mentioned previously. But even a control with IUnknown, where the mere lifetime of the control determines the existence of some resource, should be able to gain the integration capabilities described above.

Because current tools require a large set of control interfaces to gain any advantage, controls are generally led to over-implementation, such that they contain more code than they really need. Controls that could be 7K might end up being 25K, which is a big performance problem in areas such as the Internet. This has also led to the perception that one can only implement a control with one tool, such as the CDK, because of the complexity of implementing all the interfaces. This has implications when a large DLL such as Oc30.dll is required for such a control, increasing the working set. If not all interfaces are required, this opens up many developers to writing very small and light controls with straight OLE or with other tools as well, minimizing the overhead for each control.

This is why this document recognizes a "control" as any object with a CLSID and an IUnknown interface. Even with nothing more than IUnknown, a container with a programming environment should be able to provide at least features (3) and (4) from the list above. If the object provides a ToolBoxBitmap32 registry entry, it gains (1) and (2). If the object supplies IConnectionPointContainer (and IProvideClassInfo generally) for some event set, it gains (5), and if it supports IDispatch for properties and methods, it gains (6), as well as better code integration in the container.

In short, an object should be able to implement as little as IDispatch and one event set exposed through IConnectionPointContainer to gain all the visual features above.

With this in mind, the following table describes what a container might do in the absence of any possible interface. Note that only those interfaces are listed that the container will directly obtain through QueryInterface. Other interfaces, such as IOleInPlaceActiveObject, are obtained through other means.
Interface | Meaning of interface absence
IViewObject2 | The control has no visuals that it will draw itself, so has no definite extents to provide. In run time, the container simply doesn't attempt to draw anything when this interface is absent. In design time, the container must at least draw some kind of default rectangle with a name in it for such a control, so a user in a visual programming environment can select the object and check out its properties, methods, and events that exist. Handling the absence of IViewObject2 is critical for good visual programming support.
IOleObject | The control doesn't need the site whatsoever, nor does it take part in any embedded object layout negotiation. Any information (such as control extents) that a container might expect from this interface should be filled in with container-provided defaults.
IOleInPlaceObject | The control doesn't go in-place active (like a label) and thus never attempts to activate in this manner. Its only activation might be its property pages.
IOleControl | Control has no mnemonics and no use of ambient properties, and doesn't care if the container ignores events. In the absence of this interface, the container just doesn't call its members.
IDataObject | The control provides no property sets nor any visual renderings that could be cached, so the container would choose to cache some default presentation in the absence of this interface (support for CF_METAFILEPICT, specifically) and disable any property-set related functionality.
IDispatch | The control has no custom properties or methods. The container does not need to try to show any control properties in this case, and should disallow any custom method calls that the container doesn't recognize as belonging to its own extended controls (that might support methods and properties). As extended controls generally delegate certain IDispatch calls to the control, an extended control should not expect the control to have IDispatch at all.
IConnectionPointContainer | The control has no events, so the container doesn't have to think about handling any.
IProvideClassInfo[2] | The control either doesn't have type information or events, or the container needs to go into the control's type information through the control's registry entries. The existence of this interface is an optimization.
ISpecifyPropertyPages | The control has no property pages, so if the container has any UI that would invoke them, the container should disable that UI.
IPerPropertyBrowsing | The control has no display name itself, no predetermined strings and values, and no property to page mapping. This interface is nearly always used for generating container user interface, so such UI elements would be disabled in the absence of this interface.
IPersist* | The control has no persistent state to speak of, so the container doesn't have to worry about saving any control-specific data. The container will, of course, save its own information about the control in its own form or document, but the control itself has nothing to contribute to that information.
IOleCache[2] | The object doesn't support caching. A container can still support caching by just creating a data cache itself using CreateDataCache.

General Guidelines

This section describes various features, hints, and tips for ActiveX control and ActiveX control container 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.

Note A control should use IPropertyNotifySink::OnRequestEdit only for data binding; it is free to use OnChanged for either or both purposes.

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 and CoUnmarshalInterface) when passed between threads. For more information about apartment model threading, refer to the Platform SDK documentation and the OLEAPT sample (in the Platform SDK).

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 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 SimpleFrameSite 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.

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 will enable 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 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. In the future, Microsoft will update these standard APIs to use both IClassFactory and IClassFactory2, as appropriate.

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.

Related Topics

COM Objects for the Internet
Asynchronous and URL Monikers
Hyperlinks


Active Documents

Microsoft® Active Documents are also known as Document Objects or Doc Objects. Unlike an OLE embedded object that is displayed within the page of another document, an Active Document is a full-scale document that is embedded in a container. Microsoft Office Binder and Microsoft Internet Explorer are two examples of Active Document containers. Microsoft Word and Microsoft Excel are two examples of Active Document servers. Unlike embedded objects, Active Documents have complete control over their pages and can exploit the complete native functionality of the server (application) that creates them. Users can create documents using the full power of their favorite applications, if they are Active Document-enabled, yet treat the resulting project as a single entity.

If you have Microsoft Word and Internet Explorer installed, you can see an example of Active Document technology at work. Open Internet Explorer and open a Word document in Internet Explorer. You will see that the document is displayed just as it is in Word, except it is contained inside Internet Explorer. Internet Explorer provides the basic menus, toolbars, and status bars, and Word adds all of its menus, toolbars, and status bars to create a user interface that is very familiar to the user.

The following sections describe the major steps involved in creating an Active Document server or an Active Document container in C++ without using the Microsoft Foundation Class Library (MFC). You should, at a minimum, be familiar with C++, OLE, and in-place activation.

All of the Active Document-specific definitions are contained in Docobj.h. No special libraries are required to build an Active Document server or container.

Programming an Active Document Server

Programming an Active Document Container

Reference


Programming an Active Document Server

An Active Document server is an OLE local server, similar to an OLE in-place server, that provides all the functionality of the document application. The document is contained inside an Active Document container, but is allowed to merge its menus with the container's menus and has complete use of the container's view area. The server provides the majority of the user interface, including menus, toolbars, status bars, and scroll bars. This allows the server to be hosted by any number of different Active Document containers while still maintaining a user interface that is familiar to the user.

The Basics

Registering an Active Document Server

The IDataObject Interface

The IOleDocument Interface

The IOleDocumentView Interface

Palette Management for Active Document Servers

The Basics

To create an Active Document server, you must create a file-based OLE local server that implements the following interfaces. Some of these interfaces are not required for a minimal implementation of a server. This table specifies which interfaces are required.
Interface | Required | Description
IOleDocument | Yes | The container uses this interface to cause an Active Document to create views of itself, enumerate the views the object supports, and provide miscellaneous information about the object's capabilities.
IOleDocumentView | Yes | The container uses this interface to activate, deactivate, close, and communicate with a document view object.
IPersistStorage | Yes | The container uses this interface to initialize the object and put it in the loaded or running state, and to instruct the object to perform various save operations or to release its storage.
IOleObject | Yes | The container uses this interface to execute verbs, communicate site information, and close the view.
IDataObject | Yes | The container uses this interface to get and set data in the object.
IOleInPlaceObject | Yes | The container uses this interface to activate and deactivate the view object.
IOleInPlaceActiveObject | Yes | The container uses this interface to control the view object while it is active.
IEnumFORMATETC | No | The container uses this interface to determine what data formats the object supports.
IEnumOleDocumentViews | No | The container uses this interface to enumerate the views that an Active Document supports.
IOleCommandTarget | No | The container uses this interface to dispatch commands and obtain command status information from the Active Document.
IPrint | No | The container uses this interface to print an Active Document.

Registering an Active Document Server

The following are the minimum registry entries necessary to register an Active Document server. Any information in "<>" brackets is implementation-specific.

HKEY_CLASSES_ROOT\<file extension> = <prog ID>

HKEY_CLASSES_ROOT\<prog ID> = <document name>

HKEY_CLASSES_ROOT\<prog ID>\CLSID = <clsid>

HKEY_CLASSES_ROOT\CLSID\<clsid>\LocalServer32 = <server path and file>

HKEY_CLASSES_ROOT\CLSID\<clsid>\InprocHandler32 = "ole32.dll"

HKEY_CLASSES_ROOT\CLSID\<clsid>\Insertable = ""

HKEY_CLASSES_ROOT\CLSID\<clsid>\DocObject = ""

The Insertable and DocObject registry keys noted in this example are not required if the CATID_Insertable and CATID_DocObject component categories are registered for the server. Registering the component categories causes these keys to be added automatically. For more information, see the ICatRegister::RegisterClassImplCategories method.

The following registry entry is optional and causes an icon to be displayed for the file in Windows® Explorer.

HKEY_CLASSES_ROOT\CLSID\<clsid>\DefaultIcon = <server path and file>,<icon index>

The IDataObject Interface

The IDataObject interface is not specific to Active Documents, but one point must be clarified. Some Active Document containers will store the document's information in a format specific to the container. Therefore, these containers will make a copy of the data before actually activating the document. This is done by requesting the data using the object's IDataObject::GetDataHere method for the "Embed Source" registered clipboard format, DVASPECT_CONTENT and TYMED_ISTORAGE. In response to this call, your server must store the document's current data in the IStorage interface provided in the STGMEDIUM structure. This can easily be accomplished by using the OleSave API.

The IOleDocument Interface

The IOleDocument interface is the basis for your Active Document server. The container uses this interface to create views, obtain a view enumerator (IEnumOleDocumentViews), and obtain document status.

Creating the Document View

The container will request that your server create a document view to be displayed. It will do so in the following manner:

  1. The container calls your server's IOleObject::DoVerb method with the iVerb parameter equal to OLEIVERB_SHOW, OLEIVERB_PRIMARY, or OLEIVERB_UIACTIVATE.
  2. Your server obtains the container's IOleDocumentSite pointer by calling QueryInterface on the IOleClientSite pointer that is passed to IOleObject::DoVerb.
  3. Your server then calls the container's IOleDocumentSite::ActivateMe method, passing either a valid IOleDocumentView pointer or NULL. If you pass a valid pointer, the document view will be activated. If you pass NULL, the container will call your server's IOleDocument::CreateView method to create the view.

The following is a very simple example of an IOleObject::DoVerb implementation.

STDMETHODIMP COleObject::DoVerb( LONG iVerb,
                                 LPMSG lpmsg,
                                 LPOLECLIENTSITE pActiveSite,
                                 LONG lindex,
                                 HWND hwndParent,
                                 LPCRECT lprcPosRect)
{
HRESULT  hr = E_FAIL;

switch (iVerb)
   {
   case OLEIVERB_SHOW:
   case OLEIVERB_PRIMARY:
   case OLEIVERB_UIACTIVATE:
      {
      //try to get the IOleDocumentSite pointer
      LPOLEDOCUMENTSITE pOleDocSite;
      hr = pActiveSite->QueryInterface(IID_IOleDocumentSite, (LPVOID*)&pOleDocSite);
      if(SUCCEEDED(hr))
         {
         //passing NULL to this will cause the site to call our CreateView method
         hr = pOleDocSite->ActivateMe(NULL);
         }
      }
      break;

   default:
      break;
   }

return hr;
}

The following is a simple example of an IOleDocument::CreateView implementation.

STDMETHODIMP COleDocument::CreateView( IOleInPlaceSite *pInPlaceSite,
                                       IStream *pStream,
                                       DWORD dwReserved,
                                       IOleDocumentView **ppOleDocumentView)
{
HRESULT  hr = E_FAIL;

//NULL the view pointer
*ppOleDocumentView = NULL;

//this implementation only supports one view, so fail if the view already exists
if(!m_pOleDocView)
   {
   m_pOleDocView = new COleDocumentView();

   if(m_pOleDocView)
      {
      //AddRef since the pointer is being given away
      m_pOleDocView->AddRef();

      //if a site has been given, set this as the site for the view just created
      if(pInPlaceSite)
         {
         m_pOleDocView->SetInPlaceSite(pInPlaceSite);
         }

      //if given a stream to initialize from, initialize the view state
      if(pStream)
         {
         m_pOleDocView->ApplyViewState(pStream);
         }

      *ppOleDocumentView = m_pOleDocView;

      hr = S_OK;
      }
   }

return hr;
}

The IOleDocumentView Interface

The IOleDocumentView interface is used by the container to manage the display of your Active Document. The container uses this interface to set the view's site and to activate and deactivate the view, among other things.

Getting the Document View's Site

When your server's IOleDocumentView::SetInPlaceSite method is called, the server should release any saved IOleInPlaceSite pointer that it is maintaining, and then call AddRef and store the new IOleInPlaceSite pointer. This pointer will be used to inform the site of in-place activation and deactivation and to obtain the handle of the view's parent window. The parent window handle is obtained by calling this interface's IOleWindow::GetWindow. The following is a simple example of this method's implementation.

STDMETHODIMP COleDocumentView::SetInPlaceSite(IOleInPlaceSite *pNewSite)
{
//clean up previous site if it exists
if(m_pInPlaceSite)
   {
   if(m_fUIActive)
      {
      //call the view's private method that handles UI deactivation
      DeactivateUI();
      }

   m_pInPlaceSite->Release();
   m_pInPlaceSite = NULL;
   }

m_pInPlaceSite = pNewSite;

if(m_pInPlaceSite)
   {
   m_pInPlaceSite->AddRef();
   }

return S_OK;
}

Activating the Document View

The document view activation occurs in the following manner:

  1. The server's IOleDocumentView::UIActivate method is called with a nonzero fActivate parameter.
  2. The server ensures that the object is in-place active. The object is made in-place active by calling the site's IOleInPlaceSite::OnInPlaceActivate method and showing the view window.
  3. The server performs whatever actions are necessary to become UI-active. This usually involves adding toolbars, merging menu items, and adding any other UI objects.
  4. The server notifies the site that the view is getting UI-activated by calling the site's IOleInPlaceSite::OnUIActivate method.

Some containers can contain more than one Active Document. These containers notify the object that the Active Document window is getting activated by calling the object's IOleInPlaceActiveObject::OnDocWindowActivate method with a nonzero argument. This does not normally occur until the object has already become UI-active. When this occurs, the object must display its UI objects, such as menus and toolbars. It is not necessary, however, to completely UI-deactivate the object.

Deactivating the Document View

The document view deactivation occurs in the following manner:

  1. The server's IOleDocumentView::UIActivate method is called with a zero fActivate parameter.
  2. The server removes any menu items, toolbars, or other UI objects.
  3. The server notifies the site that the view is getting UI-deactivated by calling the site's IOleInPlaceSite::OnUIDeactivate method.

Some containers can contain more than one Active Document. These containers notify the object that the Active Document window is getting deactivated by calling the object's IOleInPlaceActiveObject::OnDocWindowActivate method with a zero argument. When this occurs, the object must hide its UI objects, such as toolbars. It is not necessary to remove the object's menus at this point because the container will do this on its own. It is also not necessary to completely UI-deactivate the object.

Palette Management for Active Document Servers

In general, the palette management scheme for Active Documents is the same as the scheme used for ActiveX™ Controls, except that Active Documents do not receive ambient properties from their client. The Active Document server has these responsibilities for managing palettes:

  • The server notifies the container as to which palette the Active Document needs to use by implementing the IViewObject::GetColorSet method. This is the same method that OLE controls use to manage their palettes.
  • The server must realize its palette in the background except when handling the WM_QUERYNEWPALETTE message (forwarded from the container). Again, this is exactly the way OLE controls handle this.

The only difference between the responsibilities of Active Documents and OLE controls is that Active Documents do not handle the DISPID_AMBIENT_PALETTE property.


Programming an Active Document Container

An Active Document container is an application that provides the view in which an Active Document resides. Instead of providing a document in which an in-place object resides, the container provides the basic user interface features that are specific to the container, and allows the server to merge its menus and add any other user interface components within the view area of the container. This allows the container to host any number of different Active Documents while still maintaining a user interface that is familiar to the user.

The Basics

Loading the Active Document

Miscellaneous Requirements

Palette Management for Active Document Containers

The Basics

To create an Active Document container, you must implement the following interfaces. Some of these interfaces are not required for a minimal implementation of a container. This table specifies which interfaces are required.
Interface | Required | Description
IOleDocumentSite | Yes | The Active Document object uses this interface to request that the container activate it.
IOleClientSite | Yes | The Active Document object uses this interface to obtain information about the location and extent of its display site, its user interface, and other resources provided by the container.
IOleInPlaceSite | Yes | The Active Document object uses this interface to control in-place activation from within the container.
IAdviseSink | Yes | The Active Document object uses this interface to notify objects with which they have an advisory connection of changes in an object's data, view, name, or state.
IOleCommandTarget | No | The Active Document object uses this interface to dispatch commands and obtain command status information from the Active Document.
IContinueCallback | No | This is a generic callback mechanism for interruptible processes that should periodically ask an object whether to continue.

Loading the Active Document

To load the Active Document, your container should perform the following steps. These steps discuss how and when to create, activate, display, and close an Active Document.

Creating the Active Document Object

When opening an Active Document file, you need to create an instance of the Active Document object. This is accomplished with the OleCreateFromFile API. You pass this API a valid IStorage pointer and the name of the file you want to create the object from. If the API succeeds, you will receive a pointer to the Active Document object. This is the interface you will use to control the object. You can use this pointer to use QueryInterface for the IOleObject and/or IOleDocument interface pointers.

When you have the object's interface, you need to do the following:

  1. Set the object's client site by calling its IOleObject::SetClientSite method, passing your IOleClientSite interface.
  2. Set the object's advise sink by calling the object's IOleObject::Advise method, passing your IAdviseSink interface.
  3. Place the Active Document object into the running state by calling OleRun, passing the object's interface pointer.
  4. Obtain the Active Document view pointer by calling the object's IOleObject::DoVerb method with the iVerb parameter equal to OLEIVERB_SHOW. In response to this, the object will call your IOleDocumentSite::ActivateMe method. In IOleDocumentSite::ActivateMe, if the pViewToActivate parameter is not NULL, it contains the object's IOleDocumentView pointer. If pViewToActivate is NULL, you must call the object's IOleDocument::CreateView method to create the view.

    In the IOleDocument::CreateView call, the pIPSite and pstm parameters are optional. If you don't include them, or if you obtained the object's view pointer directly in IOleDocumentSite::ActivateMe, you will need to call the object's IOleDocumentView::SetInPlaceSite method with your IOleInPlaceSite pointer to set the view's in-place site and call the object's IOleDocumentView::ApplyViewState method with your view state stream pointer. This allows the view to obtain its view state information.

Activating the Active Document View

When you have the object's view pointer, you need to activate the view. These are the steps involved in this process:

  1. Call the object's IOleDocumentView::UIActivate method with the fUIActivate parameter set to TRUE.
  2. Set the object's display rectangle by calling its IOleDocumentView::SetRect method.
  3. Show the view by calling the object's IOleDocumentView::Show method with the fShow parameter set to TRUE.

Deactivating the Active Document View

To deactivate the view, hide it by calling the object's IOleDocumentView::Show method with FALSE. You then call the object's IOleDocumentView::UIActivate method with FALSE.

Closing the Active Document

To close the document, perform the following steps:

  1. Deactivate the view.
  2. Call the object's IOleDocumentView::CloseView method to close the view.
  3. When the view has been closed, the IOleDocumentView object is no longer valid, so you must release its pointer.
  4. Release any other pointers you have obtained from the object.
  5. Release the IOleDocument pointer.

Miscellaneous Requirements

You should design your container with these points in mind:

  • The Active Document object will obtain the view window's parent by calling your IOleWindow::GetWindow method. You must provide a handle to your view parent window in response to this method.
  • While it is not required for the most basic implementation of an Active Document container, your container should implement the IOleInPlaceFrame interface. Without this interface the object will not be able to display toolbars or status text, nor will it be able to merge its menus with the container's menu. Some Active Documents might not allow themselves to be displayed without the container having implemented IOleInPlaceFrame.
  • Whenever the size of your view area changes, you need to recalculate the view rectangle and notify the view object by calling its IOleDocumentView::SetRect method.

Palette Management for Active Document Containers

In general, the palette management scheme for Active Document containers is the same as the scheme used for ActiveX™ Controls, except that containers do not give ambient properties to the document. There are two different cases that need to be considered. The first is if the container can activate only one Active Document object at a time. The second is if the container can activate more than one Active Document object at a time.

Single Document Case

In the most common cases, your container will activate only one Active Document at a time. In this situation, it is best to allow the Active Document to have complete control over the palette, which includes managing palette issues for any controls or content within that document. To accomplish this, it is necessary for you to forward all Microsoft® Windows® palette management messages on to the Active Document. The following is a simple demonstration of how to forward these messages to the Active Document.

case WM_PALETTECHANGED:
case WM_QUERYNEWPALETTE:
   {
   HWND hwnd;
   IOleWindow *pOleWindow;
   if(pDocObj && SUCCEEDED(pDocObj->QueryInterface(IID_IOleWindow,
         (LPVOID*)&pOleWindow)))
      {
      LRESULT lres = 0;
      if (SUCCEEDED(pOleWindow->GetWindow(&hwnd)))
         {
         lres = SendMessage(hwnd, uMsg, wParam, lParam);
         }

      pOleWindow->Release();
      return lres;
      }
   break;
   }

Multiple Document Case

In rare cases, your container might be able to activate more than one Active Document at a time. While you can implement such a container, you should avoid doing so if at all possible. One of the reasons this is discouraged is that no protocol currently exists for communicating palettes between an Active Document and its container. Therefore, the container cannot create a palette suitable to all Active Documents that it has activated.

Because of this, the activated Active Document in the foreground has control over the palette and will use foreground palette rendering. Other activated Active Documents in the background will use background palette rendering. The Active Document container itself does not participate in palette management at all.


Reference

This reference provides complete and detailed information on all the programming elements used with Active Document servers and containers.

Interfaces

Structures

Enumerations


Interfaces

This section describes the Active Document interfaces.
IContinueCallback
IEnumOleDocumentViews
IOleCommandTarget
IOleDocument
IOleDocumentSite
IOleDocumentView
IPrint


IContinueCallback

The IContinueCallback interface is a generic callback mechanism for interruptible processes that should periodically ask an object whether to continue.

The IContinueCallback::FContinue method is a generic continuation request. IContinueCallback::FContinuePrinting carries extra information pertaining to a printing process and is used in the context of the IPrint interface.

Methods in Vtable Order
FContinue | Answers whether an operation should continue.
FContinuePrinting | Answers whether a printing operation should continue.


IContinueCallback::FContinue

HRESULT FContinue(void);

Answers whether a given generic operation should continue.

  • Returns S_OK to continue the operation, or S_FALSE to cancel it as soon as possible.


IContinueCallback::FContinuePrinting

HRESULT FContinuePrinting(
    LONG cPagesPrinted,
    LONG nCurrentPage,
    LPOLESTR pszPrintStatus
    );

Answers whether a given lengthy printing operation should continue.

  • Returns one of the following values:
    S_OK | Continue the printing operation.
    E_FAIL | The printing operation failed.
    S_FALSE | Cancel the print job as soon as possible.
cPagesPrinted
Total number of pages that have been printed at the time the object receives a call to this method.
nCurrentPage
Page number of the page being printed at the time the object receives a call to this method.
pszPrintStatus
Address of a message about the current status of the print job. The object being printed may or may not display this message to the user. Can be NULL.

Implementations of IPrint::Print call this method at periodic intervals during the printing process. The IPrint implementation should call back at least after printing each page, so that its client can, if necessary, display useful visual feedback to the user. IPrint::Print can call back multiple times with the same cPagesPrinted and nCurrentPage values, which is useful when a page being printed is complex and it is appropriate to give a user an opportunity to cancel mid-page.


IEnumOleDocumentViews

The IEnumOleDocumentViews interface is used to enumerate the views supported by an Active Document. IEnumOleDocumentViews has the same methods as all enumerator interfaces. For general information on these methods, see IEnumXXXX.

When to Implement

Implement IEnumOleDocumentViews on enumerator objects associated with Active Documents that support more than one view of their data.

When to Use

Use the IEnumOleDocumentViews interface to enumerate all the views supported by an Active Document. The usual procedure is to first call the IOleDocument::EnumViews method. If the Active Document supports only one view, IOleDocument::EnumViews obtains a pointer to that view. If the Active Document supports two or more views, IOleDocument::EnumViews obtains a pointer to IEnumOleDocumentViews. Using this pointer, the container can then ask the Active Document to enumerate the views it supports.

Methods in Vtable Order
Next | Retrieves a specified number of items in the enumeration sequence.
Skip | Skips over a specified number of items in the enumeration sequence.
Reset | Resets the enumeration sequence to the beginning.
Clone | Creates another enumerator that contains the same enumeration state as the current one.


IEnumOleDocumentViews::Clone

HRESULT Clone(
    IEnumOleDocumentViews ** ppenum);

Creates a separate view enumerator with the same state as the current enumerator, and that iterates over the same list. This makes it possible to record a point in the enumeration sequence and return to that point at a later time.

  • Returns S_OK if successful, or an OLE defined error code otherwise.
ppenum
[out] Address of the IEnumOleDocumentViews interface pointer that receives the newly created enumerator. The caller must release this enumerator separately from the original enumerator.


IEnumOleDocumentViews::Next

HRESULT Next(
    ULONG cViews,
    IOleDocumentView * rgpView,
    [out] ULONG * pcFetched);

Enumerates the next cViews elements in the enumerator's list, returning them in rgpView, along with the actual number of enumerated elements in pcFetched. The enumerator object is responsible for calling IUnknown::AddRef and the caller is responsible for calling IUnknown::Release on each pointer returned in rgpView.

  • Returns one of the following values:
    S_OK | The number of items requested in cViews is fetched.
    S_FALSE | Less than the number of items requested is fetched.
    OLE defined error code otherwise.
cViews
[in] Number of IOleDocumentView pointers to be written to the array pointed to by rgpView. If pcFetched is NULL, this argument must be 1.
rgpView
[out, max_is(cViews)] Address of an array of IOleDocumentView pointers that receive the enumerated interfaces. The caller is responsible for allocating this array, and the array must be able to contain at least the number of elements specified in cViews.
pcFetched
[out] Address of a ULONG variable that receives the actual number of views enumerated in the rgpView parameter. This argument can be NULL, in which case the cViews argument must be 1.

E_NOTIMPL is not allowed by this method as a return value. If an error value is returned, no entries in the rgpView array are valid and no calls to IUnknown::Release are required.


IEnumOleDocumentViews::Reset

HRESULT Reset(void);

Resets the enumeration sequence to the beginning of the list of elements. There is no guarantee that the same set of elements will be enumerated on each pass through the list: It depends on the collection being enumerated. It is too expensive for some collections, such as files in a directory, to maintain this condition.

  • Returns S_OK if successful, or an OLE defined error code otherwise.


IEnumOleDocumentViews::Skip

HRESULT Skip(
    ULONG cViews);

Skips over a number of elements, specified in the cViews parameter, in the enumeration so that the next call to the IEnumOleDocumentViews::Next method will not return those elements.

  • Returns one of the following values:
    S_OK | The number of items requested in cViews is skipped.
    S_FALSE | Less than the number of items requested is skipped.
    An OLE defined error code otherwise.
cViews
[in] Number of elements to skip in the enumeration.


IOleCommandTarget

The IOleCommandTarget interface enables objects and their containers to determine if an object supports a command, to determine the status of a command, and to execute a command. For example, the container of an object can determine if the object supports the Paste command. If it does support the Paste command, the container can then determine if Paste can currently be executed. If the command can be executed, the container can use that information to enable its Paste command user interface. If the user selects Paste, the container can use this interface to execute the command on the object.

Normal in-place activation guidelines recommend that you remove or disable such buttons because no efficient, standard mechanism has been available to dispatch them to the container. Similarly, a container has heretofore had no efficient means to send commands such as Print, Page Setup, and Properties to an in-place active object. Such simple command routing could have been handled through existing OLE Automation standards and the IDispatch interface, but the overhead with IDispatch is more than is required in the case of Active Documents. The IOleCommandTarget interface provides a simpler means to achieve the same ends.

Available commands are defined by integer identifiers in a group. The group itself is identified with a GUID. The interface allows a caller both to query for support of one or more commands within a group and to issue a supported command to the object.

When to Implement

Implement IOleCommandTarget when your object supports one or more commands.

When to Use

Use IOleCommandTarget to determine the status of or execute a command implemented by an object.

Methods in Vtable Order
QueryStatus | Queries an object for status of commands.
Exec | Executes a command.


IOleCommandTarget::Exec

HRESULT Exec(
    const GUID *pguidCmdGroup,
    DWORD nCmdID,
    DWORD nCmdExecOpt,
    VARIANTARG *pvaIn,
    VARIANTARG *pvaOut
   );

Executes a specified command or displays help for a command.

  • Returns one of the following values:
    S_OK | Success.
    E_FAIL | The command failed.
    E_UNEXPECTED | The command was executed at a time when the object was not expecting the command.
    OLECMDERR_CANCELED | The user canceled the execution of the command.
    OLECMDERR_DISABLED | The command identified by nCmdID is currently disabled and cannot be executed.
    OLECMDERR_E_NOTSUPPORTED | The nCmdID parameter does not identify a valid command in the pguidCmdGroup group.
    OLECMDERR_E_UNKNOWNGROUP | The pguidCmdGroup parameter is not NULL but does not specify a recognized command group.
    OLECMDERR_NOHELP | The caller has asked for help on the command identified by nCmdID, but no help is available.
pguidCmdGroup
[unique][in] Address of a globally unique identifier (GUID) of the command group; can be NULL to specify the standard group.
nCmdID
[in] Identifier of the command to be executed. This command must belong to the group specified in the pguidCmdGroup parameter.
nCmdExecOpt
[in] Values taken from the OLECMDEXECOPT enumeration, which describes how the object should execute the command.
pvaIn
[unique][in] Address of a VARIANTARG structure containing input arguments. Can be NULL.
pvaOut
[unique][in,out] Address of a VARIANTARG structure to receive command output. Can be NULL.

The list of input and output arguments of a command and how they are packaged is unique to each command. Such information should be documented with the specification of the command group. (See the description of OLECMDID_ZOOM in the OLECMDID enumeration.) In the absence of any specific information, the command is assumed to take no arguments and have no return value.

Notes to Callers

The pguidCmdGroup and nCmdID parameters together uniquely identify the command to invoke. The nCmdExecOpt parameter specifies the exact action to be taken. (For more details, see the OLECMDEXECOPT enumeration.)

Most commands neither take arguments nor return values. For such commands, the caller can pass NULL in pvaIn and pvaOut. For commands that expect one or more input values, the caller can declare and initialize a VARIANTARG structure and pass a pointer to that structure in pvaIn. If the input to the command is a single value, the argument can be stored directly in the VARIANTARG structure and passed to the function. If the command expects multiple arguments, those arguments must be packaged appropriately within the VARIANTARG structure, using one of the supported variant types (such as the IDispatch interface or the SAFEARRAY data type).

If a command returns one or more arguments, the caller is expected to declare a VARIANTARG, initialize it to VT_EMPTY, and pass its address in pvaOut. If the command returns a single value, the object can store that value directly in pvaOut. If the command has multiple output values, it will package those in some way appropriate for the VARIANTARG.

Because pvaIn and pvaOut are both caller-allocated, stack variables are permitted for both the caller and the object receiving the call. For commands that take zero or one argument on input and return zero or one value, no additional memory allocation is necessary. Most of the types supported by VARIANTARG do not require memory allocation. Exceptions include SAFEARRAY and BSTR. For a complete list, see the OLE documentation in the Microsoft® Platform SDK.

Notes to Implementers

A command target must implement this function; E_NOTIMPL is not a valid return value.


IOleCommandTarget::QueryStatus

[input_sync] HRESULT QueryStatus(
    const GUID *pguidCmdGroup,
    ULONG cCmds,
    OLECMD *prgCmds,
    OLECMDTEXT *pCmdText
   );

Queries the object for the status of one or more commands generated by user interface events.

  • Returns one of the following values:
    S_OK | The command status and any optional strings were returned successfully.
    E_FAIL | The query failed.
    E_POINTER | The prgCmds argument is NULL.
    E_UNEXPECTED | The query was received at a time that the object was not expecting it.
    OLECMDERR_E_UNKNOWNGROUP | The pguidCmdGroup parameter is not NULL but does not specify a recognized command group.
pguidCmdGroup
[unique][in] Globally unique identifier of the command group; can be NULL to specify the standard group. All the commands that are passed in the prgCmds array must belong to the group specified by this parameter.
cCmds
[in] Number of commands in the prgCmds array.
prgCmds
[in,out] Caller-allocated array of OLECMD structures that indicates the commands for which the caller needs status information. This method fills the cmdf member of each structure with values taken from the OLECMDF enumeration.
pCmdText
[unique][in,out] Address of an OLECMDTEXT structure in which to return name and/or status information of a single command. Can be NULL to indicate that the caller does not need this information.

Callers use this method to determine which commands are supported by a target object. The caller can then disable unavailable commands that would otherwise be routed to the object. The caller can also use this method to get the name or status of a single command.

Notes to Callers

The caller passes an array of OLECMD structures in prgCmds that describes the commands of interest from the group specified in pguidCmdGroup, where each structure's cmdID member is set to a command identifier and the cmdf member is set to zero.

Notes to Implementers

The object receiving the call fills the cmdf member for each command with values taken from the OLECMDF enumeration to describe the status of each command.

The called object should first mark the command as described above. Then, if the command is supported (OLECMDF_SUPPORTED), the object should check the OLECMDTEXTF_ flags in the cmdtextf member of pCmdText. If the OLECMDTEXTF_NAME flag is specified, the object should copy the localized name of the command (for example, "Open", "Copy", and so on) into the rgwz member of pCmdText. Attention should be paid to the size specified by the cwBuf member of pCmdText.

If the caller sets the OLECMDTEXTF_STATUS flag, the object should instead write a localized status string for the command into the rgwz member. The status string is typically contextual and depends on the state of the command—enabled/disabled, for example. If the buffer is not big enough, the object should zero-terminate the buffer. Whether the buffer is big enough or not, the object must return the total actual size of the string that it attempted to copy in the cwActual member of pCmdText.

If the command array contains more than one command, the textual information should be returned for the first command in the array that the object supports. Typically, this functionality is used to show the status text of a command. The caller can use either a stack or a global variable for rgwz, because memory for this parameter is not dynamically allocated.

Because IOleCommandTarget::QueryStatus is defined with the [input_sync] attribute, the implementing object cannot yield or make another non-input_sync RPC call while executing it.

A command target must implement this function; E_NOTIMPL is not an acceptable return value.


IOleDocument

The IOleDocument interface enables an Active Document to communicate its ability to create views of its data to containers. This interface also enables an Active Document to enumerate its views and to provide containers with miscellaneous information about itself, such as whether it supports multiple views or complex rectangles.

When to Implement

An OLE document that is to be activated as an Active Document must, at the very least, implement this interface. If an application exposes more than one type of Active Document, each type must implement IOleDocument.

When to Use

An Active Document container calls the methods of this interface to ask an Active Document to create views of itself, enumerate the views it supports, or provide miscellaneous information about its capabilities.

Methods in Vtable Order
CreateView | Creates a new view object.
GetDocMiscStatus | Returns status information about the Active Document.
EnumViews | Enumerates views supported by the Active Document.


IOleDocument::CreateView

HRESULT CreateView(
    IOleInPlaceSite *pIPSite,
    IStream *pstm,
    DWORD dwReserved,
    IOleDocumentView **ppView
   );

Creates a document view object in the caller's process and obtains a pointer to that object's IOleDocumentView interface.

  • Returns one of the following values:
    S_OK | Success.
    E_FAIL | The view object creation failed.
    E_OUTOFMEMORY | There is not enough memory to create the view object.
    E_POINTER | The address in ppView is NULL.
    E_UNEXPECTED | The method was called at a time when the object was not expecting it.
pIPSite
[in] Address of the view site object to be associated with the new document view object. Can be NULL, as when the view is contained in a new, uninitialized Active Document, in which case the caller must initialize the view with a subsequent call to the IOleDocumentView::SetInPlaceSite method.
pstm
[in] Address of a stream containing data from which the new document view object should initialize itself. If NULL, the Active Document initializes the new document view object with a default state.
dwReserved
[in] Reserved for future use. Must be zero.
ppView
[out] Address of the IOleDocumentView interface pointer on the new document view object. If IOleDocument::CreateView succeeds, the caller is responsible for calling IUnknown::Release on this pointer when the view object is no longer needed.

See also IOleDocumentSite::ActivateMe, IOleDocumentView::ApplyViewState, IOleDocumentView::Show, IOleDocumentView::UIActivate


IOleDocument::EnumViews

HRESULT EnumViews(
    IEnumOleDocumentViews ** ppEnum,
    IOleDocumentView ** ppView
   );

Creates an object that enumerates the views supported by an Active Document, or, if only one view is supported, returns a pointer to that view.

  • Returns one the following values:
    S_OK | If the object supports multiple views, ppEnum contains a pointer to the enumerator object and the content of ppView is NULL. Otherwise, the content of ppEnum is NULL and ppView contains an interface pointer to the single view.
    E_OUTOFMEMORY | There is not enough memory available to create the object.
    E_POINTER | Either ppEnum or ppView is invalid. The caller must pass valid pointers for both parameters.
ppEnum
[out] Address of the IEnumOleDocumentViews interface pointer on the enumerator object.
ppView
[out] Address of the IOleDocumentView interface on a single view object.

If an Active Document supports multiple views of its data, it must also implement IEnumOleDocumentViews and place that interface's pointer in ppEnum. Using this pointer, the container can enumerate the views supported by the Active Document.

If the Active Document supports only a single view, IOleDocument::EnumViews places that view's IOleDocumentView pointer in ppView.

Notes to Callers

Call this method to determine if an Active Document supports more than one view of its data. If it does, the caller can use the pointer written to ppEnum to specify which view to activate. When finished with the pointer, the caller must free it by calling IUnknown::Release.

Notes to Implementers

This method must be completely implemented on all Active Documents; E_NOTIMPL is not an acceptable return value.


IOleDocument::GetDocMiscStatus

HRESULT GetDocMiscStatus(
    DWORD *pdwStatus
   );

Obtains information about whether an Active Document supports behaviors specified in the DOCMISC enumerator.

  • Returns S_OK if successful, or E_POINTER if the address in pdwStatus is NULL.
pdwStatus
[out] Address of the information on supported behaviors. Values written to the address specified by this pointer are taken from the DOCMISC enumeration.

This method provides a way for containers to ascertain whether an Active Document supports multiple views, complex rectangles, opening in a pop-up window, or file read/write.

Notes to Callers

By calling this method prior to activating an Active Document, containers can take whatever steps are necessary to support or otherwise accommodate specified behaviors.

Notes to Implementers

This method must be completely implemented in any Active Document, even if the dereferenced value of pdwStatus is zero. E_NOTIMPL is not an acceptable return value. Normally, the returned DOCMISC value should be hard-coded for performance.


IOleDocumentSite

The IOleDocumentSite interface enables a document that has been implemented as an Active Document to bypass the normal activation sequence for in-place active objects and to directly instruct its client site to activate it as an Active Document object. A client site with this ability is called a document site.

For each Active Document to be hosted, a container must provide a corresponding document site. This document site is an OLE Documents client site that, in addition to implementing the IOleClientSite and IAdviseSink interfaces, also implements IOleDocumentSite. Each document site implements a separate document view site object for each view of a document to be activated. The document view site implements IOleInPlaceSite and, optionally, IContinueCallback.

When to Implement

Implement IOleDocumentSite on each client site that is to host an Active Document. In addition to this interface, a document site must also implement the IOleClientSite, IOleInPlaceSite, and IAdviseSink interfaces. Implementing IContinueCallback is optional.

When to Use

An Active Document calls IOleDocumentSite::ActivateMe to ask its document site to activate it, typically in response to the container's call to IOleObject::DoVerb.

Methods in Vtable Order
ActivateMe Activates an Active Document.


IOleDocumentSite::ActivateMe

HRESULT ActivateMe(
    IOleDocumentView *pViewToActivate
    );

Asks a document site to activate the document making the call as an Active Document rather than an in-place active object. Optionally, specifies which view of the object document to activate.

  • Returns one of the following values:
    S_OK | Success.
    E_FAIL | The method was not successful.
    E_OUTOFMEMORY | There was not enough memory to complete the operation.
pViewToActivate
[in] Address of the document view to be used in activating the Active Document. Can be NULL, in which case the container should call IOleDocument::CreateView to obtain a document view pointer.

See also IOleClientSite, IOleDocumentView::SetInPlaceSite, IOleInPlaceSite, IOleObject::DoVerb


IOleDocumentView

The IOleDocumentView interface enables a container to communicate with each view supported by an Active Document.

An Active Document that supports multiple views of its data represents each view as a separate object. Each document view object implements IOleDocumentView, along with the IOleInPlaceObject and IOleInPlaceActiveObject interfaces, as well as optional interfaces such as IPrint and IOleCommandTarget. An Active Document that supports only a single view does not require that view to be implemented as a separate object. Instead, both document and view can be implemented as a single class.

When to Implement

All Active Documents must implement IOleDocumentView for each view they support. If an Active Document supports multiple views, each view must be implemented as a separate class object. If an Active Document supports only a single view, the Active Document and its view can both be implemented as a single class.

When to Use

A container calls the methods of this interface to activate, deactivate, close, and generally communicate with a document view object.

Methods in Vtable Order
SetInPlaceSite | Associates a container view site with this view.
GetInPlaceSite | Retrieves the view site associated with this view.
GetDocument | Obtains the IUnknown pointer of the Active Document that owns this view.
SetRect | Sets the coordinates of the view port.
GetRect | Retrieves the coordinates last passed in the SetRect method.
SetRectComplex | Sets the coordinates of the view port, scroll bars, and size box.
Show | In-place activates or deactivates a view.
UIActivate | In-place activates or deactivates a view's user-interface elements.
Open | Displays the view in a separate pop-up window.
CloseView | Instructs the view to close.
SaveViewState | Saves the view state into a stream.
ApplyViewState | Initializes the view with the view state previously saved in the call to the SaveViewState method.
Clone | Creates a duplicate view object.


IOleDocumentView::ApplyViewState

HRESULT ApplyViewState(
    IStream *pstm
   );

Initializes a view to a previously saved view state.

  • Returns one of the following values:
    S_OK | Success.
    E_NOTIMPL | This view has no meaningful state to load. This error should be rare because most views will have at least some state information worth loading.
    E_POINTER | The value in the pstm parameter is NULL.
pstm
[in] Address of a stream containing data from which the view should initialize itself.

Typically, this method is called after an existing view has been created in the container but before that view has been displayed. It is the responsibility of the view to validate the data in the view stream; the container does not attempt to interpret the view's state data.

See also IOleDocument::CreateView, IOleDocumentView::SaveViewState


IOleDocumentView::Clone

HRESULT Clone(
    IOleInPlaceSite *pIPSiteNew
    IOleDocumentView **ppViewNew
    );

Creates a duplicate view object with an internal state identical to that of the current view.

  • Returns one of the following values:
    S_OK | Success.
    E_FAIL | The method was not successful.
    E_NOTIMPL | The view object does not implement this interface.
    E_POINTER | The value in the ppViewNew parameter is NULL.
pIPSiteNew
[in] Address of the view site in which the new view object will be activated. On receiving this pointer, the view being cloned should pass it to the new view's IOleDocumentView::SetInPlaceSite method. This can be NULL, in which case the caller is responsible for calling IOleDocumentView::SetInPlaceSite on the new view directly.
ppViewNew
[out] Address of the IOleDocumentView interface pointer to the new view object. The caller is responsible for releasing this pointer when it is no longer needed.

This method is useful for creating a new view with a different view port and view site but with the same view context as the view being cloned. Typically, containers hosting a multiple document interface (MDI) application will call this method to provide "Window/New window" capability.


IOleDocumentView::CloseView

HRESULT CloseView(
    DWORD dwReserved
   );

Instructs a document view to close and release its IOleInPlaceSite pointer.

  • Returns S_OK if the view successfully closed, or an OLE defined error code otherwise.
dwReserved
Reserved; must be zero.

When a separate window is no longer needed, the container calls IOleDocumentView::CloseView, whereupon the view releases its site pointer to the separate window and destroys the window. Unlike the normal in-place deactivation sequence for in-place active objects, a document view continues to hold the IOleInPlaceSite pointer. This pointer is released only when the view's container calls IOleDocumentView::SetInPlaceSite, with the pIPSite parameter set to NULL, or calls IOleDocumentView::CloseView.

When a user closes a view's window, the view should not shut itself down. Instead, it should call IOleInPlaceSite::OnInPlaceActivate. The view site then decides whether to call IOleDocumentView::UIActivate with FALSE to UI-deactivate the view immediately or later. In this way, a document view displayed in a window remains available for activation in the container's own window.

The container must call this method before it deletes the view, that is, releases its last reference to the view. In general, implementation of this method will call IOleDocumentView::Show with FALSE to hide the view if it is not already hidden, then call IOleDocumentView::SetInPlaceSite with NULL and release the view site pointer.

Because IOleDocumentView::CloseView is called when a container is going to completely shut down a view, this method must be implemented and has no reason to fail.


IOleDocumentView::GetDocument

HRESULT GetDocument(
    IUnknown **ppunk
   );

Obtains the IUnknown interface pointer on the Active Document that owns this view.

  • Returns S_OK if successful. This is the only valid return value for this method.
ppunk
[out] Address of the IUnknown pointer of the Active Document that owns this view.

The caller is responsible for incrementing the reference count on the interface pointer obtained by this method. The caller must call IUnknown::Release on this pointer when it is no longer needed.

Because a view object must always be contained or aggregated in an Active Document, this method will always succeed. Before returning, this method should call IUnknown::AddRef on the pointer in the ppunk parameter.


IOleDocumentView::GetInPlaceSite

HRESULT GetInPlaceSite(
    IOleInPlaceSite **ppIPSite
   );

Retrieves the view site associated with this view object.

  • Returns S_OK if successful, or E_FAIL otherwise.
ppIPSite
[out] Address of the view site's IOleInPlaceSite interface pointer.

IOleDocumentView::GetInPlaceSite obtains the most recent IOleInPlaceSite pointer passed by IOleDocumentView::SetInPlaceSite, or NULL if IOleDocumentView::SetInPlaceSite has not yet been called. If this pointer is not NULL, this method will call IUnknown::AddRef on the pointer. The caller is responsible for releasing it. A document view must implement this method completely; E_NOTIMPL is not an acceptable return value.


IOleDocumentView::GetRect

HRESULT GetRect(
    LPRECT prcView
   );

Retrieves the rectangular coordinates of the view port in which the view is or will be activated.

  • Returns S_OK if successful, or E_UNEXPECTED if the view has not yet seen a call to IOleDocumentView::SetRect or IOleDocumentView::SetRectComplex and therefore has no rectangle to return.
prcView
[out] Address of a RECT structure that receives the coordinates of the current view port.

For a single document interface (SDI) application, the view port is the client area of the frame window minus the space allocated for toolbars, status bar, and such. For a multiple document interface (MDI) window, the view port is the client area of the MDI document window minus any other frame-level user-interface elements.

The view port coordinates obtained by this method are those set in the most recent call to either the IOleDocumentView::SetRect or IOleDocumentView::SetRectComplex method.

A document view must implement this method completely; E_NOTIMPL is not an acceptable return value.


IOleDocumentView::Open

HRESULT Open(Void);

Displays the view in a separate pop-up window. The result is similar to calling IOleObject::DoVerb with OLEIVERB_OPEN.

  • Returns one of the following values:
    S_OK | Success.
    E_FAIL | The method was not successful.
    E_NOTIMPL | The Active Document that owns this view does not support separate window activation.
    E_OUTOFMEMORY | There is not enough memory to complete this operation.
    E_UNEXPECTED | The method was called at a time when the view object was not expecting it.

A user viewing an Active Document in a container application such as a browser or binder might want to see two or more views or documents at once. Because the browser displays only one view at a time, the container needs a way to ask the other views or documents to display themselves, as required, in separate windows. The IOleDocumentView::Open method provides that way.

Notes to Callers

A successful call to IOleDocumentView::Open should be followed by a call to IOleDocumentView::Show to hide the window or to show the window and bring it to the foreground. While the view is active in its separate window, a container can show or hide the window as many times as it may require.

Notes to Implementers

An Active Document indicates that it does not support activation in a separate window by setting the DOCMISC_CANTOPENEDIT status flag and returning E_NOTIMPL to containers that call this method.

Implementation consists mainly of the view object calling its own IOleInPlaceObject::InPlaceDeactivate method, which leaves the Active Document in a running state but without in-place activation. The Active Document's user interface is not visible until the container calls IOleDocumentView::Show (see "Notes to Callers" above).

See also IOleDocumentView::CloseView, IOleDocumentView::UIActivate, IOleInPlaceSite, IOleInPlaceSite::OnInPlaceActivate


IOleDocumentView::SaveViewState

HRESULT SaveViewState(
    IStream *pstm
   );

Saves the view state into the specified stream.

  • Returns one of the following values:
    S_OK | Success.
    E_NOTIMPL | This view has no meaningful state to save.
    E_POINTER | The value in the pstm parameter is NULL.
pstm
[in] Address of the stream in which the view is to save its state data.

The E_NOTIMPL error should be rare because most views have at least some state information worth saving.

The view's state includes properties such as the view type, zoom factor, and location of insertion point. The container typically calls this function before deactivating the view. The stream can then later be used to reinitialize a view of the same document to this saved state through the IOleDocumentView::ApplyViewState method.

According to the rules governing the IPersistStream interface, a view must write its class identifier (CLSID) as the first element in the stream. Any cross-platform file format compatibility issues that apply to the document's storage representation also apply to this context.


IOleDocumentView::SetInPlaceSite

HRESULT SetInPlaceSite(
    IOleInPlaceSite * pIPSite
   );

Associates a container's document view site with a document's document view object.

  • Returns S_OK if successful, or E_FAIL otherwise.
pIPSite
[in] Address of the document view site's IOleInPlaceSite interface. Can be NULL, in which case the document view object loses all association with the container.

As part of activating an Active Document, a container must pass the object a pointer to the container's implementation of IOleInPlaceSite. This pointer designates the document view site that is to be associated with the view on which this method is called.

A container normally passes this pointer in response to a document's request to be activated. A document makes such a request by calling IOleDocumentSite::ActivateMe and passing the container a pointer to the view to be activated. The container, in turn, uses this pointer to call IOleDocumentView::SetInPlaceSite.

Notes to Callers

If the container is requesting creation and activation of a new instance of an Active Document, rather than merely the activation of a loaded instance of an Active Document, the view site is passed in the pIPSite argument of IOleDocument::CreateView. Therefore, an explicit call to IOleDocumentView::SetInPlaceSite is unnecessary.

Notes to Implementers

If this method is called on a view that is already associated with a view site, the view must do some housekeeping in preparation for activating itself in the new site. First, the view must deactivate itself in the current site and release its pointer to that site. Next, if the new IOleInPlaceSite pointer is not NULL, the view should both save the pointer and call IUnknown::AddRef on it. The view should then wait for the container to tell it when to activate itself in the new view site.

A document view must implement this method completely; E_NOTIMPL is not an acceptable return value.

See also IOleDocumentView::GetInPlaceSite


IOleDocumentView::SetRect

[input_sync] HRESULT SetRect(
    LPRECT prcView
   );

Sets the rectangular coordinates of the view port in which a view is to be activated, or resets the coordinates of the view port in which a view is currently activated.

  • Returns S_OK if successful, or E_FAIL otherwise.
prcView
[in] Address of a RECT structure containing the coordinates of the view port.

For a single document interface (SDI) application, the view port is the client area of the frame window minus the space allocated for toolbars, status bar, and such. For a multiple document interface (MDI) window, the view port is the client area of the MDI document window minus any other frame-level user-interface elements.

Notes to Callers

Calling IOleDocumentView::SetRect or IOleDocumentView::SetRectComplex is part of the normal activation sequence for Active Documents, usually following a call to IOleDocumentView::UIActivate and preceding a call to IOleDocumentView::Show.

Whenever the window used to display an Active Document is resized, the container should call IOleDocumentView::SetRect (or IOleDocumentView::SetRectComplex) to tell the document view object to resize itself to the new window dimensions.

Notes to Implementers

The coordinates of the view port are within the coordinates of the view window, which is obtained through IOleWindow::GetWindow. The view must resize itself to fit the new coordinates passed in the prcView parameter.

This method is defined with the [input_sync] attribute, which means that the view object cannot yield or make another, non-input_sync RPC call while executing this method.

A document view must implement this method completely; E_NOTIMPL is not an acceptable return value.


IOleDocumentView::SetRectComplex

[input_sync] HRESULT SetRectComplex(
    LPRECT prcView,
    LPRECT prcHScroll,
    LPRECT prcVScroll,
    LPRECT prcSizeBox
   );

Sets the rectangular coordinates of the view port, horizontal and vertical scroll bars, and the size box.

  • Returns one of the following values:
    S_OK | Success.
    E_FAIL | The method was not successful.
    E_NOTIMPL | The Active Document doesn't support complex rectangles.
prcView
[in] Address of a RECT structure containing the coordinates of the view port.
prcHScroll
[in] Address of a RECT structure containing the coordinates of the horizontal scroll bar.
prcVScroll
[in] Address of a RECT structure containing the coordinates of the vertical scroll bar.
prcSizeBox
[in] Address of a RECT structure containing the coordinates of the size box.

View frames that support a workbook metaphor, in which a single document comprises multiple sheets or pages, typically call this method to set the coordinates to be used in common by all the sheets or pages.

Notes to Callers

Calling IOleDocumentView::SetRectComplex is part of the normal activation sequence for Active Documents that support complex rectangles, usually following a call to IOleDocumentView::UIActivate and preceding a call to IOleDocumentView::Show.

Whenever the window used to display an Active Document is resized, the container should call IOleDocumentView::SetRectComplex or IOleDocumentView::SetRect to tell the view object to resize itself to the new window dimensions.

Notes to Implementers

Active Documents that support complex rectangles mark themselves with DOCMISC_SUPPORTCOMPLEXRECTANGLES, as described in DOCMISC and IOleDocument::GetDocMiscStatus. Active Documents that do not support this method can return E_NOTIMPL.

Upon receiving a call to this method, a view should resize itself to fit the coordinates specified in prcView and fit its scroll bars and size box to the areas described in prcHScroll, prcVScroll, and prcSizeBox.

This method is defined with the [input_sync] attribute, which means that the implementing object cannot yield or make another, non-input_sync RPC call while executing this method.


IOleDocumentView::Show

HRESULT Show(
    BOOL fShow
   );

Requests a view to show or hide itself, activating or deactivating itself as necessary.

  • Returns one of the following values:
    S_OK | Success.
    E_FAIL | The method was not successful.
    E_OUTOFMEMORY | There was not enough memory to complete this operation.
    E_UNEXPECTED | The method was called at an unexpected time. This is most likely due to the fact that the view object was not properly initialized before the Show method was called.
fShow
[in] If nonzero, the view is to in-place activate itself if necessary and show itself. If zero, the view is to UI-deactivate itself if necessary and hide itself.

Calling IOleDocumentView::Show is the last step in the activation sequence; before showing itself an Active Document must know exactly what space it occupies and have all its tools available.

Notes to Callers

A call to this method for the purpose of activating a view should follow calls to IOleDocumentView::SetInPlaceSite, IOleDocumentView::UIActivate, and IOleDocumentView::SetRect (or IOleDocumentView::SetRectComplex).

Notes to Implementers

Implementations of this method should embody the following pseudo-code.

if (fShow)
    {
    if(!fInPlaceActive)
        {
        //In-place activate the view but do not UI activate it.
        }

    //Show the view window.
    }
else
    {
    if(fUIActive)
        {
        //UI-deactivate the view.
        }

    //Hide the view window.
    }

All views of an Active Document must at least support in-place activation; E_NOTIMPL is not an acceptable return value.


IOleDocumentView::UIActivate

HRESULT UIActivate(
    BOOL fUIActivate
   );

Activates or deactivates a document view's user interface elements, such as menus, toolbars, and accelerators.

  • Returns one of the following values:
    S_OK | Success.
    E_FAIL | The method was not successful.
    E_OUTOFMEMORY | There was not enough memory to complete this operation.
    E_UNEXPECTED | The method was called at a time when it was not expected by the view object.
fUIActivate
[in] If TRUE, the view is to activate its user interface. If FALSE, the view is to deactivate its user interface.

Notes to Callers

Calling this method before calling IOleDocumentView::SetInPlaceSite returns E_UNEXPECTED, because the view must be associated with a view site before it can activate itself.

When IOleDocumentView::UIActivate is called as part of the activation sequence, the call should precede a call to IOleDocumentView::SetRect or IOleDocumentView::SetRectComplex, because otherwise the view dimensions would not account for toolbar space.

To deactivate a view, the container should call IOleDocumentView::Show with FALSE, followed by IOleDocumentView::UIActivate with FALSE.

Notes to Implementers

Implementations of this method should embody the following pseudo-code.

if (fActivate)
    {
    UI activate the view (do menu merging, show frame level tools, process accelerators)
    Take focus, and bring the view window forward.
    }
else
    call IOleInPlaceObject::UIDeactivate on this view

In addition, the view can and should participate in extended Help menu merging.

All views of an Active Document must support in-place activation. E_NOTIMPL is not an acceptable return value.


IPrint

The IPrint interface enables compound documents in general and Active Documents in particular to support programmatic printing. When a document is loaded, containers and other clients can call IPrint::Print to instruct a document to print itself, specifying printing control flags, the target device, the particular pages to print, and other options. The client can control the continuation of printing by calling the IContinueCallback interface.

An object that implements IPrint registers itself with the Printable key stored under its CLSID as follows:

HKEY_CLASSES_ROOT\CLSID\{...}\Printable

Callers determine whether a particular object class supports programmatic printing of its persistent state by looking in the registry for this key.

When to Implement

Implementing IPrint is optional. You usually implement IPrint on a view object.

When to Use

Call the methods of IPrint when you want an object to print its current state. Using this interface, a caller can tell the object to print itself, set the initial page number of the printed document, and obtain both the number of pages and the number of the first page to be printed.

Methods in Vtable Order
SetInitialPageNum | Sets the page number of the first page.
GetPageInfo | Retrieves the number of pages and the number of the first page.
Print | Prints the object's persistent state.


IPrint::GetPageInfo

HRESULT GetPageInfo(
    LONG *pnFirstPage,
    LONG *pcPages
   );

Retrieves the number of a document's first page and the total number of pages.

  • Returns S_OK if successful, or E_UNEXPECTED otherwise.
pnFirstPage
[out] Address of the page number of the first page. Can be NULL, indicating that the caller doesn't need this number. If the IPrint::SetInitialPageNum method has been called, this parameter should contain the same value passed to that method. Otherwise, the value is the document's internal first page number.
pcPages
[out] Address of the total number of pages in this document. Can be NULL, indicating that the caller doesn't need this number.


IPrint::Print

HRESULT Print(
    DWORD grfFlags,
    DVTARGETDEVICE **pptd,
    PAGESET **ppPageSet,
    STGMEDIUM *pstgmOptions,
    IContinueCallback *pCallback,
    LONG nFirstPage,
    LONG *pcPagesPrinted,
    LONG *pnLastPage
   );

Prints an object on the specified printer, using the specified job requirements.

  • Returns one of the following values:
    S_OK | Success.
    PRINT_E_CANCELED | Printing was canceled before the operation completed.
    PRINT_E_NOSUCHPAGE | A page specified in ppPageSet or nFirstPage does not exist.
    E_UNEXPECTED | The method was called when it was not expected by the object.
grfFlags
[in] Bit field specifying print options from the PRINTFLAG enumeration.
pptd
[in, out] Address of the target printing device.
ppPageSet
[in, out] Address of a PAGESET structure that indicates which pages are to be printed.
pstgmOptions
[unique][in, out] Address of object-specific printing options in a serialized OLE property set. Can be NULL on input or return.
pCallback
[in] Address of the IContinueCallback interface on the view site, which is to be periodically polled at human-response speeds to determine whether printing should be abandoned. Can be NULL.
nFirstPage
[in] Page number of the first page to be printed. This value overrides any value previously passed to the IPrint::SetInitialPageNum method.
pcPagesPrinted
[out] Address of the actual number of pages successfully printed.
pnLastPage
[out] Address of the page number of the last page printed.

The printer on which the object is to be printed is indicated by the DVTARGETDEVICE structure pointed to by pptd. The DEVMODE structure in the target device indicates whole-job printer-specific options, such as number of copies, paper size, and print quality. The DEVMODE structure can also contain orientation information in the dmOrientation member (this is indicated in the dmFields member). If present, this paper orientation should be used; if absent, natural orientation as determined by the object content is to be used.

Due to the possibility of user input, the parameters pptd and ppPageSet are both [in,out] structures. In the absence of user interaction (that is, if the PRINTFLAG_PROMPTUSER flag is not set), both the target device and the page set will necessarily be the same for input and output. However, if the user is prompted for print options, the object returns target device and page-set information appropriate to what the user has actually chosen.

The pstgmOptions parameter is also [in,out]. On exit, the object should write to pstgmOptions any object-specific information that it would need to reproduce this exact print job. Examples might include whether the user selected "sheet, notes, or both" in a spreadsheet application. The data passed is in the format of a serialized property set. The data is normally useful only when passed back in a subsequent call to the same object. Because a subsequent call might specify different user interaction flags, target device, or other settings, the caller can cause the document to be printed multiple times in the same way in slightly different printing contexts.


IPrint::SetInitialPageNum

HRESULT SetInitialPageNum(
    LONG nFirstPage
   );

Sets the number of the first page of a document.

  • Returns one of the following values:
    S_OK | Success.
    E_FAIL | The method was not successful.
    E_UNEXPECTED | The method was called when the object was not expecting it.
nFirstPage
[in] Desired number of the first page.

Setting the first page to a negative number is valid. This can be useful in printing a portion of the document with page numbers that specify an offset from the usual pagination.

Not all implementations permit the initial page number to be set, as some implementations simply lack the information as to how the page number should be presented in the final output.


Structures

This section describes the Active Document structures.
OLECMD
OLECMDTEXT
PAGERANGE
PAGESET


OLECMD

typedef struct _tagOLECMD
{
    ULONG cmdID;
    DWORD cmdf;
}OLECMDF;

Associates command flags from the OLECMDF enumeration with a command identifier through a call to the IOleCommandTarget::QueryStatus method.

cmdID
Command identifier; taken from the OLECMDID enumeration.
cmdf
Flags associated with cmdID; taken from the OLECMDF enumeration.


OLECMDTEXT

typedef struct _tagOLECMDTEXT
{
    DWORD cmdtextf;
    ULONG cwActual;
    ULONG cwBuf;
    wchar_t rgwz[1];
}OLECMDTEXT;

Specifies a text name or status string for a single command identifier. This structure is used with the IOleCommandTarget::QueryStatus method.

cmdtextf
Value from the OLECMDTEXTF enumeration describing whether the rgwz member contains a command name or status text.
cwActual
Number of characters actually written into the rgwz buffer before IOleCommandTarget::QueryStatus returns.
cwBuf
Number of elements in the rgwz buffer.
rgwz
Caller-allocated wide character buffer to receive the command name or status text.


PAGERANGE

typedef struct tagPAGERANGE
{
    LONG nFromPage;
    LONG nToPage;
}PAGERANGE;

Specifies a range of pages. The arrays contained in the rgPages member of the PAGESET structure are PAGERANGE structures.

nFromPage
First page of the range. This member can have any page number as a value. If this value is greater than the value specified in the nToPage member, the document will be printed in reverse page order.
nToPage
Last page of the range. A special value, PAGESET_TOLASTPAGE, indicates that all the remaining pages should be printed. This member can have any page number as a value. If this value is less than the value specified in the nFromPage member, the document will be printed in reverse page order.


PAGESET

typedef struct tagPAGESET
{
    ULONG cbStruct;
    BOOL fOddPages;
    BOOL fEvenPages;
    ULONG cPageRange;
    [size_is(cPageRange)] PAGERANGE rgPages[]
}PAGESET;

Identifies one or more page ranges to be printed and, optionally, identifies only the even or odd pages as part of a page set.

cbStruct
Number of bytes in this instance of the PAGESET structure. Must be a multiple of 4.
fOddPages
If TRUE, only the odd-numbered pages in the page set indicated by the rgPages member are to be printed.
fEvenPages
If TRUE, only the even-numbered pages in the page set indicated by the rgPages member are to be printed.
cPageRange
Number of page-range pairs specified in rgPages.
rgPages
Address of an array of PAGERANGE structures specifying the pages to be printed. One or more page ranges can be specified, as long as the number of page ranges is the value of the cPageRange member. The page ranges must be sorted in ascending order and must be nonoverlapping.


Enumerations

This section describes the Active Document enumerations.
DOCMISC
OLECMDEXECOPT
OLECMDF
OLECMDID
OLECMDTEXTF
PRINTFLAG


DOCMISC

typedef enum
{
    DOCMISC_CANCREATEMULTIPLEVIEWS    =  1,
    DOCMISC_SUPPORTCOMPLEXRECTANGLES  =  2,
    DOCMISC_CANTOPENEDIT,             =  4,
    DOCMISC_NOFILESUPPORT             =  8
} DOCMISC;

Provides miscellaneous property information about an Active Document. A combination of values from DOCMISC is returned at the location specified by the pdwStatus parameter in the IOleDocument::GetDocMiscStatus method.

DOCMISC_CANCREATEMULTIPLEVIEWS
Object supports multiple views.
DOCMISC_SUPPORTCOMPLEXRECTANGLES
Object supports complex rectangles and therefore implements the IOleDocumentView::SetRectComplex method.
DOCMISC_CANTOPENEDIT
Object supports activation in a separate window and therefore implements the IOleDocumentView::Open method.
DOCMISC_NOFILESUPPORT
Object does not support read/write files.

Objects that have a limited user interface for activation purposes should set DOCMISC_CANTOPENEDIT. Those that support the IPersistStorage interface only as a persistence mechanism should specify DOCMISC_NOFILESUPPORT. Otherwise, an object must also implement the IPersistFile interface.

If an object requires none of these flags, it must write a zero to the pdwStatus parameter of the IOleDocument::GetDocMiscStatus method.


OLECMDEXECOPT

typedef enum
{
    OLECMDEXECOPT_DODEFAULT      =  0,
    OLECMDEXECOPT_PROMPTUSER     =  1,
    OLECMDEXECOPT_DONTPROMPTUSER =  2,
    OLECMDEXECOPT_SHOWHELP       =  3
} OLECMDEXECOPT;

Specifies command execution options. One value from this enumeration is passed in the nCmdExecOpt parameter of the IOleCommandTarget::Exec method.

OLECMDEXECOPT_DODEFAULT
Use the default behavior, whether prompting the user for input or not.
OLECMDEXECOPT_PROMPTUSER
Execute the command after obtaining user input.
OLECMDEXECOPT_DONTPROMPTUSER
Execute the command without prompting the user. For example, clicking the Print toolbar button causes a document to be immediately printed without user input.
OLECMDEXECOPT_SHOWHELP
Show help for the corresponding command, but do not actually execute the command.


OLECMDF

typedef enum
{
    OLECMDF_SUPPORTED   =  1,
    OLECMDF_ENABLED     =  2,
    OLECMDF_LATCHED     =  4,
    OLECMDF_NINCHED     =  8
} OLECMDF;

Designates the type of support provided by an object for the command specified in an OLECMD structure.

OLECMDF_SUPPORTED
Command is supported by this object.
OLECMDF_ENABLED
Command is available and enabled.
OLECMDF_LATCHED
Command is an on-off toggle and is currently on.
OLECMDF_NINCHED
Reserved for future use.

Values from the OLECMDF enumeration are used to fill the value of the cmdf member of OLECMD structures passed to the IOleCommandTarget::QueryStatus method.


OLECMDID

typedef enum
{
     OLECMDID_OPEN               =  1,
     OLECMDID_NEW                =  2,
     OLECMDID_SAVE               =  3,
     OLECMDID_SAVEAS             =  4,
     OLECMDID_SAVECOPYAS         =  5,
     OLECMDID_PRINT              =  6,
     OLECMDID_PRINTPREVIEW       =  7,
     OLECMDID_PAGESETUP          =  8,
     OLECMDID_SPELL              =  9,
     OLECMDID_PROPERTIES         = 10,
     OLECMDID_CUT                = 11,
     OLECMDID_COPY               = 12,
     OLECMDID_PASTE              = 13,
     OLECMDID_PASTESPECIAL       = 14,
     OLECMDID_UNDO               = 15,
     OLECMDID_REDO               = 16,
     OLECMDID_SELECTALL          = 17,
     OLECMDID_CLEARSELECTION     = 18,
     OLECMDID_ZOOM               = 19,
     OLECMDID_GETZOOMRANGE       = 20
     OLECMDID_UPDATECOMMANDS     = 21
     OLECMDID_REFRESH            = 22
     OLECMDID_STOP               = 23
     OLECMDID_HIDETOOLBARS       = 24
     OLECMDID_SETPROGRESSMAX     = 25
     OLECMDID_SETPROGRESSPOS     = 26
     OLECMDID_SETPROGRESSTEXT    = 27
     OLECMDID_SETTITLE           = 28
     OLECMDID_SETDOWNLOADSTATE   = 29
     OLECMDID_STOPDOWNLOAD       = 30
     OLECMDID_ONTOOLBARACTIVATED = 31,
     OLECMDID_FIND               = 32,
     OLECMDID_DELETE             = 33,
     OLECMDID_HTTPEQUIV          = 34,
     OLECMDID_HTTPEQUIV_DONE     = 35,
     OLECMDID_ENABLE_INTERACTION = 36,
     OLECMDID_ONUNLOAD           = 37
} OLECMDID;

Specifies which command to execute. (The commands in this enumeration are the standard commands defined by Microsoft® Office 95.) A single value from this enumeration is passed in the nCmdID parameter of the IOleCommandTarget::Exec method.

OLECMDID_OPEN
File menu, Open command.
OLECMDID_NEW
File menu, New command.
OLECMDID_SAVE
File menu, Save command.
OLECMDID_SAVEAS
File menu, Save As command.
OLECMDID_SAVECOPYAS
File menu, Save Copy As command.
OLECMDID_PRINT
File menu, Print command.
OLECMDID_PRINTPREVIEW
File menu, Print Preview command.
OLECMDID_PAGESETUP
File menu, Page Setup command.
OLECMDID_SPELL
Tools menu, Spelling command.
OLECMDID_PROPERTIES
File menu, Properties command.
OLECMDID_CUT
Edit menu, Cut command.
OLECMDID_COPY
Edit menu, Copy command.
OLECMDID_PASTE
Edit menu, Paste command.
OLECMDID_PASTESPECIAL
Edit menu, Paste Special command.
OLECMDID_UNDO
Edit menu, Undo command.
OLECMDID_REDO
Edit menu, Redo command.
OLECMDID_SELECTALL
Edit menu, Select All command.
OLECMDID_CLEARSELECTION
Edit menu, Clear command.
OLECMDID_ZOOM
View menu, Zoom command (see below for details).
OLECMDID_GETZOOMRANGE
Retrieves zoom range applicable to View Zoom (see below for details).
OLECMDID_UPDATECOMMANDS
Informs the receiver, usually a frame, of state changes. The receiver can then query the status of the commands whenever convenient.
OLECMDID_REFRESH
Asks the receiver to refresh its display. Implemented by the document/object.
OLECMDID_STOP
Stops all current processing. Implemented by the document/object.
OLECMDID_HIDETOOLBARS
View menu, Toolbars command. Implemented by the document/object to hide its toolbars.
OLECMDID_SETPROGRESSMAX
Sets the maximum value of a progress indicator if one is owned by the receiving object, usually a frame. The minimum value is always zero.
OLECMDID_SETPROGRESSPOS
Sets the current value of a progress indicator if one is owned by the receiving object, usually a frame.
OLECMDID_SETPROGRESSTEXT
Sets the text contained in a progress indicator if one is owned by the receiving object, usually a frame. If the receiver currently has no progress indicator, this text should be displayed in the status bar (if one exists) as with the IOleInPlaceFrame::SetStatusText method.
OLECMDID_SETTITLE
Sets the title bar text of the receiving object, usually a frame.
OLECMDID_SETDOWNLOADSTATE
Sent by the object when its download state changes. Takes a VT_BOOL parameter, which is TRUE if the object is downloading data or FALSE if it is not.
OLECMDID_STOPDOWNLOAD
Stops the download when executed. Typically, this command is propagated to all contained objects. When queried, sets MSOCMDF_ENABLED. Implemented by the document/object.
OLECMDID_ONTOOLBARACTIVATED
Notifies the object that one of the container's toolbars has received the focus. The object should UI-deactivate any child objects, hide any selection, and set an internal flag. The object should reset this flag when the object gets notified that it or one of its child objects or toolbars has received the focus. While this flag is set, the object should call the container's IOleCommandTarget::QueryStatus method to determine the state of edit items (cut, copy, and paste) and call the container's IOleCommandTarget::Exec method to forward any edit commands to the container. If the object has edit items in its toolbars or menus, they should also be enabled or disabled according to the container's IOleCommandTarget::QueryStatus results. The container's IOleCommandTarget interface can be retrieved by calling the container's QueryInterface method, requesting IID_IOleCommandTarget.
OLECMDID_FIND
Not currently implemented.
OLECMDID_DELETE
TBD.
OLECMDID_HTTPEQUIV
Sent from the Active Document to the container when the parser finds a META HTTP-EQUIV tag in the document. Takes a VT_BSTR parameter that contains the parameter to the META tag, which is equivalent to an HTTP header. The Active Document can send this command more than once, but must send OLECMDID_HTTPEQUIV_DONE after all the META HTTP-EQUIV tags are processed.
OLECMDID_HTTPEQUIV_DONE
Sent from the Active Document to the container when there are no other META HTTP-EQUIV tags in the document. The Active Document must send this command after sending one or more OLECMDID_HTTPEQUIV commands.
OLECMDID_ENABLE_INTERACTION
Sent by the container to tell the object to either pause or resume any multimedia (audio or animation) in the document. Takes a VT_BOOL parameter, which is TRUE if the multimedia should be resumed or FALSE if it should be paused. The Internet Explorer 4.0 browser uses this command to inform the object of when it is minimized or completely covered by another window so that the object can pause playing of multimedia information.
OLECMDID_ONUNLOAD
Sent by the browser before a new navigation is initiated or the browser is being closed. This provides the opportunity for a document to query the user if any unsaved data should be saved. Returning S_OK allows the navigation to close. Returning S_FALSE prevents the operation. This command is sent only once. Because navigation is asynchronous, it is important to remember that the user can make changes in a page or document after this command is received but before the new page is displayed.

In OLE compound documents technology, an object that is being edited in-place disables the Zoom control on its toolbar and the Zoom command on its View menu, because the Zoom command applies logically to the container document, not to the object. The OLECMDID_ZOOM and OLECMDID_GETZOOMRANGE commands notify the container's frame object of the zoom range it should use to display an Active Document in its user interface. The container frame is the client-side object that implements IOleInPlaceFrame and, optionally, IOleCommandTarget.

The OLECMDID_ZOOM command takes one LONG argument as input and writes one LONG argument on output. This command is used for three purposes:

  • To query the current zoom value. The caller of IOleCommandTarget::Exec passes OLECMDEXECOPT_DONTPROMPTUSER as the execute option in the nCmdExecOpt parameter and NULL for pvaIn. The object returns the current zoom value in pvaOut. When the object goes UI active, it retrieves the current zoom value from the container's frame object using this same mechanism and updates its zoom control with the returned value.
  • To display the Zoom dialog box. The caller of IOleCommandTarget::Exec passes OLECMDEXECOPT_PROMPTUSER in nCmdExecOpt. The caller can optionally pass the initial value for the dialog box through pvaIn; otherwise pvaIn must be NULL. If the user clicks Cancel, the object returns OLECMDERR_E_CANCELED. If the user clicks OK, the object passes the user-selected value in pvaOut. When the user chooses the Zoom command from the View menu, the object calls the container's frame object in the same manner. The container then zooms the document to the user-selected value, and the object updates its Zoom control with that value.
  • To set a Zoom value. The caller of IOleCommandTarget::Exec passes OLECMDEXECOPT_DONTPROMPTUSER in nCmdExecOpt and passes the zoom value to apply through pvaIn. The object validates and normalizes the new value and returns the validated value in pvaOut. When the user selects a new zoom value (using the Zoom control on the toolbar, for instance), the object calls the container's frame object in this manner. The container zooms the document to 100 percent, and the object updates the Zoom control with that value.

The OLECMDID_GETZOOMRANGE command is used to determine the range of valid zoom values from an object that implements IOleCommandTarget. The caller passes MSOCMDEXECOPT_DONTPROMPTUSER in nCmdExecOpt and NULL for pvaIn. The object returns its zoom range as a DWORD in pvaOut, where the HIWORD contains the maximum zoom value and the LOWORD contains the minimum zoom value. Typically this command is used when the user drops down the Zoom control on the toolbar of the UI-active object. The applications and objects that support this command are required to support all the integral zoom values that are within the (min,max) pair they return.


OLECMDTEXTF

typedef enum
{
    OLECMDTEXTF_NONE   =  0,
    OLECMDTEXTF_NAME   =  1,
    OLECMDTEXTF_STATUS =  2
} OLECMDTEXTF;

Specifies the type of information that an object should store in the OLECMDTEXT structure passed in the IOleCommandTarget::QueryStatus method. One value from this enumeration is stored in the cmdtextf member of the OLECMDTEXT structure to indicate the desired information.

OLECMDTEXTF_NONE
No extra information is requested.
OLECMDTEXTF_NAME
Object should provide the localized name of the command.
OLECMDTEXTF_STATUS
Object should provide a localized status string for the command.


PRINTFLAG

typedef enum
{
    PRINTFLAG_MAYBOTHERUSER         =  1,
    PRINTFLAG_PROMPTUSER            =  2,
    PRINTFLAG_USERMAYCHANGEPRINTER  =  4,
    PRINTFLAG_RECOMPOSETODEVICE     =  8,
    PRINTFLAG_DONTACTUALLYPRINT     = 16,
    PRINTFLAG_FORCEPROPERTIES       = 32,
    PRINTFLAG_PRINTTOFILE           = 64
} PRINTFLAG;

Specifies printing options. A combination of values from PRINTFLAG is passed in the grfFlags parameter of IPrint::Print.

PRINTFLAG_MAYBOTHERUSER
User interaction is permitted. If this flag is not set, no part of the printing process can interact with the user.
PRINTFLAG_PROMPTUSER
Object should prompt the user for job-specific printing options, using the normal print dialog for the object. Support for this option is required. Valid only if PRINTFLAG_MAYBOTHERUSER is set.
PRINTFLAG_USERMAYCHANGEPRINTER
User can change the target printer. If this flag is not set, the user can print only on the default printer. Valid only if PRINTFLAG_PROMPTUSER is set.
PRINTFLAG_RECOMPOSETODEVICE
Object should attempt to recompose itself to the indicated target device. If this flag is not set, the object should, if possible, retain any association that it already has with a compositional device.
PRINTFLAG_DONTACTUALLYPRINT
Object should carry out actions resulting from user actions or object recomposition, but it should not actually carry out the printing operation.
PRINTFLAG_FORCEPROPERTIES
Object should always display the print properties dialog box.
PRINTFLAG_PRINTTOFILE
Object should print to the file that is named in the portname member of the DVTARGETDEVICE structure.


ActiveX Scripting

Microsoft® ActiveX™ Scripting introduces a new way for an application to add scripting and OLE Automation capabilities. With the advent of ActiveX Scripting, hosts can call upon disparate scripting engines from multiple sources and vendors to perform scripting between components. The implementation of the script itself—language, syntax, persistent format, execution model, and so on—is left to the script vendor. The designers have taken care to allow hosts that rely on ActiveX Scripting to use arbitrary language back ends.

ActiveX Scripting Hosts

ActiveX Scripting Engines

IDispatchEx

IProvideMultipleClassInfo

Active Script Debugging Interfaces

ActiveX Scripting Terms

This list contains definitions of the scripting-related terms used in this document.
Term | Definition
Code object | An instance created by the scripting engine that is associated with a named item, such as the module behind a form in Visual Basic®, or a C++ class associated with a named item. Preferably, this is an OLE Component Object Model (COM) object that supports OLE Automation so the host or other nonscript entity can manipulate the code object.
Named item | An OLE COM object (preferably one that supports OLE Automation) that the host deems interesting to the script. Examples include the HTML Page and Browser in a Web browser, and the Document and Dialogs in Microsoft Word.
Script | The data that makes up the program that the scripting engine runs. A script can be any contiguous executable data, including pieces of text, blocks of pcode, or even machine-specific executable byte codes. A host loads a script into the scripting engine through one of the IPersist* interfaces or through the IActiveScriptParse interface.
Scripting engine | The OLE object that processes scripts. A scripting engine implements the IActiveScript and, optionally, IActiveScriptParse interfaces.
Scripting host | The application or program that owns the ActiveX Scripting engine. The host implements the IActiveScriptSite and, optionally, IActiveScriptSiteWindow interfaces.
Scriptlet | A portion of a script that gets attached to an object through the IActiveScriptParse interface. The aggregate collection of scriptlets is the script.
Script language | The language in which a script is written (VBScript, for example) and the semantics of that language.

ActiveX Scripting Background

ActiveX Scripting components fall into two categories: ActiveX Scripting hosts and ActiveX Scripting engines. A host creates a scripting engine and calls on the engine to run the scripts. Examples of ActiveX Scripting hosts include:

  • Microsoft Internet Explorer
  • Internet authoring tools
  • Shell

ActiveX Scripting engines can be developed for any language or run-time environment, including:

  • Microsoft Visual Basic for Applications (VBA)
  • Microsoft Visual Basic Scripting Edition (VBScript)
  • Perl
  • Lisp

To make implementation of the host as flexible as possible, an OLE Automation wrapper for ActiveX Scripting is provided. However, a host that uses this wrapper object to instantiate the scripting engine does not have the degree of control over the run-time name space, the persistence model, and so on, that it would if it used ActiveX Scripting directly.

The ActiveX Scripting design isolates the interface elements required only in an authoring environment so that nonauthoring hosts (such as browsers and viewers) and script engines (for example, VBScript) can be kept lightweight.

ActiveX Scripting Basic Architecture

The following illustration shows the interaction between an ActiveX Scripting host and an ActiveX Scripting engine.

Following is a description of the steps involved in the interaction between the host and engine. The actual nesting of the function calls is omitted for clarity.

  1. Create a Project. The host loads a project or document. (This step is not particular to ActiveX Scripting, but is included here for completeness.)
  2. Create the ActiveX Scripting Engine. The host calls CoCreateInstance to create a new ActiveX Scripting engine, specifying the class identifier (CLSID) of the specific scripting engine to use. For example, the HTML browsing component of Internet Explorer receives the scripting engine's class identifier through the CLSID= attribute of the HTML <OBJECT> tag.
  3. Load the Script. If the script contents have been persisted, the host calls the script engine's IPersist*::Load method to feed it the script storage, stream, or property bag. Otherwise, the host uses either the IPersist*::InitNew or IActiveScriptParse::InitNew method to create a null script. A host that maintains a script as text can use IActiveScriptParse::ParseScriptText to feed the scripting engine the text of the script, after calling IActiveScriptParse::InitNew.
  4. Add Named Items. For each top-level named item (such as pages and forms) imported into the scripting engine's name space, the host calls the IActiveScript::AddNamedItem method to create an entry in the engine's name space. This step is not necessary if top-level named items are already part of the persistent state of the script loaded in step 3. A host does not use IActiveScript::AddNamedItem to add sublevel named items (such as controls on an HTML page); rather, the engine indirectly obtains sublevel items from top-level items by using the host's ITypeInfo and IDispatch interfaces.
  5. Run the Script. The host causes the engine to start running the script by setting the SCRIPTSTATE_CONNECTED flag in the IActiveScript::SetScriptState method. This call would likely perform any scripting engine construction work, including static binding, hooking up to events (see below), and executing code, in a way similar to a scripted main() function.
  6. Get Item Information. Each time the script engine needs to associate a symbol with a top-level item, it calls the IActiveScriptSite::GetItemInfo method, which returns information about the given item.
  7. Hook Up Event Advise. Before starting the actual script, the scripting engine connects to the events of all the relevant objects through the IConnectionPoint interface.
  8. Invoke Properties and Methods. As the script runs, the scripting engine realizes references to methods and properties on named objects through IDispatch::Invoke or other standard OLE binding mechanisms.


Active Script Debugging API

Active Script Debugging API Active Script Debugging API *
  * Next Topic:

 Debug Property Interfaces

Active Script Debugging Interfaces

Active Script Debugging is a set of interfaces that allow language-neutral, host-neutral debugging, with support for a wide variety of development environments.

[ Architecture GIF]

By language neutral, we mean that a debugging environment can support essentially any programming language, without having to have specific knowledge of that language built into the environment. (Today, the interfaces focus primarily on support scripting languages such as VBScript and JScript, as well as Java, but in the future we intended evolve them to support native code in languages such as C++.) Furthermore, the debugger automatically has the ability to debug applications written in a mixture of multiple languages, with cross-language stepping and breakpoints.

By host neutral, we mean that the debugger can be automatically be used with any Active Scripting host, such as Internet Explorer, Spruuids, or any any other host written in the future. We also mean that the host has control over the structure of the document tree presented to the user and the contents and syntax coloring of the documents being debugged. This allows the host to present the source code being debugged in the context of the host document (for example, scripts can be show on an HTML page.) In the subsections below, we consider each of the key components in Active Debugging and their associated interfaces.

Before proceeding further, we should define several key Active Debugging concepts:

host application: The application that hosts the script engines and provides a scriptable set of objects (or "object model").

language engine: A component that provides parsing, execution, and debugging abstractions for a particular languages.

debugger IDE: The application that provides debugging UI by communicating with the host application and language engines.

machine debug manager: A component that maintains a registry of debuggable application processes.

process debug manager: A component that maintains the tree of debuggable documents a particular application, tracks the running threads, etc.

document context:: A document context is an abstraction representing a specific range in the source code of a host document.

code context: A code context represents a particular location in the running code of a language engine (a "virtual instruction pointer".)

expression context: A particular context (for example, a stack frame) in which expressions may be evaluated by a language engine.

object browsing: A structured, language-independent representation of an objects name, type, value, and subobjects, suitable for implementating a "watch window" UI.

Below, we provide an overview of each of the key Active Debugging components and their associated interfaces, followed by the details of those interfaces.

Language Engine

The language engine provides:

- Language parsing and execution

- Debugging support (breakpoints,etc..).

- Expression evaluation.

- Syntax coloring.

- Object browsing.

- Stack enumeration and parsing.

Below is a list of the key interfaces implemented by a language engine. These interfaces are used by the host application to map between its document context and the engine's code contexts, and also by the debugger UI to do expression evaluation, stack enumeration, and object browsing.

IActiveScriptDebug; // provides syntax coloring and code context enumeration

IActiveScriptErrorDebug; // returns document contexts and stack frames for errors

IActiveScriptSiteDebug; // host provided link from script engine to debugger

IActiveScriptTextInfo; // Language engine debugging abstractions

IDebugCodeContext; // a virtual "instruction pointer" in a thread

IEnumDebugCodeContexts;

IDebugStackFrame; // logical stack frame on the stack of a thread

IDebugExpressionContext; // a context in which expressions can be evaluated

IDebugStackFrameSniffer; // enumerator for stack frames known by an engine

IDebugExpressionContext; // context for expression evaluation

IDebugExpression; // an asynchronously evaluated expression

IDebugSyncOperation;

IDebugAsyncOperation;

IDebugAsyncOperationCallBack;

IDebugExpressionCallBack; // status events for IDebugExpression evaluation progress

IEnumDebugExpressionContexts;

IProvideExpressionContexts; // Object browsing

IDebugFormatter;

Hosts

The host:

- Hosts the language engines.

- Provides an object model (set of objects that can be scripted)

- defines a tree of documents that can be debugged and their contents

- Organizes scripts into virtual applications.

There are two kinds of hosts:

1. A dumb host supports just the basic ActiveScripting interfaces. It has no control over document structure or organizations; this is determined entirely by the scripts provided to the language engines.

2. A smart host supports a larger set of interface that allow it to define the document tree, document contents, and syntax coloring. There are a set of helper interfaces, described in the next subsection, which make it much easier for a host to be a smart host.

Smart-host Helper Interfaces

The IDebugDocumentHelper are a greatly simplified set of interfaces that a host can use to gain the benefits of smart-hosting without having to handle the full complexity (and power) of the full host interfaces.

A host is not required to use these interfaces, of course, but by using these can avoid having to implement or use a number of more complicated interfaces.

IDebugDocumentHelper; // implemented by PDM

IDebugDocumentHost; // implemented (optionally) by the host

Full Smart-host Interfaces

Below is the full set of interfaces that a smart-host needs to implement or use if it's not using the helper interfaces.

// implemented by host

IDebugDocumentInfo; // provides info on (possibily uninstantiated) doc

IDebugDocumentProvider; // allows doc to be instantiated on demand

IDebugDocument; // base document interface

IDebugDocumentText; // provides access to source text of document

IDebugDocumentTextEvents; // events fired when source text changes

IDebugDocumentTextAuthor;

IDebugDocumentContext; // represents a range within the document

// implemented by PDM on behalf of the host

IDebugApplicationNode; // represents the position of a doc in the hierarchy

IDebugApplicationNodeEvents; // events fired by PDM when document hierarchy changes

Debugger IDE

The IDE is a fully language independent debugging UI. It provides:

- Document viewers/editors.

- Breakpoint management.

- Expression evaluation and watch windows.

- Stack frame browsing.

- Object/Class browsing.

- Browsing the virtual application structure.

// Debugger implementation

IDebugSessionProvider; // establishes a debug session for a running application.

IApplicationDebugger; // primary interface exposed by a debugger IDE session

Machine Debug Manager

The machine debug manager provides the hookup point between virtual applications and debuggers by maintaining and enumerating a list of active virtual applications.

IMachineDebugManager;

IMachineDebugManagerCookie;

IMachineDebugManagerEvents;

IEnumRemoteDebugApplications;

Process Debug Manager

The PDM does the following:

- Synchronizes the debugging of multiple language engines.

- Maintains a tree of debuggable documents

- Merges stack frames.

- Coordinates breakpoints and stepping across language engines.

- Tracks threads.

- Maintains a debugger thread for asynchronous processing.

- Communicates with the machine debug manager and the debugger IDE.

The following are the interfaces provided by the process debug manager.

IProcessDebugManager; // creates, adds and removes virtual applications, etc.

IRemoteDebugApplication; // virtual application abstraction

IDebugApplication;

IRemoteDebugApplicationThread; // virtual thread abstraction

IDebugApplicationThread;

IEnumRemoteDebugApplicationThreads;

IDebugThreadCall; // dispatches marshalled calls

IDebugApplicationNode; // maintains a position for a document in the hierarchy

IEnumDebugApplicationNodes;

IEnumDebugStackFrames; // merged enumeration of stack frames from engines

Structures and Enumerations

These structures and enumerations are used in a number of the interfaces below.

BREAKPOINT_STATE

Indicates the state of a breakpoint.

typedef enum tagBREAKPOINT_STATE {

BREAKPOINT_DELETED = 0,// The breakpoint no longer exists but there are still references

BREAKPOINT_DISABLED = 1,// The breakpoint exists but is disabled

BREAKPOINT_ENABLED = 2// The breakpoint exists and is enabled

} BREAKPOINT_STATE;

APPBREAKFLAGS

Application break flags. Indicates the current debug state for the application and the current thread.

When certain of these bits are set (some are per-thread and some are for all threads), language engines should break at the next opportunity.

typedef DWORD APPBREAKFLAGS;

// DEBUGGER_BLOCK

// languages should break immediately with BREAKREASON_DEBUGGER_BLOCK

const APPBREAKFLAGSAPPBREAKFLAG_DEBUGGER_BLOCK= 0x00000001;

// DEBUGGER_HALT

// languages should break immediately with BREAKREASON_DEBUGGER_HALT

const APPBREAKFLAGSAPPBREAKFLAG_DEBUGGER_HALT= 0x00000002;

// STEP

// languages should break immediately in the stepping thread with BREAKREASON_STEP

const APPBREAKFLAGSAPPBREAKFLAG_STEP= 0x00010000;

// NESTED - the application is in nested execution on a breakpoint

const APPBREAKFLAGSAPPBREAKFLAG_NESTED= 0x00020000;

// STEP TYPES - defines whether we are stepping at source, bytecode, or machine level.

const APPBREAKFLAGSAPPBREAKFLAG_STEPTYPE_SOURCE= 0x00000000;

const APPBREAKFLAGSAPPBREAKFLAG_STEPTYPE_BYTECODE= 0x00100000;

const APPBREAKFLAGSAPPBREAKFLAG_STEPTYPE_MACHINE= 0x00200000;

const APPBREAKFLAGSAPPBREAKFLAG_STEPTYPE_MASK= 0x00F00000;

// BREAKPOINT IN_PROGRESS

const APPBREAKFLAGSAPPBREAKFLAG_IN_BREAKPOINT= 0x80000000;

BREAKREASON

Indicates the cause of hitting a breakpoint.

typedef enum tagBREAKREASON{

BREAKREASON_STEP,// Caused by the stepping mode

BREAKREASON_BREAKPOINT,// Caused by an explicit breakpoint

BREAKREASON_DEBUGGER_BLOCK,// Caused by another thread breaking

BREAKREASON_HOST_INITIATED,// Caused by host requested break

BREAKREASON_LANGUAGE_INITIATED,// Caused by a scripted break

BREAKREASON_DEBUGGER_HALT,// Caused by debugger IDE requested break

BREAKREASON_ERROR// Caused by an execution error

} BREAKREASON;

BREAKRESUME_ACTION

Describes how to continue from a breakpoint

typedef enum tagBREAKRESUME_ACTION{

BREAKRESUMEACTION_ABORT,// Abort the application

BREAKRESUMEACTION_CONTINUE,// Continue running

BREAKRESUMEACTION_STEP_INTO,// Step into a procedure

BREAKRESUMEACTION_STEP_OVER,// Step over a procedure

BREAKRESUMEACTION_STEP_OUT// Step out of the current procedure

} BREAKRESUMEACTION;

ERRORRESUME_ACTION

Describes how to continue from a runtime error.

typedef enum tagERRORRESUMEACTION {

ERRORRESUMEACTION_ReexecuteErrorStatement,// try executing the erroneous line again

ERRORRESUMEACTION_AbortCallAndReturnErrorToCaller,// let the language engine handle the error

ERRORRESUMEACTION_SkipErrorStatement,// resume execution from beyond the error

} ERRORRESUMEACTION;

DOCUMENTNAMETYPE

Describes what type of name you'd like to get for a document:

typedef enum tagDOCUMENTNAMETYPE {

DOCUMENTNAMETYPE_APPNODE,// Gets the name ass it appears in the app tree

DOCUMENTNAMETYPE_TITLE,// Gets the name as it appears on the doc viewer title bar

DOCUMENTNAMETYPE_FILE_TAIL,// Gets the filename without a path (for save as...)

DOCUMENTNAMETYPE_URL,// Gets the URL of the document, if any

} DOCUMENTNAMETYPE;

SOURCE_TEXT_ATTR

Describes the attributes of a single character of source text.

typedef WORD SOURCE_TEXT_ATTR;

// The character is a part of a language keyword. Example: while

const SOURCE_TEXT_ATTR SOURCETEXT_ATTR_KEYWORD= 0x0001;

// The character is a part of a comment block.

const SOURCE_TEXT_ATTR SOURCETEXT_ATTR_COMMENT= 0x0002;

// The character is not part of compiled language source text. Example:

// the HTML surrounding script blocks.

const SOURCE_TEXT_ATTR SOURCETEXT_ATTR_NONSOURCE= 0x0004;

// The character is a part of a language operator. Example: *

const SOURCE_TEXT_ATTR SOURCETEXT_ATTR_OPERATOR= 0x0008;

// The character is a part of a language numeric constant. Example: 1234

const SOURCE_TEXT_ATTR SOURCETEXT_ATTR_NUMBER= 0x0010;

// The character is a part of a language string constant. Example: "Hello World"

const SOURCE_TEXT_ATTR SOURCETEXT_ATTR_STRING= 0x0020;

// The character indicates the start of a function block

const SOURCE_TEXT_ATTR SOURCETEXT_ATTR_FUNCTION_START = 0x0040;

TEXT_DOC_ATTR

Describes the attributes of the document

typedef DWORD TEXT_DOC_ATTR;

// Indicates that the document is read-only.

const TEXT_DOC_ATTR TEXT_DOC_ATTR_READONLY = 0x00000001;

Parse Flags

Option flags used during IDebugExpressionContext::ParseLanguageText

// Indicates that the text is an expression as opposed to a statement. This

// flag may affect the way in which the text is parsed by some languages.

const DWORD DEBUG_TEXT_ISEXPRESSION= 0x00000001;

// If a return value is available, it will be used by the caller.

const DWORD DEBUG_TEXT_RETURNVALUE= 0x00000002;

// Don't allow side effects. If this flag is set, the evaluation of the

// expression should change no runtime state.

const DWORD DEBUG_TEXT_NOSIDEEFFECTS= 0x00000004;

// Allow breakpoints during the evaluation of the text. If this flag is not

// set then breakpoints will be ignored during the evaluation of the text.

const DWORD DEBUG_TEXT_ALLOWBREAKPOINTS= 0x00000008;

// Allow error reports during the evaluation of the text. If this flag is not

// set then errors will not be reported to the host during the evaluation.

const DWORD DEBUG_TEXT_ALLOWERRORREPORT= 0x00000010;

Script Engine Debugging Interfaces

Below are the interfaces that a script engine needs to support to provide debugging, expression evaluation, and object browsing.

IActiveScriptDebug

This interface is implemented by script engines that support debugging, and is QI-able from IActiveScript. It provides the means for:

1.Smart hosts to take over document management.

2.Process debug manager to synchronize debugging of multiple script engines.

[

object,

uuid(51973C10-CB0C-11d0-B5C9-00A0244A0E7A),

pointer_default(unique)

]

interface IActiveScriptDebug : IUnknown

{

// Returns the text attributes for an arbitrary block of script text. Smart hosts

// use this call to delegate GetText calls made on their IDebugDocumentText.

HRESULT GetScriptTextAttributes(

// The script block text. This string need not be null terminated.

[in, size_is(uNumCodeChars)]LPCOLESTRpstrCode,

// The number of characters in the script block text.

[in]ULONGuNumCodeChars,

// See IActiveScriptParse::ParseScriptText for a description of this argument.

[in]LPCOLESTRpstrDelimiter,

// See IActiveScriptParse::ParseScriptText for a description of this argument.

[in]DWORDdwFlags,

// Buffer to contain the returned attributes.

[in, out, size_is(uNumCodeChars)]SOURCE_TEXT_ATTR *pattr);

// Returns the text attributes for an arbitrary scriptlet. Smart hosts

// use this call to delegate GetText calls made on their IDebugDocumentText.

// Note: this call is provided because scriptlets tend to be expressions and

// may have a different syntax than a script block. For many languages the implementation

// will be identical to GetScriptTextAttributes.

HRESULT GetScriptletTextAttributes(

// The script block text. This string need not be null terminated.

[in, size_is(uNumCodeChars)]LPCOLESTRpstrCode,

// The number of characters in the script block text.

[in]ULONGuNumCodeChars,

// See IActiveScriptParse::AddScriptlet for a description of this argument.

[in]LPCOLESTRpstrDelimiter,

// See IActiveScriptParse::AddScriptlet for a description of this argument.

[in]DWORDdwFlags,

// Buffer to contain the returned attributes.

[in, out, size_is(uNumCodeChars)]SOURCE_TEXT_ATTR *pattr);

// Used by the smart host to delegate IDebugDocumentContext::EnumDebugCodeContexts.

HRESULT EnumCodeContextsOfPosition(

[in]DWORD dwSourceContext,// As provided to IActiveScriptParse::ParseScriptText

// or IActiveScriptParse::AddScriptlet

[in]ULONG uCharacterOffset,// character offset relative

// to start of script text

[in]ULONG uNumChars,// Number of characters in context

// Returns an enumerator of code contexts.

[out] IEnumDebugCodeContexts IEnumDebugCodeContexts **ppescc);

}

IActiveScriptSiteDebug

Implemented by smart hosts and is QI-able from IActiveScriptSite. It provides the means by which a smart host takes over document management and participates in debugging.

[

object,

uuid(51973C11-CB0C-11d0-B5C9-00A0244A0E7A),

pointer_default(unique),

local

]

interface IActiveScriptSiteDebug : IUnknown

{

// Used by the language engine to delegate IDebugCodeContext::GetSourceContext.

HRESULT GetDocumentContextFromPosition(

[in]DWORD dwSourceContext,// As provided to ParseScriptText

// or AddScriptlet

[in]ULONG uCharacterOffset,// character offset relative

// to start of script block or scriptlet

[in]ULONG uNumChars,// Number of characters in context

// Returns the document context corresponding to this character-position range.

[out] IDebugDocumentContext IDebugDocumentContext **ppsc);

// Returns the debug application object associated with this script site. Provides

// a means for a smart host to define what application object each script belongs to.

// Script engines should attempt to call this method to get their containing application

// and resort to IProcessDebugManager::GetDefaultApplication if this fails.

HRESULT GetApplication(

[out] IDebugApplication IDebugApplication **ppda);

// Gets the application node under which script documents should be added

// can return NULL if script documents should be top-level.

HRESULT GetRootApplicationNode(

[out] IDebugApplicationNode IDebugApplicationNode **ppdanRoot);

// Allows a smart host to control the handling of runtime errors

HRESULT OnScriptErrorDebug(

// the runtime error that occurred

[in] IActiveScriptErrorDebug IActiveScriptErrorDebug *pErrorDebug,

// whether to pass the error to the debugger to do JIT debugging

[out]BOOL*pfEnterDebugger,

// whether to call IActiveScriptSite::OnScriptError() when the user

// decides to continue without debugging

[out]BOOL *pfCallOnScriptErrorWhenContinuing);

}

IActiveScriptErrorDebug

Provides document context information for compile and runtime errors. QI-able from IActiveScriptError.

[

object,

uuid(51973C12-CB0C-11d0-B5C9-00A0244A0E7A),

pointer_default(unique)

]

interface IActiveScriptErrorDebug : IActiveScriptError

{

// Provides the document context for the associated error. The character-position range

// should include the entire offending text.

HRESULT GetDocumentContext(

[out] IDebugDocumentContext IDebugDocumentContext **ppssc);

// For runtime errors, provides the stack frame that is in effect.

HRESULT GetStackFrame(

[out] IDebugStackFrame IDebugStackFrame **ppdsf);

}

IDebugCodeContext

Abstraction reresenting a position in executable code. It can be thought of as a "virtual program counter".

[

object,

uuid(51973C13-CB0C-11d0-B5C9-00A0244A0E7A),

pointer_default(unique)

]

interface IDebugCodeContext : IUnknown

{

// Returns the document context associated with this code context.

//

// Note: For text documents, the character-position

// range should include the text for the entire statement. This allows the debugger IDE

// to hilight the current source statement.

HRESULT GetDocumentContext(

[out] IDebugDocumentContext IDebugDocumentContext **ppsc);

// Sets or clears a breakpoint at this code context.

HRESULT SetBreakPoint(

[in] BREAKPOINT_STATE bps);

}

IDebugExpression

Abstract representation of an asynchronously evaluated expression. Normally used by the debugger IDE to implement an immediate execution window or watch window. Normally provided by language engines.

Note: Currently, IDebugExpression is only available from a stack frame.

[

object,

uuid(51973C14-CB0C-11d0-B5C9-00A0244A0E7A),

pointer_default(unique)

]

interface IDebugExpression : IUnknown

{

// Begins the evaluation of the expression.

HRESULT Start(

// Provides an event driven means for indicating that the expression evaluation

// is complete. If NULL, no events will be fired and the client will need to

// poll the expression state using QueryIsComplete.

[in] IDebugExpressionCallBack *pdecb);

// Aborts the expression. Evaluation of an expression in progress will be stopped

// at the earliest opportunity. If the expression is actually aborted, GetResultAsString

// will return E_ABORT as phrResult.

HRESULT Abort(void);

// Returns S_FALSE if the operation is still pending.

// Returns S_OK if the operation is complete.

HRESULT QueryIsComplete(void);

// Returns the result of the expression evaluation as a string and an HRESULT. Returns

// E_PENDING if the operation is still pending. Returns S_OK and E_ABORT in phrResult

// when the operation was aborted with Abort.

HRESULT GetResultAsString(

[out] HRESULT *phrResult,

[out] BSTR *pbstrResult);

// Returns the result of the expression evaluation as an

// IDebugProperty and an HRESULT. Returns

// E_PENDING if the operation is still pending. Returns S_OK and E_ABORT in phrResult

// when the operation was aborted with Abort.

HRESULT GetResultAsDebugProperty(

[out] HRESULT *phrResult,

[out] IDebugProperty **ppdp);

}

IDebugExpressionContext

Abstract representation of a context in which expressions can be evaluated. Currently, this interface is only implemented by the stack frame object. In the future, other objects, such as the document context, may provide this interface.

[

object,

uuid(51973C15-CB0C-11d0-B5C9-00A0244A0E7A),

helpstring("IDebugExpressionContext Interface"),

pointer_default(unique)

]

interface IDebugExpressionContext : IUnknown

{

// Creates an IDebugExpression for the specified text.

HRESULT ParseLanguageText(

// Provides the text of the expression or statement(s).

[in]LPCOLESTRpstrCode,

// Radix to use

[in]UINTnRadix,

// See IActiveScriptParse::ParseScriptText

[in]LPCOLESTRpstrDelimiter,

// See above flags.

[in]DWORDdwFlags,

// Returns the IDebugExpression for the given text.

[out] IDebugExpression IDebugExpression **ppe

);

// Returns a name and GUID for the langauge owning this context

HRESULT GetLanguageInfo (

[out]BSTR*pbstrLanguageName,// the name of the language

[out]GUID*pLanguageID// an unique id for this language

);

}

IDebugExpressionCallback

Provides status events related to progress of an IDebugExpression evaluation.

[

object,

uuid(51973C16-CB0C-11d0-B5C9-00A0244A0E7A),

pointer_default(unique)

]

interface IDebugExpressionCallBack : IUnknown

{

// Indicates that the expression evaluation is complete. Note that

// IDebugExpression::GetResultAsString can be called from within this event

// handler.

HRESULT onComplete(void);

}

IDebugStackFrame

Abstraction representing a logical stack frame on the stack of a thread. Note that it derives from IDebugExpressionContext. This allows the debugger to evaluate expressions in the context of a particular stack frame.

Note: In general, you can QI a stack frame for IDebugExpressionContext to allow expression evaluation and watch windows

[

object,

uuid(51973C17-CB0C-11d0-B5C9-00A0244A0E7A),

pointer_default(unique)

]

interface IDebugStackFrame : IUnknown

{

// Returns the current code context associated with the stack frame.

HRESULT GetCodeContext(

[out] IDebugCodeContext IDebugCodeContext **ppcc);

// Returns a short or long textual description of the stack frame.

// Normally, when fLong if false, this will provide only the name of the

// function associated with the stack frame. When fLong is true it may

// also provide the parameter(s) to the function or whatever else is

// relevant.

HRESULT GetDescriptionString(

[in] BOOL fLong,

[out] BSTR *pbstrDescription);

// Returns a short or long textual description of the language. When fLong

// is false, just the language name should be provided, eg, "Pascal". When

// fLong is true a full product description may be provided, eg,

// "Gnat Software's Flaming Pascal v3.72".

HRESULT GetLanguageString(

[in] BOOL fLong,

[out] BSTR *pbstrLanguage);

// Returns the thread associated with this stack frame.

HRESULT GetThread(

[out] IDebugApplicationThread IDebugApplicationThread **ppat);

// Returns a property browser for the current frame (locals, etc.)

HRESULT GetDebugProperty(

[out] IDebugProperty **ppDebugProp);

}

IDebugStackFrameSniffer

Provides a means for enumerating logical stack frames known by a certain component. Generally implemented by each script engine. Used by the process debug manager to find all stack frames associated with a given thread. Note: This interface is called from within the thread of interest. It is up to the implementor to identify the current thread and return an appropriate enumerator.

[

object,

uuid(51973C18-CB0C-11d0-B5C9-00A0244A0E7A),

pointer_default(unique)

]

interface IDebugStackFrameSniffer : IUnknown

{

// Returns an enumerator of stack frames for the current thread. Top of stack should

// be returned first (the most recently pushed frame).

HRESULT EnumStackFrames(

[out] IEnumDebugStackFrames IEnumDebugStackFrames **ppedsf);

}

IDebugStackFrameSnifferEx

Provides a means for enumerating logical stack frames known by a certain component. Generally implemented by each script engine. Used by the process debug manager to find all stack frames associated with a given thread.

Note: This interface is called from within the thread of interest. It is up to the implementor to identify the current thread and return an appropriate enumerator.

[

object,

uuid(51973C19-CB0C-11d0-B5C9-00A0244A0E7A),

pointer_default(unique)

]

interface IDebugStackFrameSnifferEx : IDebugStackFrameSniffer IDebugStackFrameSniffer

{

// Returns an enumerator of stack frames for the current thread.

// dwSpMin is the minimum address to begin enumerating stack frames

// Stack frames before this address will be omitted from the enumeration.

// Top of stack should be returned first (the most recently pushed frame).

HRESULT EnumStackFramesEx( [in] DWORD dwSpMin, [out] IEnumDebugStackFrames IEnumDebugStackFrames **ppedsf);

};

IDebugSyncOperation

IDebugSyncOperation is generally implemented by a language engine to expose expression evaluation, etc. It allows the implementor to abstract an operation that needs to be performed nested in a particular blocked thread, and it provides a mechanism for cancelling hung operations.

[

object,

uuid(51973C1a-CB0C-11d0-B5C9-00A0244A0E7A),

pointer_default(unique),

local

]

interface IDebugSyncOperation : IUnknown

{

// Get TargetThread is called by PDM to determine what thread

// to call Evaluate() in

HRESULT GetTargetThread(

[out] IDebugApplicationThread IDebugApplicationThread **ppatTarget);

// Execute is called synchronously by the PDM in the target thread. It

// synchronously peforms the operation and returns. It returns E_ABORT if

// the operation was aborted with InProgressAbort();

HRESULT Execute(

[out]IUnknown **ppunkResult);

// InProgressAbort() is called by the PDM, from within the debugger thread,

// to cancel an operation which is in progress in another thread. The

// operation should be completed or error out with E_ABORT as soon as

// possible. E_NOTIMPL can be returned if the operation cannot be cancelled.

HRESULT InProgressAbort(void);

}

IDebugAsyncOperation

IDebugAsyncOperation is generally implemented by the PDM, and is obtained by a language engine through IDebugApplication::CreateAsyncDebugOperation(). The language engine can use it to provide asynchronous access to a SyncDebugOperation.

[

object,

uuid(51973C1b-CB0C-11d0-B5C9-00A0244A0E7A),

pointer_default(unique),

local

]

interface IDebugAsyncOperation : IUnknown

{

HRESULT GetSyncDebugOperation(

[out] IDebugSyncOperation IDebugSyncOperation **ppsdo);

// Start() causes the asynchronous operation to begin. It asynchronously

// causes IDebugSyncOperation::Execute() to be called in the thread obtained

// from IDebugSyncOperation::GetTargetThread(). It should only

// be called from within the debugger thread, or it will not return until

// the operation is complete (it degenerates to synchronous).

// Returns E_UNEXPECTED if an operation is already pending.

HRESULT Start( IDebugAsyncOperationCallBack IDebugAsyncOperationCallBack *padocb);

// Abort() causes InProgressAbort() to be called on the IDebugSyncOperation

// object. It is normally called from within the debugger thread to cancel

// a hung operation. If the abort happens before the request completes,

// GetResult() will return E_ABORT. E_NOTIMPL may be returned from this

// function if the operation is not cancellable.

HRESULT Abort(void);

// QueryIsComplete() returns S_OK if the operation is complete; otherwise it

// returns S_FALSE;

HRESULT QueryIsComplete(void);

// If the request is complete, returns the HRESULT and object parameter

// returned from IDebugSyncOperation::Execute(). Otherwise, returns

// E_PENDING.

HRESULT GetResult(

[out] HRESULT *phrResult,

[out] IUnknown **ppunkResult);

}

IDebugAsyncOperationCallBack

This interface is used to signal events from an IDebugAsyncOperation.

[

object,

uuid(51973C1c-CB0C-11d0-B5C9-00A0244A0E7A),

pointer_default(unique),

local

]

interface IDebugAsyncOperationCallBack : IUnknown

{

// onComplete() is fired by the AsyncDebugOperation when a result is available.

// The event is fired in the debugger thread.

HRESULT onComplete(void);

}

IEnumDebugCodeContexts

Enumerates code contexts. Generally used to enumerate the code contexts corresponding to a document context.

[

object,

uuid(51973C1d-CB0C-11d0-B5C9-00A0244A0E7A),

helpstring("IEnumDebugCodeContexts Interface"),

pointer_default(unique)

]

interface IEnumDebugCodeContexts : IUnknown {

[local]

HRESULT __stdcall Next(

[in] ULONG celt,

[out] IDebugCodeContext IDebugCodeContext **pscc,

[out] ULONG *pceltFetched);

HRESULT Skip(

[in] ULONG celt);

HRESULT Reset(void);

HRESULT Clone(

[out] IEnumDebugCodeContexts IEnumDebugCodeContexts **ppescc);

}

DebugStackFrameDescriptor

Used to enumerate stack frames and merge output from several enumerators (on the same thread). dwMin and dwLim provide a machine dependent representation of the range of physical addresses associated with this stack frame. This is used by the process debug manager to sort the stack frames from multiple script engines.

By convention, stacks grow down and, as such, on architectures where stacks grow up the addresses should be twos-complemented.

The punkFinal is used during enumerator merging. If punkFinal is non-null, It indicates that the that the current enumerator merging should stop and a new one should be started. The object indicates how the new enumeration is to be started.

typedef struct tagDebugStackFrameDescriptor

{

IDebugStackFrame IDebugStackFrame *pdsf;

DWORD dwMin;

DWORD dwLim;

BOOL fFinal;

IUnknown *punkFinal;

} DebugStackFrameDescriptor DebugStackFrameDescriptor ;

IEnumDebugStackFrames

Enumerates stack frames. Generally used to enumerate the stack frames corresponding to a thread.

[

object,

uuid(51973C1e-CB0C-11d0-B5C9-00A0244A0E7A),

helpstring("IEnumDebugStackFrames Interface"),

pointer_default(unique)

]

interface IEnumDebugStackFrames : IUnknown

{

[local]

HRESULT __stdcall Next(

[in] ULONG celt,

[out] DebugStackFrameDescriptor DebugStackFrameDescriptor *prgdsfd,

[out] ULONG *pceltFetched);

HRESULT Skip(

[in] ULONG celt);

HRESULT Reset(void);

HRESULT Clone(

[out] IEnumDebugStackFrames IEnumDebugStackFrames **ppedsf);

}

Smart Host Interfaces

Below are the details of the interfaces implemented by a smart host. As mentioned earlier, it is possible to avoid implementing these interfaces by using the smart host helper interfaces.

IDebugDocumentInfo

Provides information on a document, which may or may not be instantiated.

[

object,

uuid(51973C1f-CB0C-11d0-B5C9-00A0244A0E7A),

helpstring("IDebugDocumentInfo Interface"),

pointer_default(unique)

]

interface IDebugDocumentInfo : IUnknown {

// Returns the specified name for the document. If the indicated name is

// not known, E_FAIL is returned.

HRESULT GetName(

[in] DOCUMENTNAMETYPE dnt,

[out] BSTR *pbstrName);

// Returns a CLSID describing the document type. This allows the debugger IDE

// to host custom viewers for this document. returns CLSID_NULL if this document

// does not have viewable data.

HRESULT GetDocumentClassId(

[out] CLSID *pclsidDocument);

}

IDebugDocumentProvider

Provides the means for instantiating a document on demand. This indirect means for instanciating a document:

1.Allows lazy loading of the document.

2.Allows the document object to live at the debugger IDE.

3.Allows more then one way of getting to the identical document object.

This effectively segregates the document from its provider; this allows the provider to carry additional runtime context information.

[

object,

uuid(51973C20-CB0C-11d0-B5C9-00A0244A0E7A),

helpstring("IDebugDocumentProvider Interface"),

pointer_default(unique)

]

interface IDebugDocumentProvider : IDebugDocumentInfo {

// Causes the document to be instantiated if it does not already exist.

HRESULT GetDocument(

[out] IDebugDocument IDebugDocument **ppssd);

}

IDebugDocument

The base interface to all debug documents.

[

object,

uuid(51973C21-CB0C-11d0-B5C9-00A0244A0E7A),

pointer_default(unique)

]

interface IDebugDocument : IDebugDocumentInfo {

}

IDebugDocumentText

The interface to a text only debug document.

Conventions:

1.Both character positions and line numbers are zero based.

2.Character-positions represent character offsets; they do not represent byte or word offsets. For Win32, a character-position is an Unicode offset.

Note: the use of line-number based text management is not recommended; instead it is recommended that character-position based management be used. The line to character-position mapping functions described in this interface may be removed.

[

object,

uuid(51973C22-CB0C-11d0-B5C9-00A0244A0E7A),

pointer_default(unique)

]

interface IDebugDocumentText : IDebugDocument IDebugDocument {

// Returns the attributes of the document.

HRESULT GetDocumentAttributes(

[out]TEXT_DOC_ATTR *ptextdocattr);

// Returns the size of the document.

HRESULT GetSize(

[out] ULONG *pcNumLines,// NULL means do not return the number of lines.

[out] ULONG *pcNumChars);// NULL means do not return the number of characters.

// Returns character-position corresponding to the first character of a line.

HRESULT GetPositionOfLine(

[in] ULONG cLineNumber,

[out] ULONG *pcCharacterPosition);

// Returns the line-number and, optionally, the character offset within the line

// that corresponds to the given character-position.

HRESULT GetLineOfPosition(

[in] ULONG cCharacterPosition,

[out] ULONG *pcLineNumber,

[out] ULONG *pcCharacterOffsetInLine); // NULL means do not return a value.

// Retrieves the characters and/or the character attributes associated with

// a character-position range; where a character position range is specified by

// a character-position and a number of characters.

HRESULT GetText(

[in] ULONG cCharacterPosition,

// Specifies a character text buffer. NULL means do not return characters.

[in, out, length_is(*pcNumChars), size_is(cMaxChars)] WCHAR *pcharText,

// Specifies a character attribute buffer. NULL means do not return attributes.

[in, out, length_is(*pcNumChars), size_is(cMaxChars), ptr] SOURCE_TEXT_ATTR *pstaTextAttr,

// Indicates the actual number of characters/attributes returned. Must be set to zero

// before the call.

[in, out] ULONG *pcNumChars,

// Specifies the number maximum number of character desired.

[in] ULONG cMaxChars);

// Returns the character-position range corresponding to a document context. The document

// context provided must be associated with this document.

HRESULT GetPositionOfContext(

[in] IDebugDocumentContext IDebugDocumentContext *psc,

[out] ULONG *pcCharacterPosition,

[out]ULONG *cNumChars);

// Creates a document context object corresponding to the provided character position range.

HRESULT GetContextOfPosition(

[in] ULONG cCharacterPosition,

[in] ULONG cNumChars,

[out] IDebugDocumentContext IDebugDocumentContext **ppsc);

}

IDebugDocumentTextEvents

Provides events indicating changes to the associated text document. Note: The text alterations are reflected in the document at the time the events on this interface are fired. Event handlers may retrieve the new text using IDebugDocumentText.

[

object,

uuid(51973C23-CB0C-11d0-B5C9-00A0244A0E7A),

pointer_default(unique)

]

interface IDebugDocumentTextEvents : IUnknown

{

// Indicates that the underlying document has been destroyed and is no longer valid.

HRESULT onDestroy(void);

// Indicates that new text has been added to the document. Example: progressive loading

// of HTML.

//

HRESULT onInsertText(

// The position where the new text is inserted.

[in] ULONG cCharacterPosition,

// The number of characters that have been inserted.

[in] ULONG cNumToInsert);

// Indicates that text has been removed from the document.

HRESULT onRemoveText(

// The character-position of the first character removed.

[in] ULONG cCharacterPosition,

// The number of characters removed.

[in] ULONG cNumToRemove);

// Indicates that text has been replaced.

HRESULT onReplaceText(

// The starting character-position of the character-position range

// that is being replaced.

[in] ULONG cCharacterPosition,

// The number of characters replaced.

[in] ULONG cNumToReplace);

// Indicates that the text attributes associated with the underlying character-position

// range has changed.

HRESULT onUpdateTextAttributes(

// The character-position of the first character whose attributes have changed.

[in] ULONG cCharacterPosition,

// The number of characters in the range.

[in] ULONG cNumToUpdate);

// Indicates that the document attributes have changed.

HRESULT onUpdateDocumentAttributes(

// The new document attributes.

[in] TEXT_DOC_ATTR textdocattr);

}

IDebugDocumentHelper

IDebugDocumentHelper greatly simplifies the task of creating a smart host for ActiveDebugging.

IDebugDocumentHelper automatically provides implementations for IDebugDocumentProvider, IDdebugDocument, IDebugDocumentText, IDebugDocumentContext, IDebugDocumentTextEvents, and many of the other interfaces necessary for smart hosting.

To be a smart host using IDebugDocumentHelper, a host application only to do only three two things:

(1) CoCreate an IProcessDebugManager and use it to add your application to the list of debuggable applications.

(2) create an IDebugDocumentHelper for each host document and make the appropriate calls to define the document name, parent document, text, and script blocks.

(3) Implement IActiveScriptSiteDebug on your IActiveScriptSite object (implemented already for Active Scripting. The only non-trivial method on IActiveScriptSiteDebug simply delegates to the helper.

Additionally, the host can optionally implement IDebugDocumentHost if it needs additional control over syntax color, document context creation, and other extended functionality.

The main limitation on the smart host helper is that can only handle documents whose contents change or shrink after they have been added (though documents can expand, of course.) For many smart hosts, however, the functionality it provides is exactly what is needed.

Below we go into each of the steps in more detail.

Create an Application Object

Before the smart host helper can be used, it is necessary to create an IDebugApplication object to represent your application in the debugger. The steps for creating an application object are as follows:

(1) Create an instance of the process debug manager using CoCreateInstance.

(2) Call IProcessDebugManager::CreateApplication().

(3) Set the name on the application using SetName().

(4) Add the application object to the list of debuggable applications using AddApplication().

Below is code to do this, minus error-check and other niceties.

CoCreateInstance(CLSID_ProcessDebugManager, NULL,

CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER

| CLSCTX_LOCAL_SERVER,

IID_IProcessDebugManager, (void **)&g_ppdm);

g_ppdm->CreateApplication(&g_pda);

g_pda->SetName(L"My cool application");

g_ppdm->AddApplication(g_pda, &g_dwAppCookie);

Using IDebugDocumentHelper

The minimal sequence of steps for using the helper is as follows:

(1) For each host document, create a helper using IProcessDebugManager::CreateDebugDocumentHelper.

(2) Call Init on the helper, giving the name, document attributes, etc.

(3) Call Attach with parent helper for the document (or NULL if the document is the root) to define the position of the document in the tree and make it visible to the debugger

(4) Call AddDBCSText() or AddUnicodeText() to define the text of the document. (These can be called multiple times if document is downloaded incrementally, as in the case of a browser.)

(5) Call DefineScriptBlock to define the ranges for each script block and the associated script engines.

Implementing IActiveScriptSiteDebug

To implement GetDocumentContextFromPosition, get the helper corresponding to the given site, then get the starting document offset for the given source context, as follows:

pddh->GetScriptBlockInfo(dwSourceContext, NULL, &ulStartPos, NULL);

Next, use the helper to create a new document context for the given character offset:

pddh->CreateDebugDocumentContext(ulStartPos + uCharacterOffset, cChars, &pddcNew);

To implement GetRootApplicationNode , simply call IDebugApplication::GetRootNode. To implement GetDebugApplication , simply return the IDebugApplication you initially created using the process debug manager.

The optional IDebugDocumentHost interface

The host can provide an implementation of IDebugDocumentHost using IDebugDocumentHelper::SetHost that gives it additional control over the helper. Here are some of the key things the host interface allows you to do:

(1) Add text using AddDeferredText so that the host doesn't have to provide the actual characters immediately. When the characters are really needed, the helper will call IDebugDocumentHost::GetDeferredCharacters on the host.

(2) Override the default syntax coloring provided by the helper. The helper will call IDebugDocumentHost::GetScriptTextAttributes when it needs to know the coloring for a range of characters, falling back on its default implementation if the host return E_NOTIMPL.

(3) Providing a controlling unknown for document contexts created by the helper my implementing IDebugDocumentHost::OnCreateDocumentContext. This allows the host to override the functionality of the default document context implementation.

(4) Provide a path name in the file system for the document. Some debugging UIs will use this to permit the user to edit and save changes to the document. IDebugDocumentHost::NotifyChanged will be called to notify the host after the document has been saved.

[

object,

uuid(51973C26-CB0C-11d0-B5C9-00A0244A0E7A),

helpstring("IDebugDocumentHelper Interface"),

pointer_default(unique)

]

interface IDebugDocumentHelper : IUnknown

{

// Initialize a debug doc helper with the given name and

// initial attributes.

//

// Note: The document will not actually appear in the tree

// until Attach is called.

HRESULT Init(

[in] IDebugApplication IDebugApplication *pda,

[in, string]LPCOLESTR pszShortName,

[in, string]LPCOLESTR pszLongName,

[in]TEXT_DOC_ATTR docAttr

);

// Add the document to the doc tree, using pddhParent as the parent.

// If the ppdhParent is NULL, the document will be top-level.

HRESULT Attach([in] IDebugDocumentHelper IDebugDocumentHelper *pddhParent);

// Remove the document from the doc tree.

HRESULT Detach();

// Add the given set of unicode characters to end of the document.

// (This will generate IDebugDocumentTextEvent notifications.)

//

// If this method is called after AddDeferredText has been called,

// E_FAIL will be returned.

HRESULT AddUnicodeText(

[in, string] LPCOLESTR pszText

);

// Add the given set of DBCS characters to end of the document.

// (This will generate IDebugDocumentTextEvent notifications.)

//

// If this method is called after AddDeferredText has been called,

// E_FAIL will be returned.

HRESULT AddDBCSText(

[in, string] LPCSTR pszText

);

// Set the DebugDocumentHost interface.

// If provided, this interface will be used for

// smart-host syntax coloring, fetching deferred text, and returning

// controlling unknowns for newly created document contexts.

HRESULT SetDebugDocumentHost(

[in] IDebugDocumentHost IDebugDocumentHost * pddh

);

// Notify the helper that the given text is available, but don't actually

// provide the characters.

// This allows the host to defer providing the characters unless they

// are actually needed, while still allowing the helper to generate

// accurate notifications and size information.

//

// dwTextStartCookie is a cookie, defined by the host, that represents

// the starting position of the text. (For example, in a host that

// represents text in DBCS, the cookie could be a byte offset.)

// This cookie will be provided in subsequent calls to GetText.

//

// NB: It is assumed that a single call to

// GetText can get characters from multiple calls to AddDeferredText.

// The helper classes may also ask for the same range of deferred

// characters more than once.

//

// It is an error to mix calls to AddDeferredText with calls to

// AddUnicodeText or AddDBCSText-- Doing so will cause E_FAIL to be

// returned.

HRESULT AddDeferredText(

[in] ULONG cChars,// number of (Unicode) characters to add

[in] DWORD dwTextStartCookie

// host-defined cookie representing the starting position of the text.

);

// Notify the helper that a particular range of

// characters is a script block handled by the given script engine.

// All syntax coloring and code context lookups for that range will be

// delegated to that script engine.

//

// This method would be used by a smart host whose documents contained

// embedded script blocks, or by a language engine containing embedded

// scripts for other languages.

//

// DefineScriptBlock should be called after the text has been

// added (via AddDBCSText, etc) but before the script script block

// has been parsed (via IActiveScriptParse).

HRESULT DefineScriptBlock(

[in] ULONG ulCharOffset,

[in] ULONG cChars,

[in] IActiveScript* pas,

[in] BOOL fScriptlet,

[out] DWORD* pdwSourceContext

);

// Set the default attribute to use for text that is not in a

// script block. (If not explicitly set, the default attributes for

// text outside of a script block is SOURCETEXT_ATTR_NONSOURCE.)

//

// This would allow, for example, for text outside of script blocks

// to be colored grey and marked read-only.

HRESULT SetDefaultTextAttr(SOURCE_TEXT_ATTR staTextAttr);

// Explicilty set the attributes on a range of text, overriding any

// other attributes on that text.

//

// It is an error to set the attributes on a text range that has not

// yet been added using AddText.

HRESULT SetTextAttributes(

[in] ULONG ulCharOffset,

[in] ULONG cChars,

[in, length_is(cChars), size_is(cChars)]

SOURCE_TEXT_ATTR* pstaTextAttr);

// Set a new long name for the document

HRESULT SetLongName(

[in, string] LPCOLESTR pszLongName);

// Set a new short name for the document

HRESULT SetShortName(

[in, string] LPCOLESTR pszShortName);

// Define a new set of document attributes

HRESULT SetDocumentAttr(

[in] TEXT_DOC_ATTR pszAttributes

);

// Return the debug application node corresponding to this document

HRESULT GetDebugApplicationNode(

[out] IDebugApplicationNode IDebugApplicationNode **ppdan);

// Once a script block has been defined, this method allows the

// associate range and script engine to be retrieved.

HRESULT GetScriptBlockInfo(

[in] DWORD dwSourceContext,

[out]IActiveScript** ppasd,

[out] ULONG *piCharPos,

[out]ULONG *pcChars);

// Allows the host to create a new debug document context

HRESULT CreateDebugDocumentContext(

[in]ULONG iCharPos,

[in]ULONG cChars,

[out] IDebugDocumentContext IDebugDocumentContext ** ppddc);

// Bring this document to the top in the debugger UI.

// If the debugger isn't started already, start it now.

HRESULT BringDocumentToTop();

// Bring the given context in this document to the top

// in the debugger UI.

HRESULT BringDocumentContextToTop( IDebugDocumentContext IDebugDocumentContext *pddc);

};

IDebugDocumentHost

The interface from the IDebugDocumentHelper back to the smart host or language engine. This interface exposes host specific functionality such as syntax coloring,

[

object,

uuid(51973C27-CB0C-11d0-B5C9-00A0244A0E7A),

helpstring("IDebugDocumentHost Interface"),

pointer_default(unique)

]

interface IDebugDocumentHost : IUnknown

{

// Return a particular range of characters in the original host document,

// added using AddDeferredText.

//

// It is acceptable for a host to return E_NOTIMPL for this method,

// as long as the host doesn't call AddDeferredText.

//

// (Note that this is text from the _original_ document. The host

// does not need to be responsible for keeping track of edits, etc.)

HRESULT GetDeferredText(

[in] DWORD dwTextStartCookie,

// Specifies a character text buffer. NULL means do not return characters.

[in, out, length_is(*pcNumChars), size_is(cMaxChars)] WCHAR *pcharText,

// Specifies a character attribute buffer. NULL means do not return attributes.

[in, out, length_is(*pcNumChars), size_is(cMaxChars)] SOURCE_TEXT_ATTR *pstaTextAttr,

// Indicates the actual number of characters/attributes returned. Must be set to zero

// before the call.

[in, out] ULONG *pcNumChars,

// Specifies the number maximum number of character desired.

[in] ULONG cMaxChars);

// Return the text attributes for an arbitrary block of document text.

// It is acceptable for hosts to return E_NOTIMPL, in which case the

// default attributes are used.

HRESULT GetScriptTextAttributes(

// The script block text. This string need not be null terminated.

[in, size_is(uNumCodeChars)]LPCOLESTRpstrCode,

// The number of characters in the script block text.

[in]ULONGuNumCodeChars,

// See IActiveScriptParse::ParseScriptText for a description of this argument.

[in]LPCOLESTRpstrDelimiter,

// See IActiveScriptParse::ParseScriptText for a description of this argument.

[in]DWORDdwFlags,

// Buffer to contain the returned attributes.

[in, out, size_is(uNumCodeChars)]SOURCE_TEXT_ATTR *pattr);

// Notify the host that a new document context is being created

// and allow the host to optionally return a controlling unknown

// for the new context.

//

// This allows the host to add new functionality to the helper-provided

// document contexts. It is acceptable for the host to return E_NOTIMPL

// or a null outer unknown for this method, in which case the context is

// used "as is".

HRESULT OnCreateDocumentContext(

[out] IUnknown** ppunkOuter);

// Return the full path (including file name) to the document's source file.

//*pfIsOriginalPath is TRUE if the path refers to the original file for the document.

//*pfIsOriginalPath is FALSE if the path refers to a newly created temporary file

//Returns E_FAIL if no source file can be created/determined.

HRESULT GetPathName(

[out] BSTR *pbstrLongName,

[out] BOOL *pfIsOriginalFile);

// Return just the name of the document, with no path information.

// (Used for "Save As...")

HRESULT GetFileName(

[out] BSTR *pbstrShortName);

// Notify the host that the document's source file has been saved and

// that its contents should be refreshed.

HRESULT NotifyChanged();

};

IDebugDocumentContext

Abstract representation of a portion of the debug document. For text documents this consists of a character-position range.

[

object,

uuid(51973C28-CB0C-11d0-B5C9-00A0244A0E7A),

pointer_default(unique)

]

interface IDebugDocumentContext : IUnknown

{

// Returns the document that contains this context.

HRESULT GetDocument(

[out] IDebugDocument IDebugDocument **ppsd);

// Enumerates the code contexts associated with this document context. Generally there will

// only be one code context but there are important exceptions, such as include file

// or templates (in C++).

HRESULT EnumCodeContexts(

[out] IEnumDebugCodeContexts IEnumDebugCodeContexts **ppescc);

}

Debugger UI interfaces

Below are the interfaces that allow other components to launch and interface with the debugger UI.

IDebugSessionProvider

The primary interface provided by a debugger IDE to enable host and language initiated debugging. Its sole purpose is to establish a debug session for a running application.

cpp_quote( "EXTERN_C const CLSID CLSID_DefaultDebugSessionProvider;")

[

object,

uuid(51973C29-CB0C-11d0-B5C9-00A0244A0E7A),

helpstring("IDebugSessionProvider Interface"),

pointer_default(unique)

]

interface IDebugSessionProvider : IUnknown

{

// Initiates a debug session with the specified application. The debugger should

// call IRemoteDebugApplication::ConnectDebugger before returning from this call.

HRESULT StartDebugSession(

[in] IRemoteDebugApplication IRemoteDebugApplication *pda);

};

IApplicationDebugger

This is the primary interface exposed by a debugger IDE.

[

object,

uuid(51973C2a-CB0C-11d0-B5C9-00A0244A0E7A),

helpstring("IApplicationDebugger Interface"),

pointer_default(unique)

]

interface IApplicationDebugger : IUnknown

{

// Indicates if the debugger is alive. Should always return S_OK. If the debugger

// has rudely shut down COM will return an error from the marshalling proxy.

HRESULT QueryAlive(void);

// Provides a mechanism for hosts and language engines running out-of-process to the

// debugger to create objects in the debugger process. This can be used for any purpose,

// including extending the debugger UI. This method simply delegates to CoCreateInstance.

HRESULT CreateInstanceAtDebugger(

[in]REFCLSID rclsid,// Class identifier (CLSID) of the object

[in]IUnknown *pUnkOuter,// Object is or isn't part of an aggregate

[in]DWORD dwClsContext,// Context for running executable code

[in]REFIID riid,// Interface identifier

[out, iid_is(riid)]IUnknown **ppvObject);

// Points to requested interface pointer

// This method is called when IDebugApplication::DebugOutput is called. The debugger

// can use this to display the string in an output window.

HRESULT onDebugOutput(

[in] LPCOLESTR pstr);

// This method is called when a breakpoint is hit. The application will remain

// suspended until the debugger IDE calls IDebugApplication::ResumeFromBreakPoint.

HRESULT onHandleBreakPoint(

// Indicates the thread in which the breakpoint occured.

[in] IRemoteDebugApplicationThread IRemoteDebugApplicationThread *prpt,

// Indicates the reason for the breakpoint.

[in]BREAKREASON br,

// optional runtime error info (for when br == BREAKREASON_ERROR)

[in] IActiveScriptErrorDebug IActiveScriptErrorDebug *pError);

// This method is called when IDebugApplication::Close is called.

HRESULT onClose(void);

// Handle a custom event.

// The semantics of the GUID and IUnknown are entirely application/debugger defined

// This method may return E_NOTIMPL.

HRESULT onDebuggerEvent(

[in]REFIID riid,

[in]IUnknown *punk);

};

IApplicationDebuggerUI

This is a secondary interface exposed by some debugger IDE that allows an external component to have additional control over the debuggers UI.

[

object,

uuid(51973C2b-CB0C-11d0-B5C9-00A0244A0E7A),

helpstring("IApplicationDebuggerUI Interface"),

pointer_default(unique)

]

interface IApplicationDebuggerUI : IUnknown

{

// Bring the window corresponding to the given debug document to the front.

// Returns E_INVALIDARG if the document is not known.

HRESULT BringDocumentToTop([in] IDebugDocumentText IDebugDocumentText * pddt);

// Bring the window containing the given doc context to the front,

// and scroll it to the correct location.

// Returns E_INVALIDARG if the context is not known.

HRESULT BringDocumentContextToTop([in] IDebugDocumentContext IDebugDocumentContext * pddc);

};

IMachineDebugManager

The primary interface to the Machine Debug Manager.

cpp_quote( "EXTERN_C const CLSID CLSID_MachineDebugManager;")

[

object,

uuid(51973C2c-CB0C-11d0-B5C9-00A0244A0E7A),

helpstring("IMachineDebugManager Interface"),

pointer_default(unique)

]

interface IMachineDebugManager : IUnknown

{

// Adds an application to the running application list. This method is called by the

// process debug manager whenever IProcessDebugManager::AddApplication is called.

HRESULT AddApplication(

[in] IRemoteDebugApplication IRemoteDebugApplication *pda,

[out] DWORD *pdwAppCookie);

// Removes an application from the running application list. This method is called by the

// process debug manager whenever IProcessDebugManager::RemoveApplication is called.

HRESULT RemoveApplication(

[in] DWORD dwAppCookie);

// Returns an enumerator of the current list of running applications. Used by the debugger

// IDE to display and attach applications for debugging purposes.

HRESULT EnumApplications(

[out] IEnumRemoteDebugApplications IEnumRemoteDebugApplications **ppeda);

};

IMachineDebugManagerCookie - As IMachineDebugManager with Debug Cookie support

[

object,

uuid(51973C2d-CB0C-11d0-B5C9-00A0244A0E7A),

helpstring("IMachineDebugManagerCookie Interface"),

pointer_default(unique)

]

interface IMachineDebugManagerCookie : IUnknown

{

// Adds an application to the running application list. This method is called by the

// process debug manager whenever IProcessDebugManager::AddApplication is called.

HRESULT AddApplication(

[in] IRemoteDebugApplication IRemoteDebugApplication *pda,

[in] DWORD dwDebugAppCookie,

[out] DWORD *pdwAppCookie);

// Removes an application from the running application list. This method is called by the

// process debug manager whenever IProcessDebugManager::RemoveApplication is called.

HRESULT RemoveApplication(

[in] DWORD dwDebugAppCookie,

[in] DWORD dwAppCookie);

// Returns an enumerator of the current list of running applications. Used by the debugger

// IDE to display and attach applications for debugging purposes.

HRESULT EnumApplications(

[out] IEnumRemoteDebugApplications IEnumRemoteDebugApplications **ppeda);

};

IMachineDebugManagerEvents

This event interface is used to signal changes in the running application list maintained by the machine debug manager. It can be used by the debugger IDE to display a dynamic list of applications.

[

object,

uuid(51973C2e-CB0C-11d0-B5C9-00A0244A0E7A),

helpstring("IMachineDebugManagerEvents Interface"),

pointer_default(unique)

]

interface IMachineDebugManagerEvents : IUnknown

{

// Indicates that a new application has appeared on the running application list.

HRESULT onAddApplication(

[in] IRemoteDebugApplication IRemoteDebugApplication *pda,

[in] DWORD dwAppCookie);

// Indicates that an application has been removed from the running application list.

HRESULT onRemoveApplication(

[in] IRemoteDebugApplication IRemoteDebugApplication *pda,

[in] DWORD dwAppCookie);

};

Process Debug Manager Interfaces

Below are the details of the interfaces for the process debug manager.

IProcessDebugManager

The primary interface to the process debug manager.

cpp_quote( "EXTERN_C const CLSID CLSID_ProcessDebugManager;")

[

object,

uuid(51973C2f-CB0C-11d0-B5C9-00A0244A0E7A),

helpstring("IProcessDebugManager Interface"),

pointer_default(unique),

local

]

interface IProcessDebugManager : IUnknown

{

// Creates a new debug application object. The new object is not added to the

// running application list and has no name.

HRESULT CreateApplication(

[out] IDebugApplication IDebugApplication **ppda);

// Returns a default application object for the current process, creating one and adding

// it to the running application list if necessary. Language engines should use this

// application if they are running on a host that does not provide an application.

HRESULT GetDefaultApplication(

[out] IDebugApplication IDebugApplication **ppda);

// Adds an application to the running application list in the machine debug manager.

HRESULT AddApplication(

[in] IDebugApplication IDebugApplication *pda,

// Returns a cookie used to remove the application from the machine debug manager.

[out] DWORD *pdwAppCookie);

// Removes an application from the running application list.

HRESULT RemoveApplication(

// The cookie provided by AddApplication.

[in] DWORD dwAppCookie);

HRESULT CreateDebugDocumentHelper(

[in] IUnknown *punkOuter,

[out] IDebugDocumentHelper IDebugDocumentHelper ** pddh);

};

IRemoteDebugApplication

An abstraction representing a running application. It need not correspond to an OS process. Applications are the smallest debuggable unit; that is, the debugger IDE normally targets an application for debugging.

The application object is normally implemented by the Process Debug Manager.

[

object,

uuid(51973C30-CB0C-11d0-B5C9-00A0244A0E7A),

helpstring("IRemoteDebugApplication Interface"),

pointer_default(unique)

]

interface IRemoteDebugApplication : IUnknown

{

// Continue an application which is currently in a breakpoint.

HRESULT ResumeFromBreakPoint(

// For stepping modes, the thread which is to be affected by the stepping mode.

[in] IRemoteDebugApplicationThread IRemoteDebugApplicationThread *prptFocus,

// The action to take (step mode, etc.) upon resuming the application

[in] BREAKRESUMEACTION bra,

// the action to take in the case that we stopped because of an error

[in] ERRORRESUMEACTION era);

// Causes the application to break into the debugger at the earliest opportunity. Note

// that a long time may elapse before the application actually breaks, particularly if

// the application is not currently executing script code.

HRESULT CauseBreak(void);

// Connects a debugger to the application. Only one debugger may be connected at a

// time; this method fails if there is already a debugger connected

HRESULT ConnectDebugger(

[in] IApplicationDebugger IApplicationDebugger *pad);

// Disconnects the current debugger from the application.

HRESULT DisconnectDebugger(void);

// Returns the current debugger connected to the application.

HRESULT GetDebugger(

[out] IApplicationDebugger IApplicationDebugger **pad);

// Provides a mechanism for the debugger IDE, running out-of-process to the

// application, to create objects in the application process.

// This method simply delegates to CoCreateInstance.

HRESULT CreateInstanceAtApplication(

[in]REFCLSID rclsid,// Class identifier (CLSID) of the object

// Note: This parameter may have to be removed.

[in]IUnknown *pUnkOuter,// Object is or isn't part of an aggregate

[in]DWORD dwClsContext,// Context for running executable code

[in]REFIID riid,// Interface identifier

[out, iid_is(riid)]IUnknown **ppvObject);

// Points to requested interface pointer

// Indicates if the application is alive. Should always return S_OK. If the application

// process has rudely shut down COM will return an error from the marshalling proxy.

HRESULT QueryAlive(

void);

// Enumerates all threads known to be associated with the application.

// New threads may be added at any time.

HRESULT EnumThreads(

[out] IEnumRemoteDebugApplicationThreads IEnumRemoteDebugApplicationThreads **pperdat);

// Returns the application node under which all nodes associated with the

// application are added.

HRESULT GetName(

[out]BSTR *pbstrName);

// Returns a node for the application

HRESULT GetRootNode(

[out] IDebugApplicationNode IDebugApplicationNode **ppdanRoot);

// Returns an enumerator that lists the global expression

// contexts for all languages running in this application

HRESULT EnumGlobalExpressionContexts (

[out] IEnumDebugExpressionContexts IEnumDebugExpressionContexts **ppedec);

};

IDebugApplication

This interface is an extension of IRemoteDebugApplication, exposing non-remotable methods for use by language engines and hosts.

[

object,

uuid(51973C32-CB0C-11d0-B5C9-00A0244A0E7A),

helpstring("IDebugApplication Interface"),

pointer_default(unique),

local

]

interface IDebugApplication : IRemoteDebugApplication IRemoteDebugApplication

{

// Sets the name of the application. The provided name will be returned in subsequent calls

// to IRemoteDebugApplication::GetName.

HRESULT SetName(

[in]LPCOLESTR pstrName);

// This method is called by language engines, in single step mode, just before they

// return to their caller. The process debug manager uses this opportunity to notify all

// other script engines that they should break at the first opportunity. This is how

// cross language step modes are implemented.

HRESULT StepOutComplete(void);

// Causes the given string to be displayed by the debugger IDE, normally in an output

// window. This mechanism provides the means for a language engine to implement language

// specific debugging output support. Example: Debug.writeln("Help") in JavaScript.

HRESULT DebugOutput(

[in]LPCOLESTR pstr);

// Causes a default debugger IDE to be started and a debug session to be attached to

// this application if one does not already exist. This is used to implement just-in-time

// debugging.

HRESULT StartDebugSession(void);

// Called by the language engine in the context of a thread that has hit a breakpoint.

// This method causes the current thread to block and a notification of the breakpoint

// to be sent to the debugger IDE. When the debugger IDE resumes the application this

// method returns with the action to be taken.

//

// Note: While in the breakpoint the language engine may be called in this thread to do

// various things such as enumerating stack frames or evaluating expressions.

HRESULT HandleBreakPoint(

[in]BREAKREASON br,

[out]BREAKRESUMEACTION *pbra);

// Causes this application to release all references and enter a zombie state. Called

// by the owner of the application generally on shut down.

HRESULT Close(void);

// Returns the current break flags for the application.

HRESULT GetBreakFlags(

[out]APPBREAKFLAGS *pabf,

[out] IRemoteDebugApplicationThread IRemoteDebugApplicationThread **pprdatSteppingThread);

// Returns the application thread object associated with the currently running thread.

HRESULT GetCurrentThread(

[out] IDebugApplicationThread IDebugApplicationThread **pat);

// Creates an IDebugAsyncOperation object to wrap a provided IDebugSyncOperation object.

// This provides a mechanism for language engines to implement asynchronous expression and

// evaluation, etc. without having to know the details of synchronization with the

// debugger thread. See the descriptions for IDebugSyncOperation and IDebugAsyncOperation

// for more details.

HRESULT CreateAsyncDebugOperation(

[in] IDebugSyncOperation IDebugSyncOperation *psdo,

[out] IDebugAsyncOperation IDebugAsyncOperation **ppado);

// Adds a stack frame sniffer to this application. Generally called by a language engine

// to expose its stack frames to the debugger. It is possible for other entities to

// expose stack frames.

HRESULT AddStackFrameSniffer(

[in] IDebugStackFrameSniffer IDebugStackFrameSniffer *pdsfs,

// Returns a cookie that is used to remove this stack frame sniffer

// from the application.

[out]DWORD *pdwCookie);

// Removes a stack frame sniffer from this application.

HRESULT RemoveStackFrameSniffer(

// The cookie returned by AddStackFrameSniffer.

[in]DWORD dwCookie);

// Returns S_OK if the current running thread is the debugger thread.

// Otherwise, returns S_FALSE.

HRESULT QueryCurrentThreadIsDebuggerThread(

void);

// Provides a mechanism for the caller to run code in the debugger thread. This is generally

// used so that language engines and hosts can implement free threaded objects on top

// of their single threaded implementations.

HRESULT SynchronousCallInDebuggerThread(

[in] IDebugThreadCall IDebugThreadCall *pptc,

[in]DWORD dwParam1,

[in]DWORD dwParam2,

[in]DWORD dwParam3);

// Creates a new application node which is associated with a specific

// document provider. Before it is visible, the new node must be

// attached to a parent node.

HRESULT CreateApplicationNode(

[out] IDebugApplicationNode IDebugApplicationNode **ppdanNew);

// Fire a generic event to the IApplicationDebugger (if any)

// The semantics of the GUID and IUnknown are entirely application/debugger defined

//

// This method is currently unimplemented but is here to allow for future extensions.

HRESULT FireDebuggerEvent(

[in]REFGUID riid,

[in]IUnknown *punk);

// Called by the language engine in the context of a thread that has caused a runtime error.

// This method causes the current thread to block and a notification of the error

// to be sent to the debugger IDE. When the debugger IDE resumes the application this

// method returns with the action to be taken.

//

// Note: While in the runtime error the language engine may be called in this thread to do

// various things such as enumerating stack frames or evaluating expressions.

HRESULT HandleRuntimeError(

[in] IActiveScriptErrorDebug IActiveScriptErrorDebug *pErrorDebug,// the error that occurred

[in]IActiveScriptSite *pScriptSite,// the script site of the thread

[out]BREAKRESUMEACTION *pbra,// how to continue execution (stepping etc...)

[out]ERRORRESUMEACTION *perra,// how to handle the error case

[out]BOOL *pfCallOnScriptError);// if TRUE then engine should call IActiveScriptSite::OnScriptError()

// return TRUE if there is a JIT debugger registered

BOOL FCanJitDebug ();

// returns TRUE if a JIT debugger is registered and it is registered to auto-JIT debug dumb hosts

BOOL FIsAutoJitDebugEnabled ();

// Adds a global expression context provider to this application

HRESULT AddGlobalExpressionContextProvider(

[in] IProvideExpressionContexts IProvideExpressionContexts *pdsfs,

// Returns a cookie that is used to remove this global expression context provider

// from the application.

[out]DWORD *pdwCookie);

// Removes a global expression context provider from this application.

HRESULT RemoveGlobalExpressionContextProvider(

// The cookie returned by AddGlobalExpressionContextProvider.

[in]DWORD dwCookie);

};

IRemoteDebugApplicationEvents:

This is the event interface supplied by a debug application: It is always called from within the debugger thread.

[

object,

uuid(51973C33-CB0C-11d0-B5C9-00A0244A0E7A),

helpstring("IRemoteDebugApplicationEvents Interface"),

pointer_default(unique)

]

interface IRemoteDebugApplicationEvents : IUnknown

{

HRESULT OnConnectDebugger(

[in] IApplicationDebugger IApplicationDebugger *pad);

HRESULT OnDisconnectDebugger(void);

HRESULT OnSetName(

[in]LPCOLESTR pstrName);

HRESULT OnDebugOutput(

[in]LPCOLESTR pstr);

HRESULT OnClose(void);

HRESULT OnEnterBreakPoint(

[in] IRemoteDebugApplicationThread IRemoteDebugApplicationThread *prdat);

HRESULT OnLeaveBreakPoint(

[in] IRemoteDebugApplicationThread IRemoteDebugApplicationThread *prdat);

HRESULT OnCreateThread(

[in] IRemoteDebugApplicationThread IRemoteDebugApplicationThread *prdat);

HRESULT OnDestroyThread(

[in] IRemoteDebugApplicationThread IRemoteDebugApplicationThread *prdat);

HRESULT OnBreakFlagChange(

[in]APPBREAKFLAGS abf,

[in] IRemoteDebugApplicationThread IRemoteDebugApplicationThread *prdatSteppingThread);

};

IDebugApplicationNode

Provides the functionality of IDebugDocumentProvider, plus a context within a project tree.

[

object,

uuid(51973C34-CB0C-11d0-B5C9-00A0244A0E7A),

pointer_default(unique)

]

interface IDebugApplicationNode : IDebugDocumentProvider IDebugDocumentProvider {

HRESULT EnumChildren(

[out] IEnumDebugApplicationNodes IEnumDebugApplicationNodes **pperddp);

HRESULT GetParent(

[out] IDebugApplicationNode IDebugApplicationNode **pprddp);

HRESULT SetDocumentProvider(

[in] IDebugDocumentProvider IDebugDocumentProvider *pddp);

HRESULT Close(void);

HRESULT Attach(

[in] IDebugApplicationNode IDebugApplicationNode *pdanParent);

HRESULT Detach(void);

}

IDebugApplicationNodeEvents

Event interface for DebugApplicationNode object.

[

object,

uuid(51973C35-CB0C-11d0-B5C9-00A0244A0E7A),

pointer_default(unique)

]

interface IDebugApplicationNodeEvents : IUnknown {

HRESULT onAddChild(

[in] IDebugApplicationNode IDebugApplicationNode *prddpChild);

HRESULT onRemoveChild(

[in] IDebugApplicationNode IDebugApplicationNode *prddpChild);

HRESULT onDetach(void);

HRESULTonAttach(

[in] IDebugApplicationNode IDebugApplicationNode *prddpParent);

}

IDebugThreadCall

IDebugThreadCall is implemented by a component making a cross-thread call using the IDebugThread marshalling implementation in the PDM. It is called by the PDM in the desired thread and should dispatches the call to the desired implementation, casting the parameter information passed in the dwParam's to the appropriate top. It is, of course, a free-threaded object.

[

object,

uuid(51973C36-CB0C-11d0-B5C9-00A0244A0E7A),

pointer_default(unique),

local

]

interface IDebugThreadCall : IUnknown

{

HRESULT ThreadCallHandler(

[in] DWORD dwParam1,

[in] DWORD dwParam2,

[in] DWORD dwParam3);

}

IRemoteDebugApplicationThread

An abstraction representing a thread of execution within a particular application.

[

object,

uuid(51973C37-CB0C-11d0-B5C9-00A0244A0E7A),

pointer_default(unique)

]

interface IRemoteDebugApplicationThread : IUnknown

{

// Returns an operating system dependent identifier associated with the thread.

//

// Note: The returned value does not need to be unique across machines.

HRESULT GetSystemThreadId(

[out]DWORD *dwThreadId);

// Returns the application object associated with the thread.

HRESULT GetApplication(

[out] IRemoteDebugApplication IRemoteDebugApplication **pprda);

// Returns an enumerator for the stack frames associated with the thread. Can only

// be called when in a breakpoint. The stack frame enumerator enumerates stack frames

// in the most recently called order.

HRESULT EnumStackFrames(

[out] IEnumDebugStackFrames IEnumDebugStackFrames **ppedsf);

HRESULT GetDescription(

[out]BSTR *pbstrDescription,

[out]BSTR *pbstrState);

// Forces execution to continue as close as possible to the

// given code context, in the context of the given frame.

// Either of these arguments may be NULL, representing the

// current frame or context.

HRESULT SetNextStatement (

[in] IDebugStackFrame IDebugStackFrame *pStackFrame,

[in] IDebugCodeContext IDebugCodeContext *pCodeContext);

// Thread State flags

typedef DWORD THREAD_STATE;

const THREAD_STATE THREAD_STATE_RUNNING=0x00000001;

const THREAD_STATE THREAD_STATE_SUSPENDED=0x00000002;

const THREAD_STATE THREAD_BLOCKED=0x00000004;

const THREAD_STATE THREAD_OUT_OF_CONTEXT=0x00000008;

// returns the current state of the thread

HRESULT GetState (

[out] DWORD *pState);

// suspends the thread (increments the suspend count)

HRESULT Suspend (

[out] DWORD *pdwCount);

// resumes the thread (decrements the suspend count)

HRESULT Resume (

[out] DWORD *pdwCount);

// returns the current suspend count of the thread

HRESULT GetSuspendCount (

[out] DWORD *pdwCount);

}

IDebugApplicationThread

An extension of IRemoteDebugApplicationThread that provides non-remotable access to the thread. This interface is used by language engines and hosts to provide thread synchronization and to maintain thread specific debug state information.

[

object,

uuid(51973C38-CB0C-11d0-B5C9-00A0244A0E7A),

pointer_default(unique),

local

]

interface IDebugApplicationThread : IRemoteDebugApplicationThread IRemoteDebugApplicationThread

{

// Provides a mechanism for the caller to run code in another thread. This is generally

// used so that language engines and hosts can implement free threaded objects on top

// of their single threaded implementations.

HRESULT SynchronousCallIntoThread(

// The interface to be called back in the target thread.

[in] IDebugThreadCall IDebugThreadCall *pstcb,

// Three arguments passed to the IDebugThreadCall.

[in]DWORD dwParam1,

[in]DWORD dwParam2,

[in]DWORD dwParam3);

// Returns S_OK when this is the currently running thread. Otherwise S_FALSE is returned.

HRESULT QueryIsCurrentThread(

void);

// Returns S_OK when this is the debugger thread. Otherwise, returns S_FALSE.

HRESULT QueryIsDebuggerThread(

void);

HRESULT SetDescription(

[in]LPCOLESTR pstrDescription);

HRESULT SetStateString(

[in]LPCOLESTR pstrState);

}

[

object, local,

uuid(51973C39-CB0C-11d0-B5C9-00A0244A0E7A),

helpstring("IDebugCookie Interface"),

pointer_default(unique)

]

interface IDebugCookie : IUnknown

{

HRESULT SetDebugCookie([in]DWORD dwDebugAppCookie);

};

IEnumDebugApplicationNodes

Enumerates Application nodes. Generally used to enumerate child nodes of a node associated with an application. Example: a project window.

[

object,

uuid(51973C3a-CB0C-11d0-B5C9-00A0244A0E7A),

helpstring("IEnumDebugApplicationNodes Interface"),

pointer_default(unique)

]

interface IEnumDebugApplicationNodes : IUnknown {

[local]

HRESULT __stdcall Next(

[in] ULONG celt,

[out] IDebugApplicationNode IDebugApplicationNode **pprddp,

[out] ULONG *pceltFetched);

HRESULT Skip(

[in] ULONG celt);

HRESULT Reset(void);

HRESULT Clone(

[out] IEnumDebugApplicationNodes IEnumDebugApplicationNodes **pperddp);

};

IEnumRemoteDebugApplications

Enumerates application objects. Generally used to enumerate the running applications on a machine. Example: the "attach to application" dialog.

[

object,

uuid(51973C3b-CB0C-11d0-B5C9-00A0244A0E7A),

helpstring("IEnumRemoteDebugApplications Interface"),

pointer_default(unique)

]

interface IEnumRemoteDebugApplications : IUnknown {

[local]

HRESULT __stdcall Next(

[in] ULONG celt,

[out] IRemoteDebugApplication IRemoteDebugApplication **ppda,

[out] ULONG *pceltFetched);

HRESULT Skip(

[in] ULONG celt);

HRESULT Reset(void);

HRESULT Clone(

[out] IEnumRemoteDebugApplications IEnumRemoteDebugApplications **ppessd);

}

IEnumRemoteDebugApplicationThreads

Enumerates thread objects. Generally used to enumerate the running threads in an application.

[

object,

uuid(51973C3c-CB0C-11d0-B5C9-00A0244A0E7A),

helpstring("IEnumRemoteDebugApplicationThreads Interface"),

pointer_default(unique)

]

interface IEnumRemoteDebugApplicationThreads : IUnknown {

[local]

HRESULT __stdcall Next(

[in] ULONG celt,

[out] IRemoteDebugApplicationThread IRemoteDebugApplicationThread **pprdat,

[out] ULONG *pceltFetched);

HRESULT Skip(

[in] ULONG celt);

HRESULT Reset(void);

HRESULT Clone(

[out] IEnumRemoteDebugApplicationThreads IEnumRemoteDebugApplicationThreads **pperdat);

}

IDebugFormatter

IDebugFormatter allows a language or IDE to customize the conversion between variants or VARTYPES and strings. This interface is used by the ITypeInfo->IDebugProperty mapping implementation.

[

object,

uuid(51973C3d-CB0C-11d0-B5C9-00A0244A0E7A),

helpstring("IDebugFormatter Interface"),

pointer_default(unique),

local

]

interface IDebugFormatter : IUnknown

{

HRESULT GetStringForVariant([in] VARIANT *pvar, [out] BSTR *pbstrValue);

HRESULT GetVariantForString([in] LPCOLESTR pwstrValue, [out] VARIANT *pvar);

HRESULT GetStringForVarType([in] VARTYPE vt, [in] TYPEDESC *ptdescArrayType, [out] BSTR *pbstr);

}

ISimpleConnectionPoint

This interface is the "IDispatchEx" of event interfaces. It provides a simple way for describing and enumerating the events fired on a particular connection pointan also for hooking up an IDispatch to those events. This interface will be available as extended info via the IDebugProperty interface on objects which support events. For simplicity, this interface only works with dispinterfaces.

[

object,

uuid(51973C3e-CB0C-11d0-B5C9-00A0244A0E7A),

helpstring("ISimpleConnectionPoint Interface"),

pointer_default(unique),

local

]

interface ISimpleConnectionPoint : IUnknown

{

// Return the number of events exposed on this interface

HRESULT GetEventCount([out] ULONG *pulCount);

// Return the DISPID and NAME for "cEvents" events, starting at "iEvent".

// The number of

//- Returns S_OK if all of the requested elements were returned.

//- Returns S_FALSE if the enumeration finished and the

//requested number of elements was not available.

//(Unavailable elements will be returned as DISPID_NULL and a null bstr.)

//- Returns E_INVALIDARG (or other error status) if no elements could be fetched

HRESULT DescribeEvents(

[in] ULONG iEvent,// starting event index

[in] ULONG cEvents,// number of events to fetch info for

[out, size_is(cEvents), length_is(*pcEventsFetched)]

DISPID *prgid,// DISPIDs of the events

[out, size_is(cEvents), length_is(*pcEventsFetched)]

BSTR *prgbstr,

[out] ULONG *pcEventsFetched

);// names of the events

HRESULT Advise([in] IDispatch *pdisp, [out] DWORD* pdwCookie);

HRESULT Unadvise([in] DWORD dwCookie);

};

IDebugHelper

IDebugHelper serves as a factory for object browsers and simple connection points.

cpp_quote( "EXTERN_C const CLSID CLSID_DebugHelper;")

[

object,

uuid(51973C3f-CB0C-11d0-B5C9-00A0244A0E7A),

helpstring("IDebugHelper Interface"),

pointer_default(unique),

local

]

interface IDebugHelper : IUnknown

{

// Returns a property browser that wraps a VARIANT

HRESULT CreatePropertyBrowser(

[in] VARIANT *pvar,// root variant to browse

[in] LPCOLESTR bstrName,// name to give the root

[in] IDebugApplicationThread IDebugApplicationThread *pdat, // thread to request properties on

// (or NULL for no marshalling)

[out] IDebugProperty**ppdob);

// Returns a property browser that wraps a VARIANT, and allows for custom conversion

// of variants or VARTYPEs to strings

HRESULT CreatePropertyBrowserEx(

[in] VARIANT *pvar,// root variant to browse

[in] LPCOLESTR bstrName,// name to give the root

[in] IDebugApplicationThread IDebugApplicationThread *pdat, // thread to request properties on

// (or NULL for no marshalling)

[in] IDebugFormatter IDebugFormatter *pdf,// provides custom formatting of variants

[out] IDebugProperty**ppdob);

// Returns an event interface that wraps the given IDispatch (see ISimpleConnectionPoint)

HRESULT CreateSimpleConnectionPoint(

[in] IDispatch *pdisp,

[out] ISimpleConnectionPoint ISimpleConnectionPoint **ppscp);

};

IEnumDebugExpressionContexts

[

object,

uuid(51973C40-CB0C-11d0-B5C9-00A0244A0E7A),

helpstring("IEnumDebugExpressionContexts Interface"),

pointer_default(unique)

]

interface IEnumDebugExpressionContexts : IUnknown

{

[local]

HRESULT __stdcall Next(

[in] ULONG celt,

[out] IDebugExpressionContext IDebugExpressionContext **ppdec,

[out] ULONG *pceltFetched);

HRESULT Skip(

[in] ULONG celt);

HRESULT Reset(void);

HRESULT Clone(

[out] IEnumDebugExpressionContexts IEnumDebugExpressionContexts **ppedec);

}

IProvideExpressionContexts

Provides a means for enumerating expression contexts known by a certain component. Generally implemented by each script engine. Used by the process debug manager to find all global expression contexts associated with a given thread. Note: This interface is called from within the thread of interest. It is up to the implementor to identify the current thread and return an appropriate enumerator.

[

object,

uuid(51973C41-CB0C-11d0-B5C9-00A0244A0E7A),

pointer_default(unique)

]

interface IProvideExpressionContexts : IUnknown

{

// Returns an enumerator of expression contexts.

HRESULT EnumExpressionContexts(

[out] IEnumDebugExpressionContexts IEnumDebugExpressionContexts **ppedec);

}

[

uuid(78a51821-51f4-11d0-8f20-00805f2cd064),

version(1.0),

helpstring("ProcessDebugManagerLib 1.0 Type Library")

]

library ProcessDebugManagerLib

{

importlib("stdole2.tlb");

interface IActiveScriptDebug;

interface IActiveScriptErrorDebug ;

interface IActiveScriptSiteDebug ;

interface IApplicationDebugger ;

interface IApplicationDebuggerUI ;

interface IDebugApplication ;

interface IDebugApplicationNode ;

interface IDebugApplicationNodeEvents ;

interface IDebugApplicationThread ;

interface IDebugAsyncOperation ;

interface IDebugAsyncOperationCallBack ;

interface IDebugCodeContext ;

interface IDebugCookie;

interface IDebugDocument ;

interface IDebugDocumentContext ;

interface IDebugDocumentHelper ;

interface IDebugDocumentHost ;

interface IDebugDocumentInfo;

interface IDebugDocumentProvider ;

interface IDebugDocumentText ;

interface IDebugDocumentTextAuthor ;

interface IDebugDocumentTextEvents ;

interface IDebugDocumentTextExternalAuthor ;

interface IDebugExpression ;

interface IDebugExpressionCallBack;

interface IDebugExpressionContext ;

interface IDebugFormatter ;

interface IDebugHelper ;

interface IDebugSessionProvider;

interface IDebugStackFrame ;

interface IDebugStackFrameSniffer ;

interface IDebugStackFrameSnifferEx ;

interface IDebugSyncOperation ;

interface IDebugThreadCall ;

interface IEnumDebugApplicationNodes ;

interface IEnumDebugCodeContexts ;

interface IEnumDebugExpressionContexts ;

interface IEnumDebugStackFrames ;

interface IEnumRemoteDebugApplications ;

interface IEnumRemoteDebugApplicationThreads ;

interface IMachineDebugManager ;

interface IMachineDebugManagerCookie ;

interface IMachineDebugManagerEvents ;

interface IProcessDebugManager;

interface IProvideExpressionContexts ;

interface IRemoteDebugApplication ;

interface IRemoteDebugApplicationEvents;

interface IRemoteDebugApplicationThread ;

interface ISimpleConnectionPoint ;

[

uuid(78a51822-51f4-11d0-8f20-00805f2cd064),

helpstring("ProcessDebugManager Class")

]

coclass ProcessDebugManager

{

[default] interface IProcessDebugManager;

};

[

uuid(0BFCC060-8C1D-11d0-ACCD-00AA0060275C),

helpstring("DebugHelper Class")

]

coclass DebugHelper

{

[default] interface IDebugHelper ;

};

// CDebugDocumentHelper

//

// The CDebugDocumentHelper makes it much easier for an ActiveScripting

// host or scripting engine to implement the IDebugDocument interfaces.

//

// Given the source text and (optionally) script blocks for a host's

// document, CDebugDocumentHelper provides implementations for

// the debug document interfaces, including:

//

//- IDebugDocumentText

//- IDebugDocumentTextAuthor (for authoring)

//- IDebugDocumentContext

//

// This class supports aggregation, so the host may provide a controlling

// unknown to CoCreateInstance for extensibility.

//

// This class fires events on IDebugDocumentTextEvents, so the host

// can monitor all changes to the document via that interface.

cpp_quote( "EXTERN_C const CLSID CLSID_CDebugDocumentHelper;")

[

uuid(83B8BCA6-687C-11D0-A405-00AA0060275C),

helpstring("DebugDocumentHelper Class")

]

coclass CDebugDocumentHelper

{

[default] interface IDebugDocumentHelper ;

interface IDebugDocumentProvider ;

interface IDebugDocument ;

interface IDebugDocumentText ;

interface IDebugDocumentTextAuthor ;

interface IConnectionPointContainer;

[default, source] interface IDebugDocumentTextEvents ;

};

};


ActiveX Scripting Hosts

When implementing an ActiveX™ Scripting host, you can safely assume that a scripting engine will only call the IActiveScriptSite interface in the context of the base thread as long as the host does the following:

  • Chooses a base thread (generally the thread that contains the message loop).
  • Instantiates the scripting engine in the base thread.
  • Calls scripting engine methods only from the base thread, except where specifically allowed, as in the cases of IActiveScript::InterruptScriptThread and IActiveScript::Clone.
  • Calls the scripting engine's dispatch object only from the base thread.
  • Ensures that the message loop runs in the base thread if a window handle is provided.
  • Ensures that objects in the host's object model only source events in the base thread.

These rules are automatically followed by all single-threaded hosts. The restricted model described above is intentionally loose enough to allow a host to abort a stuck script by calling IActiveScript::InterruptScriptThread from another thread (initiated by a CTRL+BREAK handler or the like), or to duplicate a script in a new thread using IActiveScript::Clone.

Also, none of these restrictions apply to a host that chooses to implement a free-threaded IActiveScriptSite interface and a free-threaded object model. Such a host can use the IActiveScript interface from any thread, without restriction.

Scripting Host Reference


Scripting Host Reference

The following interfaces are specific to Microsoft® ActiveX™ Scripting hosts:

  • IActiveScriptSite
  • IActiveScriptSiteWindow


IActiveScriptSite

The host must create a site for the ActiveX Scripting engine by implementing the IActiveScriptSite interface. Usually, this site will be associated with the container of all the objects that are visible to the script (for example, the ActiveX Controls). Typically, this container will correspond to the document or page being viewed. Microsoft Internet Explorer, for example, would create such a container for each HTML page being displayed. Each ActiveX control (or other automation object) on the page, and the scripting engine itself, would be enumerable within this container.

Methods in Vtable Order
GetLCID | Retrieves the locale identifier that the host uses for displaying user-interface elements.
GetItemInfo | Obtains information about an item that was added to an engine through a call to the IActiveScript::AddNamedItem method.
GetDocVersionString | Retrieves a host-defined string that uniquely identifies the current document version from the host's point of view.
OnScriptTerminate | Called when the script has completed execution.
OnStateChange | Informs the host that the scripting engine has changed states.
OnScriptError | Informs the host that an execution error occurred while the engine was running the script.
OnEnterScript | Informs the host that the scripting engine has begun executing the script code.
OnLeaveScript | Informs the host that the scripting engine has returned from executing script code.


IActiveScriptSite::GetDocVersionString

HRESULT GetDocVersionString(
    BSTR *pbstrVersionString  // address of document version string
);

Retrieves a host-defined string that uniquely identifies the current document version. If the related document has changed outside the scope of ActiveX Scripting (as in the case of an HTML page being edited with NotePad), the scripting engine can save this along with its persisted state, forcing a recompile the next time the script is loaded.

  • Returns S_OK if successful, or E_NOTIMPL if this method is not supported.
pstrVersionString
[out] Address of the host-defined document version string.

If E_NOTIMPL is returned, the scripting engine should assume that the script is in sync with the document.


IActiveScriptSite::GetItemInfo

HRESULT IActiveScriptSite::GetItemInfo(
    LPCOLESTR pstrName,     // address of item name
    DWORD dwReturnMask,     // bit mask for information retrieval
    IUnknown **ppunkItem,   // address of pointer to item's IUnknown
    ITypeInfo **ppTypeInfo  // address of pointer to item's ITypeInfo
);

Allows the scripting engine to obtain information about an item added with the IActiveScript::AddNamedItem method.

  • Returns one of the following values:
    S_OK | Success.
    E_INVALIDARG | An argument was invalid.
    E_POINTER | An invalid pointer was specified.
    TYPE_E_ELEMENTNOTFOUND | An item of the specified name was not found.
pstrName
[in] The name associated with the item, as specified in the IActiveScript::AddNamedItem method.
dwReturnMask
[in] A bit mask specifying what information about the item should be returned. The scripting engine should request the minimum amount of information possible because some of the return parameters (for example, ITypeInfo) can take considerable time to load or generate. Can be a combination of the following values:
SCRIPTINFO_IUNKNOWN | Return the IUnknown interface for this item.
SCRIPTINFO_ITYPEINFO | Return the ITypeInfo interface for this item.
ppunkItem
[out] Address of a variable that receives a pointer to the IUnknown interface associated with the given item. The scripting engine can use the IUnknown::QueryInterface method to obtain the IDispatch interface for the item. This parameter receives NULL if dwReturnMask does not include the SCRIPTINFO_IUNKNOWN value. Also, it receives NULL if there is no object associated with the item name; this mechanism is used to create a simple class when the named item was added with the SCRIPTITEM_CODEONLY flag set in the IActiveScript::AddNamedItem method.
ppTypeInfo
[out] Address of a variable that receives a pointer to the ITypeInfo interface associated with the item. This parameter receives NULL if dwReturnMask does not include the SCRIPTINFO_ITYPEINFO value, or if type information is not available for this item. If type information is not available, the object cannot source events, and name binding must be realized with the IDispatch::GetIDsOfNames method. Note that the ITypeInfo interface retrieved describes the item's coclass (TKIND_COCLASS) because the object may support multiple interfaces and event interfaces. If the item supports the IProvideMultipleTypeInfo interface, the ITypeInfo interface retrieved is the same as the index zero ITypeInfo that would be obtained using the IProvideMultipleTypeInfo::GetInfoOfIndex method.

This method retrieves only the information indicated by the dwReturnMask parameter; this improves performance. For example, if an ITypeInfo interface is not needed for an item, it is simply not specified in dwReturnMask.


IActiveScriptSite::GetLCID

HRESULT GetLCID(
    LCID *plcid  // address of variable for language identifier
);

Retrieves the locale identifier associated with the host's user interface. The scripting engine uses the identifier to ensure that error strings and other user-interface elements generated by the engine appear in the appropriate language.

  • Returns one of the following values:
    S_OK | Success.
    E_NOTIMPL | This method is not implemented. Use the system-defined locale.
    E_POINTER | An invalid pointer was specified.
plcid
[out] Address of a variable that receives the locale identifier for user-interface elements displayed by the scripting engine.

If this method returns E_NOTIMPL, the system-defined locale identifier should be used.


IActiveScriptSite::OnEnterScript

HRESULT OnEnterScript(void);

Informs the host that the scripting engine has begun executing the script code.

  • Returns S_OK if successful.

The scripting engine must call this method on every entry or reentry into the scripting engine. For example, if the script calls an object that then fires an event handled by the scripting engine, the scripting engine must call IActiveScriptSite::OnEnterScript before executing the event, and must call the IActiveScriptSite::OnLeaveScript method after executing the event but before returning to the object that fired the event. Calls to this method can be nested. Every call to this method requires a corresponding call to IActiveScriptSite::OnLeaveScript.


IActiveScriptSite::OnLeaveScript

HRESULT IActiveScriptSite::OnLeaveScript(void);

Informs the host that the scripting engine has returned from executing script code.

  • Returns S_OK if successful.

The scripting engine must call this method before returning control to a calling application that entered the scripting engine. For example, if the script calls an object that then fires an event handled by the scripting engine, the scripting engine must call the IActiveScriptSite::OnEnterScript method before executing the event, and must call IActiveScriptSite::OnLeaveScript after executing the event before returning to the object that fired the event. Calls to this method can be nested. Every call to IActiveScriptSite::OnEnterScript requires a corresponding call to this method.


IActiveScriptSite::OnScriptError

HRESULT IActiveScriptSite::OnScriptError(
    IActiveScriptError *pase  // address of error interface
);

Informs the host that an execution error occurred while the engine was running the script.

  • Returns S_OK if the error was correctly handled, or an OLE defined error code otherwise.
pase
[in] Address of the error object's IActiveScriptError interface. A host can use this interface to obtain information about the execution error.


IActiveScriptSite::OnScriptTerminate

HRESULT OnScriptTerminate(
    VARIANT *pvarResult,   // address of script results
    EXCEPINFO *pexcepinfo  // address of structure with exception information
);

Informs the host that the script has completed execution.

  • Returns S_OK if successful.
pvarResult
[in] Address of a variable that contains the script result, or NULL if the script produced no result.
pexcepinfo
[in] Address of an EXCEPINFO structure that contains exception information generated when the script terminated, or NULL if no exception was generated.

The scripting engine calls this method before the call to the IActiveScriptSite::OnStateChange method, with the SCRIPTSTATE_INITIALIZED flag set, is completed. This method can be used to return completion status and results to the host. Note that many script languages, which are based on sinking events from the host, have life spans that are defined by the host. In this case, this method may never be called.


IActiveScriptSite::OnStateChange

HRESULT IActiveScriptSite::OnStateChange(
    SCRIPTSTATE ssScriptState  // new state of engine
);

Informs the host that the scripting engine has changed states.

  • Returns S_OK if successful.
ssScriptState
[in] Value that indicates the new script state. See the IActiveScript::GetScriptState method for a description of the states.


IActiveScriptSiteWindow

This interface is implemented by hosts that support a user interface on the same object as IActiveScriptSite. Hosts that do not support a user interface, such as servers, would not implement the IActiveScriptSiteWindow interface. The scripting engine accesses this interface by calling QueryInterface from IActiveScriptSite.

Methods in Vtable Order
GetWindow | Retrieves the window handle that can act as the owner of a pop-up window that the scripting engine must display.
EnableModeless | Causes the host to enable or disable its main window as well as any modeless dialog boxes.


IActiveScriptSiteWindow::EnableModeless

HRESULT IActiveScriptSite::EnableModeless(
    BOOL fEnable  // enable flag
);

Causes the host to enable or disable its main window as well as any modeless dialog boxes.

  • Returns S_OK if successful, or E_FAIL if an error occurred.
fEnable
[in] Flag that, if TRUE, enables the main window and modeless dialogs or, if FALSE, disables them.

This method is identical to the IOleInPlaceFrame::EnableModeless method.

Calls to this method can be nested.


IActiveScriptSiteWindow::GetWindow

HRESULT GetWindow(
    HWND *phwnd  // address of variable for window handle
);

Retrieves the handle to a window that can act as the owner of a pop-up window that the scripting engine must display.

  • Returns S_OK if successful, or E_FAIL if an error occurred.
phwnd
[out] Address of a variable that receives the window handle.

This method is similar to the IOleWindow::GetWindow method.


ActiveX Scripting Engines

To implement an ActiveX™ Scripting engine, create an OLE COM object that supports the following interfaces.
Interface | Description
IActiveScript | Provides the basic scripting ability. Implementation of this interface is required.
IActiveScriptParse | Provides the ability to add script text, evaluate expressions, and so on. Implementation of this interface is optional; however, if it is not implemented, the script engine must implement one of the IPersist* interfaces in order to load a script.
IPersist* Provides persistence support. Implementation of at least one of the following interfaces is required if IActiveScriptParse is not implemented.
Interface | Description
IPersistStorage | Provides support for the DATA={url} attribute in the OBJECT tag.
IPersistStreamInit | Provides support for the same as IPersistStorage as well as the DATA="string-encoded byte stream" attribute in the OBJECT tag.
IPersistPropertyBag | Provides support for the PARAM= attribute in the OBJECT tag.

Note It is possible that the scripting engine will never be called upon to save or restore a script state through IPersist*. Instead, IActiveScriptParse is used by calling IActiveScriptParse::InitNew to create a blank script, then scriptlets are added and connected to events with IActiveScriptParse::AddScriptlet and general code is added with IActiveScriptParse::ParseScriptText. Nonetheless, a scripting engine should fully implement at least one IPersist* interface (preferably IPersistStreamInit), because other host applications may try to make use of them.

Registry Standard

Script Engine States

Scripting Engine Threading

Scripting Engine Reference

Registry Standard

An ActiveX Scripting engine can identify itself using component categories. ActiveX Scripting currently defines two component categories.
Category | Description
CATID_ActiveScript | Indicates that the class identifiers (CLSIDs) are ActiveX Scripting engines that support, at a minimum, the IActiveScript interface and a persistence mechanism (the IPersistStorage, IPersistStreamInit, or IPersistPropertyBag interface).
CATID_ActiveScriptParse | Indicates that the CLSIDs are ActiveX Scripting engines that support, at a minimum, the IActiveScript and IActiveScriptParse interfaces.

Although IActiveScriptParse is not a true persistence mechanism, it does support the IActiveScriptParse::InitNew method that is functionally equivalent to IPersist*::InitNew.

Script Engine States

At any given time, an ActiveX Scripting engine can be in one of several states.
State | Description
uninitialized | The script has not been initialized or loaded using an IPersist* interface, or does not have an IActiveScriptSite interface set. The scripting engine is generally not usable from this state until the script is loaded.
initialized The script has been initialized with an IPersist* interface and has an IActiveScriptSite interface set, but is not connected to host objects and sinking events. Note that this state simply means that the IPersist*::Load, IPersist*::InitNew, or IActiveScriptParse::InitNew method has been completed, and that the IActiveScript::SetScriptSite method has been called. The engine cannot run code in this mode. The engine queues code that the host passes to it through the IActiveScriptParse::ParseScriptText method, and executes the code after transitioning to the started state.

Because languages can vary widely in semantics, scripting engines are not required to support this state transition. Engines that support the IActiveScript::Clone method must, however, support this state transition. Hosts must prepare for this transition and take the appropriate action: release the current scripting engine, create a new scripting engine, and call IPersist*::Load, IPersist*::InitNew, or IActiveScriptParse::InitNew (and possibly also call IActiveScriptParse::ParseScriptText). Use of this transition should be considered an optimization of the above steps. Note that any information the scripting engine has obtained about the names of Named Items and the type information describing Named Items remains valid.

Because languages vary widely, defining the exact semantics of this transition is difficult. At a minimum, the scripting engine must disconnect from all events, and release all of the SCRIPTINFO_IUNKNOWN pointers obtained by calling the IActiveScriptSite::GetItemInfo method. The engine must re-obtain these pointers after the script is run again. The scripting engine should also reset the script back to an initial state that is appropriate for the language. VBScript, for example, resets all variables and retains any code added dynamically by calling IActiveScriptParse::ParseScriptText with the SCRIPTTEXT_ISPERSISTENT flag set. Other languages may need to retain current values (such as Lisp because there is no code/data separation) or reset to a well-known state (this includes languages with statically initialized variables).

Note that the transition to the started state should have the same semantics (that is, it should leave the scripting engine in the same state) as calling IPersist*::Save to save the scripting engine, and then calling IPersist*::Load to load a new scripting engine; these actions should have the same semantics as IActiveScript::Clone. Scripting engines that do not yet support IActiveScript::Clone or IPersist* should carefully consider how the transition to the started state should behave, so that such a transition would not violate the above conditions if IActiveScript::Clone or IPersist* support was later added.

During this transition to the started state, the scripting engine will disconnect from event sinks after the appropriate destructors, and so on, are executed in the script. To avoid having these destructors executed, the host can first move the script into the disconnected state before moving into the started state.

Use IActiveScript::InterruptScriptThread to cancel a running script thread without waiting for current events, and so on, to finish running.

started | The transition from the initialized state to the started state causes the engine to execute any code that was queued in the initialized state. The engine can execute code while in the started state, but it is not connected to any events added through the IActiveScript::AddNamedItem method. The engine can execute code by calling the IDispatch interface obtained from the IActiveScript::GetScriptDispatch method, or by calling IActiveScriptParse::ParseScriptText. It is possible that further background initialization (progressive loading) is still ongoing, and that calling the IActiveScript::SetScriptState method with the SCRIPTSTATE_CONNECTED flag set may cause the script to block until initialization is complete.
connected | The script is loaded and connected for sinking events from host objects. If this is a transition from the initialized state, the scripting engine should transition through the started state, performing the necessary actions, before entering the connected state and connecting to events.
disconnected | The script is loaded and has a run-time state, but is temporarily disconnected from sinking events from host objects. This can be done either logically (ignoring events received) or physically (calling IConnectionPoint::Unadvise on the appropriate connection points). If this is a transition from the initialized state, the scripting engine should transition through the started state, performing the necessary actions, before entering the disconnected state. Event sinks that are in progress are completed before the state changes (use IActiveScript::InterruptScriptThread to cancel a running script thread). This state is distinguished from the initialized state in that the transition to this state does not cause the script to reset, the run-time state of the script is not reset, and a script initialization procedure is not executed.
closed | The script has been closed. The scripting engine no longer works and returns errors for most methods.

The following illustration shows the relationships between the various scripting engine states, and shows the methods that cause transitions from one state to another.

The following illustration shows the actions that the scripting engine performs during the various state transitions.

Scripting Engine Threading

Because an ActiveX Scripting engine can be used in many environments, it is important to keep its execution model as flexible as possible. For example, a server-based host may need to preserve a multithreaded design while using ActiveX Scripting in an efficient manner. At the same time, a host that does not use threading, such as a typical application, should not be burdened with threading management. ActiveX Scripting achieves this balance by restricting the ways a free-threaded scripting engine can call back to the host, freeing hosts from this burden.

Scripting engines used on servers are typically implemented as free-threading COM objects. This means that methods on the IActiveScript interface and its associated interfaces can be called from any thread in the process, without marshaling. (Unfortunately, this also means that the scripting engine must be implemented as an in-process server, because OLE does not currently support interprocess marshaling of free-threaded objects.) Synchronization is the responsibility of the scripting engine. For scripting engines that are not internally reentrant, or for language models that are not multithreaded, synchronization could be as simple as serializing access to the scripting engine with a mutex. Of course certain methods, such as IActiveScript::InterruptScriptThread, should not be serialized in this way so that a stuck script can be terminated from another thread.

The fact that IActiveScript is typically free-threaded generally implies that the IActiveScriptSite interface and the host's object model should be free-threaded as well. This would make implementation of the host quite difficult, particularly in the common case where the host is a single-threaded Microsoft Windows®-based application with single-threaded or apartment-model ActiveX Controls in its object model. For this reason, the following constraints are placed on the scripting engine's use of IActiveScriptSite:

  • The script site is always called in the context of a host thread. That is, the scripting engine never calls the script site in the context of a thread that the scripting engine created, but only from within a scripting engine method that was called from the host through IActiveScript and its derivatives, through the exposed scripting engine's dispatch object, through a Windows message, or from an event source in the host's object model.
  • The script site is never called from within the context of a simple thread state control method (for example, the IActiveScript::InterruptScriptThread method) or from the IActiveScript::Clone method.


Scripting Engine Reference

The following elements are specific to ActiveX Scripting Engines.
Interfaces
IActiveScript
IActiveScriptError
IActiveScriptParse
Enumerations
SCRIPTSTATE
SCRIPTTHREADSTATE


IActiveScript

The IActiveScript interface provides the methods necessary to initialize the scripting engine. The scripting engine must implement the IActiveScript interface.

Methods in Vtable Order
SetScriptSite | Informs the scripting engine of the IActiveScriptSite site provided by the host.
GetScriptSite | Retrieves the site object associated with the ActiveX Scripting engine.
SetScriptState | Places the scripting engine into the specified state.
GetScriptState | Retrieves the current state of the scripting engine.
Close | Causes the scripting engine to abandon any currently loaded script, lose its state, and release any interface pointers it has to other objects, thus entering a closed state.
AddNamedItem | Adds the name of a root-level item to the scripting engine's name space.
AddTypeLib | Adds a type library to the name space for the script.
GetScriptDispatch | Retrieves the IDispatch interface for the running script.
GetCurrentScriptThreadID | Retrieves a scripting-engine-defined identifier for the currently executing thread.
GetScriptThreadID | Retrieves a scripting-engine-defined identifier for the thread associated with the given Microsoft Win32® thread.
GetScriptThreadState | Retrieves the current state of a script thread.
InterruptScriptThread | Interrupts the execution of a running script thread.
Clone | Clones the current scripting engine (minus any current execution state), returning a loaded scripting engine that has no site in the current thread.


IActiveScript::AddNamedItem

HRESULT AddNamedItem(
    LPCOLESTR pstrName,  // address of item name
    DWORD dwFlags        // item flags
);

Adds the name of a root-level item to the scripting engine's name space. A root-level item is an object with properties and methods, an event source, or all three.

  • Returns one of the following values:
    S_OK | Success.
    E_INVALIDARG | An argument was invalid.
    E_POINTER | An invalid pointer was specified.
    E_UNEXPECTED | The call was not expected (for example, the scripting engine has not yet been loaded or initialized).
pstrName
[in] Address of a buffer that contains the name of the item as viewed from the script. The name must be unique and persistable.
dwFlags
[in] Flags associated with an item. Can be a combination of these values:
SCRIPTITEM_CODEONLY | Indicates that the named item represents a code-only object, and that the host has no IUnknown to be associated with this code-only object. The host only has a name for this object. In object-oriented languages such as C++, this flag would create a class. Not all languages support this flag.
SCRIPTITEM_GLOBALMEMBERS | Indicates that the item is a collection of global properties and methods associated with the script. Normally, a scripting engine would ignore the object name (other than for the purpose of using it as a cookie for the IActiveScriptSite::GetItemInfo method, or for resolving explicit scoping) and expose its members as global variables and methods. This allows the host to extend the library (run-time functions and so on) available to the script. It is left to the scripting engine to deal with name conflicts (for example, when two SCRIPTITEM_GLOBALMEMBERS items have methods of the same name), although an error should not be returned because of this situation.
SCRIPTITEM_ISPERSISTENT | Indicates that the item should be saved if the scripting engine is saved. Similarly, setting this flag indicates that a transition back to the initialized state should retain the item's name and type information (the scripting engine must, however, release all pointers to interfaces on the actual object).
SCRIPTITEM_ISSOURCE | Indicates that the item sources events that the script can sink. Child objects (properties of the object that are in themselves objects) can also source events to the script. This is not recursive, but it provides a convenient mechanism for the common case, for example, of creating a container and all of its member controls.
SCRIPTITEM_ISVISIBLE | Indicates that the item's name is available in the name space of the script, allowing access to the properties, methods, and events of the item. By convention the properties of the item include the item's children; therefore, all child object properties and methods (and their children, recursively) will be accessible.
SCRIPTITEM_NOCODE | Indicates that the item is simply a name being added to the script's name space, and should not be treated as an item for which code should be associated. For example, without this flag being set, VBScript will create a separate module for the named item, and C++ might create a separate wrapper class for the named item.


IActiveScript::AddTypeLib

HRESULT AddTypeLib(
    REFGUID guidTypeLib,  // CLSID of type library
    DWORD dwMaj,          // major version number
    DWORD dwMin,          // minor version number
    DWORD dwFlags         // option flags
);

Adds a type library to the name space for the script. This is similar to the #include directive in C/C++. It allows a set of predefined items such as class definitions, typedefs, and named constants to be added to the run-time environment available to the script.

  • Returns one of the following values:
    S_OK | Success.
    E_INVALIDARG | An argument was invalid.
    E_UNEXPECTED | The call was not expected (for example, the scripting engine has not yet been loaded or initialized).
    TYPE_E_CANTLOADLIBRARY | The specified type library could not be loaded.
guidTypeLib
[in] CLSID of the type library to add.
dwMaj
[in] Major version number.
dwMin
[in] Minor version number.
dwFlags
[in] Option flags. Can be the following:
SCRIPTTYPELIB_ISCONTROL The type library describes an ActiveX control used by the host.


IActiveScript::Clone

HRESULT Clone(
    IActiveScript **ppscript  // receives pointer to IActiveScript
);

Clones the current scripting engine (minus any current execution state), returning a loaded scripting engine that has no site in the current thread. The properties of this new scripting engine will be identical to the properties the original scripting engine would be in if it were transitioned back to the initialized state.

  • Returns one of the following values:
    S_OK | Success.
    E_NOTIMPL | This method is not supported.
    E_POINTER | An invalid pointer was specified.
    E_UNEXPECTED | The call was not expected (for example, the scripting engine has not yet been loaded or initialized).
ppscript
[out] Address of a variable that receives a pointer to the IActiveScript interface of the cloned scripting engine. The host must create a site and call the IActiveScript::SetScriptSite method on the new scripting engine before it will be in the initialized state and, therefore, usable.

The IActiveScript::Clone method is an optimization of IPersist*::Save, CoCreateInstance, and IPersist*::Load, so the state of the new scripting engine should be the same as if the state of the original scripting engine were saved and loaded into a new scripting engine. Named items are duplicated in the cloned scripting engine, but specific object pointers for each item are forgotten and are obtained with the IActiveScriptSite::GetItemInfo method. This allows an identical object model with per-thread entry points (an apartment model) to be used.

This method is used for multithreaded server hosts that can run multiple instances of the same script. The scripting engine may return E_NOTIMPL, in which case the host can achieve the same result by duplicating the persistent state and creating a new instance of the scripting engine with an IPersist* interface.

This method can be called from non-base threads without resulting in a non-base callout to host objects or to the IActiveScriptSite interface.


IActiveScript::Close

HRESULT Close(void);

Causes the scripting engine to abandon any currently loaded script, lose its state, and release any interface pointers it has to other objects, thus entering a closed state. Event sinks, immediately executed script text, and macro invocations that are already in progress are completed before the state changes (use IActiveScript::InterruptScriptThread to cancel a running script thread). This method must be called by the creating host before the interface is released to prevent circular reference problems.

  • Returns one of the following values:
    S_OK | Success.
    E_UNEXPECTED | The call was not expected (for example, the scripting engine was already in the closed state).
    OLESCRIPT_S_PENDING | The method was queued successfully, but the state hasn't changed yet. When the state changes, the site will be called back on the IActiveScriptSite::OnStateChange method.
    S_FALSE | The method succeeded, but the script was already closed.


IActiveScript::GetCurrentScriptThreadID

HRESULT GetCurrentScriptThreadID(
    SCRIPTTHREADID *pstidThread  // receives scripting thread identifier
);

Retrieves a scripting-engine-defined identifier for the currently executing thread. The identifier can be used in subsequent calls to script thread execution-control methods such as the IActiveScript::InterruptScriptThread method.

  • Returns S_OK if successful, or E_POINTER if an invalid pointer was specified.
pstidThread
[out] Address of a variable that receives the script thread identifier associated with the current thread. The interpretation of this identifier is left to the scripting engine, but it can be just a copy of the Windows thread identifier. If the Win32 thread terminates, this identifier becomes unassigned and can subsequently be assigned to another thread.

This method can be called from non-base threads without resulting in a non-base callout to host objects or to the IActiveScriptSite interface.


IActiveScript::GetScriptDispatch

HRESULT GetScriptDispatch(
    LPCOLESTR pstrItemName  // address of item name
    IDispatch **ppdisp      // receives IDispatch pointer
);

Retrieves the IDispatch interface for the methods and properties associated with the currently running script.

  • Returns one of the following values:
    S_OK | Success.
    E_INVALIDARG | An argument was invalid.
    E_POINTER | An invalid pointer was specified.
    E_UNEXPECTED | The call was not expected (for example, the scripting engine has not yet been loaded or initialized).
    S_FALSE | The scripting engine does not support a dispatch object; the ppdisp parameter is set to NULL.
pstrItemName
[in] Address of a buffer that contains the name of the item for which the caller needs the associated dispatch object. If this parameter is NULL, the dispatch object contains as its members all of the global methods and properties defined by the script. Through the IDispatch interface and the associated ITypeInfo interface, the host can invoke script methods or view and modify script variables.
ppdisp
[out] Address of a variable that receives a pointer to the object associated with the script's global methods and properties. If the scripting engine does not support such an object, NULL is returned.

Because methods and properties can be added by calling the IActiveScriptParse interface, the IDispatch interface returned by this method can dynamically support new methods and properties. Similarly, the IDispatch::GetTypeInfo method should return a new, unique ITypeInfo interface when methods and properties are added. Note, however, that language engines must not change the IDispatch interface in a way that is incompatible with any previous ITypeInfo interface returned. That implies, for example, that DISPIDs will never be reused.


IActiveScript::GetScriptSite

HRESULT GetScriptSite(
    REFIID iid,           // interface identifier
    void **ppvSiteObject  // address of host site interface
);

Retrieves the site object associated with the ActiveX Scripting engine.

  • Returns one of the following values:
    S_OK | Success.
    E_INVALIDARG | An argument was invalid.
    E_NOINTERFACE | The specified interface is not supported.
    E_POINTER | An invalid pointer was specified.
    S_FALSE | No site has been set; the ppvSiteObject parameter is set to NULL.
iid
[in] Identifier of the requested interface.
ppvSiteObject
[out] Address of the location that receives the interface pointer to the host's site object.


IActiveScript::GetScriptState

HRESULT GetScriptState(
    SCRIPTSTATE *pss  // address of structure for state information
);

Retrieves the current state of the scripting engine. This method can be called from non-base threads without resulting in a non-base callout to host objects or to the IActiveScriptSite interface.

  • Returns S_OK if successful, or E_POINTER if an invalid pointer was specified.
pss
[out] Address of a variable that receives a value defined in the SCRIPTSTATE enumeration. The value indicates the current state of the scripting engine associated with the calling thread.


IActiveScript::GetScriptThreadID

HRESULT GetScriptThreadID(
    DWORD dwWin32ThreadID,       // Win32 thread identifier
    SCRIPTTHREADID *pstidThread  // receives scripting thread identifier
);

Retrieves a scripting-engine-defined identifier for the thread associated with the given Win32 thread.

  • Returns one of the following values:
    S_OK | Success.
    E_POINTER | An invalid pointer was specified.
    E_UNEXPECTED | The call was not expected (for example, the scripting engine has not yet been loaded or initialized) and therefore failed.
dwWin32ThreadID
[in] Thread identifier of a running Win32 thread in the current process. Use the GetCurrentScriptThreadID function to retrieve the thread identifier of the currently executing thread.
pstidThread
[out] Address of a variable that receives the script thread identifier associated with the given Win32 thread. The interpretation of this identifier is left to the scripting engine, but it can be just a copy of the Windows thread identifier. Note that if the Win32 thread terminates, this identifier becomes unassigned and may subsequently be assigned to another thread.

The retrieved identifier can be used in subsequent calls to script thread execution control methods such as the IActiveScript::InterruptScriptThread method.

This method can be called from non-base threads without resulting in a non-base callout to host objects or to the IActiveScriptSite interface.


IActiveScript::GetScriptThreadState

HRESULT GetScriptThreadState(
    SCRIPTTHREADID stidThread,    // identifier of script thread
    SCRIPTTHREADSTATE *pstsState  // receives state flag
);

Retrieves the current state of a script thread.

  • Returns one of the following values:
    S_OK | Success.
    E_POINTER | An invalid pointer was specified.
    E_UNEXPECTED | The call was not expected (for example, the scripting engine has not yet been loaded or initialized).
stidThread
[in] Identifier of the thread for which the state is desired, or one of the following special thread identifiers:
SCRIPTTHREADID_BASE | The base thread; that is, the thread in which the scripting engine was instantiated.
SCRIPTTHREADID_CURRENT | The currently executing thread.
pstsState
[out] Address of a variable that receives the state of the indicated thread. The state is indicated by one of the named constant values defined by the SCRIPTTHREADSTATE enumeration. If this parameter does not identify the current thread, the state may change at any time.

This method can be called from non-base threads without resulting in a non-base callout to host objects or to the IActiveScriptSite interface.


IActiveScript::InterruptScriptThread

HRESULT InterruptScriptThread(
    SCRIPTTHREADID   stidThread,  // identifier of thread
    const EXCEPINFO *pexcepinfo,  // receives error information
    DWORD dwFlags
);

Interrupts the execution of a running script thread (an event sink, an immediate execution, or a macro invocation). This method can be used to terminate a script that is stuck (in an infinite loop, for example). It can be called from non-base threads without resulting in a non-base callout to host objects or to the IActiveScriptSite method.

  • Returns one of the following values:
    S_OK | Success.
    E_INVALIDARG | An argument was invalid.
    E_POINTER | An invalid pointer was specified.
    E_UNEXPECTED | The call was not expected (for example, the scripting engine has not yet been loaded or initialized).
stidThread
[in] Identifier of the thread to interrupt, or one of the following special thread identifier values:
SCRIPTTHREADID_ALL | All threads. The interrupt is applied to all script methods currently in progress. Note that unless the caller has requested that the script be disconnected, by calling the IActiveScript::SetScriptState method with the SCRIPTSTATE_DISCONNECTED or SCRIPTSTATE_INITIALIZED flag set, the next scripted event causes script code to run again.
SCRIPTTHREADID_BASE | The base thread; that is, the thread in which the scripting engine was instantiated.
SCRIPTTHREADID_CURRENT | The currently executing thread.
pexcepinfo
[in] Address of an EXCEPINFO structure that receives error information associated with the error condition.
dwFlags
[in] Option flags associated with the interruption. Can be one of these values:
SCRIPTINTERRUPT_DEBUG
If supported, enter the scripting engine's debugger at the current script execution point.
SCRIPTINTERRUPT_RAISEEXCEPTION
If supported by the scripting engine's language, let the script handle the exception. Otherwise, the script method is aborted and the error code is returned to the caller; that is, the event source or macro invoker.


IActiveScript::SetScriptSite

HRESULT SetScriptSite(
    IActiveScriptSite *pScriptSite  // address of host script site
);

Informs the scripting engine of the IActiveScriptSite interface site provided by the host. This method must be called before any other IActiveScript interface methods can be used.

  • Returns one of the following values:
    S_OK | Success.
    E_FAIL | An unspecified error occurred; the scripting engine was unable to finish initializing the site.
    E_INVALIDARG | An argument was invalid.
    E_POINTER | An invalid pointer was specified.
    E_UNEXPECTED | The call was not expected (for example, a site was already set).
pScriptSite
[in] Address of the host-supplied script site to be associated with this instance of the scripting engine. The site must be uniquely assigned to this scripting engine instance; it cannot be shared with other scripting engines.


IActiveScript::SetScriptState

HRESULT SetScriptState(
    SCRIPTSTATE ss  // identifier of new state
);

Puts the scripting engine into the given state. This method can be called from non-base threads without resulting in a non-base callout to host objects or to the IActiveScriptSite interface.

  • Returns one of the following values:
    S_OK | Success.
    E_FAIL | The scripting engine does not support the transition back to the initialized state. The host must discard this scripting engine and create, initialize, and load a new scripting engine to achieve the same effect.
    E_UNEXPECTED | The call was not expected (for example, the scripting engine has not yet been loaded or initialized) and therefore failed.
    OLESCRIPT_S_PENDING | The method was queued successfully, but the state has not changed yet. When the state changes, the site will be called back through the IActiveScriptSite::OnStateChange method.
    S_FALSE | The method succeeded, but the script was already in the given state.
ss
[in] Sets the scripting engine to the given state. Can be one of the values defined in the SCRIPTSTATE enumeration.

For more information about scripting engine states, see Script Engine States.

See also IActiveScript::Clone, IActiveScript::GetScriptDispatch, IActiveScript::InterruptScriptThread, IActiveScriptParse::ParseScriptText, IActiveScriptSite::GetItemInfo


IActiveScriptError

An object implementing this interface is passed to the IActiveScriptSite::OnScriptError method whenever the scripting engine encounters an unhandled error. The host then calls methods on this object to obtain information about the error that occurred.

Methods in Vtable Order
GetExceptionInfo | Retrieves information about an error.
GetSourcePosition | Retrieves the location in the source code where an error occurred.
GetSourceLineText | Retrieves the line in the source file where an error occurred.


IActiveScriptError::GetExceptionInfo

HRESULT GetExceptionInfo(
    EXCEPINFO *pexcepinfo  // structure for exception information
);

Retrieves information about an error that occurred while the scripting engine was running a script.

  • Returns S_OK if successful, or E_FAIL if an error occurred.
pexcepinfo
[out] Address of an EXCEPINFO structure that receives error information.


IActiveScriptError::GetSourceLineText

HRESULT GetSourceLineText(
    BSTR *pbstrSourceLine  // address of buffer for source line
);

Retrieves the line in the source file where an error occurred while a scripting engine was running a script.

  • Returns S_OK if successful, or E_FAIL if the line in the source file was not retrieved.
pbstrSourceLine
[out] Address of a buffer that receives the line of source code in which the error occurred.


IActiveScriptError::GetSourcePosition

HRESULT GetSourcePosition(
    DWORD *pdwSourceContext,  // context cookie
    ULONG *pulLineNumber,     // line number of error
    LONG *pichCharPosition    // character position of error
);

Retrieves the location in the source code where an error occurred while the scripting engine was running a script.

  • Returns S_OK if successful, or E_FAIL if the location was not retrieved.
pdwSourceContext
[out] Address of a variable that receives a cookie that identifies the context. The interpretation of this parameter depends on the host application.
pulLineNumber
[out] Address of a variable that receives the line number in the source file where the error occurred.
pichCharPosition
[out] Address of a variable that receives the character position in the line where the error occurred.


IActiveScriptParse

If the ActiveX Scripting engine allows raw text code scriptlets to be added to the script or allows expression text to be evaluated at run time, it implements the IActiveScriptParse interface. For interpreted scripting languages that have no independent authoring environment, such as VBScript, this provides an alternate mechanism (other than IPersist*) to get script code into the scripting engine, and to attach script fragments to various object events.

Methods in Vtable Order
InitNew | Initializes the scripting engine.
AddScriptlet | Adds a code scriptlet to the script.
ParseScriptText | Parses the given code scriptlet, adding declarations into the name space and evaluating code as appropriate.


IActiveScriptParse::AddScriptlet

HRESULT AddScriptlet(
    LPCOLESTR pstrDefaultName,   // address of default name of scriptlet
    LPCOLESTR pstrCode,          // address of scriptlet text
    LPCOLESTR pstrItemName,      // address of item name
    LPCOLESTR pstrSubItemName,   // address of subitem name
    LPCOLESTR pstrEventName,     // address of event name
    LPCOLESTR pstrDelimiter,     // address of end-of-scriptlet delimiter
    DWORD dwSourceContextCookie, // application-defined value for debugging
    ULONG ulStartingLineNumber,  // starting line of the script
    DWORD dwFlags,               // scriptlet flags
    BSTR *pbstrName,             // address of actual name of scriptlet
    EXCEPINFO *pexcepinfo        // address of exception information
);

Adds a code scriptlet to the script. This method is used in environments where the persistent state of the script is intertwined with the host document and the host is responsible for restoring the script, rather than through an IPersist* interface. The primary examples are HTML scripting languages that allow scriptlets of code embedded in the HTML document to be attached to intrinsic events (for instance, ONCLICK="button1.text='Exit'").

  • Returns one of the following values:
    S_OK | Success.
    DISP_E_EXCEPTION | An exception occurred in the parsing of the scriptlet; the pexcepinfo parameter contains information about the exception.
    E_INVALIDARG | An argument was invalid.
    E_NOTIMPL | This method is not supported; the scripting engine does not support adding event-sinking scriptlets.
    E_POINTER | An invalid pointer was specified.
    E_UNEXPECTED | The call was not expected (for example, the scripting engine has not yet been loaded or initialized) and therefore failed.
    OLESCRIPT_E_INVALIDNAME | The default name supplied is invalid in this scripting language.
    OLESCRIPT_E_SYNTAX | An unspecified syntax error occurred in the scriptlet.
pstrDefaultName
[in] Address of a default name to associate with the scriptlet. If the scriptlet does not contain naming information (as in the ONCLICK example above), this name will be used to identify the scriptlet. If this parameter is NULL, the scripting engine manufactures a unique name, if necessary.
pstrCode
[in] Address of the scriptlet text to add. The interpretation of this string depends on the scripting language.
pstrItemName
[in] Address of a buffer that contains the item name associated with this scriptlet. This parameter, in addition to pstrSubItemName, identifies the object for which the scriptlet is an event handler.
pstrSubItemName
[in] Address of a buffer that contains the name of a subobject of the named item with which this scriptlet is associated; this name must be found in the named item's type information. This parameter is NULL if the scriptlet is to be associated with the named item instead of a subitem. This parameter, in addition to pstrItemName, identifies the specific object for which the scriptlet is an event handler.
pstrEventName
[in] Address of a buffer that contains the name of the event for which the scriptlet is an event handler.
pstrDelimiter
[in] Address of the end-of-scriptlet delimiter. When the pstrCode parameter is parsed from a stream of text, the host typically uses a delimiter, such as two single quotation marks ("), to detect the end of the scriptlet. This parameter specifies the delimiter that the host used, allowing the scripting engine to provide some conditional primitive preprocessing (for example, replacing a single quotation mark ['] with two single quotation marks for use as a delimiter). Exactly how (and if) the scripting engine makes use of this information depends on the scripting engine. Set this parameter to NULL if the host did not use a delimiter to mark the end of the scriptlet.
dwSourceContextCookie
[in] Application-defined value that is used for debugging purposes.
ulStartingLineNumber
[in] Zero-based value that specifies which line the parsing will begin at.
dwFlags
[in] Flags associated with the scriptlet. Can be a combination of the following values:
SCRIPTTEXT_ISVISIBLE | Indicates that the script text should be visible (and, therefore, callable by name) as a global method in the name space of the script.
SCRIPTTEXT_ISPERSISTENT | Indicates that the code added during this call should be saved if the scripting engine is saved (for example, through a call to IPersist*::Save), or if the scripting engine is reset by way of a transition back to the initialized state. For more information about this state, see Script Engine States.
pbstrName
[out] Actual name used to identify the scriptlet. This will be, in order of preference: a name explicitly specified in the scriptlet text, the default name provided in pstrDefaultName, or a unique name synthesized by the scripting engine.
pexcepinfo
[out] Address of a structure containing exception information. This structure should be filled in if DISP_E_EXCEPTION is returned.


IActiveScriptParse::InitNew

HRESULT InitNew(void);

Initializes the scripting engine.

  • Returns S_OK if successful, or E_FAIL if an error occurred during initialization.

Before the scripting engine can be used, one of the following methods must be called: IPersist*::Load, IPersist*::InitNew, or IActiveScriptParse::InitNew. The semantics of this method are identical to IPersistStreamInit::InitNew, in that this method tells the scripting engine to initialize itself. Note that it is not valid to call both IPersist*::InitNew or IActiveScriptParse::InitNew and IPersist*::Load, nor is it valid to call IPersist*::InitNew, IActiveScriptParse::InitNew, or IPersist*::Load more than once.


IActiveScriptParse::ParseScriptText

HRESULT ParseScriptText(
    LPCOLESTR pstrCode,          // address of scriptlet text
    LPCOLESTR pstrItemName,      // address of item name
    IUnknown *punkContext,       // address of debugging context
    LPCOLESTR pstrDelimiter,     // address of end-of-scriptlet delimiter
    DWORD dwSourceContextCookie, // application-defined value for debugging
    ULONG ulStartingLineNumber,  // starting line of the script
    DWORD dwFlags,               // scriptlet flags
    VARIANT *pvarResult,         // address of buffer for results
    EXCEPINFO *pexcepinfo        // address of buffer for error data
);

Parses the given code scriptlet, adding declarations into the name space and evaluating code as appropriate.

  • Returns one of the following values:
    S_OK | Success.
    DISP_E_EXCEPTION | An exception occurred in the processing of the scriptlet. The pexcepinfo parameter contains information about the exception.
    E_INVALIDARG | An argument was invalid.
    E_POINTER | An invalid pointer was specified.
    E_NOTIMPL | This method is not supported. The scripting engine does not support run-time evaluation of expressions or statements.
    E_UNEXPECTED | The call was not expected (for example, the scripting engine is in the uninitialized or closed state, or the SCRIPTTEXT_ISEXPRESSION flag was set and the scripting engine is in the initialized state).
    OLESCRIPT_E_SYNTAX | An unspecified syntax error occurred in the scriptlet.
pstrCode
[in] Address of the scriptlet text to evaluate. The interpretation of this string depends on the scripting language.
pstrItemName
[in] Address of the item name that gives the context in which the scriptlet is to be evaluated. If this parameter is NULL, the code is evaluated in the scripting engine's global context.
punkContext
[in] Address of the context object. This object is reserved for use in a debugging environment, where such a context may be provided by the debugger to represent an active run-time context. If this parameter is NULL, the engine uses pstrItemName to identify the context.
pstrDelimiter
[in] Address of the end-of-scriptlet delimiter. When pstrCode is parsed from a stream of text, the host typically uses a delimiter, such as two single quotation marks ("), to detect the end of the scriptlet. This parameter specifies the delimiter that the host used, allowing the scripting engine to provide some conditional primitive preprocessing (for example, replacing a single quotation mark ['] with two single quotation marks for use as a delimiter). Exactly how (and if) the scripting engine makes use of this information depends on the scripting engine. Set this parameter to NULL if the host did not use a delimiter to mark the end of the scriptlet.
dwSourceContextCookie
[in] Application-defined value that is used for debugging purposes.
ulStartingLineNumber
[in] Zero-based value that specifies which line the parsing will begin at.
dwFlags
[in] Flags associated with the scriptlet. Can be a combination of these values:
SCRIPTTEXT_ISEXPRESSION | If the distinction between a computational expression and a statement is important but syntactically ambiguous in the script language, this flag specifies that the scriptlet is to be interpreted as an expression, rather than as a statement or list of statements. By default, statements are assumed unless the correct choice can be determined from the syntax of the scriptlet text.
SCRIPTTEXT_ISPERSISTENT | Indicates that the code added during this call should be saved if the scripting engine is saved (for example, through a call to IPersist*::Save), or if the scripting engine is reset by way of a transition back to the initialized state.
SCRIPTTEXT_ISVISIBLE | Indicates that the script text should be visible (and, therefore, callable by name) as a global method in the name space of the script.
pvarResult
[out] Address of a buffer that receives the results of scriptlet processing, or NULL if the caller expects no result (that is, the SCRIPTTEXT_ISEXPRESSION value is not set).
pexcepinfo
[out] Address of a structure that receives exception information. This structure is filled if IActiveScriptParse::ParseScriptText returns DISP_E_EXCEPTION.

If the scripting engine is in the initialized state, no code will actually be evaluated during this call; rather, such code is queued and executed when the scripting engine is transitioned into (or through) the started state. Because execution is not allowed in the initialized state, it is an error to call this method with the SCRIPTTEXT_ISEXPRESSION flag when in the initialized state.

The scriptlet can be an expression, a list of statements, or anything allowed by the script language. For example, this method is used in the evaluation of the HTML <SCRIPT> tag, which allows statements to be executed as the HTML page is being constructed, rather than just compiling them into the script state.

The code passed to this method must be a valid, complete portion of code. For example, in VBScript it is illegal to call this method once with Sub Function(x) and then a second time with End Sub. The parser must not wait for the second call to complete the subroutine, but rather must generate a parse error because a subroutine declaration was started but not completed.

For more information about script states, see Script Engine States.


Enumerations

This section describes the enumerations used by ActiveX Scripting engines.

SCRIPTSTATE

SCRIPTTHREADSTATE


SCRIPTSTATE

typedef enum tagSCRIPTSTATE {
    SCRIPTSTATE_UNINITIALIZED = 0,
    SCRIPTSTATE_INITIALIZED   = 5,
    SCRIPTSTATE_STARTED       = 1,
    SCRIPTSTATE_CONNECTED     = 2,
    SCRIPTSTATE_DISCONNECTED  = 3,
    SCRIPTSTATE_CLOSED        = 4
} SCRIPTSTATE;

Contains named constant values that specify the state of a scripting engine. This enumeration is used by the IActiveScript::GetScriptState, IActiveScript::SetScriptState, and IActiveScriptSite::OnStateChange methods.

SCRIPTSTATE_UNINITIALIZED
Script has just been created, but has not yet been initialized using an IPersist* interface and IActiveScript::SetScriptSite.
SCRIPTSTATE_INITIALIZED
Script has been initialized, but is not running (connecting to other objects or sinking events) or executing any code. Code can be queried for execution by calling the IActiveScriptParse::ParseScriptText method.
SCRIPTSTATE_STARTED
Script can execute code, but is not yet sinking the events of objects added by the IActiveScript::AddNamedItem method.
SCRIPTSTATE_CONNECTED
Script is loaded and connected for sinking events.
SCRIPTSTATE_DISCONNECTED
Script is loaded and has a run-time execution state, but is temporarily disconnected from sinking events.
SCRIPTSTATE_CLOSED
Script has been closed. The scripting engine no longer works and returns errors for most methods.


SCRIPTTHREADSTATE

typedef enum tagSCRIPTTHREADSTATE {
    SCRIPTTHREADSTATE_NOTINSCRIPT  = 0,
    SCRIPTTHREADSTATE_RUNNING      = 1
} SCRIPTTHREADSTATE;

Contains named constant values that specify the state of a thread in a scripting engine. This enumeration is used by the IActiveScript::GetScriptThreadState method.

SCRIPTTHREADSTATE_NOTINSCRIPT
Specified thread is not currently servicing a scripted event, processing immediately executed script text, or running a script macro.
SCRIPTTHREADSTATE_RUNNING
Specified thread is actively servicing a scripted event, processing immediately executed script text, or running a script macro.


IDispatchEx

IDispatchEx, an extension of the IDispatch interface, supports features appropriate for dynamic languages such as scripting languages. This section describes the IDispatchEx interface itself, the differences between IDispatch and IDispatchEx, and the rationale for the extensions. It is expected that readers are familiar with IDispatch and have access to the IDispatch documentation.

Description

IDispatchEx Methods

IDispatch Methods

Examples

Description

IDispatch was developed essentially for Microsoft® Visual Basic®. The primary limitation of IDispatch is that it assumes objects are static, that is, they do not change during run time and thus can be fully described at compile time by type information. Dynamic run time models as found in scripting languages such as Visual Basic Scripting Edition (VBScript) and JScript and object models such as Dynamic HTML require a more flexible interface.

IDispatchEx was developed to provide all the services of IDispatch as well as some extensions that are appropriate for more dynamic late-bound languages such as scripting languages. The additional features of IDispatchEx beyond those provided by IDispatch are:

  • Add new members to an object ("expando")—use GetDispID with the fdexNameEnsure flag.
  • Delete members of an object—use DeleteMemberByName or DeleteMemberByDispID.
  • Case-sensitive dispatch operations—use fdexNameCaseSensitive or fdexNameCaseInsensitive.
  • Search for member with implicit name—use fdexNameImplicit.
  • Enumerate DISPIDs of an object—use GetNextDispID.
  • Map from DISPID to element name—use GetMemberName.
  • Obtain properties of object members—use GetMemberProperties.
  • Method invocation with this pointer—use InvokeEx with DISPATCH_METHOD.
  • Allow browsers that support the concept of name spaces to obtain the name space parent of an object—use GetNameSpaceParent.

Objects that support IDispatchEx might also support IDispatch for backward compatibility. The dynamic nature of objects that support IDispatchEx has a few implications for the IDispatch interface of those objects. For example, IDispatch assumes that "The member and parameter DISPIDs must remain constant for the lifetime of the object. This allows a client to obtain DISPIDs once, and cache them for later use." Since IDispatchEx allows the addition and deletion of members, the set of valid DISPIDs does not remain constant. However, IDispatchEx requires that the mapping between DISPID and member name remain constant. This means that if a member is deleted:

  • The DISPID cannot be reused unless a member with the same name is created.
  • The DISPID must remain valid for GetNextDispID.
  • The DISPID must be accepted gracefully by any of the IDispatch/IDispatchEx methods—they must recognize the member as deleted and return an appropriate error code (usually DISP_E_MEMBERNOTFOUND or S_FALSE).


IDispatchEx Methods

DeleteMemberByDispID
DeleteMemberByName
GetDispID
GetMemberName
GetMemberProperties
GetNameSpaceParent
GetNextDispID
InvokeEx


IDispatchEx::DeleteMemberByDispID

HRESULT DeleteMemberByDispID(
    DISPID id
);

Deletes a member by DISPID.

  • Returns one of the following values:
    S_OK | Success.
    S_FALSE | Member exists but cannot be deleted.
id
Member identifier. Use GetDispID or GetNextDispID to obtain the dispatch identifier.

If the member is deleted, the DISPID needs to remain valid for GetNextDispID.

If a member with a given name is deleted and later a member with the same name is recreated, the DISPID should be the same. (Whether member names that differ only by case are the "same" is object-dependent.)

Example

BSTR bstrName;
DISPID dispid;
IDispatch *pdex;

// Assign to pdex and bstrName
pdex->GetDispID(bstrName, fdexNameCaseSensitive, &dispid);
pdex->DeleteMemberByDispID(dispid);


IDispatchEx::DeleteMemberByName

HRESULT DeleteMemberByName(
   BSTR bstrName,
   DWORD grfdex
);

Deletes a member by name.

  • Returns one of the following values:
    S_OK | Success.
    S_FALSE | Member exists but cannot be deleted.
bstrName
Name of member to be deleted.
grfdex
Determines if the member name is case sensitive. This can be one of the following values:
fdexNameCaseSensitive | Request that the name lookup be done in a case-sensitive manner. Can be ignored by object that does not support case-sensitive lookup.
fdexNameCaseInsensitive | Request that the name lookup be done in a case-insensitive manner. Can be ignored by object that does not support case-insensitive lookup.

If the member is deleted, the DISPID needs to remain valid for GetNextDispID.

If a member with a given name is deleted and later a member with the same name is recreated, the DISPID should be the same. (Whether members that differ only by case are the "same" is object-dependent.)

Example

BSTR bstrName;
IDispatch *pdex;

// Assign to pdex and bstrName
pdex->DeleteMemberByName(bstrName, fdexNameCaseSensitive);


IDispatchEx::GetDispID

 HRESULT GetDispID(
   BSTR bstrName,
   DWORD grfdex,
   DISPID *pid
);

Maps a single member name to its corresponding DISPID, which can then be used on subsequent calls to IDispatchEx::InvokeEx.

  • Returns one of the following values:
    S_OK | Success.
    E_OUTOFMEMORY | Out of Memory.
    DISP_E_UNKNOWNNAME | The name was not known.
bstrName
Passed in name to be mapped.
grfdex
Determines the options for obtaining the member identifier. This can be a combination of the following values:
fdexNameCaseSensitive | Request that the name lookup be done in a case-sensitive manner. May be ignored by object that does not support case-sensitive lookup.
fdexNameEnsure | Request that the member be created if it does not already exist. The new member should be created with the value VT_EMPTY.
fdexNameImplicit | Indicates that the caller is searching object(s) for a member of a particular name, when the base object is not explicitly specified.
fdexNameCaseInsensitive | Request that the name lookup be done in a case-insensitive manner. May be ignored by object that does not support case-insensitive lookup.
pid
Pointer to caller-allocated location to receive DISPID result. If an error occurs, pid contains DISPID_UNKNOWN.

GetDispID can be used instead of GetIDsOfNames to obtain the DISPID for a given member.

Because IDispatchEx allows the addition and deletion of members, the set of DISPIDs does not remain constant for the lifetime of an object.

The unused riid parameter in IDispatch::GetIDsOfNames has been removed.

Example

BSTR bstrName;
   DISPID dispid;
   IDispatch *pdex;

   // Assign to pdex and bstrName
   pdex->GetDispID(bstrName, fdexNameCaseSensitive, &dispid);
   // Use dispid


IDispatchEx::GetMemberName

HRESULT GetMemberName(
   DISPID id,
   BSTR *pbstrName
);

Retrieves the name of a member.

  • Returns one of the following values:
    S_OK | Success.
    DISP_E_MEMBERNOTFOUND | The requested member does not exist.
id
Identifies the member. Use GetDispID or GetNextDispID to obtain the dispatch identifier.
pbstrName
Address of a BSTR that receives the name of the member. The calling application is responsible for freeing this value.

Example

HRESULT hr;
   BSTR bstrName;
   DISPID dispid;
   IDispatch *pdex;

   // Assign to pdex
   hr = pdex->GetNextDispID(fdexEnumAll, DISPID_STARTENUM, &dispid);
   while (hr != S_FALSE)
   {
   pdex->GetMemberName(dispid, &bstrName);
   if (!wcscmp(bstrName, OLESTR("Bar")))
      return TRUE;
   SysFreeString(bstrName);
   hr = pdexObj->GetNextDispID(fdexEnumAll, dispid, &dispid);
}


IDispatchEx::GetMemberProperties

HRESULT GetMemberProperties(
   DISPID id,
   DWORD grfdexFetch,
   DWORD *pgrfdex
);

Retrieves a member's properties.

  • Returns one of the following values:
    S_OK | Success.
    DISP_E_MEMBERNOTFOUND | The requested member does not exist.
id
Identifies the member. Use GetDispID or GetNextDispID to obtain the dispatch identifier.
grfdexFetch
Determines which properties to retrieve. This can be a combination of the values listed under pgrfdex and/or a combination of the following values:
grfdexPropCanAll | Combines fdexPropCanGet, fdexPropCanPut, fdexPropCanPutRef, fdexPropCanCall, fdexPropCanConstruct and fdexPropCanSourceEvents.
grfdexPropCannotAll | Combines fdexPropCannotGet, fdexPropCannotPut, fdexPropCannotPutRef, fdexPropCannotCall, fdexPropCannotConstruct and fdexPropCannotSourceEvents.
grfdexPropExtraAll | Combines fdexPropNoSideEffects and fdexPropDynamicType.
grfdexPropAll | Combines grfdexPropCanAll, grfdexPropCannotAll and grfdexPropExtraAll.
pgrfdex
Address of a DWORD that receives the requested properties. This can be a combination of the following values:
fdexPropCanGet | The member can be obtained using DISPATCH_PROPERTYGET.
fdexPropCannotGet | The member cannot be obtained using DISPATCH_PROPERTYGET.
fdexPropCanPut | The member can be set using DISPATCH_PROPERTYPUT.
fdexPropCannotPut | The member cannot be set using DISPATCH_PROPERTYPUT.
fdexPropCanPutRef | The member can be set using DISPATCH_PROPERTYPUTREF.
fdexPropCannotPutRef | The member cannot be set using DISPATCH_PROPERTYPUTREF.
fdexPropNoSideEffects | The member does not have any side effects. For example, a debugger could safely get/set/call this member without changing the state of the script being debugged.
fdexPropDynamicType | The member is dynamic and can change during the lifetime of the object.
fdexPropCanCall | The member can be called as a method using DISPATCH_METHOD.
fdexPropCannotCall | The member cannot be called as a method using DISPATCH_METHOD.
fdexPropCanConstruct | The member can be called as a constructor using DISPATCH_CONSTRUCT.
fdexPropCannotConstruct | The member cannot be called as a constructor using DISPATCH_CONSTRUCT.
fdexPropCanSourceEvents | The member can fire events.
fdexPropCannotSourceEvents | The member cannot fire events.

Example

BSTR bstrName;
   DISPID dispid;
   IDispatch *pdex;
   DWORD dwProps;

   // Assign to pdex and bstrName
   pdex->GetDispID(bstrName, fdexNameCaseSensitive, &dispid);
   pdex->GetMemberProperties(dispid, grfdexPropAll, &dwProps);
   if (dwProps & fdexPropCanPut)
      // Assign to member


IDispatchEx::GetNameSpaceParent

HRESULT GetNameSpaceParent(
   IUnknown **ppunk
);

Retrieves the interface for the namespace parent of an object.

  • Returns S_OK if successful or an OLE-defined error code otherwise.
ppunk
Address of an IUnknown interface pointer that receives the namespace parent's interface.


IDispatchEx::GetNextDispID

HRESULT GetNextDispID(
   DWORD grfdex,
   DISPID id,
   DISPID *pid
);

Enumerates the members of the object.

  • Returns one of the following values:
    S_OK | Success.
    S_FALSE | Enumeration is done.
grfdex
Determines which set of item will be enumerated. This can be a combination of the following values:
fdexEnumDefault | Request that the object enumerate the default elements. The object is allowed to enumerate any set of elements.
fdexEnumAll | Request that the object enumerate all of the elements. The object is allowed to enumerate any set of elements.
id
Identifies the current member. GetNextDispID will retrieve the item in the enumeration after this one. Use GetDispID or a previous call to GetNextDispID to obtain this identifier. Use the DISPID_STARTENUM value to obtain the first identifier of the first item.
pid
Address of a DISPID variable that receives the identifier of the next item in the enumeration.

If a member is deleted by DeleteMemberByName or DeleteMemberByDispID, the DISPID needs to remain valid for GetNextDispID.

Example

HRESULT hr;
   BSTR bstrName;
   DISPID dispid;
   IDispatch *pdex;

   // Assign to pdex
   hr = pdex->GetNextDispID(fdexEnumAll, DISPID_STARTENUM, &dispid);
   while (hr != S_FALSE)
   {
      pdex->GetMemberName(dispid, &bstrName);
      if (!wcscmp(bstrName, OLESTR("Bar")))
      return TRUE;
      SysFreeString(bstrName);

      hr = pdexObj->GetNextDispID(fdexEnumAll, dispid, &dispid);
   }


IDispatchEx::InvokeEx

HRESULT InvokeEx(
   DISPID id,
   LCID lcid,
   WORD wFlags,
   DISPARAMS *pdp,
   VARIANT *pVarRes,
   EXCEPINFO *pei,
   IServiceProvider *pspCaller
);

Provides access to properties and methods exposed by an IDispatchEx object.

  • Returns one of the following values:
    S_OK | Success.
    DISP_E_BADPARAMCOUNT | The number of elements provided to DISPPARAMS is different from the number of arguments accepted by the method or property.
    DISP_E_BADVARTYPE | One of the arguments in rgvarg is not a valid variant type.
    DISP_E_EXCEPTION | The application needs to raise an exception. In this case, the structure passed in pei should be filled in.
    DISP_E_MEMBERNOTFOUND | The requested member does not exist, or the call to InvokeEx tried to set the value of a read-only property.
    DISP_E_NONAMEDARGS | This implementation of IDispatch does not support named arguments.
    DISP_E_OVERFLOW | One of the arguments in rgvarg could not be coerced to the specified type.
    DISP_E_PARAMNOTFOUND | One of the parameter DISPIDs does not correspond to a parameter on the method.
    DISP_E_TYPEMISMATCH | One or more of the arguments could not be coerced.
    DISP_E_UNKNOWNLCID | The member being invoked interprets string arguments according to the LCID, and the LCID is not recognized. If the LCID is not needed to interpret arguments, this error should not be returned.
    DISP_E_PARAMNOTOPTIONAL | A required parameter was omitted.
id
Identifies the member. Use GetDispID or GetNextDispID to obtain the dispatch identifier.
lcid
The locale context in which to interpret arguments. The lcid is passed to InvokeEx to allow the object to interpret its arguments specific to a locale.
wFlags
Flags describing the context of the InvokeEx call:
DISPATCH_METHOD | The member is invoked as a method. If a property has the same name, both this and the DISPATCH_PROPERTYGET flag may be set (defined by IDispatch).
DISPATCH_PROPERTYGET | The member is retrieved as a property or data member (defined by IDispatch).
DISPATCH_PROPERTYPUT | The member is changed as a property or data member (defined by IDispatch).
DISPATCH_PROPERTYPUTREF | The member is changed by a reference assignment, rather than a value assignment. This flag is valid only when the property accepts a reference to an object (defined by IDispatch).
DISPATCH_CONSTRUCT The member is being used as a constructor (this is a new value defined by IDispatchEx). The legal values for wFlags are:
DISPATCH_PROPERTYGET
DISPATCH_METHOD
DISPATCH_PROPERTYGET | DISPATCH_METHOD
DISPATCH_PROPERTYPUT
DISPATCH_PROPERTYPUTREF
DISPATCH_PROPERTYPUT | DISPATCH_PROPERTYPUTREF
DISPATCH_CONSTRUCT
pdp
Pointer to a structure containing an array of arguments, an array of argument DISPIDs for named arguments, and counts for the number of elements in the arrays. See the IDispatch documentation for a full description of the DISPPARAMS structure.
pVarRes
Pointer to the location where the result is to be stored, or Null if the caller expects no result. This argument is ignored if DISPATCH_PROPERTYPUT or DISPATCH_PROPERTYPUTREF is specified.
pei
Pointer to a structure that contains exception information. This structure should be filled in if DISP_E_EXCEPTION is returned. Can be Null. See the IDispatch documentation for a full description of the EXCEPINFO structure.
pspCaller
Pointer to a service provider object supplied by the caller which allows the object to obtain services from the caller. Can be Null.

IDispatchEx::InvokeEx provides all of the same features as IDispatch::Invoke and adds a few extensions:
DISPATCH_CONSTRUCT | Indicates that the item is being used as a constructor.
pspCaller | The pspCaller allows the object access to services provided by the caller. Specific services may be handled by the caller itself or delegated to callers further up the call chain. For example, if a script engine inside a browser makes an InvokeEx call to an external object, the object can follow the pspCaller chain to obtain services from the script engine or browser. (Note that the call chain is not the same as the creation chain – a.k.a. container chain or site chain – the creation chain may be available through some other mechanism such as IObjectWithSite.)
"this" pointer When DISPATCH_METHOD is set in wFlags, there may be a "named parameter" for the "this" value. The DISPID will be DISPID_THIS and it must be the first named parameter.

The unused riid parameter in IDispatch::Invoke has been removed.

The puArgArr parameter in IDispatch::Invoke has been removed.

See the IDispatch::Invoke documentation for the following examples:

  • "Calling a method with no arguments"
  • "Getting and setting properties"
  • "Passing parameters"
  • "Indexed Properties"
  • "Raising exceptions during Invoke"
  • "Returning errors"

Example

VARIANT var;
   BSTR bstrName;
   DISPID dispid;
   IDispatch *pdex;
   DISPPARAMS dispparamsNoArgs = {NULL, NULL, 0, 0};

   // Assign to pdex and bstrName
   pdex->GetDispID(bstrName, fdexNameCaseSensitive, &dispid);
   pdex->InvokeEx(dispid, LOCALE_USER_DEFAULT,
   DISPATCH_PROPERTYGET, &dispparamsNoArgs,
   &var, NULL, NULL);


IDispatch Methods

IDispatchEx is a subclass of IDispatch – therefore, any object that supports the IDispatchEx interface also has implementations of the IDispatch methods:

  • IDispatch::GetTypeInfoCount
  • IDispatch::GetTypeInfo
  • IDispatch::GetIDsOfNames
  • IDispatch::Invoke

These methods behave as defined by IDispatch except that they might provide additional support as defined by IDispatchEx. For example, IDispatchEx::Invoke should support the same values as IDispatchEX::InvokeEx (for example, DISPID_THIS, DISPATCH_CONSTRUCT). This allows an implementation of IDispatchEx::Invoke to simply delegate calls to IDispatchEx::InvokeEx. An object that supports both IDispatch and IDispatchEx might also choose to provide this additional support in IDispatch::Invoke.


Examples

This JScript code in the function test() does the following:

  • Create a new object by calling the "Object" constructor and assign a pointer to the new object to the variable "Obj".
  • Create a new element ("Elem") in the object and assign to this element a pointer to the function "foo".
  • Call this function. Since it is being called as a method, the "this" pointer refers to the object "Obj". The function adds a new element "Bar" to the object.
  <HTML>
   <BODY>
   <SCRIPT LANGUAGE="JScript">
   function foo()
   {
      // Create new element and assign the value 10
      this.Bar = 10;
   }

   function test()
   {
      // Construct new object
      Obj = new Object();

      // Create new element and assign function pointer
      Obj.Elem = foo;

      // Call Elem method ("this" == Obj)
      Obj.Elem();

      // Obj.Bar now exists
   }
   test();
   </SCRIPT>
   </BODY>
   </HTML>

A control placed on this same web page could obtain from the browser a dispatch pointer to the script engines. The control could then implement the function test():

  <HTML>
   <BODY>
   <SCRIPT LANGUAGE="JScript">
   function foo()
   {
      // Create new element and assign the value 10
      this.Bar = 10;
   }
   </SCRIPT>
   <OBJECT ID="test" <CLASSID="CLSID:9417DB5D-FA2A-11D0-8CB3-00C04FC2B085">>
   </OBJECT>
   </BODY>
   </HTML>

Code from the control "test" that does the same thing as the JScript function test(). Note that these dispatch calls are made into the 'running' JScript engine and change the state of the engine:

  • Obtain the dispatch pointer to the "foo" function using GetDispID().
  • Obtain the dispatch pointer to the "Object" function using GetDispID().
  • Construct an object by calling the "Object" function with InvokeEx() and obtain a dispatch pointer to the newly constructed object.
  • Create a new element "Elem" in the object using GetDispID() with the fdexNameEnsure flag.
  • Put the dispatch pointer to "foo" in the element using InvokeEx().
  • Call the dispatch pointer to "foo" as a method by calling InvokeEx() and passing in the dispatch pointer to the constructed object as the "this" pointer.
  • The "foo" method creates a new element "Bar" on the current "this" object.
  • Verify that the new element "Bar" was created in the constructed object by enumerating through the elements using GetNextDispID().
  BOOL test(IDispatchEx *pdexScript)
   {
      HRESULT hr;
      VARIANT var;
      BSTR bstrName;
      DISPID dispid, putid;
      IDispatchEx *pdexObj;
      IDispatch   *pdispObj, *pdispFoo;
      DISPPARAMS dispparams, dispparamsNoArgs = {NULL, NULL, 0, 0};

      // Get dispatch pointer for "foo"
      bstrName = SysAllocString(OLESTR("foo"));
      pdexScript->GetDispID(bstrName, 0, &dispid);
      SysFreeString(bstrName);
      pdexScript->InvokeEx(dispid, LOCALE_USER_DEFAULT,
         DISPATCH_PROPERTYGET, &dispparamsNoArgs,
         &var, NULL, NULL);
      pdispFoo = var.pdispVal;

      // Create object by calling "Object" constructor
      bstrName = SysAllocString(OLESTR("Object"));
      pdexScript->GetDispID(bstrName, 0, &dispid);
      SysFreeString(bstrName);
      pdexScript->InvokeEx(dispid, LOCALE_USER_DEFAULT,
         DISPATCH_CONSTRUCT, &dispparamsNoArgs,
         &var, NULL, NULL);
      pdispObj = var.pdispVal;
      pdispObj->QueryInterface(IID_IDispatchEx, (void **)&pdexObj);

      // Create new element in object
      bstrName = SysAllocString(OLESTR("Elem"));
      pdexObj->GetDispID(bstrName, fdexNameEnsure, &dispid);
      SysFreeString(bstrName);

      // Assign "foo" dispatch pointer to element
      putid = DISPID_PROPERTYPUT;
      var.vt = VT_DISPATCH;
      var.pdispVal = pdispFoo;
      dispparams.rgvarg = &var;
      dispparams.rgdispidNamedArgs = &putid;
      dispparams.cArgs = 1;
      dispparams.cNamedArgs = 1;
      pdexObj->InvokeEx(dispid, LOCALE_USER_DEFAULT,
         DISPATCH_PROPERTYPUTREF, &dispparams,
         NULL, NULL, NULL);

      // Invoke method with "this" pointer
      putid = DISPID_THIS;
      var.vt = VT_DISPATCH;
      var.pdispVal = pdispObj;
      dispparams.rgvarg = &var;
      dispparams.rgdispidNamedArgs = &putid;
      dispparams.cArgs = 1;
      dispparams.cNamedArgs = 1;
      pdexObj->InvokeEx(dispid, LOCALE_USER_DEFAULT,
         DISPATCH_METHOD, &dispparams,
      NULL, NULL, NULL);

      // Confirm that new element "Bar" is in object
      hr = pdexObj->GetNextDispID(fdexEnumAll, DISPID_STARTENUM, &dispid);
      while (hr != S_FALSE)
      {
         pdexObj->GetMemberName(dispid, &bstrName);
         if (!wcscmp(bstrName, OLESTR("Bar")))
            return TRUE;
         SysFreeString(bstrName);
         hr = pdexObj->GetNextDispID(fdexEnumAll, dispid, &dispid);
      }
      return FALSE;
   }


IProvideMultipleClassInfo

IProvideMulitpleClassInfo, an extension of the IProvideClassInfo2 interface, provides access to the type information of the default interfaces that describe an extended object.

A container typically implements an extender object to add properties, methods, and events to an existing object, the extendee. This interface can be implemented to return the type information of the extender (as index 0) and the type information of the extendee (as index 1). If this extendee is itself an extension, this interface must also return the type information of what that object extends (as index 2 in this example). This process continues for all extended objects until the base object (the innermost object being extended) is reached.

Only the default interfaces are part of the logical merging. Other interfaces are simply ignored; nothing can be inferred by their presence or absence.

This interface is typically implemented by an Active Scripting host and requested by an Active Scripting engine.

Methods


Methods

The IProvideMultipleClassInfo interface consists of two methods (listed in vtable order):

  • GetMultiTypeInfoCount
  • GetInfoOfIndex


Welcome to Scriptlet Technology

Microsoft® Scripting Components (scriptlets) provide you with an easy but powerful way to create controls from Web pages (.htm files) that use Microsoft Dynamic HTML. You can use scriptlets as ordinary controls in applications such as Microsoft Visual Basic, Microsoft Internet Explorer 4.0, and Microsoft Visual InterDev.

To | See
Understand scriptlets and how they work. | Scriptlet Overview
Create scriptlets and add them to your applications | Designing and Using Scriptlets
Find descriptions of the properties, methods, and events that you can use when using a scriptlet in your application. | Scriptlet Container Object Reference
Find descriptions of the properties, methods, and events that you can use when creating a scriptlet. | Scriptlet Model Extensions


Topic Not Found

No matching topic was found. Some possible reasons and remedies are as follows:

  • The file (.chm) that contains the destination topic is not in the expected location.

    Remedy: Rerun the MSDN Library Setup.

  • The file (.chm) that contains the current topic or destination topic is out-of-date and the link is no longer valid.

    Remedy: Install the latest version of the MSDN Library.

  • The MSDN Library CD is not in the CD-ROM drive.

    Remedy: Insert the MSDN Library CD into the CD-ROM drive.