Using automation objects

This chapter describes the way an automation client can find an automation server, have it create an object and how the automation object is freed again. Two different ways, early bound and late bound will be described. On the way the relation between these two and the role of the type library will be explained.

Registering the object

An automation server will have to register itself in the Windows registry. That is the place where automation clients will be able to find the info they need to work with the server. A stand alone, out of process, server registers itself every time it is run. An in-process server in a DLL can be registered by the Delphi IDE (In the menu Run | Register ActiveX server) or with the command-line utility RegSvr32.exe which is included in Windows.

RegSvr32 FileManager.dll

ProgId

Having registered the  server you will find the FileManager.FileZapper server in the registry

Under the key classes root there is this key named FileManager.FileZapper. This is the name of the automation server I created in the introduction chapter and it is the name of the server I created in the chapter on the server DLL. It is called the progId of the server, a human readable identifier.

In script this progId string was passed to the CreateOleObject function

Set MyManager = CreateObject("FileManager.FileZapper")

The registry does not give me more info here. The only value is this thing called a Clsid

ClassID

Clsid stands for class ID. Every COM  class has an unique key which identifies the class to the COM subsystem. The key is encoded in a GUID, a Globally Unique IDentifier.

In the registry you will find a huge subtree CLSID under CLASSES_ROOT. When I look up the given GUID the following information is shown. 

The ClassID has several subkeys, the first one is InprocServer32. It's value is the name and location of the automation DLL built in the previous chapter. We now know how the COM subsystem, given a progid, can find the server.

The second subkey repeats the ProgId FileManager.FileZapper.

Who is the real FileManager.FileZapper ?

Following the ProgID I found the inproc automation server. But what happened to the stand alone server with the same name I build in the first chapter ?

By running it, it will again register itself. Having done that I will go back to the registry editor to look for the ProgID again.

There is still a CLSID as key value, but it has a different value. Looking up this GUID in the CLSID subtree leads me to the server

And there is my standalone server again. The key is named LocalServer32 (instead of InprocServer32) and it describes the name and location of my .exe.

If I now register the DLL again the ProgId will lead again to the in-process server. So a progID is not really a good way to identify a server. In this case I gave the two servers the same name on purpose, no need saying that it would be a coincidence if nobody ever gave two different servers the same name by accident.

Late binding

I used the progid in a script to create the server object. What I will get as a result will be a complete surprise. And what methods or properties that server would have would be a mystery as well. At runtime the script engine will do a lookup on every method or property before actually executing it. This is called late binding.

This is of course very flexible but needless to say it will be slow. Besides there is no way to check code at design time. A simple typing error could lead to a program not working at all.

Early binding

It would be a good idea for the automation client to directly work with the ClassID and just forget the ProgID. And that's just what modern software does. It uses the typelibrary of our server to find out everything it wants to know.

Let's go back to Word using my server in a macro. In VBA there is the menu option Tools | References. It adds a refrence to a typelibrary to the current VBA project.

  

The filemanager library appears twice in the list. Word does see both the servers, which of the two servers is meant can be seen in the bottom of the dialog. I will use the in-proc DLL, as it will be faster. 

The Word macro can now be written as:

The server variabele is  now be typed as being a (filemanager.)filezapper

Dim MyManager As New FileZapper

The new statement will create the filezapper object, there is no more need for the CreateOleobject function or the ProgId.

The VBA environment does a lot with the information in the typelibrary, as you can see it shows all its properties and methods in the code completion. Having all information at hand at the time writing the code is called early binding. The former version of the macro, working with an untyped server variable, used late binding. 

Type library

The information necessary for early binding is found in the typelibrary. This typelibrary can also be found using the registry. 

In the second subkey of the CLSID there is a GUID pointing to a typelib. I will use this to start looking in the subtree CLASSES_ROOT\Typelib.

The subtree holds a variety of information, I am using win32 and as key value I can see the name and location of the in-process DLL.

A typelibrary can be stored as a stand alone .tlb file, but it can be stored inside the server as well. Delphi does the latter, it includes the library as a resource in the code. Which is very nice, because now every automation server contains a description of itself in a format readable to any automation client. 

Using Delphi as an early binding automation client

To get a better idea what is going on I will use Delphi as an early bound client for the server. After creating a new project I will import a type library. You can find this in the IDE menu as Project | Import Type libarary. 

This looks familiar, again both Filemanger libraries appear. I will uncheck the Generate Component Wrapper checkbox, click Create Unit and I will end up with a FileManger_TLB.pas. This unit looks very much like the unit with the same name in the server project, the only differences are the (generated) comments. But Delphi did generate this one out of the typelib info in the filemanager server. Delphi did not even know it was inspecting a piece of Delphi code, if the server had been written in (say) C++ the result would have been just the same.

The filezapper automation class has two identities, the first one is the class Co(m)FIleZapper. This class has only one method, a class function :

class function CoFileZapper.Create: IFileZapper;
begin
    Result := CreateComObject(CLASS_FileZapper) as IFileZapper;
end;

The other identity is the IfileZapper interface. Here we can see the methods, property-getters and property-setters of the server.

IFileZapper = interface(IDispatch)
['{2E2FC5E0-5C0E-4C4F-8CC1-D9F6C5A92BA6}']
    function Get_Directory: WideString; safecall;
    procedure Set_Directory(const Value: WideString); safecall;
    function Get_FileMask: WideString; safecall;
    procedure Set_FileMask(const Value: WideString); safecall;
    function Get_FileCount: Integer; safecall;
    function Select(const FileName: WideString): WordBool; safecall;
    procedure DeleteSelected; safecall;
    function Get_AllowDelete: WordBool; safecall;
    procedure Set_AllowDelete(Value: WordBool); safecall;
    property Directory: WideString read Get_Directory write Set_Directory;
    property FileMask: WideString read Get_FileMask write Set_FileMask;
    property FileCount: Integer read Get_FileCount;
    property AllowDelete: WordBool read Get_AllowDelete write Set_AllowDelete;
end;

To work with the server I will declare an fFileZapper variable, of type IFileZapper. And now I can start coding.

fFileZapper is initialized by a call to CoFileZapper.Create. I have dedicated a whole chapter on the differences and similarities between Interfaces and classes,  for the moment it will do to say that you can use an interface as an object variable, the main difference is the way it is created and destroyed.

Having an interface to the server object, the properties can be set straight out of the Delphi edit-boxes. And you can see that code completion works.

Stopping the server

We have seen many different ways to start the automation server. But how can it be stopped ? Being an object you would say by destroying this object. But the client cannot destroy the server object. The client has an interface to the object and this interface does not have any destructors. Even worse, the client should never destroy the object, it is possible that more then one client is working with the same object.

The server will have to destroy itself. The server can do this because it keeps track of the number of clients which are using it. More on this in the chapter on interfaces and objects. For the client it is sufficient to finalize the interface variable. 

procedure TForm1.StopServerExecute(Sender: TObject);
begin
   fFileZapper:= nil;
end;

Firing the Startserver action will pop up the form of the Filemanager server, after firing the stopserver action the form is gone again. As nobody is using the server object any longer, it destroys itself, freeing the form in the beforedestruction method.

In VBA or VBS(cript) this is coded as

Set MyServer = nothing

You do not explicitly have to reset the server variable. When the program ends, the variable goes out of scope. Which has the same result as resetting the variable. So when I just stop the client program, the server form disappears as well. 

Where are we ? 

We have seen how a client starts an automation server. In the late bound way all is done at runtime, in the early bound way there is a lot of information in the typelibrary which is used by the compiler. Not every client environment support early binding, the support in Delphi is (of course) very good.  After creating a server object the client cannot really access the server object itself. Instead it has an interface to the object. The server object will destroy itself the moment no more clients have an interface to it.

What's next ?

  • Understanding objects and interfaces