Class: NiceFFI::Struct

Inherits:
FFI::Struct
  • Object
show all
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

Instance Method Summary collapse

Methods included from AutoRelease

included

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, 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

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.

Returns:

  • (Boolean)


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.

Returns:

  • (Boolean)


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( options={} )
  NiceFFI::TypedPointer.new(self, options)
end

Instance Method Details

#to_aryObject

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_bytesObject

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_hashObject

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_sObject 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