Legal Information

ActiveX Designer Programmer's Reference

Information in this document is subject to change without notice. The names of companies, products, people, characters, and/or data mentioned herein are fictitious and are in no way intended to represent any real individual, company, product, or event, unless otherwise noted. Complying with all applicable copyright laws is the responsibility of the user. No part of this document may be reproduced or transmitted in any form or by any means, electronic or mechanical, for any purpose, without the express written permission of Microsoft Corporation. If, however, your only means of access is electronic, permission to print one copy is hereby granted.

Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject matter in this document. Except as expressly provided in any written license agreement from Microsoft, the furnishing of this document does not give you any license to these patents, trademarks, copyrights, or other intellectual property.

© 1997 Microsoft Corporation. All rights reserved.

Microsoft, MS-DOS, MS, Windows, Windows NT, Microsoft Press, Visual Basic, Visual C++, Win32, and ActiveX are either registered trademarks or trademarks of Microsoft Corporation in the United States and/or other countries.

Other product and company names mentioned herein may be the trademarks of their respective owners.


Introduction

This document contains information you'll need to design, implement, and distribute ActiveX™ designers. Microsoft® Visual Basic version 5.0 is the initial host for ActiveX designers.

ActiveX designers are ActiveX components that contain a dedicated design environment with which end users can customize applications. ActiveX designers are written in a high-level language that provides ActiveX and OLE support (such as Microsoft® Visual C++®).

Before developing an ActiveX designer, you should be proficient with an object-oriented language and experienced in creating OLE applications and ActiveX controls (formerly called OLE controls).

In addition to this document, you'll need to refer to the following sources, all of which can be found in the Microsoft Developer Network (MSDN):

  • Automation
  • COM
  • Inside OLE, Second Edition
  • Microsoft Visual C++ product documentation

Note

ActiveX is Microsoft's brand name for the technologies that enable interoperability using the Component Object Model (COM). OLE (Object Linking and Embedding) is a special-purpose subset of ActiveX. In this document, OLE refers specifically to cases where an object is linked or embedded. ActiveX encompasses the broader technology that includes OLE, the Internet, and multimedia.


Document Conventions

This document uses the following typographic conventions.

Convention

Description

bold

Words in bold with all lowercase letters indicate C++ keywords and data types. For example, if, double.

Bold

Words in bold with initial letter capitalized indicate names of interfaces, properties, and methods; registry keys and subkeys; and names of menus. For example, IactiveDesigner, QueryInterface, ToolBoxBitmap32, Insert menu.

BOLD

Words in bold with all capital letters indicate Windows API data types and structures. For example, FUNCDESC.

Italics

Words in italic letters indicate variables or placeholders for information you supply. For example, dwFlags.

UPPERCASE

Words in all capital letters indicate key names and key sequences, flags, and enumeration constants. For example, ESC, ENTER, E_NOTIMPL, HELP_WHATS_THIS_MODE_ON.

Mixed Case

Words in mixed case indicate directory and file names. For example, C:\Msdev, MyDesigner.dll.

Monospace

This font is used for code, variables, and error message text. For example,

MyString = "Hello, world!"


Structure of This Document

This document is organized as follows:

Chapter 1, " Overview of ActiveX Designers ," provides an overview of ActiveX designers and how they fit with related technologies.

Chapter 2, " Using ActiveX Designers with Visual Basic ," describes how ActiveX designers work in the Visual Basic host environment. Visual Basic 5.0 is the initial host for ActiveX designers.

Chapter 3, " Designing and Implementing an ActiveX Designer ," outlines what you must implement to create an ActiveX designer.

Chapter 4, " Supporting ActiveX Interfaces ," outlines the OLE and ActiveX interfaces required for ActiveX designers to support in-place activation, OLE objects, events, and properties.

Chapter 5, " Type Information and Extended Objects ," discusses static and dynamic type information and extended objects.

Chapter 6, " Services ," describes the service architecture and tells how to use services.

Chapter 7, " Retaining Persistent Data and Saving the Run-Time Object ," explains how to save the persistent data you'll need for the visual designer and the run-time object. It also covers the IActiveDesigner interface, which provides information about the run-time object.

Chapter 8, " Registering, Licensing, and Upgrading an ActiveX Designer ," covers issues related to licensing and creating multiple versions of ActiveX designers, and tells how to register these objects in the system-wide registry.

Chapter 9, " API Reference ," is a reference to the APIs that are specific to ActiveX designers.

Chapter 10, " Service Reference ," describes the services ActiveX designers can use.

Note

The interface syntax in this book follows the variable-naming convention known as Hungarian notation, invented by programmer Charles Simonyi. Variables are prefixed with lowercase letters indicating their data type. For example, lpszNewDocname signifies a long pointer to a zero-terminated string named NewDocname. For information on Hungarian notation, see Programming Windows by Charles Petzold.


Overview Of ActiveX Designers

A designer is a top-level software component that includes a dedicated design environment. Built into a running program, designers provide features that allow users to customize their applications. The Forms class provided with Microsoft® Visual Basic® is one type of designer. ActiveX™ designers are another.

This chapter provides information on the following topics:

  • ActiveX Designers and the Component Object Model
  • Components of an ActiveX Designer
  • Features of an ActiveX Designer
  • Where to Go for More Information

ActiveX designers are based on the Component Object Model (COM). An ActiveX designer is an ActiveX component (formerly called an OLE embedding server) that has a programmable interface and a dedicated visual designer. When loaded into a host's development environment, an ActiveX designer allows end users to customize objects for run-time use.

For example, the supplier of a large MIS application might develop an ActiveX designer that contains forms and controls unique to its databases. Visual Basic database programmers at customer sites can then use the ActiveX designer to design local applications that access the database and perform specific query and update tasks.

A multimedia design package might include an ActiveX designer that allows users to edit text, graphics, and sound, but builds only the resulting object into an application. Using this designer, a user could create a "Happy Birthday" object that contains only the text "Happy Birthday!", the sound clip of the birthday song, and the image of a cake, candles blazing.

An even simpler ActiveX designer might allow users to add their own properties to an ActiveX control.

Nearly all the technology needed for ActiveX designers already exists in COM, Automation (formerly called OLE Automation), and ActiveX controls (formerly called OLE Controls). The ActiveX Designer Programmer's Reference describes new features and discusses how to integrate the various technologies into an ActiveX designer.


ActiveX Designers and the Component Object Model

COM, on which ActiveX and OLE technologies are based, is a standard that defines how applications expose objects. Using this standard, you can create classes that comply with the model. ActiveX compound documents (formerly OLE compound documents) and ActiveX controls are two such classes; ActiveX designers represent a third.

Because ActiveX designers are based on COM, they provide several significant benefits:

  • They fit neatly into the existing architecture.
  • They make it easy for programmers to create classes that comply with the COM standard.
  • They simplify and streamline the development process by hiding complexity from the programmer.

The following figureshows how ActiveX designers fit into COM:


Components of an ActiveX Designer

At a minimum, every ActiveX designer must support the following:

  • A visual designer to handle the user interface at design time.
  • ActiveX and COM interfaces for support for type information, programmability, and a class factory.
  • Registration in the system-wide registry, so that applications and development tools can find the ActiveX designer.

The following figure summarizes these minimum requirements.

In addition, an ActiveX designer may create a separate object that is used at run time.

ActiveX designers are usually distributed in dynamic-link libraries (.dll). However, if the ActiveX designer is also an ActiveX control, it can be distributed in an .ocx file instead.


The Visual Designer

The visual designer, or design-time object, manages the design-time aspects of the ActiveX designer. The visual designer creates and destroys windows, handles the user interaction, and controls the look and feel of the ActiveX designer within the host environment. It also provides information about the objects in the ActiveX designer and allows browsers, wizards, and other tools to manipulate them. All ActiveX designers must provide a visual designer.

Using the visual designer, end users can not only create objects and use their properties, methods, and events, but also can write application-specific code to manipulate the objects they create. The end user's code and objects become part of the executable application, or run-time object. The run-time object may be the same as the visual designer, or it may be different.

ActiveX designers may have different design-time and run-time objects for the following reasons:

  • The design-time object and the run-time object will differ in implementation. For example, the design-time object must be visible, but the run-time object may be invisible.
  • The application requires a different visual interface at run time because of size or ease of implementation.
  • Licensing requirements are different at design time and at run time.

If the run-time object can be different from the design-time object, the ActiveX designer must provide information describing the types and visual representation for the run-time object. Languages such as Visual Basic use this information when creating an executable file. It must also implement a class factory that can create instances of the run-time object.

If the run-time object and the design-time object are the same, the ActiveX designer uses the same type information and class factory at both design time and run time.

For example, ActiveX designers that define forms or forms packages often require the same type information at design time as at run time. Their design-time and run-time objects are usually the same.

Other kinds of ActiveX designers may have different requirements for type information or user interfaces, and therefore implement separate design-time and run-time objects. An ActiveX designer that allows its users to devise queries to remote databases, for example, may not need a user interface at run time. And an ActiveX designer used for creating multimedia objects is likely to need different type information at run time.


ActiveX and OLE Support

ActiveX technologies provide the following features required by ActiveX designers:

  • Embedding.
  • In-place activation.
  • The ability to work as an in-process server.
  • Programmability using Automation.

In addition, ActiveX designers must implement a class factory to create instances of the design-time and run-time objects.

See Chapter 4, "Supporting ActiveX Interfaces," for additional information on ActiveX and COM requirements.


Registration

ActiveX designers must register as Component Categories in the system registry. Registration enables the objects to be found by applications that use them. ActiveX designers use the DLLRegisterServer and DLLUnregisterServer functions to register. See Chapter 8, "Registering, Licensing, and Upgrading an ActiveX Designer," for details.


Features of an ActiveX Designer

An ActiveX designer can be as simple as an object that makes itself visible at design time, or as complex as a form that hosts controls.

ActiveX designers and ActiveX controls share the following characteristics:

  • They can have different behavior at design-time and run-time. An ActiveX designer need not have a visible user interface at run time.
  • They can be attached to code that is written by an end user.
  • They can be compiled into an executable application.

By adding code to register itself appropriately, an ActiveX control can become an ActiveX designer. This is possible because ActiveX designers rely on previously defined technology wherever possible.


Existing Technologies

The following table lists the existing technologies used in ActiveX designers.

Feature | Technology
Properties | Automation, property notifications, property pages, connectable objects
Events | Events, Automation, connectable objects
Visual representation | Compound documents
Persistence | Structured storage (plus extensions)
Design-time layout | Compound documents, drag and drop
Container event handlers | Events, Automation
Container extended objects | ActiveX controls, property pages
Container keyboard | ActiveX controls


New Technologies

  • New technologies defined for ActiveX designers are listed in the following table.

Feature | Technology
Provide access to groups of features without delegating to many individual interfaces. | Services
Allow an object's type information to change, adding properties, methods, and events. | Dynamic type information
Add new interfaces for visual designer, code navigation, object selection. | Object extensions


Where to Go for More Information

This document covers features that are specific to ActiveX designers, including Application Programming Interfaces (APIs), services, and dynamic type information. For information on ActiveX and OLE features related to ActiveX designers, refer to the documents listed in the table that follows.

Component

Interface

Documented in:

Automation

ICreateTypeInfo

ICreateTypeInfo2

ICreateTypeLib2

IDispatch

ITypeInfo

ITypeLib

Automation Programmer's Reference

OLE, persistence, and compound documents

IClassFactory

IPersist

IPersistStorage

IPersistStream

IStorage

IStream

IUnknown

COM Programmer's Reference

ActiveX controls

IClassFactory2

IConnectionPoint

IConnectionPointContainer

IEnumConnectionPoints

IOleControl

IOleControlSite

IPerPropertyBrowsing

IPersistPropertyBag

IPersistStreamInit

IPropertyNotifySink

IPropertyPage2

IPropertyPageSite

IProvideClassInfo

ISpecifyPropertyPages

COM Programmer's Reference; see also Microsoft Visual C++ documentation


Using ActiveX Designers with Visual Basic

Microsoft Visual Basic version 5.0 supports multiple kinds of designers, including ActiveX designers. Previous versions of Visual Basic supported only a single kind of designer, the built-in Forms designer.

To write ActiveX designers that work properly in the Visual Basic development environment, you need to understand how Visual Basic and Visual Basic programmers work with ActiveX designers.

This chapter covers the following topics:

  • ActiveX designers and other Visual Basic objects.
  • Creating an application with an ActiveX designer.
  • Lifetime of visual designer objects.


ActiveX Designers and Other Visual Basic Objects

Visual Basic provides several ways for software developers to supply custom features:

  • Class modules.
  • Insertable ActiveX and OLE objects.
  • ActiveX controls.
  • ActiveX designers.

The following figure shows the interaction among these components.

Like Visual Basic forms (.frm) and class modules, ActiveX designers operate at project-level scope. ActiveX designers can host standard controls, ActiveX controls, and other objects, and can create instances of classes defined in class modules. In addition, the code behind an ActiveX designer can create instances of other forms or ActiveX designers. In the same way, the code behind a form can create an instance of another form or ActiveX designer.


Frames

In the Visual Basic environment, an ActiveX designer appears in a frame at design time, just as a form does. A frame is a container that looks like a window and defines window-like properties, methods, and events. Visual Basic creates and manages the frame, and the visual designer interacts with it through ActiveX interfaces. Within the frame, the ActiveX designer controls its own visual representation.

The following figureillustrates the frame for an ActiveX designer named CircleCoCtl1.

Visual Basic provides the frame. Within the frame's boundaries, CircleCoCtl1 controls the look and feel of the user interface, either handling events itself or passing them on to Visual Basic.


Extended Objects and Class Customization

Visual Basic 5.0 allows users to extend an object. Extending an object means changing the object's behavior by writing code to handle events and add properties, methods, and events. Using this capability, end users can create customized classes by combining an ActiveX designer, a frame supplied by Visual Basic, and a code module. In a similar manner, end users can attach code to documents, creating customized documents.

Because the ActiveX designers you distribute can be customized by end users, you need to understand the object extensions model and be aware of the types of behavior changes that are possible. For more information, see Chapter 5, "Type Information and Extended Objects," and the Automation Programmer's Reference.


Creating an Application with an ActiveX Designer

In Visual Basic 5.0, the Add ActiveX Designer command on the Project menu adds a previously defined ActiveX designer to the development environment. After adding an ActiveX designer, an end user can incorporate it into an executable Visual Basic program.

The table below lists the steps involved in creating a simple application that uses an ActiveX designer. For each user step, Visual Basic takes one or more actions. The sections that follow describe these actions in detail.

User action | Visual Basic action
Installs an ActiveX designer. | None.
Adds an ActiveX designer to the environment. | Obtains the list of ActiveX designers.
Adds an instance of an ActiveX designer to the project. | Creates and initializes a visual designer. Displays the designer's user interface in a frame.
Browses and edits properties. | Uses property browser. Manages the user's selections. Tracks changes to type information.
Writes code attached to an ActiveX designer. | Displays the code window.
Builds or runs the project. | Creates a run-time object.
Clicks End to stop execution. | Ceases execution.


Adding an ActiveX Designer to the Development Environment

To add an ActiveX designer to the development environment, an end user takes these steps:

  1. Clicks Components on the Project menu to open the Components dialog box.

  2. Clicks the Designers tab in the Components dialog box.

  3. Selects one or more ActiveX designers, as shown in the following figure, and clicks OK.

To find out which ActiveX designers are available, Visual Basic searches the system registry for entries that contain the ActiveX designer component category. Using the class identifiers (CLSIDs) and strings of these entries, it creates the list of ActiveX designers in the Components dialog box. The end user selects ActiveX designers from the box; the selected designers subsequently appear on the submenu displayed when the user clicks the Add ActiveX Designer command on the Project menu.


Adding an Instance of an ActiveX Designer

To add an instance of an ActiveX designer to a project, the end user clicks the Add ActiveX Designer command on the Project menu and selects a designer from those listed on the submenu. The list contains the names of all the ActiveX designers that have been added to the development environment. In response, Visual Basic takes the following steps:

  1. Creates a visual designer object. Visual Basic calls CoCreateInstance, passing the CLSID of the visual designer, to create a visual designer to manage the user interface. The designer's class factory ensures that any required licenses are present. If so, it creates the object.

  2. Initializes the visual designer. Visual Basic calls IOleObject::SetClientSite to associate the designer with a site. Then it calls the InitNew or Init method of the designer's persistence interface to load the designer's persistent data.

  3. Displays the visual designer. Visual Basic provides a frame and uses ActiveX and COM interfaces to display the user interface.

Visual Basic creates a default instance of the visual designer in much the same way that it creates a default instance of Form1.


Browsing and Editing Properties

ActiveX designers are not required to support per-property browsing, though it is strongly recommended that they do so. Designers that implement this feature use the ActiveX property page interfaces to create and manage property sheets. For more information, see "Managing Properties" in Chapter 4, "Supporting ActiveX Interfaces."

Visual Basic provides the STrackSelection service to track the user's selections and pass them appropriately to the ActiveX designer. By using the service, you can ensure that the designer's user interface works much like selecting controls on a form. See Chapter 6, "Services," for details on the service architecture.


Tracking Changes to Design-Time Type Information

When the end user inserts an object into an ActiveX designer, the type information for the visual designer has to change dynamically to include information about the new instance. The ActiveX designer uses Automation's ICreateTypeInfo and ICreateTypeInfo2 interfaces to change the type information. In turn, Automation triggers an ITypeChangeEvents notification to Visual Basic, which calls the designer's IProvideDynamicClassInfo interface to get the new type information.

For details on dynamic type information, refer to Chapter 5, "Type Information and Extended Objects."


Displaying a Code Window

When an end user selects an object and clicks View Code, Visual Basic displays the default event procedure for the object. The end user can then write code in the procedure to attach to the object.

The SCodeNavigate service performs a similar operation. When the end user clicks on an object supplied by the visual designer, the designer can call this service to display the appropriate code window.


Creating the Run-Time Object

When the end user wants to compile or run the application, the ActiveX designer creates a run-time object. First, Visual Basic calls IProvideClassInfo or IProvideDynamicClassInfo to get the type information it needs to create the object.

Before compiling the application or shifting into run mode, Visual Basic asks the ActiveX designer to save the prototypical instance state for the run-time object. If the visual designer implements the IActiveDesigner interface, Visual Basic calls its SaveRuntimeState method to save the data. If not, Visual Basic uses the Save method of the visual designer's persistence interface, in the following order of preference:

  • IPersistStreamInit

  • IPersistStream

  • IPersistStorage

Persistence and the IActiveDesigner interface are described in greater detail in Chapter 7, "Retaining Persistent Data and Saving the Run-Time Object."

Finally, at execution time, Visual Basic takes the following steps, shown in the figure, to create the ActiveX designer's run-time object. It calls:

  1. CoCreateInstance, passing the CLSID saved with the persistent data. The class factory for the run-time object ensures that it is properly licensed.

  2. Load method of the persistence interface to load persistent data for the new object.

  3. IConnectionPointContainer or IConnectionPoint to connect run-time events with the event handlers defined in the ActiveX designer. Run-time objects must support events.

An end user can create additional instances of a class defined by an ActiveX designer by using the New operator in Visual Basic code. Like form classes, but unlike ActiveX controls and public class modules, the classes produced by ActiveX designers are private classes. Accoringly, users of the Professional or Enterprise editions of Visual Basic who create ActiveX components cannot declare public methods that use these classes as argument types or return types.


Lifetime of Visual Designer Objects

Instances of the visual designer may be created and destroyed many times during a Visual Basic development session.

Every time the visual designer's window is closed, Visual Basic destroys the current object. And whenever the end user opens the ActiveX designer item in the Project window, Visual Basic creates a new visual designer object. The visual designer must be able to write and load its persistent data so that it can replicate its exact state each time the host creates it.


Designing and Implementing an ActiveX Designer

The ActiveX designer model allows for flexibility in implementation. While all ActiveX designers must be embedded, in-process ActiveX components, register as designers, and provide a visual design-time interface, you still must decide what features to provide in the user interface and how to structure the run-time objects that users create.

In general, an ActiveX designer has two distinct parts: a visual designer, also called a design-time object, and a run-time object. The visual designer is an embedded, in-place object that is invoked in the host's environment. Through its visual user interface, end users can write code and change properties, methods, and events to create an executable application.

The run-time object is the customized object that the visual designer creates. When the end user builds the application, the ActiveX designer saves the information necessary to run the application. At run time, the system uses the saved information to load instances of the run-time object.

This chapter describes the following:

  • Interfaces Used with ActiveX Designer Components
  • Creating the Visual Designer
  • Designing the Run-Time Object
  • Interaction Between ActiveX Designer and Host


Interfaces Used with ActiveX Designer Components

The following table lists the interfaces used with ActiveX designers and indicates which objects must implement them.

Interface | Visual Designer | Visual Run-Time Object | Nonvisual Run-Time Object
IOleObject | Required. | Required. | Optional.
IOleInPlaceObject | Required. | Required. | Does not apply.
IOleInPlaceActiveObject | Required. | Required. | Does not apply.
IProvideClassInfo or IProvideDynamicClassInfo | Required. | Required. | Required.
IActiveDesigner | Optional. | Does not apply. | Does not apply.
IPersist, IPersistStorage, IPersistStream, or IPersistStreamInit | Required. | Required. | Required.
IConnectionPoint, IConnectionPointContainer | Required. | Required. | Required.
IPerPropertyBrowsing, IPersistPropertyBag, IPropertyNotifySink, IPropertyPage, IPropertyPage2, ISpecifyPropertyPages | Optional. | Optional. | Does not apply.
IDispatch | Required. | Required. | Required.
IQuickActivate | Optional. | Optional. | Does not apply.
IViewObject, IViewObject2, IViewObjectEx | Optional. | Optional. | Does not apply.
IOleControl | Optional. | Optional. | Optional.


Creating the Visual Designer

The visual designer negotiates the interaction between the ActiveX designer, the host, and the end user. It includes a visual user interface, which collects and saves information from the end user to create a run-time object.

The following table summarizes the functions of the visual designer. Not all of these capabilities must be provided by every designer.

Function | What you must do
Be an embedded object (required). | Implement IOleObject.
Provide a visual designer (required). | Implement IOleInPlaceObject and IOleInPlaceActiveObject.
Save and load persistent data (required). | Implement IPersist, and IPersistStream, IPersistStreamInit, or IPersistStorage.
Use services (optional). | Use IServiceProvider.
Handle events (required). | Implement IConnectionPointContainer and IConnectionPoint.
Support a property browsing mechanism (optional). | Implement IDispatch, IConnectionPointContainer, IConnectionPoint, IPropertyNotifySink, IPerPropertyBrowsing, IPropertyPage, IPropertyPage2, ISpecifyPropertyPages. Use IPropertyPageSite.
Provide convenient navigation to and from code (optional). | Use SCodeNavigate.
Provide type information to the host (required). | Implement IProvideClassInfo or IProvideDynamicClassInfo.
Create a different run-time object (optional). | Implement IActiveDesigner.
Support programmability by add-in objects (optional). | Implement IActiveDesigner.
Support "Save As Text" (optional). | Implement IPersistPropertyBag.
Use ambient properties (optional). | Implement IOleControl. Use IOleControlSite.


Required Interfaces for the Visual Designer

Each visual designer is required to implement the following interfaces to fulfill the OLE requirements: IOleObject, IOleInPlaceActiveObject, and IOleInPlaceObject.

It must also implement IDispatch to invoke properties and methods, and IConnectionPoint and IConnectionPointContainer to handle events. It needs either IProvideClassInfo or IProvideDynamicClassInfo to supply type information.

In addition, it must implement at least one of the persistence interfaces (IPersistStorage, IPersistStream, IPersistStreamInit) to save persistent data from one session to the next. If the visual designer doesn't implement the optional IActiveDesigner interface, the host uses one of the persistence interfaces to save data for the run-time object.


Optional Interfaces for the Visual Designer

If the visual designer creates a different run-time object, you should implement IActiveDesigner, which provides information about the run-time object. IActiveDesigner also includes a method that provides access to the designer's programmability object (an IDispatch interface) for add-ins.

To allow end users to view and change properties, the designer may supply IPerPropertyBrowsing and ISpecifyPropertyPages. Supporting a property browser is strongly recommended, though it is not required.

The designer may also choose to implement IOleControl; IViewObject, IViewObject2 and IViewObjectEx; IQuickActivate; and any other interfaces.


Designing the Run-Time Object

Next to designing the visual user interface, deciding what form the run-time object should take is perhaps the most important issue in building an ActiveX designer. You have three choices:

  • Use the same object at both design time and run time.
  • Create a run-time object that presents a visual interface but has different type information from the visual designer.
  • Create a run-time object that does not present a visual interface.

The simplest approach is to replicate the design-time object as the run-time object. The advantage is that you have to write less code. You don't need to implement IActiveDesigner and you can use the same implementations of IOleObject, IOleInPlaceObject, and IOleInPlaceActiveObject as in the visual designer.

The disadvantage, however, is that the run-time object may contain type information and code used only at design time, and not needed at run time. The increased size can result in poor performance. In addition, you may need to license the run-time object separately from the visual designer. If so, you have to create a separate object.

Whether the visual designer and run-time objects are the same or different, they can be in the same file (.dll or .ocx) or in different files.


Required Interfaces for the Run-Time Object

The interfaces required for a run-time object depend on whether it presents a visual user interface. Like the visual designer, a visual run-time object must provide in-place activation capabilities. A nonvisual object, on the other hand, doesn't need these features.

The following table summarizes the functions of the run-time object.

Function | What you must do
Be an embedded object (required). | Implement IOleObject.
Provide a visual interface (optional). | Implement IOleInPlaceObject and IOleInPlaceActiveObject.
Supply type information (required). | Implement IProvideClassInfo or IProvideDynamicClassInfo.
Load persistent data (required). | Implement IPersistStorage, IPersistStream, or IpersistStreamInit.
Handle events (required). | Implement IconnectionPointContainer and IconnectionPoint.


Interfaces for a Visual Run-Time Object

If the run-time object is visible, it must implement most of the same interfaces as the design-time object. The IOleObject, IOleInPlaceObject, and IOleInPlaceActiveObject interfaces provide the basic features for embedded, in-place active objects.

IConnectionPoint and IConnectionPointContainer are required to support events.

IDispatch is necessary for invoking the object, its properties and methods.

IProvideClassInfo or IProvideDynamicClassInfo (if the visual designer implements it) is required. These interfaces return type information about the object.

In addition, the run-time object must support the same persistence interfaces that the design-time object supports. The persistence interfaces allow the object to be loaded with the appropriate type information and data.

The other interfaces are optional. The run-time object may need to support IQuickActivate; IViewObject, IViewObject2, or IViewObjectEx; or IOleControl.

The property browsing interfaces IPerPropertyBrowsing and ISpecifyPropertyPages aren't needed at run time.


Interfaces for a Nonvisual Run-Time Object

A run-time object that does not have a visible interface must set the OLEMISC_INVISIBLEATRUNTIME bit in the OLE miscellaneous status flags. This bit is set through the IOleObject interface and appears in the registry entry for the run-time object.

If the run-time object does not have a visual interface, you can eliminate the interfaces required for OLE embedding and in-place activation.

Nonvisual run-time objects need IDispatch to invoke properties and methods; IConnectionPoint and IConnectionPointContainer to handle events; IProvideClassInfo or IProvideDynamicClassInfo for type information; and the same persistence interfaces the design-time object supports.

Optionally, a nonvisual object may support IOleObject for siting.


Interaction Between ActiveX Designer and Host

The ActiveX designer relies on its host to implement the other interfaces that are required for OLE embedding. The list includes IAdviseSink, IOleClientSite, IOleInPlaceFrame, IOleInPlaceSite, IOleInPlaceUIWindow, and IOleWindow.


Supporting ActiveX Interfaces

Every ActiveX Designer must implement the following ActiveX and OLE interfaces: IOleObject, IOleInPlaceObject, IOleInPlaceActiveObject, and IClassFactory or IClassFactory2

These interfaces provide a way for the ActiveX Designer to communicate with its container, support in-place activation, and create instances of itself. In addition, the designer may handle events and manage properties. This chapter outlines what you have to implement to support these features:

  • Implementing IOleObject
  • Implementing IOleInPlaceObject
  • Implementing IOleInPlaceActiveObject
  • Implementing a Class Factory
  • Handling Events
  • Managing Properties


Implementing IOleObject

The IOleObject interface is the principal means by which the ActiveX Designer communicates with its container. In most situations, an ActiveX Designer's container is the same as its host, for example, Visual Basic. However, an ActiveX Designer may also be nested within another container, such as another ActiveX Designer.

Although the IOleObject interface contains 21 methods, ActiveX Designers need not implement all of them; only those listed in the following table are required. The designer should return E_NOTIMPL for the methods it does not implement.

Method | Description
Close | Moves the ActiveX Designer from the running state to the loaded state.
DoVerb | Invokes the ActiveX Designer to perform one of its enumerated actions.
GetClientSite | Retrieves the ActiveX Designer's client site.
GetMiscStatus | Returns status information about the ActiveX Designer.
SetClientSite | Informs the ActiveX Designer of its client site in the container.
SetHostNames | Communicates names of container application and container document.

You may, however, implement any of the other methods your ActiveX Designer requires for various other tasks. See the COM Programmer's Reference in the Platform Software Development Kit (SDK) of the Microsoft Developer's Network (MSDN) for more information on IOleObject.


Siting the Visual Designer

ActiveX Designers receive services through their site, which is established with the SetClientSite method. The site functions as the designer's display location within its container on the screen.

The SetClientSite method takes one input parameter, a pointer to the site. The host calls this method after fully loading an instance of the object.


Returning Object Status

The GetMiscStatus method returns status information about the ActiveX Designer's aspect; that is, how it should be displayed and what types of behavior it supports. This method is not required by OLE or ActiveX, but ActiveX Designers must implement it.

For each aspect your ActiveX Designer supports, this method returns the value of the OLE miscellaneous status flags. These flags are defined in the OLEMISC enumeration. For ActiveX Designers, the OLEMISC_INVISIBLEATRUNTIME flag must be true if the run-time object does not have a user interface.

At a minimum, ActiveX Designers must handle the DVASPECT_CONTENT case, which indicates how the designer should be displayed on screen or in print, as an embedded object inside a container. This aspect defines the display of the ActiveX Designer within the host.

Some ActiveX Designers may also need to handle DVASPECT_DOCPRINT. This constant specifies how to display a preview of printed output. This aspect is useful for showing how a visual design-time object might be rendered.

You can ignore the DVASPECT_THUMBNAIL and DVASPECT_ICON values, which denote miniature and iconic representations. Currently, hosts do not use these aspects for ActiveX Designers.


Setting the Host Name

The host calls SetHostNames to inform the ActiveX Designer of its name and the name of the container document. Although OLE requires that all objects implement this method, most ActiveX Designers have no need for the information and can simply return E_NOTIMPL.


Closing the ActiveX Designer

The Close method changes the ActiveX Designer from the running state to the loaded state. The host calls Close when the user closes the frame or application. If the object is active, the Close method should deactivate it. Close accepts one input parameter, a flag indicating whether to save the object. The visual designer is always saved; therefore Close can safely ignore this parameter.


Responding to End-User Actions

The host calls the DoVerb method of IOleObject in response to end-user actions such as mouse clicks. In return, the ActiveX Designer performs some action, either an action predefined by OLE or a custom action defined and implemented by the designer.

Predefined verbs are listed in the table that follows. Except for OLEIVERB_PRIMARY, all the predefined verbs have negative values. ActiveX Designers need not support all the predefined verbs. Whenever DoVerb is called with a negative value that the designer does not recognize, the designer should ignore the call and return E_NOTIMPL.

Positive values are reserved for custom verbs specific to the application. For example, an ActiveX Designer might define a custom verb that displays a code window, to be applied when the user selects an object that can have code behind it. When DoVerb is called with a positive value the designer doesn't recognize, it should treat the unknown verb as if it were the primary verb (OLEIVERB_PRIMARY) and return OLE_S_INVALIDVERB.

Verb | Value | Description
OLEIVERB_PRIMARY | 0L | Action that occurs when a user double-clicks the object. ActiveX Designers should activate the object in place.
OLEIVERB_SHOW | – 1 | Instructs the object to show itself for editing or viewing.
OLEIVERB_OPEN | – 2 | Instructs the object to open itself in a new window. ActiveX Designers may choose to ignore this verb.
OLEIVERB_HIDE | – 3 | Removes the object's user interface from view.
OLEIVERB_UIACTIVATE | – 4 | Activates the object in place, along with its complete set of user interface tools.
OLEIVERB_INPLACEACTIVATE | – 5 | Activates the object in place without its user interface tools.
OLEIVERB_DISCARDUNDOSTATE | – 6 | Instructs the object to discard any "Undo" information.


Implementing IOleInPlaceObject

The IOleInPlaceObject interface manages the activation and deactivation of in-place objects and determines how much of the in-place object should be visible. ActiveX Designer objects (at both design time and run time) that present a user interface must implement this interface to support in-place activation. Hosts call its methods to activate and deactivate the visual interface.

The interface includes the four methods listed in the following table. The InPlaceDeactivate, UIDeactivate, and SetObjectRect methods are required. ReactivateAndUndo is optional for ActiveX Designers. If you choose not to implement it, return E_NOTIMPL.

Method | Description
InPlaceDeactivate | Deactivates an active in-place object (required).
ReactivateAndUndo | Reactivates a previously deactivated object (optional).
SetObjectRects | Defines the portion of the in-place object that should be visible (required).
UIDeactivate | Deactivates and removes the user interface of the active object (required).

See the COM Programmer's Reference in the Platform Software Development Kit (SDK) of MSDN for more information on IOleInPlaceObject.


Implementing IOleInPlaceActiveObject

The IOleInPlaceActiveObject interface allows the host to manipulate the ActiveX Designer while it is active. The interface provides a direct channel of communication between the active visual designer and its frame. The interface includes the five methods listed in the table.

Method | Description
EnableModeless | Enables or disables modeless dialog boxes.
OnDocWindowActivate | State of container document window.
OnFrameWindowActivate | State of container's top-level frame.
ResizeBorder | Alerts object of need to resize border space.
TranslateAccelerator | Translates messages.

Of these five methods, only the TranslateAccelerator method is relevant to ActiveX Designers. However, you must implement the other methods and they must return S_OK rather than E_NOTIMPL. See the COM Programmer's Reference in the Platform Software Development Kit (SDK) of MSDN for details on IOleInPlaceActiveObject.


Implementing a Class Factory

ActiveX Designers must have class factories to create instances of their visual designer and run-time object. You can support either the IClassFactory or IClassFactory2 interface for each object. See the COM Programmer's Reference in the Platform SDK of MSDN for more information on these two interfaces.

All licensing of ActiveX Designers is handled through the IClassFactory2 interface. If either your visual designer or your run-time object must be licensed, use IClassFactory2 to implement the class factory for that component. If no license is required for the component, use IClassFactory.


Handling Events

ActiveX Designers handle events of several types. The most common involves a user clicking the mouse on an object presented at design time, typically to change its properties or attach code. At run time, the designer must also be prepared to handle events.

ActiveX Designers use the standard ActiveX event handling mechanism, supporting the IConnectionPoint and IConnectionPointContainer interfaces, along with interfaces for each type of event to be handled. The figure shows the basic approach to event handling.

IConnectionPoint allows the designer to expose an outgoing pointer to an object, typically an event sink. IConnectionPointContainer enumerates the connection points supported by an object, so that a caller can find the right connection point. When an event occurs, the ActiveX Designer uses a connection between the event source, which calls the interface that handles events of that type, and the event sink, which implements the interface. The sink implements the member functions for a set of events.

At design time, the host enumerates the set of events an object can trigger by inspecting the type information for the object's event set. To allow users of ActiveX Designers to write event handlers, the host can check the parameters of the events, and from them create a stub event handler for the user to fill in.

For property changes, ActiveX Designers implement a standard sink interface called IPropertyNotifySink.


Managing Properties

Most ActiveX Designers need to enable end users to view and change properties of objects. To provide this capability, the visual designer supports one or more of the following groups of interfaces:

  • IDispatch and either IProvideClassInfo or IProvideDynamicClassInfo.
  • IConnectionPointContainer, IConnectionPoint, and IPropertyNotifySink.
  • IPerPropertyBrowsing, IPropertyPage, IPropertyPage2, IPropertyPageSite, and ISpecifyPropertyPages.

This section describes the levels of property support that an ActiveX Designer may implement and shows how to implement each of these approaches. Refer to the OLE Programmer's Reference for details of the property management interfaces.


Levels of Property Support

ActiveX Designers may support property browsing in several ways:

  • By providing minimal support for browsing only. Users cannot change property values with the host's default mechanisms.
  • By allowing users to view and change properties through the host's Properties window and to support changes to properties from other sources.
  • By implementing their own property browsing mechanism that uses property pages.

For maximum flexibility, ActiveX Designers should support all three methods.

If the ActiveX Designer supports only property browsing through the host's Properties window, and does not allow users to change any properties, it must support IDispatch. The host gains access to the set of properties to browse through the designer's implementation of IProvideClassInfo or IProvideDynamicClassInfo.

If the properties can change in any way other than through the property browser, the ActiveX Designer must support the connection point interfaces and IPropertyNotifySink. These interfaces are used to inform the host's property browser of changes to properties.

ActiveX Designers can also support their own property browsing through the property page mechanism. This approach allows for the most general, host-independent code, and gives your ActiveX Designer the greatest flexibility. To do this, ActiveX Designers need to support IPerPropertyBrowsing, IPropertyPage, IPropertyPage2, IPropertyPageSite, and ISpecifyPropertyPages in addition to IDispatch and the connection point interfaces.


Using the Standard Browser with IDispatch

The IDispatch interface, defined as part of Automation, allows access to the properties and methods of an ActiveX object. For ActiveX Designers that do not support a property browsing mechanism, the host can change property values by calling the IDispatch methods of the objects.


Supporting Property Pages

ActiveX Designers can support property pages to allow users to change and view properties. Each property page is associated with an object and allows the end user to edit properties for the object. An object can have more than one property page; each page handles a group of related properties.

Users access the property pages through a property frame. The property frame displays the property pages that apply to an object (or a set of objects).

The following Application Programming Interfaces (APIs) and interfaces handle the interactions between objects, their properties, property pages, and property frames:

  • The ISpecifyPropertyPages interface defines the set of property pages that apply to an object.
  • The IPropertyPage interface creates and manipulates an individual property page.
  • The IPropertyPageSite interface associates a site with a property page.
  • The OleCreatePropertyFrame API creates a property frame.
  • The IPropertyPage2 interface navigates to a property on a property page.


Type Information and Extended Objects

Automation supports both dynamic and static type information and type libraries. Because the type information can change, end users can edit objects at design time, writing code or adding properties and events, to create new, unique run-time objects.

When you implement an ActiveX designer, you have to decide which kind of type information to support. It helps to understand how hosts aggregate the objects your designer and the user create.

This chapter covers the following topics:

  • Type information for ActiveX designers.
  • Supporting static type information.
  • Supporting dynamic type information.
  • Extended objects and aggregation.


Type Information for ActiveX Designers

ActiveX designers can support either static or dynamic type information. Static type information remains the same throughout the object's lifetime. Dynamic type information can change while the object is active. ActiveX designers that support dynamic type information change their type information objects and type libraries in response to end user edits. Adding an event, for example, can change the type information for the designer.

Dynamic type information and type libraries allow you to share and reuse code, reducing implementation time. And instead of having to create several type libraries, you create only one, preventing potential inconsistencies that could cause problems for end users.

The IProvideClassInfo interface provides static type information; the IProvideDynamicClassInfo interface provides dynamic type information.

When you create an ActiveX designer, you should consider at the outset which kind of type information to support. If the type information can never change, support static type information. For example, if your ActiveX designer can only be built into other objects and does not allow an end user to attach code that would alter the type information, you can implement IProvideClassInfo.

If the type information can change, you have to support dynamic type information. For example, if you expect end users to add event procedures or controls to your ActiveX designer, implement IProvideDynamicClassInfo.

The visual designer and the run-time object must implement the same type information interface.

Although Visual Basic 5.0 can use either IProvideClassInfo or IProvideDynamicClassInfo, older hosts may support only IProvideClassInfo. If you expect your ActiveX designer to be used with hosts other than Visual Basic 5.0, you should support both interfaces.

For details on the type information interfaces, see Chapter 9, "API Reference," and the Automation Programmer's Reference.


How the Host Obtains Type Information

When the end user inserts an ActiveX designer into the host environment, the host takes the following steps to create an instance of the visual designer and obtain type information:

  • Calls CoCreateInstance to create an instance of the visual designer.
  • Calls QueryInterface on the created instance to find its persistence interface.
  • Calls the InitNew or Load method of the persistence interface, as appropriate, to load the persistent state of the visual designer.
  • Calls the GetDynamicClassInfo method of the IProvideDynamicClassInfo interface to get the visual designer's type information. If this interface is not implemented, the host calls the GetClassInfo method of the IProvideClassInfo interface.
  • Calls InPlaceActivate to activate the created instance.

If the type information changes dynamically, the host is notified via ITypeChangeEvents, triggered by Automation. It can then call GetDynamicClassInfo to retrieve the new information for property browsers, event handlers, and other features that require up-to-date type information.


Creating Type Libraries

All the type information used with ActiveX designers must be in a type library. To create the type library, you use the ICreateTypeLib2 interface described in the Automation Programmer's Reference .

The ICreateTypeLib2 interface creates a type library in a new format, instead of the older format created by ICreateTypeLib. The new format allows you to add and delete type descriptions from the library. The older format allowed only additions. All ActiveX designers are required to use type libraries in the new format.


Supporting Static Type Information

To support static type information, your ActiveX designer has to implement the IProvideClassInfo interface. The interface has a single method, GetClassInfo. If the designer doesn't support dynamic type information, the host gets type information from GetClassInfo after creating and loading the object and before activating it.

The GetClassInfo method returns a pointer to a type information object that describes the incoming and outgoing interfaces to the ActiveX designer. In the following example, the method gets a pointer to the designer's type library, and through it, uses the ITypeLib interface to retrieve the type information.

<STYLE>STDMETHODIMP MyDesigner::GetClassInfo (LPTYPEINFO FAR* ppTI)
{
    *ppTI = NULL;                     // Default value if failure.
    ITypeInfo *pITypeInfo = NULL;
    ITypeLib  *pITypeLib  = NULL;

    // Get type library.
    hresult = LoadTypeLib (g_pTypeLibName, &pITypeLib);
    if (SUCCEEDED(hresult) != TRUE)
        { ASSERT(FALSE); return E_NOINTERFACE;}

    //    Note: If loaded it, will have a global reference to it.
    //    Free this later.

    // Get the type information of the coclass from the library.
    hresult = pITypeLib -> GetTypeInfoOfGuid
            (m_pDesigner->m_clsidPersist, &pITypeInfo);

    // Assign the typeInfo return value.
    // The caller should free this.
    *ppTI = pITypeInfo;

    // Release reference.
    pITypeLib->Release();
}

The example retains the globally unique identifier (GUID) of the designer's coclass in the global structure pointed to by m_pDesigner, so it uses the GetTypeInfoOfGuid method to obtain the type information. (Alternatively, if you have the index into the type information, you can use the GetTypeInfo method instead.) Then it simply returns the type information object to the caller.


Supporting Dynamic Type Information

To support dynamic type information, your ActiveX designer has to implement the IProvideDynamicClassInfo interface and handle design-time changes from the end user.


Implementing IProvideDynamicClassInfo

The IProvideDynamicClassInfo interface is called by the host when an ActiveX designer is loaded into the design-time environment and in response to ITypeChangeEvents notifications triggered by Automation.

The interface has two methods. GetDynamicClassInfo provides a pointer to the type information object. FreezeShape freezes the dynamically changing type information object.


Providing Type Information

The GetDynamicClassInfo method gives the host access to dynamic type information for the objects an ActiveX designer creates. You should implement this method if you expect any changes to the type information for the visual designer.

The method returns two parameters: ppTypeInfoOut, an indirect pointer to a type information object that describes a coclass; and pdwCookie, a handle that the host retains and saves in its own cache along with the type information.

The remainder of this section steps through tasks you may need to perform to create and return a dynamic type information object. Assume that the following declarations have been made:

ICreateTypeInfo *pctiDispinterface = NULL;
ICreateTypeInfo *pctiCoClass = NULL;
ICreateTypeLib2 *pCreateTypeLib = NULL;
ITypeInfo *ptiEvents = NULL, *ptiDispinterface;
HREFTYPE hreftype;
HRESULT  hr;

Error handling has been omitted for brevity throughout this section.

Creating the Type Library

The dynamic type information object must be in a new format type library.

To create a new type library, you use the ICreateTypeLib2 interface or the CreateTypeLib2 Application Programming Interface (API), as in the following example.

hr = CreateTypeLib2(SYS_WIN32, pwszFile, &pCreateTypeLib);

// Set the guid, helpstring DLL and name.
hr = pCreateTypeLib->SetGuid(m_state.guidTypeLib);
hr = pCreateTypeLib->SetHelpStringDll(L"");
hr = pCreateTypeLib->SetName(L"MyDesignerTypeLib");

In the example, pwszFile contains a temporary name for the type library and m_state is a global structure that holds useful information for the designer, including its GUID. You must set the name for the new type library.

Creating the Type Information Object

After creating the type library, you can create the type information object. The easiest way is to use an existing static type information object as a template. If the existing static object describes a virtual function table (VTBL) interface (or a dual interface), you can simply copy the object. Automation handles the necessary inheritance. You can then return an indirect pointer to your copy as the type information object (see "Returning Parameters," later in this section).

For a dispatch interface, however, the procedure is more complicated. You must copy the entire object and set up the inheritance yourself, in code. The following code fragment shows this procedure.

// Get the dispatch interface to use as a template.
hr = LoadRegTypeLib(*g_pLibid, (USHORT)VERSIONOFOBJECT(m_ObjectType),0,
            LANG_NEUTRAL, &pTypeLibStatic);

hr = pTypeLibStatic->GetTypeInfoOfGuid(DIID_DMyDesigner,
                                           &ptiDispinterface);
if (SUCCEEDED(hr)) {
    // Now get the type information of event interface.
    hr = pTypeLibStatic->GetTypeInfoOfGuid(
                        DIID_DMyDesignerEvents,ppTypeInfo);
    if (SUCCEEDED(hr)) {
        // And get the type information of control object.
        hr = pTypeLibStatic->GetTypeInfoOfGuid(CLSID_MyObject,                                     &m_state.pMyObjectTI);
    }
 }
pTypeLibStatic->Release();

The preceding code fragment gets an existing type information object that describes a dispatch interface. The next few lines create a new dispatch type information object and set its GUID. The local function _GetIDispatchTypeInfo returns the dispatch type information for the existing object. Calls to AddRefTypeInfo and AddImplTypeInfo set up the inheritance.

hr = pTypeLib->CreateTypeInfo(L"DMyDesignerObject", TKIND_DISPATCH,
                                  &pctiDispinterface);
hr = pctiDispinterface->SetGuid(m_state.guidTypeInfo);

hr = _GetIDispatchTypeInfo(&ptiDispatch);
hr = pctiDispinterface->AddRefTypeInfo(ptiDispatch, &hreftype);
hr = pctiDispinterface->AddImplType(0, hreftype);

Finally, the code copies the existing type information from *ptiDispInterface to the new object, *pctiDispInterface, and lays out the type information. In this example, CopyDispInterfaceTypeInfo is a local function that copies the information.

hr = CopyDispinterfaceTypeInfo(ptiDispinterface, pctiDispinterface);
pctiDispinterface->LayOut();
pctiDispinterface->AddRef();

At this point, pctiDispInterface points to a TKIND_DISPATCH type information object to be used as a template for the new dynamic type information object. The new object must contain type information for a coclass, so you have to create a blank object of TKIND_COCLASS:

hr = pCreateTypeLib->CreateTypeInfo(L"MyDesigner", TKIND_COCLASS,
                                    &pctiCoClass);

Next, you get the type information for the existing interfaces and events from the template in pctiDispinterface, add these to the new type information in ptiCoClass, and set the implementation type flags:

hr = pctiDispinterface->QueryInterface(IID_ITypeInfo,
                                           (void **)&ptiDispinterface);
// Information on the interface.
hr = pctiCoClass->AddRefTypeInfo(ptiDispinterface, &hreftype);
ptiDispinterface->Release();
hr = pctiCoClass->AddImplType(0, hreftype);

// Information on the events.
hr = pctiCoClass->AddRefTypeInfo(ptiEvents, &hreftype);
hr = pctiCoClass->AddImplType(1, hreftype);

// Now set up the flags for these two items.
hr = pctiCoClass->SetImplTypeFlags(0, IMPLTYPEFLAG_FDEFAULT);
hr = pctiCoClass->SetImplTypeFlags(1, IMPLTYPEFLAG_FDEFAULT
                                   | IMPLTYPEFLAG_FSOURCE);

When this is done, the type information object is set up. Only two tasks remain: setting up the output parameter and incrementing the cookie.

Returning Parameters

The ppTypeInfo output parameter of GetDynamicClassInfo is an indirect pointer to an ITypeInfo interface. Because pctiCoClass represents an ICreateTypeInfo interface, you have to query for the correct interface and cast the resulting pointer:

pctiCoClass->QueryInterface(IID_ITypeInfo, (void **)ppTypeInfo)

Finally, pass the current value of the cookie, which is stored in the global variable m_dwTICookie:

if (pdwCookie)
        *pdwCookie = m_dwTICookie;

The value of *pdwCookie is initially set in the InitNew or Load method of the designer's persistence interfaces, and is incremented if an unrecoverable error occurs when the persistent data are loaded.

When the host is reloaded, it checks the cookie to determine whether its cached version of the type information is up-to-date. If the cached version is accurate, the host can use it without having to reload the persistent state. If the cache is outdated, the host must reload the persistent state.


Freezing Type Information

When the user requests that the application be run or built, the host calls the FreezeShape method to freeze the dynamic type information. After FreezeShape has been called, no additional changes can take place.

The FreezeShape method is simple to implement with a global flag. The flag is set to True when FreezeShape is called, as in the following example:

STDMETHODIMP CMyDesigner::FreezeShape
(
    void
)
{
    m_fFreezeShape = TRUE;
    return S_OK;
}

The m_fFreezeShape flag is reset to False in the class factory for the visual designer.


Handling Design-Time Changes

At design time, an end user can add properties and methods to an ActiveX designer or remove them from the designer. When this occurs, your code has to change the existing type information and lay out the new type information.

The action you have to take depends on the kind of object you're working with. When you change the type information for a VTBL interface, you treat properties and methods as functions. Use the AddFuncDesc method of the ICreateTypeInfo interface to add a property or method, and DeleteFuncDesc (or DeleteFuncDescByMemId) in the ICreateTypeInfo2 interface to remove a property or method.

When you change the type information for a dispatch interface, you treat properties as variables and methods as functions. To add and remove properties, you use the AddVarDesc and DeleteVarDesc (or DeleteVarDescByMemId) methods. To add and remove methods you use the AddFuncDesc and DeleteFuncDesc (or DeleteFuncDescByMemId) methods.

Afterwards, you call ITypeInfo::LayOut, which assigns a dispatch identifier (DISPID) and a VTBL offset to the new subobject. A brief discussion of these methods follows; refer to the Automation Programmer's Reference for complete details.

Whenever your code adds or deletes a function or variable descriptor, Automation triggers an ITypeChangeEvents notification to the host. This notification makes the host aware that it should call the ActiveX designer's GetDynamicClassInfo method to get the new type information.


Adding Type Information

To add type information, you use the AddFuncDesc or AddVarDesc method of the ICreateTypeInfo interface.

Call AddFuncDesc to add a method to a dispatch interface or to add a property or method to a VTBL interface. Call AddVarDesc to add a property to a dispatch interface.

Each of these methods takes two parameters: an index for the new variable or function in the type description; and a pointer to a FUNCDESC or VARDESC structure that describes the new property or method.

The values of the fields in the function and variable descriptors depend on the type of interface you're working with and the property or method you're adding. For AddFuncDesc, build a function descriptor according to the values in the following table. See the Automation Programmer's Reference for additional details.

Field | Description | Suggested Value
memid | Function member identifier; same as the DISPID. | MEMBERID_NIL. The ID will be assigned later by the LayOut method.
LprgScode | Valid return values for the function. | User-defined.
LprgElemdescParam | Array of parameter types. | User-defined.
Funckind | Type of function. | FUNC_PUREVIRTUAL or FUNC_DISPATCH
invkind | Invocation kind. | INVOKE_PROPERTYGET or INVOKE_FUNC
callconv | Calling convention. | CC_STDCALL
cParams | Total number of parameters. | User-defined.
cParamsOpt | Number of optional parameters. | User-defined.
oVft | Offset into virtual function table. | 0. The VTBL address will be assigned later by the LayOut method.
cScodes | Number of return values in lprgScode. | User-defined.
elemdescFunc | ELEMDESC structure that gives the return type for the function. | User-defined.
wFuncFlags | FUNCFLAG enumeration that describes function. | Use FUNCFLAG_FSOURCE if the function is a source of events.

For AddVarDesc, build a variable descriptor according to the values in the following table. See the Automation Programmer's Reference for additional details.

Field | Description | Suggested Value
memid | Member identifier for the property; same as the DISPID. | MEMBERID_NIL. The ID will be assigned later by the LayOut method.
ElemdescVar | ELEMDESC structure that contains the type information for the property. | User-defined.
WVarFlags | VARFLAG enumeration that describes the property. | Use VARFLAG_FSOURCE if the property is a source of events.
Varkind | VARKIND enumeration that identifies the property. | VAR_DISPATCH

For both a function and a variable, specify an index of 0 to place the description at the front of the type information.

The following example builds a variable descriptor and adds a property to a dispatch interface. In the example, the property is one of a group of subobjects that the designer can create. The variable vd is a structure of type VARDESC and td is a structure of type TYPEDESC.

First, the example sets up some constants in the VARDESC structure. It specifies VAR_DISPATCH to indicate that it applies to a dispatch interface, and variable flags to show that it is a source of events and is read-only. This example also specifies its own DISPIDs, rather than allowing the Layout method to assign them.

vd.memid = index + DISPID_CUSTOMOFFSET;
vd.varkind = VAR_DISPATCH;
vd.wVarFlags = VARFLAG_FSOURCE | VARFLAG_FREADONLY;

Next, it adds a reference to the new property's type information:

hr = m_state.pRuntimeDispinterface->AddRefTypeInfo(
                                    m_state.pObjectTI, &href);

With the handle to the referenced type information in href, the rest of the variable and type descriptors can be filled in. The variable descriptor is then passed to AddVarDesc to add the new type information:

td.vt = VT_USERDEFINED;
td.hreftype = href;
vd.elemdescVar.tdesc.lptdesc = &td;
vd.elemdescVar.tdesc.vt = VT_PTR;
hr = m_state.pRuntimeDispinterface->AddVarDesc(0, &vd);

After adding the property, you should set its name. The code fragment below keeps the names of the subobjects in the global structure m_state, and retrieves it from there to pass to the SetVarName method:

bstrName = m_state.rgObjects[index].bstrName;
hr =  m_state.pRuntimeDispinterface->SetVarName(0, bstrName);

Finally, you must lay out the new type information. See "Laying Out the Type Information" later in this chapter.


Removing a Type Description

When the end user deletes a method or property, you use the DeleteFuncDesc (or DeleteFuncDescByMemId) or DeleteVarDesc (or DeleteVarDescByMemId) method in the ICreateTypeInfo2 interface to remove its type description These methods are described in the Automation Programmer's Reference.

If you know the index for the property or method in the type information, use DeleteFuncDesc or DeleteVarDesc. These methods take one parameter, index, which supplies the index into the type information.

If you don't know the index but know the name of the property or method, you can use DeleteFuncDescByMemId or DeleteVarDescByMemId instead. These two methods require the member identifier (MEMBERID) of the property or method and its INVKIND, which is either INVOKE_PROPERTYGET or INVOKE_FUNC.

The MEMBERID is available through the object's ITypeComp interface, as in the following example. (Error handling is omitted for brevity.)

hr = m_pRuntimeTypeInfo->QueryInterface(IID_ITypeInfo,
                                       (void **)&ptiTemp);
hr = ptiTemp->GetTypeComp(&pTypeComp);

After getting a pointer to the object's ITypeComp interface, you use the Bind method to map the subobject's name to its type information. In the following code fragment, the variable pwsz represents the name of the subobject.

hr = pTypeComp->Bind(pwsz, LHashValOfNameSys(SYS_WIN32,
                     g_lcidLocale, pwsz), INVOKE_PROPERTYGET,
                     &ptiTemp, &dk, &bp);

Next, get the MEMBERID from the function descriptor and release the reference to ITypeComp:

memid = bp.lpfuncdesc->memid;
pTypeComp->Release();

Finally, with the MEMBERID in memid, call DeleteFuncDescByMemId to delete the function.

hr = m_pRuntimeTypeInfo->DeleteFuncDescByMemId(memid,
                                               INVOKE_PROPERTYGET);


Laying Out the Type Information

Each time you add or remove an object from the type information, you have to call ICreateTypeInfo::LayOut. The LayOut method assigns VTBL offsets and member identifiers (same as DISPIDs) to the objects in the type information.

hr = m_pRuntimeTypeInfo->LayOut();

After the call to LayOut, the VTBL offsets and member identifiers are available through the ITypeInfo structure for the object.


Extended Objects and Aggregation

This section briefly describes extended objects and focuses on their role in ActiveX designers. The COM Programmer's Reference and a variety of other sources in the Microsoft Developer Network (MSDN) provide detailed explanations of aggregation and the controlling IUnknown; refer to those sources for additional information.

An extended object is an instance that has attached code (usually code that handles events) or added properties, methods, or events. For an ActiveX designer, an extended object has three parts: the ActiveX designer object; the extension, which the host adds; and the code written by the user to extend the designer. At run time, the host aggregates the parts as shown in the following diagram:

In the aggregate, the controlling extensible object determines how the object behaves and operates. It implements a special instance of the IUnknown interface called the controlling IUnknown. The controlling IUnknown must always be implemented as part of the new code when the aggregate is put together.

For the aggregate to work correctly, both the object and its extension must be written to cooperate with the controlling extensible object. Both the object and the extension implement IUnknown. Instead of acting independently, however, they should forward their AddRef, Release, and QueryInterface calls to the controlling IUnknown. The controlling IUnknown maintains a reference count for the aggregate as a whole, so that it can be kept alive if there are references to any of the interfaces supported by the controlling extensible object, the extension, or the object itself.

In the figure, the lines and circles that extend beyond the bounds of the aggregate indicate interfaces that are externally available. Both the object and the extension can expose interfaces. However, the controlling IUnknown is exposed to the outside world, while the object and extension IUnknown implementations are not. Within the aggregate, the controlling extensible object holds a pointer to the object's IUnknown implementation, indicated by the dotted line, and vice versa.

When an instance of the object is created at run time, the caller receives a pointer to the controlling IUnknown rather than to the object's IUnknown interface. Calls to the object's IUnknown methods are delegated through the controlling IUnknown as necessary. This process ensures that the object's IUnknown interface remains private and that reference counts are accurately tracked.


Services

Hosts can provide a variety of services for ActiveX designers to use. Services are a convenient means of getting access to features implemented by the site. This chapter covers the following topics you need to understand if your ActiveX designer will use services:

  • The service architecture.
  • Service definitions.
  • Using services.
  • Services that are provided by Microsoft hosts.


The Service Architecture

A service is a group of related interfaces that perform a task, such as finding the code attached to an object or displaying help. A host provides services to ensure that these tasks are accomplished in a uniform way across its contained objects.

ActiveX designers access services through their site. The interfaces that make up the service may be implemented on the site object or on any other object to which the site has access.


Siting and Containers

A container communicates with the ActiveX controls and designers it contains through a site. A site is a class implemented by the container. There is one site for each contained object. The site acts as an intermediary between the container and the object. When an ActiveX designer requires the use of a method or property of its container, it requests the feature through its site. This design lets the container know which object is making the request.

The site implements the IOleClientSite interface (or the IOleControlSite interface, if it contains ActiveX controls), and the ActiveX designer implements the IOleObject interface, as in the following figure.

At design time, the site calls IOleObject::SetClientSite, which gives the ActiveX designer a pointer to the site. Through this pointer, the ActiveX designer can use the site's IDispatch interface to change ambient properties.

Although the site provides useful information to the container about what the designer and other contained objects are doing, siting makes interactions indirect. To use interfaces implemented by the container, the ActiveX designer must issue requests through the site. Services provide an efficient architecture for locating and using these interfaces.


Providing Services Through a Site

Services are groups of related interfaces. Typically, a service is defined for actions that are performed by a site. The service itself is defined by the site, but the interfaces that make up the service may be implemented by other objects on the site.

The service architecture provides a flexible, general means of making features accessible to contained objects. The flexibility comes from several important aspects of the design:

  • Interfaces that are part of a service may be implemented on the site object or on a different object.
  • The object that implements a service may implement other interfaces in addition to those that are part of the service.
  • The same interface may be part of more than one service.
  • Services may designate some interfaces as optional.

The following figure shows a hypothetical service implementation.

The figure shows four interfaces. The IServiceProvider interface provides access to services. All sites that support services implement this interface. ActiveX designers and other contained objects call the QueryService method of IServiceProvider to get pointers to the interfaces they want to use.

The other three interfaces illustrate hypothetical service implementations. The table shows the various combinations of services that can be defined. Each service has a unique service identifier (SID), similar to an interface identifier (IID). The SID is a globally unique identifier (GUID) that identifies the service in calls to QueryService.

Service | Service and Interface Identifiers | Description
Montana service | SID_SMontana | Provides access to the Montana interfaces.
 | IID_IMontana | Montana interface.
Washington service | SID_SWashington | Provides access to the Washington interfaces.
 | IID_IWashington | Original Washington interface.
Washington2 service | SID_SWashington2 | Second version of the Washington service.
 | IID_IWashington | Original Washington interface. Same as IID_IWashington in SID_SWashington.
 | IID_IWashington2 | Second version of the Washington interface, with different features.

In the figure, the Montana service is implemented on the site object, and the two Washington services are implemented on a separate object. The following configurations are also possible:

  • All three services can be on the site object.
  • All three services can be on the same helper object.
  • The Montana service can be on one helper object and the Washington services can be on a different helper object.

Because the IWashington interface is the same in both SWashington and SWashington2, both of these services can be implemented on the same object.

This design allows ActiveX designers and other embedded objects straightforward access to useful features of their containers. The container, in turn, can configure these features efficiently. All configurations are transparent to the ActiveX designer, and different containers may organize the services differently.


Services, Interfaces, and Object Identity

QueryService identifies an object that supports the requested service, with the returned pointer already set to the specified interface.

It's important to keep in mind the following:

  • Some of the interfaces in the service may be optional. The returned object may not support every interface defined in the service description.
  • The returned object may have additional interfaces that are not part of the definition of the service.
  • Two services may be implemented on the same object. As a result, passing different SIDs to QueryService does not necessarily mean that you'll receive different objects in return.
  • An interface with the same name and IID may be part of two different services.

Two different services (SID_SIdaho and SID_SMontana, for example) can both specify the use of a single interface (for example, IDispatch), even though the implementation of the interface may have nothing in common between the two services. This works because the following two hypothetical calls can return different objects:

hr = QueryService (SID_SMontana, IID_IDispatch, &pDisp)
hr = QueryService (SID_SIdaho, IID_IDispatch, &pDisp)

Remember that object identity is not assumed if you ask for a different service identifier.


Service Definitions

Services useful for ActiveX designers are defined in the Designer.h header file provided by Microsoft. The following code, taken from Designer.h, defines the SCodeNavigate service, which allows an extended object to display the code module attached to it.

// { 6d5140c4-7436-11ce-8034-00aa006009fa }
DEFINE_GUID(IID_ICodeNavigate, 0x6d5140c4, 0x7436, 0x11ce, 0x80, 0x34,
0x00, 0xaa, 0x00, 0x60, 0x09, 0xfa);
#define SID_SCodeNavigate IID_ICodeNavigate

#undef  INTERFACE
#define INTERFACE  ICodeNavigate
DECLARE_INTERFACE_(ICodeNavigate, IUnknown)
{
    BEGIN_INTERFACE
    // *** IUnknown methods ***
    STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID FAR* ppvObj)
            PURE;
    STDMETHOD_(ULONG, AddRef)(THIS) PURE;
    STDMETHOD_(ULONG, Release)(THIS) PURE;

    // *** ICodeNavigate methods ***
    STDMETHOD(DisplayDefaultEventHandler)(
                        THIS_ /* [in] */ LPCOLESTR lpstrObjectName
                        ) PURE;
};

Every service is associated with an SID. Components that need to use a service pass the SID to IServiceProvider::QueryService. In the example, the SID for SCodeNavigate is SID_SCodeNavigate. Often, the SID is the same as the IID for one of the interfaces that belongs to the service. Here, SID_SCodeNavigate is defined to equal IID_ICodeNavigate.

The service includes the standard IUnknown interface and one or more additional interfaces. In the example, only one additional interface, ICodeNavigate, is defined. This interface contains one method, DisplayDefaultEventHandler.


Using Services

Services provide several important advantages for ActiveX designers:

  • Designers require less code, because they can use the host's service implementations instead of writing new code to perform common tasks.
  • Applications operate more efficiently. QueryService returns a pointer to a requested interface, instead of delegating method calls through the container.
  • Designers can ensure compatibility with multiple hosts.

The following sections describe how to find and call services, and what the differences are between using services and using the QueryInterface method.


Finding a Service

To locate a service, an ActiveX designer calls the IServiceProvider interface, implemented by its site. The interface has the following definition:

interface IServiceProvider : IUnknown
{
    HRESULT QueryService ( REFGUID rsid, REFIID iid, void ** ppvObj)
};

The input parameters to the QueryService method are the SID of the service being requested and the IID of the interface you want to use. In return, you receive a pointer to the requested interface if the host implements the service, or E_NOTIMPL if the host does not implement it.

For example, the following code fragment queries for the SCodeNavigate service. (Error handling has been omitted for brevity.)

hr = pClientSite->QueryInterface(IID_IServiceProvider, &pISP);
hr = pISP->QueryService(SID_SCodeNavigate, IID_ICodeNavigate, &pICN);

The example calls QueryInterface on the site to get a pointer to the site's IServiceProvider interface. Using the returned pointer, it passes the SID of the SCodeNavigate service and the IID of the ICodeNavigate interface to QueryService. If the call is successful, QueryService returns a pointer to the ICodeNavigate interface. Through this pointer, the ActiveX designer can invoke the methods defined for ICodeNavigate.

In effect, the site performs the equivalent of the following for each call to QueryService:

pSite->QueryService (SID_SCodeNavigate, IID_IUnknown, &pUnk);
pUnk->QueryInterface (IID_ICodeNavigate, &pICN);
pUnk->Release();

The ActiveX designer receives a pointer through which it can directly access the requested interface. The site does not have to delegate each individual method call.


Calling a Service

After it has called QueryService to get a pointer to an interface that is part of a service, an ActiveX designer can simply call the interface through the pointer. For example, the following two lines of code get a pointer to the ICodeNavigate interface in the SCodeNavigate service, as described in the preceding section:

hr = pClientSite->QueryInterface(IID_IServiceProvider, &pISP);
hr = pISP->QueryService(SID_SCodeNavigate, IID_ICodeNavigate, &pICN);

Assuming that the QueryService call returns without error, the following code fragment calls the DisplayDefaultEventHandler method of ICodeNavigate:

hr = pICN->DisplayDefaultEventHandler(lpMyObject);

Typically called in response to an event such as a mouse-click, this method displays the default event handler for the object named in lpMyObject.


Differences between QueryService and QueryInterface

Although it might seem logical to call QueryInterface to find the interface you want to use, services are a more efficient means of getting the same capabilities.

  • When related interfaces are grouped into a service, a single call to IServiceProvider returns a pointer to the requested interface, regardless of the object it's implemented on.
  • The container need not delegate calls to individual interfaces, as is done with QueryInterface. Instead, the ActiveX designer can invoke them directly.
  • The interfaces need not be implemented by the object that is queried; they may be implemented as part of any object that the container is aware of.

The key difference between the QueryService and QueryInterface methods is that QueryInterface on an object returns an interface on the same object, whereas QueryService makes no such guarantee. QueryService on an object may return an interface on the same object or on a different object.

Because services need not be implemented on the site object, calling QueryInterface on the site to look for interfaces in a service is not guaranteed to succeed.

In addition, calling QueryInterface on an object you receive from QueryService is valid only for interfaces that are part of the service. Querying for any other interfaces results in an error status.


Services Available for ActiveX Designers

Currently, ActiveX designers may use the services listed in the following table.

Service | Interfaces | Purpose
SApplicationObject | IDispatch | Returns the programmability interface for the host's add-in model.
SCodeNavigate | ICodeNavigate | Navigates to the code associated with an object.
SProfferTypeLib | IProfferTypeLib | Allows components to add type libraries to the project.
STrackSelection | ITrackSelection, ISelectionContainer | Tracks menu and object selections in a user interface.

Not all hosts support all of these services, and some hosts may support other services. Refer to the host documentation for details.

See Chapter 10, "Service Reference," for details of the individual services.


Retaining Persistent Data and Saving the Run-Time Object

ActiveX Designers support persistence interfaces to save data for their visual designers and run-time objects. The host loads the persistent data to reestablish the visual designer or to load the run-time object.

To retain persistent data, you have to implement one or more of the ActiveX persistence interfaces in both the visual designer and the run-time object. You may also implement the IActiveDesigner interface.

The IActiveDesigner interface provides a way of saving persistent data when the visual designer and the run-time object are different. It also provides programmability access for add-ins.

This chapter describes:

  • Overview of Persistence Requirements.
  • Implementing the Persistence Interfaces.
  • Implementing the IActiveDesigner Interface.


Overview of Persistence Requirements

Through the persistence interfaces, the ActiveX Designer can save information for use at two different times:

  • To reestablish the visual designer (the design-time object) the next time it is loaded.
  • To create and load the run-time object.

The persistent data might include type information, properties, and global variables such as time-out values and flags that the user has already set. If your visual designer supports dynamic type information, its persistent data should include the cookie value (pdwCookie) returned from GetDynamicClassInfo. The InitNew method of the persistence interfaces should reset the cookie to zero each time a new visual designer is created.

If the visual designer implements IActiveDesigner, the host uses the SaveRuntimeState method to save persistent data for the run-time object. Without IActiveDesigner, however, the run-time object must be able to load itself from the visual designer's persistent data.

Regardless of how data are saved, the run-time object must implement the same persistence interfaces as the visual designer so that the host can load the data at run time.


Implementing the Persistence Interfaces

The visual designer must implement IPersist and at least one of its derived interfaces: IPersistStreamInit, IPersistStream, and IPersistStorage.

The IPersist interface is the base interface for the other three interfaces. It has one method, GetClassID, which returns the class identifier of an object that implements one or more of the other persistence interfaces. For an ActiveX Designer, this method should return the class identifier (CLSID) of the visual designer.

You also have to implement one or more of the other persistence interfaces, depending on the type and size of the run-time objects you expect to be built from your ActiveX Designer, and which hosts will use it.

Hosts request a persistence interface by iteratively calling QueryInterface on the ActiveX Designer object, passing interface identifiers (IIDs) in order of preference. Different hosts may request different persistence interfaces. In general, IPersistStreamInit is the preferred interface because it is the fastest and because its InitNew method allows the object to avoid loading itself twice (when the instance is created and again when the Load method is called). IPersistStream is fast, but does not include an InitNew method, so you can't take advantage of this loading optimization. Some hosts, however, use IPersistStorage. Although storage persistence requires significantly more overhead, some applications may need structured storage, and some subobjects may require it.

In the visual designer, you may also want to implement IPersistPropertyBag, which saves information about properties and methods in a textual format. See the "Knowledge Base" section of the Microsoft Developer Network (MSDN) Library for more information on the IPersistPropertyBag interface.


Stream Persistence

The IPersistStream and IPersistStreamInit interfaces save and load objects from a simple serial stream. Both interfaces include the following methods:

Method | Description
GetSizeMax | Returns the size of the stream needed for the object.
IsDirty | Checks for changes to the object since it was last saved.
Load | Initializes the object from where it was saved.
Save | Saves the object to a stream.

In addition, IPersistStreamInit has a fifth method called InitNew, which notifies the object when it is being newly created, rather than being loaded from a saved instance. Implementing IPersistStreamInit is recommended because it is the fastest interface and because Visual Basic queries for it before seeking other persistence interfaces. See the COM Programmer's Reference in the Platform Software Development Kit (SDK) section of the Microsoft Developer's Network (MSDN) for more information on IPersistStreamInit.


Storage Persistence

The IPersistStorage interface supports structured storage for embedded objects. The interface includes the following methods:

Method | Description
HandsOffStorage | Instructs the object to release all storage objects that have been passed to it by its container and to enter HandsOffAfterSave or HandsOffFromNormal mode.
InitNew | Initializes a new storage object.
IsDirty | Indicates whether the object has changed since it was last saved to its current storage.
Load | Initializes an object from its existing storage.
Save | Saves an object, and any nested objects that it contains, into the specified storage object. The object enters NoScribble mode.
SaveCompleted | Notifies the object that it can revert from NoScribble or HandsOff mode, in which it must not write to its storage object, to Normal mode in which it can.

For details, refer to the COM Programmer's Reference in the Platform SDK section of MSDN.

While hosts typically look for stream persistence preferentially, it's a good idea to implement IPersistStorage anyway, to make the designer as flexible and general as possible. A simple approach is to open a stream in the storage and save the required data.


Implementing IActiveDesigner

The IActiveDesigner interface includes methods that provide access to information about the ActiveX Designer's run-time object.

IActiveDesigner includes the following methods:

  • GetExtensibilityObject
  • GetRuntimeClassID
  • GetRuntimeMiscStatusFlags
  • QueryPersistenceInterface
  • SaveRuntimeState

Although IActiveDesigner is strongly recommended, ActiveX Designers are not required to implement it. Designers that have identical visual designers and run-time objects do not have to implement the interface. However, these designers must still implement at least one of the persistence interfaces.


Returning the Extensibility Object

The GetExtensibilityObject method gives the host access to a special dispatch interface for the visual designer. This interface is not the same as the standard OLE dispatch interface; this special IDispatch is available to add-ins (such as servers that extend the Visual Basic environment) through the development environment's extensibility object.

Not all ActiveX Designers need to provide access to this extensibility object. You should implement this method if you want to provide the capability for your designer to be manipulated through the host's extensibility mechanism. If your ActiveX Designer doesn't have a separate run-time object, you can implement only this method and return E_NOTIMPL for the other IActiveDesigner methods.

The following example creates a new extensibility object and returns a pointer to its IDispatch interface for add-ins:

STDMETHODIMP CMyDesigner::GetExtensibilityObject
(
    IDispatch **ppExtensibilityObject
)
{
    if (!ppExtensibilityObject) return E_POINTER;

    *ppExtensibilityObject = new CMyAddInTopLevelAutomationObject;
    return (*ppExtensibilityObject) ? S_OK : E_OUTOFMEMORY;
}

If you don't implement the method, return E_NOTIMPL.


Providing Information on the Run-Time Object

The GetRuntimeClassID and GetRuntimeMiscStatusFlags methods return information about the run-time object. Although the information is stored in the system registry, hosts can query the ActiveX Designer more efficiently than they can query the registry.

The host application calls the GetRuntimeClassID method to get the CLSID when it builds the executable application. In the following example, the ActiveX Designer returns the CLSID from a global constant:

STDMETHODIMP CMyDesigner::GetRuntimeClassID
(
    CLSID *pclsid
)
{
    CHECK_POINTER(pclsid);

    *pclsid = CLSID_RuntimeInstance;
    return S_OK;
}

The GetRuntimeMiscStatusFlags method returns status flags that are defined for all ActiveX objects. In the following example, the flag values are stored in the constant RUNTIME_MISCSTATUS. The method simply returns the value of the constant to the caller:

STDMETHODIMP CMyDesigner::GetRuntimeMiscStatusFlags
(
    DWORD *pdwMiscStatus
)
{
    CHECK_POINTER(pdwMiscStatus);

    *pdwMiscStatus = RUNTIME_MISCSTATUS;
    return S_OK;
}

For information about the flags, refer to the COM Programmer's Reference.


Saving Run-Time Information

The QueryPersistenceInterface and SaveRuntimeState methods allow the host to find and use the persistence interface for the run-time object. (These methods apply to the run-time object only. Persistent data for the visual designer are handled directly through IPersist and its derived interfaces.)

Calls to QueryPersistenceInterface pass the interface identifier (IID) of one of the persistence interfaces (IPersistStreamInit, IPersistStream, or IPersistStorage). If the ActiveX Designer supports the specified interface, it returns S_OK; if not, it returns S_FALSE, as in the following example.

STDMETHODIMP CMyDesigner::QueryPersistenceInterface
(
    REFIID riid
)
{
        if (riid == IID_IPersistStreamInit)
                return S_OK;
        else if (riid == IID_IPersistStorage)
                return S_OK;
        else if (riid == IID_IPersistStream)
                return S_OK;
        else
                return S_FALSE;
}

The example shows a designer that supports all three persistence interfaces for the run-time object.

After the host queries for a persistence interface, it opens a stream or storage object, as appropriate, and calls SaveRuntimeState to save data for the run-time object. SaveRuntimeState passes in the IID of the persistence interface, the IID of the stream or storage object, and a pointer to the stream or storage object.

The following example checks to see what type of interface the host has passed and sets up the pointer appropriately. For storage persistence, it opens a stream within the storage object and writes the data to the stream.

STDMETHODIMP CMyDesigner::SaveRuntimeState
(
    REFIID riidItf,
    REFIID riidObj,
    void  *pMedium
)
{
    IStream *pStream;
    long     l;
    HRESULT  hr;
    LPWSTR   pwsz;

    // Get an IStream.

    if (riid == IID_IStream) {
            pStream = (IStream *)pMedium;
            pStream->AddRef();
            }
    else if (riid == IID_IStorage) {
            // Save to the CONTENTS stream.

            hr = ((IStorage *)pMedium)->
                CreateStream(s_wszRuntimeSaveStream,
                    STGM_WRITE | STGM_SHARE_EXCLUSIVE | STGM_CREATE,
                    0, 0, &pStream);
            RETURN_ON_FAILURE(hr);
            }
    else {
         FAIL("Unsupported persistence interface!");
        }

    // Save some standard state information. The Load
    // routine will look for it in the stream.

    hr = SaveStandardState(pStream);
    RETURN_ON_FAILURE(hr);

    // Save property.

    if (m_bstrMyString) {
        pwsz = m_bstrMyString;
        l = SysStringLen(m_bstrMyString) + 1;
    } else {
        pwsz = L"";
        l = 1;
    }

    hr = pStream->Write(&l, sizeof(l), NULL);
    RETURN_ON_FAILURE(hr);
    hr = pStream->Write(pwsz, l * sizeof(WCHAR), NULL);

    pStream->Release();
    return hr;
}

The example saves two kinds of persistent data: some standard data that is the same for all objects of this class, and a local property that the end user can change. After writing the data to the stream, the sample code releases the stream pointer and returns.

Your requirements will vary depending on the nature and complexity of your ActiveX Designer. You should make sure that the persistent data includes all the information needed to create an instance of the run-time object. This might include type information, properties, and global variables such as time-out values that may have been set at design time.

When you implement the Load method of the persistence interfaces, you write code to load the saved information into the run-time object. The host calls the Load method when it creates an instance of the run-time object.


Registering, Licensing, and Upgrading an ActiveX Designer

ActiveX designers must be registered in the system-wide registry so that hosts and other objects can find them. ActiveX designers also support licensing, which allows you to protect your proprietary product from unauthorized use. Upgrading allows you to distribute newer versions of your ActiveX designers which work with earlier versions in a clean, predictable manner.

This chapter discusses the following topics:

  • Registering an ActiveX designer.
  • Licensing the visual designer and run-time object.
  • Upgrading and releasing a new version of an ActiveX designer.


Registering an ActiveX Designer

ActiveX designers provide the DLLRegisterServer and DLLUnregisterServer entry points for use with the registry. DLLRegisterServer stores information about the visual designer and run-time object in the registry. DLLUnregisterServer removes information from the registry.

Registering an ActiveX designer is much like registering an ActiveX control. With the following exceptions, designers register the same information as controls:

  • Designers register CATID_Designer in the Implemented Categories subkey of the CLSID key. The value of CATID_Designer is defined in the header file Designer.h.
  • Designers register the CLSID of the run-time object under the Instance CLSID subkey of the CLSID key. Registering the CLSID of the run-time object is required only if the run-time object is different from the visual designer.
  • Designers omit the Control subkey of the CLSID key.

ActiveX designers register with Component Categories, using the ICatRegister interface. The following example registers CATID_Designer and the designer's CLSID:

hr = CoCreateInstance (CLSID_StdComponentCategoriesMgr, NULL,
                          CLSCTX_INPROC_SERVER, IID_ICatRegister,
                          (void **)&pCatRegister);
if (SUCCEEDED(hr)) {
                      pCatRegister->RegisterClassImplCategories(
                    CLSID_DifferentDesigner, 1, (GUID *)&CATID_Designer
                    );
                     pCatRegister->Release();
};

The example calls CoCreateInstance to create an instance of the component categories manager and get pCatRegister, a pointer to the ICatRegister interface. It then calls the RegisterClassImplCategories method, passing the CLSID of the designer and CATID_Designer, to register the designer under the Implemented Categories subkey. Finally, the example releases its reference to the ICatRegister interface.

In addition, if the run-time object is different from the visual designer, its CLSID must be registered under the Instance CLSID subkey of the CLSID key. You use the RegSetValue API for this task, as the following example shows:

l = RegSetValue (HKEY_CLASSES_ROOT, szInstanceInfo, REG_SZ, szInstCLSID,
                 sizeof(szInstCLSID));

In the example, HKEY_CLASSES_ROOT indicates where the information should be stored. The REG_SZ parameter denotes the type of information being supplied, and must always be REG_SZ. The szInstanceInfo and szInstanceCLSID parameters give the subkey and the value to be stored for it. In the example, these parameters have been defined to contain the Instance CLSID subkey and the CLSID of the run-time object, as follows:

static char szInstanceInfo [] =
    "CLSID\\{d342aea0-97d0-11cf-a0d2-00aa0062be57}\\Instance CLSID";
static char szInstCLSID [] = "{fdc139b0-9800-11cf-a0d2-00aa0062be57}";

The function returns l, a long integer indicating success or failure.

The example is not licensed. If it were, it would register a license under the Licenses key.

Including the information registered by ActiveX controls, designers must register the following:

  • The CLSID of the visual designer.
  • The value of CATID_Designer under the Implemented Categories subkey.
  • The CLSID of the runtime object (if different from the visual designer) under the Instance CLSID subkey.
  • The license key (if a license is required) under the Licenses key.
  • The Apartment threading model in the Threading Model subkey.
  • The OLE miscellaneous status flags for the visual designer under the MiscStatus subkey.

Under the MiscStatus key, the DVASPECT flag should be 1, to indicate DVASPECT_CONTENT. If the run-time object does not have a visual interface, you should include OLEMISC_INVISIBLEATRUNTIME with the miscellaneous status flags.


Licensing an ActiveX Designer

ActiveX designers, like ActiveX controls, support license keys which can be built into an application. Using license keys, a designer can distinguish between a license valid for creating new applications that use it (a design-time license) and a license valid to run an application that uses it (a run-time license).

One way of handling licensing is to supply a license file (.lic) with your software components. The preferable method, however, is to register a license in the registry as part of your designer's setup procedure. Licenses are stored under the Licenses subkey.

The host handles all licensing through the IClassFactory2 interface. At design time, the host assumes that an ActiveX designer is appropriately licensed if the class factory successfully creates the visual designer. When the host attempts to create an executable application (.exe file), it requests a license for the run-time object. If the designer presents a license, the host uses it; if no license is presented, the host assumes that none is required.


Designing the Licensing Scheme

Software components like forms, ActiveX designers, and ActiveX controls use licenses to protect against unauthorized use. On the one hand, you need to ensure that your customers can build, package, and redistribute applications that use these components. On the other, however, you may wish to prohibit people who purchase the resulting applications from reusing your ActiveX designer in other applications without licensing that capability from you.

At run time, three licensing options are possible:

  • No license is required to use the run-time object. The host does not save a license when it creates the run-time object, and the run-time object's class factory does not request one before creating an instance.
  • The license to use the visual designer is sufficient to use a run-time object. The host presents the stored license key to the run-time object. The run-time object verifies that the stored key matches its own copy, and execution proceeds.
  • The run-time object requires a separate license. Each user of each compiled, executable application (.exe) has to get a license from the visual designer that created the run-time object. The host presents the stored license key to the run-time object, and if the run-time object can verify the key, execution proceeds.

In some cases, the distinction between designing and running an application is not clear. For example, consider a spreadsheet application with an embedded licensed control (for example, a workbook). In a sense, the workbook can be thought of as an application for distribution. But anyone with that workbook can modify it to the point where it becomes a very different application. Unless the original license key is automatically discarded, the second user has, in effect, obtained unauthorized application design rights by virtue of the original application license key. Yet many workbook changes — data entry, for example — should not necessarily invalidate the original application license.

You should carefully consider what differentiates design-time activity from run-time activity in your ActiveX designer, especially in situations where the design-time and run-time objects are the same. Failure to do so erodes the meaning of enforceable application licensing that is critical to many third-party licensing and distribution models, which in turn may hamper the market for third-party components.

Your ActiveX designer can register separate design-time and run-time license keys. Separate licensing allows users to distribute applications built with the designer, while prohibiting them from distributing the designer itself. Users with a run-time license can't write new applications based on the ActiveX designer without first licensing the visual designer from you.

If you want to allow your ActiveX designer to be used with applications that are not licensing-aware, you should design your license enforcement scheme with this in mind. The simplest approach would be to require users of those applications to buy a design-time (machine or user) license for the component. Alternatively, you might sell a different license for end users.


Implementing the Licensing Interfaces

To support licensing, your ActiveX designer needs to implement the IClassFactory2 interface, which derives from IClassFactory. The interface includes three methods for licensing:

  • GetLicInfo
  • RequestLicKey
  • CreateInstanceLic

The interface is defined in the header file Olectl.h It uses the LICINFO structure, which includes the following fields:

Field | Type | Description
cbLicInfo | long | Size of LICINFO structure.
FRuntimeKeyAvail | BOOL | True if the class provides a run-time key.
FLicVerified | BOOL | True if the license has been verified.

The structure provides information about the licensing key. The first member, cbLicInfo, gives the size of the structure. The fRuntimeKeyAvail member is a Boolean that is True if the class provides a run-time key that can be built into an application.

The Boolean fLicVerified flag is set to True when the ActiveX designer has verified that it is licensed for this machine. If the designer can determine that a valid license is present before it receives a key, it sets this flag. If the current user or machine license is supposed to override the application license corresponding to a key, the designer can use fLicVerified to avoid passing the key unnecessarily.

To provide maximum flexibility, license keys are defined as BSTR, which can be any length and can include Null characters.


Licensing in the Visual Designer

At design-time, license verification proceeds as shown in the following figure:

When a programmer loads the ActiveX designer, the host calls GetLicInfo to verify that the appropriate license is present. If the license is not present, the method returns E_NOTLICENSED and the visual designer is not created. If a license is present or if no license is required, the method creates an instance and returns S_OK.

The following code sample shows how GetLicInfo might be implemented:

STDMETHODIMP CClassFactory::GetLicInfo
(
    LICINFO *pLicInfo
)
{
    CHECK_POINTER(pLicInfo);

    pLicInfo->cbLicInfo = sizeof(LICINFO);

    // If true, RequestLicKey will work.
    pLicInfo->fRuntimeKeyAvail = g_fMachineHasLicense;

    // If true, the standard CreateInstance will work.
    pLicInfo->fLicVerified = g_fMachineHasLicense;

    return S_OK;
}

After construction of the application is complete, the programmer selects Build or Run.

During this process, the host calls RequestLicKey to get a key to embed into the resulting application. The following sample shows one approach to implementing this method:

STDMETHODIMP CClassFactory::RequestLicKey
(
    DWORD  dwReserved,
    BSTR  *pbstr
)
{
    // If the machine isn't licensed, don't give this.
    if (!g_fMachineHasLicense)
        return CLASS_E_NOTLICENSED;

    *pbstr = SysAllocString(g_wszLicenseKey);
    return (*pbstr) ? S_OK : E_OUTOFMEMORY;
}

As with GetLicInfo, RequestLicKey returns CLASS_E_NOTLICENSED if the license key is not valid.


Licensing in the Run-Time Object

The run-time verification process is illustrated below:

When the application is started, the ActiveX designer's run-time object typically needs to be created. Before doing so, the application must verify that the ActiveX designer is licensed for the machine. It calls CreateInstanceLic, passing the embedded license key as a parameter.

CreateInstanceLic compares the embedded string with the ActiveX designer's copy saved with the run-time object's class factory. If the strings match, CreateInstanceLic creates an instance of the run-time object; the returned status reflects the success or failure of the instance creation. If the strings do not match, CreateInstanceLic returns CLASS_E_NOTLICENSED.

The following sample shows an implementation of CreateInstanceLic:

STDMETHODIMP CClassFactory::CreateInstanceLic
(
    IUnknown *pUnkOuter,
    IUnknown *pUnkReserved,
    REFIID    riid,
    BSTR      bstrKey,
    void    **ppvObjOut
)
{
    BOOL  fMatch;

    *ppvObjOut = NULL;

    // See if the key given matches.
    fMatch = ( 0 == lstrcmpW(g_wszLicenseKey, bstrKey));
    if (!fMatch)
        return CLASS_E_NOTLICENSED;

    // If it does, create the object.
    return CreateOleObjectFromIndex(pUnkOuter, m_iIndex,
            ppvObjOut, riid);
}

CreateInstanceLic is similar to IClassFactory::CreateInstance, except that it adds the license key verification.

If the host passes a key that indicates more restricted capabilities than the user or machine license allows, the ActiveX designer should provide only the limited capabilities for the newly created instance, as if the user or machine license had not been previously verified.


Upgrading an ActiveX Designer

ActiveX designers follow the Automation model for upgrades. To release a new version, you supply a new type library with a new version number. To change the version number, use the SetVersion method in the ICreateTypeLib interface. This method takes two parameters, a major version number and a minor version number.

A new version that is compatible with previous versions requires only a new type library with an increment in the minor version number.

A new version that is incompatible with previous versions requires that you increment the programmatic identifier (PROGID) and supply a new type library with an increment in the major version number. You also have to update the registry to include the new PROGID and version number.

See the Automation Programmer's Reference for more information.


API Reference

This chapter provides reference information on the Application Programming Interfaces (APIs) that ActiveX designers use and implement. The interfaces are as follows:

  • IActiveDesigner interface
  • IProvideDynamicClassInfo interface
  • IServiceProvider interface

These interfaces are defined in the header file Designer.h.


IActiveDesigner Interface

The IActiveDesigner interface provides access to and information about an ActiveX designer's run-time object. The following methods make up the interface.

Method | Description
GetExtensibilityObject | Returns the extensibility object for the ActiveX designer.
GetRuntimeClassID | Returns the class identifier (CLSID) of the run-time object.
GetRuntimeMiscStatusFlags | Returns the miscellaneous status flags for the run-time object.
QueryPersistenceInterface | Identifies the type of persistence interface supported by the ActiveX designer.
SaveRuntimeState | Saves the information necessary to create a run-time object.

See "Implementing IActiveDesigner" in Chapter 7, "Retaining Persistent Data and Saving the Run-Time Object" for more information.


IActiveDesigner::GetExtensibilityObject

Returns the extensibility object for the ActiveX designer.

HRESULT GetExtensibilityObject(
IDispatch **ppvObjOut
);

Parameter

ppvObjOut

[out] Pointer to an IDispatch interface.

Return Values

The return value obtained from the returned HRESULT is one of the following:

Return Value | Meaning
S_OK | Success.
E_FAIL | The method failed for an unknown reason.
E_NOTIMPL | The run-time and design-time objects are the same, or the designer does not support programmatic manipulation.
E_OUTOFMEMORY | Not enough memory to complete the operation.
E_POINTER | The ppvObjOut parameter is not a valid pointer.

Comments

If the ActiveX designer can be programmatically manipulated, its IDispatch interface is available in two ways. If the visual designer implements IActiveDesigner, the IDispatch interface is available through GetExtensibilityObject. If the visual designer does not implement IActiveDesigner, objects must call QueryInterface to get the IDispatch interface.

In Visual Basic 5.0, the extensibility object for the visual designer is available to add-ins (servers that extend the integrated development environment (IDE)) through the extensibility object of the IDE.

You may implement this method without implementing the other methods of the interface.

Example

The following example creates a new extensibility object and returns its address to the caller:

STDMETHODIMP CMyDesigner::GetExtensibilityObject
(
    IDispatch **ppExtensibilityObject
)
{
    if (!ppExtensibilityObject) return E_POINTER;

    *ppExtensibilityObject = new CMyAddInTopLevelAutomationObject;
    return (*ppExtensibilityObject) ? S_OK : E_OUTOFMEMORY;
}


IActiveDesigner::GetRuntimeClassID

Returns the CLSID of the run-time object.

HRESULT GetRuntimeClassID(
CLSID *pClsid
);

Parameter

pClsid

[out] CLSID of the run-time object created by the ActiveX designer.

Return Values

The return value obtained from the returned HRESULT is one of the following:

Return Value | Meaning
S_OK | Success.
E_FAIL | The method failed for an unknown reason.
E_NOTIMPL | The run-time and design-time objects are the same.

Comments

The host calls GetRuntimeClassID to get the CLSID of the run-time object. The CLSID is also marked in the registry entry for the ActiveX designer under the Instance CLSID heading.

Example

In the following example, the CLSID is retained as a constant and returned upon request through GetRuntimeClassID:

STDMETHODIMP CMyDesigner::GetRuntimeClassID
(
    CLSID *pclsid
)
{
    CHECK_POINTER(pclsid);

    *pclsid = CLSID_RuntimeInstance;
    return S_OK;
}


IActiveDesigner::GetRuntimeMiscStatusFlags

Returns the miscellaneous status flags for the run-time object.

HRESULT GetRuntimeMiscStatusFlags (
DWORD *dwMiscFlags
);

Parameter

dwMiscFlags

[out] Pointer to the miscellaneous status flags. The flags have the following meanings:

Flag | Meaning
OLEMISC_ACTSLIKEBUTTON | The container is prepared to act like a button. Typically used by containers that provide Default and Cancel buttons.
OLEMISC_ACTSLIKELABEL | ActiveX objects can change the container's preset label.
OLEMISC_ALIGNABLE | The container supports aligned controls.
OLEMISC_ALWAYSRUN | The object should be put into the run state even if it is not visible. Not normally used by in-process servers, including ActiveX designers.
OLEMISC_IMEMODE | The object understands IME mode. Used only by double-byte character set (DBCS) applications.
OLEMISC_INVISIBLEATRUNTIME | The object is visible only at design time. Used by ActiveX designer objects that are invisible at run time.
OLEMISC_NOUIACTIVATE | The object does not support user interface activation.
OLEMISC_SETCLIENTSITEFIRST | The object requires status information from IOleControl::SetClientSite immediately after creation, rather than after a call to IPersistStorage::Load or IPersistStorage::InitNew.
OLEMISC_SIMPLEFRAME | The object supports the ISimpleFrameSite protocol.

Return Values

The return value obtained from HRESULT is one of the following:

Return Value | Meaning
S_OK | Success.
E_FAIL | The method failed for an unknown reason.
E_NOTIMPL | The design-time and and run-time objects are the same.

Comments

ActiveX designers should return all the status flags that appear in the registry for the run-time object. For more information, see the COM Programmer's Reference.

Example

The following example stores the flag values in the constant RUNTIME_MISCSTATUS and simply returns its value to the caller:

STDMETHODIMP CMyDesigner::GetRuntimeMiscStatusFlags
(
    DWORD *pdwMiscStatus
)
{
    CHECK_POINTER(pdwMiscStatus);

    *pdwMiscStatus = RUNTIME_MISCSTATUS;
    return S_OK;
}


IActiveDesigner::QueryPersistenceInterface

Asks about the persistence interface implemented by the run-time object.

HRESULT QueryPersistenceInterface(
REFIID riid
);

Parameter

riid

[in] Identifier of a persistence interface. Possible values are as follows:

Interface Identifier | Meaning
IID_IPersistFile | File persistence.
IID_IPersistStorage | Storage persistence.
IID_IPersistStream | Stream persistence.
IID_IPersistStreamInit | Stream persistence with initialization.

Return Values

The return value obtained from HRESULT is one of the following:

Return Value | Meaning
S_OK | The designer supports the specified persistence interface.
S_FALSE | The designer does not support the interface.
E_FAIL | The method failed for an unknown reason.
E_INVALIDARG | The argument is invalid.
E_NOTIMPL | The design-time and run-time objects are the same.
E_OUTOFMEMORY | Out of memory.

Comments

The persistence interface requested depends on the host; different hosts will request different interfaces. Visual Basic 5.0, for example, does not use file persistence.

The host uses this method together with the SaveRuntimeState method to save persistent data needed later to create and load the run-time object. It asks the visual designer for persistence interfaces in order of preference.

Example

The following example implements QueryPersistenceInterface for an ActiveX designer that supports three persistence interfaces:

STDMETHODIMP CMyDesigner::QueryPersistenceInterface
(
    REFIID riid
)
{
        if (riid == IID_IPersistStreamInit)
                return S_OK;
        else if (riid == IID_IPersistStorage)
                return S_OK;
        else if (riid == IID_IPersistStream)
                return S_OK;
        else
                return S_FALSE;
}


IActiveDesigner::SaveRuntimeState

Saves the state information required to create a run-time object.

HRESULT SaveRuntimeState(
REFIID riidItf
REFIID riidObj,
void *pObj
);

Parameters

riidItf

[in] Identifier of the persistence interface. Possible values are as follows:

Interface Identifier | Meaning
IID_IPersistPropertyBag | Textual property persistence.
IID_IPersistStorage | Storage persistence.
IID_IPersistStream | Stream persistence.
IID_IPersistStreamInit | Stream persistence with initialization.

riidObj

[in] Identifier of the object designated by pObj. Possible values are as follows:

Interface Identifier | Meaning
IID_IStorage | Storage object.
IID_IStream | Stream object.

pObj

[in] Pointer to a persistence object opened by the host.

Return Values

The return value obtained from HRESULT is one of the following:

Return Value | Meaning
S_OK | Success.
E_FAIL | The method failed for an unknown reason.
E_INVALIDARG | One or more of the arguments is invalid.
E_NOTIMPL | The design-time and run-time objects are the same.
E_OUTOFMEMORY | Out of memory.

Comments

SaveRuntimeState saves information needed to create the ActiveX designer's run-time object. The Load method of the run-time object's persistence interface must be able to load this information when the run-time object is created.

Before calling SaveRuntimeState, the host calls QueryPersistenceInterface to determine which persistence interface to use.

Example

The following example checks to see what type of interface the host has passed and sets up the pointer appropriately. For storage persistence, it opens a stream within the storage object and writes the data to the stream.

STDMETHODIMP CMyDesigner::SaveRuntimeState
(
    REFIID riidItf,
    REFIID riidObj,
    void  *pMedium
)
{
    IStream *pStream;
    long     l;
    HRESULT  hr;
    LPWSTR   pwsz;

    // Get an IStream.

    if (riid == IID_IStream) {
            pStream = (IStream *)pMedium;
            pStream->AddRef();
            }
    else if (riid == IID_IStorage) {
            // Save to the CONTENTS stream.

            hr = ((IStorage *)pMedium)->
                CreateStream(s_wszRuntimeSaveStream,
                    STGM_WRITE | STGM_SHARE_EXCLUSIVE | STGM_CREATE,
                    0, 0, &pStream);
            RETURN_ON_FAILURE(hr);
            }
    else {
         FAIL("Unsupported persistence interface!");
        }

    // Save some standard state. The Load
    // routine will look for it in the stream.
    hr = SaveStandardState(pStream);
    RETURN_ON_FAILURE(hr);

    // Save property.

    if (m_bstrMyString) {
        pwsz = m_bstrMyString;
        l = SysStringLen(m_bstrMyString) + 1;
    } else {
        pwsz = L"";
        l = 1;
    }

    hr = pStream->Write(&l, sizeof(l), NULL);
    RETURN_ON_FAILURE(hr);
    hr = pStream->Write(pwsz, l * sizeof(WCHAR), NULL);

    pStream->Release();
    return hr;
}


IProvideDynamicClassInfo Interface

The IProvideDynamicClassInfo interface provides access to the dynamic type information for an object. Each object has one dynamic type information object throughout its lifetime, regardless of the number of changes to the type information. The type information itself changes while the type information object maintains its identity.

This interface derives from IProvideClassInfo and includes two methods.

Method | Description
FreezeShape | Freezes the dynamic type information for an object.
GetDynamicClassInfo | Returns the dynamic type information for an object.


IProvideDynamicClassInfo::FreezeShape

Freezes the dynamic type information so that no more changes are permitted to the object.

HRESULT FreezeShape();

Parameters

None.

Return Values

The return value obtained from HRESULT is one of the following:

Return Value | Meaning
S_OK | Success.
E_FAIL | The method failed for an unknown reason.
E_OUTOFMEMORY | Out of memory.

Comments

The host calls FreezeShape immediately after loading the run-time object, so the type information for the run-time object cannot change.

Example

The following example sets a global flag, m_fFreezeShape, to indicate that the type information cannot change any further. The flag will be cleared after the current object is destroyed and a new instance is created.

STDMETHODIMP CMyDesigner::FreezeShape
(
    void
)
{
    m_fFreezeShape = TRUE;
    return S_OK;
}


IProvideDynamicClassInfo::GetDynamicClassInfo

Returns the current dynamic type description for an object, including any changes that have been made dynamically.

HRESULT GetDynamicClassInfo(
ITypeInfo ** ppTI,
DWORD * pdwCookie
);

Parameters

ppTI

[out] Indirect pointer to the current type information for the object.

pdwCookie

[out] Cookie to be used as a flag.

Return Values

The return value obtained from the HRESULT is one of the following:

Return Value | Meaning
S_OK | Success.
E_FAIL | The method failed for an unknown reason.
E_OUTOFMEMORY | Out of memory.
E_UNEXPECTED | The method is implemented, but an unexpected error occurred.

Note

This method can also return any of the TYPE_E errors.

Comments

The GetDynamicClassInfo method returns an object's dynamic type information object. The dynamic type information changes as users change the object's properties, events, and methods and write code attached to the object.

Each object has only one dynamic type information object (that is, one ITypeInfo that points to dynamic type information) throughout its lifetime. Although the type information itself changes, the type information object retains its identity.

The ITypeInfo returned by this method must describe a component object class (coclass)(TKIND_COCLASS).

The pdwCookie value is saved by the host to determine whether the newly loaded object has the same type information as when it was last saved. The Save method of the visual designer's persistence interface should set the value of the cookie each time it saves the persistent data. The host checks the cookie when it gets the dynamic type information to see if the type information has changed.

Example

The following example returns a dynamic type information object:

STDMETHODIMP CMyDesigner::GetDynamicClassInfo
(
    ITypeInfo **ppTypeInfoOut,
    DWORD      *pdwCookie
)
{
    HRESULT hr;

    if (pdwCookie)
        *pdwCookie = m_dwTICookie;

    if (!ppTypeInfoOut)
        return S_OK;

return GetMyDynamicTypeInfo(ppTypeInfoOut);
}

The value of pdwCookie is set in the persistence methods for the visual designer. This value should be part of the data you save with the Save method of your persistence interfaces.

The example calls the local function GetMyDynamicTypeInfo, which creates the type information if necessary. You can either load a static type information object and copy it, or call CreateTypeLib2 to create a new type information object in a new format type library. For a detailed explanation, see "Providing Type Information" in Chapter 5, "Type Information and Extended Objects."


IServiceProvider Interface

IServiceProvider enables an object to obtain services from a service provider. It has one method.

Method | Description
QueryService | Returns an interface that is part of a requested service.


IServiceProvider::QueryService

Returns an interface that is part of a requested service.

HRESULT QueryService(
REFGUID rsid,
REFIID iid,
VOID ** ppvObj
);

Parameters

rsid

[in] Pointer to a service identifier (SID).

iid

[in] Identifier of the interface the caller needs to gain access to.

ppvObj

[out] Indirect pointer to the requested interface.

Return Values

The return value obtained from the returned HRESULT is one of the following:

Return Value | Meaning
S_OK | Success.
E_INVALIDARG | One or more of the arguments is invalid.
E_NOINTERFACE | The requested interface is not part of this service, or the service is unknown.
E_OUTOFMEMORY | Out of memory.

Comments

QueryService returns an indirect pointer to the requested interface in the specified service. The caller is responsible for releasing this pointer when it is no longer needed.

QueryService is similar to the QueryInterface method of IUnknown. The key difference is that QueryInterface on an object returns an interface on the same object, whereas QueryService makes no such guarantee. QueryService on an object may return an interface on the same object or on a different object.

When you call QueryService, you pass both a service identifier (rsid) and an interface identifier (iid). The rsid specifies the service to which you want access, and the iid identifies an interface that is part of the service. In return, you receive an indirect pointer to the interface.

The object that implements the interface may also implement interfaces that are part of other services. Keep in mind the following:

  • Some of these interfaces may be optional. Not all the interfaces defined in the service description are necessarily present on every implementation of the service (or on every returned object).
  • Unlike calls to QueryInterface, passing a different service identifier does not necessarily mean that a different COM object will be returned.
  • The returned object may have additional interfaces that are not part of the definition of the service.

Two different services (SID_SMyService and SID_SYourService, for example) can both specify the use of the same interface, even though the implementation of the interface may have nothing in common between the two services. This works because a call to QueryService (SID_SMyService, IID_IDispatch) can return a different object than QueryService (SID_SYourService, IID_IDispatch). Remember that object identity is not assumed when you specify a different service identifier.

Example

The following example requests the ITrackSelection interface, which is part of the STrackSelection service:

hr = QueryService (SID_STrackSelection, IID_ITrackSelection, &pITS);

For additional information, see Chapter 6, "Services."


Service Reference

This chapter provides a reference to the services available to ActiveX designers. The services are:

  • SApplicationObject service
  • SCodeNavigate service
  • SProfferTypeLib service
  • STrackSelection service

These services are defined in the header file Designer.h.


SApplicationObject Service

The SApplicationObject service provides access to a pointer to the host's add-in model. The add-in model is the top-level Application object, an Automation object that designers, wizards, and add-ins can use. Not all hosts implement this service, and different hosts define the add-in model differently. Refer to your host documentation for more information.

You can query the service for the IDispatch and IUnknown interfaces. For information on these interfaces, see the Automation Programmer's Reference.

The service identifier (SID) for this service is SID_SApplicationObject.


SCodeNavigate Service

The site provides the SCodeNavigate service for components that need to assist users in navigating from an object to its associated code. Using the service, components can manipulate the user interface to cause navigation. For example, when a user double-clicks an ActiveX designer, the designer can call DisplayDefaultEventHandler to display the code for the default event.

SCodeNavigate includes one interface, ICodeNavigate; its method is listed in the following table.

Method | Description
DisplayDefaultEventHandler | Displays the default event handler for an object.

The SID for this service is SID_SCodeNavigate.


ICodeNavigate::DisplayDefaultEventHandler

Displays the default event handler for an object.

HRESULT DisplayDefaultEventHandler(
 LPSTR lpstrObjectName
);

Parameter

lpstrObjectName

[in] Pointer to the name of an object.

Return Values

The return value obtained from HRESULT is one of the following:

Return Value | Meaning
S_OK | Success.
E_INVALIDARG | One or more of the arguments is invalid.

Comments

The DisplayDefaultEventHandler method causes the default event handler for the named object to be displayed. The name passed in lpstrObjectName refers to the property that returns the object. This name appears in the type information for the ActiveX designer. Examples are "Button1" and "Form1."

Example

The following example queries for the SCodeNavigate service and calls the DisplayDefaultEventHandler method. The variable pwstr gives the name of the object for which it will display the event handler.

hr = m_pServiceProvider->QueryService(SID_SCodeNavigate,
                        IID_ICodeNavigate, (void **)&pNavigate);
if (FAILED(hr)) return;

pNavigate->DisplayDefaultEventHandler(pwstr);
pNavigate->Release();


SProfferTypeLib Service

ActiveX designers use the SProfferTypeLib service to contribute type libraries to a project. The service defines one interface, IProfferTypeLib, listed in the following table.

Method | Description
ProfferTypeLib | Adds a type library to the project.

The SID for this service is SID_SProfferTypeLib.


IProfferTypeLib::ProfferTypeLib

Adds a reference to a type library to the project.

HRESULT ProfferTypeLib(
 REFGUID guidTypeLib,
UINT uVerMaj,
UINT uVerMin,
DWORD dwFlags
);

Parameters

guidTypeLib

[in] Globally unique identifier of the type library to add.

uVerMaj

[in] Major version number of the type library.

uVerMin

[in] Minor version number of the type library.

dwFlags

[in] Flags indicating the kind of type library. With Visual Basic 5.0, this value must be 0.

Return Values

The return value obtained from HRESULT is one of the following:

Return Value | Meaning
S_OK | Success.
E_INVALIDARG | One or more of the arguments is invalid.
E_OUTOFMEMORY | Out of memory.

Comments

The ProfferTypeLib method adds a programmatic reference to a type library. It is the equivalent of choosing References on the Project menu in Visual Basic 5.0.

The order in which type libraries are added is important. Type libraries added in an earlier call are listed before type libraries added in a later call. After type libraries have been added, a user can change the order in which they are referenced by rearranging their names in the References dialog box. The order of reference may affect the user's code, particularly if two type libraries expose the same name.

References are persistent. A project remembers which type libraries a component contributed during an earlier session. A component remembers which type libraries it contributed. A component cannot alter the order in which its libraries are referenced after user editing has occurred. A user may remove a reference and then add it again, but the component cannot control where the reference appears in the project-level list.

Example

The following example queries for the SProfferTypeLib service, then calls the ProfferTypeLib method to add a type library to the project:

hr = m_pServiceProvider->QueryService(SID_SProfferTypeLib,
                        IID_IProfferTypeLib, (void **)&pProfferTypeLib);
if (FAILED(hr)) return;

// Add the type library.
pProfferTypeLib->ProfferTypeLib(m_guidTypeLib, uMaj, uMin, dwFlags);

// Done.
pProfferTypeLib->Release();


STrackSelection Service

The STrackSelection service allows extended interaction between the host's property browser and an ActiveX designer. A browser can change the current selection, show or hide the property browser, and allow selections of groups of subobjects in the ActiveX designer.

STrackSelection defines two interfaces, ISelectionContainer and ITrackSelection, as listed in the following table.

Interface | Method | Description
ISelectionContainer | CountObjects | Returns the number of selected or selectable objects.
 | GetObjects | Gets the selected or selectable objects.
 | SelectObjects | Selects a single object.
ITrackSelection | OnSelectChange | Passes a pointer to ISelectionContainer.

The ActiveX designer implements the ISelectionContainer interface and calls OnSelectChange to pass the host a pointer to this interface. After it has the pointer, the host can call the methods in ISelectionContainer in response to user selections.

The SID for this service is SID_STrackSelection.


ISelectionContainer::CountObjects

Returns the total number of objects or the number of objects selected.

HRESULT CountObjects(
DWORD dwFlags,
ULONG *pc
);

Parameters

dwFlags

[in] Flag specifying which count to return, either GETOBJS_ALL or GETOBJS_SELECTED.

pc

[out] Number of objects returned.

Return Values

The return value obtained from HRESULT is one of the following:

Return Value | Meaning
S_OK | Success.
E_INVALIDARG | One or more of the arguments is invalid.

Comments

The ActiveX designer implements this method.

If the caller specifies GETOBJS_ALL, the CountObjects method returns the total number of objects in the selection container. If the caller specifies GETOBJS_SELECTED, the method returns the total number of selected objects in the container.

Example

The following example implements CountObjects for a simple container:

STDMETHODIMP CMySelectionContainer::CountObjects
(
    DWORD dwFlags,
    ULONG * pc
)
{
    if (GETOBJS_ALL == dwFlags) {
        // |objects| + 1 for the container.
        *pc = m_state.dwObjectCount + 1;
        }
    else {
        // Either the container or one of the objects is always
        // selected.
        *pc = 1;
    }
    return S_OK;
}


ISelectionContainer::GetObjects

Returns either all selectable objects or all selected objects, depending on the flag passed.

HRESULT GetObjects(
DWORD dwFlags,
ULONG cObjects,
IUnknown **apUnkObjects
);

Parameters

dwFlags

[in] Flag specifying which objects to return, either GETOBJS_ALL or GETOBJS_SELECTED.

cObjects

[in] Number of objects returned.

apUnkObjects

[out] Pointer to an array of returned objects.

Return Values

The return value obtained from HRESULT is one of the following:

Return Value | Meaning
S_OK | Success.
E_INVALIDARG | One or more of the arguments is invalid.

Comments

The ActiveX designer implements this method.

The GetObjects method returns either all selectable objects or all selected objects, depending on the flag passed. The returned array of objects is used as the basis for SelectObjects.

The designer should pass a pointer to its extended object, rather than to itself. The extended object includes the ambient properties supplied by the host, which the user may wish to browse. The extended object is available through the site. See the COM Programmer's Reference for more information.

Because the returned objects support IDispatch, the host can obtain information directly from them. For example, to get an object's name, the host can call IDispatch::Invoke (or ITypeInfo::Invoke) with a predefined dispatch identifier (DISPID) that signifies the name. The predeclared DISPIDs are negative to ensure that they don't conflict with user-defined identifiers. The table that follows describes some of this information and how it is obtained.

Item | How it is Obtained
The name of the object. | DISPID_NAME
The index of the object. | DISPID_OBJECT
The type of the object. | IProvideClassInfo::QueryInterface

Example

The following example implements GetObjects; the returned array is used by the SelectObjects method.

STDMETHODIMP CMySelectionContainer::GetObjects
(
    DWORD dwFlags,
    ULONG cObjects,
    IUnknown **apUnkObjects
)
{
    // Initialize the array.
    for (ULONG i = 0; i < cObjects; i++) {
        apUnkObjects[i] = NULL;
    }

    IUnknown * pUnkUs;
    HRESULT hr;
    BOOL bUsedUnkUs = FALSE;

    // Can't pass self as an object because the user won't be
    // able to browse ambient properties, so need to pass
    // extender. Get it from site.
    IDispatch * pDisp = NULL;
    hr = m_pControlSite->GetExtendedControl(&pDisp);
    if (SUCCEEDED(hr)) {
        hr = pDisp->QueryInterface(IID_IUnknown, (void**)&pUnkUs);
        pDisp->Release();
    }

    if (FAILED(hr)) {
        // Can't get the extender, so use self anyway.
        pUnkUs = (IUnknown*)(ISelectionContainer*)this;
        pUnkUs -> AddRef();
    }

    if (GETOBJS_ALL == dwFlags) {
        ULONG x = 0;
        for (int i = 0; i < MAXOBJECTS && x < (int) cObjects; i++) {
                If (m_state.rgObjects[i].bHaveIt) {
                apUnkObjects[x] = m_state.rgObjects[i].pObject;
                apUnkObjects[x]->AddRef();
                x++;
                }
        }
    if (x < (int) cObjects) {
        apUnkObjects[x] = pUnkUs;
        bUsedUnkUs = TRUE;
        }
    }
    else {
    // Host requested all selected objects.
    if (cObjects > 0) {
        if (m_state.iSelectedObject != -1) {
            apUnkObjects[0] =
                m_state.rgCircles[m_state.iSelectedObject].pObject;
            apUnkObjects[0]->AddRef();
                }
        else {
            apUnkObjects[0] = pUnkUs;
            bUsedUnkUs = TRUE;
                }
        }
    }
    if (!bUsedUnkUs) {
        pUnkUs->Release();
    }
    return S_OK;
}

See Also

"Extended Objects and Aggregation" in Chapter 5, "Type Information and Extended Objects."


ISelectionContainer::SelectObjects

Selects a single object from a group of objects.

HRESULT SelectObjects(
ULONG cSelect,
 IUnknown ** apUnkSelect,
 DWORD dwFlags
);

Parameters

cSelect

[in] Index into the array of objects returned by ISelectionContainer::GetObjects.

apUnkSelect

[in] Pointer to the selected object.

dwFlags

[in] Flags modifying the selection. Set to SELOBJS_ACTIVATEWINDOW to activate the window that displays the selected objects.

Return Values

The return value obtained from HRESULT is one of the following:

Return Value | Meaning
S_OK | Success.
E_INVALIDARG | One or more of the arguments is invalid.

Comments

The ActiveX designer implements this method.

The GetObjects method returns either all selectable objects or all selected objects, depending on the flags passed. The SelectObjects method allows any of these objects to be selected.

Because the returned objects support IDispatch, the host can obtain information directly from them. For example, to get the name of an object, the host can call IDispatch::Invoke (or ITypeInfo::Invoke) with a predefined DISPID that signifies the name. The predeclared DISPIDs are negative to ensure that they don't conflict with user-defined identifiers. The table that follows describes some of this information and how it is obtained. These DISPIDs are defined in the header file Typelib2.h.

Item | How it is Obtained
The name of the object. | DISPID_NAME
The index of the object. | DISPID_OBJECT
The type of the object. | IProvideClassInfo::QueryInterface

Example

The following example implements SelectObjects using the array returned by GetObjects.

STDMETHODIMP CMySelectionContainer::SelectObjects
(
    ULONG cSelect,
    IUnknown **apUnkSelect,
    DWORD dwFlags
)
{
    // Select the first thing given.
    int index = -1;
    if (cSelect > 0) {
        // See what the selected object is. Default is the main
        // object.
        for (int i = 0; i < MAXOBJECTS; i++) {
                if (m_state.rgObjects[i].bHaveIt &&
                apUnkSelect[0] == m_state.rgObjects[i].pObject) {
                index = i;
            break;
            }
    }

    MaybeSelectionChanged(index, FALSE);
    }
    return S_OK;
}

Although calling MaybeSelectionChanged results in a call to the host's ITrackSelection mechanism, it's necessary because the host has to display the main control window differently (with stretch bars) if its selection is selected, which is always the case.


ITrackSelection::OnSelectChange

Specifies the selection container interface to call for the current object.

HRESULT OnSelectChange(
ISelectionContainer *pSC
);

Parameter

pSC

[in] Pointer to an object of type ISelectionContainer.

Return Values

The return value obtained from HRESULT is one of the following:

Return Value | Meaning
S_OK | Success.
E_INVALIDARG | The argument is invalid.

Comments

The host implements OnSelectChange.

The ActiveX designer calls OnSelectChange, passing a pointer to its implementation of the ISelectionContainer interface. As the user browses and edits the objects, the host calls the methods supported by ISelectionContainer to manage the selection.

Through the CountObjects, GetObjects, and SelectObjects methods, the selection container enables enumeration and selection of one or more objects.

Example

The following example calls OnSelectChange to pass a pointer to pISC, a selection container interface. The host can call CountObjects, GetObjects, and SelectObjects through this pointer.

hr =  m_state.pServiceProvider->QueryService(SID_STrackSelection,
                         IID_ITrackSelection,
                         (void **) &pITS);
hr = pITS->OnSelectChange(pISC);
pITS->Release();


Glossary

A

ActiveX

Microsoft's brand name for the technologies that enable interoperability using the Component Object Model (COM).

ActiveX component

Physical file that contains classes, which are definitions of objects. Formerly these were known as "OLE Automation servers" and "OLE Automation controllers."

ActiveX control

The new name for programmable elements formerly known variously as OLE Controls, OCXs, or OLE Custom Controls. Controls built with the MFC Control Developer's Kit meet the ActiveX control specification.

ActiveX designer

An ActiveX component that has a programmable interface and presents a visual interface at design time. Loaded into a host's development environment, ActiveX designers are top-level project components that third-party developers can use to create executable applications.

ambient properties

Properties that define a container's surroundings, including default colors, fonts, and alignment. Ambient properties are exposed to the controls contained in the container.

Application object

The single instance of the Application class. The Application object controls documents, views, frame windows, and templates, and specifies application behavior such as initialization and cleanup for every instance of an application.

aspect

A characteristic of an ActiveX object, specified in the IOleObject interface. An object's aspect includes how it should be displayed and what types of behavior it supports.

attached code

Code written by a user and associated with an object. Attached code can be used to customize an object by adding properties, methods, or events.

Automation

COM-based technology that enables interoperability among components.

C

class

The formal definition of an object. The class acts as the template from which an instance of an object is created at run time. The class defines the properties of the object and the methods used to control the object's behavior.

A Component Object Model (COM) class is not necessarily the same as a class in an object-oriented language. A COM class can be identified by a CLSID.

class factory

An object that implements the IClassFactory or IClassFactory2 interface, which allows it to create other objects of a specific class.

Component Object Model (COM)

The programming model and binary standard on which ActiveX and Automation technologies are based. COM defines how ActiveX and OLE objects and their clients interact within processes or across process boundaries.

COM object

An object that conforms to the Component Object Model. Such objects implement and use the set of interfaces that support object interaction.

container

An application or object that can contain other objects, and which interacts with the contained object through Automation.

control

An object that you can place on a form or designer and that has its own set of recognized properties and events. Controls can receive user input, display output, and trigger event procedures. You can manipulate most controls using methods. Some controls are interactive (responsive to user actions), while others are static (accessible only through code).

control container

An object that provides sites that can contain controls. Typically these sites all exist on the same document or form. The control container implements entry points for the controls and exposes ambient properties to them.

controlling extensible object

See extended object.

controlling IUnknown

A special instance of the IUnknown interface that is implemented in an extended object.

cookie

A special reference or handle that is associated with a specific object.

customized class

A class to which an end user has attached code, using Visual Basic's object extension technology.

customized document

A document object to which an end user has attached code, using Visual Basic's object extension technology.

D

designer

A top-level software component that includes a dedicated design environment. The object that controls the look and feel of a user interface at design time.

design-time object

An object that is used at design time within a host's development environment. See also run-time object.

dynamic type information

Type information that changes when a user attaches code. An object can have dynamic or static type information; it has one dynamic type information object for its lifetime, regardless of how often its type information changes. See also type information.

E

embedded object

An object whose data is stored along with that of its container but that runs in the process space of its server.

event sink

An event sink implements the member functions for a set of events.

event source

An event source calls the interface that handles events.

extended object

An object that has attached code (usually code that handles events) or added properties, methods, or events.

F

form

A window or dialog box. Forms are containers for controls. A multiple-document interface (MDI) form can also act as a container for child forms (forms contained within MDI forms) and some controls. An ActiveX designer may work like a form, although it does not have to be a form.

frame

A visual enclosure for a form or ActiveX designer in the user interface. The frame looks like a window and defines window-like properties, methods, and events.

H

host application

The application, such as Visual Basic, into which an ActiveX designer is loaded.

HRESULT

An opaque result handle defined to be zero for a successful return from a function and nonzero if error or status information is to be returned.

I

in-process server

A component is said to run in process when it provides objects from within another component or application's process space. Examples include dynamic-link libraries and controls.

insertable object

An object that can be embedded or linked. Also called OLE object.

instance

An object for which memory is allocated or which is persistent.

L

license key

A string that, if present, allows a software component to be created.

O

object

A combination of code and data that can be treated as a unit, for example a control, form, designer, or application. Each kind of object is defined by a class.

In object-oriented programming, an object is an entity that has state, behavior, and identity. An object's state consists of its attributes and the attributes' current values. An object's behavior consists of the operations that can be performed on it and the accompanying state changes. An object's identity is what you use to distinguish it from other objects.

In contrast, a COM object's behavior is defined by the interfaces it supports. A COM object's state is not explicitly specified, but is implied by its interfaces. A COM object's identity is defined by the ability to use IUnknown::QueryInterface to move between interfaces.

A COM object follows a specific model in which clients (those using an object's services) gain access to the object's data only through a pointer to an interface consisting of a set of methods (related functions). The client can then call these methods to perform desired operations.

OLE

Microsoft's object-based technology for sharing and transferring information between applications through linking and embedding.

OLE container

An OLE-aware application that can store embedded or linked objects provided by OLE servers.

P

persistent

Lasting between program sessions, or renewed when a new program session is begun.

persistent storage

Storage of a file or object in a medium such as a file system or database so that the file can be closed and then re-opened at a later time. Data can be retrieved from the file until the underlying file is deleted.

properties window

A window used to display or change properties of a selected form or control at design time. See also property page and property sheet.

property page

A grouping of properties presented as a tabbed page of a property sheet.

property set

Information describing a document, stored in a standard format so that other applications can locate and read that information. For example, a document created with a word processor can have a property set describing the author, title, and keywords.

property sheet

A special kind of dialog box (or window) through which users can modify the attributes of an external object, such as the current selection in a view. A property sheet has three main parts: the containing dialog box, one or more property pages shown one at a time, and a tab at the top of each page that the user clicks to select that page. An example of a property sheet is the Project Settings dialog box in the Microsoft Developer Studio.

R

registry key

A unique identifier assigned to each piece of information in the system registration database.

run-time object

An object that is created at run time and used as part of an executable application. See also design-time object.

S

service

For ActiveX designers, a group of interfaces that perform related tasks.. A caller asks an object for a service and, in return, receives an ActiveX object that supports the designated interfaces. Services do not delegate to their individual member interfaces, but rather to the IServiceProvider interface.

site

Object through which project items communicate with their surroundings. Each project item has one site.

static type information

Type information that remains the same throughout the lifetime of the object. See also type information.

subobject

An object defined, created, and managed by another object.

T

type information

Type information is the Automation standard for describing exposed objects, properties, and methods to an application or programming tool that accesses an exposed object. You provide type information in one of two ways: (a) As a type library written in Microsoft Object Description Language (ODL) and compiled by the MkTypLib utility or with the Microsoft Interface Definition Language compiler, or (b) As a data structure exported at run time. See also static type information and dynamic type information.

V

visual designer

The design-time object in an ActiveX designer. Using the visual designer, users can customize objects for use in executable applications.


ActiveX Designer Samples Release Notes

About the Designers

  • Circle: A designer that shows how to implement designers with subobjects. (Right click on designer to add a subobject)

Click here to open or copy the Circle project files

  • Different Designer: Design-time portion of a designer that separates the designer & the run-time instance into separate DLLs. (Both Designer & Instance have user interfaces.)

Click here to open or copy the Different Designer project files

  • Different Instance: Run-time portion of a designer that separates the designer & the run-time instance into separate DLLs. (Both Designer & Instance have user interfaces.)

Click here to open or copy the Different Instance project files

  • Invisible Designer: Design-time portion of a designer that separates the designer & the run-time instance into separate DLLs.The run-time instance is invisible (the designer itself is visible).

Click here to open or copy the Invisible Designer project files

  • Invisible Instance: Run-time portion of a designer that separates the designer & the run-time instance into separate DLLs. The run-time instance is invisible (the designer itself is visible).

Click here to open or copy the Invisible Instance project files

Compiling and Running the Designers

To run a designer

Each of the designers listed above has a named directory in the SDK. This directory contains a Debug directory with a fully compiled ActiveX Designer DLL. These designers (and their run-time components - if any) must be registered before they are used. To register the components run: 'regserver32 <component.dll>'

To compile/modify a designer

  1. Insure that <ActiveXDesignerSDK>\Samples\Framewrk\Debug\AxfwD.lib and <ActiveXDesignerSDK>\Samples\Framewrk\Release\AxfwR.lib are on the link path for the linker. (These are the debug and release versions of the ActiveX Designer Framework. The VC++ project for these is in the Framewrk directory as VC_Framework.)

    2. Click here to open or copy the Framewrk project files
  2. Insure that the files in <ActiveXDesignerSDK>\Samples\Include\ are on the include path for the compiler.

    3. Click here to open or copy the Include project files
  3. Insure that the files in <ActiveXDesignerSDK>\Samples\Lib\<platform> are on the link path for the linker.

    4. Click here to open or copy the Library project files
  4. Load the appropriate project for the designer.
  5. Compile.

To add a property or method to a designer

  1. Add the property or method to the .odl file in the designer's directory.
  2. Run the BuildTlb.bat file in the designer's directory. (This rebuilds the type library and the associated header file).
  3. Write code to implement the property or method.
  4. Recompile.

Note

If you experience any linking errors, try the Rebuild All command on the Framewrk project listed above. Errors may occur when testing on different configurations. Doing a Rebuild All generally takes care of the problem. (Make sure to put the recompiled libraries on the link path.)


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.