views:

319

answers:

2

Has anyone attempted using perlembed in Mono on Linux?

Here is the link: perlembed

Here is my first attempt at the DllImport signatures:

private const string PERL_LIB = "/usr/lib/perl5/5.8.8/i386-linux-thread-multi/CORE/libperl.so";

[DllImport(PERL_LIB, EntryPoint = "perl_alloc", SetLastError = true)]
public static extern IntPtr Alloc();

[DllImport(PERL_LIB, EntryPoint = "perl_construct", SetLastError = true)]
public static extern void Construct(IntPtr hPerl);

[DllImport(PERL_LIB, EntryPoint = "perl_destruct", SetLastError = true)]
public static extern void Destruct(IntPtr hPerl);

[DllImport(PERL_LIB, EntryPoint = "perl_free", SetLastError = true)]
public static extern void Free(IntPtr hPerl);

[DllImport(PERL_LIB, EntryPoint = "perl_parse", SetLastError = true)]
public static extern void Parse(IntPtr hPerl, IntPtr @null, int argc, StringBuilder argv, StringBuilder env);

[DllImport(PERL_LIB, EntryPoint = "perl_run", SetLastError = true)]
public static extern void Run(IntPtr hPerl);

[DllImport(PERL_LIB, EntryPoint = "eval_pv", SetLastError = true)]
public static extern void Eval(string expr, bool flag);

The CORE directory can change per Linux distro.

The following code runs, but crashes on the Parse() method:

try
{
    Console.WriteLine("Alloc");
    IntPtr perl = Alloc();

    Console.WriteLine("Construct");
    Construct(perl);

    Console.WriteLine("Parse");
    Parse(perl, IntPtr.Zero, 3, new StringBuilder("-e 0"), new StringBuilder());

    Console.WriteLine("Run");
    Run(perl);

    Console.WriteLine("Eval");
    Eval("$a = 3.14; $a **= 2", true);

    Console.WriteLine("Destruct");
    Destruct(perl);

    Console.WriteLine("Free");
    Free(perl);
} 
catch (Exception exc)
{
    Console.WriteLine(exc.ToString());
}

My crash reads:

=================================================================
Got a SIGSEGV while executing native code. This usually indicates
a fatal error in the mono runtime or one of the native libraries
used by your application.
=================================================================

Stacktrace:

in (wrapper managed-to-native) Woot:Parse (intptr,intptr,int,System.Text.StringBuilder,System.Text.StringBuilder) <0x4>
in (wrapper managed-to-native) Woot:Parse (intptr,intptr,int,System.Text.StringBuilder,System.Text.StringBuilder) <0xffff9f85>
in Woot:Main () <0x8d>
in (wrapper runtime-invoke) System.Object:runtime_invoke_void (object,intptr,intptr,intptr) <0x7c79b09>

Native stacktrace:

 mono(mono_handle_native_sigsegv+0xbb) [0x81368fb]
 mono [0x8105670]
 [0x4c45a440]
 /usr/lib/perl5/5.8.8/i386-linux-thread-multi/CORE/libperl.so(perl_parse+0xa3)    [0x4c6e6e93]
 [0x43ad78]
 [0x434cae]
 [0x434abe]
 mono(mono_runtime_exec_main+0x62) [0x80ae5a2]
 mono(mono_runtime_run_main+0x152) [0x80af6e2]
 mono(mono_main+0xef9) [0x805dae9]
 mono [0x805c702]
 /lib/libc.so.6(__libc_start_main+0xdc) [0x4c48d724]
 mono [0x805c651]

There are some PERL_SYS_INIT3, and PERL_SYS_TERM calls that perlembed mentions, but I have not been able to call those methods through DllImport. I always get EntryPointNotFoundException in those cases. I'm sure they are in a different file I need to import.

Can someone direct me in the correct way to call DllImports to perlembed?

+3  A: 

It fails on perl_parse() because your binding is incorrect.

The argv argument is a char** (to be interpreted as an argc-sized array of char*): this has no relation to StringBuilder, which represents a modifiable string.

I suggest you manually marshal this array: use a IntPtr[] as the argv and env arguments and fill the elements of the array with pointers to byte strings, for example using Marshal.StringToCoTaskMemAnsi() if the encoding is good enough for you. Remember also to free the memory allocated by that.

Of course, you should make all this work inside an helper method that exposes a more natural interface to C# programmers that takes a string[] instead of the argc/argv pair.

lupus
I will try this, does anyone know how to make the calls to PERL_SYS_INIT3 and PERL_SYS_TERM, I bet these are necessary for it to all work.
Jonathan.Peppers
I've gotten perl_parse() to work with your method, but now I get a EntryPointNotFoundException on eval_pv().I bet I need to import the compiled version of EXTERN.H, but there are no more .so files in the CORE directory I mentioned....Does anyone know of a tool that will display all the methods contained in a .so file?
Jonathan.Peppers
That function is exported as Perl_eval_pv, so you need to use that in C#, too. You can check the exported symbols of libperl with something like: nm -D /usr/lib/libperl.so | grep ' T '
lupus
+1  A: 

Got it to work!

Made the perl program, showtime.pl:

#/usr/bin/perl

sub showtime {
    print "WOOT!\n";
}

Made the c program, perlembed.c:

#include <EXTERN.h>
#include <perl.h>

static PerlInterpreter *my_perl;

void Initialize(char* processName, char* perlFile)
{
    int argc = 2;
    char *argv[] = { processName, perlFile },
     *env[]  = { "" };

    PERL_SYS_INIT3(&argc, &argv, &env);
    my_perl = perl_alloc();
    perl_construct(my_perl);
    perl_parse(my_perl, NULL, argc, argv, NULL);
    PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
}

void Call(char* subName)
{
    char *args[] = { NULL };
    call_argv(subName, G_DISCARD | G_NOARGS, args);
}

void Dispose()
{
    if (my_perl != NULL)
    {
      perl_destruct(my_perl);
      perl_free(my_perl);
      PERL_SYS_TERM();
      my_perl = NULL;
    }
}

Compiled it via:

"gcc -shared -Wl,-soname,perlembed.so -o perlembed.so perlembed.c `perl -MExtUtils::Embed -e ccopts -e ldopts`"

Made this C# program, perlembed.cs:

using System;
using System.Runtime.InteropServices;

public class Woot
{
  [DllImport("perlembed.so", SetLastError = true)]
  public static extern void Initialize(string processName, string perlFile);

  [DllImport("perlembed.so", SetLastError = true)]
  public static extern void Call(string subName);

  [DllImport("perlembed.so", SetLastError = true)]
  public static extern void Dispose();

    static void Main()
    {
     Console.WriteLine("Starting up C#...");

     try
     {
      Initialize("perlembed.exe", "showtime.pl");

      Call("showtime");
     }
     catch(Exception exc)
     {
      Console.WriteLine(exc.ToString());
     }
     finally
     {
      Dispose();
     }

     Console.WriteLine("DONE!...");
    }
}

Compiled it with gmcs, and got the output:

Starting up C#...
WOOT!
DONE!...

Hope this helps anyone out there, can't believe it took 3 languages to happen. I will move on to passing scalars, arrays, etc. but it should be a breeze from here.

Jonathan.Peppers