Skip to content
cedlemo edited this page Jun 4, 2018 · 7 revisions

OCaml-GI-ctypes-bindings-generator

The Loader module is used to load a namespace and generate automatically most of the Ctypes bindings.

Builder Code rules

Constants

There are 2 kinds of constants described by a GIConstantInfo (Constant_info module):

  • the ones that are directly in the main namespace.
  • the ones that are related to an object or an interface.

The firsts, the module constants, are generated as value in the core.mli and core.ml files. They can be used with GLib.Core.a_constant for example. The seconds will be generated in the files of the object or of the interface.

Structures And Unions

  • For each a module is created in a mli file and a ml file.
  • For a C name MyStructName, the OCaml type is named My_struct_name.t
  • For a C name MyStructName, the Ctypes typ is named My_struct_name.t_typ
  • the fields are named f_field_name (in order to avoid conflict with OCaml keywords).

Enumerations

Previous implementation

All the code related to the enum bindings where in the Core modules.

  • Simple Enumerations The bindings will use the "unsafe" form. (ref ).

    enum letters { AB, CD, EF };

    become :

    type letters =
    | Ab
    | Cd
    | Ef
    
    (* Unsigned.uint32 -> letters *)
    letters_of_value v =
      if v = Unsigned.UInt32.of_int 0 then Ab
      else if v = Unsigned.UInt32.of_int 1 then Cd
      else if v = Unsigned.UInt32.of_int 2 then Ef
      else raise (Invalid_argument \"Unexpected Letters value\")"
    
    (* letters -> Unsigned.uint32 *)
    letters_to_value = function
    | Ab -> Unsigned.UInt32.of_int 0
    | Cd -> Unsigned.UInt32.of_int 1
    | Ef -> Unsigned.UInt32.of_int 2
    
    (* val letters = letters typ *)
    let letters = view
                  ~read:letters_of_value
      	~write:letters_to_value
      	uint32_t
  • Flags : enumerations for bitwise operations The constants of those enumerations are generally used as ORed flags. The idea is to define a type with variants for all the constants that the enums contains.

    enum letters { AB, CD, EF };

    become :

    type letters =
    | Ab
    | Cd
    | Ef
    
    type letters_list = letters list
    
    (* Unsigned.uint32 -> letters *)
    let letters_of_value v =
      if v = Unsigned.UInt32.of_int 0 then Ab
      else if v = Unsigned.UInt32.of_int 1 then Cd
      else if v = Unsigned.UInt32.of_int 2 then Ef
      else raise (Invalid_argument \"Unexpected Letter value\")"
    
    (* letters -> Unsigned.uint32 *)
    let letters_to_value = function
    | Ab -> Unsigned.UInt32.of_int 0
    | Cd -> Unsigned.UInt32.of_int 1
    | Ef -> Unsigned.UInt32.of_int 2
    
    (* letters_list -> Unsigned.uint32*)
    let letters_list_to_value flags =
      let rec logor_flags l acc =
      match l with
      | [] -> acc
      | f :: q -> let v = optionflags_to_value f in
        let acc' = logor acc v in
        logor_flags q acc'
      in
      logor_flags flags 0
    
    (* Unsigned.uint32 -> letters_list *)
     let letters_list_of_value v =
       let open Unsigned.UInt32 in
       let flags = [] in
         if ((logand v (of_int 0)) != zero) then ignore (Ab :: flags);
         if ((logand v (of_int 1)) != zero) then ignore (Cd :: flags);
         if ((logand v (of_int 2)) != zero) then ignore (Ef :: flags);
         flags
    
    (* val letters_list = letters_list typ *)
    let letters_list = view
                       ~read:letters_list_of_value
      	     ~write:letters_list_to_value
      	     uint32_t
New implementation

In order to avoid cyclic dependencies at compile time, the idea is to create a module for each enums. For a C enum named DayOfWeek, a pair of files called Day_of_week.mli and Day_of_week.ml are created for example.

  • Simple Enumerations The bindings will use the "unsafe" form. (ref ).

    enum letters { AB, CD, EF };

    become in a Letters.ml file:

    type t =
    | Ab
    | Cd
    | Ef
    
    (* Unsigned.uint32 -> t *)
    of_value v =
      if v = Unsigned.UInt32.of_int 0 then Ab
      else if v = Unsigned.UInt32.of_int 1 then Cd
      else if v = Unsigned.UInt32.of_int 2 then Ef
      else raise (Invalid_argument \"Unexpected Letters value\")"
    
    (* t -> Unsigned.uint32 *)
    to_value = function
    | Ab -> Unsigned.UInt32.of_int 0
    | Cd -> Unsigned.UInt32.of_int 1
    | Ef -> Unsigned.UInt32.of_int 2
    
    (* val t = letters typ *)
    let t_view = view
                 ~read:letters_of_value
             ~write:letters_to_value
             uint32_t
  • Flags : enumerations for bitwise operations The constants of those enumerations are generally used as ORed flags. The idea is to define a type with variants for all the constants that the enums contains.

    enum letters { AB, CD, EF };

    become in a Letters.ml file:

    type t =
    | Ab
    | Cd
    | Ef
    
    type t_list = t list
    
    (* Unsigned.uint32 -> t *)
    let of_value v =
      if v = Unsigned.UInt32.of_int 0 then Ab
      else if v = Unsigned.UInt32.of_int 1 then Cd
      else if v = Unsigned.UInt32.of_int 2 then Ef
      else raise (Invalid_argument \"Unexpected Letter value\")"
    
    (* t -> Unsigned.uint32 *)
    let to_value = function
    | Ab -> Unsigned.UInt32.of_int 0
    | Cd -> Unsigned.UInt32.of_int 1
    | Ef -> Unsigned.UInt32.of_int 2
    
    (* t_list -> Unsigned.uint32*)
    let list_to_value flags =
      let rec logor_flags l acc =
      match l with
      | [] -> acc
      | f :: q -> let v = optionflags_to_value f in
        let acc' = logor acc v in
        logor_flags q acc'
      in
      logor_flags flags 0
    
    (* Unsigned.uint32 -> t_list *)
     let list_of_value v =
       let open Unsigned.UInt32 in
       let flags = [] in
         if ((logand v (of_int 0)) != zero) then ignore (Ab :: flags);
         if ((logand v (of_int 1)) != zero) then ignore (Cd :: flags);
         if ((logand v (of_int 2)) != zero) then ignore (Ef :: flags);
         flags
    
    (* val t_list_view = t_list typ *)
    let t_list_view = view
                       ~read:letters_list_of_value
      	     ~write:letters_list_to_value
      	     uint32_t

Functions.

Functions are described by GObject_introspection.Function_info and GObject_introspection.Callable_info. The Loader differenciate the main module functions and the methods. The main module functions are implemeneted as Namespace.Core.my_function while the methods which are related to a container (ie: a structure, an object ...) are implemented as Namespace.My_container.my_method. Here is two examples:

The former are generally represented as Base_info.Function when the Loader iterate throught the toplevel Base_info. The later are found with the get_method and get_n_methods of entity like Structure_info or Object_info. The way the bindings are generated for each is the same, it is just where they are created that is different.

Functions with only in arguments.

The simpler case. All the arguments of the function are used by the user to give data. Example:

Is implemented like this in OCaml:

(* mli signature *)
val date_get_sunday_weeks_in_year:
  Unsigned.uint16 -> Unsigned.uint8
(* ml implementation *)
let date_get_sunday_weeks_in_year =
  foreign "g_date_get_sunday_weeks_in_year" (uint16_t @-> returning (uint8_t))

Now this kind of function can return GError like:

OCaml :

let dir_make_tmp tmpl =
  let dir_make_tmp_raw =
    foreign "g_dir_make_tmp" (string_opt@-> ptr (ptr_opt Error.t_typ) @-> returning (string_opt))
  in
  let err_ptr_ptr = allocate (ptr_opt Error.t_typ) None in
  let value = dir_make_tmp_raw tmpl err_ptr_ptr in
  match (!@ err_ptr_ptr) with
  | None -> Ok value
  | Some _ -> let err_ptr = !@ err_ptr_ptr in
    let _ = Gc.finalise (function | Some e -> Error.free e | None -> () ) err_ptr in
    Error (err_ptr)

Functions with out arguments.

Out arguments are arguments used to get data from a function like:

in OCaml:

(* mli signature *)
val get_ymd :
  t structure ptr -> (int32 * int32 * int32)
(* ml *)
let get_ymd self =
  let year_ptr = allocate int32_t Int32.zero in
  let month_ptr = allocate int32_t Int32.zero in
  let day_ptr = allocate int32_t Int32.zero in
  let get_ymd_raw =
    foreign "g_date_time_get_ymd" (ptr t_typ @-> ptr (int32_t) @-> ptr (int32_t) @-> ptr (int32_t) @-> returning void)
  in
  let ret = get_ymd_raw self year_ptr month_ptr day_ptr in
  let year = !@ year_ptr in
  let month = !@ month_ptr in
  let day = !@ day_ptr in
  (year, month, day)

Now some of those functions, can throw GError too like:

(* mli signature *)
val filename_from_uri :
  string -> (string option * string option, Error.t structure ptr option) result

(* ml *)
let filename_from_uri uri =
  let hostname_ptr = allocate string_opt None in
  let err_ptr_ptr = allocate (ptr_opt Error.t_typ) None in
  let filename_from_uri_raw =
    foreign "g_filename_from_uri" (string @-> ptr (string_opt) @-> ptr (ptr_opt Error.t_typ) @-> returning (string_opt))
  in
  let ret = filename_from_uri_raw uri hostname_ptr err_ptr_ptr in
  let get_ret_value () =
    let hostname = !@ hostname_ptr in
    (ret, hostname)
  in
  match (!@ err_ptr_ptr) with
  | None -> Ok (get_ret_value ())
  | Some _ -> let err_ptr = !@ err_ptr_ptr in
    let _ = Gc.finalise (function | Some e -> Error.free e | None -> () ) err_ptr in
    Error (err_ptr)

Functions with in/out arguments.

To be implemented.

Patterns.

  • it will remain some patterns:
    • functions that return bool with out arguments should not return in OCaml the tuple (boolean value, out_arg1, out_arg2). A rule must be created to filter and transform the return value to out_arg1 type option.
  • When there are some buffer with name str, str_len, the Loader should generate instruction to construct an OCaml string with this.