This ppx allows you to interface with C libraries by writing C code inline in your OCaml files. It's much easier and more flexible than the traditional way of writing C bindings, and should improve code quality and development velocity.
ppx_c_bindings generates a C file with OCaml bindings from your OCaml file, and calls those bindings from OCaml where appropriate.
You can do just about anything you can do in a C file:
One limitation is that you can't currently use this to write foreign bindings that work with both native and js_of_ocaml compilation.
C++ bindings are also supported via a dedicated mode; see the C++ Bindings section below.
jbuild SetupTo add C bindings in ${NAME}.ml, add the following to the library or executables stanza in your jbuild
(c_names (${NAME}_stubs))
(preprocess (pps (ppx_jane ppx_c_bindings)))and then add the following to the end of the jbuild
;; generate the supporting c code
(rule (
(deps (${NAME}.ml))
(targets (${NAME}_stubs.c))
(action "%{bin:ppx-c-bindings} %{deps}>%{target}")))
;; disable formating of the generated files
(enforce_style ((exceptions (${NAME}_stubs.c))))You can include multiple files by adding them to the c_names and enforce_style exceptions lists, and repeating the generation rule for each file.
Ignore the generated C files from being commited by adding the filenames to the .hgignore.in of each directory where the files are generated.
${NAME}_stubs.cYou can use the [%%c] directive to copy code directly into the generated C file. This is useful for header includes and helper functions.
[%%c {|
#include <math.h>
#include <stdio.h>
|}][%%c {|
int add_one(int x) {
return x + 1;
}
|}]This will be copied verbatim into the generated C code. Use it to include headers or add defines or helper functions in C.
You can include individual C expressions using the [%c.no_alloc] or [%c.alloc] directives (most of the time you probably want [%.no_alloc] but see (#when_to_use_alloc) for more details).
(* Prints "Hello World!" *)
[%c.no_alloc {| printf(stdout, "Hello World!"); |}]You can also pass arguments to the C expression:
(* Prints "The answer is 42!" *)
let answer = 42 in
[%c.no_alloc {| printf("The answer is %ld!", %{answer:int}); |}]We have special handling for various primitive types:
OCaml Type | Parameter Syntax | C Type |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
You can use other types with the syntax %{IDENT:TYPE value} (see the Types section for details on how to use custom types). You can use the TYPE_val macro in C to convert the value to the appropriate pointer type ( see OCaml manual section 4.3 for details). For example, you can read a string using the String_val macro:
(* Prints "Hello Streeter!" *)
let name = "Streeter" in
[%c.no_alloc {| printf("Hello %s!", String_val(%{name:string value})); |}]If you use the same value multiple times, you only need to include the type on the first occurrence.
(* Prints "buffalo buffalo buffalo buffalo buffalo" *)
let word = "buffalo" in
[%c.no_alloc {| printf("%s %s %s %s %s.", String_val(%{word:string value}), String_val(%{word}), String_val(%{word}), String_val(%{word}), String_val(%{word})); |}]By default we assume your expression will not give a result (void in C, unit in ocaml). To return a primitive value from the C expression to your OCaml code, you can annotate your function call with the appropriate type, e.g.:
let result = [%c.no_alloc ({|return sin(%{theta:float});|} : float)]Similar to parameters we have special handling that you can use for many cases.
OCaml Type | Return Syntax | C Type |
|---|---|---|
| none | implemented as Val_unit |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
When you are building with oxcaml we additionally allow the following parameter and return types.
OCaml Type / Return Syntax | Parameter Syntax | C Type |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
FYI: We redefine CAMLreturn where appropriate to have the correct type, so you do not need to use CAMLreturnT explicitly.
You can return a 2-element unboxed tuple from [c...] expressions. The native C function returns a small struct via the SysV small-struct ABI (in rax/rdx on x86-64).
Within the C body you populate the result using the compound literal syntax.
let #(a, b) = [%c.no_alloc ({| return { 42, Val_int(24) }; |} : #(Ox.i64 * int value))]or
let #(a, b) = [%c.no_alloc ({| CAMLreturn( { 42, Val_int(24) } ); |} : #(Ox.i64 * int value))]The struct field types follow each component's natural OxCaml layout:
Component | C field type |
|---|---|
| See Unboxed Types |
|
|
|
|
int, float, Int32.t and Int64.t are not currently supported in tuples (Bad interaction with [@untagged]/[@unboxed] attributes), use an appropriate Ox. type instead.[%c.no_alloc] vs [%c.alloc]Generally the recommendation is to write your bindings with simple C snippets that do not need to do any allocation (on the ocaml heap), but sometimes there are exceptions.
In general if you do anything that requires interaction with the OCaml GC:
then you need to use [%c.alloc].
Tip: you can use go-lang style tuple returns (see unboxed tuples above) to return errors without requiring allocations.
Sometimes its helpful to return more than just primitive values from C.
(* Returns the string "hello" as an OCaml string *)
let one = [%c.alloc ({| CAMLreturn(caml_copy_string("hello")); |}:string value) ]If you want to return anything other than an int,float or boolean you need to:
TYPE value or local TYPE value is important as it indicates that you are providing the correct runtime representation.[%c.alloc] since you are allocating an OCaml valueCAMLreturn (and possibly also CAMLlocal).although if you're just copying a string the example above is all you need.
There are lots of really useful C libraries where the functionality you want is expensive in terms of CPU or wall time. If you're using async you probably don't want to lock up your program while these functions run.
This is where the OCaml runtime lock comes in.
(* Sleep for 30 seconds *)
In_thread.run (fun () ->
[%c.alloc {|
caml_release_runtime_system();
sleep(30);
caml_acquire_runtime_system();
|}])You probably want to review the OCaml manual chapter multithreading in c bindings, however the general rules are:
[%c.alloc (and if returning a value CAMLreturn)caml_release_runtime_system(), followed by caml_acquire_runtime_system()%{.... : ... value} anywhere between the release and acquire.You can't always just use primitive types, sometimes the library you're wrapping defines its own types that you need to pass around.
[%%c {|
struct my_c_struct {
nativeint[2] fields;
uint8_t *ptr;
}
|}]
type foo = [%c {| struct my_c_struct |}
~free:{| if (t->ptr != NULL) {
free(t->ptr);
t->ptr = NULL;
} |}]This will define:
foo that contains the data specified in the C struct definition. When the OCaml runtime garbage collects a value of this type, it will call [free] with t as a pointer to the structure.Foo_val(...) that you can use to access the content of foo within C code.Foo_alloc() that you can use to construct a value of this type from C.val alloc_foo : unit -> foo that you can use to create a new value of type foo in OCaml. The value will be uninitialized (not necessarily zero-initialized), but you can use [%c.no_alloc] (or [%c.alloc]) and Foo_val to populate it.If the type does not define a ~free (GC finalizers are not supported in stack local allocations), then additionally you get
Foo_alloc__stack() to construct a stack local value of the typeval alloc_foo__stack : unit -> foo @ local (best called as (alloc_foo[@alloc stack])) to do the same in OCaml.The content of the type will be on the OCaml heap, so you may only access it if you have the runtime lock, and the value may be moved around during garbage collection.
Most C libraries do not handle this well, so typically you will just have a pointer to the actual C data structure allocated outside the OCaml heap.
type c_string = [%c {| char* |}
~free:{| if ( *t != NULL ) { free( *t ); } |}]
let init () =
let t = alloc_c_string () in
(* [no_alloc] is Ok here since [*v] gets allocated on outside of the OCaml heap by `calloc` *)
let () = [%c.no_alloc {|
char *v;
v = calloc(100);
*C_string_val(%{t:t value}) = v;
|}] in
tThis PPX works in two passes. The first pass runs as a normal OCaml PPX. It discards top level [%%c ...] statements and rewrites [%c ...] expressions to external calls as follows.
[%c.no_alloc ({|return sin(%{theta:float});|} : float)],
[%c.alloc ({|CAMLreturn(caml_copy_string("Hi!");|} : string value)]... turns into ...
(let open struct
external unique_name : float[@unboxed] -> float[@unboxed]=
"PPX_C_BINDINGS_DOES_NOT_SUPPORT_BYTE_CODE" "FILE_long_unique_name"
end in unique_name theta),
(let open struct
external unique_name2 : unit -> string =
"PPX_C_BINDINGS_DOES_NOT_SUPPORT_BYTE_CODE" "FILE_long_unique_name2"
end in unique_name2 ()),The second pass (the rule in the jbuild above), will generate a C file, by copying top level expressions, and generating suitable functions to enclose the code.
CAMLprim double FILE_long_unique_name(double theta) {
return sin((theta));
}
CAMLprim value FILE_long_unique_name2() {
CAMLparam0();
CAMLreturn(caml_copy_string("Hi!"));
}ppx_c_bindings ppx expansion and generated C code are only supported when used with a native target of ocamlc and are not compatible with the bytecode compiler. You should get build time linker errors if this applies to you. (Earlier versions of ppx_c_bindings included some simple bytecode bindings but these were unused so far as we know and entirely untested).You can use ppx_c_bindings to bind to C++ libraries. Pass -cpp to the code generator and target a .cppoutput file. In C++ mode, each generated/inserted C snippet (type definitions, expression wrappers, and the contents of[%%c ...]blocks) is individually wrapped in its ownextern "C" { ... }block, so OCaml sees ordinary C-linkage symbols.[%%cpp ...]` blocks are emitted verbatim (not wrapped).
jbuild setupUse cxx_names / cxx_flags and generate a .cpp file:
(cxx_names (${NAME}_stubs))
(cxx_flags (:standard -std=gnu++17))
(preprocess (pps (ppx_jane ppx_c_bindings)))C++ mode is selected by the stub-generation rule: pass -cpp to ppx-c-bindings and emit a .cpp output. The PPX itself always accepts [%%cpp ...] (it is a no-op at the OCaml level), so there is no separate PPX flag to keep in sync.
(rule (
(deps (${NAME}.ml))
(targets (${NAME}_stubs.cpp))
(action "%{bin:ppx-c-bindings} -cpp %{deps}>%{target}")))If [%%cpp ...] is used without -cpp on ppx-c-bindings, the stub generator emits a #error directive at the original source location, so the mismatch is reported by the C++ compiler instead of silently producing broken stubs.
[%%cpp ...][%%c ...] blocks land inside the generated extern "C" { ... } wrapper. That is fine for C headers (e.g. <math.h>, <string.h>), which typically have their own extern "C" guards internally, but it does not work for C++ headers such as <string> or <vector>, which must be included at C++ linkage.
Use [%%cpp ...] for anything that needs C++ linkage: C++ standard library includes, namespace declarations, helper classes / templates, etc.
[%%cpp
{|
#include <string>
#include <vector>
namespace my_helpers {
static std::string greet(const std::string& who) {
return std::string("hello, ") + who;
}
}
|}]You can freely mix [%%c ...] and [%%cpp ...] blocks. Inside [%c.no_alloc] / [%c.alloc] expression bodies you can still use C++ features such as STL types and lambdas, because the body itself is compiled as C++; only the function's linkage is "C" (the body can use any C++ construct, subject to the caveats below).
extern "C" function bodiesextern "C" only changes linkage (no name mangling, no overloading); it does not restrict what C++ features can appear inside the function body. In practice, effectively anything that is legal in a normal C++ function body is legal in the bodies ppx_c_bindings generates for you:
std::string, std::vector, etc.)auto, range-for, templated helpers, etc.throw and try / catch, as long as exceptions are caught before returningThe useful restrictions to keep in mind are:
std::terminate). Wrap fallible C++ code in try { ... } catch (...) { ... } and convert failures into an OCaml-visible error (e.g. raise an OCaml exception via caml_failwith / caml_raise_*).If support for features like "automatically convert C++ exceptions to OCaml exceptions" would be useful, it is a natural next extension to this mode.
C bindings can be tricky. The following may be helpful to get a better understanding of the details.