devxlogo

Make Your Existing Perl Apps .NET-compliant

Make Your Existing Perl Apps .NET-compliant

The Perl language gains popularity daily among programmers from all over the world because of its flexibility, vast sphere of application, and easy source-code availability. The Comprehensive Perl Archive Network (CPAN) distributes Perl modules as open-source software. You can download the packages from CPAN using the ppm tool that comes with the ActivePerl distribution. ActiveState’s latest release of PerlNET, included in the Perl Dev Kit, brings Perl programming into the realm of .NET compliant languages, which may increase Perl usage substantially.

PerlNET allows you to create .NET-compliant applications and components using the Perl language. One key feature is that you can use existing Perl packages (modules) from CPAN within the .NET environment and in your favorite .NET compliant language without rewriting the Perl code. Instead, you create a simple wrapper class in PerlNET for the package, and enumerate all the module methods or properties you want to expose to .NET. In other words, you need to provide an interface definition through which NET programs will interact with the Perl module. PerlNET introduces new Plain Old Documentation (POD) blocks for this purpose, using the syntax:

=for interface. . .=cut

Inside these blocks, you define the prototypes of methods and declare properties. You can separate the definitions into several blocks or unify them under a single block. When providing an interface definition for Core Perl modules, include the [interface: pure] attribute inside one of the POD blocks (usually the first). This instructs PerlNET to create a .NET component in the Core Perl manner:

=for interface   [interface: pure]   . . .=cut

Because .NET is a strongly typed environment, you must provide the types for methods arguments, for methods return values, and for properties when defining the interface. For example, if some subroutine Foo accepts a string and returns an integer, it should have the following prototype in the =for interface block:

=for interface   int Foo(str text);=cut

Table 1 shows .NET types from the System namespace and the corresponding PerlNET types.

.NET Type

PerlNET type

Boolean

bool

Char

char

Sbyte

sbyte

Int16

short

Int32

int

Int64

long

Byte

byte

UInt16

ushort

UInt32

uint

UInt64

ulong

Single

float

Double

double or num

Decimal

decimal

Object

any

String

str

The type system in PerlNET is almost identical to that of the .NET C# language. There are two exceptions: str and any types are used for the System.String and System.Object classes respectively (C# uses string and object aliases for them). You may also use both the num and the double types for denoting the System.Double value type.

Every non-static method of the Core Perl module expects a reference to a hash as a first argument. This hash may be also referred to as a package. It stores values of properties for the current instance. Its reference plays the same role as the this pointer in C++ or Me in VB. PerlNET automatically passes a reference to a package as a first argument to all non-static methods, and you should not list it in the arguments list.To create a wrapper class, you create an interface based on the functionality of the Perl module rather than providing a complete implementation. The process of wrapping a module is like application design in reverse. Given an existing package, you must decide which methods and properties to expose or not to expose simply by including them in?or excluding them from?the interface. Before wrapping any module, be sure that it is installed on your system. Remember that when writing a PerlNET wrapper class, make sure you give it the same name as the Perl package you’re wrapping.

Here’s a simple module from CPAN, Text::Typoifier, which randomly creates errors in the text. This module has many procedures, but most of them are for inner use. To exploit the functionality of the module, you must be able to call the constructor, the transform method, and the errorRate property. Therefore, the wrapper component will have three entries in the interface definition:

# Typoifier.pmpackage Text::Typoifier;=for interface   [interface: pure]   # Constructor   static Typoifier Typoifier();   # transform method prototype   str transform(str text);   # errorRate property declaration   int errorRate;=cutrequire Text::Typoifier;

The Class constructor is always static (class method) and it must either return an object of the class you define or raise an exception. You may omit the return type for constructors. If you don’t define a constructor, PerlNET will automatically generate the default constructor. After the interface definition, add the line to “require” the wrapped module itself. Compile this code into a DLL using plc.exe (PerlNET compiler) with the following command line (assuming that the source file name is Typoifier.pm):

plc -target= "library" Typoifier.pm

This command creates a typoifier.dll assembly, which can be referenced from .NET programs. The sample code for this article uses C# to create client programs that use PerlNET components.

Component Structure
Before presenting the C# client program for the Typoifier component, it’s useful to look inside the new assembly (the dll) using the Intermediate Language Disassembler?ildasm.exe. This tool is very useful for determining what classes expose what methods and properties inside the assembly. Figure 1 shows the internal structure of the Typoifier component.

?
Figure 1: Looking inside the Typoifier component with ildasm.exe.

As you can see, the Text namespace exposes the Typoifier class. This class’s methods and properties are exposed by the interface?the class constructor, the transform method, and the errorRate property. PerlNET generated the special getter (get_errorRate) and setter (set_errorRate) methods for the errorRate property, and implicitly added other entries when building the assembly.

Client Program
To test the PerlNET-generated assembly, build a simple .NET client program. Listing 1 shows a C# application using the Typoifier component to create sentences with typos according to a preset the errorRate. At this point, the fact that Typoifier was originally written in Perl is transparent to C#?you can work with the class in the same way as with any other standard .NET framework class. If you’re working in the Visual Studio IDE, create a C# project named TypoifierCli, add a reference to the Typoifier assembly, and then save and run the application. If you’re working with the command-line compiler, you can compile the client program with the following command-line, which references the Typoifier assembly:

csc /reference:Typoifier.dll TypoifierCli.cs

The end result is an executable called TypoifierCli.exe. This program outputs ten lines with different type errors in the “perl” string.

PerlNET adds the Perl Interpreter component to every .NET assembly. Its function is to readdress the .NET calls in the client program to point to the corresponding subroutines inside the wrapped Perl module.

Wrapping for the Typoifier module is pretty straight-forward. Simply choose methods and properties to expose and add entries for them in the interface definition, supplying property, method argument, and return value types. However, you may often need to perform a workaround in the wrapper component to make it functional. The following are examples of special wrapping cases that require a small amount of code to handle specific problems.

Returning a List
When a wrapped module contains a Perl subroutine that returns a list (a Perl array or Perl hash), it would be nice to automatically convert this Core Perl structure into a .NET array of the correct type. Fortunately, PerlNET provides the wantarray! method modifier, which does exactly that:

=for interface   wantarray! str[] SomeWords();=cut. . .# Subroutine implementation in some modulesub SomeWords{   return qw(Computer Mouse Printer);}

Class Methods
Many Core Perl modules provide class methods and you don’t have to instantiate an object to invoke them. In .NET, such methods are static, so when exposing them to .NET, apply the static modifier inside the interface definition. This tells PerlNET not to supply a package as the first argument to those methods. For example, the wrapper for the Roman module (see Listing 2) exposes only static methods.

Constructor Name Collision
As I mentioned earlier, a wrapper class must share its name with the package of the module it wraps. This restriction can sometimes cause name collisions. For instance, the Roman module provides a procedural interface for converting Roman numbers into Arabic and vice versa. One of its subroutines is called Roman. It accepts an Arabic number and returns the corresponding Roman number in all capital letters. Using the standard wrapping naming convention here results in this entry for the Roman subroutine in the interface definition:

static str Roman(int arabic);

Because the package name is also Roman, PerlNET treats this entry as a class constructor despite the str type for return value and doesn’t readdress the calls to the Roman subroutine. To avoid this, create a new method called RomanFwd in the interface definition. This method forwards the calls to the original Roman method. Listing 2 shows the code for the wrapper. Next, provide the implementation for this method, and call the Roman subroutine. The Roman module also has a roman method, but this doesn’t create a name collision because, like Perl, PerlNET and .NET are case-sensitive.

Property Methods Name Collisions
Another problem arises when you wrap a module using methods with the prefixes get, set, get_, set_. PerlNET treats get_XXX (getXXX) and set_XXX (setXXX) methods as accessors and mutators of an XXX property respectively. To treat such subroutines as regular methods, create new methods that forward the calls to the original subroutines (see Listing 3). Create a PerlNET wrapper for the Text::Structured module that exposes two problematic methods, get_text_at and get_text_re. The client program invokes these methods using their new names: Get_text_at and Get_text_re.

Many Perl subroutines accept a Perl array or hashe of dynamic length as an argument. In the interface definition of the wrapper class, you need to supply the fixed number of arguments. For instance, the Postscript::Simple module from CPAN provides a simple functionality for creating Postscript files. Its constructor accepts hash as an argument, through which you supply the initial properties, such as page size, for the new document. One simple way to deal with this is to define the following prototype for the constructor:

static Simple(str key1, str value1,              str key2, str value2,              str key3, str value3);

This method limits you to hashes with three values. Another method is to overload the constructor with additional versions:

static Simple(str key1, str value1);static Simple(str key1, str value1,              str key2, str value2);

A more elegant solution is to define a special method that accepts the .NET array as an argument. This special method transfers its elements into the Perl list and forwards the call to the original constructor. As a result, client programs will have to use your special method to construct objects of the class (instead of the new operator).

The wrapper class for the Postscript::Simple module (Listing 4) demonstrates this technique. The SimpleCtor method translates the .NET array into the Perl list and calls the original constructor. Property method collisions are also handled because the wrapper exposes only part of methods provided by the module. The client program (Listing 5) illustrates how to use this class to create simple postscript documents from the C# code. Notice, that you call the SimpleCtor static method to instantiate the Postscript.Simple object.

One of the key features of PerlNET is the ability to wrap existing Perl components. There are many more to be explored. With PerlNET, you can create .NET native components, work with Windows Forms, access databases through ADO.NET classes, create ASP.NET Web Forms, and loads of other .NET-relative tasks. One thing is certain: Perl’s new .NET compliance promises to strengthen the language’s appeal to a wider audience of developers. It may be worth it to keep your eye on Perl!

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist