lib/fiddle/struct.rb in fiddle-1.0.0 vs lib/fiddle/struct.rb in fiddle-1.0.1
- old
+ new
@@ -2,26 +2,118 @@
require 'fiddle'
require 'fiddle/value'
require 'fiddle/pack'
module Fiddle
- # C struct shell
+ # A base class for objects representing a C structure
class CStruct
+ include Enumerable
+
# accessor to Fiddle::CStructEntity
def CStruct.entity_class
CStructEntity
end
+
+ def each
+ return enum_for(__function__) unless block_given?
+
+ self.class.members.each do |name,|
+ yield(self[name])
+ end
+ end
+
+ def each_pair
+ return enum_for(__function__) unless block_given?
+
+ self.class.members.each do |name,|
+ yield(name, self[name])
+ end
+ end
+
+ def to_h
+ hash = {}
+ each_pair do |name, value|
+ hash[name] = unstruct(value)
+ end
+ hash
+ end
+
+ def replace(another)
+ if another.nil?
+ self.class.members.each do |name,|
+ self[name] = nil
+ end
+ elsif another.respond_to?(:each_pair)
+ another.each_pair do |name, value|
+ self[name] = value
+ end
+ else
+ another.each do |name, value|
+ self[name] = value
+ end
+ end
+ self
+ end
+
+ private
+ def unstruct(value)
+ case value
+ when CStruct
+ value.to_h
+ when Array
+ value.collect do |v|
+ unstruct(v)
+ end
+ else
+ value
+ end
+ end
end
- # C union shell
+ # A base class for objects representing a C union
class CUnion
# accessor to Fiddle::CUnionEntity
def CUnion.entity_class
CUnionEntity
end
end
+ # Wrapper for arrays within a struct
+ class StructArray < Array
+ include ValueUtil
+
+ def initialize(ptr, type, initial_values)
+ @ptr = ptr
+ @type = type
+ @is_struct = @type.respond_to?(:entity_class)
+ if @is_struct
+ super(initial_values)
+ else
+ @size = Fiddle::PackInfo::SIZE_MAP[type]
+ @pack_format = Fiddle::PackInfo::PACK_MAP[type]
+ super(initial_values.collect { |v| unsigned_value(v, type) })
+ end
+ end
+
+ def to_ptr
+ @ptr
+ end
+
+ def []=(index, value)
+ if index < 0 || index >= size
+ raise IndexError, 'index %d outside of array bounds 0...%d' % [index, size]
+ end
+
+ if @is_struct
+ self[index].replace(value)
+ else
+ to_ptr[index * @size, @size] = [value].pack(@pack_format)
+ super(index, value)
+ end
+ end
+ end
+
# Used to construct C classes (CUnion, CStruct, etc)
#
# Fiddle::Importer#struct and Fiddle::Importer#union wrap this functionality in an
# easy-to-use manner.
module CStructBuilder
@@ -33,62 +125,113 @@
# * corresponding +members+
#
# Fiddle::Importer#struct and Fiddle::Importer#union wrap this functionality in an
# easy-to-use manner.
#
- # Example:
+ # Examples:
#
# require 'fiddle/struct'
# require 'fiddle/cparser'
#
# include Fiddle::CParser
#
# types, members = parse_struct_signature(['int i','char c'])
#
# MyStruct = Fiddle::CStructBuilder.create(Fiddle::CUnion, types, members)
#
- # obj = MyStruct.allocate
+ # MyStruct.malloc(Fiddle::RUBY_FREE) do |obj|
+ # ...
+ # end
#
+ # obj = MyStruct.malloc(Fiddle::RUBY_FREE)
+ # begin
+ # ...
+ # ensure
+ # obj.call_free
+ # end
+ #
+ # obj = MyStruct.malloc
+ # begin
+ # ...
+ # ensure
+ # Fiddle.free obj.to_ptr
+ # end
+ #
def create(klass, types, members)
new_class = Class.new(klass){
- define_method(:initialize){|addr|
- @entity = klass.entity_class.new(addr, types)
+ define_method(:initialize){|addr, func = nil|
+ if addr.is_a?(self.class.entity_class)
+ @entity = addr
+ else
+ @entity = self.class.entity_class.new(addr, types, func)
+ end
@entity.assign_names(members)
}
+ define_method(:[]) { |*args| @entity.send(:[], *args) }
+ define_method(:[]=) { |*args| @entity.send(:[]=, *args) }
define_method(:to_ptr){ @entity }
define_method(:to_i){ @entity.to_i }
+ define_singleton_method(:types) { types }
+ define_singleton_method(:members) { members }
members.each{|name|
+ name = name[0] if name.is_a?(Array) # name is a nested struct
+ next if method_defined?(name)
define_method(name){ @entity[name] }
define_method(name + "="){|val| @entity[name] = val }
}
- }
- size = klass.entity_class.size(types)
- new_class.module_eval(<<-EOS, __FILE__, __LINE__+1)
- def new_class.size()
- #{size}
+ entity_class = klass.entity_class
+ alignment = entity_class.alignment(types)
+ size = entity_class.size(types)
+ define_singleton_method(:alignment) { alignment }
+ define_singleton_method(:size) { size }
+ define_singleton_method(:malloc) do |func=nil, &block|
+ if block
+ entity_class.malloc(types, func, size) do |entity|
+ block.call(new(entity))
+ end
+ else
+ new(entity_class.malloc(types, func, size))
+ end
end
- def new_class.malloc()
- addr = Fiddle.malloc(#{size})
- new(addr)
- end
- EOS
+ }
return new_class
end
module_function :create
end
- # A C struct wrapper
+ # A pointer to a C structure
class CStructEntity < Fiddle::Pointer
include PackInfo
include ValueUtil
+ def CStructEntity.alignment(types)
+ max = 1
+ types.each do |type, count = 1|
+ if type.respond_to?(:entity_class)
+ n = type.alignment
+ else
+ n = ALIGN_MAP[type]
+ end
+ max = n if n > max
+ end
+ max
+ end
+
# Allocates a C struct with the +types+ provided.
#
- # When the instance is garbage collected, the C function +func+ is called.
- def CStructEntity.malloc(types, func = nil)
- addr = Fiddle.malloc(CStructEntity.size(types))
- CStructEntity.new(addr, types, func)
+ # See Fiddle::Pointer.malloc for memory management issues.
+ def CStructEntity.malloc(types, func = nil, size = size(types), &block)
+ if block_given?
+ super(size, func) do |struct|
+ struct.set_ctypes types
+ yield struct
+ end
+ else
+ struct = super(size, func)
+ struct.set_ctypes types
+ struct
+ end
end
# Returns the offset for the packed sizes for the given +types+.
#
# Fiddle::CStructEntity.size(
@@ -100,13 +243,19 @@
offset = 0
max_align = types.map { |type, count = 1|
last_offset = offset
- align = PackInfo::ALIGN_MAP[type]
+ if type.respond_to?(:entity_class)
+ align = type.alignment
+ type_size = type.size
+ else
+ align = PackInfo::ALIGN_MAP[type]
+ type_size = PackInfo::SIZE_MAP[type]
+ end
offset = PackInfo.align(last_offset, align) +
- (PackInfo::SIZE_MAP[type] * count)
+ (type_size * count)
align
}.max
PackInfo.align(offset, max_align)
@@ -116,49 +265,98 @@
#
# When the instance is garbage collected, the C function +func+ is called.
#
# See also Fiddle::Pointer.new
def initialize(addr, types, func = nil)
+ if func && addr.is_a?(Pointer) && addr.free
+ raise ArgumentError, 'free function specified on both underlying struct Pointer and when creating a CStructEntity - who do you want to free this?'
+ end
set_ctypes(types)
super(addr, @size, func)
end
# Set the names of the +members+ in this C struct
def assign_names(members)
- @members = members
+ @members = []
+ @nested_structs = {}
+ members.each_with_index do |member, index|
+ if member.is_a?(Array) # nested struct
+ member_name = member[0]
+ struct_type, struct_count = @ctypes[index]
+ if struct_count.nil?
+ struct = struct_type.new(to_i + @offset[index])
+ else
+ structs = struct_count.times.map do |i|
+ struct_type.new(to_i + @offset[index] + i * struct_type.size)
+ end
+ struct = StructArray.new(to_i + @offset[index],
+ struct_type,
+ structs)
+ end
+ @nested_structs[member_name] = struct
+ else
+ member_name = member
+ end
+ @members << member_name
+ end
end
# Calculates the offsets and sizes for the given +types+ in the struct.
def set_ctypes(types)
@ctypes = types
@offset = []
offset = 0
max_align = types.map { |type, count = 1|
orig_offset = offset
- align = ALIGN_MAP[type]
+ if type.respond_to?(:entity_class)
+ align = type.alignment
+ type_size = type.size
+ else
+ align = ALIGN_MAP[type]
+ type_size = SIZE_MAP[type]
+ end
offset = PackInfo.align(orig_offset, align)
@offset << offset
- offset += (SIZE_MAP[type] * count)
+ offset += (type_size * count)
align
}.max
@size = PackInfo.align(offset, max_align)
end
- # Fetch struct member +name+
- def [](name)
+ # Fetch struct member +name+ if only one argument is specified. If two
+ # arguments are specified, the first is an offset and the second is a
+ # length and this method returns the string of +length+ bytes beginning at
+ # +offset+.
+ #
+ # Examples:
+ #
+ # my_struct = struct(['int id']).malloc
+ # my_struct.id = 1
+ # my_struct['id'] # => 1
+ # my_struct[0, 4] # => "\x01\x00\x00\x00".b
+ #
+ def [](*args)
+ return super(*args) if args.size > 1
+ name = args[0]
idx = @members.index(name)
if( idx.nil? )
raise(ArgumentError, "no such member: #{name}")
end
ty = @ctypes[idx]
if( ty.is_a?(Array) )
- r = super(@offset[idx], SIZE_MAP[ty[0]] * ty[1])
+ if ty.first.respond_to?(:entity_class)
+ return @nested_structs[name]
+ else
+ r = super(@offset[idx], SIZE_MAP[ty[0]] * ty[1])
+ end
+ elsif ty.respond_to?(:entity_class)
+ return @nested_structs[name]
else
r = super(@offset[idx], SIZE_MAP[ty.abs])
end
packer = Packer.new([ty])
val = packer.unpack([r])
@@ -174,18 +372,48 @@
val = val[0]
end
if( ty.is_a?(Integer) && (ty < 0) )
return unsigned_value(val, ty)
elsif( ty.is_a?(Array) && (ty[0] < 0) )
- return val.collect{|v| unsigned_value(v,ty[0])}
+ return StructArray.new(self + @offset[idx], ty[0], val)
else
return val
end
end
- # Set struct member +name+, to value +val+
- def []=(name, val)
+ # Set struct member +name+, to value +val+. If more arguments are
+ # specified, writes the string of bytes to the memory at the given
+ # +offset+ and +length+.
+ #
+ # Examples:
+ #
+ # my_struct = struct(['int id']).malloc
+ # my_struct['id'] = 1
+ # my_struct[0, 4] = "\x01\x00\x00\x00".b
+ # my_struct.id # => 1
+ #
+ def []=(*args)
+ return super(*args) if args.size > 2
+ name, val = *args
+ name = name.to_s if name.is_a?(Symbol)
+ nested_struct = @nested_structs[name]
+ if nested_struct
+ if nested_struct.is_a?(StructArray)
+ if val.nil?
+ nested_struct.each do |s|
+ s.replace(nil)
+ end
+ else
+ val.each_with_index do |v, i|
+ nested_struct[i] = v
+ end
+ end
+ else
+ nested_struct.replace(val)
+ end
+ return val
+ end
idx = @members.index(name)
if( idx.nil? )
raise(ArgumentError, "no such member: #{name}")
end
ty = @ctypes[idx]
@@ -200,37 +428,34 @@
else
return val
end
end
+ undef_method :size=
def to_s() # :nodoc:
super(@size)
end
end
- # A C union wrapper
+ # A pointer to a C union
class CUnionEntity < CStructEntity
include PackInfo
- # Allocates a C union the +types+ provided.
- #
- # When the instance is garbage collected, the C function +func+ is called.
- def CUnionEntity.malloc(types, func=nil)
- addr = Fiddle.malloc(CUnionEntity.size(types))
- CUnionEntity.new(addr, types, func)
- end
-
# Returns the size needed for the union with the given +types+.
#
# Fiddle::CUnionEntity.size(
# [ Fiddle::TYPE_DOUBLE,
# Fiddle::TYPE_INT,
# Fiddle::TYPE_CHAR,
# Fiddle::TYPE_VOIDP ]) #=> 8
def CUnionEntity.size(types)
types.map { |type, count = 1|
- PackInfo::SIZE_MAP[type] * count
+ if type.respond_to?(:entity_class)
+ type.size * count
+ else
+ PackInfo::SIZE_MAP[type] * count
+ end
}.max
end
# Calculate the necessary offset and for each union member with the given
# +types+
@@ -239,6 +464,5 @@
@offset = Array.new(types.length, 0)
@size = self.class.size types
end
end
end
-