|
Automation
by Binh Ly
A script-based client is a client application that is not compiled and, hence, requires a scripting engine in order to run.
An example of a script-based client is Microsoft's Active
Server Pages (ASP) technology. If you're unfamiliar with ASP, it provides a
scripting language used to dynamically serve web pages. ASP provides constructs that
allow scripts to access and manipulate COM server objects.
If you've been following me so far, we all know that COM server objects and
interfaces are accessed using vtable binding from clients in a compiled state.
Scripts aren't compiled. More specifically, ASP scripts are interpreted by a
script engine that's part of Microsoft Internet
Information Server (IIS). Because scripts are non-compiled and cannot do vtable
binding, COM introduces another protocol to enable
script-based clients to tap into the power of COM servers.
Script-based automation is based on a protocol of runtime discovery and
execution of methods of an interface. The protocol works like this:
- A scripting-client wants to call a method X of an interface. The
client asks the server "Do you have a method named X?" If the object responds "No, I don't have a
method named X", the client knows that it cannot call that
method. If the object responds "Yes, I have a method named X", the
client knows that it can call that method.
- If a server says yes, it also gives the client a numeric identifier that
corresponds to that method (similar, but not identical, in concept to a method's position index
in a vtable). This numeric identifier is semantically unique for each method
within the interface that contains the method. The client then goes back to
the server and says "Execute the method that corresponds to this
ID", passing in the numeric identifier earlier obtained from the
server.
Quite a simple protocol isn't it? Let's look at some advantages of this
protocol:
- This protocol is very simple and can be implemented by any server. On the
client side, since the protocol is well-defined, the script
interpreter/engine should be able to implement the protocol on behalf of
the script. As the script
engine runs/interprets the client script, it simply translates object method
calls using this protocol.
- This protocol is not bound by restrictions found in vtable binding. For
instance, although it is fatal to rearrange methods in a vtable, it is not
in this protocol. Why? Because this protocol works in such a way that the
client first asks if a method is supported and gets an ID back if the server
says yes. The client then sends back this ID to server when it wants to call
that method. Based on the ID, the server determines which method to execute.
Notice that the client has no concept nor view of the ordering
of the interface methods; all it cares about is runtime discovery of 1)
obtaining an ID for a given method and 2) executing the method using the ID.
Although this protocol is not strict with method ordering, it is however
heavily dependent on method names - the client passes the method name into
server when asking for the method's ID.
In COM terminology, this protocol is called
late binding. The term "late" is used to denote that the client has no
knowledge of how to call interface methods until (late at) runtime. Contrast that with
vtable binding which requires that a client "know" the exact interface and the
exact method position within that interface to call - and that
"knowledge" is compiled into the client, not discovered at runtime.
Late-binding is made possible using a well known COM interface called
IDispatch. Based on the late binding protocol, we expect IDispatch to provide at
least 2 methods: one to determine if a given method exists (and return an ID if
it does) and another to execute a given method given its ID. IDispatch has
exactly those 2 methods:
IDispatch = interface
procedure GetIDsOfNames;
procedure Invoke;
...
end;
GetIDsOfNames is used to retrieve a method's ID given its name and Invoke is
used to execute a method given its ID. In order to get the entire late binding
picture, let's look at a sample ASP script:
Assume that our script is trying to create a Foo object contained in a
FooServer server. IFoo implements a Bar method.
'declare Foo variable
dim Foo
'create Foo object
set Foo = Server.CreateObject ("FooServer.Foo")
'call Foo.Bar
Foo.Bar
Remember, this is a script and is non-compiled. A script engine/interpreter
will read this script line by line and manually execute statements within the
script. This is roughly how a script engine would execute the above script:
Statement 1: dim Foo
Script engine allocates space for variable named Foo
Statement 2: set Foo = Server.CreateObject ("FooServer.Foo")
Script engine contacts COM to create the object whose PROGID is "FooServer.Foo".
We've previously discussed how this mechanism works. COM then contacts FooServer
for Foo asking for Foo's IDispatch interface. Foo hands out its IDispatch
pointer to COM and then COM hands it back to the script engine.
Statement 3: Foo.Bar
Script engine then calls IDispatch.GetIDsOfNames ("Bar") to get Bar's
ID. Upon receipt of Bar's ID, script engine then calls IDispatch.Invoke (BarsID) to
execute Bar.
Here's some important observations from the above process:
- The script engine talks to the server through IDispatch and only through
IDispatch. This allows the script engine to talk to any server (past,
present, and future) as long as the server exposes an IDispatch interface.
Furthermore, the script itself is not concerned with interfaces nor vtables
nor IDispatch; it's the script engine that takes care of all of that. For
instance, the script can say Foo.ExecuteMethodThatDoesntExist and the script
engine would be more than happy to call IDispatch.GetIDsOfNames ("ExecuteMethodThatDoesntExist")
which, of course, would get back an result indicating that that method
doesn't exist. In this case, the script engine won't call IDispatch.Invoke
but will instead raise an error indicating an erroneous script statement.
- A server object that wishes to support script-based clients must
support IDispatch (at the least, GetIDsOfNames and Invoke). If the server is
not going to be used by script-based clients, there is no need for the
server to support IDispatch - this is important, a lot of COM newbies think
that objects must support IDispatch to be usable by clients. However, there
is no harm in supporting IDispatch even if you won't have any script-based
clients.
Where Are We?
We've just seen a basic overview and rationale for enabling script-based
clients to "connect" with COM servers. Using the IDispatch interface,
it's clearly possible to devise a generic mechanism of creating script engines
that hide the complexity of interfaces from non-compiled clients. Knowledge of
this technique can also be used to create your own "generic
interface-based" communication mechanisms.
Further Reading
- Inside COM by Dale Rogerson
- Inside DCOM by the Eddons
- Automation Programmer's Reference
- Inside OLE by Kraig Brockshmidt
- Essence of COM by David Platt
|