Class: NiceFFI::Struct
- Inherits:
-
FFI::Struct
- Object
- FFI::Struct
- NiceFFI::Struct
- Includes:
- AutoRelease
- Defined in:
- lib/nice-ffi/struct.rb
Overview
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.
-
Implements #to_ary and #to_hash to dump the struct data.
-
Implements #to_s and #inspect for nice debugging output.
-
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 => false as an option to #new).
Class Method Summary collapse
-
.hidden(*members) ⇒ Object
Mark the given members as hidden, i.e.
-
.hidden?(member) ⇒ Boolean
True if the member has been marked #hidden, false otherwise.
-
.layout(*spec) ⇒ Object
Same syntax as FFI::Struct#layout, but also defines nice accessors for the attributes.
-
.read_only(*members) ⇒ Object
Mark the given members as read-only, so they won’t have write accessors.
-
.read_only?(member) ⇒ Boolean
True if the member has been marked #read_only, false otherwise.
-
.typed_pointer(options = {}) ⇒ Object
Returns a NiceFFI::TypedPointer instance for this class.
Instance Method Summary collapse
-
#initialize(val, options = {}) ⇒ Struct
constructor
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.
-
#to_ary ⇒ Object
Dump this instance as an Array of its struct data.
-
#to_bytes ⇒ Object
Dump this instance as a string of raw bytes of its struct data.
-
#to_hash ⇒ Object
Dump this instance as a Hash containing => data pairs for every member in the struct.
- #to_s ⇒ Object (also: #inspect)
Methods included from AutoRelease
Constructor Details
#initialize(val, options = {}) ⇒ Struct
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 => false in options
.
(Note: FFI::MemoryPointer and FFI::Buffer have built-in memory management, so MyClass.release is never called for them.)
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 |
# File 'lib/nice-ffi/struct.rb', line 331 def initialize( val, ={} ) # Stores certain kinds of member values so that we don't need # to create a new object every time they are read. @member_cache = {} = {:autorelease => true}.merge!( ) 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, [:autorelease] ) # Normal FFI::Struct behavior to wrap the pointer. super( val ) else raise TypeError, "cannot create new #{self.class} from #{val.inspect}" end end |
Class Method Details
.hidden(*members) ⇒ Object
Mark the given members as hidden, i.e. do not create accessors for them in #layout, and do not print them out in #to_s, etc. You can call this before or after calling #layout, and can call it more than once if you like.
Note: They can still be read and written via #[] and #[]=, but will not have convenience accessors.
Note: This will remove the accessor methods (if they exist) for the members! So if you’re defining your own custom accessors, do that after you have called this method.
Example:
class SecretStruct < NiceStruct
# You can use it before the layout...
hidden( :hidden1 )
layout( :visible1, :uint16,
:visible2, :int,
:hidden1, :uint,
:hidden2, :pointer )
# ... and/or after it.
hidden( :hidden2 )
# :hidden1 and :hidden2 are now both hidden.
end
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/nice-ffi/struct.rb', line 138 def hidden( *members ) if defined?(@hidden_members) @hidden_members += members else @hidden_members = members end members.each do |member| # Remove the accessors if they exist. [member, "#{member}=".to_sym].each { |m| begin remove_method( m ) rescue NameError end } end end |
.hidden?(member) ⇒ Boolean
True if the member has been marked #hidden, false otherwise.
158 159 160 161 |
# File 'lib/nice-ffi/struct.rb', line 158 def hidden?( member ) return false unless defined?(@hidden_members) @hidden_members.include?( member ) end |
.layout(*spec) ⇒ Object
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
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
# 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 |
.read_only(*members) ⇒ Object
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
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 |
# 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 |
.read_only?(member) ⇒ Boolean
True if the member has been marked #read_only, false otherwise.
209 210 211 212 |
# File 'lib/nice-ffi/struct.rb', line 209 def read_only?( member ) return false unless defined?(@readonly_members) @readonly_members.include?( member ) end |
.typed_pointer(options = {}) ⇒ Object
Returns a NiceFFI::TypedPointer instance for this class. Equivalent to NiceFFI::TypedPointer.new( this_class, options )
68 69 70 |
# File 'lib/nice-ffi/struct.rb', line 68 def typed_pointer( ={} ) NiceFFI::TypedPointer.new(self, ) end |
Instance Method Details
#to_ary ⇒ Object
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]
404 405 406 |
# File 'lib/nice-ffi/struct.rb', line 404 def to_ary members.collect{ |m| self[m] } end |
#to_bytes ⇒ Object
Dump this instance as a string of raw bytes of its struct data.
411 412 413 |
# File 'lib/nice-ffi/struct.rb', line 411 def to_bytes return self.pointer.get_bytes(0, self.size) end |
#to_hash ⇒ Object
Dump this instance as a Hash containing => 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}
424 425 426 427 |
# File 'lib/nice-ffi/struct.rb', line 424 def to_hash return {} if members.empty? Hash[ *(members.collect{ |m| [m, self[m]] }.flatten!) ] end |
#to_s ⇒ Object Also known as: inspect
430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 |
# 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 |