This chapter describes how user-defined primitives, written in C, can
be linked with Caml code and called from Caml functions.
18.1 |
Overview and compilation information |
|
User primitives are declared in an implementation file or
struct...end module expression using the external keyword:
external name : type = C-function-name
This defines the value name name as a function with type
type that executes by calling the given C function.
For instance, here is how the input primitive is declared in the
standard library module Pervasives:
external input : in_channel -> string -> int -> int -> int
= "input"
Primitives with several arguments are always curried. The C function
does not necessarily have the same name as the ML function.
External functions thus defined can be specified in interface files or
sig...end signatures either as regular values
val name : type
thus hiding their implementation as a C function, or explicitly as
``manifest'' external functions
external name : type = C-function-name
The latter is slightly more efficient, as it allows clients of the
module to call directly the C function instead of going through the
corresponding Caml function.
The arity (number of arguments) of a primitive is automatically
determined from its Caml type in the external declaration, by
counting the number of function arrows in the type. For instance,
input above has arity 4, and the input C function is called with
four arguments. Similarly,
external input2 : in_channel * string * int * int -> int = "input2"
has arity 1, and the input2 C function receives one argument (which
is a quadruple of Caml values).
Type abbreviations are not expanded when determining the arity of a
primitive. For instance,
type int_endo = int -> int
external f : int_endo -> int_endo = "f"
external g : (int -> int) -> (int -> int) = "f"
f has arity 1, but g has arity 2. This allows a primitive to
return a functional value (as in the f example above): just remember
to name the functional return type in a type abbreviation.
18.1.2 |
Implementing primitives |
|
User primitives with arity n £ 5 are implemented by C functions
that take n arguments of type value, and return a result of type
value. The type value is the type of the representations for Caml
values. It encodes objects of several base types (integers,
floating-point numbers, strings, ...), as well as Caml data
structures. The type value and the associated conversion
functions and macros are described in details below. For instance,
here is the declaration for the C function implementing the input
primitive:
CAMLprim value input(value channel, value buffer, value offset, value length)
{
...
}
When the primitive function is applied in a Caml program, the C
function is called with the values of the expressions to which the
primitive is applied as arguments. The value returned by the function is
passed back to the Caml program as the result of the function
application.
User primitives with arity greater than 5 should be implemented by two
C functions. The first function, to be used in conjunction with the
bytecode compiler ocamlc, receives two arguments: a pointer to an
array of Caml values (the values for the arguments), and an
integer which is the number of arguments provided. The other function,
to be used in conjunction with the native-code compiler ocamlopt,
takes its arguments directly. For instance, here are the two C
functions for the 7-argument primitive Nat.add_nat:
CAMLprim value add_nat_native(value nat1, value ofs1, value len1,
value nat2, value ofs2, value len2,
value carry_in)
{
...
}
CAMLprim value add_nat_bytecode(value * argv, int argn)
{
return add_nat_native(argv[0], argv[1], argv[2], argv[3],
argv[4], argv[5], argv[6]);
}
The names of the two C functions must be given in the primitive
declaration, as follows:
external name : type =
bytecode-C-function-name native-code-C-function-name
For instance, in the case of add_nat, the declaration is:
external add_nat: nat -> int -> int -> nat -> int -> int -> int -> int
= "add_nat_bytecode" "add_nat_native"
Implementing a user primitive is actually two separate tasks: on the
one hand, decoding the arguments to extract C values from the given
Caml values, and encoding the return value as a Caml
value; on the other hand, actually computing the result from the arguments.
Except for very simple primitives, it is often preferable to have two
distinct C functions to implement these two tasks. The first function
actually implements the primitive, taking native C values as
arguments and returning a native C value. The second function,
often called the ``stub code'', is a simple wrapper around the first
function that converts its arguments from Caml values to C values,
call the first function, and convert the returned C value to Caml
value. For instance, here is the stub code for the input
primitive:
CAMLprim value input(value channel, value buffer, value offset, value length)
{
return Val_long(getblock((struct channel *) channel,
&Byte(buffer, Long_val(offset)),
Long_val(length)));
}
(Here, Val_long, Long_val and so on are conversion macros for the
type value, that will be described later. The CAMLprim macro
expands to the required compiler directives to ensure that the
function following it is exported and accessible from Caml.)
The hard work is performed by the function getblock, which is
declared as:
long getblock(struct channel * channel, char * p, long n)
{
...
}
To write C code that operates on Objective Caml values, the following
include files are provided:
Include file |
Provides |
caml/mlvalues.h |
definition of the value type, and conversion macros |
caml/alloc.h |
allocation functions (to create structured Caml
objects) |
caml/memory.h |
miscellaneous memory-related functions
and macros (for GC interface, in-place modification of structures, etc). |
caml/fail.h |
functions for raising exceptions
(see section 18.4.5) |
caml/callback.h |
callback from C to Caml (see
section 18.7). |
caml/custom.h |
operations on custom blocks (see
section 18.9). |
caml/intext.h |
operations for writing user-defined
serialization and deserialization functions for custom blocks
(see section 18.9). |
These files reside in the caml/ subdirectory of the Objective Caml
standard library directory (usually /usr/local/lib/ocaml).
18.1.3 |
Statically linking C code with Caml code |
|
The Objective Caml runtime system comprises three main parts: the bytecode
interpreter, the memory manager, and a set of C functions that
implement the primitive operations. Some bytecode instructions are
provided to call these C functions, designated by their offset in a
table of functions (the table of primitives).
In the default mode, the Caml linker produces bytecode for the
standard runtime system, with a standard set of primitives. References
to primitives that are not in this standard set result in the
``unavailable C primitive'' error. (Unless dynamic loading of C
libraries is supported -- see section 18.1.4 below.)
In the ``custom runtime'' mode, the Caml linker scans the
object files and determines the set of required primitives. Then, it
builds a suitable runtime system, by calling the native code linker with:
-
the table of the required primitives;
- a library that provides the bytecode interpreter, the
memory manager, and the standard primitives;
- libraries and object code files (.o files) mentioned on the
command line for the Caml linker, that provide implementations
for the user's primitives.
This builds a runtime system with the required primitives. The Caml
linker generates bytecode for this custom runtime system. The
bytecode is appended to the end of the custom runtime system, so that
it will be automatically executed when the output file (custom
runtime + bytecode) is launched.
To link in ``custom runtime'' mode, execute the ocamlc command with:
-
the -custom option;
- the names of the desired Caml object files (.cmo and .cma files) ;
- the names of the C object files and libraries (.o and .a
files) that implement the required primitives. Under Unix and Windows,
a library named libname.a residing in one of the standard
library directories can also be specified as -cclib -lname.
If you are using the native-code compiler ocamlopt, the -custom
flag is not needed, as the final linking phase of ocamlopt always
builds a standalone executable. To build a mixed Caml/C executable,
execute the ocamlopt command with:
-
the names of the desired Caml native object files (.cmx and
.cmxa files);
- the names of the C object files and libraries (.o, .a,
.so or .dll files) that implement the required primitives.
Starting with OCaml 3.00, it is possible to record the
-custom option as well as the names of C libraries in a Caml
library file .cma or .cmxa. For instance, consider a Caml library
mylib.cma, built from the Caml object files a.cmo and b.cmo,
which reference C code in libmylib.a. If the library is
built as follows:
ocamlc -a -o mylib.cma -custom a.cmo b.cmo -cclib -lmylib
users of the library can simply link with mylib.cma:
ocamlc -o myprog mylib.cma ...
and the system will automatically add the -custom and -cclib -lmylib options, achieving the same effect as
ocamlc -o myprog -custom a.cmo b.cmo ... -cclib -lmylib
The alternative, of course, is to build the library without extra
options:
ocamlc -a -o mylib.cma a.cmo b.cmo
and then ask users to provide the -custom and -cclib -lmylib
options themselves at link-time:
ocamlc -o myprog -custom mylib.cma ... -cclib -lmylib
The former alternative is more convenient for the final users of the
library, however.
18.1.4 |
Dynamically linking C code with Caml code |
|
Starting with OCaml 3.03, an alternative to static linking of C code
using the -custom code is provided. In this mode, the Caml linker
generates a pure bytecode executable (no embedded custom runtime
system) that simply records the names of dynamically-loaded libraries
containing the C code. The standard Caml runtime system ocamlrun
then loads dynamically these libraries, and resolves references to the
required primitives, before executing the bytecode.
This facility is currently supported and known to work well under
Linux and Windows (the native Windows port). It is supported, but not
fully tested yet, under FreeBSD, Tru64, Solaris and Irix. It is not
supported yet under other Unixes, Cygwin for Windows, and MacOS.
To dynamically link C code with Caml code, the C code must first be
compiled into a shared library (under Unix) or DLL (under Windows).
This involves 1- compiling the C files with appropriate C compiler
flags for producing position-independent code, and 2- building a
shared library from the resulting object files. The resulting shared
library or DLL file must be installed in a place where ocamlrun can
find it later at program start-up time (see
section 10.3).
Finally (step 3), execute the ocamlc command with
-
the names of the desired Caml object files (.cmo and .cma files) ;
- the names of the C shared libraries (.so or .dll files) that
implement the required primitives. Under Unix and Windows,
a library named dllname.so (respectively, .dll) residing
in one of the standard library directories can also be specified as
-dllib -lname.
Do not set the -custom flag, otherwise you're back to static linking
as described in section 18.1.3.
Under Unix, the ocamlmklib tool (see section 18.10)
automates steps 2 and 3.
As in the case of static linking, it is possible (and recommended) to
record the names of C libraries in a Caml .cmo library archive.
Consider again a Caml library
mylib.cma, built from the Caml object files a.cmo and b.cmo,
which reference C code in dllmylib.so. If the library is
built as follows:
ocamlc -a -o mylib.cma a.cmo b.cmo -dllib -lmylib
users of the library can simply link with mylib.cma:
ocamlc -o myprog mylib.cma ...
and the system will automatically add the -dllib -lmylib option,
achieving the same effect as
ocamlc -o myprog a.cmo b.cmo ... -dllib -lmylib
Using this mechanism, users of the library mylib.cma do not need to
known that it references C code, nor whether this C code must be
statically linked (using -custom) or dynamically linked.
18.1.5 |
Choosing between static linking and dynamic linking |
|
After having described two different ways of linking C code with Caml
code, we now review the pros and cons of each, to help developers of
mixed Caml/C libraries decide.
The main advantage of dynamic linking is that it preserves the
platform-independence of bytecode executables. That is, the bytecode
executable contains no machine code, and can therefore be compiled on
platform A and executed on other platforms B, C, ..., as long
as the required shared libraries are available on all these
platforms. In contrast, executables generated by ocamlc -custom run
only on the platform on which they were created, because they embark a
custom-tailored runtime system specific to that platform. In
addition, dynamic linking results in smaller executables.
Another advantage of dynamic linking is that the final users of the
library do not need to have a C compiler, C linker, and C runtime
libraries installed on their machines. This is no big deal under
Unix and Cygwin, but many Windows users are reluctant to install
Microsoft Visual C just to be able to do ocamlc -custom.
There are two drawbacks to dynamic linking. The first is that the
resulting executable is not stand-alone: it requires the shared
libraries, as well as ocamlrun, to be installed on the machine
executing the code. If you wish to distribute a stand-alone
executable, it is better to link it statically, using ocamlc -custom -ccopt -static or ocamlopt -ccopt -static. Dynamic linking also
raises the ``DLL hell'' problem: some care must be taken to ensure
that the right versions of the shared libraries are found at start-up
time.
The second drawback of dynamic linking is that it complicates the
construction of the library. The C compiler and linker flags to
compile to position-independent code and build a shared library vary
wildly between different Unix systems. Also, dynamic linking is not
supported on all Unix systems, requiring a fall-back case to static
linking in the Makefile for the library. The ocamlmklib command
(see section 18.10) tries to hide some of these system
dependencies.
In conclusion: dynamic linking is highly recommended under the native
Windows port, because there are no portability problems and it is much
more convenient for the end users. Under Unix, dynamic linking should
be considered for mature, frequently used libraries because it
enhances platform-independence of bytecode executables. For new or
rarely-used libraries, static linking is much simpler to set up in a
portable way.
18.1.6 |
Building standalone custom runtime systems |
|
It is sometimes inconvenient to build a custom runtime system each
time Caml code is linked with C libraries, like ocamlc -custom does.
For one thing, the building of the runtime system is slow on some
systems (that have bad linkers or slow remote file systems); for
another thing, the platform-independence of bytecode files is lost,
forcing to perform one ocamlc -custom link per platform of interest.
An alternative to ocamlc -custom is to build separately a custom
runtime system integrating the desired C libraries, then generate
``pure'' bytecode executables (not containing their own runtime
system) that can run on this custom runtime. This is achieved by the
-make_runtime and -use_runtime flags to ocamlc. For example,
to build a custom runtime system integrating the C parts of the
``Unix'' and ``Threads'' libraries, do:
ocamlc -make-runtime -o /home/me/ocamlunixrun unix.cma threads.cma
To generate a bytecode executable that runs on this runtime system,
do:
ocamlc -use-runtime /home/me/ocamlunixrun -o myprog \
unix.cma threads.cma your .cmo and .cma files
The bytecode executable myprog can then be launched as usual:
myprog args or /home/me/ocamlunixrun myprog args.
Notice that the bytecode libraries unix.cma and threads.cma must
be given twice: when building the runtime system (so that ocamlc
knows which C primitives are required) and also when building the
bytecode executable (so that the bytecode from unix.cma and
threads.cma is actually linked in).
All Caml objects are represented by the C type value,
defined in the include file caml/mlvalues.h, along with macros to
manipulate values of that type. An object of type value is either:
-
an unboxed integer;
- a pointer to a block inside the heap (such as the blocks
allocated through one of the
alloc_*
functions below);
- a pointer to an object outside the heap (e.g., a pointer to a block
allocated by malloc, or to a C variable).
Integer values encode 31-bit signed integers (63-bit on 64-bit
architectures). They are unboxed (unallocated).
Blocks in the heap are garbage-collected, and therefore have strict
structure constraints. Each block includes a header containing the
size of the block (in words), and the tag of the block.
The tag governs how the contents of the blocks are structured. A tag
lower than No_scan_tag indicates a structured block, containing
well-formed values, which is recursively traversed by the garbage
collector. A tag greater than or equal to No_scan_tag indicates a
raw block, whose contents are not scanned by the garbage collector.
For the benefits of ad-hoc polymorphic primitives such as equality and
structured input-output, structured and raw blocks are further
classified according to their tags as follows:
Tag |
Contents of the block |
0 to No_scan_tag-1 |
A structured block (an array of
Caml objects). Each field is a value. |
Closure_tag |
A closure representing a functional value. The first
word is a pointer to a piece of code, the remaining words are
value containing the environment. |
String_tag |
A character string. |
Double_tag |
A double-precision floating-point number. |
Double_array_tag |
An array or record of double-precision
floating-point numbers. |
Abstract_tag |
A block representing an abstract datatype. |
Custom_tag |
A block representing an abstract datatype
with user-defined finalization, comparison, hashing,
serialization and deserialization functions atttached. |
18.2.3 |
Pointers outside the heap |
|
Any word-aligned pointer to an address outside the heap can be safely
cast to and from the type value. This includes pointers returned by
malloc, and pointers to C variables (of size at least one word)
obtained with the &
operator.
Caution: if a pointer returned by malloc is cast to the type value
and returned to Caml, explicit deallocation of the pointer using
free is potentially dangerous, because the pointer may still be
accessible from the Caml world. Worse, the memory space deallocated
by free can later be reallocated as part of the Caml heap; the
pointer, formerly pointing outside the Caml heap, now points inside
the Caml heap, and this can confuse the garbage collector. To avoid
these problems, it is preferable to wrap the pointer in a Caml block
with tag Abstract_tag or Custom_tag.
18.3 |
Representation of Caml data types |
|
This section describes how Caml data types are encoded in the
value type.
Caml type |
Encoding |
int |
Unboxed integer values. |
char |
Unboxed integer values (ASCII code). |
float |
Blocks with tag Double_tag. |
string |
Blocks with tag String_tag. |
int32 |
Blocks with tag Custom_tag. |
int64 |
Blocks with tag Custom_tag. |
nativeint |
Blocks with tag Custom_tag. |
Tuples are represented by pointers to blocks, with tag 0.
Records are also represented by zero-tagged blocks. The ordering of
labels in the record type declaration determines the layout of
the record fields: the value associated to the label
declared first is stored in field 0 of the block, the value associated
to the label declared next goes in field 1, and so on.
As an optimization, records whose fields all have static type float
are represented as arrays of floating-point numbers, with tag
Double_array_tag. (See the section below on arrays.)
Arrays of integers and pointers are represented like tuples,
that is, as pointers to blocks tagged 0. They are accessed with the
Field macro for reading and the modify function for writing.
Arrays of floating-point numbers (type float array)
have a special, unboxed, more efficient representation.
These arrays are represented by pointers to blocks with tag
Double_array_tag. They should be accessed with the Double_field
and Store_double_field macros.
Constructed terms are represented either by unboxed integers (for
constant constructors) or by blocks whose tag encode the constructor
(for non-constant constructors). The constant constructors and the
non-constant constructors for a given concrete type are numbered
separately, starting from 0, in the order in which they appear in the
concrete type declaration. Constant constructors are represented by
unboxed integers equal to the constructor number. Non-constant
constructors declared with a n-tuple as argument are represented by
a block of size n, tagged with the constructor number; the n
fields contain the components of its tuple argument. Other
non-constant constructors are represented by a block of size 1, tagged
with the constructor number; the field 0 contains the value of the
constructor argument. Example:
Constructed term |
Representation |
() |
Val_int(0) |
false |
Val_int(0) |
true |
Val_int(1) |
[] |
Val_int(0) |
h::t |
Block with size = 2 and tag = 0; first field
contains h, second field t |
As a convenience, caml/mlvalues.h defines the macros Val_unit,
Val_false and Val_true to refer to (), false and true.
Objects are represented as zero-tagged blocks. The first field of the
block refers to the object class and associated method suite, in a
format that cannot easily be exploited from C. The remaining fields of
the object contain the values of the instance variables of the object.
Instance variables are stored in the order in which they appear in the
class definition (taking inherited classes into account).
One may extract a public method from an object using the C function
caml_get_public_method (declared in <caml/mlvalues.h>.)
Since public method tags are hashed in the same way as variant tags,
and methods are functions taking self as first argument, if you want
to do the method call foo#bar from the C side, you should call:
callback(caml_get_public_method(foo, hash_variant("bar")), foo);
Like constructed terms, values of variant types are represented either
as integers (for variants without arguments), or as blocks (for
variants with an argument). Unlike constructed terms, variant
constructors are not numbered starting from 0, but identified by a
hash value (a Caml integer), as computed by the C function
hash_variant (declared in <caml/mlvalues.h>):
the hash value for a variant constructor named, say, VConstr
is hash_variant("VConstr").
The variant value `VConstr is represented by
hash_variant("VConstr"). The variant value `VConstr(v) is
represented by a block of size 2 and tag 0, with field number 0
containing hash_variant("VConstr") and field number 1 containing
v.
Unlike constructed values, variant values taking several arguments are
not flattened. That is, `VConstr(v, v') is
represented by a block of size 2, whose field number 1 contains
the representation of the pair (v, v'), but not as a
block of size 3 containing v and v' in fields 1 and 2.
18.4 |
Operations on values |
|
-
Is_long(v) is true if value v is an immediate integer,
false otherwise
- Is_block(v) is true if value v is a pointer to a block,
and false if it is an immediate integer.
-
Val_long(l) returns the value encoding the long int l.
- Long_val(v) returns the long int encoded in value v.
- Val_int(i) returns the value encoding the int i.
- Int_val(v) returns the int encoded in value v.
- Val_bool(x) returns the Caml boolean representing the
truth value of the C integer x.
- Bool_val(v) returns 0 if v is the Caml boolean
false, 1 if v is true.
- Val_true, Val_false represent the Caml booleans true and false.
-
Wosize_val(v) returns the size of the block v, in words,
excluding the header.
- Tag_val(v) returns the tag of the block v.
- Field(v, n) returns the value contained in the
nth field of the structured block v. Fields are numbered from 0 to
Wosize_val(v)-1.
- Store_field(b, n, v) stores the value
v in the field number n of value b, which must be a
structured block.
- Code_val(v) returns the code part of the closure v.
- string_length(v) returns the length (number of characters)
of the string v.
- Byte(v, n) returns the nth character of the string
v, with type char. Characters are numbered from 0 to
string_length(v)-1.
- Byte_u(v, n) returns the nth character of the string
v, with type unsigned char. Characters are numbered from 0 to
string_length(v)-1.
- String_val(v) returns a pointer to the first byte of the string
v, with type char *. This pointer is a valid C string: there is a
null character after the last character in the string. However, Caml
strings can contain embedded null characters, that will confuse
the usual C functions over strings.
- Double_val(v) returns the floating-point number contained in
value v, with type double.
- Double_field(v, n) returns
the nth element of the array of floating-point numbers v (a
block tagged Double_array_tag).
- Store_double_field(v, n, d) stores the double precision floating-point number d
in the nth element of the array of floating-point numbers v.
- Data_custom_val(v) returns a pointer to the data part
of the custom block v. This pointer has type void * and must
be cast to the type of the data contained in the custom block.
- Int32_val(v) returns the 32-bit integer contained
in the int32 v.
- Int64_val(v) returns the 64-bit integer contained
in the int64 v.
- Nativeint_val(v) returns the long integer contained
in the nativeint v.
The expressions Field(v, n),
Byte(v, n) and
Byte_u(v, n)
are valid l-values. Hence, they can be assigned to, resulting in an
in-place modification of value v.
Assigning directly to Field(v, n) must
be done with care to avoid confusing the garbage collector (see
below).
-
Atom(t) returns an ``atom'' (zero-sized block) with tag t.
Zero-sized blocks are preallocated outside of the heap. It is
incorrect to try and allocate a zero-sized block using the functions below.
For instance, Atom(0) represents the empty array.
- alloc(n, t) returns a fresh block of size n
with tag t. If t is less than No_scan_tag, then the
fields of the block are initialized with a valid value in order to
satisfy the GC constraints.
- alloc_tuple(n) returns a fresh block of size
n words, with tag 0.
- alloc_string(n) returns a string value of length n characters.
The string initially contains garbage.
- copy_string(s) returns a string value containing a copy of
the null-terminated C string s (a char *).
- copy_double(d) returns a floating-point value initialized
with the double d.
- copy_int32(i), copy_int64(i) and
copy_nativeint(i) return a value of Caml type int32,
int64 and nativeint, respectively, initialized with the integer
i.
- alloc_array(f, a) allocates an array of values, calling
function f over each element of the input array a to transform it
into a value. The array a is an array of pointers terminated by the
null pointer. The function f receives each pointer as argument, and
returns a value. The zero-tagged block returned by
alloc_array(f, a) is filled with the values returned by the
successive calls to f. (This function must not be used to build
an array of floating-point numbers.)
- copy_string_array(p) allocates an array of strings, copied from
the pointer to a string array p (a
char **
). p must
be NULL-terminated.
The following functions are slightly more efficient than alloc, but
also much more difficult to use.
From the standpoint of the allocation functions, blocks are divided
according to their size as zero-sized blocks, small blocks (with size
less than or equal to Max_young_wosize
), and large blocks (with
size greater than Max_young_wosize
). The constant
Max_young_wosize
is declared in the include file mlvalues.h. It
is guaranteed to be at least 64 (words), so that any block with
constant size less than or equal to 64 can be assumed to be small. For
blocks whose size is computed at run-time, the size must be compared
against Max_young_wosize
to determine the correct allocation procedure.
-
alloc_small(n, t) returns a fresh small block of size
n £ Max_young_wosize words, with tag t.
If this block is a structured block (i.e. if t < No_scan_tag), then
the fields of the block (initially containing garbage) must be initialized
with legal values (using direct assignment to the fields of the block)
before the next allocation.
- alloc_shr(n, t) returns a fresh block of size
n, with tag t.
The size of the block can be greater than
Max_young_wosize
. (It
can also be smaller, but in this case it is more efficient to call
alloc_small instead of alloc_shr.)
If this block is a structured block (i.e. if t < No_scan_tag), then
the fields of the block (initially containing garbage) must be initialized
with legal values (using the initialize function described below)
before the next allocation.
Two functions are provided to raise two standard exceptions:
-
failwith(s), where s is a null-terminated C string (with
type
char *
), raises exception Failure with argument s.
- invalid_argument(s), where s is a null-terminated C
string (with type
char *
), raises exception Invalid_argument
with argument s.
Raising arbitrary exceptions from C is more delicate: the
exception identifier is dynamically allocated by the Caml program, and
therefore must be communicated to the C function using the
registration facility described below in section 18.7.3.
Once the exception identifier is recovered in C, the following
functions actually raise the exception:
-
raise_constant(id) raises the exception id with
no argument;
- raise_with_arg(id, v) raises the exception
id with the Caml value v as argument;
- raise_with_string(id, s), where s is a
null-terminated C string, raises the exception id with a copy of
the C string s as argument.
18.5 |
Living in harmony with the garbage collector |
|
Unused blocks in the heap are automatically reclaimed by the garbage
collector. This requires some cooperation from C code that
manipulates heap-allocated blocks.
All the macros described in this section are declared in the
memory.h header file.
Rule 1
A function that has parameters or local variables of type value must
begin with a call to one of the CAMLparam macros and return with
CAMLreturn or CAMLreturn0.
There are six CAMLparam macros: CAMLparam0 to CAMLparam5, which
take zero to five arguments respectively. If your function has fewer
than 5 parameters of type value, use the corresponding macros
with these parameters as arguments. If your function has more than 5
parameters of type value, use CAMLparam5 with five of these
parameters, and use one or more calls to the CAMLxparam macros for
the remaining parameters (CAMLxparam1 to CAMLxparam5).
The macros CAMLreturn and CAMLreturn0 are used to replace the C
keyword return. Every occurence of return x must be replaced by
CAMLreturn (x), every occurence of return without argument must be
replaced by CAMLreturn0. If your C function is a procedure (i.e. if
it returns void), you must insert CAMLreturn0 at the end (to replace
C's implicit return).
Note:
some C compilers give bogus warnings about unused
variables caml__dummy_xxx at each use of CAMLparam and
CAMLlocal. You should ignore them.
Example:
void foo (value v1, value v2, value v3)
{
CAMLparam3 (v1, v2, v3);
...
CAMLreturn0;
}
Note:
if your function is a primitive with more than 5 arguments
for use with the byte-code runtime, its arguments are not values and
must not be declared (they have types value * and int).
Rule 2
Local variables of type value must be declared with one of the
CAMLlocal macros. Arrays of values are declared with
CAMLlocalN.
The macros CAMLlocal1 to CAMLlocal5 declare and initialize one to
five local variables of type value. The variable names are given as
arguments to the macros. CAMLlocalN(x, n) declares
and initializes a local variable of type value [n]. You can
use several calls to these macros if you have more than 5 local
variables. You can also use them in nested C blocks within the
function.
Example:
value bar (value v1, value v2, value v3)
{
CAMLparam3 (v1, v2, v3);
CAMLlocal1 (result);
result = alloc (3, 0);
...
CAMLreturn (result);
}
Rule 3
Assignments to the fields of structured blocks must be done with the
Store_field macro (for normal blocks) or Store_double_field macro
(for arrays and records of floating-point numbers). Other assignments
must not use Store_field nor Store_double_field.
Store_field (b, n, v) stores the value
v in the field number n of value b, which must be a
block (i.e. Is_block(b) must be true).
Example:
value bar (value v1, value v2, value v3)
{
CAMLparam3 (v1, v2, v3);
CAMLlocal1 (result);
result = alloc (3, 0);
Store_field (result, 0, v1);
Store_field (result, 1, v2);
Store_field (result, 2, v3);
CAMLreturn (result);
}
Warning:
The first argument of Store_field and
Store_double_field must be a variable declared by CAMLparam* or
a parameter declared by CAMLlocal* to ensure that a garbage
collection triggered by the evaluation of the other arguments will not
invalidate the first argument after it is computed.
Rule 4 Global variables containing values must be registered
with the garbage collector using the register_global_root function.
Registration of a global variable v is achieved by calling
register_global_root(&v) just before a valid value is stored in v
for the first time.
A registered global variable v can be un-registered by calling
remove_global_root(&v).
Note: The CAML macros use identifiers (local variables, type
identifiers, structure tags) that start with caml__. Do not use any
identifier starting with caml__ in your programs.
We now give the GC rules corresponding to the low-level allocation
functions alloc_small and alloc_shr. You can ignore those rules
if you stick to the simplified allocation function alloc.
Rule 5 After a structured block (a block with tag less than
No_scan_tag) is allocated with the low-level functions, all fields
of this block must be filled with well-formed values before the next
allocation operation. If the block has been allocated with
alloc_small, filling is performed by direct assignment to the fields
of the block:
Field(v, n) = vn;
If the block has been allocated with alloc_shr, filling is performed
through the initialize function:
initialize(&Field(v, n), vn);
The next allocation can trigger a garbage collection. The garbage
collector assumes that all structured blocks contain well-formed
values. Newly created blocks contain random data, which generally do
not represent well-formed values.
If you really need to allocate before the fields can receive their
final value, first initialize with a constant value (e.g.
Val_unit), then allocate, then modify the fields with the correct
value (see rule 6).
Rule 6 Direct assignment to a field of a block, as in
Field(v, n) = w;
is safe only if v is a block newly allocated by alloc_small;
that is, if no allocation took place between the
allocation of v and the assignment to the field. In all other cases,
never assign directly. If the block has just been allocated by alloc_shr,
use initialize to assign a value to a field for the first time:
initialize(&Field(v, n), w);
Otherwise, you are updating a field that previously contained a
well-formed value; then, call the modify function:
modify(&Field(v, n), w);
To illustrate the rules above, here is a C function that builds and
returns a list containing the two integers given as parameters.
First, we write it using the simplified allocation functions:
value alloc_list_int(int i1, int i2)
{
CAMLparam0 ();
CAMLlocal2 (result, r);
r = alloc(2, 0); /* Allocate a cons cell */
Store_field(r, 0, Val_int(i2)); /* car = the integer i2 */
Store_field(r, 1, Val_int(0)); /* cdr = the empty list [] */
result = alloc(2, 0); /* Allocate the other cons cell */
Store_field(result, 0, Val_int(i1)); /* car = the integer i1 */
Store_field(result, 1, r); /* cdr = the first cons cell */
CAMLreturn (result);
}
Here, the registering of result is not strictly needed, because no
allocation takes place after it gets its value, but it's easier and
safer to simply register all the local variables that have type value.
Here is the same function written using the low-level allocation
functions. We notice that the cons cells are small blocks and can be
allocated with alloc_small, and filled by direct assignments on
their fields.
value alloc_list_int(int i1, int i2)
{
CAMLparam0 ();
CAMLlocal2 (result, r);
r = alloc_small(2, 0); /* Allocate a cons cell */
Field(r, 0) = Val_int(i2); /* car = the integer i2 */
Field(r, 1) = Val_int(0); /* cdr = the empty list [] */
result = alloc_small(2, 0); /* Allocate the other cons cell */
Field(result, 0) = Val_int(i1); /* car = the integer i1 */
Field(result, 1) = r; /* cdr = the first cons cell */
CAMLreturn (result);
}
In the two examples above, the list is built bottom-up. Here is an
alternate way, that proceeds top-down. It is less efficient, but
illustrates the use of modify.
value alloc_list_int(int i1, int i2)
{
CAMLparam0 ();
CAMLlocal2 (tail, r);
r = alloc_small(2, 0); /* Allocate a cons cell */
Field(r, 0) = Val_int(i1); /* car = the integer i1 */
Field(r, 1) = Val_int(0); /* A dummy value
tail = alloc_small(2, 0); /* Allocate the other cons cell */
Field(tail, 0) = Val_int(i2); /* car = the integer i2 */
Field(tail, 1) = Val_int(0); /* cdr = the empty list [] */
modify(&Field(r, 1), tail); /* cdr of the result = tail */
return r;
}
It would be incorrect to perform
Field(r, 1) = tail directly, because the allocation of tail
has taken place since r was allocated. tail is not registered as
a root because there is no allocation between the assignment where it
takes its value and the modify statement that uses the value.
This section outlines how the functions from the Unix curses library
can be made available to Objective Caml programs. First of all, here is
the interface curses.mli that declares the curses primitives and
data types:
type window (* The type "window" remains abstract *)
external initscr: unit -> window = "curses_initscr"
external endwin: unit -> unit = "curses_endwin"
external refresh: unit -> unit = "curses_refresh"
external wrefresh : window -> unit = "curses_wrefresh"
external newwin: int -> int -> int -> int -> window = "curses_newwin"
external mvwin: window -> int -> int -> unit = "curses_mvwin"
external addch: char -> unit = "curses_addch"
external mvwaddch: window -> int -> int -> char -> unit = "curses_mvwaddch"
external addstr: string -> unit = "curses_addstr"
external mvwaddstr: window -> int -> int -> string -> unit = "curses_mvwaddstr"
(* lots more omitted *)
To compile this interface:
ocamlc -c curses.mli
To implement these functions, we just have to provide the stub code;
the core functions are already implemented in the curses library.
The stub code file, curses.o, looks like:
#include <curses.h>
#include <mlvalues.h>
value curses_initscr(value unit)
{
CAMLparam1 (unit);
CAMLreturn ((value) initscr()); /* OK to coerce directly from WINDOW * to
value since that's a block created by malloc() */
}
value curses_wrefresh(value win)
{
CAMLparam1 (win);
wrefresh((WINDOW *) win);
CAMLreturn (Val_unit);
}
value curses_newwin(value nlines, value ncols, value x0, value y0)
{
CAMLparam4 (nlines, ncols, x0, y0);
CAMLreturn ((value) newwin(Int_val(nlines), Int_val(ncols),
Int_val(x0), Int_val(y0)));
}
value curses_addch(value c)
{
CAMLparam1 (c);
addch(Int_val(c)); /* Characters are encoded like integers */
CAMLreturn (Val_unit);
}
value curses_addstr(value s)
{
CAMLparam1 (s);
addstr(String_val(s));
CAMLreturn (Val_unit);
}
/* This goes on for pages. */
The file curses.c can be compiled with:
cc -c -I/usr/local/lib/ocaml curses.c
or, even simpler,
ocamlc -c curses.c
(When passed a .c file, the ocamlc command simply calls the C
compiler on that file, with the right -I option.)
Now, here is a sample Caml program test.ml that uses the curses
module:
open Curses
let main_window = initscr () in
let small_window = newwin 10 5 20 10 in
mvwaddstr main_window 10 2 "Hello";
mvwaddstr small_window 4 3 "world";
refresh();
for i = 1 to 100000 do () done;
endwin()
To compile this program, run:
ocamlc -c test.ml
Finally, to link everything together:
ocamlc -custom -o test test.cmo curses.o -cclib -lcurses
(On some machines, you may need to put -cclib -ltermcap or
-cclib -lcurses -cclib -ltermcap instead of -cclib -lcurses.)
18.7 |
Advanced topic: callbacks from C to Caml |
|
So far, we have described how to call C functions from Caml. In this
section, we show how C functions can call Caml functions, either as
callbacks (Caml calls C which calls Caml), or because the main program
is written in C.
18.7.1 |
Applying Caml closures from C |
|
C functions can apply Caml functional values (closures) to Caml values.
The following functions are provided to perform the applications:
-
callback(f, a) applies the functional value f to
the value a and return the value returned by f.
- callback2(f, a, b) applies the functional value f
(which is assumed to be a curried Caml function with two arguments) to
a and b.
- callback3(f, a, b, c) applies the functional value f
(a curried Caml function with three arguments) to a, b and c.
- callbackN(f, n, args) applies the functional value f
to the n arguments contained in the array of values args.
If the function f does not return, but raises an exception that
escapes the scope of the application, then this exception is
propagated to the next enclosing Caml code, skipping over the C
code. That is, if a Caml function f calls a C function g that
calls back a Caml function h that raises a stray exception, then the
execution of g is interrupted and the exception is propagated back
into f.
If the C code wishes to catch exceptions escaping the Caml function,
it can use the functions callback_exn, callback2_exn,
callback3_exn, callbackN_exn. These functions take the same
arguments as their non-_exn counterparts, but catch escaping
exceptions and return them to the C code. The return value v of the
callback*_exn functions must be tested with the macro
Is_exception_result(v). If the macro returns ``false'', no
exception occured, and v is the value returned by the Caml
function. If Is_exception_result(v) returns ``true'',
an exception escaped, and its value (the exception descriptor) can be
recovered using Extract_exception(v).
18.7.2 |
Registering Caml closures for use in C functions |
|
The main difficulty with the callback functions described above is
obtaining a closure to the Caml function to be called. For this
purpose, Objective Caml provides a simple registration mechanism, by
which Caml code can register Caml functions under some global name,
and then C code can retrieve the corresponding closure by this global
name.
On the Caml side, registration is performed by evaluating
Callback.register n v. Here, n is the global name
(an arbitrary string) and v the Caml value. For instance:
let f x = print_string "f is applied to "; print_int n; print_newline()
let _ = Callback.register "test function" f
On the C side, a pointer to the value registered under name n is
obtained by calling caml_named_value(n). The returned
pointer must then be dereferenced to recover the actual Caml value.
If no value is registered under the name n, the null pointer is
returned. For example, here is a C wrapper that calls the Caml function f
above:
void call_caml_f(int arg)
{
callback(*caml_named_value("test function"), Val_int(arg));
}
The pointer returned by caml_named_value is constant and can safely
be cached in a C variable to avoid repeated name lookups. On the other
hand, the value pointed to can change during garbage collection and
must always be recomputed at the point of use. Here is a more
efficient variant of call_caml_f above that calls caml_named_value
only once:
void call_caml_f(int arg)
{
static value * closure_f = NULL;
if (closure_f == NULL) {
/* First time around, look up by name */
closure_f = caml_named_value("test function");
}
callback(*closure_f, Val_int(arg));
}
18.7.3 |
Registering Caml exceptions for use in C functions |
|
The registration mechanism described above can also be used to
communicate exception identifiers from Caml to C. The Caml code
registers the exception by evaluating
Callback.register_exception n exn, where n is an
arbitrary name and exn is an exception value of the
exception to register. For example:
exception Error of string
let _ = Callback.register_exception "test exception" (Error "any string")
The C code can then recover the exception identifier using
caml_named_value and pass it as first argument to the functions
raise_constant, raise_with_arg, and raise_with_string (described
in section 18.4.5) to actually raise the exception. For
example, here is a C function that raises the Error exception with
the given argument:
void raise_error(char * msg)
{
raise_with_string(*caml_named_value("test exception"), msg);
}
In normal operation, a mixed Caml/C program starts by executing the
Caml initialization code, which then may proceed to call C
functions. We say that the main program is the Caml code. In some
applications, it is desirable that the C code plays the role of the
main program, calling Caml functions when needed. This can be achieved as
follows:
-
The C part of the program must provide a main function,
which will override the default main function provided by the Caml
runtime system. Execution will start in the user-defined main function
just like for a regular C program.
- At some point, the C code must call caml_main(argv) to
initialize the Caml code. The argv argument is a C array of strings
(type char **), terminated with a NULL pointer,
which represents the command-line arguments, as
passed as second argument to main. The Caml array Sys.argv will
be initialized from this parameter. For the bytecode compiler,
argv[0] and argv[1] are also consulted to find the file containing
the bytecode.
- The call to caml_main initializes the Caml runtime system,
loads the bytecode (in the case of the bytecode compiler), and
executes the initialization code of the Caml program. Typically, this
initialization code registers callback functions using Callback.register.
Once the Caml initialization code is complete, control returns to the
C code that called caml_main.
- The C code can then invoke Caml functions using the callback
mechanism (see section 18.7.1).
18.7.5 |
Embedding the Caml code in the C code |
|
The bytecode compiler in custom runtime mode (ocamlc -custom)
normally appends the bytecode to the executable file containing the
custom runtime. This has two consequences. First, the final linking
step must be performed by ocamlc. Second, the Caml runtime library
must be able to find the name of the executable file from the
command-line arguments. When using caml_main(argv) as in
section 18.7.4, this means that argv[0] or argv[1] must
contain the executable file name.
An alternative is to embed the bytecode in the C code. The
-output-obj option to ocamlc is provided for this purpose.
It causes the ocamlc compiler to output a C object file (.o file)
containing the bytecode for the Caml part of the program, as well as a
caml_startup function. The C object file produced by ocamlc -output-obj can then be linked with C code using the standard C
compiler, or stored in a C library.
The caml_startup function must be called from the main C program in
order to initialize the Caml runtime and execute the Caml
initialization code. Just like caml_main, it takes one argv
parameter containing the command-line parameters. Unlike caml_main,
this argv parameter is used only to initialize Sys.argv, but not
for finding the name of the executable file.
The native-code compiler ocamlopt also supports the -output-obj
option, causing it to output a C object file containing the native
code for all Caml modules on the command-line, as well as the Caml
startup code. Initialization is performed by calling caml_startup as
in the case of the bytecode compiler.
For the final linking phase, in addition to the object file produced
by -output-obj, you will have to provide the Objective Caml runtime
library (libcamlrun.a for bytecode, libasmrun.a for native-code),
as well as all C libraries that are required by the Caml libraries
used. For instance, assume the Caml part of your program uses the
Unix library. With ocamlc, you should do:
ocamlc -output-obj -o camlcode.o unix.cma other .cmo and .cma files
cc -o myprog C objects and libraries \
camlcode.o -L/usr/local/lib/ocaml -lunix -lcamlrun
With ocamlopt, you should do:
ocamlopt -output-obj -o camlcode.o unix.cmxa other .cmx and .cmxa files
cc -o myprog C objects and libraries \
camlcode.o -L/usr/local/lib/ocaml -lunix -lasmrun
Warning:
On some ports, special options are required on the final
linking phase that links together the object file produced by the
-output-obj option and the remainder of the program. Those options
are shown in the configuration file config/Makefile generated during
compilation of Objective Caml, as the variables BYTECCLINKOPTS
(for object files produced by ocamlc -output-obj) and
NATIVECCLINKOPTS (for object files produced by ocamlopt -output-obj). Currently, the only ports that require special
attention are:
-
Alpha under Digital Unix / Tru64 Unix with gcc:
object files produced by ocamlc -output-obj must be linked with the
gcc options -Wl,-T,12000000 -Wl,-D,14000000.
This is not necessary for object files produced by ocamlopt -output-obj.
- Windows NT: the object file produced by Objective Caml have been
compiled with the /MT flag, and therefore all other object files
linked with it should also be compiled with /MT.
18.8 |
Advanced example with callbacks |
|
This section illustrates the callback facilities described in
section 18.7. We are going to package some Caml functions
in such a way that they can be linked with C code and called from C
just like any C functions. The Caml functions are defined in the
following mod.ml Caml source:
(* File mod.ml -- some ``useful'' Caml functions *)
let rec fib n = if n < 2 then 1 else fib(n-1) + fib(n-2)
let format_result n = Printf.sprintf "Result is: %d\n" n
(* Export those two functions to C *)
let _ = Callback.register "fib" fib
let _ = Callback.register "format_result" format_result
Here is the C stub code for calling these functions from C:
/* File modwrap.c -- wrappers around the Caml functions */
#include <stdio.h>
#include <string.h>
#include <caml/mlvalues.h>
#include <caml/callback.h>
int fib(int n)
{
static value * fib_closure = NULL;
if (fib_closure == NULL) fib_closure = caml_named_value("fib");
return Int_val(callback(*fib_closure, Val_int(n)));
}
char * format_result(int n)
{
static value * format_result_closure = NULL;
if (format_result_closure == NULL)
format_result_closure = caml_named_value("format_result");
return strdup(String_val(callback(*format_result_closure, Val_int(n))));
/* We copy the C string returned by String_val to the C heap
so that it remains valid after garbage collection. */
}
We now compile the Caml code to a C object file and put it in a C
library along with the stub code in modwrap.c and the Caml runtime system:
ocamlc -custom -output-obj -o modcaml.o mod.ml
ocamlc -c modwrap.c
cp /usr/local/lib/ocaml/libcamlrun.a mod.a
ar r mod.a modcaml.o modwrap.o
(One can also use ocamlopt -output-obj instead of ocamlc -custom -output-obj. In this case, replace libcamlrun.a (the bytecode
runtime library) by libasmrun.a (the native-code runtime library).)
Now, we can use the two fonctions fib and format_result in any C
program, just like regular C functions. Just remember to call
caml_startup once before.
/* File main.c -- a sample client for the Caml functions */
#include <stdio.h>
int main(int argc, char ** argv)
{
int result;
/* Initialize Caml code */
caml_startup(argv);
/* Do some computation */
result = fib(10);
printf("fib(10) = %s\n", format_result(result));
return 0;
}
To build the whole program, just invoke the C compiler as follows:
cc -o prog main.c mod.a -lcurses
(On some machines, you may need to put -ltermcap or
-lcurses -ltermcap instead of -lcurses.)
18.9 |
Advanced topic: custom blocks |
|
Blocks with tag Custom_tag contain both arbitrary user data and a
pointer to a C struct, with type struct custom_operations, that
associates user-provided finalization, comparison, hashing,
serialization and deserialization functions to this block.
18.9.1 |
The struct custom_operations |
|
The struct custom_operations is defined in <caml/custom.h> and
contains the following fields:
-
char *identifier
A zero-terminated character string serving as an identifier for
serialization and deserialization operations.
- void (*finalize)(value v)
The finalize field contains a pointer to a C function that is called
when the block becomes unreachable and is about to be reclaimed.
The block is passed as first argument to the function.
The finalize field can also be NULL to indicate that no
finalization function is associated with the block.
Important note: the v parameter of this function is of type value, but
it must not be declared using the CAMLparam macros.
- int (*compare)(value v1, value v2)
The compare field contains a pointer to a C function that is
called whenever two custom blocks are compared using Caml's generic
comparison operators (=, <>, <=, >=, <, > and
compare). The C function should return 0 if the data contained in
the two blocks are structurally equal, a negative integer if the data
from the first block is less than the data from the second block, and
a positive integer if the data from the first block is greater than
the data from the second block.
Note: You must use CAMLparam to declare v1 and v2 and CAMLreturn
to return the result.
The compare field can be set to custom_compare_default; this
default comparison function simply raises Failure.
- long (*hash)(value v)
The hash field contains a pointer to a C function that is called
whenever Caml's generic hash operator (see module Hashtbl) is
applied to a custom block. The C function can return an arbitrary
long integer representing the hash value of the data contained in the
given custom block. The hash value must be compatible with the
compare function, in the sense that two structurally equal data
(that is, two custom blocks for which compare returns 0) must have
the same hash value.
Note: You must use CAMLparam to declare v and CAMLreturn
to return the result.
The hash field can be set to custom_hash_default, in which case
the custom block is ignored during hash computation.
- void (*serialize)(value v, unsigned long * wsize_32, unsigned long * wsize_64)
The serialize field contains a pointer to a C function that is
called whenever the custom block needs to be serialized (marshaled)
using the Caml functions output_value or Marshal.to_....
For a custom block, those functions first write the identifier of the
block (as given by the identifier field) to the output stream,
then call the user-provided serialize function. That function is
responsible for writing the data contained in the custom block, using
the serialize_... functions defined in <caml/intext.h> and listed
below. The user-provided serialize function must then store in its
wsize_32 and wsize_64 parameters the sizes in bytes of the data
part of the custom block on a 32-bit architecture and on a 64-bit
architecture, respectively.
Note: You must use CAMLparam to declare v and CAMLreturn
to return the result.
The serialize field can be set to custom_serialize_default,
in which case the Failure exception is raised when attempting to
serialize the custom block.
- unsigned long (*deserialize)(void * dst)
The deserialize field contains a pointer to a C function that is
called whenever a custom block with identifier identifier needs to
be deserialized (un-marshaled) using the Caml functions input_value
or Marshal.from_.... This user-provided function is responsible for
reading back the data written by the serialize operation, using the
deserialize_... functions defined in <caml/intext.h> and listed
below. It must then rebuild the data part of the custom block
and store it at the pointer given as the dst argument. Finally, it
returns the size in bytes of the data part of the custom block.
This size must be identical to the wsize_32 result of
the serialize operation if the architecture is 32 bits, or
wsize_64 if the architecture is 64 bits.
The deserialize field can be set to custom_deserialize_default
to indicate that deserialization is not supported. In this case,
do not register the struct custom_operations with the deserializer
using register_custom_operations (see below).
18.9.2 |
Allocating custom blocks |
|
Custom blocks must be allocated via the alloc_custom function.
alloc_custom(ops, size, used, max)
returns a fresh custom block, with room for size bytes of user
data, and whose associated operations are given by ops (a
pointer to a struct custom_operations, usually statically allocated
as a C global variable).
The two parameters used and max are used to control the
speed of garbage collection when the finalized object contains
pointers to out-of-heap resources. Generally speaking, the
Caml incremental major collector adjusts its speed relative to the
allocation rate of the program. The faster the program allocates, the
harder the GC works in order to reclaim quickly unreachable blocks
and avoid having large amount of ``floating garbage'' (unreferenced
objects that the GC has not yet collected).
Normally, the allocation rate is measured by counting the in-heap size
of allocated blocks. However, it often happens that finalized
objects contain pointers to out-of-heap memory blocks and other resources
(such as file descriptors, X Windows bitmaps, etc.). For those
blocks, the in-heap size of blocks is not a good measure of the
quantity of resources allocated by the program.
The two arguments used and max give the GC an idea of how
much out-of-heap resources are consumed by the finalized block
being allocated: you give the amount of resources allocated to this
object as parameter used, and the maximum amount that you want
to see in floating garbage as parameter max. The units are
arbitrary: the GC cares only about the ratio used / max.
For instance, if you are allocating a finalized block holding an X
Windows bitmap of w by h pixels, and you'd rather not
have more than 1 mega-pixels of unreclaimed bitmaps, specify
used = w * h and max = 1000000.
Another way to describe the effect of the used and max
parameters is in terms of full GC cycles. If you allocate many custom
blocks with used / max = 1 / N, the GC will then do one
full cycle (examining every object in the heap and calling
finalization functions on those that are unreachable) every N
allocations. For instance, if used = 1 and max = 1000,
the GC will do one full cycle at least every 1000 allocations of
custom blocks.
If your finalized blocks contain no pointers to out-of-heap resources,
or if the previous discussion made little sense to you, just take
used = 0 and max = 1. But if you later find that the
finalization functions are not called ``often enough'', consider
increasing the used / max ratio.
18.9.3 |
Accessing custom blocks |
|
The data part of a custom block v can be
accessed via the pointer Data_custom_val(v). This pointer
has type void * and should be cast to the actual type of the data
stored in the custom block.
The contents of custom blocks are not scanned by the garbage
collector, and must therefore not contain any pointer inside the Caml
heap. In other terms, never store a Caml value in a custom block,
and do not use Field, Store_field nor modify to access the data
part of a custom block. Conversely, any C data structure (not
containing heap pointers) can be stored in a custom block.
18.9.4 |
Writing custom serialization and deserialization functions |
|
The following functions, defined in <caml/intext.h>, are provided to
write and read back the contents of custom blocks in a portable way.
Those functions handle endianness conversions when e.g. data is
written on a little-endian machine and read back on a big-endian machine.
Function |
Action |
serialize_int_1 |
Write a 1-byte integer |
serialize_int_2 |
Write a 2-byte integer |
serialize_int_4 |
Write a 4-byte integer |
serialize_int_8 |
Write a 8-byte integer |
serialize_float_4 |
Write a 4-byte float |
serialize_float_8 |
Write a 8-byte float |
serialize_block_1 |
Write an array of 1-byte quantities |
serialize_block_2 |
Write an array of 2-byte quantities |
serialize_block_4 |
Write an array of 4-byte quantities |
serialize_block_8 |
Write an array of 8-byte quantities |
deserialize_uint_1 |
Read an unsigned 1-byte integer |
deserialize_sint_1 |
Read a signed 1-byte integer |
deserialize_uint_2 |
Read an unsigned 2-byte integer |
deserialize_sint_2 |
Read a signed 2-byte integer |
deserialize_uint_4 |
Read an unsigned 4-byte integer |
deserialize_sint_4 |
Read a signed 4-byte integer |
deserialize_uint_8 |
Read an unsigned 8-byte integer |
deserialize_sint_8 |
Read a signed 8-byte integer |
deserialize_float_4 |
Read a 4-byte float |
deserialize_float_8 |
Read an 8-byte float |
deserialize_block_1 |
Read an array of 1-byte quantities |
deserialize_block_2 |
Read an array of 2-byte quantities |
deserialize_block_4 |
Read an array of 4-byte quantities |
deserialize_block_8 |
Read an array of 8-byte quantities |
deserialize_error |
Signal an error during deserialization;
input_value or Marshal.from_... raise a Failure exception after
cleaning up their internal data structures |
Serialization functions are attached to the custom blocks to which
they apply. Obviously, deserialization functions cannot be attached
this way, since the custom block does not exist yet when
deserialization begins! Thus, the struct custom_operations that
contain deserialization functions must be registered with the
deserializer in advance, using the register_custom_operations
function declared in <caml/custom.h>. Deserialization proceeds by
reading the identifier off the input stream, allocating a custom block
of the size specified in the input stream, searching the registered
struct custom_operation blocks for one with the same identifier, and
calling its deserialize function to fill the data part of the custom block.
Identifiers in struct custom_operations must be chosen carefully,
since they must identify uniquely the data structure for serialization
and deserialization operations. In particular, consider including a
version number in the identifier; this way, the format of the data can
be changed later, yet backward-compatible deserialisation functions
can be provided.
Identifiers starting with _ (an underscore character) are reserved
for the Objective Caml runtime system; do not use them for your custom
data. We recommend to use a URL
(http://mymachine.mydomain.com/mylibrary/version-number)
or a Java-style package name
(com.mydomain.mymachine.mylibrary.version-number)
as identifiers, to minimize the risk of identifier collision.
Custom blocks generalize the finalized blocks that were present in
Objective Caml prior to version 3.00. For backward compatibility, the
format of custom blocks is compatible with that of finalized blocks,
and the alloc_final function is still available to allocate a custom
block with a given finalization function, but default comparison,
hashing and serialization functions. alloc_final(n, f, used, max) returns a fresh custom block of
size n words, with finalization function f. The first
word is reserved for storing the custom operations; the other
n-1 words are available for your data. The two parameters
used and max are used to control the speed of garbage
collection, as described for alloc_custom.
18.10 |
Building mixed C/Caml libraries: ocamlmklib |
|
The ocamlmklib command facilitates the construction of libraries
containing both Caml code and C code, and usable both in static
linking and dynamic linking modes.
Windows:
This command is available only under Cygwin, but not for the
native Win32 port.
The ocamlmklib command takes three kinds of arguments:
-
Caml source files and object files (.cmo, .cmx, .ml)
comprising the Caml part of the library;
- C object files (.o, .a) comprising the C part of the
library;
- Support libraries for the C part (-llib).
It generates the following outputs:
-
A Caml bytecode library .cma incorporating the .cmo and
.ml Caml files given as arguments, and automatically referencing the
C library generated with the C object files.
- A Caml native-code library .cmxa incorporating the .cmx and
.ml Caml files given as arguments, and automatically referencing the
C library generated with the C object files.
- If dynamic linking is supported on the target platform, a
.so shared library built from the C object files given as arguments,
and automatically referencing the support libraries.
- A C static library .a built from the C object files.
In addition, the following options are recognized:
-
-cclib, -ccopt, -I, -linkall
-
These options are passed as is to ocamlc or ocamlopt.
See the documentation of these commands.
- -pthread, -rpath, -R, -Wl,-rpath, -Wl,-R
-
These options are passed as is to the C compiler. Refer to the
documentation of the C compiler.
- -custom
- Force the construction of a statically linked library
only, even if dynamic linking is supported.
- -failsafe
- Fall back to building a statically linked library
if a problem occurs while building the shared library (e.g. some of
the support libraries are not available as shared libraries).
- -Ldir
- Add dir to the search path for support
libraries (-llib).
- -ocamlc cmd
- Use cmd instead of ocamlc to call
the bytecode compiler.
- -ocamlopt cmd
- Use cmd instead of ocamlopt to call
the native-code compiler.
- -o output
- Set the name of the generated Caml library.
ocamlmklib will generate output.cma and/or output.cmxa.
If not specified, defaults to a.
- -oc outputc
- Set the name of the generated C library.
ocamlmklib will generate liboutputc.so (if shared
libraries are supported) and liboutputc.a.
If not specified, defaults to the output name given with -o.
Example
Consider a Caml interface to the standard libz
C library for reading and writing compressed files. Assume this
library resides in /usr/local/zlib. This interface is
composed of a Caml part zip.cmo/zip.cmx and a C part zipstubs.o
containing the stub code around the libz entry points. The
following command builds the Caml libraries zip.cma and zip.cmxa,
as well as the companion C libraries dllzip.so and libzip.a:
ocamlmklib -o zip zip.cmo zip.cmx zipstubs.o -lz -L/usr/local/zlib
If shared libraries are supported, this performs the following
commands:
ocamlc -a -o zip.cma zip.cmo -dllib -lzip \
-cclib -lzip -cclib -lz -ccopt -L/usr/local/zlib
ocamlopt -a -o zip.cmxa zip.cmx -cclib -lzip \
-cclib -lzip -cclib -lz -ccopt -L/usr/local/zlib
gcc -shared -o dllzip.so zipstubs.o -lz -L/usr/local/zlib
ar rc libzip.a zipstubs.o
If shared libraries are not supported, the following commands are
performed instead:
ocamlc -a -custom -o zip.cma zip.cmo -cclib -lzip \
-cclib -lz -ccopt -L/usr/local/zlib
ocamlopt -a -o zip.cmxa zip.cmx -lzip \
-cclib -lz -ccopt -L/usr/local/zlib
ar rc libzip.a zipstubs.o
Instead of building simultaneously the bytecode library, the
native-code library and the C libraries, ocamlmklib can be called
three times to build each separately. Thus,
ocamlmklib -o zip zip.cmo -lz -L/usr/local/zlib
builds the bytecode library zip.cma, and
ocamlmklib -o zip zip.cmx -lz -L/usr/local/zlib
builds the native-code library zip.cmxa, and
ocamlmklib -o zip zipstubs.o -lz -L/usr/local/zlib
builds the C libraries dllzip.so and libzip.a. Notice that the
support libraries (-lz) and the corresponding options
(-L/usr/local/zlib) must be given on all three invocations of ocamlmklib,
because they are needed at different times depending on whether shared
libraries are supported.