Module: FFI::DRY::StructHelper

Defined in:
lib/ffi/dry.rb

Overview

A module to add syntactic sugar and some nice automatic getter/setter logic to FFI::Struct, FFI::ManagedStruct, etc.

For example:

require 'rubygems'
require 'ffi'
require 'ffi/dry'
require 'pp'

class SomeStruct < FFI::Struct
  include FFI::DRY::StructHelper

  # we get a new way of specifying layouts with a 'dsl'-like syntax
  dsl_layout do
    field   :field1,  :uint16, :desc => 'this is field 1'
    field   :field2,  :uint16, :desc => 'this is field 2'
  end
end

ss0=SomeStruct.new

pp ss0.   # we can look at definition metadata

# produces...
#  [{:type=>:uint16, :name=>:field1, :desc=>"this is field 1"},
#   {:type=>:uint16, :name=>:field2, :desc=>"this is field 2"}]

# And we have additional ways of instantiating and declaring values
# 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| pp ["ss#{i}",[x.field1, x.field2]]}

 # produces...
 # ["ss0", [0, 0]]
 # ["ss1", [0, 65535]]
 # ["ss2", [1, 2]]
 # ["ss3", [1, 0]]
 # ["ss4", [1, 65535]]

Defined Under Namespace

Modules: ClassMethods

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#dsl_metadataObject (readonly)

< ::FFI::Struct



59
60
61
# File 'lib/ffi/dry.rb', line 59

def 
  @dsl_metadata
end

Class Method Details

.included(base) ⇒ Object



181
182
183
# File 'lib/ffi/dry.rb', line 181

def self.included(base)
  base.extend(ClassMethods)
end

Instance Method Details

#copy(grown = 0) ⇒ Object

Returns a new instance of self.class containing a seperately allocated copy of all our data. This abstract method should usually be called with super() from overridden ‘copy’ implementations for structures containing pointers to other memory or variable length data at the end.

Note also that, by default, this implementation determine’s size automatically based on the structure size. This is comparable to sizeof(some_struct) in C. However, you can supply a ‘grown’ parameter which can be used to add to the size of the copied instance as it is allocated and copied.



129
130
131
# File 'lib/ffi/dry.rb', line 129

def copy(grown=0)
  self.class.new( :raw => self.to_ptr.read_string(self.size+grown) )
end

#initialize(*args) {|_self| ... } ⇒ Object

Adds field setting on initialization to ::FFI::Struct.new as well as a “yield(self) if block_given?” at the end.

The field initialization kicks in if there is only one argument, and it is a Hash.

Note: The :raw parameter is a special tag in the hash. The value is taken as a string and initialized into a new FFI::MemoryPointer which this Struct then overlays.

If your struct layout has a field named :raw field, it won’t be assignable through the hash argument.

See also: set_fields() which is called automatically on the hash, minus the :raw tag.

Yields:

  • (_self)

Yield Parameters:



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/ffi/dry.rb', line 78

def initialize(*args)
  @dsl_metadata = self.class.
  params=nil

  if args.size == 1 and (oparams=args[0]).is_a? Hash
    params = oparams.dup
    if raw=params.delete(:raw)
      super( ::FFI::MemoryPointer.from_string(raw) )
    else
      super()
    end
  else
    super(*args)
  end

  set_fields(params)
  yield self if block_given?
end

#ptr_to(field) ⇒ Object

Returns a pointer to the specified field, which is the name assigned to a member in the layout.



135
136
137
138
# File 'lib/ffi/dry.rb', line 135

def ptr_to(field)
  x = self[field] # this is actually a test, to raise if missing
  return (self.to_ptr + self.offset_of(field))
end

#set_fields(params = nil) ⇒ Object

Sets field values in the struct specified by their symbolic name from a hash of ‘:field => value’ pairs. Uses accessor field wrapper methods instead of a direct reference to the field (as in “obj.field1 = x”, not “obj = x”). The difference is subtle, but this allows you to take advantage of any wrapper methods you override when initializing a new object. The only caveat is that the wrapper method must be named the same as the field, and the field must be included in members() from the layout.

This method is called automatically if you are using the initialize() method provided in the DryStruct class and passing it a Hash as its only argument.



109
110
111
112
113
114
115
116
117
# File 'lib/ffi/dry.rb', line 109

def set_fields(params=nil)
  (params || {}).keys.each do |p|
    if members().include?(p)
      self.__send__(:"#{p}=", params[p])
    else
      raise(::ArgumentError, "#{self.class} does not have a '#{p}' field")
    end
  end
end