Chapter 47
Creating an Automation server

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

Creating an Automation object for an application

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:

  1. Choose File|New.
  2. Select the tab labeled, ActiveX.
  3. Double-click the Automation Object icon.

In the wizard dialog, specify the following:

CoClass Name

Specify the class whose properties and methods you want to expose to client applications. (Delphi prepends a T to this name.)

Instancing

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.

Threading Model

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.

Generate event support code

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"

Managing events in your Automation object

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.

Exposing an application's properties, methods, and events

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.

Exposing a property for Automation

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,

  1. In the type library editor, select the default interface for the Automation object.

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

  2. To expose a read/write property, click the Property button on the toolbar; otherwise, click the arrow next to the Property button on the toolbar, and then click the type of property to expose.
  3. In the Attributes pane, specify the name of the property.
  4. In the Parameters pane, specify the property's return type and add the appropriate parameters.
  5. On the toolbar, click the Refresh button.

    A definition and dummy implementation for the property is inserted into the Automation object's unit files.

  6. In the dummy implementation for the property, add code (between try and finally statements) that provides the expected functionality. In many cases, this code will simply call an existing function inside the application.

Exposing a method for Automation

A method can be a procedure or a function. To expose a method for Automation,

  1. In the Type Library editor, select the default interface for the Automation object.

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

  2. Click the Method button.
  3. In the Attributes pane, specify the name of the method.
  4. In the Parameters pane, specify the method's return type and add the appropriate parameters.
  5. On the toolbar, click the Refresh button.

    A definition and dummy implementation for the method is inserted into the Automation object's unit files.

  6. In the dummy implementation for the method, add code between try and finally statements that provides the expected functionality. In many cases, this code will simply call an existing function inside the application.

Exposing an event for Automation

To expose an event for Automation,

  1. In the Automation wizard, check the box, Generate event support code.

    The wizard creates an Automation object that includes an Events interface.

  2. In the Type Library editor, select the Events interface for the Automation object.

    The Events interface should be the name of the Automation object preceded by the letter "I" and succeeded by the word "Events."

  3. Click the Method button from the Type Library toolbar.
  4. In the Attributes pane, specify the name of the method, such as MyEvent.
  5. On the toolbar, click the Refresh button.

    A definition and dummy implementation for the event is inserted into the Automation object's unit files.

  6. In the Code Editor, create an event handler inside the TAutoObject descendant in the Automation object class. For example,
    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}
    
  7. At the end of the Initialize method, assign an event to the event handler you just created. For example,
    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;
    
  8. Add the necessary code to call the method implemented by the sink. For example, provide the following code substituting the name of your event for "MyEvent."
    procedure TMyAutoObject.EventHandler;
    begin
      if FEvents <> nil then FEvents.MyEvent;        { Call method implemented by the sink.}
    end;
    

Getting more information

Press F1 anywhere in the Type Library Editor to get more information on using the editor.

Registering an application as an Automation server

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.

Registering an in-process server

To register an in-process server (DLL or OCX),

To unregister an in-process server,

Registering an out-of-process server

To register an out-of-process server,

To unregister an out-of-process server,

Testing and debugging the application

To test and debug an Automation server,

  1. Turn on debugging information using the Compiler tab on the Project|Options dialog box, if necessary. Also, turn on Integrated Debugging in the Tools|Debugger Options dialog.
  2. For an in-process server, choose Run|Parameters, type the name of the Automation controller in the Host Application box, and choose OK.
  3. Choose Run|Run.
  4. Set breakpoints in the Automation server.
  5. Use the Automation controller to interact with the Automation server.

The Automation server pauses when the breakpoints are reached.

Automation interfaces

Delphi wizards implement the dual interface by default, which means that the Automation object supports both

Dual interfaces

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.

Figure 47.1   Dual interface VTable

Dispatch interfaces

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

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.

Marshaling data

For out-of-process and remote servers, you must consider how COM marshals data outside the current process. You can provide marshaling:

Automation compatible types

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:

Type restrictions for automatic marshaling

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.

Custom marshaling

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.