Writing COM Objects with Scripting Languages

Dino Esposito
Microsoft Corporation

November 1998

Summary: Compares the DHTML and XML scripting languages and discusses the pros and cons of building Component Object Model (COM) objects with each. (15 printed pages) Covers:

DHTML vs. XML Scriptlets
The XML Scriptlets Architecture
XML Scriptlet Files
Interface Handlers
The Automation Handler
XML Scriptlets and Windows Scripting Host
XML Compliance

Introduction

Technology is rapidly evolving, there's no doubt about it. If you want proof, however, consider this story: Once upon a time, about a year ago, little actors called scriptlets made their debut in the dynamic HTML (DHTML) theatre. They were HTML pages acting as real components. They exposed properties and methods. Scriptlets were also capable of bubbling system events and firing their own notifications. Judging from this, they were really a great and decisive step forward in the long run to componentize the Web. Due to scriptlets, writing reusable HTML code was no longer an issue. You could arrange an HTML component, such as a data-aware table, make it as parametric as possible, and use just a line of code to insert it in any page. Seemingly the only drawback to scriptlets was the cross-browser compatibility issue, because their use was limited to Microsoft Internet Explorer 4.0 and later software.

But as I said earlier, technology is rapidly evolving. So what yesterday were revolutionary and cutting-edge solutions are today going to be superseded by more general and powerful approaches. That's just what happened to scriptlets. Only a few months after their release—they originally shipped with Internet Explorer 4.0—scriptlets were renamed to the more specific Dynamic HTML scriptlets. The role of Dynamic HTML scriptlets was then reduced by an emerging new technology called XML scriptlets or, now, simply scriptlets.

As a result, today we have two possible choices when it comes to designing script components to be used in Web-based projects. The first is DHTML scriptlets, and the second is XML scriptlets. They aren't mutually exclusive, but each has its own independent and well-defined field of application. Furthermore, at least in principle, you can use both either on the client or the server side of the Web application.

DHTML vs. XML Scriptlets

Let's see why these two technologies can be considered, and actually are, two sides of the same coin. The coin here is the concept of a software component. For our purposes, a good definition of software components is "self-contained, programmable, reusable, language-neutral pieces of code easily pluggable into applications." They expose properties and methods, they fire events, and they are univocally identified through name and/or ID.

Both DHTML and XML scriptlets apparently satisfy the preceding definition. The difference is that XML scriptlets completely rely on the Component Object Model (COM), while DHTML scriptlets leverage a totally different implementation that somewhat simulates the COM features. Table 1 shows a synoptic view of how DHTML and XML scriptlets implement common features of software components.

Table 1. How DHTML and XML Scriptlets Implement Common Features for Software Components

Feature DHTML scriptlet XML scriptlet
Programming interface Through a JavaScript function Type library
Creation mechanism A JavaScript function called public_description DllGetClassObject() function
Methods and properties public_description methods COM Automation interface
Events DHTML's window, external object COM connection pointer interface
Development language HTML with script XML
Object identification File name and MIME type ProgID and CLSID
Registration Unneeded. It suffices to give it a unique file name. System registry
Hosting in Web pages <OBJECT> tag with MIME type <OBJECT> tag with CLASSID attribute
Hosting in desktop applications Through a hosting Microsoft ActiveX® control Through direct instantiation

The programming interface exposed by a DHTML scriptlet is nothing more than the collection of methods belonging to a dynamically defined JavaScript function:

<script language="JavaScript">
public_description = new CreateScriptlet;
function CreateHotImage() {
   this.get_Property1;
   this.put_Property1;
   this.Method1;
}

The programming interface of an XML scriptlet, on the other hand, is coded into a regular COM type library. Moreover, functions are grouped by COM interfaces. With DHTML scriptlets you can have a single, all-encompassing interface, whereas XML scriptlets aren't limited in this sense. To identify a DHTML scriptlet it suffices to give it a unique file name. No registration is needed. The file is ready to use as soon as you create it or download it to your computer.

Hosting scriptlets in desktop applications still reflects their structural differences. Because a DHTML scriptlet is basically a Web page, you need at minimum a WebBrowser control to host it in—for example, a Visual Basic program. However, a better approach would be to make use of a specialized scriptlet control, available from the Microsoft Scripting Technologies Web site at http://msdn.microsoft.com/scripting/. Because an XML scriptlet works in exactly the same way as a COM object, an instance of it is all that you need to set it to work in a desktop application. In Visual Basic, for example, you might want to use  code such as:

Dim o As Object
Set o = CreateObject( "XML.Scriptlet" )

Of course, "XML.Scriptlet" will be the ProgID of the component.

DHTML scriptlets are user interface (UI) components made up of any combination of HTML elements: images, tables, text, hyperlinks, and so on. You can make them cooperate using script code and leveraging the Dynamic HTML object model to give life and interactivity to the final component.

XML scriptlets provide a more general infrastructure for script-based components, where the focus is more on the behavior of the component than on its user interface.

The XML Scriptlets Architecture

A short but accurate definition for an XML scriptlet might be the following: "COM component written in a scripting language." You can write them with Microsoft JScript® development software, Microsoft Visual Basic® Scripting Edition (VBScript), and any other language for which there exists an ActiveX scripting-compliant parser. At this point, I guess the first questions that arise are, "Where's the magic?" and "Where did you hide all the usual COM stuff like class factories, IUnknown, and so forth?"

Well, of course there's an explanation. What's interesting, though, is that it stems from one of the basic principles of COM+: isolating the user-understandable component's behavior from the more or less quirky underpinnings required by COM. COM+ promises to help us write components more easily by providing a system run-time module to give a default implementation to tasks like class factoring, dynamic-link library (DLL) registering, reference counting, dispatching, connection points handling, and interface queries, just to name a few. In other words, XML scriptlets have a system run time and use metadata to describe the object in a manner not unlike COM+. What you'll have to provide is:

  1. Metadata to describe the interfaces you're implementing.

  2. Code to implement the various methods.

This principle applies perfectly to the XML scriptlets architecture depicted in Figure 1. A scriptlet file is made up of three main segments: registration information, interface mapping, and script code. Aside from the registration information, which is common to any COM component, the remaining two pieces have an immediate correspondence to metadata and code.

Figure 1. XML scriptlets architecture

Client applications always deal with the scriptlet's run-time module—a system DLL called scrobj.dll. It provides the plumbing to let any COM-aware client application use an XML scriptlet as a real and effective binary COM object. For example, the run-time module may expose an IDispatch interface that clients will deal with. Any function invoked by clients then causes the run time to call into the proper interface handler to execute the related pieces of JScript or VBScript code. In this way, we actually have a COM object written with the simplest script languages. The run time usually makes externally available all and only the interfaces for which a predefined interface handler exists in the XML source code (more on interface handlers later). Every XML scriptlet always has scrobj.dll as its actual in-process server module.

An XML scriptlet is an Automation object with no UI and no object model to rely on. If you want a UI you should resort to DHTML scriptlets.

A typical scenario in which you might want to exploit XML scriptlets is in creating small, efficient, and reusable components to use in server-side programming from within ASP pages. Later in the article, however, I'll show you another quite compelling way of taking advantage of such scriptlets.

XML Scriptlet Files

An XML scriptlet is an ASCII file with the usual extension of .sct. It is composed by three main tags: <registration>, <implements>, and <script>. The first tag contains all the information that is relevant to the component identification. Among other things, there are the ProgID and the CLSID. Both are common elements in any COM object and constitute, respectively, the string and the 128-bit number that can be used to create an instance of the component. Both are unique identifiers. As usual, the CLSID is expressed through its textual representation given by a series of hexadecimal digits separated by dashes and enclosed in curly brackets. When you develop an XML scriptlet in this section you usually put in a description and a version number. All the source code for an XML scriptlet goes inside a <scriptlet> tag. Here's an example:

<scriptlet>
<registration
   Description="This is a bare-bones Scriptlet."
   ProgID="BareBones.Scriptlet"
   Version="1.00"
   ClassID="{00000000-1010-1010-83d1-f49604c10010}"
>
</registration>
</scriptlet>

The most interesting part of the story begins with the <implements> section. Here you define which interfaces the component will implement. In other words, each of the <implements> tags you insert corresponds to a COM interface the clients will be able to retrieve and call through QueryInterface. At this stage in the technology you can't implement any COM interface via scripting, but only those for which an interface handler exists. To make it a bit easier, interfaces aren't then identified by their CLSID or by their programming name (like ISomething). Instead, interfaces are identified by an attribute called Type, whose content is like a keyword and tells the run-time module which COM server to call to handle client requests. An interface can also have an ID attribute that serves the sole purpose of allowing you to treat it as an object throughout the code. This is quite useful if the interface handler has its own object model. What appears inside any <implements> tag depends on the nature of the interface itself. It also depends on how high-level the layer is that is built by the specific handler on top of the interface. The following is a typical <implements> section:

<implements type="Automation" id="dispatcher">
  <method name="Hello">  
     <parameter name=numHello />
  </method>
</implements>

Finally, the <script> section includes all the source code needed to implement the various functions exposed by all the interfaces implemented. For example, the preceding scriptlet can have a <script> section like this, where the method simply returns a string given by the specified number of "Hello":

<script language="JavaScript">
function Hello (numHello) {
   var strText = "";
   for( i=0; i<numHello; i++ )
     strText += strText + "Hello";
   return strText;
}
</script>

Interface Handlers

An interface handler is an in-process COM server that provides a standard implementation for the given interface. It is the most important feature of an XML scriptlet. Put another way, the whole collection of interface handlers lists all the COM interfaces the scriptlet is implementing.

There are a few limitations at the moment for such handlers. First, you can't define custom interfaces. Second, there's not yet a documented way to create your own handlers. Today you can write mostly XML scriptlets that are Automation servers—which is great news. With the release of Internet Explorer 5.0, the number of available handlers should grow considerably, including events, behaviors, and ASP code.

The Automation handler takes care of Automation interfaces. In particular, it implements IDispatchEx. The event handler provides all that's needed to fire events, namely the connection point interfaces. As you can see, there's a one-to-many relationship between handlers and interfaces.

Figure 2. How an XML scriptlet works

Each handler is intended to support a specific interface (or set of interfaces) and stay between the scriptlet and the client, making them communicate, as shown in Figure 2. From script code, however, you can successfully dialog only with objects that expose an Automation interface. For this reason, the interface handler must sometimes expose its own object model to help the scripts work with it. This point is addressed in Figure 2. In light of this, the role of an interface handler could be rephrased like this: It is an object implementing one or more COM interfaces, plus everything else that could make those functions easily callable and exploitable from script code. Furthermore, any handler requires different XML tags within the <implements> to provide the right information.

The Automation Handler

The Automation handler comprises the list of properties and methods a given scriptlet exposes. The dispatching engine is hidden inside the run-time module. The syntax allowed requires you to specify elements through <method> and <property> tags. A method must have a name, and possibly an internal name, to identify the script procedure that actually implements it. In case you omit the internal name, a procedure with the same name as the method is assumed. Properties can be exposed as simple global variables, or you can define specific procedures to read and write them. In this way, you can make them write-only or read-only.

<method name="Method1" internalName="DoMethod1" />
<property name="Prop1" internalName="m_Prop1" />

The preceding example shows the declaration of a method (Method1) coded into a DoMethod1 function and a Prop1 property rendered through an m_Prop1 variable. The internalName attribute is optional. The m_Prop1 variable is a global variable that acts as the container for the property values. Alternatively, you could have something like:

<property name="Prop1">
<get internalName="DoGetProp1" />
<put />
</property>

where the Prop1 property is read via a function called DoGetProp1 and written through put_Prop1, which is a function that follows the standard naming convention. Such rules want reading done through a procedure called get_ followed by the public name of the property. Of course, all the necessary functions, both for methods and properties, must be implemented in the <script> section.

Notice that defining methods and properties this way is a high-level way to look at Automation. All COM requires from here down to dispatching interfaces is done by the handler.

Connection Points

If you want your scriptlet to fire events you need to map them to the event handler, which provides the required COM infrastructure in terms of a standard implementation of both IConnectionPoint and IConnectionPointContainer. What I said before about the necessity of an additional layer of Automation code between the scriptlet and the handler is particularly true for events. The event handler is intended to provide a service—firing events from script code. That conceptually simple action, though, needs a certain amount of work to be done under the hood by the handler. It has to expose an outgoing interface, allow clients to sink, and then look for registered sinks when it raises a given notification. This process needs simplification. In fact, the event handler object exposes a fireEvent method, which allows scriptlets to easily notify their events to the outside world:

<implements type="Event" id="MyScriptlet">   
   <event name="BeginWork" />
   <event name="WorkCompleted" />
</implements>

This sample shows us a scriptlet that exposed two events, BeginWork and WorkCompleted. COM always associates an event with a dispatch identifier called a dispid. That ID is then compiled into the type library and used by the client to bind to the event. Normally, the module used to generate a type library for scriptlets (see next code sample) can give automatic dispids to the exposed events. However, by adding a dispid attribute you can decide yourself which dispid should individuate a given event.

<event name="BeginWork" dispid=65 />

Here's an example of how events are actually raised from within code:

<script language="JScript">
function DoWork() {
  MyScriptlet.fireEvent("BeginWork")
  // place code here
  MyScriptlet.fireEvent("WorkCompleted")
}
</script>

If we used the default attribute in the event handler declaration we could have done without the MyScriptlet prefix in script code.

Internet Explorer 5.0 Behaviors

The third type of interface handler defined so far is the Internet Explorer 5.0 behaviors. As you might guess, they are supported only in Internet Explorer 5.0. Aside from this, behaviors are nothing more than XML scriptlets implementing a bunch of specific Internet Explorer 5.0 interfaces, such as IElementBehaviorXxx. The idea behind behaviors is quite simple. They're intended to reduce all the script code that qualifies a given HTML tag to a cascading style sheet (CSS) style. In this way, when you define the style of an element, or a group of elements, you can specify the "behavior" as well as graphical attributes like font and colors.

A behavior is a component that defines what the elements of a certain CSS class can do. For example, if you need them to handle DHTML events, or expose additional methods or properties, you can write a behavior. A behavior also makes the source code of a Web page much more readable and straightens out its maintenance by dramatically reducing the need for in-site script code. At the same time, this doesn't limit at all the power you have to enrich an HTML element—be it a standard element like an <img> or a "custom" element like a DHTML scriptlet—with additional features.

An Internet Explorer 5.0 behavior must implement the behavior handler. It could also implement the Automation or the event handlers if necessary. The need for the latter, however, is quite rare. A behavior needs Automation to properly handle special attributes of the HTML code that uses the element. For example, let's assume we have a behavior for an <img> tag and want it to display a different image according to the value of an attribute. That attribute could refer, say, to the color depth. In the HTML page we could have code like this:

<img id="one" class="colorimg" src="" color="16" child="one">

Inside the colorimg class there will be definitions like the following:

<style>
.colorimg {behavior: url(colorimg.sct)}
</style>

The scriptlet colorimg.sct contains the behavior for all the elements of class colorimg. The code of such a scriptlet will look like this:

<scriptlet>
<implements type="behavior" />
<implements type="automation" />
  <property name="color" />
  <property name="child" />
</implements>

<script language="JavaScript">
attachEvent( "onload", event_onload );

function event_onload() {
  document.all(child).src = child + color + ".gif";
}
</script>
</scriptlet>

The scriptlet provides a behavior as well as an Automation interface with two properties: color and child. The scriptlet attaches itself to the onload event of the Web page invoking it. This is done through the attachEvent function, which is specific to the Internet Explorer 5 DHTML object model. While responding to this event, the scriptlet decides which image to display for that <img> tag. Its name is given by the name of the tag, passed through child, and the number of colors passed through color.

<img id="one" class="colorimg" src="" color="16" child="one">

In this case, the name will be one16.gif.

Scriptlets and Client Applications

Because XML scriptlets are really COM objects, you can use them with any COM-compliant development environment, due to the language neutrality of COM: from Visual Basic to Delphi, and from Visual C++ to ASP code.

Let's have a quick look now at how the interaction between XML scriptlets and client applications takes place. To create an instance of an XML scriptlet, client applications don't use a different approach than they would for any other COM object. Thus, in Visual Basic you can call:

Dim o As Object
Set o = CreateObject("BareBones.Scriptlet")
o.Hello 4 
Set o = Nothing 

Behind CreateObject (or its counterpart in other languages) there's the usual conversation between clients and COM servers. In the preceding code snippet, the client asks for the IDispatch interface and a method called Hello. The file that gets called after CreateObject is scrobj.dll, the run-time module. It works as a proxy between the client and the Automation interface handler. It returns the client a reference to the interfaces actually implemented by the scriptlet, but it doesn't implement them. Instead, it relies on those specialized modules called interface handlers.

In this Visual Basic code we used CreateObject and late binding. Provided that you have a type library for the scriptlet, you can employ early binding as well.

Scriptlet's Type Library

A type library is a file that collects details about the interfaces implemented by a COM component. XML scriptlets are no exception. While creating a type library is not strictly necessary, it can turn out to be very useful, especially when using the component in Visual Basic. The Microsoft IntelliSense® technology, for instance, relies heavily on type libraries to show you all the properties and methods available for a given object. Speaking of Visual Basic, by referencing the scriptlet's type library you can declare it with the WithEvents qualifier and be ready to hook on its events.

There are two ways to generate a type library for an XML scriptlet. You can do it in an offline manner, by right-clicking the .sct file and choosing the right command. Alternatively, you can do it programmatically, as shown here:

Set oTL = CreateObject("Scriptlet.GenerateTypeLib")
oTL.AddURL "c:\MyDir\myScriptlet.sct"
oTL.Write
oTL.Reset

This code can go anywhere—in another component aimed at generating type libraries, or even in the scriptlet itself to execute once during the registration stage. If you want code to run when you're registering the scriptlet, you can add a <script> tag to <registration> and define a function called register:

<registration
   Description="This is a bare-bones Scriptlet."
   ProgID="BareBones.Scriptlet"
   Version="1.00"
   ClassID="{00000000-1010-1010-83d1-f49604c10010}"

   <script language="VBScript">
      Function register()
        ' create type library
      End Function
      Function unregister()
        ' do what you need to do
      End Function
   </script>
</registration>

An unregister function executes during unregistering.

A type library can include more than one scriptlet. In this case, repeatedly call AddURL with the various file names. Check out the scriptlets documentation at http://msdn.microsoft.com/scripting/ for more details about the options available when generating a type library.

XML Scriptlets and Windows Scripting Host

An XML scriptlet is a COM object made up of a layer of both XML and script code that works on the top of a run-time environment. The run time takes care of the magic that makes the scriptlet appear as a real COM object to its clients in the same manner that Microsoft Visual J++™, Visual FoxPro®, and Visual Basic® work. We can use XML scriptlets as silent and UI-less modules both on the client (DHTML components and behaviors) and server side (ASP pages). Furthermore, on the client side we could also employ the variant called DHTML scriptlets that are programmable and embeddable DHTML pages.

The advent of Windows Scripting Host (WSH) opened up an exciting new field for XML scriptlets. If you're writing WSH applets, you normally use VBScript or JScript. It's quick and easy and far more powerful than batch files. But what about reusability? A good answer to this is XML scriptlets.

Let's see an example, which provides a good opportunity for seeing XML scriptlets in practice. Our goal is to enumerate all the files in a given folder that were last modified on a certain date. While this is not a very tough task in C or C++ code, it sounds a bit harder to do with script. What's important, however, is being able to reuse the code once you've written it. Code sample 1 shows an XML scriptlet that does just this, called filefinder.sct. The progID is FileFinder, an Automation scriptlet exposing two methods:

FindFirstFileByDate( pathSpec, date )
FindNextFileByDate()

You specify a folder name and a string containing a date and the functions will enumerate all the files found that were modified by that date. To enumerate the folder content we make use of the new scriptable shell objects introduced with Internet Explorer 4.0:

Set m_Shell = CreateObject( "Shell.Application" )
Set m_Folder = m_Shell.NameSpace( m_PathSpec ) 

The object m_Folder now contains a collection with all the folder items. What remains is to compare their date with ours. The NameSpace method returns an object of type Folder, which is a collection of FolderItem objects. The shell object model is documented in the Internet Client SDK Help files.

The sample code sequentially scans the list checking for the date. The comparison is made through the VBScript's DateDiff function:

FileDate = m_Folder.Items.Item(m_Index).ModifyDate
d = DateDiff( "d", FileDate, m_Date )

DateDiff returns the difference between the two dates, expressed in days. Such value is measured in days due to the "d" argument. The scriptlet first initializes the folder object, and then begins moving the index along the list, searching for all the files whose dates match the user's requirements. You'll call FindFirstFileByDate to initialize the engine and get the first file name. Then, FindNextFileByDate will be called for all the successive files.

Looking at code sample 1, you'll notice an odd line at the beginning of the file that looks like <?scriptlet validate="true" error="true" debug="true"?>.

It is there just for debugging purposes. In case of errors, the scripting run-time engine will display a more or less hasty message. That line serves the purpose of having a more descriptive and accurate text for each error.

A second point that should strike your curiosity is the <![CDATA[ … ]]> that brackets all the script code. As I'll show you, it ensures that any possible misleading character used in script code won't confuse the XML parser. Using those brackets saves you from annoying and inexplicable errors. Without it, I even experienced an error on a commented line! See the next section for the whys. For now, keep in mind that the CDATA tag is strictly tied to the <?XML version="1.0"?> that appears at the top of the XML file.

Code sample 2 shows how to take advantage of the FileFinder object in a WSH script. A simple script written with VBScript takes on the command line the two arguments it needs to do the work: the folder path and the date. Such arguments default to the C: disk and the current date.

As the FileFinder component demonstrates, with XML scriptlets at last you have a way to write reusable code with the same ease of use as batch files.

XML Compliance

So far we've learned that an XML scriptlet is primarily an XML file, but what about the language conformance? XML is a more rigorous language than HTML. When writing scriptlets, the most common mistakes you can make are:

  1. Using the wrong cases for tags.

  2. Using special XML characters like < and > for other purposes (for example, in script code).

  3. Avoiding quotes when specifying the content of tag attributes.

All of these are errors that never occur with HTML. By default, the run time parses the XML scriptlet file as if it were an HTML file. In other words, it lets you follow the previous three conventions, which are normal in HTML but not in XML. On the other hand, such a loose syntax will surely give you problems as soon as you move your code inside an XML 1.0-compliant editor. Recently, I've experienced this using the XML object model built into Internet Explorer 4.0. I've written an ActiveX control working as a generic XML parser capable of rendering the content of any XML file into a treeview. (By the way, it's available at http://www.microsoft.com/mind/ with the source code in the September '98 issue.) It made use of the MsXml component, which strictly follows the 1.0 standard. As a result, it was unable to handle many of my XML scriptlets. Needless to say, all of those scriptlets worked perfectly at run time. In all these cases, the script tags included operators such as greater than (>) or less than (<). Unless you prefix them with escapes, the parser gets confused and uses them to close a tag or open a new one with unpredictable results.

To reinforce the run-time checking, and avoid the loose HTML-based syntax rules, you can put at the beginning of your XML scriptlet a line like this:

<?XML version="1.0"?>

The content of the version attribute is the version number of the XML standard with which the code is declared to be compliant. In case of errors, the XML scriptlet object won't get created. Almost all of the previous code snippets aren't compatible with the 1.0 standard. I've done this deliberately to illustrate where the errors can lie. The reasons the code isn't compatible are because they violated one of these two rules:

  1. All the tag and attribute names must be lowercase.

  2. All the attribute content must be enclosed in quotes, either double or single.

Being respectful of the 1.0 standard requires using escape characters in script code to prefix < and >, even when they're used on commented lines. To make the XML file 1.0 compliant, but save yourself the bother of using escapes, enclose the whole script code in a <![CDATA[…]]> section. The square brackets encompass the function code. Here's an example:

<script language="VBScript">   
<![CDATA[   
Function method()
      ' place here the script code
End Function   
]]>

The solution is independent of the script language used, so you can employ it also with JScript scripts.

Summary

XML scriptlets go far beyond the DHTML scriptlets that first appeared on the scene. However, both can be really useful to developers, each one in the application that best suits its own features. So you might want to employ DHTML scriptlets when you need independent and self-contained UI objects for Web pages. (You can also consider DHTML behaviors for this.) If, however, you need worker objects with a friendly programming interface to be used anywhere, from ASP to HTML, and from desktop applications to WSH, there's just one answer: XML scriptlets.