Got it working, thanks to @thierry-martinez 's answer!
If that might be helpful for someone, or even future me, here is a detailed test setup on a simple example library.
It’s header:
typedef void(*function_pointer)(void);
function_pointer load(char *name);
- my_test.c (used to generate /usr/lib/libmy_test.so):
#include<stdio.h>
#include<string.h>
#include"my_test.h"
typedef int(*int_binary_opperator)(int, int);
typedef void(*function_pointer)(void);
int add(int a, int b){
printf("in C: a = %d, b = %d, add(a, b) = %d\n", a, b, a + b);
return a + b;
}
function_pointer load(char *name){
if(!strcmp(name, "add")) return (function_pointer)add;
else return NULL;
}
I set that up with (please be careful not breaking your system):
sudo gcc -Wall -Wextra -Werror -fpic -o my_test.o -c my_test.c
sudo gcc -shared -o libmy_test.so my_test.o
sudo cp my_test.h /usr/include/
sudo cp libmy_test.so /usr/lib/
Now the library is ready for binding!
Dune now accepts ctypes stanza, that does the generation steps for you.
I used that AND Foreign, because I only get the pointer at execution time.
The dune file:
(executable
(name main)
(libraries ctypes ctypes.foreign)
(flags (:standard -w -9-27))
(ctypes
(external_library_name my_test)
(headers (include "/usr/include/my_test.h"))
(type_description
(instance Type)
(functor Type_description))
(function_description
(instance Function)
(functor Function_description))
(generated_entry_point C)))
Very similar to the “Toy example” in Dealing with Foreign Libraries — Dune documentation.
It uses 2 functors,
- type_description.ml:
module Types(T : Ctypes.TYPE) = struct
let function_pointer_type = T.(void @-> returning void)
let binary_operator_type = T.(int @-> int @-> returning int)
let function_pointer = T.(static_funptr function_pointer_type)
let binary_operator = T.(static_funptr binary_operator_type)
end
- function_description.ml:
module Types = Types_generated
module Functions(F : Ctypes.FOREIGN) = struct
let load = F.(foreign "load" (Ctypes.string @-> returning Types.function_pointer))
end
note that previous module is available as Types_generated.
Now, I use that in main.ml:
let pointer = C.Function.load "add"
let adder =
Ctypes.coerce
C.Type.function_pointer
(Foreign.funptr C.Type.binary_operator_type)
pointer
let a, b = 7, 11
let result = adder a b
let () = Printf.printf "ocaml side: a = %d, b = %d, add a b = %d\n" a b result
The module C (generated, and named like so in dune) contains Type and Function, each containing related definitions.
output of dune ./main.exe
:
in C: a = 7, b = 11, add(a, b) = 18
ocaml side: a = 7, b = 11, add a b = 18
I think it is mandatory to use Foreign in the main, as the pointer is retrieved and cast at run time. If someone can confirm this, or correct me if I am wrong, you are more than welcome!
On another note, I wonder if performance might be an issue with function pointers loaded at run time and used via ocaml…