Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PerlEmbed - C# - Mono - Linux

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?

like image 829
jonathanpeppers Avatar asked Jul 28 '09 20:07

jonathanpeppers


2 Answers

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.

like image 122
jonathanpeppers Avatar answered Nov 12 '22 08:11

jonathanpeppers


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.

like image 3
lupus Avatar answered Nov 12 '22 06:11

lupus