PREVIOUS  TABLE OF CONTENTS  NEXT 

Scriptlets

Ken Bandes

Packages Used
Server Scriplets:      http://www.msdn.microsoft.com/scripting
PerlScript:               http://www.activestate.com

Optional Packages

Win32::OLE:           CPAN

In towns where many cultures meet and trade, there often arises a dialect, severely limited in syntax and expressiveness, in which essential business can be conducted. Linguists call this a "pidgin" language. The pidgin of Windows is OLE Automation, which, though lacking in expressive power, provides a common ground for many components and applications to communicate with one another.

OLE Automation specifies a well-defined binary interface by which one program can gain access to an object in another. Here, "access" means that it can call the object's properties and methods. Because it is a binary interface, it's not specific to any one language.

In addition, OLE Automation provides late binding. This means that a program can wait until it runs to find out what properties and methods an object supports, and what parameter and return types they have. This eliminates the need for language-specific header files and interface descriptions. Part of the price paid for these advantages is a very restricted set of types that properties and methods can have. Data structures, such as linked lists or trees, are definitely out of the question.

OLE and COM

OLE Automation is a particular application of COM, the Component Object Model. COM, too, is a way for programs to communicate with objects in other programs or modules, by means of a well-defined binary standard. COM interfaces can employ a rich vocabulary of types, including the complex data structures excluded from OLE Automation. However, COM interfaces do not provide late binding.

It is important to distinguish between COM and OLE Automation, because many applications of COM do not involve OLE Automation and are not accessible to languages that only understand the latter. (I might also point out that while "COM" and "OLE Automation" have very precise technical definitions, "ActiveX" and "OLE" are essentially marketing terms with highly elastic meanings that have changed radically over time as Microsoft's business strategy has evolved.)

If you're serious about understanding COM in all its depths (though you need not be to follow this article, write useful programs, or earn a respectable living), I recommend Essential COM by Don Box (Addison-Wesley), an illuminating book, engagingly written and blessedly concise.

The Need For Scriptlets

Until recently, Perl's access to OLE Automation was strictly one-sided. Perl could issue commands to other applications (through the Win32::OLE module), but could not receive them. That has now been remedied, and there are two ways to create OLE Automation objects in Perl that can be called from other languages. One of these is PerlCtrl, which comes with the (highly recommended) Perl Resource Kit for Win32. The other comes from Microsoft in the form of a technology called Server Scriptlets. PerlCtrl is amply documented in the Resource Kit and in a very nice article by Brian Jepson in issue #11 of The Perl Journal, while server scriptlets in Perl are the subject of this article.

Scriptlets allow any scripting language to create COM objects, as long as that language supports the ActiveX Scripting interface. In addition, scriptlets will soon have something PerlCtrl doesn't: software components called handlers that provide the glue to a COM interface. At the moment, the only handler available is for OLE Automation, but as others become available, server scriptlets will take Perl into applications that can't be touched by PerlCtrl or other OLE Automation-aware technologies. In this article, everything that I demonstrate can be accomplished with PerlCtrl as well.

Scriptlets are not specific to Perl; indeed, Microsoft's documentation for scriptlets only discusses VBScript and JavaScript. In this article, I will show you how to write Perl scriptlets.

In particular, I'll show you PerlScript scriptlets. PerlScript is just a packaging of Perl that supports the ActiveX Scripting interfaces (a set of COM interfaces that allow script language engines to be used interchangeably). This makes it usable not only for scriptlets, but for all other ActiveX scripting hosts, such as Active Server Pages. First, I'll show you the scriptlet itself. Then I'll show you the Perl module that it uses, and finally, you'll see a Visual Basic interface that displays this:

Figure 1: National Weather Zone Forecasts

Figure 1: National Weather Zone Forecasts

It will help to become familiar with the Win32::OLE module. I recommend (again) the Perl Resource Kit for Win32, or the article in the Summer 1998 issue of The Perl Journal, "OLE Automation with Perl," by Jan Dubois.

To begin with, you'll need to obtain the server scriptlets kit from Microsoft. This can be downloaded from http://msdn.microsoft.com/scripting/ (look under Scriptlets, and downloads).

You will also need PerlScript, which you can obtain online (see www.perl.com for the link) or in the Perl Resource Kit. Microsoft provides a wizard to generate a skeletal scriptlet. Unfortunately, it will only generate VBScript or JavaScript scriptlets. (It has other problems as well, but why dwell on them?)

A server scriptlet is an XML file that defines an interface and contains source code. In the same way that an HTML page may have embedded scripts within "script" tags, the source code of a server scriptlet sits within a script tag in its XML file. I will discuss some of the other kinds of information contained in a scriptlet file later.

Now I'll present a simple application with a Visual Basic user interface and a Perl "engine." The application retrieves a weather report for a given state from the National Weather Service, extracts the sections for the various regions within the state, and displays the report for a selected region.

Parsing these forecasts would be daunting in most languages - certainly Visual Basic! In Perl, the code is quite concise, with the parsing performed mostly by a single regular expression. (However, the solution presented here isn't perfect - the NWS reports are prepared by hand and the format varies quite a bit from state to state, so some areas are inadvertently omitted.) The user interface is in Visual Basic, which really does work well for this sort of thing. As you'll see, the code is very compact and not unattractive.

Creating a server scriptlet involves defining the properties and methods of an interface, and mapping them to elements (variables or methods) in the scripting language. There isn't much more to it, but I'll point out a few subtleties as we go along.

The approach I've taken is to put the bulk of the Perl code in a module, so that the script embedded in the XML file can make the module do most of the work. This makes testing the code much easier - important, since the debugging facilities for scriptlets are quite crude. This kind of code organization is not possible in VBScript or JavaScript, which have no equivalent to Perl's modules.

The nws.sct scriptlet

The nws.sct scriplet (Listing 1).

Listing 1, nws.sct, shows the XML scriptlet file, which I'll discuss in detail. Listing 2, NWS.pm, presents the module that does the actual work. Listing 3 presents the Visual Basic code that displays the results.

The first two lines of nws.sct are optional, but I recommend them for all scriptlets:

<?XML version="1.0" ?>
<?scriptlet validate="true" error="true" debug="true"?>

These lines ensure that the file is indeed a valid, well-formed XML scriptlet. I'm not sure why you might not want to do this, but the wizard provided with the kit does not use these and, indeed, does not generate well-formed XML.

The second line (a processing instruction in XML terms) also enables scriptlet error messages to appear in dialog boxes. This is important, at least during development, because otherwise a Perl syntax error just quietly causes the scriptlet to fail.

The remainder of the file is enclosed within <scriptlet> and </scriptlet> tags.

THE <REGISTRATION> SECTION

The registration section provides information that the scriptlet runtime uses to register the scriptlet as a COM object.

<registration
    description="nws"
    progid="nws.Scriptlet"
    version="1.00"
    classid="{c92ae570-3bb1-11d2-b6b2-00a0c99efa91}"
>
</registration>

The progid and classid are two ways to refer to this COM object, the former being more human-readable. The scriptlet identifies itself to Windows when it's launched, and enters these identifiers in the system registry, along with information for COM about how to load and instantiate this kind of object.

Whenever you create a new scriptlet, you must provide it with a unique classid. Microsoft provides tools to generate these, called uuidgen and guidgen, at least one of which comes with most any Microsoft development tool (but not, so far, with the server scriptlets distribution). They also come with the Platform SDK, which you can download from Microsoft's site (it's huge, though). Alternatively, I've written a Perl module called Win32::OLE::Guid to generate guids, which I will submit to CPAN as soon as I finish this article.

THE <IMPLEMENTS> SECTION

The implements section describes the interfaces provided by this object. The initial tag specifies the kind of interface: <implements id="Automation" type="Automation">.

The type attribute specifies the handler to use. As I mentioned, only the OLE Automation handler is currently implemented. The contents of the implements tag are available to it, and define the properties and methods of the interface. A different handler might require different information in this tag.

Within the implements element are property and method elements. These name the properties and methods of the interface and in this case describe their mapping to Perl language elements.

There are four property elements: State, Zones, Counties, and Cities. Each specifies the name of the property (as it appears to client programs using the scriptlet), and the functions that provide access to it. A property can have a get method to retrieve its value, and a put method to set its value. For example:

<property name="state">
    </get>
    </put>
</property>

This defines the state property as having both get and put methods. This tag, then, defines how the property appears from the outside (the client program) and the inside (the scriptlet source code).

The get and put methods are not seen by the client program, which simply assigns to or from the property as if it were a variable. By default, the scriptlet's names for the get and put methods are 'get_' and 'put_', followed by the property name (in the example above, get_state and put_state). It is possible to override these and specify your own names for these methods with the internalName attribute. For example:

<property name="state">
<get internalName="getstate" />
<put internalName="setstate" />
</property>

This snippet indicates that the Perl functions corresponding to get_state and put_state are called getstate and setstate.

The scriptlet syntax allows for a property to map directly to a script variable. However, I haven't been able to get this to work with PerlScript.

A method declaration, by default, maps to a Perl function of the same name. Again, this can be overridden with the internalName attribute, e.g.,

<method name="meth1" internalName="meth1_impl" />

The interface I define here allows the client to specify a state. This causes the object to retrieve and parse a file of forecasts for that state. Other properties allow the client to retrieve lists of zones, counties, and cities for which forecasts are provided. The methods return the actual forecasts for those regions.

Apart from state, the other properties have only get functions. That makes them read-only.

Aside from properties and methods, an implements section can also define events. These are callbacks that the object makes to the client - a feature that PerlCom does not provide and which, when it works, will be quite useful. As of this writing, it does not work in the current beta, as far as I can tell.

THE <SCRIPT> SECTION

Following the implements section is the script section containing the implementations of the interface's properties and methods:

<script language="PerlScript">

Notice that I've enclosed the script in XML CDATA (character data) tags, <![CDATA[ and ]]>. This indicates that the enclosed text (the script) is not to be interpreted as containing any XML tags. If you don't specify validate="true" in the <?scriptlet?> tag at the top, you can't (and needn't) use these. They just insure that the file is correct XML.

The NWS module

The NWS.pm Module (Listing 2).

The functions in my scriptlet are simple wrappers on my NWS module, which does the actual work. In this case, they merely check to make sure that an NWS object has been created (if defined $nws) before calling its methods. They pass references to arrays returned by the module methods.

The script first declares the NWS module and some variables:

use strict;
use NWS;
my $nws;
my $state;

The busiest function in the script is the set_State() function, which creates an NWS object for the specified state. This causes the state forecast to be retrieved (viaLWP::Simple) and parsed:

sub put_State {
    $state = shift;
    $nws = NWS->new($state);
}

The other properties and methods just enjoy the fruits of this process. For example:

sub get_Counties {
    if (defined $nws) {
        my @counties = $nws->counties();
        return \@counties;
    } else {
        return undef;
    }
}

Note that some of these properties return references to lists. PerlScript converts these to a type called a SAFEARRAY, which Visual Basic understands, after a fashion. I'll discuss how this looks from Visual Basic soon.

I won't say much about the NWS.pm module, because there's nothing at all specific to scriptlets in it. It follows the standard module format as generated by h2xs. The heart is the regex in the new() function, which extracts individual area forecasts from the file (retrieved as a single string). I made it a bit more readable with the /x flag.

In a list context, the get_ methods return a list with the zone id, date and time, and forecast text. In a scalar context, they return just the forecast text.

The Interface

Listing 3 presents the Visual Basic code for the user interface. The UI is quite simple: there is a drop-down list of states. On choosing a state, the counties are put into the county listbox. When a county is double-clicked, its forecast is displayed, along with the date and time.

The Interface (Listing 3).

The NWS object is created in the Form_Load() subroutine. This is executed when the form is loaded (since this is the main and only form, that occurs when the program runs). This subroutine also populates the drop-down menu of states:

Private Sub Form_Load()
    Dim states As Variant
    Dim st As Variant
    Set nws = CreateObject("nws.Scriptlet")
    states = Array( _
    "AL", "AK", "AZ", "AR", "CA", _
    "CO", "CT", "DE", "FL", "GA", _
    ?
    "WY")
    For Each st In states
    cmbState.AddItem st
    Next
End Sub

The cmbState_Click() subroutine gets called when a state is selected from the drop down menu. As I mentioned, it gets the list of counties from the NWS object and loads them into a listbox:

Private Sub cmbState_Click()
    Dim county_list As Variant
    Dim county As Variant
    ' clear out previous county entries
    lstCounties.Clear
    MousePointer = vbHourglass
    ' set the state, which causes the data to
    ' be retrieved from the weather service
    nws.State = cmbState.Text
    MousePointer = vbDefault
    ' get the list of counties as a variant,
    ' then reference as an array
    county_list = nws.Counties
    For Each county In county_list
    lstCounties.AddItem county
    Next
End Sub

Functions or methods in Visual Basic can't return arrays. However, they can return Variants, and Variants can contain arrays. This is how Visual Basic sees the value of the Counties property, which we returned as an array reference in Perl. The loop variable of the For Each statement also must be a Variant, as shown above.

Finally, the lstCounties_DblClick() subroutine is called when an item in the list of counties is double-clicked. Recall that the GetCounty method returns an array containing the hurricane zone id, date and time, and forecast. Again, we treat this return value as a Variant and, knowing it's an array, use the VB array index syntax to get at these components:

Private Sub lstCounties_DblClick()
    Dim nwsRes As Variant
    nwsRes = nws.GetCounty(lstCounties.Text)
    forecast.Text = nwsRes(2)
    forecastDate.Caption = nwsRes(1)
End Sub

To give Visual Basic its due, this code is as concise as one could wish for what it does. This kind of division of labor - Visual Basic for the front end, Perl for the back - is quite reasonable and effective in Windows.

In addition, scriptlets of this sort can be used in other applications. For example, Word and Excel both use Visual Basic for Applications (VBA), which understands OLE Automation. Thus it is possible to write Word or Excel macros that call server scriptlets to do some of their work.

Having the ability to create OLE Automation objects in Perl is a great benefit for Windows developers. We are fortunate to have a choice of technologies for doing so. Server scriptlets, though far from complete or polished, nevertheless provide useful functionality today and promise a great deal more.

__END__


Ken Bandes is a consultant with R2K LLC, based in New York City. He can be reached at ken.bandes@r2k.com.
PREVIOUS  TABLE OF CONTENTS  NEXT