"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
- Directory property. The name of the selected directory.
- Filemask property. Initialy set to *.~??.
- FileCount property. The number of files in the directory.
- Select(FileName) method. Select a file in the directory.
- DeleteSelected method. Delete all selected files.
- AllowDelete property.
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
|