mirror of
https://github.com/mistergibson/gxg-web-client.git
synced 2026-08-15 03:26:00 -07:00
319 lines
9 KiB
Ruby
319 lines
9 KiB
Ruby
class Rubinius::NativeFunction
|
|
attr_accessor :return_type
|
|
attr_accessor :argument_types
|
|
end
|
|
|
|
module Rubinius
|
|
module FFI
|
|
def self.generate_function(ptr, name, args, ret)
|
|
Rubinius.primitive :nativefunction_generate
|
|
raise PrimitiveFailure, "FFI.generate_function primitive failed"
|
|
end
|
|
|
|
def self.generate_trampoline(obj, name, args, ret)
|
|
Rubinius.primitive :nativefunction_generate_tramp
|
|
raise PrimitiveFailure, "FFI.generate_function_tramp primitive failed"
|
|
end
|
|
|
|
module Library
|
|
LIBC = Rubinius::LIBC
|
|
|
|
# Set which library or libraries +attach_function+ should
|
|
# look in. By default it only searches for the function in
|
|
# the current process. If you want to specify this as one
|
|
# of the locations, add FFI::USE_THIS_PROCESS_AS_LIBRARY.
|
|
# The libraries are tried in the order given.
|
|
#
|
|
def ffi_lib(*names)
|
|
@ffi_lib = names.map do |x|
|
|
if x == FFI::CURRENT_PROCESS
|
|
DynamicLibrary::CURRENT_PROCESS
|
|
|
|
# When the element is an array, it's an ordered choice,
|
|
# ie, pick the first library that works.
|
|
elsif x.kind_of? Array
|
|
lib = nil
|
|
|
|
x.each do |name|
|
|
begin
|
|
lib = DynamicLibrary.new(name, @ffi_lib_flags)
|
|
break
|
|
rescue LoadError
|
|
end
|
|
end
|
|
|
|
# If .new worked, then lib is set and we can use it.
|
|
unless lib
|
|
raise LoadError, "Unable to find library among: #{x.inspect}"
|
|
end
|
|
|
|
lib
|
|
else
|
|
DynamicLibrary.new(x)
|
|
end
|
|
end
|
|
end
|
|
|
|
def ffi_libraries
|
|
@ffi_lib or [DynamicLibrary::CURRENT_PROCESS]
|
|
end
|
|
private :ffi_libraries
|
|
|
|
# Flags used in {#ffi_lib}.
|
|
#
|
|
# This map allows you to supply symbols to {#ffi_lib_flags} instead of
|
|
# the actual constants.
|
|
def flags_map
|
|
{
|
|
:global => DynamicLibrary::RTLD_GLOBAL,
|
|
:local => DynamicLibrary::RTLD_LOCAL,
|
|
:lazy => DynamicLibrary::RTLD_LAZY,
|
|
:now => DynamicLibrary::RTLD_NOW
|
|
}
|
|
end
|
|
|
|
# Sets library flags for {#ffi_lib}.
|
|
#
|
|
# @example
|
|
# ffi_lib_flags(:lazy, :local) # => 5
|
|
#
|
|
# @param [Symbol, …] flags (see {FlagsMap})
|
|
# @return [Fixnum] the new value
|
|
def ffi_lib_flags(*flags)
|
|
@ffi_lib_flags = flags.inject(0) { |result, f| result | flags_map[f] }
|
|
end
|
|
|
|
# Attach a C function to this module. The arguments can have two forms:
|
|
#
|
|
# attach_function c_name, [c_arg1, c_arg2], ret
|
|
# attach_function mod_name, c_name, [c_arg1, c_arg2], ret
|
|
#
|
|
# In the first form, +c_name+ will also be used for the name of the module
|
|
# method. In the second form, the module method name is +mod_name+.
|
|
#
|
|
# In either form, an optional options hash will be accepted as an
|
|
# additional argument, although currently all options are ignored.
|
|
#
|
|
# The +c_name+ and +mod_name+ can be given as Strings or Symbols.
|
|
#
|
|
# The types of the arguments to the C function, +c_arg1+, +c_arg2+, etc, are
|
|
# given as an array even if there is only one.
|
|
#
|
|
# The final argument, +ret+, is the type of the return value from the C
|
|
# function.
|
|
def attach_function(name, a2, a3, a4=nil, a5=nil)
|
|
if a4 && (a2.kind_of?(String) || a2.kind_of?(Symbol))
|
|
cname = a2.to_s
|
|
args = a3
|
|
ret = a4
|
|
else
|
|
cname = name.to_s
|
|
args = a2
|
|
ret = a3
|
|
end
|
|
|
|
mname = name.to_sym
|
|
|
|
ffi_libraries.each do |lib|
|
|
if ptr = lib.find_symbol(cname)
|
|
return pointer_as_function(mname, ptr, args, ret)
|
|
end
|
|
end
|
|
|
|
ffi_function_missing cname, mname, args, ret
|
|
end
|
|
|
|
# Attach a C variable to this module. The arguments can have two forms:
|
|
#
|
|
# attach_variable mod_name, type
|
|
# attach_variable mod_name, c_name, type
|
|
#
|
|
# In the first form, +mod_name+ will also be used for the name of the C variable.
|
|
# In the second form, the C variable name is +c_name+.
|
|
#
|
|
# The +mod_name+ and +c_name+ can be given as Strings or Symbols.
|
|
#
|
|
# The final argument, +type+, is the type of the C variable
|
|
def attach_variable(mname, a1, a2=nil)
|
|
cname, type = a2 ? [ a1, a2 ] : [ mname.to_s, a1 ]
|
|
ptr = nil
|
|
|
|
ffi_libraries.each do |lib|
|
|
ptr = lib.find_symbol(cname.to_s)
|
|
break unless ptr.nil?
|
|
end
|
|
|
|
raise FFI::NotFoundError, "Unable to find '#{cname}'" if ptr.nil? || ptr.null?
|
|
|
|
if type.kind_of?(Class) and type.ancestors.include?(FFI::Struct)
|
|
c = type.new(ptr)
|
|
|
|
self.module_eval <<-code, __FILE__, __LINE__
|
|
@ffi_gvar_#{mname} = c
|
|
def self.#{mname}
|
|
@ffi_gvar_#{mname}
|
|
end
|
|
code
|
|
|
|
else
|
|
enclosing_module = self
|
|
|
|
cs = Class.new(FFI::Struct) do
|
|
@enclosing_module = enclosing_module
|
|
end
|
|
|
|
cs.layout :gvar, type
|
|
c = cs.new(ptr)
|
|
|
|
self.module_eval <<-code, __FILE__, __LINE__
|
|
@ffi_gvar_#{mname} = c
|
|
def self.#{mname}
|
|
@ffi_gvar_#{mname}[:gvar]
|
|
end
|
|
def self.#{mname}=(value)
|
|
@ffi_gvar_#{mname}[:gvar] = value
|
|
end
|
|
code
|
|
|
|
end
|
|
|
|
return ptr
|
|
end
|
|
|
|
# Generic error method attached in place of missing foreign functions
|
|
# during loading the core library. See core/zed.rb for a version that
|
|
# raises immediately if the foreign function is unavaiblable.
|
|
def ffi_function_not_implemented(*args)
|
|
raise NotImplementedError, "function not implemented on this platform"
|
|
end
|
|
|
|
# Protocol for attaching foregin functions. If #attach_function fails to
|
|
# find a foreign function, this method will be called. Client code can
|
|
# provide an override to customize features.
|
|
def ffi_function_missing(cname, mname, args, ret)
|
|
if func = Rubinius.find_method(self, :ffi_function_not_implemented)
|
|
func = func[0].dup
|
|
func.name = cname.to_sym
|
|
add_function mname, func
|
|
end
|
|
end
|
|
|
|
def add_function(name, func)
|
|
# Make it available as a method callable directly..
|
|
sc = Rubinius::Type.object_singleton_class(self)
|
|
Rubinius::VM.reset_method_cache sc, name
|
|
|
|
sc.method_table.store name, nil, func, nil, 0, :public
|
|
|
|
# and expose it as a private method for people who
|
|
# want to include this module.
|
|
method_table.store name, nil, func, nil, 0, :private
|
|
end
|
|
private :add_function
|
|
|
|
def pointer_as_function(name, ptr, args, ret)
|
|
args.map! { |a| find_type a }
|
|
|
|
if func = FFI.generate_function(ptr, name.to_sym, args, find_type(ret))
|
|
add_function name, func
|
|
return func
|
|
end
|
|
|
|
raise FFI::NotFoundError, "Unable to attach pointer"
|
|
end
|
|
|
|
def callback(a1, a2, a3=nil)
|
|
if a3
|
|
name = a1
|
|
params = a2
|
|
ret = a3
|
|
else
|
|
name = nil
|
|
params = a1
|
|
ret = a2
|
|
end
|
|
|
|
args = params.map { |x| find_type(x) }
|
|
|
|
func, ptr = FFI.generate_trampoline nil, :ffi_tramp,
|
|
args, find_type(ret)
|
|
|
|
func.argument_types = params
|
|
func.return_type = ret
|
|
|
|
if name
|
|
@ffi_callbacks ||= {}
|
|
@ffi_callbacks[name] = func
|
|
|
|
typedef func, name
|
|
end
|
|
|
|
return func
|
|
end
|
|
|
|
def typedef(old, add)
|
|
@typedefs ||= Rubinius::LookupTable.new
|
|
@typedefs[add] = find_type(old)
|
|
end
|
|
|
|
def find_type(name)
|
|
@typedefs ||= Rubinius::LookupTable.new
|
|
|
|
if name.kind_of? Rubinius::NativeFunction or name.kind_of? FFI::Enum
|
|
return name
|
|
end
|
|
|
|
if type = @typedefs[name]
|
|
return type
|
|
end
|
|
|
|
FFI.find_type(name)
|
|
end
|
|
|
|
def enum(*args)
|
|
@tagged_enums ||= Rubinius::LookupTable.new
|
|
@anon_enums ||= Array.new
|
|
|
|
tag, values = if args[0].kind_of?(Symbol) && args[1].kind_of?(Array)
|
|
[ args[0], args[1] ]
|
|
elsif args[0].kind_of?(Array)
|
|
[ nil, args[0] ]
|
|
else
|
|
[ nil, args ]
|
|
end
|
|
|
|
enum = FFI::Enum.new values, tag
|
|
|
|
if tag
|
|
typedef(enum, tag)
|
|
@tagged_enums[tag] = enum
|
|
else
|
|
@anon_enums << enum
|
|
end
|
|
|
|
return enum
|
|
end
|
|
|
|
def enum_type(tag)
|
|
|
|
if enum = @tagged_enums[tag]
|
|
enum
|
|
else
|
|
@anon_enums.detect { |enum| enum.symbols.include?(tag) }
|
|
end
|
|
|
|
end
|
|
|
|
def enum_value(value)
|
|
|
|
if enum = @anon_enums.detect { |enum| enum.symbols.include?(value) }
|
|
enum
|
|
else
|
|
tag,enum = @tagged_enums.detect { |tag,enum| enum.symbols.include?(value) }
|
|
return enum
|
|
end
|
|
|
|
end
|
|
end
|
|
end
|
|
end
|