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