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:
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:
<%
     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.
No comments yet. Be the first to comment!