Module: FFI::DRY::StructHelper

Defined in:
lib/ffi/dry.rb

Overview

A module to add syntactic sugar and some nice automatic getter/setter methods 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)

Returns the value of attribute dsl_metadata.



77
78
79
# File 'lib/ffi/dry.rb', line 77

def 
  @dsl_metadata
end

Class Method Details

.included(base) ⇒ Object



225
226
227
# File 'lib/ffi/dry.rb', line 225

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.



151
152
153
# File 'lib/ffi/dry.rb', line 151

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

#copy_sizeObject

This method is called when creating a copy of self. It can be overridden by derived structures to return another size. This is sometimes done to account for alignment issues, etc.



158
159
160
# File 'lib/ffi/dry.rb', line 158

def copy_size
  self.size
end

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

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

Field initialization happens if there is only one argument and it is a Hash.

The params hash is taken as a set of values for fields where the hash keys specify the field names to set.

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.

Parameters:

  • params (Hash)

Yields:

  • (_self)

Yield Parameters:



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/ffi/dry.rb', line 100

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.new(raw.size).write_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.



164
165
166
167
# File 'lib/ffi/dry.rb', line 164

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 Struct class and passing it a Hash as its only argument.



131
132
133
134
135
136
137
138
139
# File 'lib/ffi/dry.rb', line 131

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