Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
cedlemo opened this issue May 29, 2019 · 6 comments
Closed

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

cedlemo opened this issue May 29, 2019 · 6 comments

Comments

@cedlemo
Copy link

cedlemo commented May 29, 2019

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
Copy link
Owner

yallop commented May 29, 2019

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

You might consider

  • using a view to automatically convert back and forth between lists of flags values and the uint32_t
  • using the constant support to retrieve the values for the flags from C rather than hard-coding them into your program (see Enum support #29 for some discussion). I think ppx_cstubs might make this easier (but I haven't yet used it myself)

@cedlemo
Copy link
Author

cedlemo commented May 29, 2019

Ok, thanks @yallop.

@cedlemo cedlemo closed this as completed May 29, 2019
@cedlemo cedlemo reopened this May 29, 2019
@cedlemo
Copy link
Author

cedlemo commented May 29, 2019

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
Copy link
Contributor

fdopen commented May 29, 2019

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
Copy link
Owner

yallop commented May 30, 2019

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
Copy link
Author

cedlemo commented May 30, 2019

thanks to both of you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants