"Hello automation object"

In this chapter I will create a simple automation server. This will expose the functionality of a form in an existing  Delphi application to VBscript.

The existing application

Let's take an average Delphi app. It's a small custom file-manager to clean up Delphi backup files.  I have created a small form,  added a DirectoryOutline (from the Delphi6 samples page) for browsing the directories and added a FileListBox to show the files in the directory. By setting the mask property of the filelistbox to *.~?? it will filter out the desired files. A button will actually delete the files, there is a safety checkbox to enable the deletion and finally an actionlist to structure the program.

All together it's just a few clicks and a couple of lines of code. That's what makes Delphi such a delicious tool. Now wouldn't it be nice to be able to use this tool in VBscript ? 

What functionality will be exposed ?

Think first program late, the saying goes. Let's make a list of the functionality which will be exposed to automation clients

The properties and the parameter of the method have a type.  The number of available types to an automation object is limited, the main reason is that all automation clients do have to understand the types used.

Wrapping the functionality in an automation object

I will add an automation object to the app using the Delphi wizard. This wizard is started using the menu File | New | Other. Then select the ActiveX tab and choose Automation Object. The wizard will ask for a Co(m)ClassName, I will name the class FileZapper. Now the wizard is completed and the Type Library editor pops up.

 

A typelibrary describes an automation object. A client will use this typelibrary to find out which automation objects can be created and what methods and properties they support. 

We now have a  CoClass called FileZapper which implements the interface IFileZapper. (background). It is this interface that clients will use, to this interface I will add methods and properties.

Creating methods and properties

Selecting the Ifilezapper interface in the typelibrary editor enables the "New method" and the "New property" button. Let's start with the directory property. Type is defaulted to long, that's an integer value. Directory is a string. There is quite a choice in the combobox, to choose an automation compatible string pick BSTR.

FileMask is another BSTR property.

Both properties show two entries in the list, one is a property getter, the other a property setter (propertyput they call it here). Automation properties are very much alike Delphi properties, they always have a function to set or get the value. 

FileCount is a readonly property, it is a count of the number of existing files. There is a dropdown beside the new property button, select a read-only property now. If you made a mistake you can just delete the propertyput.

Next comes the Select method. It would be handy if this method had a (boolean) return value to check whether it was successful.  After creating the method select the parameters tab. The filename will be passed in the way we are used to. The result of the method will be passed back in the last parameter. To get a boolean result we take Variant_Bool as type and have to add an * to it to indicate that it is a pointer to a parameter. In the modifier box you have to select the out and the retval flags.

The deleteselected method should provide no problem now.

Allowdelete will be just a another property, the type will be variant_Bool.

The Delphi code

In the typelibrary editor we have now filled in what our FileZapper object looks like. The next step will be to work on the real code itself in Delphi. Hitting the refresh button will generate (or update) a Delphi unit.

In this unit a class tFileZapper is declared

type
TFileZapper = class(TAutoObject, IFileZapper)
protected
    function Get_Directory: WideString; safecall;
    function Get_FileCount: Integer; safecall;
    function Get_FileMask: WideString; safecall;
    procedure Set_Directory(const Value: WideString); safecall;
    procedure Set_FileMask(const Value: WideString); safecall;
    function Get_AllowDelete: WordBool; safecall;
    function Select(const FileName: WideString): WordBool; safecall;
    procedure DeleteSelected; safecall;
    procedure Set_AllowDelete(Value: WordBool); safecall;
{ Protected declarations }
end;

tFileZapper inherits from tAutoObject, that is the VCL class which provides the basic automation object support.  tFileZapper declares as well to provide an implementation for IfileZapper, that is our automation interface. All methods, property getters and property setters we just entered in the typelibrary can be found. Notice how the Select method has been translated to a Pascal function.

Another interesting piece of code can be found in the bottom of the unit

initialization
TAutoObjectFactory.Create(ComServer, TFileZapper, Class_FileZapper,
ciMultiInstance, tmApartment);
end.

What does this code do ? It is executed as soon as the app is loaded and creates an instance of an object factory. tAutoObjectfactory is a vcl class. The COM subsystem communicates with this classfactory and the classfactory creates the actual automation objects. That's why it needs a class type as parameter.

Filling in the implementation

Now we have empty method skeletons and we can use them do wrap up the desired implementation. Adding FileManagerForm to the uses clause makes it possible to talk to the form.

Let's look at some examples.

function TFileZapper.Get_Directory: WideString;
begin
    result:= Form1.FileListBox1.Directory;
end;

The directory can be read out of the component. In the component directory is a Delphi string, in the automation object directory is a Widestring. All conversions needed are handled by the Delphi compiler.

procedure TFileZapper.Set_FileMask(const Value: WideString);
begin
    Form1.FileListBox1.Mask:= Value;
end;

The trick works the other way round as well. Just as:

procedure TFileZapper.Set_AllowDelete(Value: WordBool);
begin
    Form1.CheckBox1.Checked:= Value;
end;

The actionlist in the app is going to prove extra handy, I can use it for the implementation of the Deleteselected method

procedure TFileZapper.DeleteSelected;
begin
Form1.ActionDeleteFileExecute(nil);
end;

I will have to do some work for the Select method as there was no counterpart yet in the existing app.

function TFileZapper.Select(const FileName: WideString): WordBool;
var I : integer;
begin
for i:= 0 to Form1.FileListBox1.items.count - 1 do
    if CompareText(Form1.FileListBox1.Items[i], FileName) = 0 then
        Form1.FileListBox1.Selected[i]:= True;
end;

The implementation of my automation object did not cost me a lot of programming, as planned it is just a wrapping up of existing code.

Using the application through automation

I will now write a piece of VBscript to start automating the object

dim MyManager 

set MyManager = CreateObject("FileManager.FileZapper")
MyManager.Directory = "C:\"
MyManager.Filemask = "*.htm"
MyManager.AllowDelete = True
MsgBox "Click OK to exit" 

This script creates a FileManager.FileZapper automation object. Which will start the application. The script sets a couple of properties, which will be reflected on the form. The call to  msgbox prevents the script from completing. As soon as the script is completed, the automation object will destroy itself and the application will terminate.

Where are we ?

We have seen the basics of creating an automation object and how to give it properties and methods. We have seen that an automation object is just a wrapper around any functionality you can imagine in Delphi. We have seen that this Delphi functionality can be used in a total different environment.

What's next