How to pass variant/record using Ctypes inverted?

I have been trying to call OCaml code from C. I have it working for basic types like strings and ints, but I had some trouble with variants. This example is a proof of concept, but I had an error as I don’t know what to return. I think I may have to use the enum module, but there isn’t much documentation on how to use it.

This is my module and function that I want to bind: Test.ml

    module Testing =
    struct
     type person =
     |Hello
     |Bye
     let add a = [Hello;Bye;a]
    end

Test.mli

module Testing:
  sig type person = Hello | Bye val add : person  end

bindings.ml


open Ctypes
open Foreign
open Test


module Stubs(I: Cstubs_inverted.INTERNAL) =
struct
let () = I.internal
       "add" (void @-> returning ?????)  add

end

Some guidance on the enum bindings would be helpful.

Here’s a working example that shows how to expose simple variant types as C enums.

Ingredients

First, here’s the OCaml module that we’ll expose, with a “day” type and a function that cycles round days (day.ml):

type t = Sun | Mon | Tue | Wed | Thu | Fri | Sat [@@deriving enum]

let of_int64 i = let Some d = of_enum (Int64.to_int i) in d
let to_int64 d = Int64.of_int (to_enum d)

let tomorrow : t -> t = function
  | Sun -> Mon | Mon -> Tue | Tue -> Wed | Wed -> Thu
  | Thu -> Fri | Fri -> Sat | Sat -> Sun

Here’s the ctypes code that builds a view over int64_t to represent days, and exposes an enum and a C function that correspond to the OCaml type and function above (bindings.ml):

let day_t = Ctypes.(typedef (view int64_t ~read:Day.of_int64 ~write:Day.to_int64) "enum day_enum")

module Stubs(I: Cstubs_inverted.INTERNAL) = struct
  I.enum ["Sun", 0L; "Mon", 1L; "Tue", 2L; "Wed", 3L;
          "Thu", 4L; "Fri", 5L; "Sat", 6L] day_t;;
  let tomorrow = I.internal "tomorrow" Ctypes.(day_t @-> returning day_t) Day.tomorrow
end

We also need a stub generator module that’s essentially the same as generate.ml in the example repository (gen.ml). The code in the example repository generates a binding for xmlm; we’ll need to change the names so that it builds days.h, days.c and days_bindings.ml.

We’ll need a one-line module that applies the ctypes bindings functor above to the generated code (link.ml):

include Bindings.Stubs(Days_bindings)

Finally, we’ll need to initialize the OCaml runtime somehow. The easiest way is to include the init.c code from the example repository.

Build instructions

Here are the steps to build the example. First, build the code generator from the OCaml code, the ctypes binding description and the generator module:

ocamlfind opt -g -package ctypes.stubs,ppx_deriving.enum day.ml bindings.ml gen.ml -linkpkg -o gen.exe

Next, run the generator to produce days.h, days.c and days_bindings.ml:

./gen.exe

Finally, link the generated code together with the pieces described above to build a shared library:

ocamlfind opt -g -ccopt -fPIC -output-obj -runtime-variant _pic -package ctypes.stubs days.c days_bindings.ml day.cmx bindings.cmx link.ml init.c -linkpkg -o libdays.so

Example

Here’s a piece of C code that uses the library built above (daytest.c)

#include "days.h"
#include <stdio.h>

int daydiff(enum day_enum from, enum day_enum to)
{
  int i = 0;
  while (from != to) {
    from = tomorrow(from);
    i++;
  }
  return i;
}

int main()
{
  printf("from Mon to Fri: %d days\n", daydiff(Mon, Fri));
  printf("from Fri to Mon: %d days\n", daydiff(Fri, Mon));
}

The code can be built, linked against the library, and run in the usual way:

gcc -L . -ldays daytest.c -o daytest.exe
LD_LIBRARY_PATH=. ./daytest.exe

and the output is as expected:

from Mon to Fri: 4 days
from Fri to Mon: 3 days
4 Likes

Thanks for the help. I was a bit late to seeing this, but I got another, similar method to work, but I’ll try this as well. I think I am going to move onto unions after this.
EDIT: I figured out the unions as well. Thank you!