Return a perl object from a different perl class to C#

Posted by douglas.webb on 2010-04-21 08:16
Forums: PDK Support | OS: Windows

I have two Perl modules which I want to expose as object types to C#. One of them constructs objects of the other type and returns it using a method as shown below. I'm including a reference to Type2.dll in Type1.dll, and referencing them both in C#. As the code shows I can construct a Type2 object directly from C#, but I can't return a Type2 object that was constructed by a method in Type1. Any ideas?

// C#
Type1 obj1 = new Type1(); // Works
Type2 test = new Type2(); // Works
Type2 obj2 = obj1.make2(); // Fails: System.InvalidCastException: Unable to cast object of type 'PerlRunTime.SV' to type 'Type2' at Type1.make2()

// Perl: Type1.pm
package Type1;

use strict;
use Type2;

=for interface
[interface: pure]
static Type1();
Type2 make2();
=cut

sub new {
my $class = shift;
return bless {}, $class;
}

sub make2 {
my $this = shift;
return Type2->new();
}

1;

// Perl: Type2.pm
package Type2;

use strict;

=for interface
[interface: pure]
static Type2();
=cut

sub new {
my $class = shift;
return bless {}, $class;
}

1;

grahams
ActiveState Staff
Wed, 2010-04-28 13:50

You cannot construct .NET objects of a different type directly at the Perl level without going through the .NET constructor of the actual type.

That is a general fact about .NET: only constructors of a type can create new instances of that type (that's why they are called constructors). Other functions (and constructors of other types) can't make up new objects of that type; they have to call one of the real constructors first, and then may modify the state of the returned instance before returning it as a "new" object of a different type.

However, you can go back to the .NET constructor from Perl code. Modified code snippets might look like this:

----------[ Type2.pm ]----------
package Sample::Type2;

use strict;

=for interface
[interface: pure]
static Type2();
=cut

sub new {
my $class = shift;
return bless {}, $class;
}

1;
----------

The difference from your Type2.pm is that the package name has changed from "Type2" to "Sample::Type2"; putting the *type* "Type2" into the *namespace* "Sample".

----------[ Type1.pm ]----------
package Sample::Type1;

use strict;
use namespace "Sample";

=for interface
[interface: pure]
static Type1();
Type2 make2();
=cut

sub new {
my $class = shift;
return bless {}, $class;
}

sub make2 {
my $this = shift;
return Type2->new();
}

1;
----------

The difference here is that "use Type2" is replaced with "use namespace Sample". It no longer loads the Type2.pm file to call the Perl code directly, but instead makes any symbols inside the Sample namespace available directly (in this case the *class* "Type2"). Class "Type1" can be put into the "Sample" namespace as well ("package Sample::Type1").

----------[ main.cs ]----------
using Sample;

class main
{
public static void Main(string[] args)
{
Type1 obj1 = new Type1(); // Works
Type2 test = new Type2(); // Works
Type2 obj2 = obj1.make2(); // Fails: System.InvalidCastException: Unable to cast object of type
}
}
----------

You didn't provide a full test driver, so we used this minimal C# program that executes the 3 lines you gave in the post.

Note the "using Sample" line at the top, which imports the "Sample" namespace into the current scope.

This was then compiled as follows:

plc --target library Type2.pm
plc --target library --ref Type2.dll Type1.pm
csc -r:Type1.dll -r:Type2.dll main.cs

And then verified using AP 5.10.1.1007 and PDK 8.2.1.