6

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?

Sinan Ünür
  • 116,958
  • 15
  • 196
  • 339
jonathanpeppers
  • 26,115
  • 21
  • 99
  • 182

2 Answers2

6

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.

jonathanpeppers
  • 26,115
  • 21
  • 99
  • 182
  • is there any way to skip the perlembed.c part? Great work BTW! – Vivek Bernard Apr 16 '12 at 14:18
  • We actually abandoned this route shortly after I got this working. My company ended up writing the final product completely in perl--it was really a temporary solution to reuse some C# code that did the same thing. – jonathanpeppers Apr 16 '12 at 17:36
  • Oh..I see.. but unfortunately I have to use this route.So can you guide me on this.. ? so is it possible to skip the 'C' part and use only C# and the perlembed library – Vivek Bernard Apr 16 '12 at 18:20
  • I think you have to use C to some extent for sure. perlembed has macros all over the place, and you can't exactly call into those from C#. We also abandoned it b/c you can't call to perl from multiple threads at the same time, so you would have to run perl on it's own thread and use locking in a mult-threaded system. Only other option would be to run multiple perl interpreters, but we couldn't figure out how to make that work. – jonathanpeppers Apr 17 '12 at 12:01
3

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
  • 3,963
  • 1
  • 18
  • 13
  • 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. – jonathanpeppers Jul 29 '09 at 12:59
  • 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? – jonathanpeppers Jul 30 '09 at 12:57
  • 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 Jul 31 '09 at 05:12