Module: FFI::Library
Relationships & Source Files | |
Defined in: | lib/ffi/library.rb |
Overview
This module is the base to use native functions.
A basic usage may be:
require 'ffi'
module Hello
extend FFI::Library
ffi_lib FFI::Library::LIBC
attach_function 'puts', [ :string ], :int
end
Hello.puts("Hello, World")
Constant Summary
-
CURRENT_PROCESS =
# File 'lib/ffi/library.rb', line 73FFI::CURRENT_PROCESS
-
FlagsMap =
Flags used in #ffi_lib.
This map allows you to supply symbols to #ffi_lib_flags instead of the actual constants.
{ :global => DynamicLibrary::RTLD_GLOBAL, :local => DynamicLibrary::RTLD_LOCAL, :lazy => DynamicLibrary::RTLD_LAZY, :now => DynamicLibrary::RTLD_NOW }
-
LIBC =
# File 'lib/ffi/library.rb', line 74FFI::Platform::LIBC
Class Method Summary
-
.extended(mod) ⇒ nil
Test if extended object is a Module.
Instance Method Summary
-
#attach_function(func, args, returns, options = {}) ⇒ FFI::VariadicInvoker
Attach C function
func
to this module. -
#attach_variable(mname, cname, type) ⇒ DynamicLibrary::Symbol
Attach C variable
cname
to this module. -
#attached_functions ⇒ Hash< Symbol => [FFI::Function, FFI::VariadicInvoker] >
Retrieve all attached functions and their function signature.
-
#attached_variables ⇒ Hash< Symbol => ffi_type >
Retrieve all attached variables and their type.
-
#bitmask(name, values) ⇒ FFI::Bitmask
Create a new
Bitmask
- #callback(name, params, ret) ⇒ FFI::CallbackInfo
-
#enum(name, values) ⇒ FFI::Enum
Create a new
Enum
. -
#enum_type(name) ⇒ FFI::Enum
Find an enum by name.
-
#enum_value(symbol) ⇒ FFI::Enum
Find an enum by a symbol it contains.
-
#ffi_convention(convention = nil) ⇒ Symbol
Set the calling convention for #attach_function and #callback
-
#ffi_lib(*names) ⇒ Array<DynamicLibrary>
Load native libraries.
-
#ffi_lib_flags(*flags) ⇒ Integer
Sets library flags for #ffi_lib.
-
#ffi_libraries ⇒ Array<FFI::DynamicLibrary>
Get FFI libraries loaded using #ffi_lib.
-
#find_type(t) ⇒ Type
Find a type definition.
-
#freeze
Freeze all definitions of the module.
-
#function_names(name, arg_types) ⇒ Array<String>
This function returns a list of possible names to lookup.
-
#typedef(old, add, info = nil) ⇒ FFI::Enum, FFI::Type
Register or get an already registered type definition.
- #generic_enum(klass, *args) private
Class Method Details
.extended(mod) ⇒ nil
Test if extended object is a Module. If not, raise ::RuntimeError
.
# File 'lib/ffi/library.rb', line 80
def self.extended(mod) raise RuntimeError.new("must only be extended by module") unless mod.kind_of?(::Module) end
Instance Method Details
#attach_function(func, args, returns, options = {}) ⇒ FFI::VariadicInvoker
#attach_function(name, func, args, returns, options = {}) ⇒ FFI::VariadicInvoker
Attach C function func
to this module.
# File 'lib/ffi/library.rb', line 177
def attach_function(name, func, args, returns = nil, = nil) mname, a2, a3, a4, a5 = name, func, args, returns, cname, arg_types, ret_type, opts = (a4 && (a2.is_a?(String) || a2.is_a?(Symbol))) ? [ a2, a3, a4, a5 ] : [ mname.to_s, a2, a3, a4 ] # Convert :foo to the native type arg_types = arg_types.map { |e| find_type(e) } = { :convention => ffi_convention, :type_map => defined?(@ffi_typedefs) ? @ffi_typedefs : nil, :blocking => defined?(@blocking) && @blocking, :enums => defined?(@ffi_enums) ? @ffi_enums : nil, } @blocking = false .merge!(opts) if opts && opts.is_a?(Hash) # Try to locate the function in any of the libraries invokers = [] ffi_libraries.each do |lib| if invokers.empty? begin function = nil function_names(cname, arg_types).find do |fname| function = lib.find_function(fname) end raise LoadError unless function invokers << if arg_types[-1] == FFI::NativeType::VARARGS VariadicInvoker.new(function, arg_types, find_type(ret_type), ) else Function.new(find_type(ret_type), arg_types, function, ) end rescue LoadError end end end invoker = invokers.compact.shift raise FFI::NotFoundError.new(cname.to_s, ffi_libraries.map { |lib| lib.name }) unless invoker invoker.attach(self, mname.to_s) invoker end
#attach_variable(mname, cname, type) ⇒ DynamicLibrary::Symbol
#attach_variable(cname, type) ⇒ DynamicLibrary::Symbol
Attach C variable cname
to this module.
# File 'lib/ffi/library.rb', line 274
def attach_variable(mname, a1, a2 = nil) cname, type = a2 ? [ a1, a2 ] : [ mname.to_s, a1 ] mname = mname.to_sym address = nil ffi_libraries.each do |lib| begin address = lib.find_variable(cname.to_s) break unless address.nil? rescue LoadError end end raise FFI::NotFoundError.new(cname, ffi_libraries) if address.nil? || address.null? if type.is_a?(Class) && type < FFI::Struct # If it is a global struct, just attach directly to the pointer s = s = type.new(address) # Assigning twice to suppress unused variable warning self.module_eval <<-code, __FILE__, __LINE__ @ffi_gsvars = {} unless defined?(@ffi_gsvars) @ffi_gsvars[#{mname.inspect}] = s def self.#{mname} @ffi_gsvars[#{mname.inspect}] end code else sc = Class.new(FFI::Struct) sc.layout :gvar, find_type(type) s = sc.new(address) # # Attach to this module as mname/mname= # self.module_eval <<-code, __FILE__, __LINE__ @ffi_gvars = {} unless defined?(@ffi_gvars) @ffi_gvars[#{mname.inspect}] = s def self.#{mname} @ffi_gvars[#{mname.inspect}][:gvar] end def self.#{mname}=(value) @ffi_gvars[#{mname.inspect}][:gvar] = value end code end address end
#attached_functions ⇒ Hash
< Symbol
=> [FFI::Function, FFI::VariadicInvoker] >
Retrieve all attached functions and their function signature
This method returns a Hash of method names of attached functions connected by #attach_function and the corresponding function type. The function type responds to #return_type
and #param_types
which return the ::FFI
types of the function signature.
# File 'lib/ffi/library.rb', line 544
def attached_functions @ffi_functions || {} end
#attached_variables ⇒ Hash
< Symbol
=> ffi_type
>
Retrieve all attached variables and their type
This method returns a Hash of variable names and the corresponding type or variables connected by #attach_variable .
# File 'lib/ffi/library.rb', line 553
def attached_variables ( (defined?(@ffi_gsvars) ? @ffi_gsvars : {}).map do |name, gvar| [name, gvar.class] end + (defined?(@ffi_gvars) ? @ffi_gvars : {}).map do |name, gvar| [name, gvar.layout[:gvar].type] end ).to_h end
#bitmask(name, values) ⇒ FFI::Bitmask
#bitmask(*args) ⇒ FFI::Bitmask
#bitmask(values) ⇒ FFI::Bitmask
#bitmask(native_type, name, values) ⇒ FFI::Bitmask
#bitmask(native_type, *args) ⇒ FFI::Bitmask
#bitmask(native_type, values) ⇒ FFI::Bitmask
Create a new Bitmask
# File 'lib/ffi/library.rb', line 520
def bitmask(*args) generic_enum(FFI::Bitmask, *args) end
#callback(name, params, ret) ⇒ FFI::CallbackInfo
#callback(params, ret) ⇒ FFI::CallbackInfo
# File 'lib/ffi/library.rb', line 330
def callback(*args) raise ArgumentError, "wrong number of arguments" if args.length < 2 || args.length > 3 name, params, ret = if args.length == 3 args else [ nil, args[0], args[1] ] end native_params = params.map { |e| find_type(e) } raise ArgumentError, "callbacks cannot have variadic parameters" if native_params.include?(FFI::Type::VARARGS) = Hash.new [:convention] = ffi_convention [:enums] = @ffi_enums if defined?(@ffi_enums) ret_type = find_type(ret) if ret_type == Type::STRING raise TypeError, ":string is not allowed as return type of callbacks" end cb = FFI::CallbackInfo.new(ret_type, native_params, ) # Add to the symbol -> type map (unless there was no name) unless name.nil? typedef cb, name end cb end
Create a new Enum
.
# File 'lib/ffi/library.rb', line 477
def enum(*args) generic_enum(FFI::Enum, *args) end
#enum_type(name) ⇒ FFI::Enum
Find an enum by name.
# File 'lib/ffi/library.rb', line 527
def enum_type(name) @ffi_enums.find(name) if defined?(@ffi_enums) end
#enum_value(symbol) ⇒ FFI::Enum
Find an enum by a symbol it contains.
# File 'lib/ffi/library.rb', line 534
def enum_value(symbol) @ffi_enums.__map_symbol(symbol) end
#ffi_convention(convention = nil) ⇒ Symbol
:stdcall
is typically used for attaching Windows API functions
Set the calling convention for #attach_function and #callback
# File 'lib/ffi/library.rb', line 106
def ffi_convention(convention = nil) @ffi_convention ||= :default @ffi_convention = convention if convention @ffi_convention end
#ffi_lib(*names) ⇒ Array
<DynamicLibrary>
Load native libraries.
# File 'lib/ffi/library.rb', line 89
def ffi_lib(*names) raise LoadError.new("library names list must not be empty") if names.empty? lib_flags = defined?(@ffi_lib_flags) && @ffi_lib_flags @ffi_libs = names.map do |name| FFI::DynamicLibrary.send(:load_library, name, lib_flags) end end
#ffi_lib_flags(*flags) ⇒ Integer
Sets library flags for #ffi_lib.
# File 'lib/ffi/library.rb', line 139
def ffi_lib_flags(*flags) @ffi_lib_flags = flags.inject(0) { |result, f| result | FlagsMap[f] } end
#ffi_libraries ⇒ Array
<FFI::DynamicLibrary>
Get FFI libraries loaded using #ffi_lib.
# File 'lib/ffi/library.rb', line 116
def ffi_libraries raise LoadError.new("no library specified") if !defined?(@ffi_libs) || @ffi_libs.empty? @ffi_libs end
#find_type(t) ⇒ Type
Find a type definition.
# File 'lib/ffi/library.rb', line 401
def find_type(t) if t.kind_of?(Type) t elsif defined?(@ffi_typedefs) && @ffi_typedefs.has_key?(t) @ffi_typedefs[t] elsif t.is_a?(Class) && t < Struct Type::POINTER elsif t.is_a?(DataConverter) # Add a typedef so next time the converter is used, it hits the cache typedef Type::Mapped.new(t), t end || FFI.find_type(t) end
#freeze
Freeze all definitions of the module
This freezes the module’s definitions, so that it can be used in a Ractor. No further methods or variables can be attached and no further enums or typedefs can be created in this module afterwards.
# File 'lib/ffi/library.rb', line 568
def freeze instance_variables.each do |name| var = instance_variable_get(name) FFI.make_shareable(var) end nil end
#function_names(name, arg_types) ⇒ Array
<String
>
Function
names on windows may be decorated if they are using stdcall. See
-
en.wikipedia.org/wiki/Name_mangling#C_name_decoration_in_Microsoft_Windows
-
msdn.microsoft.com/en-us/library/zxk0tw93%28v=VS.100%29.aspx
-
en.wikibooks.org/wiki/X86_Disassembly/Calling_Conventions#STDCALL
Note that decorated names can be overridden via def files. Also note that the windows api, although using, doesn’t have decorated names.
This function returns a list of possible names to lookup.
# File 'lib/ffi/library.rb', line 232
def function_names(name, arg_types) result = [name.to_s] if ffi_convention == :stdcall # Get the size of each parameter size = arg_types.inject(0) do |mem, arg| size = arg.size # The size must be a multiple of 4 size += (4 - size) % 4 mem + size end result << "_#{name.to_s}@#{size}" # win32 result << "#{name.to_s}@#{size}" # win64 end result end
#generic_enum(klass, *args) (private)
# File 'lib/ffi/library.rb', line 422
def generic_enum(klass, *args) native_type = args.first.kind_of?(FFI::Type) ? args.shift : nil name, 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 @ffi_enums = FFI::Enums.new unless defined?(@ffi_enums) @ffi_enums << (e = native_type ? klass.new(native_type, values, name) : klass.new(values, name)) # If called with a name, add a typedef alias typedef(e, name) if name e end
#typedef(old, add, info = nil) ⇒ FFI::Enum, FFI::Type
Register or get an already registered type definition.
To register a new type definition, old
should be a Type
. add
is in this case the type definition.
If old
is a DataConverter
, a Type::Mapped
is returned.
If old
is :enum
-
and
add
is anArray
, a call to #enum is made withadd
as single parameter; -
in others cases,
info
is used to create a named enum.
If old
is a key for type map, #typedef
get old
type definition.
# File 'lib/ffi/library.rb', line 374
def typedef(old, add, info=nil) @ffi_typedefs = Hash.new unless defined?(@ffi_typedefs) @ffi_typedefs[add] = if old.kind_of?(FFI::Type) old elsif @ffi_typedefs.has_key?(old) @ffi_typedefs[old] elsif old.is_a?(DataConverter) FFI::Type::Mapped.new(old) elsif old == :enum if add.kind_of?(Array) self.enum(add) else self.enum(info, add) end else FFI.find_type(old) end end