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?
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With