Developing COM Applications
Delphi provides wizards and classes to make it easy to implement applications based on the Component Object Model (COM) from Microsoft. With these wizards, you can create COM-based classes and components to use within applications or you can create fully functional COM clients and servers that implement COM objects, Automation servers (including Active Server Objects), ActiveX controls, or ActiveForms.
This topic covers:
- COM Technologies Overview
- COM Interfaces
- COM Servers
- COM Clients
COM Technologies Overview
COM is a language-independent software component model that enables interaction between software components and applications running on a Windows platform. The most important aspect of COM is that it enables communication between components, between applications, and between clients and servers through clearly defined interfaces. Interfaces provide a way for clients to ask a COM component which features it supports at run time. To provide additional features for your component, you simply add an additional interface for those features.
Applications can access the interfaces of COM components that exist on the same computer as the application or that exist on another computer on the network using a mechanism called Distributed COM (DCOM).
COM is both a specification and an implementation. The COM specification defines how objects are created and how they communicate with each other. According to this specification, COM objects can be written in different languages, run in different process spaces and on different platforms. As long as the objects conform to the written specification, they can communicate. This allows you to integrate legacy code as a component with new components implemented in object-oriented languages.
The COM implementation is built into the Win32 subsystem, which provides a number of core services that support the specification. The COM library contains a set of standard interfaces that define the core functionality of a COM object, and a small set of API functions for creating and managing COM objects.
When you use Delphi wizards and VCL objects in your application, you are using Delphi's implementation of the COM specification. In addition, Delphi provides some wrappers for COM services for those features that it does not implement directly (such as Active Documents). You can find these wrappers defined in the ComObj unit and the API definitions in the AxCtrls unit.
Note: The Delphi interfaces and language follow the COM specification. Delphi implements objects conforming to the COM spec using a set of classes called the Delphi ActiveX framework (DAX). These classes are found in the AxCtrls, OleCtrls, and OleServer units. In addition, the Delphi interface to the COM API is in ActiveX.pas and ComSvcs.pas.
COM clients communicate with objects through COM interfaces. Interfaces are groups of logically or semantically related routines which provide communication between a provider of a service (server object) and its clients.
For example, every COM object must implement the basic interface, IUnknown. Through a routine called QueryInterface in IUnknown, clients can request other interfaces implemented by the server.
Objects can have multiple interfaces, where each interface implements a feature. An interface provides a way to tell the client what service it provides, without providing implementation details of how or where the object provides this service.
Key aspects of COM interfaces are as follows:
- Once published, interfaces do not change. You can rely on an interface to provide a specific set of functions. Additional functionality is provided by additional interfaces.
- By convention, COM interface identifiers begin with a capital I and a symbolic name that defines the interface, such as IMalloc or IPersist.
- Interfaces are guaranteed to have a unique identification, called a Globally Unique Identifier (GUID), which is a 128-bit randomly generated number. Interface GUIDs are called Interface Identifiers (IIDs). This eliminates naming conflicts between different versions of a product or different products.
- Interfaces are language independent. You can use any language to implement a COM interface as long as the language supports a structure of pointers, and can call a function through a pointer, either explicitly or implicitly.
- Interfaces are not objects themselves, they provide a way to access an object. Therefore, clients do not access data directly, they access data through an interface pointer. Windows 2000 adds another layer of indirection, known as an interceptor, through which it provides COM+ features such as just-in-time activation and object pooling.
- Interfaces are always inherited from the base interface, IUnknown.
- Interfaces can be redirected by COM through proxies to enable interface method calls to call between threads, processes, and networked machines, all without the client or server objects ever being aware of the redirection.
The IUnknown interface
All COM objects must support the fundamental interface, called IUnknown, a typedef to the base interface type IInterface. IUnknown contains the following routines:
- QueryInterface: Provides pointers to other interfaces that the object supports.
- AddRef and Release: Simple reference counting methods that keep track of the object's lifetime so that an object can delete itself when the client no longer needs its service.
Clients obtain pointers to other interfaces through the IUnknown method, QueryInterface. QueryInterface knows about every interface in the server object and can give a client a pointer to the requested interface. When receiving a pointer to an interface, the client is assured that it can call any method of the interface.
Objects track their own lifetime through the IUnknown methods, AddRef and Release, which are simple reference counting methods. As long as the reference count of an object is nonzero, the object remains in memory. Once the reference count reaches zero, the interface implementation can safely dispose of the underlying object.
COM Interface Pointers
An interface pointer is a pointer to an object instance that points, in turn, to the implementation of each method in the interface. The implementation is accessed through an array of pointers to these methods, which is called a vtable. Vtables are similar to the mechanism used to support virtual functions in Delphi. Because of this similarity, the compiler can resolve calls to methods on the interface the same way it resolves calls to methods on Delphi classes.
The vtable is shared among all instances of an object class, so for each object instance, the object code allocates a second structure that contains its private data. The client's interface pointer, then, is a pointer to the pointer to the vtable.
In Windows 2000 and subsequent versions of Windows, when an object is running under COM+, another level of indirection is provided between the interface pointer and the vtable pointer. The interface pointer available to the client points at an interceptor, which in turn points at the vtable. This allows COM+ to provide such services as just-in-time activation, where the server can be deactivated and reactivated dynamically in a way that is opaque to the client. To achieve this, COM+ guarantees that the interceptor behaves as if it were an ordinary vtable pointer.
A COM server is an application or a library that provides services to a client application or library. A COM server consists of one or more COM objects, where a COM object is a set of properties and methods.
Clients do not know how a COM object performs its service; the object's implementation remains hidden. An object makes its services available through its interfaces as described previously.
In addition, clients do not need to know where a COM object resides. COM provides transparent access regardless of the object's location.
When a client requests a service from a COM object, the client passes a class identifier (CLSID) to COM. A CLSID is simply a GUID that identifies a COM object. COM uses this CLSID, which is registered in the system registry, to locate the appropriate server implementation. Once the server is located, COM brings the code into memory, and has the server create an object instance for the client. This process is handled indirectly, through a special object called a class factory (based on interfaces) that creates instances of objects on demand.
As a minimum, a COM server must perform the following:
- Register entries in the system registry that associate the server module with the class identifier (CLSID).
- Implement a class factory object, which creates another object of a particular CLSID.
- Expose the class factory to COM.
- Provide an unloading mechanism through which a server that is not servicing clients can be removed from memory.
COM clients are applications that make use of a COM object implemented by another application or library. The most common types are Automation controllers, which control an Automation server and ActiveX containers, which host an ActiveX control.
There are two types of COM clients, controllers and containers. Controllers launch the server and interact with it through its interface. They request services from the COM object or drive it as a separate process. Containers host visual controls or objects that appear in the container's user interface. They use predefined interfaces to negotiate display issues with server objects. It is impossible to have a container relationship over DCOM; for example, visual controls that appear in the container's user interface must be located locally. This is because the controls are expected to paint themselves, which requires that they have access to local GDI resources.
The task of writing these two types of COM client is remarkably similar: The client application obtains an interface for the server object and uses its properties and methods. Delphi makes it easier for you to develop COM clients by letting you import a type library or ActiveX control into a component wrapper so that server objects look like other VCL components. Delphi lets you wrap the server CoClass in a component on the client, which you can even install on the Component palette. Samples of such component wrappers appear on two pages of the Component palette, sample ActiveX wrappers appear on the ActiveX page, and sample Automation objects appear on the Servers page.
Even if you do not choose to wrap a server object in a component wrapper and install it on the Component palette, you must make its interface definition available to your application. To do this, you can import the server's type information.
Clients can always query the interfaces of a COM object to determine what it is capable of providing. All COM objects allow clients to request known interfaces. In addition, if the server supports the IDispatch interface, clients can query the server for information about what methods the interface supports. Server objects have no expectations about the client using its objects. Similarly, clients do not need to know how an object provides the services, they simply rely on server objects to provide the services they describe in their interfaces.
As COM has evolved, it has been extended beyond the basic COM services. COM serves as the basis for other technologies such as Automation, ActiveX controls, Active Documents, and Active Directories. In addition, when working in a large, distributed environment, you can create transactional COM objects. Prior to Windows 2000, these objects were not an architectural part of COM, but ran in the Microsoft Transaction Server (MTS) environment. As of Windows 2000, this support is integrated into COM+. Delphi provides wizards to easily implement applications that use the above technologies in the Delphi environment.
Automation refers to the ability of an application to control the objects in another application programmatically, such as a macro that can manipulate more than one application at the same time. The server object being manipulated is called the Automation object, and the client of the Automation object is referred to as an Automation controller. Automation can be used on in-process, local, and remote servers.
Automation is defined by two major points:
- The Automation object defines a set of properties and commands, and describes their capabilities through type descriptions. In order to do this, it must have a way to provide information about its interfaces, the interface methods, and the arguments to those methods. Typically, this information is available in a type library. The Automation server can also generate type information dynamically when queried via its IDispatch interface.
- Automation objects make their methods accessible so that other applications can use them. For this, they implement the IDispatch interface. Through this interface an object can expose all of its methods and properties. Through the primary method of this interface, the object's methods can be invoked, once having been identified through type information.
Developers often use Automation to create and use non-visual OLE objects that run in any process space, because the Automation IDispatch interface automates the marshaling process. Automation does, however, restrict the types that you can use.
Active X Controls
Delphi wizards allow you to easily create ActiveX controls. ActiveX is a technology that allows COM components, especially controls, to be more compact and efficient. This is especially necessary for controls that are intended for Intranet applications, which need to be downloaded by a client before they are used.
ActiveX controls are visual controls that run only as in-process servers, and can be plugged into an ActiveX control container application. They are not complete applications in themselves, but can be thought of as already written OLE controls that are reusable in various applications. ActiveX controls have a visible user interface, and rely on predefined interfaces to negotiate I/O and display issues with their host containers.
ActiveX controls make use of Automation to expose their properties, methods, and events. Features of ActiveX controls include the ability to fire events, bind to data sources, and support licensing.
One use of ActiveX controls is on a Web site as interactive objects in a Web page. As such, ActiveX is a standard that targets interactive content for the World Wide Web, including the use of ActiveX Documents used for viewing non-HTML documents through a Web browser. For more information about ActiveX technology, see the Microsoft ActiveX Web site.
Active Documents (previously referred to as OLE documents) are a set of COM services that support linking and embedding, drag-and-drop, and visual editing. Active Documents can seamlessly incorporate data or objects of different formats, such as sound clips, spreadsheets, text, and bitmaps.
Unlike ActiveX controls, Active Documents are not limited to in-process servers; they can be used in cross-process applications.
Unlike Automation objects, which are almost never visual, Active Document objects can be visually active in another application. Thus, Active Document objects are associated with two types of data: presentation data, used for visually displaying the object on a display or output device, and native data, used to edit an object.
Active Document objects can be document containers or document servers. While Delphi does not provide an automatic wizard for creating Active Documents, you can use the VCL class, Vcl.OleCtnrs.TOleContainer, to support linking and embedding of existing Active Documents.
You can also use Vcl.OleCtnrs.TOleContainer as a basis for an Active Document container. To create objects for Active Document servers, use the COM object wizard and add the appropriate interfaces, depending on the services the object needs to support. For more information about creating and using Active Document servers, see the Microsoft ActiveX Web site.
Note: While the specification for Active Documents has built-in support for marshaling in cross-process applications, Active Documents do not run on remote servers because they use types that are specific to a system on a given machine such as window handles, menu handles, and so on.
Delphi uses the term "transactional objects" to refer to objects that take advantage of the transaction services, security, and resource management supplied by Microsoft Transaction Server (MTS) (for versions of Windows prior to Windows 2000) or COM+ (for Windows 2000 and later). These objects are designed to work in a large, distributed environment.
The transaction services provide robustness so that activities are always either completed or rolled back. The server never partially completes an activity. The security services allow you to expose different levels of support to different classes of clients. The resource management allows an object to handle more clients by pooling resources or keeping objects active only when they are in use. To enable the system to provide these services, the object must implement the IObjectControl interface. To access the services, transactional objects use an interface called IObjectContext, which is created for them by MTS or COM+.
Under MTS, the server object must be built into a DLL library, which is then installed in the MTS runtime environment. That is, the server object is an in-process server that runs in the MTS runtime process space. Under COM+, this restriction does not apply because all COM calls are routed through an interceptor. To clients, the difference between MTS and COM+ is transparent.
MTS or COM+ servers group transactional objects that run in the same process space. Under MTS, this group is called an MTS package, while under COM+ it is called a COM+ application. A single machine can be running several different MTS packages (or COM+ applications), where each one is running in a separate process space.
To clients, the transactional object may appear like any other COM server object. The client does not need know about transactions, security, or just-in-time activation unless it is initiating a transaction itself.
Both MTS and COM+ provide a separate tool for administering transactional objects. This tool lets you configure objects into packages or COM+ applications, view the packages or COM+ applications installed on a computer, view or change the attributes of the included objects, monitor and manage transactions, make objects available to clients, and so on. Under MTS, this tool is the MTS Explorer. Under COM+ it is the COM+ Component Manager.
Type libraries provide a way to get more type information about an object than can be determined from an object's interface. The type information contained in type libraries provides needed information about objects and their interfaces, such as what interfaces exist on what objects (given the CLSID), what member functions exist on each interface, and what arguments those functions require.
You can obtain type information either by querying a running instance of an object or by loading and reading type libraries. With this information, you can implement a client which uses a desired object, knowing specifically what member functions you need, and what to pass those member functions.
Clients of Automation servers, ActiveX controls, and transactional objects expect type information to be available. All of Delphi's wizards generate a type library automatically, although the COM object wizard makes this optional. You can view or edit this type information by using the Type Library Editor.