ffi_dry¶ ↑
Helpers, sugar methods, and new features over Ruby FFI to do some common things and add support for some uncommon ones.
Requirements¶ ↑
-
ffi (>= 0.5.0) - github.com/ffi/ffi
Synopsis¶ ↑
(samples/ in the package for code)
One major feature is a DSL“-like” syntax for declaring structure members in FFI::Struct or FFI::ManagedStruct definitions.
require 'rubygems' require 'ffi' require 'ffi/dry' class SomeStruct < FFI::Struct include FFI::DRY::StructHelper # we get a new way of specifying layouts with a 'dsl'-like syntax # The hash containing {:desc => ... } can contain arbitrary keys which # can be used however we like. dsl_metadata will contain all these # in the class and instance. dsl_layout do field :field1, :uint16, :desc => 'this is field 1' field :field2, :uint16, :desc => 'this is field 2' end end ss0=SomeStruct.new
With the declarations above, we specified :desc hash value in metadata. Extra metadata can have arbitrary keys and is accessible in every instance and class.
pp ss0.dsl_metadata [{:type=>:uint16, :name=>:field1, :desc=>"this is field 1"}, {:type=>:uint16, :name=>:field2, :desc=>"this is field 2"}] # => nil pp SomeStruct.dsl_metadata #...
We get some additional ways of instantiating and declaring values for free during initialization. (The FFI standard ways still work too)
raw_data = "\x00\x00\xff\xff" ss1=SomeStruct.new :raw => raw_data ss2=SomeStruct.new :raw => raw_data, :field1 => 1, :field2 => 2 ss3=SomeStruct.new {|x| x.field1=1 } ss4=SomeStruct.new(:raw => raw_data) {|x| x.field1=1 } [ ss0, ss1, ss2, ss3, ss4 ].each_with_index {|x,i| p ["ss#{i}",[x.field1, x.field2]]} # which produces... # ["ss0", [0, 0]] # ["ss1", [0, 65535]] # ["ss2", [1, 2]] # ["ss3", [1, 0]] # ["ss4", [1, 65535]]
Here’s a broader example which utilizes that arbitrary ‘:desc’ parameter in a “neighborly” way. This also demonstrates using superclasses to add common struct features, declaring array fields, as well as nesting other structs.
require 'rubygems' require 'ffi' require 'ffi/dry' class NeighborlyStruct < ::FFI::Struct include ::FFI::DRY::StructHelper def self.describe print "Struct: #{self.name}" dsl_metadata().each_with_index do |spec, i| print " Field #{i}\n" print " name: #{spec[:name].inspect}\n" print " type: #{spec[:type].inspect}\n" print " desc: #{spec[:desc]}\n\n" end print "\n" end def describe; self.class.describe; end end class TestStruct < NeighborlyStruct dsl_layout do field :field1, :uint8, :desc => "test field 1" field :field2, :uint8, :desc => "test field 2" end end class SomeStruct < NeighborlyStruct dsl_layout do field :kind, :uint8, :desc => "a type identifier" struct :tst, TestStruct, :desc => "a nested TestStruct" field :len, :uint8, :desc => "8-bit size value (>= self.size+2)" array :str, [:char,255], :desc => "a string up to 255 bytes bound by :len" end # override kind getter method with our own # resolves kind to some kind of type array for example... def kind [:default, :bar, :baz][ self[:kind] ] end end s1=TestStruct.new s2=SomeStruct.new # check out that 'kind' override: s2.kind # => :default # oh and the regular FFI way is always intact s2[:kind] # => 0 s2[:kind]=1 s2.kind # => :bar s2.kind=3 s2.kind # => :baz puts "*"*70 s1.describe ## we get a dump of metadata # ********************************************************************** # Struct: TestStruct # Field 0 # name: :field1 # type: :uint8 # desc: test field 1 # # Field 1 # name: :field2 # type: :uint8 # desc: test field 2 puts "*"*70 s2.describe ## we get a dump of metadata # Struct: SomeStruct Field 0 # name: :kind # type: :uint8 # desc: a type identifier # # Field 1 # name: :tst # type: TestStruct # desc: a nested TestStruct # # Field 2 # name: :len # type: :uint8 # desc: 8-bit size value (>= self.size+2) # # Field 3 # name: :str # type: [:char, 255] # desc: a string up to 255 bytes bound by :len puts "*"*70 s2.tst.describe ## same as s1.describe # ********************************************************************** # Struct: TestStruct # Field 0 # name: :field1 # type: :uint8 # desc: test field 1 # # Field 1 # name: :field2 # type: :uint8 # desc: test field 2
There’s also some helper modules for collecting lookup maps for constants, a common and handy thing when porting various libraries. We use the Ruby Socket socket namespace here for demonstration purposes. You can ‘slurp’ constants from any namespace this way.
require 'ffi/dry' require 'socket' module AddressFamily include FFI::DRY::ConstMap slurp_constants ::Socket, "AF_" def list ; @@list ||= super() ; end # only generate the hash once end
AddressFamily now has all the constants it found for Socket::AF_* minus the prefix.
AddressFamily::INET AddressFamily::LINK AddressFamily::INET6
etc…
We can do type or value lookups using []
AddressFamily[2] # => "INET" AddressFamily["INET"] # => 2
We can get a hash of all constant->value pairs with .list
AddressFamily.list # => {"NATM"=>31, "DLI"=>13, "UNIX"=>1, "NETBIOS"=>33, ...}
… and invert for a reverse mapping
AddressFamily.list.invert # => {16=>"APPLETALK", 5=>"CHAOS", 27=>"NDRV", 0=>"UNSPEC", ...}
License¶ ↑
Copyright © 2009 Eric Monti. See LICENSE for details.