eXtropia: the open web technology company
Technology | Support | Tutorials | Development | About Us | Users | Contact Us
Resources
 ::   Tutorials
 ::   Presentations
Perl & CGI tutorials
 ::   Intro to Perl/CGI and HTML Forms
 ::   Intro to Windows Perl
 ::   Intro to Perl 5
 ::   Intro to Perl
 ::   Intro to Perl Taint mode
 ::   Sherlock Holmes and the Case of the Broken CGI Script
 ::   Writing COM Components in Perl

Java tutorials
 ::   Intro to Java
 ::   Cross Browser Java

Misc technical tutorials
 ::   Intro to The Web Application Development Environment
 ::   Introduction to XML
 ::   Intro to Web Design
 ::   Intro to Web Security
 ::   Databases for Web Developers
 ::   UNIX for Web Developers
 ::   Intro to Adobe Photoshop
 ::   Web Programming 101
 ::   Introduction to Microsoft DNA

Misc non-technical tutorials
 ::   Misc Technopreneurship Docs
 ::   What is a Webmaster?
 ::   What is the open source business model?
 ::   Technical writing
 ::   Small and mid-sized businesses on the Web

Offsite tutorials
 ::   ISAPI Perl Primer
 ::   Serving up web server basics
 ::   Introduction to Java (Parts 1 and 2) in Slovak

 

writing COM components in Perl

by Nikhil Kaul and Selena Sol

When you strip away all the layers of marketing hype and industry jargon, Microsoft's Component Object Model (COM) is simply a technology that allows you to create an object in the language of your choice, and to make the functionality of that object available to any other object, or any other application, regardless of the language that the other object or application is written in.

Yikes! Quite a mouthful. Maybe it would be useful to come at this by way of an example.

Suppose that we are required to write three applications. These three applications will be identical in terms of functionality. Specifically, they will gather data entered on a form and email it.

However, though all three applications will do the same thing, in our hypothetical example, each application will be programmed in a different programming language. Specifically, we will write one form processor in C++, one in Java, and one in Perl.

In writing these applications, it is likely that we will define several objects.

For example, there will be an object representing the form and there will be an object in charge of connecting to a mail server and delivering the email message.

In order for each program to work, each of these objects, will have to be defined in each of the three languages. Thus, you will have one mailer in C++, one mailer in Java, and one mailer in Perl.

As you might imagine, this is quite inefficient. The algorithm involved in connecting to a mail server and delivering the email message is not really language specific. So why should we have to write it three times?

Well traditionally, the reason is because C++ applications can't really speak to Java objects. Java objects can't speak to Perl objects and Perl objects can't speak to Visual Basic objects. Traditionally, objects are like cliques in high school! Groups never mingle.

In the COM universe however, anything can talk to anything else. Thus, in the COM universe, you do not write three mailer objects. Instead, you write one special mailer object that can be used by any application in any language. It is an object that conforms to the COM specification. It is a COM object.

As we said, this COM object may be written in any language you like (C++, Java, Perl, VB, etc) but because it conforms to the COM specification, it will be available as a resource to any COM-aware applications regardless of what language that application is written in. Thus, you could write a single mailer (as a COM object) in Perl and make that mailer available to all your programs, whether those programs are written in Perl, Java, VB, or C++.

What are COM objects?  
So how does COM allow you to perform this magic?

    The key to writing COM components in Perl is Activestate's Perl Development Kit [ the PDK - currently in release 1.2.4 ] . The reason you need a third party tool in order to turn your Perl objects into COM components will become apparent as you read further.

When you break them down to their essences, all objects do the same basic things no matter what languages they are written in. Specifically, all objects have methods and properties that define how they work internally.

Further, all objects expose a set of "public" methods that allow objects in the outside world to interact with them.

Finally, it is often the case that methods accept a set of incoming parameters that modify the behavior of the method, and return some type of output to the caller of the method.

Usually, every programming language will implement these basic characteristics, but will do so in slightly different ways. For example, consider the same add() method defined first in Java and then in Perl:

public int add(int a, int b) {
    return a+b;
}

sub add {
    my $a = shift;
    my $b = shift;
    return $a + $b;
}

Quite different, eh? That's why it is so hard for languages to interact.

The beauty of COM is that it defines a language-independent specification that abstracts out these basic object properties.

COM is a protocol that defines a standard way for objects to speak to one another, regardless of the specific way in which they actually implement methods and properties.

More specifically, COM is a specification that defines how to call methods on objects.

The IDL  
Ask a seasoned COM developer what concept is at the heart of COM's egalitarian society, and the response will probably be something to the effect of "COM depends on interfaces".

An interface provides a detailed contract that specifies exactly how the objects should be allowed to communicate.

So why go through the trouble of writing a contract for every object in your program?!?

Well, the interface provides a protective layer that sits between objects.

As such, an interface assures both parties that no matter how each object changes internally, that the two objects will always be able to communicate because they will always implement the interface in exactly the same way.

In other words, implementation is entirely opaque to those who use the object - and this is exactly the way that it should be. Not only do I now want to spend time understanding how object X implemented its functionality, but if object X changes that implementation, it should not break objects that had depended on it.

Let's look at this by example. Consider what happens if you press the accelerator in a car - it speeds up. The wonder of a car is that you don't have to know anything whatsoever about the functioning of the engine in order to accelerate. You just depress the gas pedal and go. And this is the same for any car you use!

Further, how you use the car is independent of how the engine changes, you always go faster by doing the same thing - pressing the accelerator. The engine's implementation, whether it is a 6 litre or a 4 litre, whether it uses the newest new fangled electro-E-Virtual-Cyber carburettor or not, is entirely internal. You interact with nothing except its interface. In fact you don't need to know or even care about how the engine works!

To abstract away implementation, COM defines an interface definition language [IDL]. This IDL is used to specify the abstract characteristics of methods (such as they take parameters and can return results) such that the concept of methods is separated from the implementation of methods.

    If you are a Java programmer, think of COM as being roughly similar to the way that Javabeans work. When you write a Javabean and drop it into a Bean-aware development environment, the environment only knows what your object is capable of because you have written your object so as to comply with the Javabean pseudo API.

The interface definition language wraps your object and does nothing else except describe your object in terms of the methods and properties that it exposes to the world at large. It is a contract that your object makes with the COM runtime about what it is capable of. The implementation of the functionality is of no importance to COM - it is your responsibility as a developer to provide it.

As a result, COM does not understand anything other than an IDL. Thus, if you write an object and you want to package it as a COM component, you [or your development environment] must define the IDL that describes your object in terms of COM.

Once the IDL is validly defined, you can plop your object into any COM-aware environment and other objects can begin to use it.

    COM objects run within a COM runtime. The runtime is responsible for all the message passing between COM components and applications, or between COM components and each other. Because your object is defined in terms of a valid IDL, the COM run time will have all it needs to do the message passing.

Objects actually define multiple interfaces depending on their complexity. But this should not be hard to understand. Most objects can do many different things. Consider the human being in terms of a programming object.

Humans are very flexible in terms of their capabilities. They can, amongst other things, move as well as sense their surroundings. Within the first category, the object may have methods such as walk, run and crawl. Within the second category, senses, we may choose to include such things as hearing, seeing, tasting and touching.

While it is therefore true to say that the entire interface of the [ simplified ] human object are walking, running, crawling, hearing, seeing, tasting, and touching, COM developers tend instead to describe their objects in terms of functionally related methods instead, although this division is not mandatory.

The human COM object, therefore, would be more likely described as implementing 2 interfaces, the movement interface and the sensing interface. Each interface would then contain a list of its related functions. Of course it is the ubiquitous IDL that is going to provide the interface descriptions.

Of course, although any object can have multiple interfaces, they must have at least one.

Each and every COM object that you write implements the IUnknown interface by default (you do not need to do anything to ensure this). It is the IUnknown interface that provides the absolute minimum functionality that every COM object is going to require, regardless of the methods/properties that you provide via the custom interfaces that you implement. The methods of the IUnknown interface deal with such things as reference counting for the object, so as to ensure it is garbage collected at the appropriate time. It is also via the IUnknown interface that the object can be queried regarding the other interfaces that it implements, and therefore about its capabilities.

COM objects that are going to be used by languages without strict data typing [ such as VBScript, Perl, and Javascript ], also implement the IDispatch interface.

A detailed discussion of the functionality and internals of the IUnknown and IDispatch interfaces is really beyond the scope of this article, as it would lead very quickly into the detailed internals of COM. But you should at least get familiar with the names.

    COM components run in the context of Microsoft's COM runtime. It is the COM runtime that is responsible, transparently, for making sure that method calls reach the proper objects and methods within a COM object, and that the results of these calls are returned to the calling application as expected.

    The objects that comprise the COM runtime, and do all this work behind the scenes are packaged in a number of Dynamic Link Libraries [DLLs], that are installed on the system.

    In the context of Perl, the PDK is this development environment that is going to take your Perl components, and describe them in a COM compliant manner - in effect, wrapping your Perl object in a COM IDL wrapper.

    So where do you obtain the PDK? How do you install the PDK? How do you use the PDK? All these questions will be answered in good time. But first, we have not completed our whistle stop tour of COM.

How does the IDL describe my object?  

The IDL description of your object operates in terms of Interface definitions.

If an object states in the IDL that it implements a particular interface then it is advertising the fact that it implements all the methods within that interface.

For example, consider a SeniorManager object. This object might implement the following methods

  • hirePeople()
  • firePeople()
  • startProject()
  • endProject()

The IDL description of this object could state that this object implemented 2 custom interfaces, the changeHeadCount interface [ comprising methods 1 and 2 ] and the manageProject interface [ comprising methods 3 and 4 ].

In fact, given the interface naming conventions that COM uses, these 2 interfaces would probably more likely be called IchangeHeadCount and ImanageProject, with the leading "I" standing, funnily enough, for "Interface".

The topic of COM interfaces is substantially more detailed than has been laid out above, but a Perl developers writing COM components does not need to have more than a very basic knowledge of the significance of interfaces in COM. This is for 2 reasons

  • COM objects written in Perl have their IDL description generated by the PDK. This means that you do not have to worry about writing any of your own interface definition for your object, as you might have to were you implementing it in C++.

  • Because the PDK generates the IDL description of the methods of your object, you have no way of defining any custom interfaces that the COM object implements. Consider the IchangeHeadCount and ImanageProject interfaces mentioned above. If you wanted to state that your object implemented these, you would have to have some way of writing the IDL to say as much. The PDK, however, does this all for you. All Perl COM components implement only one interface, called "I[perl class name]" - so for example the SeniorManager object would be stated to implement ISeniorManager, which would comprise, all together, the 4 methods listed above. It is the PDK, therefore, that is responsible for defining the custom interface that your class implements.

This is nothing more than a very cursory introduction to a topic that lies at the very heart of COM - interface based programming. For those of you who wish to investigate the subject above and beyond the scope of this article, topics such as the IDispatch and IUnknown default COM interfaces, and early and late binding [ amongst many other things ] await you.

How do I package my PERL code as a COM component?  

And so, on to the PDK itself. As we said above, the PDK is the development environment you use to create Perl COM objects. The kit itself can be downloaded from

http://www.activestate.com/pdk/download.htm

NOTE: The PDK itself operates in the context of a Perl runtime. As a result, you must have a Perl interpreter installed in order to use it.

The PDK installation process is extremely simple. The kit is downloaded in the form of a self installing executable. Double clicking on the file will start the installation process.

During the installation process, you will be presented with a number of screens that ask you for preferences regarding [ amongst other things ] where you want the kit to be installed, as well as which components you want installed.

The installation is straightforward - you do not have to alter any of these choices. Move from screen to screen, accepting all defaults, and then allow installation to complete.

When this is done, find the directory where the installation was placed [ "ActivePerl" by default ], and navigate to the "bin" sub-directory. Inside the bin directory, you will find a perl script called PerlCtrl.pl.

It is this script that is responsible for generating a COM component from your Perl class.

The first thing to do is to make sure that PerlCtrl.pl is in your system's PATH environment variable, so that it can be invoked by name from the command line.

Go to the command prompt, and type "PerlCtrl.pl". If you receive a screen full of usage instructions about the script, you are ready to start. If, on the other hand, you receive an error message telling you that the command was not recognised, you need to set your PATH environment variable to include the path to the ActiveState/bin directory on your system.

Note that if you are uncomfortable modifying your PATH variable, you can always just call PerlCtrl.pl absolutely such as:

c:\ActiveState\bin\PerlCtrl.pl

Congratulations, you are now ready to write your first Perl COM component.

By way of an example, lets write a COM component that is capable of sending mail messages via arbitrary SMTP servers. We will concentrate on the Perl first, and look at the steps required to package the module as a COM object later on.

The Perl code for the object appears below [ with in-line comments ]

package WebMail;
use Mail::Sender;

sub send {
    my($from, $replyto, $to, $cc,
       $bcc, $smtp, $subject, 
       $message, $file) = @_;

    my $sender;

              # retvalue reflects the value that is going to 
              # be returned from this sub and its value is 
              # an indication of whether the mailing operation 
              # has succeeded or not -1 is true, 0 is false.  

    my $retValue=-1;

              # attempt to construct the Mail::Sender 
              # object that is going to perform the mailing
              # operation

    ref ($sender = Mail::Sender->new({
        from    =>      $from,
        replyto =>      $replyto,
        to      =>      $to,
        cc      =>      $cc,
        bcc     =>      $bcc,
        smtp    =>      $smtp,
        subject =>      $subject,
    })) or $retValue=0;

              # if the construction of the Mail::Sender 
              # object has succeeded, then attempt to send the
              # message.

    if($retValue!=0) {
              # the method that is used to send the message 
              # out depends on whether a file needs to be 
              # attached to the mail or not.	

        if(!defined $file) {
            ref($sender->MailMsg({
                msg=>$message
            })) or ($retValue=0);
        } else {
            ref($sender->MailFile({
                msg=>$message,
                file=>[(split/\,/,$file)]
            })) or ($retValue=0);
        }
              # if the transmission of the message 
              # succeeded, close the mailer object
        if($retValue!=0) {
            $sender->Close();
        }
    }
              # return the result of attempting to send 
              # the specified message

    return($retValue);
}

sub getError {
    return ($Mail::Sender::Error);
}
1; 

This is all relatively straightforward. We have written a mail object that takes a number of parameters, and sends a message based upon them. Really the only point worth noting is that the return value of the "send" subroutine within the Perl is set to either 0 or -1. This is because these values correspond to boolean false and true [ respectively ] within VBScript, and we are going to test our completed COM object by calling it from an ASP page.

All the work of actually performing the mailing is performed by a class called Mail::Sender. Mail::Sender is a standard CPAN module, and it is your responsibility to ensure that the module is installed on your system prior to attempting this example.

So now we must consider how to wrap the Perl objects up as COM components. In order to do this, we have to use the PDK to generate the IDL description of our object, as well as package the object into a Dynamic Link Library [DLL] that is going to be registered with the system.

Think of a DLL as a repository of code that can be dynamically referenced at runtime - thus importing the objects that it contains into memory for use. Conceptually, It is not a million miles removed from the Perl "require" command. The DLL will contain your COM object.

The PDK needs help, however, in order to generate the IDL description of your object. It needs you to write a template [ which is pure Perl code ], that describes the functionality of your object in terms of COM. The template itself is boilerplate - the PDK will generate it automatically. All you need to do is edit it to reflect the functionality of your object. The PDK will interpret it and generate the IDL description of your class, and package it all up into a COM object.

Generate the template by typing "PerlCtrl.pl -t" at the command line. The result ought to look something like this:

=POD
=BEGIN PerlCtrl
    %TypeLib = (
        PackageName     => 'MyPackage::MyName',
        TypeLibGUID     => '{32E6513E-DF20-11D3-B454-00805F9BDE4A}', # do NOT edit this line
        ControlGUID     => '{32E6513F-DF20-11D3-B454-00805F9BDE4A}', # do NOT edit this line either
        DispInterfaceIID=> '{32E65140-DF20-11D3-B454-00805F9BDE4A}', # or this one
        ControlName     => 'MyApp.MyObject',
        ControlVer      => 1,  # increment if new object with same ProgID
                               # create new GUIDs as well
        ProgID          => 'MyApp.MyObject',
        DefaultMethod   => 'MyMethodName1',
        Methods         => {
            'MyMethodName1' => {
                RetType             =>  VT_I4,
                TotalParams         =>  5,
                NumOptionalParams   =>  2,
                ParamList           =>[ 'ParamName1' => VT_I4,
                                        'ParamName2' => VT_BSTR,
                                        'ParamName3' => VT_BOOL,
                                        'ParamName4' => VT_I4,
                                        'ParamName5' => VT_UI1 ]
            },
            'MyMethodName2' => {
                RetType             =>  VT_I4,
                TotalParams         =>  2,
                NumOptionalParams   =>  0,
                ParamList           =>[ 'ParamName1' => VT_I4,
                                        'ParamName2' => VT_BSTR ]
            },
        },  # end of 'Methods'
        Properties      => {
            'MyIntegerProp' => {
                Type                => VT_I4,
                ReadOnly            => 0,
            },
            'MyStringProp' => {
                Type                => VT_BSTR,
                ReadOnly            => 0,
            },
            'Color' => {


                Type                => VT_BSTR,
                ReadOnly            => 0,
            },
            'MyReadOnlyIntegerProp' => {
                Type                => VT_I4,
                ReadOnly            => 1,
            },
        },  # end of 'Properties'
    );  # end of %TypeLib
=END PerlCtrl
=cut

Looks nastier than it really is. The most important thing to remember is that ALL the above is generated automatically, and you actually must modify very little in order to make the standard template reflect the functionality of the WebMail class.

So lets look at the steps needed to customise the template by going through it in parts:

=POD
=BEGIN PerlCtrl
    %TypeLib = (
        PackageName     => 'MyPackage::MyName',
        TypeLibGUID     => '{32E6513E-DF20-11D3-B454-00805F9BDE4A}', # do NOT edit this line
        ControlGUID     => '{32E6513F-DF20-11D3-B454-00805F9BDE4A}', # do NOT edit this line either
        DispInterfaceIID=> '{32E65140-DF20-11D3-B454-00805F9BDE4A}', # or this one
        ControlName     => 'MyApp.MyObject',
        ControlVer      => 1,  # increment if new object with same ProgID
                               # create new GUIDs as well
        ProgID          => 'MyApp.MyObject',

PackageName:
This reflects the name of the package in which you have placed the Perl object

TypeLibGUID, ControlGUID, and DispInterfaceIID:
As the comments above state, do NOT edit these. They are the unique identifiers by which the system upon which the component is registered is going to locate it.

ControlName:
This is the descriptive name you give the component

ProgID:
This is the identifier that the person who uses your object is going to use to identify and instantiate it - for example if you were to use "Server.CreateObject("MyApp.MyObject")", you would define the following

DefaultMethod   => 'MyMethodName1',
Methods         => {
            'MyMethodName1' => {
                RetType             =>  VT_I4,
                TotalParams         =>  5,
                NumOptionalParams   =>  2,
                ParamList           =>[ 'ParamName1' => VT_I4,
                                        'ParamName2' => VT_BSTR,
                                        'ParamName3' => VT_BOOL,
                                        'ParamName4' => VT_I4,
                                        'ParamName5' => VT_UI1 ]

The methods section is really a number of repetitions. It is the section that allows you to describe the input and output parameters and data types that the various methods of your object have as their signature.

DefaultMethod:
This is the method that is going to be called from your object if someone uses it in a method context without specifying a method name. For example, if your object name is X, and its default method is Y, then saying X("some argument") will call method Y on the "some argument" parameter.

Methods:
Provide a method name where the above template code says 'MyMethodName1'. Specify the total number of parameters that the method accepts [ Total Params], and the number of those parameters that are optional [NumOptionalParams ] .

In the ParamList, where the template code says "ParamName1" and so on, specify the parameter names that the method expects. The names you give your params ought to reflect the names you have given them in your Perl code - so a parameter labelled $smtpServer translates to a parameter name of "smtpServer" in the template code.

The datatypes that the parameters are designated as are expressed in terms of Visual Basic Variant data types. These comprise the following:

VB Variant Data Type    Description

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

If you want to convert a Perl data type to a VB Variant data type, you need to look at the documentation for the Win32::OLE::Variant module. This is only really relevant, however, if you are using pure Perl scripts to talk to COM objects on the windows platform. In that case, you would need to perform the data conversion from Perl data types to native VB variant data types before passing them to the COM objects you have instantiated in your Perl script.

But that is an adventure for another day.

In our case, we are doing something different. We are writing a COM object in Perl that is returning data to whatever calling context it is being used in. The COM layer will manage the conversion of our return data into the correct VB variant type. Of course it would not be able to do that if we wrote our template description of the WebMail methods incorrectly. In a nutshell, you are going to cause problems for yourself if you state that a particular method in WebMail returns, for example, a VT_DATE. None of the methods actually return a string of the correct format to be coerced into such a variant type.

For WebMail, we are only concerned with 2 VB variant data types. VT_BSTR [ strings ] and VT_BOOL [ booleans ]. All the arguments to the "send()" method are strings, the method returns -1 or 0, a VT_BOOL, and the getError() method, in cases where sending the mail has failed, returns a VT_BSTR [ the actual error message ].

Properties      => {
               'MyIntegerProp' => {
                Type                => VT_I4,
                ReadOnly            => 0,

The properties section is relatively straightforward. List the instance variables of your object, and what VB variant data types that they map to. Also, state whether the properties are read only or not.

And that is it. With all this in mind, it comes as little surprise that generating the template code for the WebMail component is very straightforward. The template we are going to use follows:

=POD
=BEGIN PerlCtrl
    %TypeLib =
    (
	PackageName     => 'WebMail',
        TypeLibGUID     => '{B3C98206-C910-11D3-B450-00805F9BDE4A}', # do NOT edit this line
        ControlGUID     => '{B3C98207-C910-11D3-B450-00805F9BDE4A}', # do NOT edit this line either
        DispInterfaceIID=> '{B3C98208-C910-11D3-B450-00805F9BDE4A}', # or this one
	ControlName     => 'WebMail',
	ControlVer      => 1,  # increment if new object with same ProgID
			       # create new GUIDs as well
	ProgID          => 'WebMail.Mailer',
	DefaultMethod   => '',
	Methods    =>
	{
	    'send' =>
	    {
		RetType             =>  VT_BOOL,
		TotalParams         =>  9,
		NumOptionalParams   =>  0,
		ParamList           =>[
					 'from'	=> VT_BSTR,
					 'replyto' => VT_BSTR,
					 'to' => VT_BSTR,
					 'cc' => VT_BSTR,
					 'bcc' => VT_BSTR,
					 'smtp' => VT_BSTR,
					 'subject' => VT_BSTR,
					 'message' => VT_BSTR,
					 'file' => VT_BSTR,
				       ]
	    },
	    'getError' =>
	    {
		RetType             =>  VT_BSTR,
		TotalParams         =>  0,
		NumOptionalParams   =>  0,
		ParamList           =>[]
	    }
	},  # end of 'Methods'
	Properties      => {}
    );  # end of %TypeLib
=END PerlCtrl
=cut

Of course, the TypeLibGUID, ControlGUID, and DispInterfaceIID will be different in the case of the template that you generate with the PerlCtrl.pl -t option, but otherwise, you should edit the template to appear exactly as above.

The next thing you need to do it paste the template code in at the very end of the WebMail.pm file [ after the line that reads "1;" ].

You are now almost ready for PerlCtrl to weave its magic. You have 2 options open to you. Either you can generate a freestanding COM component, or you can generate a dependent COM component. A freestanding component has everything that it needs packaged up inside it, including a Perl interpreter. A dependent one does not, and as such needs to be used on a machine with Perl installed on it.

The freestanding component will be larger than the dependent one.

Before you create your COM component within its DLL, you have to cater for the fact that, at the time of writing, version 1.2.4 of the PDK has a bug in it. The kit cannot deal, according to ActiveState, with lines of Perl that use require statements of the form:

require "aModule.pm";

These need to appear as:

require aModule;

[ which in any event, assumes that aModule is a ".pm" file anyway ].

So you need to alter line 8 of Mail::Sender from

require "Exporter.pm"

to

require Exporter;

If you do not, the PDK is going to end up generating a faulty COM object, and you are going to drive yourself insane trying to work out what is wrong with it. Trust us - we speak from experience.

To generate the DLL, type PerlCtrl.pl -f WebMail.pm to generate a freestanding component, or -d for a dependent one.

If you receive the message:

Creating PerlCtrl WebMail.dll ...
Your license has expired. Please purchase 
a license from http://www.ActiveState.com
The PerlCtrl Builder will now shutdown...
PerlCtrl ERROR: BeginCreatePerlCtrl() failed

Then you need to obtain a new license key from the ActiveState site in order to run PerlCtrl. It is available free from the ActiveState site, and will be sent to you via email as a self-installing exe. Run the file to install the license.

The URL you need to visit to obtain the license is:

https://www.ActiveState.com/cgibin/license/pdk12/newtrial.pl

The build process will produce output that looks like this:

WebMail.pm syntax OK
Creating PerlCtrl WebMail.dll ...
Adding Module: C:/APPS/ActivePerl/site/lib/MIME/QuotedPrint.pm
Adding Module: C:/APPS/ActivePerl/lib/Symbol.pm
Adding Module: C:/APPS/ActivePerl/lib/re.pm
Adding Module: C:/APPS/ActivePerl/lib/Fcntl.pm
Adding Module: C:/APPS/ActivePerl/lib/Exporter.pm
Adding Module: C:/APPS/ActivePerl/lib/strict.pm
Adding Module: C:/APPS/ActivePerl/site/lib/MIME/Base64.pm
Adding Module: C:/APPS/ActivePerl/lib/vars.pm
Adding Module: C:/APPS/ActivePerl/site/lib/Win32/OLE/Lite.pm
Adding Module: C:/APPS/ActivePerl/lib/SelectSaver.pm
Adding Module: C:/APPS/ActivePerl/site/lib/Win32/OLE.pm
Adding Module: C:/APPS/ActivePerl/lib/IO/Seekable.pm
Adding Module: C:/APPS/ActivePerl/lib/DynaLoader.pm
Adding Module: C:/APPS/ActivePerl/lib/auto/DynaLoader/dl_expandspec.al
Adding Module: C:/APPS/ActivePerl/lib/auto/DynaLoader/dl_findfile.al
Adding Module: C:/APPS/ActivePerl/lib/auto/DynaLoader/dl_find_symbol_anywhere.al
Adding Module: C:/APPS/ActivePerl/lib/auto/DynaLoader/autosplit.ix
Adding Module: C:/APPS/ActivePerl/lib/Carp.pm
Adding Module: C:/APPS/ActivePerl/lib/IO/File.pm
Adding Module: C:/APPS/ActivePerl/lib/Socket.pm
Adding Module: C:/APPS/ActivePerl/lib/File/Basename.pm
Adding Module: C:/APPS/ActivePerl/lib/FileHandle.pm
Adding Module: C:/APPS/ActivePerl/lib/integer.pm
Adding Module: C:/APPS/ActivePerl/lib/AutoLoader.pm
Adding Module: C:/APPS/ActivePerl/site/lib/PerlCOM.pm
Adding Module: C:/APPS/ActivePerl/site/lib/Mail/Sender.pm
Adding Module: C:/APPS/ActivePerl/lib/IO/Handle.pm
Adding Binary: C:/APPS/ActivePerl/site/lib/auto/MIME/Base64/Base64.dll
Adding Binary: C:/APPS/ActivePerl/lib/auto/Socket/Socket.dll
Adding Binary: C:/APPS/ActivePerl/lib/auto/Fcntl/Fcntl.dll
Adding Binary: C:/APPS/ActivePerl/site/lib/auto/Win32/OLE/OLE.dll
Adding Binary: C:/APPS/ActivePerl/lib/auto/IO/IO.dll
all done.

[ although it will be a lot shorter if you are building a dependent COM component, seeing as the assumption will be made that all the modules required for the component will be installed on the machine where it is going to be used. ].

The result of this operation will be the creation of a DLL called WebMail.dll, containing your COM component.

Next, register your component with the system. Type "regsvr32 WebMail" . You should see a dialog box that announces that the component has been validly registered.

Incidentally, if you ever want to change the functionality of your component, you will have to:

  • Change the Perl code, and the template description of your object at the end of it.
  • do NOT change the automatically generated ID strings allocated to your component. The same object should always be referenced by the same unique ID strings
  • regenerate the DLL via PerlCtrl.pl -f or -d . To do so, you are going to have to first UN register the DLL containing the old COM object. To do so, type "regsvr/u WebMail".

After you have regenerated the DLL, you will need to re-register it to use it.

So, finally, let us write a brief ASP page to test our object.


<%
	option Explicit
	Dim mailer
	Dim sent

	set mailer=Server.CreateObject("WebMail.Mailer")
	sent = mailer.send(
            "[FROM]",
            "[REPLY TO ADDRESS]",
            "[TO ADDRESS]",
            "[CC ADDRESS/ES]",
            "[BCC ADDRESS/ES]",
            "[SMTP SERVER IP ADDRESS]",
            "[SUBJECT]",
            "[MESSAGE BODY]",
            "[FILE/S TO ATTACH]"
        )

	If(sent=false)Then
		Response.Write(mailer.getError())
	End If	

	If(sent=true)Then
		Response.Write("Message sent")
	End If	
%>

Bear in mind that the underlying module that is going to be performing the mailing is Mail::Sender. As a result, you can count on Mail::Sender's behaviour, so that the CC and BCC addresses can be comma-separated strings.

Filenames for attachments ought to be absolute paths from root.

Running the page in your browser will send the mail according to the parameters that you have inserted in your ASP page.

Once you have grasped the fundamentals contained within this article, writing other COM objects in Perl simply comprises variations on a theme.

  • Write your Perl code. Test that it works as pure Perl
  • Generate the template description of the functionality of your class via PerlCtrl.pl with the -t flag
  • Customise the template
  • Attach the template to your code, and package your code as either a freestanding or dependent COM object
  • Register and use the COM object you have created.