techvanguards.com
Last updated on 1/25/2016

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.

Late Binding

Script-based automation is based on a protocol of runtime discovery and execution of methods of an interface. The protocol works like this:

  1. 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.
     
  2. 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:

  1. 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.
     
  2. 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.

IDispatch

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:

  1. 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.
     
  2. 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
Copyright (c) 1999-2011 Binh Ly. All Rights Reserved.