Ctypes stubs generation with structs generation

Hello,

I have a library that I am currently using Ctypes to bind against kqueue, and on Linux builds I am using libkqueue so I can use the library there.

In order to make the bindings portable, in order to use the structs and constants, I am using Ctypes structs for subs generation, like below:

module Stubs =
functor
  (S : Cstubs_structs.TYPE)
  ->
  struct
    module Kevent = struct
      (** The complete, public, defintion of a [struct kqueue] *)
      type kevent

      type t = kevent C.structure

      let t : t S.typ = S.structure "kevent"
      let ident = S.(field t "ident" uintptr_t)
      let filter = S.(field t "filter" short)
      let flags = S.(field t "flags" ushort)
      let fflags = S.(field t "fflags" uint)
      let data = S.(field t "data" intptr_t)
      let udata = S.(field t "udata" uintptr_t)
      let () = S.seal t
    end

  (* yadda *)
end

And then I do something like:

module Stubs = Kqueue_bindings.Stubs (Kqueue_bindings_stubs)

module Bindings = struct
  let kqueue = F.foreign "kqueue" C.(void @-> returning int)

  let kevent =
    F.foreign
      ~release_runtime_lock:true
      "kevent"
      C.(
        int
        @-> ptr Stubs.Kevent.t
        @-> int
        @-> ptr Stubs.Kevent.t
        @-> int
        @-> ptr Stubs.Timespec.t
        @-> returning int)
end

Now, I would like to actually be able to statically link against libkqueue so that I can ship Linux users a binary and they don’t have to install it.

My understanding is that Ctypes requires loading a shared object, and to link against a static library I need to go one layer of indirection more and use Cstubs to generate the C code and the ML code for the stub.

I have two questions:

  1. Can I do both stub generations within the same module (the first bit of code I referenced) because it looks like I cannot reference the Kevent.t, for example, inside the functor because it’s not the correct type. Do I have to separate these two code gens out?
  2. Does the stubs generation of the function code support releasing the runtime lock? Ctypes.FOREIGN.foreign does not seem to have an operation for it. (EDIT: I figured this out via the concurrency option).

I’m also kind of wondering: is this worth it? I could just write the C stubs function for this and link against it. Can I use my generated Cstub_structs in a ‘classical’ function call stub?

Thank you

Wow, win to LLMs for once in my life, I tried this trick:

external _force_link_kqueue : unit → unit = “kqueue”
external _force_link_kevent : unit → unit = “kevent”

And it worked!

I’m also kind of wondering: is this worth it? I could just write the C stubs function for this and link against it.

That’s what I would do. Because I find understanding and debugging Ctypes-generated bindings too difficult. I also like the suggestion to have an LLM generate a skeleton.

If I have something in a Ctypes.ptr, is can I call an old-school style stub with that and access the underlying value in the stub? I’m too ignorant to understand the compatibility here.