## # A Foreign Function Interface used to bind C libraries to ruby. module Rubinius module FFI # Specialised error classes class TypeError < RuntimeError; end class NotFoundError < RuntimeError; end class << self def add_typedef(current, add) if current.kind_of? Fixnum code = current else code = FFI::TypeDefs[current] raise TypeError, "Unable to resolve type '#{current}'" unless code end FFI::TypeDefs[add] = code end def find_type(name) code = FFI::TypeDefs[name] raise TypeError, "Unable to resolve type '#{name}'" unless code return code end ## # Given a +type+ as a number, indicate how many bytes that type # takes up on this platform. def type_size(type) Rubinius.primitive :nativefunction_type_size case type when Symbol return type_size(find_type(type)) when Rubinius::NativeFunction return type_size(TYPE_PTR) when FFI::Enum return type_size(TYPE_ENUM) end raise PrimitiveFailure, "FFI.type_size primitive failed: #{type}" end def size_to_type(size) if sz = TypeSizes[size] return sz end # Be like C, use int as the default type size. return :int end def config(name) Rubinius::Config["rbx.platform.#{name}"] end def config_hash(name) vals = {} section = "rbx.platform.#{name}." Rubinius::Config.section section do |key, value| vals[key.substring(section.size, key.length)] = value end vals end def errno FFI::Platform::POSIX.errno end # Convenience method for determining if a function call succeeded or failed def call_failed?(return_code) return_code == -1 end end # Represents a C enum. class Enum attr_reader :tag attr_reader :kv_map def initialize(info, tag=nil) @tag = tag @kv_map = Hash.new unless info.nil? last_cst = nil value = 0 info.each do |i| case i when Symbol @kv_map[i] = value last_cst = i value += 1 when Integer @kv_map[last_cst] = i value = i + 1 end end end end private :initialize def inspect "#<%s:0x%x %s=>%s>" % [self.class, self.object_id, @tag, @kv_map.inspect] end def anonym? @tag.nil? end def values @kv_map.values end def symbols @kv_map.keys end def [](sym) @kv_map[sym] end def symbol(value) key, val = @kv_map.detect { |key, val| val == value } key end end # It's a class to be compat with the ffi gem. class Type class Array def initialize(element_type, size, impl_class=nil) @element_type = element_type @size = size @implementation = impl_class end private :initialize attr_reader :element_type attr_reader :size attr_reader :implementation end class StructByValue def initialize(struct) @implementation = struct end private :initialize attr_reader :implementation end Struct = StructByValue CHAR = TYPE_CHAR UCHAR = TYPE_UCHAR BOOL = TYPE_BOOL SHORT = TYPE_SHORT USHORT = TYPE_USHORT INT = TYPE_INT UINT = TYPE_UINT LONG = TYPE_LONG ULONG = TYPE_ULONG LL = TYPE_LL ULL = TYPE_ULL FLOAT = TYPE_FLOAT DOUBLE = TYPE_DOUBLE PTR = TYPE_PTR VOID = TYPE_VOID STRING = TYPE_STRING STRPTR = TYPE_STRPTR CHARARR = TYPE_CHARARR ENUM = TYPE_ENUM VARARGS = TYPE_VARARGS end ## # Namespace for holding platform-specific C constants. module Platform def self.windows? Rubinius.windows? end def self.mac? Rubinius.darwin? end def self.unix? ! windows? end end ## # Represents a C struct as ruby class. class Struct class InlineArray include Enumerable def initialize(type, ptr) @pointer = ptr @type = type.element_type @size = type.size end private :initialize attr_reader :size def [](idx) if idx >= @size raise ArgumentError, "index out of range (#{idx} >= #{@size})" end @pointer.get_at_offset(idx * @type, @type) end def []=(idx, val) if idx >= @size raise ArgumentError, "index out of range (#{idx} >= #{@size})" end @pointer.set_at_offset(idx * @type, @type, val) end def each @size.times do |ele| yield @pointer.get_at_offset(ele * @type, @type) end end def to_a ary = [] each { |x| ary << x } ary end def to_ptr @pointer end end class InlineCharArray < InlineArray def to_s str = @pointer.read_string(@size) # NULL clamp this, to be the same as the ffi gem. if idx = str.index("\0") return str[0, idx] end return str end alias_method :to_str, :to_s alias_method :inspect, :to_s end def self.find_nested_parent return nil if self.name.nil? path = self.name.split("::") path.pop # remove ourself mod = Object begin path.each { |c| mod = mod.const_get(c) } rescue NameError # bail. return nil end mod.respond_to?(:find_type) ? mod : nil end attr_reader :pointer def self.layout(*spec) return @layout if spec.size == 0 # Pick up a enclosing FFI::Library @enclosing_module ||= find_nested_parent cspec = Rubinius::LookupTable.new i = 0 @size = 0 @members = [] while i < spec.size name = spec[i] @members << name f = spec[i + 1] type_code = nil if f.kind_of? Array ary_type = f[0] ary_size = f[1] if @enclosing_module type_code = @enclosing_module.find_type(ary_type) end type_code ||= FFI.find_type(ary_type) type_size = FFI.type_size(type_code) case type_code when TYPE_CHAR, TYPE_UCHAR klass = InlineCharArray else klass = InlineArray end type = FFI::Type::Array.new(type_code, ary_size, klass) element_size = type_size * ary_size elsif f.kind_of?(Class) and (f < FFI::Struct || f < FFI::Union) type = FFI::Type::StructByValue.new(f) element_size = type_size = f.size else if @enclosing_module type_code = @enclosing_module.find_type(f) rescue nil end type_code ||= FFI.find_type(f) type = type_code element_size = type_size = FFI.type_size(type_code) end offset = spec[i + 2] if offset.kind_of?(Fixnum) i += 3 else if self < FFI::Union offset = 0 else offset = @size mod = offset % type_size unless mod == 0 # we need to align it. offset += (type_size - mod) end end i += 2 end cspec[name] = [offset, type] ending = offset + element_size @size = ending if @size < ending end @layout = cspec unless self == FFI::Struct return cspec end def self.config(base, *fields) @size = Rubinius::Config["#{base}.sizeof"] cspec = Rubinius::LookupTable.new fields.each do |field| offset = Rubinius::Config["#{base}.#{field}.offset"] element_size = Rubinius::Config["#{base}.#{field}.size"] type = Rubinius::Config["#{base}.#{field}.type"] type = type ? type.to_sym : FFI.size_to_type(size) type_code = FFI.find_type type if :char_array == type type_code = FFI::Type::Array.new(FFI.find_type(:char), element_size, InlineCharArray) end cspec[field] = [offset, type_code] ending = offset + element_size @size = ending if @size < ending end @layout = cspec return cspec end def self.size @size end def size self.class.size end def self.offset_of(name) @layout[name].first end def offset_of(name) @cspec[name].first end def self.offsets members.map do |member| [member, @layout[member].first] end end def offsets members.map do |member| [member, @cspec[member].first] end end def self.members @members end def members self.class.members end def initialize(pointer=nil, *spec) @cspec = self.class.layout(*spec) if pointer @pointer = pointer else @pointer = MemoryPointer.new size end end private :initialize def to_ptr @pointer end def free @pointer.free end def ==(other) return false unless other.is_a?(self.class) @pointer == other.pointer end def initialize_copy(ptr) @pointer = ptr.pointer.dup end private :initialize def []=(field, val) offset, type = @cspec[field] raise "Unknown field #{field}" unless offset case type when Fixnum @pointer.set_at_offset(offset, type, val) when FFI::Type::Array if type.implementation == InlineCharArray (@pointer + offset).write_string StringValue(val), type.size return val end raise TypeError, "Unable to set inline array" when Rubinius::NativeFunction @pointer.set_at_offset(offset, FFI::TYPE_PTR, val) else @pointer.set_at_offset(offset, type, val) end return val end def [](field) offset, type = @cspec[field] raise "Unknown field #{field}" unless offset case type when FFI::TYPE_CHARARR (@pointer + offset).read_string when Fixnum @pointer.get_at_offset(offset, type) when FFI::Type::Array type.implementation.new(type, @pointer + offset) when FFI::Type::StructByValue type.implementation.new(@pointer + offset) when Rubinius::NativeFunction ptr = @pointer.get_at_offset(offset, FFI::TYPE_PTR) if ptr FFI::Function.new(type.return_type, type.argument_types, ptr) else nil end else @pointer.get_at_offset(offset, type) end end def values members.map { |m| self[m] } end def null? @pointer == FFI::Pointer::NULL end end ## # Represents a C union as ruby class. class Union < Struct end end end