core/library.rb
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