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.
[{:type=>:uint16, :name=>:field1, :desc=>"this is field 1"},
{:type=>:uint16, :name=>:field2, :desc=>"this is field 2"}]
# => nil
pp SomeStruct.
#...
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}"
().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.