[ocaml-ctypes] Wrapping string into Bigarray
Matthieu Dubuget
matthieu.dubuget at gmail.com
Sun Dec 6 16:42:05 GMT 2015
Hello,
I'm posting here some code samples that helped me a lot, hoping they
could be of any help to somebody else. They were kindly provided by
Jeremy Yallop
(https://github.com/yallop/ocaml-ctypes-inverted-stubs-example/issues/11).
I have an OCaml function returning a string, and I wanted to deliver
this function through a shared library to be called by a foreign language.
My first naive try was to do something like this (see
https://github.com/yallop/ocaml-ctypes-inverted-stubs-example for a
complete example of how to wrap OCaml code in a shared library):
```OCaml
let () = I.internal "compute" (string @-> returning string)
MyLibrary.my_function
```
The problem with this solution is that the interface of the `compute`
function is:
```C
char* compute(char* x17);
```
There is no contract on how to free the memory after it has been used,
which is problematic.
Since I'm a bit lazy, instead of registering the string as a gc root,
and exposing a deallocation function to C, I decided to delegate all
the work (allocation/deallocation) to the client of my library. The
interface being something like:
```C
int compute(char * question, char * answer_buffer, size_t buffer_sz);
```
Jeremy proposed two solutions. The first one makes use of Bigarrays:
```OCaml
let compute question buffer buffer_sz =
let int_sz = Unsigned.Size_t.to_int buffer_sz in
let ba_buffer = Ctypes.(bigarray_of_ptr array1) int_sz Bigarray.char
buffer in
let computed_size = ... in
if computed_size > int_sz then null
else ...
```
and the second one is based on Ctypes.CArray module:
```OCaml
let compute question buffer buffer_sz =
let int_sz = Unsigned.Size_t.to_int buffer_sz in
let ba_buffer = Ctypes.CArray.from_ptr buffer int_sz in
let computed_size = ... in
if computed_size > int_sz then null
else ...
```
I choosed the first approach with great success. Since my function is
to be called more than once, I choosed to have the client allocate two
buffers to be reused. 1 for the question, and another to be filled
with the answer:
```OCaml
(* char Ctypes.ptr -> Unsigned.Size_t.t -> char Ctypes.ptr ->
Unsigned.Size_t.t -> int *)
let calcul_buffers question question_sz buffer buffer_sz =
let ans = string_from_ptr question ~length:(Unsigned.Size_t.to_int
question_sz)
|> Calcback.traite in
let ans_sz = Bytes.length ans in
let int_buffer_sz = Unsigned.Size_t.to_int buffer_sz in
let ba_buffer = Ctypes.(bigarray_of_ptr array1) int_buffer_sz
Bigarray.Char buffer in
for i = 0 to pred (min int_buffer_sz ans_sz) do
Bigarray.Array1.set ba_buffer i ans.[i]
done;
ans_sz
```
The returned `int` is the size needed to return the complete answer:
this is also the job of the client to increase the size of the buffer
if needed.
In order to avoid the for loop, I tried to wrap `(ans:string)` into a
Bigarray, in order to use a `blit` (I may be wrong, but I think it
ends as a memcopy, instead of a char by char copy).
The trick was to coerce the string into a `(ptr char)`.
```OCaml
let compute question question_sz buffer buffer_sz =
let ans =
string_from_ptr question ~length:(Unsigned.Size_t.to_int question_sz)
|> MyLibrary.my_function in
let ans_sz = Bytes.length ans in
let int_buffer_sz = Unsigned.Size_t.to_int buffer_sz in
let size_transmitted = min int_buffer_sz ans_sz in
Bigarray.Array1.blit
(bigarray_of_ptr array1 size_transmitted Bigarray.char (coerce
string (ptr char) ans))
(bigarray_of_ptr array1 size_transmitted Bigarray.Char buffer);
ans_sz
```
Ctypes is really nice. And I thank Jeremy for his kind support.
Happy hacking.
--
Matthieu Dubuget
Guide d’autodéfense numérique : http://guide.boum.org
More information about the Ctypes
mailing list