I have been able to pass a string argument to OCaml side from C side using a callback and caml_alloc_initialized_string as instructed here (OCaml - Interfacing C with OCaml).
Now, I want to add a second argument: an array of bytes representing binary data. Is there a caml_alloc_ function I am missing for a sequence of bytes?
The manual says that string and a sequence of bytes use the same representation. So I tried the following stub:
char * message(char * typ, uint8_t * data)
{
static const value * closure = NULL;
if (closure == NULL) {
closure = caml_named_value("message");
}
value msg_type = caml_alloc_initialized_string(strlen(typ), typ);
value msg_data = caml_alloc_initialized_string(strlen(data), data);
return strdup(String_val(caml_callback2(*closure, msg_type, msg_data)));
}
but trying to compile it gives me a warning:
warning: passing 'uint8_t *' (aka 'unsigned char *') to parameter of type 'const char *' converts between pointers to integer types where one is of the unique plain 'char' type and the other is not [-Wpointer-sign]
As the warning says, you are trying to pass an argument of type uint8_t * to a function that is waiting for a value of type char const *. For historical reasons, char and uint8_t are completely unrelated types, though they happen to have the same memory representation in practice. So, you should just cast one pointer type to the other:
value msg_data = caml_alloc_initialized_string(strlen((char *)data), (char *)data);
By the way, you can use caml_copy_string instead of caml_alloc_initialized_string here.
value msg_data = caml_copy_string((char *)data);
Also, you really need to protect your variables of type value with CAMLlocal, as any of the memory allocation might trigger a garbage collection, which would indirectly corrupt their content.
As for the global variable closure, you would have to declare it as garbage collector root. (EDIT: as pointed out by @vrotaru, its content does not need to be protected any further.)
Thank you for the warning. In order to stick to only the sections relevant to my work, I had skipped the sections dealing with garbage collection (#5 and #6).
That’s correct. I make use of these in the interop.
A sample (which borrows much of the code from section #8, which did not talk about CAMLlocal and CAMLreturn) follows:
(* sample.ml *)
let say_hello name = match name with
| "" -> "Hello, world!"
| v -> "Hello, " ^ v ^ "!"
let _ = Callback.register "say_hello" say_hello
// samplewrap.c
#include <stdio.h>
#include <string.h>
#include <caml/mlvalues.h>
#include <caml/callback.h>
#include <caml/alloc.h>
char * say_hello(char * s)
{
static const value * closure = NULL;
if (closure == NULL) {
closure = caml_named_value("say_hello");
}
value str = caml_copy_string(s);
return strdup(String_val(caml_callback(*closure, str)));
/* We copy the C string returned by String_val to the C heap
so that it remains valid after garbage collection. */
}
Protecting result is not necessary, since there is no point of allocation between its definition and its use. (And protecting msg_data is not necessary either, for the same reason.)
When should I prefer value type parameters to the normal C types (like the one I used - int, unsigned char * ? value type is what CAMLparam is needed for.
If the C function is called by an OCaml function, then it should take arguments of type value. If the C function is called from another language (including C), it should take arguments suitable for that language.