![]() |
ActivePerl User Guide |
![]() |
Windows Script Components (WSC), formerly known as Scriplets, is a technology for developing powerful COM components in an easy fashion. WSC's can be authored in any scripting language that has implemented the ActiveX Scripting Interfaces, which means that units of PerlScript code can be encapsulated as Windows Script Components.
Be warned, reader, a lengthy introduction to the Component Object Model (COM) stretches until the topic of Windows Script Components. Please move directly to the topic of Windows Script Components if you feel that you understand the COM; the COM is essential to WSC, thus it is necessary to cover the fundamentals of the COM.
The Component Object Model (COM) is a language-independent and object-oriented programming model. It is not, however, a programming language, and it does not demand that a new style of programming be learned. It is a binary standard that enables software to be implemented as units called components. The component can be authored in any programming language or scripting language provided that the language supports the standard defined by the COM. After the binary unit has been built, it can communicate with other units that were produced in any other language, on the same machine or on remote machines provided that the units were written as COM components.
As many see it, the Component Object Model is elegant and powerful. The basic yet ingenious programming model provides the foundation for application-development. The power resides in that everything built on the same foundation can communicate with each other without any restrictions such as programming languages or platforms. When developing an application devoted to COM, components should encapsulate every functionality that the application requires. A task performed by the application would be executed by a component, and these components would be reused in other and future applications. For example, a Perl component can contain the process of sending an email or performing a series of regular expressions on text. The component can then be used to provide functionality for an Active Server Pages application or a Windows desktop application.
In summary, although the language in which a component is generated may vary from component to component, a binary standard overcomes the limitation of programming in different languages. It does not care about anything else but the binary form of the machine code that the once source code was turned into and the fact that is implemented into COM. In this sense, the component is not a regular script or executable, but a black box that performs one task and returns the projected result. Multiple languages can be used for one application because COM provides a binary standard. A binary standard also results in platform-independence provided that multiple platforms support COM. Units of code that are used in COM are known as components, and on Windows a component is normally file with the extension .dll; however, there are offshoots such as Windows Script Components, which have the extension .wsc. The purpose of the component is to perform the one task that it has been programmed for, and through COM, it can communicate with other components, languages, and platforms.
COM is both a specification and an implementation. And being both a specification and an implementation, the COM defines a standard for how the components are created and how they communicate - namely as objects. As a result, the specification solves the implementation issues of the following key issues:
- The calling of a component
- The locating of a component
- The identification of a component
- The creation of a component
Although the parts above are involved in the specification of how to implement components, the programmer normally does not have to worry much about them. A software development tool that holds high class will create a skeleton for a component, and leave it up to the programmer only to plug in the code in the right places. Thanks to this, the code itself can be developed, debugged, and experimented with as usual. And then when the code is finished, it is placed in the skeleton, the right buttons are clicked, and the component pops out.
When wondering what makes up a component, recall that COM is an object-oriented programming model that requires every component to be implemented as an object, so from this we know that we will be talking about classes, methods, properties, and such entities that belong in an object-oriented programming model. However, let’s start at the bottom level and move our way up from interfaces, to classes, and to objects.
When the component is implemented, it exposes its features through one or more interfaces. The interface is a specification of a collection of methods and properties that are related to each other, so when a component is called, it must be called through one of its interfaces. In general, this means that an interface relates to an operation such as verifying a credit-card number or sending an email. Because the component may expose several interfaces and the method-calls pass by way of the correct interface, the interface that is called must be identifiable. It is a fact that names easily clash, and especially with a high pace of component development, so another solution is provided. An interface uses a globally unique identifier (GUID) which is a 128-bit integer. And through the GUID, the interface is identified to the COM.
Next, the COM class implements, or maybe better 'inherits', one or more COM interfaces. A Class ID (CLSID) uniquely identifies the COM class, and while the interfaces are at the base level of the COM architecture, the COM class is on top of the interfaces - one step close to the application. The main purpose of the COM class is to generate, or instantiate, the COM object that represents the functionality of the unit of code.
As previously mentioned, this part of COM simply is the instance of a COM class, or the entity from which all the features of the COM class are accessed.
In the midst of everything, where does the component fit into the picture? It can be easily summarized as that the component is used to include a COM class by identifying it by the CLSID, create an instance of the COM class as the COM object, and then let the application make use if the COM object on the machine.
Automation, formerly OLE automation, makes it easier for the masses to access COM object servers such as components. In terms of a scripting language, the only way for a COM component to make its features available is through Automation. Automation is one level higher up than COM, in other words one step closer to the application, and it is the technology that lets software packages such as Microsoft Office expose its functionality in an object-model for scripting languages that support Automation.
In the same manner as large applications like MS Office are exposed, smaller COM components are made available to a scripting language through Automation, as well. Without it, the scripting language would necessarily have to know all the interfaces of an object that it wants to use before calling it - too much information to build into a language.
Instead of bloating a language with junk, COM defines the standard for accessing COM object servers: Automation. In turn, Automation makes sure that a standard interface that allows, in our case, script-access to the component is always available for object-access. This standard interface used in Automation is called a dispatch interface, dispinterface, or automation interface.
A dispatch interface is somewhat different than the standard COM interface. Methods are associated with dispatch IDs (dispids), and methods have been provided for reading and writing the values of properties - the access to the data members in the underlying data structure.
The standard dispatch interface for automation is called IDispatch. Through IDispatch, a component can expose as much as it wants to expose. Then in order for Perl to gain access to components, the interpreter needs to support an object-oriented programming model, have the ability to call methods through IDispatch, know how to return errors, and be able to destroy objects.
A method that belongs to an interface of a COM component is retrieved by the dispatch interface and in Perl this can occur by what is known as late binding. A dispatch method called GetIDsOfNames is called with the name and the parameters of the method of the COM interface that is called. The dispatch ID of the method is returned. Next, the Invoke-method of IDispatch is called with all the processed information returned from GetIDsOfNames, and from that call the result is returned in an output parameter. This method is called late binding, and there are two things to notice about it:
- There is an overhead involved in the Automation discussed
- All data types that are sent as parameters through Invoke to the method of an interface must be passed as Variants or converted into Variants.
When not using late binding, the application already is aware of all the dispatch IDs and does not call GetIDsOfNames at all. This is called early binding since the information needed for using the components have been built into the application and are used at run-time.
Perl uses late binding, and the process of late binding is simple. First, a moniker is required, which is a name that uniquely identifies the COM object. The moniker is then used to locate the object, which next is either in running state or put into running state. When in running state, the server application can access the interfaces of the COM object, and when that is completed, an interface pointer is returned to it. The dispatch interface that makes COM objects accessible to a scripting language is implemented as a COM interface that uses IDispatch. And the IDispatch functions of this interface only calls the methods that are laid out in the COM interface, and these two interfaces are called dual interfaces because an application that knows only early-binding can access it while another language that knows only late-binding can access it, as well.
Although the implementation of all of the above is hidden to Perl, there are some things to know. In most cases, Automation will accept and convert scalar data into the correct variant-type. However, it might be necessary to specify the type of data; so to solve this issue, you would convert Perl data types to the variants used.
VT_EMPTY No Value VT_NULL Null Value VT_I2 2-byte integer VT_I4 4-byte integer VT_R4 4-byte real value VT_R8 8-byte real value VT_CY Currency VT_DATE Date VT_BSTR Binary string VT_DISPATCH Automation object VT_ERROR Error code VT_BOOL Boolean value VT_VARIANT Variant VT_UNKNOWN IUknown Pointer VT_UI1 Unsigned 1-byte character VT_BYREF Describes the data as passed by reference VT_ARRAY An OLE Safearray
In Perl, Automation and creation of Automation objects is provided by the Win32::OLE module, Furthermore, Variant data types can be converted by the Variant() method or the OLE module although Perl seamlessly takes care of most of the conversion needed.
When it comes to writing a WSC, it is a surprisingly quick and easy process. In fact, anybody who has written an ASP page with some script commands and HTML will have completed a WSC in less than five minutes. And although .wsc files are Extensible Markup Language (XML) files, no previous experience with XML is necessary in order to build a successful component.
In order to create a WSC, you can use the Windows Script Component Wizard. The wizard will produce a valid skeleton for your WSC, and all you will have to do is enter your PerlScript. Let's create a simple WSC that can be instantiated within a script and holds the basic functionality of being passed a string and retuns the reversed string, so follow me on these steps.
- Open the Windows Script Component Wizard
- Enter "Easy" in the component name-field; progID automatically becomes Easy.WSC, which is fine.
- Click "Next"
- Set the "Language"-option for the component to "Other" and enter "PerlScript"
- Click "Next"
- Click "Next" in the "Properties" window
- Enter "SayHello" as a method with no parameters
- Click "Next"
- Click "Next" in the "Events" windows
- Click "Finish"
Following the simple steps above will create a nice skeleton to hold your code. With the exception for the classid, which is unique each time one is generated, it will look as illustrated.
<?xml version="1.0"?>
<component>
<registration>
description="Easy"
progid="Easy.WSC"
version="1.00"
classid="{74bb1ba9-2e69-4ad6-b02c-c52f3cbe153b}"
</registration>
<public>
<method name="SayHello">
</method>
</public>
<script language="PerlScript">
<![CDATA[
]]>
</script>
</component>
As seen above, you have a Window Script Component that is an XML file. The first declaration of the WSC enables strict XML. In that mode, the elements and attributes are case-sensitive, and attribute values must be enclosed within single quotes or double quotes. You may omit the XML declaration on top of the document and it will not be compiled as strictly, but in these examples, we will stick with XML conformity and leave the declaration in each document. Note: XML elements is that they, like HTML, have tag pairs like "<registration>" and "</registration>".
Secondly, you have a component element. This element is used to enclose each component. You will place one at the beginning, and one at the end. As an exception to the rule, there is an element that has a higher priority than the component, and that element must be used whenever you keep more than one component in your WSC file. Is is the package element. It will as a single element enclose all components; however, as mentioned, it is not required when you have one component within the file.
Next, the registration element contains the information about your component such as the progid, classid, description, and version number. Description is a string in which you can write a short abstract summary the funcitonality of your component. The progid is used by the program which creates an instance of your component, and the version number should be incremented if you release a new version of your component. The version number can also be appeneded to the progid as as Easy.WSC.1.00 when creating the instance of your component.
After the registration element, the data and functionality that the component expose are defined. The public element will hold properties, methods, and events. We declare a method by the name "SayHello" and then skip on down to the script-elements. As you can tell, there is no source code, so we need to fill that out. In the empty space, enter the following:
sub SayHello {
my($param) = shift @_;
return reverse($param);
}
Finally, your script file will look like this:
<?xml version="1.0"?>
<component>
<registration>
description="Easy"
progid="Easy.WSC"
version="1.00"
classid="{74bb1ba9-2e69-4ad6-b02c-c52f3cbe153b}"
</registration>
<public>
<method name="SayHello">
</method>
</public>
<script language="PerlScript">
<![CDATA[
sub SayHello {
my($param) = shift @_;
return reverse($param);
}
]]>
</script>
</component>
Now, it's time to register the component on the system so that it can be used. If we assume that the file is saved as c:\easy.wsc, there are two ways to do this:
regsvr32 c:\easy.wsc
in the command window.Either one of the above methods for registering a component should notify you
upon success or failure. After registering the component, you can use it from
within Active Server Pages by authoring a small script.
<%@Language=PerlScript%>
<%
$obj = $Server->CreateObject('Easy.WSC');
$retval = $obj->SayHello("Hello World");
$Response->Write($retval);
%>
Next, let's look at how to get a few properties included in the component, too. The following example will display how to read and write properties. It also includes comments, and, in addition, a custom subroutine is run when the component is registered and unregistered. What will happen is that when the component has been registered, a message box pops up with the text "Windows Script Component says: First.WSC has been registered!" and a similar tailored message when the component is unregistered by either one of the ways previously shown. So, open up the Windows Script Component wizard, again, and this time enter the read/write property "YourName", and the method "SayHello." The property is a global variable, and it internally is read and written by subroutines that implement the functionality needed for performing the given operation. The property can either use "get" and "set" attributes that point to the mentioned subroutines or there can be separate "get" and "set" elements as in the example. Their values point to internal Perl subroutines that do their thing on the property. It is a simple example, and it is as follows.
<?xml version="1.0"?>
<component>
<registration>
description="First"
progid="First.WSC"
version="1.00"
classid="{d0ccb637-bd0c-4c90-a4bd-7473f499d35a}">
<comment> This makes the messagebox pop up on
registration and unregistation </comment>
<script language="PerlScript">
<![CDATA[
use Win32;
sub register {
Win32::MsgBox('Windows
Script Component says: First.WSC has been registered!');
}
sub unregister {
Win32::MsgBox('Windows
Script Component says: First.WSC has been unregistered!');
}
]]>
</script>
</registration>
<comment> The methods and properties to expose to the data consumer
</comment>
<public>
<property name="YourName">
<get
internalName="hiddenGetProperty"/>
<put
internalName="hiddenSetProperty"/>
</property>
<method name="SayHello">
</method>
</public>
<comment> The code that implements the functionality of the component
</comment>
<script language="PerlScript">
<![CDATA[
use vars qw($YourName_Property);
sub hiddenGetProperty {
return $YourName_Property;
}
sub hiddenSetProperty {
my $param = shift;
$YourName_Property = $param;
}
sub SayHello {
return "Hello
$YourName_Property!";
}
]]>
</script>
</component>
Windows Script Components use XML to mark up the definition of the component and what is exposed by the component. Listed below are the elements that are valid XML elements for use within WSC components.
The component element is used to define the beginning and the end of the components. It encapsulates all other WSC tags as illustrated.
<component>
.
.
.
</component>
You can also set a boolean value of true (1) or false (0) for error checking or
debugging by using <? component error="true"
debug="true" ?>
In case your file will contain more than one component, you use a <component
id=componentID>
element for each, and you are required to then enclose
all components within a <package> element.
<package>
<component id="ComponentA">
.
.
.
</component>
<component id="ComponentB">
.
.
.
</component>
</package>
The default value for the component ID is ComponentCoClass, and when you define your own, either to identify the components or for generating a type library, the name must be unique and it must begin with a letter and contain no spaces.
When using more than one component within a package, you can create instances of
the other component within the current component by calling the createComponent(componentID)
function.
<registration>
contains the necessary information in order to successfully
register the component as a COM component, and it has two ways of writing.
Syntax:
<registration
progid="progID"
classid="GUID"
description="description"
version="version"
[remotable=remoteFlag]
/>
or
<registration
progid="progID"
classid="GUID"
description="description"
version="version"
[remotable=remoteFlag]
>
<script>
(registration and unregistration script)
</script>
</registration>
Most of the element attributes have been discussed in the introduction to the COM. However, remotable, which is optional, specifies if the component can be instantiated using DCOM. Its value can be true or false.
The public element implements Automation, formerly known as OLE Automation, and within the public elements you define the properties, methods, and events that the component exposes after it has been registered. This is done using the property, method, and event elements.
<public>
<property name="myProperty"/>
<method name="myMethod"/>
<event name="myEvent"/>
</public>
Property declares a property exposed by the component. Syntax:
<property name="myProperty"
[internalName="propertyScalarVariable"] />
The name of the property is what it will be exposed as, which needs to be the same name as the global variable used to represent the proeprty. In contrast, you can set an internalName attribute and run the property under another name within the <script> elements. You may also use the following syntax if you wish to implement the properties as subroutines that calculate the value of the property.
<property name="myProperty"
get="getSubroutineNamet" put="putSubroutineName"/>
or
<property name="myProperty">
<get [internalName="getSubroutineName"]
/>
<put [internalName="putSubroutineName"]
/>
</property>
Put indicates write-permissions while get indicated read-permissions. A combination of the both as seen above indicated read/wrote permissons.
The method elements define the methods that are exposed by the component.
Syntax:
<method name="methodName"
internalName="subroutineName" dispid=dispatchID />
or
<method name="methodName"
internalName="subroutineName" dispid=dispatchID >
[<parameter name="param"/>]
</method>
The dispatchID is automatically generated unless you specify "0" as a dispatchID, which will result in it being the default method of the component. Parameter elements may belong to the method, if defined.
<parameter name="param"/>
Declare an event that can be fired from within the component.
Syntax:
<event name="name" dispid=dispatchID/>
DispatchID is a numeric value that is generated automatically unless you specify
it. From within your script, you use the fireEvent(eventname);
method to execute an event.
The Implements element enables you to include more COM interface handlers within your script.
Syntax:
<implements type="COMHandlerName"
[id="internalName"] [default=fAssumed] >
Information related to the COMHandler goes here
</implements>
COMHandlerName is the name of an interface handler that you wish to implement, and internalName is the name to which you want to reference the COMHandler. This is useful to avoid naming conflicts because the components exposed data is available in the global namespace. The fAssumed flag is a boolean value used to define if internalName is assumed in scripts, which is the default setting.
The Windows Script Components run-time (scrobj.dll) implement handlers for Automation and ASP, and also handlers for accesing the DHTML object model in the page.
<component>
<registration progid="SimpleASP.WSC"/>
<public>
<method name="TestWrite"/>
</public>
<implements type="ASP"/>
<script language="PerlScript">
<![CDATA[
sub TestWrite {
Response.Write("Hello
World, says ASP!")
}
]]>
</script>
</component>
The script element lets you define the scripting language to use, and then with its closing-tag functions as delimiters for the script code.
Syntax:
<script language="languageName"> code </script>
For example.
<?XML version="1.0"?>
<component>
...
<script language="PerlScriptt">
<![CDATA[
sub ReturnValue {
#
# Perl code here
#
}
]]>
</script>
</component>
The object element enables you to create an instance of a COM object that you want to use within your Windows Script Component.
Syntax:
<object id="objectID" [classid="classid:GUID" |
progid="progID"]/>
The objectID is the name by which you want to reference the object within your script, and you can use either the progID or classID to locate the component. For example, how to create an instance using the COM components progID:
<?XML version="1.0"?>
<component>
<object id="conn" progid="ADODB.Connection.2.5">
<script language="PerlScript">
<![CDATA[
sub OpenConn {
my($status);
$conn->Open('ADOSamples');
if($conn->{State} ==
adStateOpen) {
$status
= "Connection was a success";
}
else {
$status
= "Connection failed because ";
$status
.= $conn->Errors(0)->{Description};
}
$conn->Close();
return $status;
}
]]>
</script>
</component>
The resource element is a placeholder for strings or numeric data that should be separate from the script commands yet may be used within the script.
Syntax:
<resource id="resourceID"> text or number to represent
resource goes here </resource>
You use the getResource(resourceID)
to retrieve the contents of the
resource specified in the resourceID parameter.
You can import external type libraries into your component by using the reference element. By importing a type library into your component, you will be able to naturally access the constants that belongs to it, too.
Syntax:
<reference [object="progID" | guid="typelibGUID"]
[version="versionNo"] />
For example, you can use an ActiveX Data Object within your component.
<?XML version="1.0"?>
<component>
<reference object="ADODB.Connection.2.5"/>
<registration progid="SimpleADO.WSC"/>
<public>
<method name="OpenConn"/>
</public>
<script language="PerlScript">
<![CDATA[
sub OpenConn {
my($status);
$conn = new
Win32::OLE("ADODB.Connection");
$conn->Open("ADOSamples');
if($conn->{State} ==
adStateOpen) {
$status
= "Connection was a success";
}
else {
$status
= "Connection failed because ";
$status
.= $conn->Errors(0)->{Description};
}
$conn->Close();
return $status;
}
]]>
</script>
</component>
The Comment element encloses descriptive strings that are used to describe the on-goings in the code and the strings are ignored by the language parser when preparing the code to execute.
<comment>
Author: John Doe
Description: This WSC component is used to output a
Binary Large Object (BLOB)
from an SQL Server database . . .
</comment>
Copyright (c) 2000 Tobias Martinsson. All Rights Reserved.
When included as part of the Standard Version of Perl, or as part of its complete documentation whether printed or otherwise, this work may be distributed only under the terms of Perl's Artistic License. Any distribution of this file or derivatives thereof outside of that package require that special arrangements be made with copyright holder.
Irrespective of its distribution, all code examples in this file are hereby placed into the public domain. You are permitted and encouraged to use this code in your own programs for fun or for profit as you see fit. A simple comment in the code giving credit would be courteous but is not required.
Windows Script Components is copyright (c) 1991-2000 Microsoft Corporation. All rights reserved.
Windows Script Components |