yallop / ocaml-ctypes

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

Does the C stubs enum supports C flags usage ? #598

Closed cedlemo closed 5 years ago

cedlemo commented 5 years ago

I would like to know if using the Cstubs enum in order to bind a C enum values that are used as flags (ie ored value) is the good way to do?

For example this function:

g_function_info_get_flags (GIFunctionInfo *info);

returns a set of values that are bitwise ored. Previously, I created my bindings like this:

type flags =
      | Is_method
      | Is_constructor
      | Is_getter
      | Is_setter
      | Wraps_vfunc
      | Throws

    let get_flags info =
      let get_flags_raw =
        foreign "g_function_info_get_flags"
          (ptr functioninfo @-> returning uint32_t)
        in
        let v = get_flags_raw info in
        let open Unsigned.UInt32 in
        let all_flags = [( 1, Is_method ); ( 2, Is_constructor ); ( 4 , Is_getter ); ( 8 , Is_setter ); ( 16 , Wraps_vfunc ); (32, Throws)]
        in
        let rec build_flags_list allf acc =
          match allf with
          | [] -> acc
          | (i, f) :: q -> if ((logand v (of_int i )) <> zero) then build_flags_list q (f :: acc)
             else build_flags_list q acc
        in build_flags_list all_flags []

And it returns a list of flags.Is there a way to deal with ored values with the Cstubs enum type?

yallop commented 5 years ago

There's no direct support for flag sets in enum, and what you have looks like a fine approach.

You might consider

cedlemo commented 5 years ago

Ok, thanks @yallop.

cedlemo commented 5 years ago

Sorry to reopen this but I would like that you have a look at what I have done:

I use Cstubs constant and I create a view to deal with flags list.

First I wrote this in my bindings module:

(** Flags for a Function_info struct. *)
type flags =
  | Is_method      (** is a method. *)
  | Is_constructor (** is a constructor. *)
  | Is_getter      (** is a getter of a Property_info. *)
  | Is_setter      (** is a setter of a Property_info. *)
  | Wraps_vfunc    (** represents a virtual function. *)
  | Throws         (** the function may throw an error. *)

module Enums = functor (T : Cstubs.Types.TYPE) -> struct
  (** Other stuff *)

  let gi_function_is_method = T.constant "GI_FUNCTION_IS_METHOD" T.int64_t
  let gi_function_is_constructor = T.constant "GI_FUNCTION_IS_CONSTRUCTOR" T.int64_t
  let gi_function_is_getter = T.constant "GI_FUNCTION_IS_GETTER" T.int64_t
  let gi_function_is_setter = T.constant "GI_FUNCTION_IS_SETTER" T.int64_t
  let gi_function_wraps_vfunc = T.constant "GI_FUNCTION_WRAPS_VFUNC" T.int64_t
  let gi_function_throws = T.constant "GI_FUNCTION_THROWS" T.int64_t

end

I have a Stubs.ml file:

module Enums = Bindings.Enums(Bindings_stubs)
include Enums

And in my Function_info.ml file, I have the following:

open Ctypes
open Foreign
open Stubs

type t
let functioninfo : t structure typ = structure "Function_info"

let all_flags : (int64 * Bindings.flags) list= [
    gi_function_is_method, Is_method;
    gi_function_is_constructor, Is_constructor;
    gi_function_is_getter, Is_getter;
    gi_function_is_setter, Is_setter;
    gi_function_wraps_vfunc, Wraps_vfunc;
    gi_function_throws, Throws;
  ]

let flags_of_int64 v =
  let open Int64 in
  let rec build_flags_list allf acc =
    match allf with
    | [] -> acc
    | (i, f) :: q -> if ((logand v i) <> zero) then build_flags_list q (f :: acc)
       else build_flags_list q acc
  in build_flags_list all_flags []

let int64_of_flags (f : Bindings.flags list) =
  let open Int64 in
  let bitwise_or = fun acc value ->
    let (i, _f) = List.find (fun (i', f') -> value = f') all_flags in logor acc i
  in
  List.fold_left bitwise_or Int64.zero f

let flags =
  view int64_t
    ~read:flags_of_int64
    ~write:int64_of_flags

let get_flags =
    foreign "g_function_info_get_flags"
      (ptr functioninfo @-> returning flags)

I can compile it and simple tests seem to be fine.

fdopen commented 5 years ago

The problem is the int64_t, that you use to define the view. Is the underlying type really 64-bit long and signed? Probably not, c compilers are free to choose an appropriate type for enums.

Your flags type will work as expected, if you use it in order to pass parameters to functions (or as a return value). The c compiler will then implicitly cast it to an integer type of the proper width or back to int64_t. You will probably not trigger any bugs due to differences in signedness and bit-width, because your constants are within a safe range.

But if you use flags inside other contexts (as pointer, as a field inside a structure, Foreign,...), it's not longer safe. If the type of GI_FUNCTION_IS_METHOD has less than 64 bits, you will write out of bounds.

yallop commented 5 years ago

The problem is the int64_t, that you use to define the view. Is the underlying type really 64-bit long and signed? Probably not, c compilers are free to choose an appropriate type for enums.

This is true, and the problem can be fixed by using enum, which will retrieve the correct size and alignment for the enum type from C.

So if you have a C definition like this:

enum flags { ... };

then you might add the following to your bindings functor:

let enum_flags = enum "flags" [] ~unexpected:(fun x -> x)

When you link everything together you'll end up with a value of the type int64 typ with the correct size and alignment for enum flags, which you can use as the basis for the view:

let flags = view enum_flags ~read:... ~write:...
cedlemo commented 5 years ago

thanks to both of you.