Document Tree | ||
|
NAVIGATION How to...
Contact |
Consider you have a project mainly written in Perl, but a specific problem requires a solution in a more efficient language such as O'Caml. In the following I will show how to load the O'Caml bytecode interpreter as shared library from Perl, and how to call O'Caml functions from Perl. The following instructions apply to all Unix operating systems with support for loading dynamic libraries at runtime (i.e., libdl.so is present). They were tested on Linux and Solaris. First, you need the bytecode interpreter as shared library. It is simple to build it, because it is sufficient to pass the option -fPIC to the C compiler, and to transform the archive file (suffix .a) into a shared object (suffix .so):
Note: You need of course an installed O'Caml compiler and runtime system of the same version to build your application that is going to be executed by this special bytecode interpreter. An ordinary runtime system (i.e. without -fPIC) is sufficient for that purpose. Test your shared objects: Create a "Hello world" program "t.ml": print_endline "Hello world";;Create the C loader "l.c": main (int argc, char **argv) { caml_startup(argv); }Compile t.ml with ocamlc -o t.o -output-obj -ccopt -fPIC t.mland l.c with ocamlc -o l -c l.cLink the executable with cc -o l l.o t.o -L/where/to/find/libcamlrun.so -lcamlrunNow try ./lYou will normally get an error that the library libcamlrun.so is not found. There are several ways to solve this: Add the path to the directory of libcamlrun.so to the environment variable LD_LIBRARY_PATH, or add this path to the executable (cc options "-R path" on Solaris, "-Wl,-rpath,path" on Linux), or add this path to the system configuration (edit /etc/ld.so.conf on Linux and run "ldconfig"). The application must provide the functions to be called from Perl, and register the functions. Here a simple example "dup.ml": let dup_string x = x ^ x;; Callback.register "ocaml_dup_string" dup_string;;Compile with ocamlc -o dup.o -output-obj -ccopt -fPIC dup.mland create a shared object: ld -shared --whole-archive -o libdup.so dup.o -L/where/to/find/libcamlrun.so -lcamlrunNote that the application must be a program output as an object file; it is not possible to only produce an bytecode archive and load that later (though it is possible to load bytecode archives dynamically with the dynlink extension of O'Caml). Perl comes with a utility h2xs which sets up a Perl module with C extension initially. Our Perl module is called Dup, and we begin with h2xs -A -n Dupwhich creates a directory Dup in the current directory containing the files Dup.pm, Dup.xs, Makefile.PL, test.pl, and some other files. Makefile.PL is the configuration of the Makefile to generate later. Change it as follows: use ExtUtils::MakeMaker; # See lib/ExtUtils/MakeMaker.pm for details of how to influence # the contents of the Makefile that is written. if (`uname` =~ /^SunOS/) { $L='-lcurses -lnsl -lsocket -lm'; } elsif (`uname` =~ /^Linux/) { $L='-lm'; } else { die "Do not know CCLIBS"; } WriteMakefile( 'NAME' => 'Dup', 'VERSION_FROM' => 'Dup.pm', # finds $VERSION 'LIBS' => ["-L.. -ldup -L/where/to/find/libcamlrun.so -lcamlrun $L"], 'DEFINE' => '', # e.g., '-DHAVE_SOMETHING' 'INC' => '-I/where/to/find/caml/headers', # 'CCFLAGS' => '-g', );The variable $D contains the system libraries necessary to run O'Caml; these libraries can be found in the config/Makefile that was used while building O'Caml in the line setting the variable CCLIBS. The above code simply knows what to do when running under Linux or Solaris; this is not the best solution but works at the moment. The file Dup.pm needs not to be edited; you can put additional Perl code here, and add documentation. The file Dup.xs is the input of a preprocessor that generates Dup.c later. It may contain normal C code, and so-called XSUBs. See the documentation under "perlxs" and "perlxstut" coming with Perl. Here, the following is sufficient: #include "EXTERN.h" #include "perl.h" #include "XSUB.h" #include "mlvalues.h" #include "callback.h" MODULE = Dup PACKAGE = Dup SV * dup(expr) SV *expr PROTOTYPE: $ CODE: char *s; int len_expr; value s_caml, r_caml; value *f; /* Coerce the scalar value "expr" as string into a buffer "s", and put * into "len_expr" the length of the string. * Note that "s" may contain NUL bytes. */ s = SvPV(expr, len_expr); /* Convert the buffer "s" with length "len_expr" into a Caml string * "s_caml": */ s_caml = alloc_string(len_expr); memcpy(String_val(s_caml), s, len_expr); /* Invoke the function registered under the name "ocaml_dup_string": */ f = caml_named_value("ocaml_dup_string"); if (f == NULL) { croak("OCaml function not found: ocaml_dup_string"); }; r_caml = callback_exn(*f, s_caml); /* Handle normal results and exceptions differently: */ if (Is_exception_result(r_caml)) { croak("Uncaught OCaml exception: %s", (*format_caml_exception)(Extract_exception(r_caml))); } else { /* Create a new scalar value from the string with the given * length: */ RETVAL = newSVpv(String_val(r_caml), (*string_length)(r_caml)); } OUTPUT: RETVAL BOOT: { char **a; /* Fake the command-line arguments: */ a = malloc(2*sizeof(char *)); a[0] = malloc(1); a[0][0] = 0; a[1] = NULL; caml_startup(a); }An XSUB is defined with name dup which accepts a scalar argument and returns a scalar argument. The Perl macro SvPV interprets the scalar argument expr as string and returns the string in the buffer s. The length of the buffer is stored into len_expr. This buffer is then converted into a O'Caml string using alloc_string and copying the buffer into the allocated memory. The registered function "ocaml_dup_string" is looked up and called back (note that callback_exn does not work properly in O'Caml 2.02; it is recommended to use 2.04). If an exception is raised, it is turned into a Perl exception (if the "die" mechanism can be regarded as exception mechanism at all). Otherwise, a regular result has been computed which can be passed back to Perl using newSVpv. This function creates a new scalar value from a string. The BOOT section initializes the bytecode interpreter by calling caml_startup; the argument passed to this function must be an argv-type array (from O'Caml visible in Sys.argv). If you do a 'use Dup;' from Perl, the necessary shared objects are loaded, and the BOOT section is executed. This means that especially the O'Caml statement Callback.register in the file dup.ml is run such that the registered functions can be later called back. If you execute then "$result = Dup::dup($argument);" the XSUB dup is called which passes control over to the dup function defined in the O'Caml code file. This example demonstrates only how to pass strings from Perl to O'Caml and vice versa. Of course, it is possible to pass structured values; but this is far more complicated. Usually, it is simpler to pass such values from Perl to O'Caml as string terms, and parse these strings in the O'Caml module. This is written pretty quickly using ocamllex and ocamlyacc. In the other direction, it is possible to generate in the O'Caml module strings that can be evaluated as Perl code; for example you may return "{ 'a' => 'b' }" to return a hash reference, and do $r = eval("{ 'a' => 'b' }"); die ($@) if ($@);to get the hash. Compile:Do perl Makefile.PL make Test: Edit the file test.pl as follows: # Before `make install' is performed this script should be runnable with # `make test'. After `make install' it should work as `perl test.pl' ######################### We start with some black magic to print on failure. # Change 1..1 below to 1..last_test_to_print . # (It may become useful if the test is moved to ./t subdirectory.) BEGIN { $| = 1; print "1..2\n"; } END {print "not ok 1\n" unless $loaded;} use Dup; $loaded = 1; print "ok 1\n"; ######################### End of black magic. # Insert your test code below (better if it prints "ok 13" # (correspondingly "not ok 13") depending on the success of chunk 13 # of the test code): my $s = Dup::dup("abc"); if ($s eq "abcabc") { print "ok 2\n"; } else { print "not ok 2\n"; }Run this script by make testIt should output 1..2 ok 1 ok 2 Install: You can install the Perl part of the whole project using "make install". It is only possible to load O'Caml bytecode programs from Perl because they can be compiled as position-independent code; native programs are position-dependent and because of this not suited to be loaded dynamically. You may ask the O'Caml developers to add such a feature. |