Ctypes - how to cast a function pointer and then call it?

To provide a simple example imagine we have a Simplistic C library:

typedef void (*void_function)(void);
typedef int (*adder)(int, int);
int add(int a, int b){ return a + b; }
void_function load(void){
        return (void_function)add;

I can bind load with Ctypes using
let load = foreign "load" (Ctypes.void @-> returning Types.void_function)
where void_function is defined in the types pass as a (unit → unit) (I’m using the new dune ctypes stanza)
But now, I would like to “cast” the (unit → unit) function pointer I get when calling load to it’s actual type, (int → int → int) and call it in my ocaml code.
How to achieve this? Using coerce_fn should’nt work, as they do not have the same arity…
To use coerce, I need to be in the function pass, but I also need to use load which isn’t available until function pass is complete… I feel stuck.

I’m new here, so feel free to tell me if I missed something :slight_smile:

Disclaimer: I am a complete beginner in ctypes and I would be very interested if you could share your dune file showing how you use the ctypes stanza!

Could you show how void_function is defined in the type description module? I would suggest to use the following definitions:

let void_function = static_funptr (void @-> returning void)

let adder = static_funptr (int @-> int @-> returning int)

you can then coerce load's result between the two types:

let add = coerce Types.void_function Types.adder (load ())

and call add by coercing it to Foreign.funptr:

let () =
  Format.eprintf "%d@."
    (coerce Types.adder (Foreign.funptr (int @-> int @-> returning int)) add 1 2)

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:

  • /usr/include/my_test.h:
typedef void(*function_pointer)(void);
function_pointer load(char *name);
  • my_test.c (used to generate /usr/lib/libmy_test.so):
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:

  (name main)
  (libraries ctypes ctypes.foreign)
  (flags (:standard -w -9-27))
    (external_library_name my_test)
    (headers (include "/usr/include/my_test.h"))
      (instance Type)
      (functor Type_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,

  1. 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)
  1. function_description.ml:
module Types = Types_generated
module Functions(F : Ctypes.FOREIGN) = struct
  let load = F.(foreign "load" (Ctypes.string @-> returning Types.function_pointer))

note that previous module is available as Types_generated.

Now, I use that in main.ml:

let pointer = C.Function.load "add"
let adder =
    (Foreign.funptr C.Type.binary_operator_type)
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…


does not feels like so :grin:

1 Like