yallop / ocaml-ctypes

Library for binding to C libraries using pure OCaml
MIT License
363 stars 95 forks source link

Static function pointers for stubs and inverted stubs #711

Open sim642 opened 2 years ago

sim642 commented 2 years ago

Use case

Currently it is possible to use foreign to declare C functions that take function arguments via static_funptr. In various places (including issues of this repository) it is shown how to coerce such function pointers from Foreign.funptrs, which add the libffi dependency. And as far as I can find, there's no other way to construct static_funptrs.

However, it might be that in the OCaml code, one might want to pass a (static) function pointer of another C function (be it another foreign or even internal). Since such function's pointer can be statically taken, there should be no need to involve ctypes.foreign. There should just be a way to get an 'a static_funptr from a function name and 'a fn. That would allow passing one C function as an argument to another C function directly.

Furthermore, without discrimination it should equally well be possible to get the function pointer of an inverted stub internal as static_funptr and pass it to other C functions. Although the final implementation is in OCaml, it would avoid the need for ctypes.foreign since there exists a static C function for it.

Current state

As far as I can find, there is currently no way to do so. The closest I could find is foreign_value, which doesn't seem to be described anywhere. When attempting to use foreign_value with a function name and type, there's an undesired extra level of pointer. Namely, that would give 'a static_funptr ptr, which is a pointer to a pointer to a function. Moreover, the generated C stub emits compiler warnings for incompatible pointer types where it assigns a function pointer to a double pointer.

Possible solutions

I'm not intimately familiar with the internals of ctypes, but from I understand I can think of two solutions:

  1. Add another function like foreign_funptr: string -> 'a fn -> 'a static_funptr to FOREIGN, which is very similar to foreign_value, but handles function pointers correctly and doesn't do use invalid double pointers and provides a function-pointer-specific signature. For inverted stubs, their return type could be changed from unit to a corresponding static_funptr for the pointer to the inverse stubbed C function.

  2. In the spirit of modularity of ctypes, it might be possible to provide alternative families of forward and inverted stub generators that for foreign give the function pointer (instead of the callable OCaml function) and for internal give the function pointer (instead of unit). This would leave all existing interfaces untouched, preserving compatibility, but would require additional C and OCaml stub generators for both directions.

In turn, it would allow joint inverted stubbing and their function pointer taking such that an OCaml function can be passed as static_funptr to some C stubs with no ctypes.foreign involvement. This could cover many uses of inverted stubbing where the OCaml functions are fixed, rather than being arbitrary closures (for which you'd still need ctypes.foreign for).

yallop commented 2 years ago

At the moment I think the easiest way to do this is to add a line of C to create a function pointer to the function you want to bind. For example, if you want to bind a function

int compare (void *, void *);

then you could add an additional line as follows

static int (*compare_pointer) (void *, void *) = &compare;

and then bind compare_pointer using static_funptr and foreign_value like this:

let compare = foreign_value "compare_pointer"
                        (static_funptr (ptr void @-> ptr void @-> returning int))

When attempting to use foreign_value with a function name and type, there's an undesired extra level of pointer

Returning a pointer from foreign_value is a more general interface than returning the value directly, since it supports both reading and writing (as well as repeated reading of a changing value).