yallop / ocaml-ctypes

Library for binding to C libraries using pure OCaml
MIT License
363 stars 95 forks source link

Exposing method that returns a list with Ctypes inverted? #649

Closed akshajgaur closed 3 years ago

akshajgaur commented 4 years ago

I have defined unions, and structs in my Ctypes Inverted interface with great success, but I have been having trouble in passing a list of them with the inverted stubs.

This is a basic premise of what I want to do. I have a function: let test a = [a;a;a]

let t_type = view ~read:get_t~write:set_t fin_t (this is what will be passed as a paremeter).

let() = I.internal "test" (t_type @-> returning ????) test

I have tried various methods using big_Arrays, and CArrays, but the return type still isn't what it is supposed to be. Most of the documentation I have seen has been of the regular Ctypes, and not the inverted bindings.

yallop commented 4 years ago

The easiest approach is to use a root.

You can use Root.createto obtain an opaque handle to your list, and expose it to C as a void * (or as a pointer to an incomplete struct type to give a little more type safety). When the void * value is passed back to OCaml you can use Root.get to retrieve the list:

# let p = Ctypes.Root.create [1;2;3];;
val p : unit Ctypes.ptr = (void*) 0x557bc90e8910
# (Ctypes.Root.get p : int list);;
- : int list = [1; 2; 3]

You'll also need a free-like function to release the root (by calling Root.release) when it's no longer in use by the C code.

akshajgaur commented 4 years ago

Thank you for the help. I used Ctypes.Root and Ctypes.get before the thread, but I was confused as to how to retreive the value when C passes the value back. This is my method. The add function takes a list of type x as an argument, and returns another list. The helper function takes a void ptr as an argument, and then passes an x list to the add function. I think that this portion of the code is correct, but I'm not sure how to retrive the value from C.

let add_helper a = 
let x = (Root.get a :(x list)) in 
Root.create (add x)

module internal ...

let () = I.internal "add_helper"(ptr void @-> returning (ptr void)) add_helper

I'm not sure where to call Root.get to retreive the value.

yallop commented 4 years ago

The root interface is quite low level, so you may find it easier to build a thin type-safe wrapper around it, and then use that. For example, here's a module that hides the void pointers and the unsafe types of the root operations:

module CList :
sig
  type t
  val t : t typ  (* Used to describe interfaces to C *) 

  val alloc : int list -> t
  val free : t -> unit

  val get : t -> int list
  val set : t -> int list -> unit
end =
struct
  type t = unit ptr
  let t = ptr void

  let alloc = Root.create
  let free = Root.release

  let get = Root.get
  let set = Root.set
end

Now you can use this interface to build functions that operate on C-accessible lists:

let length : CList.t -> int =
  fun t -> List.length (CList.get t)

let sum : CList.t -> int =
  fun t -> List.fold_left (+) 0 (CList.get t)

let empty : CList.t =
  CList.alloc []

let reverse : CList.t -> CList.t =
  fun t -> t |> CList.get |> List.rev |> CList.alloc

let cons : int -> CList.t -> CList.t =
  fun x t -> CList.alloc (x :: (CList.get t))

and you can expose these functions to C using internal and CList.t:

let () = I.internal "reverse" (CList.t @-> returning CList.t) reverse
let () = I.internal "cons" (int -> CList.t @-> returning CList.t) cons
let () = I.internal "free" (CList.t -> void) CList.free
(* etc. *)
akshajgaur commented 4 years ago

Sorry I got to this a little late. I wrote a similar interface to this, that just exposes the list as void pointers, but I kept on getting a seg fault. Then I tried this interface, but ran into a similar error. Is there something wrong with my little client program.

int main(){
  int x[3] = {3, 4, 5};
  int (*ptr)[3];
  ptr = &x;
  printf("%p\n", reverse(ptr));
 free(reverse(ptr))
}

I was just trying to print the address in memory as the function returns a pointer. Even when I try to get the first element of the returned list, I get a seg fault.

yallop commented 4 years ago
  int x[3] = {3, 4, 5};
  int (*ptr)[3];

This snippet deals with C arrays, which have different memory representations to OCaml lists. Since the representations are different, It's not possible to treat the same value as both an OCaml list (in OCaml code), and a C array (in C code): you'll have to choose one representation or the other.

There are two options available:

  1. exposing OCaml lists to C code.
    Use the Root approach described above.
    In OCaml you'll be able to use normal programming facilities (pattern matching, etc.)
    In C you'll only be able to access the value through the functions you export; you won't be able to use C array/pointer facilities.

  2. Exposing OCaml functions that deal with C arrays to C code.
    Use CArray or ptr or bigarray.
    In OCaml you'll need to use the functions in the ctypes interface (CArray.get, !@, etc.) to manipulate values. In C you'll be able to treat the values as arrays, pointers, etc., as your client program does.

akshajgaur commented 4 years ago

That makes a lot more sense. I think my underlying question was can you pass a list input from C to OCaml through a void pointer. However, this seems only possible using a array on the C side and this makes first option not viable, as there is no method of giving a input for a list.