FFI::Struct
A class to be used as a baseclass where you would use FFI::Struct. It acts mostly like FFI::Struct, but with nice extra features and conveniences to make life easier:
Automatically defines read and write accessor methods (e.g. x, x=) for struct members when you call layout. (You can use hidden and read_only before or after calling layout to affect which members have accessors.)
Implements "smart" accessors for TypedPointer types, seamlessly wrapping those members so you don't even have to think about the fact they are pointers!
Implements a nicer new method which allows you to create a new struct and set its data in one shot by passing an Array, Hash, or another instance of the class (to copy data). You can also use it to wrap a FFI::Pointer like FFI::Struct can.
Adds ::typed_pointer convenience alias to create a TypedPointer for this klass.
Provides automatic memory management for Pointers if you define MyClass.release( pointer). (This can be disabled per-instance by providing {:autorelease => false} as an option to new).
Same syntax as FFI::Struct#layout, but also defines nice accessors for the attributes.
Example:
class Rect < NiceStruct layout( :x, :int16, :y, :int16, :w, :uint16, :h, :uint16 ) end
# File lib/nice-ffi/struct.rb, line 85 def layout( *spec ) @nice_spec = spec # Wrap the members. 0.step(spec.size - 1, 2) { |index| member, type = spec[index, 2] wrap_member( member, type) } simple_spec = spec.collect { |a| case a when NiceFFI::TypedPointer :pointer else a end } # Normal FFI::Struct behavior super( *simple_spec ) end
Create a new instance of the class, reading data from a Hash or Array of attributes, a bytestring of raw data, copying from another instance of the class, or wrapping (not copying!) a FFI::Pointer.
If val is an instance of FFI::Pointer and you have defined MyClass.release, the pointer will be passed to MyClass.release when the memory is no longer being used. Use MyClass.release to free the memory for the struct, as appropriate for your class. To disable autorelease for this instance, set {:autorelease => false} in options.
(Note: FFI::MemoryPointer and FFI::Buffer have built-in memory management, so MyClass.release is never called for them.)
# File lib/nice-ffi/struct.rb, line 331 def initialize( val, options={} ) # Stores certain kinds of member values so that we don't need # to create a new object every time they are read. @member_cache = {} options = {:autorelease => true}.merge!( options ) case val when Hash super(FFI::Buffer.new(size)) init_from_hash( val ) # Read the values from a Hash. # Note: plain "Array" would mean FFI::Struct::Array in this scope. when ::Array super(FFI::Buffer.new(size)) init_from_array( val ) # Read the values from an Array. when String super(FFI::Buffer.new(size)) init_from_bytes( val ) # Read the values from a bytestring. when self.class super(FFI::Buffer.new(size)) init_from_bytes( val.to_bytes ) # Read the values from another instance. when FFI::Pointer, FFI::Buffer val = _make_autopointer( val, options[:autorelease] ) # Normal FFI::Struct behavior to wrap the pointer. super( val ) else raise TypeError, "cannot create new #{self.class} from #{val.inspect}" end end
Mark the given members as read-only, so they won't have write accessors.
Note: They can still be written via #[]=, but will not have convenience accessors.
Note: This will remove the writer method (if it exists) for the members! So if you're defining your own custom writer, do that after you have called this method.
Example:
class SecretStruct < NiceStruct # You can use it before the layout... read_only( :readonly1 ) layout( :visible1, :uint16, :visible2, :int, :readonly1, :uint, :readonly2, :pointer ) # ... and/or after it. read_only( :readonly2 ) # :readonly1 and :readonly2 are now both read-only. end
# File lib/nice-ffi/struct.rb, line 192 def read_only( *members ) if defined?(@readonly_members) @readonly_members += members else @readonly_members = members end members.each do |member| # Remove the write accessor if it exists. begin remove_method( "#{member}=".to_sym ) rescue NameError end end end
True if the member has been marked read_only, false otherwise.
# File lib/nice-ffi/struct.rb, line 209 def read_only?( member ) return false unless defined?(@readonly_members) @readonly_members.include?( member ) end
Returns a NiceFFI::TypedPointer instance for this class. Equivalent to NiceFFI::TypedPointer.new( this_class, options )
# File lib/nice-ffi/struct.rb, line 68 def typed_pointer( options={} ) NiceFFI::TypedPointer.new(self, options) end
Dump this instance as an Array of its struct data. The array contains only the data, not the member names.
Note: the order of data in the array always matches the order of members given in layout.
Example:
Rect.new( :x=>1, :y=>2, :w=>3, :h=>4 ).to_ary # => [1,2,3,4]
# File lib/nice-ffi/struct.rb, line 404 def to_ary members.collect{ |m| self[m] } end
Dump this instance as a string of raw bytes of its struct data.
# File lib/nice-ffi/struct.rb, line 411 def to_bytes return self.pointer.get_bytes(0, self.size) end
Dump this instance as a Hash containing {member => data} pairs for every member in the struct.
Example:
Rect.new( :x=>1, :y=>2, :w=>3, :h=>4 ).to_hash # => {:h=>4, :w=>3, :x=>1, :y=>2}
# File lib/nice-ffi/struct.rb, line 424 def to_hash return {} if members.empty? Hash[ *(members.collect{ |m| [m, self[m]] }.flatten!) ] end
# File lib/nice-ffi/struct.rb, line 430 def to_s begin if self.pointer.null? return "#<NULL %s:%#.x>"%[self.class.name, self.object_id] end rescue NoMethodError end mems = members.collect{ |m| unless self.class.hidden?( m ) val = self.send(m) # Cleanup/simplify for display if val.nil? or (val.is_a? FFI::Pointer and val.null?) val = "NULL" elsif val.kind_of? FFI::Struct val = "#<#{val.class}:%#.x>"%val.object_id end "@#{m}=#{val}" end }.compact.join(", ") if( mems == "" ) return "#<%s:%#.x>"%[self.class.name, self.object_id] else return "#<%s:%#.x %s>"%[self.class.name, self.object_id, mems] end end
Generated with the Darkfish Rdoc Generator 2.