Camlid helps to bind a C library or code to OCaml code. Camlid generates the boilerplate C code and OCaml code from a user provided description of the functions from the C library the user wants to be accessible in the OCaml code.
Compared to camlidl, the description is written in OCaml which gives access to a great language support, and it is simple to extend it for its own need (c.f. Expert)
Compared to ctypes, it emphasises the direct generation of C code in order to simplify the build, C datastructures are manipulated in C.
In addition to the following starting example, standalone examples can be found in the tests directory with the corresponding generated files.
Camlid allows to define an executable that will generate here a `mylib_stub.c` and `mylib.ml` file.
Lets suppose that you want to bind to the following C functions, declared in alib.h:
void f_input(int);
void f_output(int *);
int f_with_res();
void f_no_arg_no_result();In your project in a generator sub-directory define the following dune file:
(executable (name generator) (libraries camlid))and generator.ml file:
open Camlid
open Helper
let () = Generate.to_file
(* Indicates the basename used for the generated files *)
"mylib"
(* Indicates header to include *)
~headers:["alib.h"]
[
func "f_input" [ input int_trunc];
func "f_output" [ output (ptr_ref int_trunc)];
func "f_with_res" [] ~result:int_trunc;
func "f_no_arg_no_result" [];
]Each func "name" l describes the signature of one C function named "name" and its parameters. Each elements of l are usually arguments of the function that should become inputs or outputs of the OCaml function. It is usual for a C function to fill an argument of type int * during its call, so an argument of the C function can be an output. Inputs or outputs are distintinguished using the function Camlid.Helper.input or Camlid.Helper.output, which takes as argument a description of the type. Parameters of the C function can also be ignored using instead Camlid.Helper.ignored.
The description of the type of the parameters describe the C type, the OCaml type, the convertion function to use and even in some case the initialisation or deallocation needed. Here the !CamlId.Helper.int_trunc is used for converting OCaml type int to ocaml C type int. It is called int_trunc since the value is truncated on 64-bits platform. The OCaml int type corresponds to intptr_t, and !CamlId.Helper.int can be used in this case.
Now in the directory of your OCaml library, you add in the dune file:
(rule (targets mylib_stub.c mylib.ml) (action (run %{exe:generator/generator.exe})))You can add to this stanza (mode (promote (until-clean))), if you want to see the generated files in the source directory (c.f. modes). Other promotion mode `(mode promote)` should be avoided because dune will not regenerate them in mode release and the generation is different with the version of OCaml.
The compilation of the stubs is now done as usual, for example if alib.h and alib.c are in the current directory.
(library (public_name mylib) (foreign_stubs (language c) (names mylib_stub alib) (flags -Werror=implicit-function-declaration -std=c11)))Camlid do not need to be added in the libraries used. Additional C flags can be added in development mode:
(env (dev (c_flags -Wall -Werror -Wconversion -Wstrict-prototypes --pedantic-errors :standard )))The Camlid.Helper modules allows to describe simply common cases. The kind of arguments are specified using Camlid.Helper.input, Camlid.Helper.output, Camlid.Helper.inout, Camlid.Helper.ignored. Morevoer the result is specified as optional arguments of Camlid.Helper.func. With the label ~result when the result should be returned by the OCaml function and ~result_ignored when the result should be discarded.
OCaml common types can be translated to there C counter-parts:
Camlid | OCaml | C | remarks |
|---|---|---|---|
|
| Loose one bit when converting to OCaml | |
|
| 32 bit on 64 bit architecture, loose half the bits going to C on 64bit and one going to ML in 32 bit | |
|
| ||
|
| ||
|
| ||
|
| ||
|
|
They are unboxed on the ocaml side when possible.
For outputs, using Camlid.Helper.(output int_trunc) is not useful since a parameter of scalar type like int will not change its value after a call. In this case the parameter is more often of type int *, it is achieved using Camlid.Helper.ptr_ref ty. The OCaml type is the same as ty but a pointer to a location of type ty is created and given as parameter of the C function.
Types that require allocations have more versions:
For string the simplest is Camlid.Helper.string_nt ?owned (), which convert between string and char *. The conversion stop at the first null character, in both side. Since OCaml string can contains multiple null characters, in this case only a part of the OCaml string is converted to char *.
If the C function accept the length of the string as a separate argument the function Camlid.Helper.input_string and Camlid.Helper.output_string that return a pair of parameters. They are directly used to describe parameters in Camlid.Helper.func.
(let a = input_string () in
func "f_input" [ a.t; a.len ]);Only a.t will appear as a parameter of the OCaml function, a.len is only used for the C call as a size_t. a.t is a char *.
(let a = output_string () in
func "f_input" [ a.t; a.len ]);In the case of Camlid.Helper.output_string, a.t is a char ** and a.len is a size_t *.
The parameters a.t, a.len can be used in the description of different functions but only once by function. a.len should only be used when a.t is also present, since it is just a view on the length of a.t.
If the length of the string is known by the caller, Camlid.Helper.fixed_length_string should be used.
(let a = fixed_length_string () in
func "f_output" [ a.t; a.len ]);In that case a.len is an input. If the length should also be given to the C call len_used_in_call should be added:
(let a = fixed_length_string ~len_used_in_call:true () in
func "f_output" [ a.t; a.len ]);For arrays the functions take additionnaly the type of the elements, Camlid.Helper.input_array, Camlid.Helper.output_array, Camlid.Helper.fixed_length_array.
OCaml allows to embed any data into an OCaml value with the Abstract_tag. Camlid allows to simply defined such embedding, by simply giving the c type and and the name for the abstract OCaml type.
let pointer = abstract ~ml:"myptr" ~c:"int *" ()
let () =
Generate.to_file ~headers:[ "lib.h" ] "mylib"
[
func "of_int" [ input int; output pointer ];
func "to_int" [ input pointer ] ~result:int;
]It defines an abstract type type myptr.
In the case of output parameters that are not also an input, an initialization function can be given.
let pointer = abstract ~initialize:"lib_init" ~ml:"myptr" ~c:"int *" ()If the user wants to provide user-defined comparison, hashing or just a function to call during deallocation of the OCaml value, a custom value can be defined with Camlid.Helper.custom:
let pointer =
custom ~finalize:"finalize_ptr" ~compare:"compare_ptr" ~hash:"hash_ptr"
~initialize:"initialize_ptr" ~ml:"myptr" ~c:"int *" ()
let () =
Generate.to_file "mylib" ~headers:[ "lib.h" ]
[
func "of_int" [ input int; output pointer ];
func "to_int" [ input pointer ] ~result:int;
]When the C type wrapped is a pointer, Camlid.Helper.custom_ptr is prefered because the functions take the pointer as argument instead of a reference on it.
The Camlid.Expert modules allows to define its own kind of parameters, or convertible types. Their definitions are base on the step of the stub of a C function:
In addition unboxable parameters have conversion function from ML to the unboxed type, and the unboxed type to the C value, but it is not necessary to take into account those differences.
A parameter is of type Camlid.Type.param and it uses directly the variable that are used during the generation. A convertible type is of type Camlid.Type.mlc. It is a template for a parameter and it is implicitly parameterized by the variable of type Camlid.Expr.var it contains (the fields v, c, u, ml) . The function Camlid.Helper.input generates new fresh variables and bind them in the definition of a convertible type.
Camlid handles fresh identifiers with Camlid.Expr.id and variable Camlid.Expr.var. An identifier that is attached to some code (a function, the name of a structure) is handled with Camlid.Expr.defined. The printing function Camlid.Expr.pp_def do not print directly the definition but the identifier associated with this definition, however the definition will be printed before the definition which mentions its identifier. The type Camlid.Expr.code is the definition of a function. It adds to Camlid.Expr.defined the list of its formals. The function Camlid.Expr.code allows to define a function using its body. All the variables that used in its code are added as formals except if mentioned in locals. The function Camlid.Expr.pp_call and Camlid.Expr.pp_calli print the call to the function, and which expression to appply for each formals. A formals that is not binded is applied directly. In a way the variable is still free and propagates to the context.