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.
|