-
Notifications
You must be signed in to change notification settings - Fork 4
Home
The Loader module is used to load a namespace and generate automatically most
of the Ctypes bindings. Here is the samples/gi_builder_glib.ml
code in the OCaml-gobject-introspection
library :
module GI = GObject_introspection
let print_infos loader =
let namespace = GI.Loader.get_namespace loader in
let version = GI.Loader.get_version loader in
print_endline (">> " ^ namespace);
print_endline ("\t - version :" ^ version)
let () =
match GI.Loader.load "GLib" () with
| None -> print_endline "Please check the namespace, something is wrong"
| Some loader -> print_infos loader;
let loader = GI.Loader.set_build_path loader "samples" in
GI.Loader.parse loader ()
It generates the GLib bindings in samples/GLib/lib
The idea is to iterate through all the toplevel baseinfo of the namespace and generate the related Ctypes bindings automatically.
- Constants
- Structures
- Unions
- Enumerations
- Flags (enumerations but handled as list).
- Functions
- interface
- object
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.
- For each a module is created in a mli file and a ml file.
- For a C name
MyStructName
, the OCaml type is namedMy_struct_name.t
- For a C name
MyStructName
, the Ctypes typ is namedMy_struct_name.t_typ
- the fields are named
f_field_name
(in order to avoid conflict with OCaml keywords).
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
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 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:
-
g_date_get_sunday_week_in_year: will be implemented as
GLib.Core.get_sunday_week_of_year
-
g_checksum_get_string: will be implemented as
GLib.Checksum.get_string
.
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.
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)
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)
To be implemented.
- 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
.
- 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
- When there are some buffer with name str, str_len, the Loader should generate instruction to construct an OCaml string with this.