| Store | Cart

ActiveState Docs

PDK 8.0 Documentation

Loading...

PerlNET - Overview

What is PerlNet?

PerlNET is ActiveState's technology that allows .NET Framework code to access Perl code running outside Microsoft's .NET framework using the traditional Perl interpreter.

PerlNET provides the following functionality:

  • Perl code runs at the same speed within .NET as it does outside.

  • All extension modules, including the ones using XS code, are supported.

  • PerlNET code is completely compatible with the standard Perl language, including the string form of eval and the runtime use of require.

PerlNET features include:

  • Create .NET applications using .NET components.

  • Wrap existing Perl modules into .NET components.

  • Create new .NET components written in Perl.

  • Extend existing .NET components with Perl.

Getting Started with PerlNET

Here's a "Hello World" program in Perl, using the .NET System. The console class to output the string could look like this:

    use namespace "System";
    use PerlNET qw(AUTOCALL);
    
    Console->WriteLine("Hello World!");

This program can be compiled from the command line with the plc command:

    plc hello.pl

It produces an executable file called hello.exe. The output of this program is:

    Hello World!

The following points about this sample should be noted:

  • The use namespace "System"; pragma tells Perl to look up types in the System namespace. This allows use of the unqualified type Console instead of System.Console later in the program.

  • The use PerlNET qw(AUTOCALL); pragma instructs Perl to automatically translate Perl class-method calls into .NET calls.

The combined effect of both directives is that the statement:

    Console->WriteLine("Hello World!")

...is treated as a short version of:

    PerlNET::call("System.Console.WriteLine", "Hello World!");

Creating PerlNET Components

PerlNET makes it easy to wrap an existing Perl module by just providing an additional interface specification for it. The following sample code wraps the WWW::Babelfish module from CPAN into a .NET component:

    package WWW::Babelfish;
    
    =for interface
        [interface: pure]
        static Babelfish Babelfish();
        str translate(str key1, str value1,
          str key2, str value2,
          str key3, str value3);
        wantarray str[] languages();
    =cut
    
    require WWW::Babelfish;

This makes WWW::Babelfish available to every other .NET-compliant language. The following C# program gives an example:

    using System;
    
    class main
    {
        public static void Main(string[] args)
        {
            string text = "The quick brown fox jumps over the lazy dog";
            string[] lang = { "French", "German", "Spanish", "Italian" };
    
            Console.WriteLine("{0}: {1}", "English", text);
            using (WWW.Babelfish fish = new WWW.Babelfish()) {
                foreach (string dest in lang) {
                    string trans = fish.translate("source",       "English",
                                                  "destination",  dest,
                                                  "text",         text);
                    Console.WriteLine("{0}: {1}", dest, trans);
                }
                Console.WriteLine();
    
                foreach (string str in fish.languages())
                    Console.WriteLine("Language: {0}", str);
            }
        }
    }

To run this sample, first install WWW::Babelfish on your system. Then compile the PerlNET component and the test program with:

    plc --target library Babelfish.pl
    csc test.cs -r:Babelfish.dll

Distributing .NET Assemblies

PerlNet components require either Perl58RT80.dll and Perl58NH80.dll for ActivePerl 5.6 and 5.8 support, or Perl510RT80.dll and Perl510NH80.dll for ActivePerl 5.10 support. You will need to distribute these 2 DLLs with your .NET assemblies.

Perl58NH80.dll and Perl510NH80.dll can be found in /Program Files/ActiveState Perl Dev Kit 8.0/bin (the default PDK 8.0 install location).

Perl58RT80.dll and Perl510RT80.dll are installed in the Global Assembly Cache (GAC). The directory for the ActivePerl 5.10 runtime library will look like this:

  /WINDOWS/assembly/GAC_MSIL/Perl510RT80/8.0.0.27479__cea8284aa6739163/

Using .NET Objects

The following example shows how to create an instance of an object, invoke methods through it and access its properties:

    # Call constructor (with arguments)
    my $str = System::Text::StringBuilder->new("Hello");
    
    # Instance-method call
    $str->Append(" World!");
    
    # Reading named and indexed property
    printf "str=%s length=%d firstchar=%s/n",
           $str, $str->{Length}, $str->[0];
    
    # Assigning to an indexed property
    
    $str->{Length} = 11; # Remove trailing '!'
    $str->[5] = '-';     # Replace space by dash
    
    printf "str=%s length=%d firstchar=%s/n",
           $str, $str->{Length}, $str->[0];

The first statement of the program constructs a StringBuilder object, initialized to the string "Hello":

    my $str = System::Text::StringBuilder->new("Hello");

Since a namespace pragma was not used, a fully qualified type name is required. To avoid using quotes around the typename, we replaced the dots in System.Text.StringBuilder with the :: separators.

The type constructor is always called new() and can potentially take parameters. PerlNET automatically selects the right constructor based on parameter types.

Calling methods on .NET objects uses exactly the same syntax as calling Perl methods:

    $str->Append(" World!");

The $str object now contains the text "Hello World!". Using $str in a string context automatically invokes the ToString() method.

Access named properties of an object using the hash reference syntax and indexed properties through array references:

    printf "str=%s length=%d firstchar=%s/n",
           $str, $str->{Length}, $str->[0];

This will print:

    str=Hello World! length=12 firstchar=H

Named and indexed properties can also be changed by simple assignment. Changing the Length property truncates the string; the indexed assignment replaces a single character:

    $str->{Length} = 11;
    $str->[5] = '-';

This will now print:

    str=Hello-World length=11 firstchar=H

Using P/Invoke

"Platform Invoke" provides access to normal unmanaged Win32 functions in external DLLs. The following sample determines the path of the temp directory and displays it in a messagebox:

    use strict;
    use PerlNET qw(call);
    use namespace "System.Text";
    use namespace "System.Runtime.InteropServices";
    
    =for interface
         [DllImport("kernel32.dll")]
         static external int GetTempPath(int bufsize, StringBuilder buffer);
         [DllImport("user32.dll")]
         static external int MessageBox(int h, str m, str c, int type);
    =cut
    
    my $path = StringBuilder->new(300);
    my $res = call("GetTempPath", 300, $path);
    call("MessageBox", 0, $path->ToString(), "@ARGV", 0);

Callbacks

This release of PerlNET does not support callbacks from unmanaged code because it doesn't support the definition of new Delegates.

WinForms

The following sample shows how .NET-compliant Perl code can make use of the Microsoft Winforms function.

    package HelloWorldForm;
    use strict;
    use PerlNET qw(with AUTOCALL);
    
    use namespace "System";
    use namespace "System.Windows.Forms";
    use namespace "System.Drawing";
    
    =for interface
        [extends: Form]
        [STAThread]
        static void Main();
        private field TextBox textBox1;
        private void button1_Click(any sender, EventArgs evArgs);
    =cut
    
    sub Main {
        my $self = HelloWorldForm->new;
        Application->Run($self);
    }
    
    sub HelloWorldForm {
        my $this = shift;
    
        with(my $textBox1 = TextBox->new(),
             Text     => "Hello Windows Forms World",
             Location => Point->new(16, 24),
             Size     => Size->new(360, 20),
             TabIndex => 1);
    
        with(my $button1 = Button->new(),
       Text     => "Click Me!",
       Location => Point->new(256, 64),
       Size     => Size->new(120, 40),
       TabIndex => 2);
    
        $button1->add_Click(EventHandler->new($this, "button1_Click"));
    
        my $height = SystemInformation->CaptionHeight;
        with($this,
             Text              => "Hello Windows Forms World",
             AutoScaleBaseSize => Size->new(5, 13),
             ClientSize        => Size->new(392, 117),
             MinimumSize       => Size->new(392, int(117 + $height)),
             AcceptButton      => $button1,
             textBox1          => $textBox1);
    
        $this->{Controls}->Add($_) for $textBox1, $button1;
    }
    
    sub button1_Click {
        my($this, $sender, $evargs) = @_;
        MessageBox->Show("Text is: '$this->{textBox1}->{Text}'");
    }

Implementing Types

PerlNET not only provides access to .NET objects but also supports implementing and extending .NET types. The .NET Framework is a strongly typed environment, whereas Perl is a dynamically typed language. It is therefore necessary to supply an interface definition in addition to the implementation in plain Perl code. This interface definition is included in POD blocks throughout the component source file.

PerlNET distinguishes between "pure" Perl types, .NET types and "mixed" types.

Perl Types

A pure Perl type uses a blessed Perl reference as the $self variable.

To designate a component to use a pure Perl interface you have to include the following pseudo custom attribute in the interface specification:

    =for interface
         [interface: pure]
    =cut

A pure Perl type cannot inherit from a .NET type and cannot implement fields or virtual methods. All property accesses to the $self object are interpreted as accessing the blessed hash.

Pure Perl types are best used for wrapping existing Perl modules, and for new Perl components that will also be used outside the .NET Framework.

.NET Types

A .NET type uses a $this parameter that represents the actual .NET object. No blessed Perl scalar is available to the component; everything needs to be stored in the .NET visible part of the object.

Mixed Types

A mixed type is basically the same as a .NET type, except that every constructor, method and property accessor receives both a $this and a $self pointer. You designate an interface as "mixed" with the following pseudo custom attribute:

    =for interface
         [interface: mixed]
    =cut

The object is represented by the $this pointer; the $self variable references a blessed hash that allows the object to keep Perl references as instance data. All references to the object itself should still be made through the $this pointer. The $self pointer is implicitly provided by PerlNET; it is not listed in the interface signature. For example:

    =for interface
         void Foo(str param);
    =cut
    
    sub Foo {
        my($this,$self,$param) = @_;
        $self->{param} = $param;
        $this->Bar();
    }

In all other aspects, mixed types act exactly as .NET types. Whenever this reference distinguishes between Perl types and .NET types, treat mixed types as .NET types.

Type Inheritance

PerlNET components can inherit from other .NET types, including ones implemented by PerlNET. .NET only allows single inheritance. The base class for a PerlNET component is specified by a pseudo custom attribute:

    =for interface
         [extends: BaseClass]
    =cut

Garbage Collection

Perl uses reference counting to do garbage collection: whenever the last reference to an object is removed, the object is collected (the DESTROY method is called and the memory is freed).

The .NET Framework uses a 'mark and sweep' algorithm to reclaim unreferenced memory. When it runs out of memory on the heap, it enumerates all objects, and any object no longer accessible will be reclaimed. Since no reference counting is being performed, both the time and the order of destruction are indeterminate.

For managing the lifetime of unmanaged resources encapsulated in managed objects, .NET defines the IDisposable interface. It is a standardized mechanism to release resources held by an object once they are no longer being used. Please refer to the .NET Framework documentation to learn more about the IDisposable interface.

An object implementing IDisposable is typically used like this (in C#):

    MyObj obj = new MyObj();
    try {
        // do something with obj
    }
    finally {
        obj.Dispose();
    }

C# provides some syntactic sugar for this usage pattern in form of the using statement:

    using (MyObj obj = new MyObj()) {
        // do something with obj
    }

PerlNET implements the IDisposable interface when the 'disposable' attribute is specified:

    =for interface
         [interface: disposable]
    =cut

It can also be combined with the 'pure' or 'mixed' attribute:

    =for interface
         [interface: mixed, disposable]
    =cut

Whenever possible, PerlNET implements the IDisposable paradigm recommended in the .NET Framework documentation by providing the following three member functions:

    public void Dispose();
    protected virtual void Dispose(bool disposing);
    protected void virtual Finalize();

When the PerlNET component inherits from a base class that already implements IDisposable in a different style, PerlNET tries to stay compatible with the base class implementation.

Perl Types

Calling the Dispose() method on a 'pure' PerlNET object immediately decrements the reference count of the Perl object. The Destroy() method is called if it is the last reference. The .NET part of the object continues to exist, but calling any method (except Dispose()) raises an ObjectDisposedException.

.NET Types

These types must define a special Dispose() method:

    sub DISPOSE {
        my($this,$disposing) = @_;
        # or ($this,$self,$disposing) for "mixed" types
        # ...
    }

$disposing is true when called from the <Dispose()> method and false when called by the finalizer. Do not access any managed objects called from the finalizer because the other objects may have already been destroyed by the garbage collector.

Disposable .NET types must not define a Destroy() method. The method is defined by PerlNET as part of the IDisposable implementation.

Interfaces

PerlNET components can implement .NET interfaces defined in an external assembly. The implemented interfaces need to be listed in a pseudo custom attribute. For example:

    =for interface
         [implements: ICollection, IEnumerable]
    =cut

You must list prototypes for all implemented methods explicitly. This requirement may change in a future release of PerlNET. This release of PerlNET does not allow you to define new .NET interfaces.

Namespaces

Creating Types Inside a Namespace

The .NET Framework uses namespaces to organize types. PerlNET uses the Perl package name to deduce the namespace for a type. If you implement a My::Package::Sample component, PerlNET automatically creates the type sample in the My.Package namespace.

Using the Namespace Pragma to Look Up Types

PerlNET also provides a namespace pragma to specify namespaces that should be searched to resolve non-qualified typenames. For example:

    use namespace "System.Text";

This lets you later create a System.Text.StringBuilder object by writing:

    my $str = StringBuilder->new("Initial string");

...instead of:

    my $str = System::Text::StringBuilder->new("Initial string");

The namespace pragma is in effect both at compiletime (interface specifications) and at runtime.

Constructors

Constructors return a new instance of a type.

Invoking Constructors

The constructor is called as a class method called new(). Constructors may take additional arguments:

    Type->new(@args)

In case Type is also used as a Perl package, it is always possible to invoke the constructor using the ctor() helper function:

    PerlNET::ctor("type", @args)

Defining Constructors

In the interface definition, constructors are defined as static methods with the same name as the type itself:

    =for interface
         static Type();
         static Type(string arg);
    =cut

It is not necessary to specify a return type; all constructors must return an object of the implemented type or raise an exception. It is possible to overload the signature of the constructor by providing multiple prototypes. All constructors will call the same Perl method; you can inspect @_ to determine what parameters have been provided.

Constructors are implemented differently for pure Perl vs. .NET types.

Perl Types

For pure Perl types, the constructor needs to be called 'new'. The first argument to the constructor is the package name, and the constructor must return a reference to an object blessed into this package.

    sub new {
        my($package) = @_;
        return bless {}, $package;
    }

.NET Types

For .NET types, the constructor must have the same name as the type. The first argument to the constructor is the $this pointer to the already-allocated object. It is possible to invoke additional instance methods through this pointer during construction time. The constructor is called in void context; it is not expected to return anything.

    sub Type {
        my($this, @args) = @_;
        $this->Init(@args);
    }

Default Constructor

If you do not define a constructor for your type, PerlNET automatically provides a default constructor that takes no arguments but just allocates the object.

Class Constructors

Class constructors are invoked before the type is instantiated for the first time. There is no special syntax to define a class constructor in Perl. The Perl source file implementing the component is compiled and executed as part of the class construction process.

Methods

Calling Methods

.NET methods are called just like normal Perl methods:

    $obj->Method(@args);

The types of the arguments must match the types of the method signature exactly. This allows PerlNET to automatically select the correct overloaded method at runtime. For example, if the method requires an int parameter, you cannot pass a float. Because Perl automatically translates integers to floats whenever they are used in an arithmetic context, it may be necessary to write:

    $obj->Method(int($var));

...instead of just:

    $obj->Method($var);

Similarly, boolean values must be explicitly passed as boolean, not as int or str. PerlNET contains the boolean constants VPerlNET::true and PerlNET::false as well as the PerlNET::bool() function that converts a Perl boolean value into a .NET boolean.

    $obj->Method( PerlNET::bool($a == $b) );

Typed Null References

Sometimes you may want to pass a null reference as a method parameter. The Perl undef value is implicitly treated as being of System.Object type. The PerlNET::null() function provides typed null references to help PerlNET select the right method signature:

    $obj->Method( PerlNET::null("System.String") );

Static Methods

Static methods (aka class methods) can be called using the PerlNET::call() function:

    PerlNET::call("Type.Method", @args);

It is also possible to instruct PerlNET to retry every static method call for which no Perl method is found. This feature needs to be enabled explicitly with:

    use PerlNET qw(AUTOCALL);

Then the call above can be rewritten as:

Type->Method(@args);

Defining Methods

All methods must be declared in the interface definition. A method declaration consists of a list of method modifiers, followed by the return type, followed by the method name, followed by the parameter list declaration. For example:

    =for interface
         static int StaticMethod();
         protected virtual void VirtualMethod(int Arg1, int Arg2);
         override str BaseMethod();
         void OverloadedMethod(int arg);
         void OverloadedMethod(str arg);
    =cut

Method modifiers include access modifiers: public, protected, private or internal. They are mutually exclusive except for protected internal. The default access is public.

Another set of mutually exclusive modifiers consists of static, virtual and <override>. static indicates that this is a class method, virtual that the method can be overridden in a derived class, and override that a method already defined in the base class is overridden. In addition, the 'platform invoke' feature (P/invoke) uses the combination of static external to indicate a forwarder declaration to an unmanaged Win32 function in an external DLL.

The parameter declaration is a comma-separated list of type and parameter names enclosed in parentheses.

Overloading

All overloaded methods call the single Perl method of the same name. The Perl method can inspect the types of the arguments in @_ to determine which signature was used to invoke the method. It is not allowed to overload a method purely on the return type.

List Context

Usually, all methods are called in scalar context. If the method has been declared to return an array, the Perl method is expected to return either a reference to a Perl array containing elements of the correct type or a .NET array.

For pure Perl interfaces, especially when used to wrap existing modules, it is useful to be able to invoke a method in list context and automatically transform the returned list into a .NET array of the correct type. This can be done with the wantarray method modifier:

    =for interface
         static wantarray str[] SomeWords();
    =cut
    
    sub SomeWords {
        return qw(a list of some words);
    }

Properties

The .NET Framework uses typed properties. These properties are accessed via getter and setter methods.

Accessing Properties

Properties are accessed using the Perl hash reference syntax:

    $obj->{Property} = $value;
    print "Property is $obj->{Property}/n";

It is illegal to have a property and a method of the same name. Therefore, it is also possible to retrieve a property value using method call syntax:

    printf "Property is %s/n", $obj->Property;

However, setting the value of the property must always use the hash reference syntax.

Static properties can be accessed using the PerlNET::get() and PerlNET::set() helper functions:

    my $prop = PerlNET::get("MyType.Property");
    PerlNET::set("MyType.Property", $value);

Defining Properties

A property is defined by an access modifier, followed by the type, followed by the property name. The getter and setter methods both invoke the Perl method of the same name as the property. The setter method supplies the new value as the argument:

    =for interface
         protected str MyProp;
    =cut
    
    sub MyProp {
        my($this, $value) = @_;
        unless (defined $value) {
            # return current value
        }
        # set property to $value
    }

Properties with Parameters

Properties can potentially take parameters. They are not supported with special syntax in PerlNET. They can be accessed through the method call interface: the setter method is called set_PropertyName() and the getter is get_PropertyName().

This release of PerlNET does not support the definition of properties with parameters.

Indexers

Many types have the concept of a "default indexer". This is just a property, taking (usually) a single argument. Indexers are used for syntactic sugar to directly access elements of collections without having to explicitly use a property name.

Accessing Indexers

PerlNET uses array reference syntax to implement indexers:

    print "The second character is $str->[1]/n";

Due to the way Perl works internally, this only works if the indexer expects an int parameter. In all other cases, the indexer has to be invoked using method syntax. Usually, the name of the indexer property is Item, but some types use a different name (e.g. System.String and System.Text.StringBuilder use Chars).

The example above rewritten with method syntax would be:

    printf "The second character is %s/n", $str->get_Chars(1);

Defining Indexers

This release of PerlNET does not support the definition of indexed properties.

Fields

Fields are similar to properties. The difference is that they are not accessed via accessor methods but directly through the memory location.

Accessing Fields

Fields are accessed using Perl hash reference syntax. It is illegal to have a property and a field of the same name. Fields never take a parameter.

Static fields are accessed using PerlNET::get() and PerlNET::set(), just like static properties. Additionally, the object's own fields can be accessed through Perl package variables of the same name. PerlNet automatically ties these global variables to the corresponding .NET field:

    package MyClass;
    =for interface
         static field str Name;
    =cut
    
    $Name = "Foo";
    # implicitly does the same as
    PerlNET::set("MyClass.Name", "Foo");

Defining Fields

Fields are defined as properties. In addition, you need to specify the field modifier:

    private field int Count;

Value Types

PerlNET stores .NET value types as boxed objects, maintaining their type information, instead of converting them to Perl integers and numbers. This makes it easier to pass these values to .NET methods.

PerlNET also provides built-in constructors for the standard value types:

    use PerlNET qw(char);
    # ...
    my $ch = char('a');

The PerlNET::char() constructor/cast operator produces a System.Char value. The following table shows the mapping between PerlNET names and the System.* typenames.

    PerlNET     System      Example
    bool        Boolean     bool(0)
    char        Char        char("/x{263A}")
    sbyte       SByte       sbyte(-128)
    short       Int16       short(0x7fff)
    int         Int32       int(42)
    long        Int64       long("12345")
    byte        Byte        byte(255)
    ushort      UInt16      ushort(0xffff)
    uint        UInt32      uint(0)
    ulong       UInt64      ulong("-1")
    float       Single      float(3.14)
    double      Double      double(1.0)
    decimal     Decimal     decimal("7")

PerlNET automatically converts all value types into strings as needed and also implements autoincrement and autodecrement operators for them, covering the whole valid value range. The 'wrap-around' behavior is the same as of the underlying .NET types:

    my $byte = PerlNET::byte(255);
    ++$byte;

...yields the same result as:

    my $byte = PerlNET::byte(0);

However, normal arithmetic operations convert the .NET value types into Perl integers or floating point numbers. Consequently, the result is no longer a .NET value type and that values exceeding 32-bit integers (for long, ulong and decimal types) may not be converted correctly. Numeric comparisons for these three types will not work either. (Overloaded arithmetic operations are under consideration for a future release of PerlNET.)

    use PerlNET qw(uint);
    # ...
    my $word = uint(42);
    $word += 7;
    
    # need to convert back to System.UInt32 as $word is now just a Perl int
    $obj->Method( uint($word) );

Note that you will override the built-in int() function if you import PerlNET::int() into your program:

    use PerlNET qw(int);
    # ...
    
    # use CORE::int() to call builtin Perl int() function
    my $int = CORE::int($val*1.68);

Booleans

PerlNET treats System.Boolean values the same as Perl boolean expressions: in numeric context they evaluate to 1 or 0 and in string context to "1" or "" (the empty string).

PerlNet also defines the constants <PerlNET::true> and PerlNET::false, which are the same as PerlNET::bool(1) and PerlNET::bool(0), respectively.

Decimals

PerlNET::decimal(NUMBER) always assumes that the character '.' is used for the decimal point, and that the character ',' can be ignored as a thousand separator. Please use the System.Convert class to construct <System.Decimal> values from localized strings:

    # Locale with ',' decimal point and '.' thousands separator
    my $decimal = PerlNET::call("System.Convert.ToDecimal", "123.456,78");

...or:

    use PerlNET qw(AUTOCALL);
    # ...
    
    my $decimal = System::Convert->ToDecimal("123.456,78");

Enumerations

Enumerations provide symbolic names to numeric (integer) constants.

Using Enumeration Constants

Enumeration members can be accessed using the PerlNET::enum() helper function:

    my $monday = PerlNET::enum("System.DayOfWeek.Monday");

All enumeration objects return their numeric values when used as numbers and return a text representation when used as strings. The following expressions are both true:

    $monday eq "Monday"
    $monday == 1

Defining Enumeration Types

This release of PerlNET does not support the definition of enumeration types.

Delegates

Using Delegates

Delegates are constructed like any other .NET objects. The first argument to the constructor is an object pointer and the second one is the method name of the callback function:

    my $handler = System::EventHandler->new($this, "callback");

This release of PerlNET does not support delegates on pure Perl types.

head2 Defining Delegates

This release of PerlNET does not support the definition of delegates.

Exceptions

Throwing Exceptions from Perl Code

When a Perl method dies, the exception message is propagated to the .NET environment and can be caught there. The Perl exception message from $@ is wrapped into an PerlRuntime.PerlException object. It is also possible to throw a .NET exception object directly:

    die System::ApplicationException->new("something is wrong");

Catching Exceptions in Perl Code

Exceptions thrown in .NET code can be caught in an eval block from within Perl.

Custom Attributes

Applying Attributes

Custom attributes are applied to elements of an interface specification. The attribute constructor is called from within square brackets placed directly in front of the element:

    =for interface
         [ObsoleteAttribute("Method() will be removed in the next version")]
         void Method();
    =cut

If the attribute name ends with "Attribute", this suffix can be omitted.

Targets Modifiers

The syntax for custom attributes in PerlNET is exactly the same as for C#. An optional target modifier can be used to apply attributes to other elements other than the one that immediately follows. For example, the "assembly" or "type" targets apply the attribute to the assembly or the class itself, not to the next method, field or property:

    =for interface
         [assembly: System.Reflection.AssemblyVersion("1.2.3.4")]
         [type: Obsolete("You should use MyOtherType instead")]
    =cut

In addition to the targets defined by the C# language PerlNET also implements the "set" and "get" targets, which can only be specified on front of a property definition. They will apply to the property accessors and not to the property itself.

Pseudo Targets

PerlNET also uses the custom attribute syntax to specify the base class, implemented interfaces and attributes of the PerlNET interface. Those use the pseudo targets 'extends', 'implements' and 'interface' respectively. But they are not .NET level custom attributes.

Attribute Parameter Types

Attribute parameters are limited to the following types:

  • bool, byte, char, double, float, int, long, short, string

  • the type object

  • the type System.Type

  • an enum type

  • a single-dimensional array of the above types

The values for attribute parameters must be compiletime constants and have to be specified in C# syntax. The following sample assumes that you have defined attributes like BooleanAttribute, taking a bool parameter, etc.

    =for interface
         [Boolean(true)]
         [Byte(255)]
         [Char('/x263a')]
         [Double(3.14)]
         [Float(2.3f)]
         [Int16(255)]
         [Int32(0x7fffffff)]
         [Int64(-9223372036854775808L)]
         [String("Perl")]
         [Enum(DayOfWeek.Monday)]
         [Type(typeof(System.String))]
         [Object("string")]
         [Array(new object[]{"string", 3.14, typeof(System.String)})]
    =cut

Defining Attributes

Custom attributes are .NET classes that inherit from the System.Attribute class. They must be marked with the System.AttributeUsageAttribute and should have a name that ends with 'Attribute'. The .NET framework documentation contains more information about custom attributes. Here is a sample attribute defined in Perl:

    package MyAttribute;
    
    use namespace "System";
    
    =for interface
         [extends: Attribute]
         [type: AttributeUsage(AttributeTargets.All, AllowMultiple=true)]
         static MyAttribute(string label);
         readonly string Label;
         private field string label;
    =cut
    
    sub MyAttribute {
        my($this,$label) = @_;
        $this->{label} = $label;
    }
    
    sub Label {
        my($this) = @_;
        return $this->{label};
    }

It can be used in a PerlNET component like this:

    =for interface
         [assembly: My("assembly")]
    
         [type: My("type")]
    
         [My("constructor"), My("can be used multiple times")]
         void Sample();
    
         [method: My("method")]
         [return: My("return")]
         void Method([My("param1")]string Param1, [param: My("param2")]int Param2);
    
         [My("property")]
         [get: My("getter")]
         [set: My("setter")]
         string Property;
    
         [My("field")]
         field string Field;
    =cut

Main Function

If you provide a static method called Main() in your component and compile the component into an .exe file, PerlNET makes this function the main entry point. This is primarily useful if you need to specify custom attributes for the Main() function (e.g. [STAThread] for Windows Forms).

Arrays

Accessing Arrays

Single-dimensional arrays can be accessed with the normal array indexer syntax:

    my $sum = $a->[0] + $a->[1];
    print join(":", @$a), "/n";
    $a->[0] = int($a->[0] + 2);

You can create so called 'jagged' arrays in .NET, which basically are arrays of arrays. They work similarly to arrays of arrays in Perl, except that all elements need to have the same type in .NET. You can access them using chained indexer syntax:

    print $a->[1]->[2], "/n";
    print $$a[1][2], "/n";

True multi-dimensional arrays must be accessed using the GetValue() and SetValue() methods of their System.Array base class:

    my $sum = $a->GetValue(0,0,0) + $a->GetValue(1,1,1);
    
    # use "Int32[]" arguments to GetValue() if the array rank is greater than 3
    my $sum = $a->GetValue("Int32[]"->new(0,0,0,0)) +
              $a->GetValue("Int32[]"->new(1,1,1,1));

Creating Arrays

Array objects can be constructed using [] syntax in the constructor:

    my $a = "System.Int32[10]"->new();
    my $b = "System.Int32[]"->new(2..5);
    my $c = PerlNET::ctor("System.String[2,2,]", "aa" .. "bb");

Returning Arrays from Methods

PerlNET methods can return single-dimensional arrays of any .NET type, including array of arrays. If the Perl code returns a Perl array reference, PerlNET converts this automatically into a .NET array of the specified type.

    =for interface
         DayOfWeek[] Enum_Array();
             int[][] Array_of_Array();
    =cut
    
    sub Enum_Array {
        return [undef, "Monday", PerlNET::enum("DayOfWeek.Tuesday"), 3, "4"];
    }
    
    sub Array_of_Array {
        return [[3,4],[5,6,7],[]];
    }

Multi-dimensional return values must be specified as either System.Object or System.Array, and then need to be casted to the right type on the caller side.