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