ocaml-ctypes

repository·master·Indexed 19 days ago

https://github.com/yallop/ocaml-ctypes

A library for binding to C libraries using pure OCaml. It uses combinators to describe C types and functions directly in OCaml to avoid manual C stub functions. Includes Cstubs_structs, which uses C to generate OCaml definitions at compile-time for large structs, OS-specific types, or types defined via C macros.

Tokens
1.2K
Snippets
4
Records
6
Agent score
15%

What's inside ocaml-ctypes

  1. How Cstubs_structs works

    master

    While ctypes typically uses a runtime DSL to define C types, this can be difficult for large structs, OS-specific types, or types defined via C macros. Cstubs_structs solves this by using C itself to generate the OCaml definitions at compile-time, ensuring offsets and sizes are always correct.

    The workflow follows a multi-step generation process:

    1. Define a Stubs Functor: Write an OCaml functor that defines the desired bindings (struct members or macros) using string identifiers that the C generator will look for.
    2. Generate C Source: Write an OCaml program that calls Cstubs_structs.write_c with your functor. When executed, this produces a .c file.
    3. Compile & Run C Generator: Compile and run the generated C program. This program, when executed, outputs an OCaml module containing the correct type definitions.
    4. Apply Generated Module: Compile the resulting OCaml module and apply it to your original Stubs Functor to get the final, type-safe bindings.
  2. How ctypes works for C bindings

    master
    ctypes allows you to bind to C libraries using pure OCaml by describing C types through a set of combinators. Instead of writing C 'stub' functions or generating C code, you use these combinators to describe the structure of numeric types, arrays, pointers, structs, unions, and functions. This approach reduces errors by eliminating the need for manual C extension code.
  3. Define bindings using a Stubs Functor

    master

    To use Cstubs_structs, you must first define a functor. In this functor, you specify the names of the C structs and macros you want to bind. The names provided as strings are critical, as they are used by the C generator to locate the corresponding members in the actual C code.

    For example, to bind a partial struct tm and a macro like SHRT_MAX, you define a module for the struct (specifying the fields you want to access) and a module for the constant.

    (* Example conceptual structure of bindings.ml *)
    module type Stubs = sig
      module Tm : sig
        type t
        val tm_hour : t -> int
        val tm_year : t -> int
      end
      module Limits : sig
        val shrt_max : int
      end
    end
    
    module Stubs (M : module of ... ) = ...
  4. Use generated bindings in your application

    master

    Once the generation pipeline is complete and you have an OCaml module (e.g., Bindings_stubs) produced by the C program, you use it by applying it to your original Bindings.Stubs functor. This provides a seamless interface where accessing the struct or constant feels identical to standard ctypes usage.

    (* In main.ml *)
    module FinalBindings = Bindings.Stubs(Bindings_stubs)
    
    (* Now use FinalBindings.Tm.t and FinalBindings.Limits.shrt_max *)
  5. Generate C source with Cstubs_structs.write_c

    master

    The Cstubs_structs.write_c function is the entry point for the C generation phase. It takes your Stubs Functor as an argument. When the OCaml program containing this call is executed, it generates a C source file that implements the requested bindings.

    (* In bindings_c_gen.ml *)
    Cstubs_structs.write_c (fun module_that_generates_c)
  6. Bind C functions using the `foreign` combinator

    master

    To bind a C function, use the foreign function. You provide the C function name as a string and a type description using ctypes combinators.

    Common combinators used in function descriptions include:

    • ptr T: Represents a pointer to type T.
    • @->: The function arrow combinator used to chain argument types.
    • returning T: Specifies the return type of the function.

    Example binding to C signal functions:

    let sigemptyset = foreign "sigemptyset" (ptr sigset_t @-> returning int)
    let sigfillset = foreign "sigfillset" (ptr sigset_t @-> returning int)
    let sigaddset = foreign "sigaddset" (ptr sigset_t @-> int @-> returning int)
    let sigdelset = foreign "sigdelset" (ptr sigset_t @-> int @-> returning int)
    let sigismember = foreign "sigismember" (ptr sigset_t @-> int @-> returning int)