mirror of
https://github.com/mistergibson/gxg-web-client.git
synced 2026-08-15 11:34:29 -07:00
528 lines
12 KiB
Ruby
528 lines
12 KiB
Ruby
##
|
|
# 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
|