Enums in C are weird. They like #define constant, with delusions of being a "real type". Behind the scenes, however, they're just a number. And there's no guarantee that the number won't change.

FFI comes with a neat "constant generator", that will avoid the problem of constants changing by getting the constant values out of the source code, at runtime. This is great, but enums have exactly the same problem as #defined constants, and the constant generator doesn't work on enums. So, I adapted the FFI::ConstGenerator code into this handy-dandy enum generator. As a bonus, since you often want to be able to refer to enum values as constants, you can turn all the symbols in an enum into constants on a module.

Installation

It's a gem:

gem install ffi-constant-generator

If you're the sturdy type that likes to run from git:

rake build; gem install pkg/ffi-enum-generator-<whatever>.gem

Or, if you've eschewed the convenience of Rubygems, then you presumably know what to do already.

Usage

To generate an enum, use the generate_enum method in your FFI::Library-using class:

require 'ffi/enum_generator'

class MyFFI
  extend ::FFI::Library
  ffi_lib "foo.so"

  generate_enum :foo_bar_opts do |eg|
    # The enum is defined in here, so we need to know that so we can
    # get the values
    eg.include "foo/bar.h"

    eg.symbol("FOO_BAR_FROB",   "FROB")
    eg.symbol("FOO_BAR_BAZ",    "BAZ")
    eg.symbol("FOO_BAR_WOMBAT", "WOMBAT")
  end
end

This will create an enum named :foo_bar_opts, with the symbols :FROB, :BAZ, and :WOMBAT with values equal to the C enum's values for FOO_BAR_FROB, FOO_BAR_BAZ, and FOO_BAR_WOMBAT, respectively. If you leave off the second argument to eg.symbol, the symbols in your enum will be the same as the original names, but since anyone sane namespaces their enum values with prefixes, it's rare that you'll want to do that.

Once your enum is generated, you can use it in exactly the same way as you would any other typed enum. You can refer to it in your attach_function calls:

class MyFFI
  attach_function :frobber,
                  [:pointer, :foo_bar_opts],
                  :int
end

Or retrieve it at will using MyFFI.enum_type:

puts "foo_bar_opts[:wombat] is #{MyFFI.enum_type(:foo_bar_opts)[:WOMBAT]}"

Since going through all that rigamarole is a lot of typing, and not very Rubyesque, you can set all the enum symbols up as constants on a module, like so:

module FooBarOpts; end

MyFFI.enum_type(:foo_bar_opts).set_consts(FooBarOpts)

puts "foo_bar_opts[:wombat] is #{FooBarOpts::WOMBAT}"

Neat, huh?

Contributing

Bug reports should be sent to the Github issue tracker, or e-mailed. Patches can be sent as a Github pull request, or e-mailed.