An Automation server is an application that exposes its functionality for client applications, called Automation controllers, to use. Controllers can be any applications that support Automation, such as Delphi, Visual Basic, or C++Builder. An Automation server can be an application or library.
This chapter shows how to create an Automation server using the Delphi Automation server wizard. With this, you can expose properties and methods of an existing application for Automation control.
Here are the steps for creating an Automation server from an existing application:
For information about creating an Automation controller, see "Creating an Automation controller." For general information about the COM technologies, see "Overview of COM technologies."
An Automation object is an ObjectPascal class descending from TAutoObject that supports OLE Automation, exposing itself for other applications to use. Since TAutoObject supports OLE Automation, any object derived from it gets Automation support automatically. You create an Automation object using the Automation Object wizard.
Before you create an Automation object, create or open the project for an application containing functionality that you want to expose. The project can be either an application or ActiveX library, depending on your needs.
To display the Automation wizard:
In the wizard dialog, specify the following:
Specify the class whose properties and methods you want to expose to client applications. (Delphi prepends a T to this name.) | |
Specify an instancing mode to indicate how your Automation server is launched. For details, see"COM object instancing types". Note: When your Automation object is used only as an in-process server, instancing is ignored. | |
Choose the threading model to indicate how client applications can call your object's interface. This is the threading model that you commit to implementing in the Automation object. For more information on threading models, see"Choosing a threading model". Note: The threading model you choose determines how the object is registered. You must make sure that your object implementation adheres to the model selected. | |
Check this box to tell the wizard to implement a separate interface for managing events of your Automation object. |
When you complete this procedure, a new unit is added to the current project that contains the definition for the Automation object. In addition, the wizard adds a type library project and opens the type library. Now you can expose the properties and methods of the interface through the type library as described next.
The Automation object implements a dual interface, which supports both early (compile-time) binding through the VTable and late (runtime) binding through the IDispatch interface. For more information, see "Dual interfaces".
The Automation wizard automatically generates event code if you check the option, Generate Support Code in the Automation Object wizard dialog box.
For a server to support events, it provides a definition of an outgoing interface which is implemented by a client. The client determines what outgoing interfaces are available by querying the server's IConnectionPointContainer interface, and it uses methods provided by the server's IConnectionPoint interface to pass the server a pointer to the client's implementation of the events (known as a sink). The server must maintain a list of such sinks and call methods on them when an event occurs. When you select Generate Event Support Code, Delphi automatically generates the code necessary to support IConnectPoint and IConnectPointContainer.
When you build the Automation server with the Automation wizard, it automatically generates a type library, which provides a way for host applications to find out what the object can do. To expose the properties, methods, and events of an application, you modify the Automation server's type library as described below.
A property is a member function that sets or returns information about the state of the object, such as color or font. For example, a button control might have a property declared as follows:
property Caption: WideString;
To expose a property for Automation,
The default interface should be the name of the Automation object preceded by the letter "I". To determine the default, in the Type Library editor, choose the CoClass and Implements tab, and check the list of implemented interfaces for the one marked, "Default."
A definition and dummy implementation for the property is inserted into the Automation object's unit files.
A method can be a procedure or a function. To expose a method for Automation,
The default interface should be the name of the Automation object preceded by the letter "I". To determine the default, in the Type Library editor, choose the CoClass and Implements tab, and check the list of implemented interfaces for the one marked, "Default."
A definition and dummy implementation for the method is inserted into the Automation object's unit files.
To expose an event for Automation,
The wizard creates an Automation object that includes an Events interface.
The Events interface should be the name of the Automation object preceded by the letter "I" and succeeded by the word "Events."
A definition and dummy implementation for the event is inserted into the Automation object's unit files.
unit ev;
interface
uses
ComObj, AxCtrls, ActiveX, Project1_TLB;
type
TMyAutoObject = class (TAutoObject,IConnectionPointContainer, IMyAutoObject)
private
.
.
.
public
prodecure Initialize; override;
procedure EventHandler; { Add an event handler}
procedure TMyAutoObject.Initialize;
begin
inherited Initialize;
FConnectionPoints:= TConnectionPoints.Create(Self);
if AutoFactory.EventTypeInfo <> nil then
FConnectionPoints.CreateConnectionPoint (AUtoFactory.EventIID,
ckSingle, EventConnect);
OnEvent = EventHandler; { Assign an event to the event handler }
end;
procedure TMyAutoObject.EventHandler;
begin
if FEvents <> nil then FEvents.MyEvent; { Call method implemented by the sink.}
end;
Press F1 anywhere in the Type Library Editor to get more information on using the editor.
You can register the Automation server as an in-process or an out-of-process server. For more information on the server types, see"In-process, out-of-process, and remote servers".
Note: When you want to remove the Automation server from your system, it is recommended that you first unregister it, removing its entries from the Windows registry.
To register an in-process server (DLL or OCX),
To unregister an in-process server,
To register an out-of-process server,
You can set command-line options with the Run|Parameters dialog box.
To unregister an out-of-process server,
To test and debug an Automation server,
The Automation server pauses when the breakpoints are reached.
Delphi wizards implement the dual interface by default, which means that the Automation object supports both
A dual interface is a custom interface and a dispinterface at the same time. It is implemented as a COM VTable interface that derives from IDispatch. For those controllers that can access the object only at runtime, the dispinterface is available. For objects that can take advantage of compile-time binding, the more efficient VTable interface is used.
Dual interfaces offer the following combined advantages of VTable interfaces and dispinterfaces:
The following diagram depicts the IMyInterface interface in an object that supports a dual interface named IMyInterface. The first three entries of the VTable for a dual interface refer to the IUnknown interface, the next four entries refer to the IDispatch interface, and the remaining entries are COM entries for direct access to members of the custom interface.
Automation controllers are clients that use the COM IDispatch interface to access the COM server objects. The controller must first create the object, then query the object's IUnknown interface for a pointer to its IDispatch interface. IDispatch keeps track of methods and properties internally by a dispatch identifier (dispID), which is a unique identification number for an interface member. Through IDispatch, a controller retrieves the object's type information for the dispatch interface and then maps interface member names to specific dispIDs. These dispIDs are available at runtime, and controllers get them by calling the IDispatch method, GetIDsOfNames.
Once it has the dispID, the controller can then call the IDispatch method, Invoke, to execute the appropriate code (property or method), packaging the parameters for the property or method into one of the Invoke parameters. Invoke has a fixed compile-time signature that allows it to accept any number of arguments when calling an interface method.
The Automation object's implementation of Invoke must then unpackage the parameters, call the property or method, and be prepared to handle any errors that occur. When the property or method returns, the object passes its return value back to the controller.
This is called late binding because the controller binds to the property or method at runtime rather than at compile time.
Custom interfaces are user-defined interfaces that allow clients to invoke interface methods based on their order in the VTable and knowledge of the argument types. The VTable lists the addresses of all the properties and methods that are members of the object, including the member functions of the interfaces that it supports. If the object does not support IDispatch, the entries for the members of the object's custom interfaces immediately follow the members of IUnknown.
If the object has a type library, you can access the custom interface through its VTable layout, which you can get using the Type Library editor. If the object has a type library and also supports IDispatch, a client can also get the dispIDs of the IDispatch interface and bind directly to a VTable offset. Delphi's type library importer (TLIBIMP) retrieves dispIDs at import time, so clients that use dispinterface wrappers can avoid calls to GetIDsOfNames; this information is already in the _TLB file. However, clients still need to call Invoke.
For out-of-process and remote servers, you must consider how COM marshals data outside the current process. You can provide marshaling:
Function result and parameter types of the methods declared in dual and dispatch interfaces must be Automation-compatible types. The following types are OLE Automation-compatible:
The ActiveX control and ActiveForm wizards create these helper objects automatically when needed. To use the helper objects, call the global routines, GetOleFont, GetOleStrings, GetOlePicture, respectively.
For an interface to support automatic marshaling, the following restrictions apply. When you edit your Automation object using the type library editor, the editor enforces these restrictions:
Note: One way to bypass the Automation types restrictions is to implement a separate IDispatch interface and a custom interface. By doing so, you can use the full range of possible argument types. This means that COM clients have the option of using the custom interface, which Automation controllers can still access. In this case, though, you must implement the marshaling code manually.
Typically, you will use automatic marshaling in your out-of-process and remote servers because it is easier--COM does the work for you. However, you may decide to provide custom marshaling if you think you can improve marshaling performance.
pubsweb@inprise.com
Copyright © 1999, Inprise Corporation. All rights reserved.