Jump to content

Call a function in a shared library/OCaml

From Rosetta Code
Revision as of 21:14, 17 May 2010 by rosettacode>Blue Prawn (prevent rem successive spaces)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Here is the file "dlffi.ml" which provides the interface to OCaml:

<lang ocaml> type lib_handle type func_handle

type pointer

type arg_type =

 | Int of int
 | Float of float
 | Double of float
 | String of string
 | Ptr of pointer
 | Void

type return_type =

 | Return_int
 | Return_float
 | Return_double
 | Return_string
 | Return_ptr
 | Return_void

type rtld_flags =

 | RTLD_LAZY
 | RTLD_NOW
 | RTLD_GLOBAL
 | RTLD_LOCAL
 | RTLD_NODELETE
 | RTLD_NOLOAD
 | RTLD_DEEPBIND

external dlopen: libname:string -> flags:rtld_flags list -> lib_handle = "ml_dlopen"

external dlsym: lib:lib_handle -> func_name:string -> func_handle = "ml_dlsym" external dlclose: lib:lib_handle -> unit = "ml_dlclose"

external fficall: func:func_handle -> args:arg_type array -> return:return_type -> arg_type = "ml_fficall" </lang>




Here is the file "dlffi_stubs.c":

<lang c>#include <dlfcn.h>

  1. include <ffi.h>
  1. include <caml/mlvalues.h>
  2. include <caml/memory.h>
  3. include <caml/alloc.h>
  4. include <caml/fail.h>
  1. include <stdio.h>
  2. include <stdlib.h>
  3. include <string.h>

static const int rtld_flags_table[] = {

 RTLD_LAZY,
 RTLD_NOW,
 RTLD_GLOBAL,
 RTLD_LOCAL,
 RTLD_NODELETE,
 RTLD_NOLOAD,
 RTLD_DEEPBIND,

};

static inline int rtld_flags_val( value mask_list ) {

 int c_mask = 0; 
 while (mask_list != Val_emptylist) {
   value head = Field(mask_list, 0);
   c_mask |= rtld_flags_table[Long_val(head)];
   mask_list = Field(mask_list, 1);
 }
 return c_mask;

}

CAMLprim value ml_dlopen( value libname, value ml_flags ) {

 void *lib_handle = dlopen(String_val(libname), rtld_flags_val(ml_flags));
 if (lib_handle == NULL) failwith(dlerror());
 return (value) lib_handle;

}

CAMLprim value ml_dlsym( value lib_handle, value func_name ) {

 char *status;
 void (*func_handle)();
 (void)dlerror();
 *(void **)(&func_handle) = dlsym((void *)lib_handle, String_val(func_name));
 status = dlerror();
 if (status != NULL) failwith(status);
 return (value) func_handle;

}

CAMLprim value ml_dlclose( value lib_handle ) {

 int status = dlclose((void *)lib_handle);
 if (status != 0) failwith("dlclose");
 return Val_unit;

}

  1. define Val_tagged(v, tag) \
 this = caml_alloc(1, tag); \
 Store_field(this, 0, v);

//define ALLOC_INT_PARAM 1 //define ALLOC_DOUBLE_PARAM 1 //define ALLOC_STRING_PARAM 1

CAMLprim value ml_fficall( value func_handle, value ml_args, value ml_return ) {

 CAMLparam3(func_handle, ml_args, ml_return);
 CAMLlocal2(ml_ret, this);
 unsigned int i, nargs;
 ffi_cif    cif;
 ffi_type   **arg_types;
 void       **arg_values;
 ffi_status status;
 ffi_type   *rtype;
 int ffi_prep_cif_failed = 0;
 nargs = Wosize_val(ml_args);
 arg_types  = (ffi_type **) malloc(nargs * sizeof(ffi_type *));
 arg_values = (void **) malloc(nargs * sizeof(void *));
 /* Set up the parameters */
 for (i = 0; i < nargs; i++) {
   value v = Field(ml_args, i);
   switch (Tag_val(v)) {
     case 0:  /* Int */
  1. if defined(ALLOC_INT_PARAM)
     { int *arg;
       arg = malloc(sizeof(int));
       *arg = Long_val(Field(v,0));
       arg_types[i] = &ffi_type_sint;
       arg_values[i] = arg;
     } break;
  1. else
     { arg_types[i] = &ffi_type_sint;
       Field(v,0) >>= 1;
       arg_values[i] = &Field(v,0);
     } break;
  1. endif
     case 1:  /* Float */
     { float *arg;
       arg = malloc(sizeof(float));
       *arg = (float) Double_val(Field(v,0));
       arg_types[i] = &ffi_type_float;
       arg_values[i] = arg;
     } break;
     case 2:  /* Double */
  1. if defined(ALLOC_DOUBLE_PARAM)
     { double *arg;
       arg = malloc(sizeof(double));
       *arg = Double_val(Field(v,0));
       arg_types[i] = &ffi_type_double;
       arg_values[i] = arg;
     } break;
  1. else
     { arg_types[i] = &ffi_type_double;
       arg_values[i] = &Double_val(Field(v,0));
     } break;
  1. endif
     case 3:  /* String */
  1. if defined(ALLOC_STRING_PARAM)
     { char *arg;
       int len = caml_string_length(Field(v,0)) + 1;
       arg = malloc(sizeof(char) * len);
       memcpy(arg, String_val(Field(v,0)), len);
       arg_types[i] = &ffi_type_float;
       arg_values[i] = &arg;
     } break;
  1. else
     { arg_types[i] = &ffi_type_pointer;
       arg_values[i] = &Byte(v,0);
     } break;
  1. endif
     case 4:  /* Ptr */
     { arg_types[i] = &ffi_type_pointer;
       arg_values[i] = &Field(v,0);
     } break;
     case 5:  /* Void */
       caml_invalid_argument("fficall");
       break;
   }
 }
 switch (Int_val(ml_return)) {  /* return_type */
   case 0:  /* Return_int */
     rtype = &ffi_type_sint;
     break;
   case 1:  /* Return_float */
     rtype = &ffi_type_float;
     break;
   case 2:  /* Return_double */
     rtype = &ffi_type_double;
     break;
   case 3:  /* Return_string */
     rtype = &ffi_type_pointer;
     break;
   case 4:  /* Return_ptr */
     rtype = &ffi_type_pointer;
     break;
   case 5:  /* Return_void */
     rtype = &ffi_type_uint;
     break;
 }
 status = ffi_prep_cif(&cif, FFI_DEFAULT_ABI, nargs, rtype, arg_types);
 if (status != FFI_OK) {
   ffi_prep_cif_failed = 1;
   goto freeandfail;
 }
 switch (Int_val(ml_return)) {
   case 0:  /* Return_int */
   { int result;
     ffi_call(&cif, (void (*)(void)) func_handle, &result, arg_values);
     ml_ret = Val_tagged( Val_long(result), 0);
   } break;
   case 1:  /* Return_float */
   { float result;
     ffi_call(&cif, (void (*)(void)) func_handle, &result, arg_values);
     ml_ret = Val_tagged( caml_copy_double(result), 1);
   } break;
   case 2:  /* Return_double */
   { double result;
     ffi_call(&cif, (void (*)(void)) func_handle, &result, arg_values);
     ml_ret = Val_tagged( caml_copy_double(result), 2);
   } break;
   case 3:  /* Return_string */
   { char *result;
     ffi_call(&cif, (void (*)(void)) func_handle, &result, arg_values);
     ml_ret = Val_tagged( caml_copy_string((result == NULL ? "" : result)), 3);
   } break;
   case 4:  /* Return_ptr */
   { void *result;
     ffi_call(&cif, (void (*)(void)) func_handle, &result, arg_values);
     ml_ret = Val_tagged( (value) result, 4);
   } break;
   case 5:  /* Return_void */
   { ffi_arg result;
     ffi_call(&cif, (void (*)(void)) func_handle, &result, arg_values);
     ml_ret = Val_int(0);
   } break;
 }

freeandfail:

 for (i = 0; i < nargs; i++) {
   switch (Tag_val(Field(ml_args, i))) {
     case 0:  /* Int */
  1. if defined(ALLOC_INT_PARAM)
       free(arg_values[i]);
  1. endif
       break;
     case 1:  /* Float */
       free(arg_values[i]);
       break;
     case 2:  /* Double */
  1. if defined(ALLOC_DOUBLE_PARAM)
       free(arg_values[i]);
  1. endif
       break;
     case 3:  /* String */
  1. if defined(ALLOC_STRING_PARAM)
     { char **arg;
       arg = arg_values[i];
       free(*arg);
     }
  1. endif
       break;
     case 4:  /* Ptr */
       break;
   }
 }
 free(arg_values);
 free(arg_types);
 if (ffi_prep_cif_failed)
   failwith("ffi_prep_cif");
 CAMLreturn(ml_ret);

} </lang>




Here is the "Makefile":

<lang make>FFI_LIBS := $(shell pkg-config --libs libffi) FFI_CFLAGS := $(shell pkg-config --cflags libffi)

all: test_opt

dlffi_stubs.o: dlffi_stubs.c ocamlc -g -c -ccopt $(FFI_CFLAGS) $<

dlldlffi_stubs.so: dlffi_stubs.o ocamlmklib -o dlffi_stubs $< -ldl $(FFI_LIBS)

dlffi.cmo: dlffi.ml ocamlc -c $<

dlffi.cmx: dlffi.ml ocamlopt -c $<

dlffi.cma: dlffi.cmo dlldlffi_stubs.so ocamlc -a -o $@ $< -dllib -ldlffi_stubs -cclib -ldl -cclib $(FFI_LIBS)

dlffi.cmxa: dlffi.cmx dlldlffi_stubs.so ocamlopt -a -o $@ $< -cclib -ldlffi_stubs -cclib -ldl -cclib $(FFI_LIBS)

fakelib.so: fakelib.c gcc -g -shared -nostartfiles $< -o $@

.PHONY: vim vim: vim dlffi_stubs.c dlffi.ml Makefile test.ml fakelib.c

test: dlffi.cma fakelib.so ocaml dlffi.cma test.ml

test.opt: test.ml dlffi.cmxa fakelib.so ocamlopt -g -o $@ -I . dlffi.cmxa $<

test_opt: test.opt ./$<

.PHONY: all clean test test_opt clean: rm -f *.[oa] *.so *.cm[ixoa] *.cmxa *.opt </lang>

sed -i "s/        /\t/g" Makefile




Here is a test file named "test.ml":

<lang ocaml>open Dlffi

let handle_return = function

 | Int v    -> Printf.printf "ocaml: got (Int %d)\n%!" v
 | Float v  -> Printf.printf "ocaml: got (Float %g)\n%!" v
 | Double v -> Printf.printf "ocaml: got (Double %g)\n%!" v
 | String v -> Printf.printf "ocaml: got (String \"%s\")\n%!" v
 | Ptr _    -> Printf.printf "ocaml: got (Ptr)\n%!"
 | Void     -> Printf.printf "ocaml: got (Void)\n%!"

let main1() =

 let lib = dlopen "./fakelib.so" [RTLD_LAZY] in
 let func = dlsym ~lib ~func_name:"func_static_handle" in
 handle_return(fficall ~func ~args:[| Float 1.1 |] ~return:Return_int);
 handle_return(fficall ~func ~args:[| Float 2.2 |] ~return:Return_int);
 handle_return(fficall ~func ~args:[| Float 3.3 |] ~return:Return_int);
 dlclose ~lib;

let main2() =

 let lib = dlopen "./fakelib.so" [RTLD_LAZY] in
 let func = dlsym ~lib ~func_name:"func_string_param" in
 handle_return(fficall ~func ~args:[| String "one" |] ~return:Return_string);
 dlclose ~lib;

let main3() =

 let lib = dlopen "./fakelib.so" [RTLD_LAZY] in
 let func = dlsym ~lib ~func_name:"func_int_param" in
 handle_return(fficall ~func ~args:[| Int 244 |] ~return:Return_int);
 dlclose ~lib;

let main4() =

 let lib = dlopen "./fakelib.so" [RTLD_LAZY] in
 let func = dlsym ~lib ~func_name:"func_double_param" in
 handle_return(fficall ~func ~args:[| Double 211.6 |] ~return:Return_double);
 dlclose ~lib;

let main5() =

 let lib = dlopen "./fakelib.so" [RTLD_LAZY] in
 let func = dlsym ~lib ~func_name:"func_float_param" in
 handle_return(fficall ~func ~args:[| Float 211.7 |] ~return:Return_float);
 dlclose ~lib;

let main6() =

 let lib = dlopen "./fakelib.so" [RTLD_LAZY] in
 let func = dlsym ~lib ~func_name:"func_void_param" in
 handle_return(fficall ~func ~args:[| |] ~return:Return_void);
 dlclose ~lib;

let () =

 print_newline();  main1();
 print_newline();  main2();
 print_newline();  main3();
 print_newline();  main4();
 print_newline();  main5();
 print_newline();  main6();
</lang>




Here is a test library "fakelib.c":

<lang c>#include <stdio.h>

  1. include <stdlib.h>
  2. include <string.h>

int func_static_handle(float v) {

 static int handle = 1;
 fprintf(stderr, "fun called %d times, arg: %g\n", handle, v);
 fflush(stderr);  /* ocaml's stderr is different from C's stderr */
 return handle++;

}

char *msgs[] = {

 "alpha",
 "beta",
 "gamma",
 "delta",
 "epsilon",
 "zeta",

};

char *func_string_param(const char *s) {

 static int handle = 0;
 char *ret;
 ret = &(msgs[handle % 6][0]);
 handle++;
 fprintf(stderr, "c: got string (%s) index[%d], returning: [%s]\n", s, handle, ret);
 fflush(stderr);
 return ret;
 //return NULL;  /* test the special case with NULL */

}

int func_int_param(const int d) {

 printf("c: got int (%d)\n", d); fflush(stdout);
 return 107;

}

double func_double_param(const double d) {

 printf("c: got double (%g)\n", d); fflush(stdout);
 return 233.1;

}

float func_float_param(const float f) {

 printf("c: got float (%g)\n", f); fflush(stdout);
 return 233.2;

}

void func_void_param(void) {

 printf("c: void function\n"); fflush(stdout);

} </lang>

If you find some use for this module, you can reuse this code under public domain.

Cookies help us deliver our services. By using our services, you agree to our use of cookies.