How to pass a C callback for OCaml to call when it has data?

I’m studying on how to integrate MirageOS OCaml TCP/IP stack into my C++ project. I already know how to call C from OCaml and call OCaml from C.

The OCaml will be controlled by C++, not the other way around. So, for a TCP/IP stack, I must be able to send and receive packets. I can easily send data to the TCP/IP stack through C++ calling OCaml, but how to receive it?

For example: the TCP/IP stack might have its own thread waiting for packets to arrive, but at some point, it must deliver it to C++ code. Ideally I’d like to pass a C function onTcpPacket(uint8_t* payload) to act as a callback. That is, OCaml would be able to call onTcpPacket with the payload buffer.

There’s also a concern about which threads can access or be accessed by OCaml runtime but I guess it shouldn’t be a problem.

PS: I’ve read https://caml.inria.fr/pub/docs/manual-ocaml/intfc.html#s:c-callback and even though it says “c callback”, it doesn’t looks like to be what I need. I don’t know what callback means for the author but it’s not passing a C function to be called by OCaml

PS: Mirage’s TCP/IP stack uses Lwt, so its threads come from Lwt.

1 Like

While we’re waiting for someone who may have a good answer …

If you can’t allocate a function object per se, you can allocate a C++ instance, and a C function the OCaml code can call to invoke its method.

Indeed you’re precisely right. In the language of most higher-level programming languages, the terms “callback” and (sometimes) “callout” are reserved for a call from C to the language. The term for “call from language X to C/C++” is invariably “foreign function interface”. Thus (barring odd/interesting/pathological edge-cases) your requirement, “a way to call from Ocaml into C/C++” should be the most straightforward thing, and well-supported.

It’s the other direction ( a call from C/C++ into Ocaml code) that is harder to support.

Is there something I’m missing?

How I understand the question is (not knowing anything about any MirageOS anything)

OCaml: output_as_packet: bytes -> (bytes -> int -> ()) -> int

C++: n = ocaml_output_as_packet(caml_alloc_initialized_string(n, buf), Val_function(TcpPacket));

– except that there is nothing like Val_function, is there?

But he can make a helper function or two and pass around a function in a custom data object, so while the received object can’t be treated directly as a function, OCaml code can call its function indirectly.

type packet_object
external call_packet_object: packet_object -> bytes -> int -> int = “c_call_me”

output_as_packet: bytes -> packet_object -> int

Im trying to understand your answer. output_as_packet would be called with the bytes, but what is the second argument? A function that gets bytes and a size? Why is it needed?

What is Val_function?

I had a very simple idea: pass the int64_t address of the pointer to OCaml, and OCaml calls a simple C function passing this number and the packet, and then C calls another function that executes the pointer with the buffer.

But if you guys have a better idea, I’d prefer

Anyways, can buffers be passed from OCaml to C? Which structure should I use?

MirageOS seems to use Cstruct, but I dont know how it works

The int is indeed a byte count. Not strictly necessary, it just allows you to use a partially filled byte buffer without copying it to one with a smaller size. Maybe I’m too old school.

There is no Val_function, as far as I know.

I believe your int64_t pointer is the very same concept as my proposed work-around, so you may choose whichever you like best. int64_t is simpler to implement than a custom object, and in today’s increasingly tradition bound computing environment there seems to be no chance that it could fail as a way to pass a pointer to a function.

int8_t is string/byte, to my simple way of looking at it anyway.