diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/ffi/autopointer.rb | 1 | ||||
-rw-r--r-- | lib/ffi/compat.rb | 43 | ||||
-rw-r--r-- | lib/ffi/dynamic_library.rb | 2 | ||||
-rw-r--r-- | lib/ffi/ffi.rb | 2 | ||||
-rw-r--r-- | lib/ffi/function.rb | 71 | ||||
-rw-r--r-- | lib/ffi/library.rb | 55 | ||||
-rw-r--r-- | lib/ffi/platform.rb | 28 | ||||
-rw-r--r-- | lib/ffi/struct_layout.rb | 2 | ||||
-rw-r--r-- | lib/ffi/struct_layout_builder.rb | 2 | ||||
-rw-r--r-- | lib/ffi/types.rb | 35 | ||||
-rw-r--r-- | lib/ffi/variadic.rb | 27 |
11 files changed, 232 insertions, 36 deletions
diff --git a/lib/ffi/autopointer.rb b/lib/ffi/autopointer.rb index 4f44171..bbcb6db 100644 --- a/lib/ffi/autopointer.rb +++ b/lib/ffi/autopointer.rb @@ -107,6 +107,7 @@ module FFI # @return [Boolean] +autorelease+ # Set +autorelease+ property. See {Pointer Autorelease section at Pointer}. def autorelease=(autorelease) + raise FrozenError.new("can't modify frozen #{self.class}") if frozen? @releaser.autorelease=(autorelease) end diff --git a/lib/ffi/compat.rb b/lib/ffi/compat.rb new file mode 100644 index 0000000..7569013 --- /dev/null +++ b/lib/ffi/compat.rb @@ -0,0 +1,43 @@ +# +# Copyright (C) 2023-2023 Lars Kanis +# +# This file is part of ruby-ffi. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of the Ruby FFI project nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +module FFI + if defined?(Ractor.make_shareable) + # This is for FFI internal use only. + def self.make_shareable(obj) + Ractor.make_shareable(obj) + end + else + def self.make_shareable(obj) + obj.freeze + end + end +end diff --git a/lib/ffi/dynamic_library.rb b/lib/ffi/dynamic_library.rb index a5469c4..b415033 100644 --- a/lib/ffi/dynamic_library.rb +++ b/lib/ffi/dynamic_library.rb @@ -35,7 +35,7 @@ module FFI SEARCH_PATH << '/opt/homebrew/lib' end - SEARCH_PATH_MESSAGE = "Searched in <system library path>, #{SEARCH_PATH.join(', ')}" + SEARCH_PATH_MESSAGE = "Searched in <system library path>, #{SEARCH_PATH.join(', ')}".freeze def self.load_library(name, flags) if name == FFI::CURRENT_PROCESS diff --git a/lib/ffi/ffi.rb b/lib/ffi/ffi.rb index dfffa8c..bc1b2e6 100644 --- a/lib/ffi/ffi.rb +++ b/lib/ffi/ffi.rb @@ -28,6 +28,7 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +require 'ffi/compat' require 'ffi/platform' require 'ffi/data_converter' require 'ffi/types' @@ -45,3 +46,4 @@ require 'ffi/autopointer' require 'ffi/variadic' require 'ffi/enum' require 'ffi/version' +require 'ffi/function' diff --git a/lib/ffi/function.rb b/lib/ffi/function.rb new file mode 100644 index 0000000..ac4daf0 --- /dev/null +++ b/lib/ffi/function.rb @@ -0,0 +1,71 @@ +# +# Copyright (C) 2008-2010 JRuby project +# +# This file is part of ruby-ffi. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of the Ruby FFI project nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +module FFI + class Function + # Only MRI allows function type queries + if private_method_defined?(:type) + # Retrieve the return type of the function + # + # This method returns FFI type returned by the function. + # + # @return [FFI::Type] + def return_type + type.return_type + end + + # Retrieve Array of parameter types + # + # This method returns an Array of FFI types accepted as function parameters. + # + # @return [Array<FFI::Type>] + def param_types + type.param_types + end + end + + # Stash the Function in a module variable so it can be inspected by attached_functions. + # On CRuby it also ensures that it does not get garbage collected. + module RegisterAttach + def attach(mod, name) + funcs = mod.instance_variable_get("@ffi_functions") + unless funcs + funcs = {} + mod.instance_variable_set("@ffi_functions", funcs) + end + funcs[name.to_sym] = self + # Jump to the native attach method of CRuby, JRuby or Tuffleruby + super + end + end + private_constant :RegisterAttach + prepend RegisterAttach + end +end diff --git a/lib/ffi/library.rb b/lib/ffi/library.rb index 92d2143..c23112b 100644 --- a/lib/ffi/library.rb +++ b/lib/ffi/library.rb @@ -31,7 +31,7 @@ require 'ffi/dynamic_library' module FFI - CURRENT_PROCESS = USE_THIS_PROCESS_AS_LIBRARY = Object.new + CURRENT_PROCESS = USE_THIS_PROCESS_AS_LIBRARY = FFI.make_shareable(Object.new) # @param [#to_s] lib library name # @return [String] library name formatted for current platform @@ -209,7 +209,7 @@ module FFI end raise LoadError unless function - invokers << if arg_types.length > 0 && arg_types[arg_types.length - 1] == FFI::NativeType::VARARGS + invokers << if arg_types[-1] == FFI::NativeType::VARARGS VariadicInvoker.new(function, arg_types, find_type(ret_type), options) else @@ -281,6 +281,7 @@ module FFI # Attach C variable +cname+ to this module. 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 @@ -295,9 +296,10 @@ module FFI # 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_gvar_#{mname} = s + @ffi_gsvars = {} unless defined?(@ffi_gsvars) + @ffi_gsvars[#{mname.inspect}] = s def self.#{mname} - @@ffi_gvar_#{mname} + @ffi_gsvars[#{mname.inspect}] end code @@ -309,12 +311,13 @@ module FFI # Attach to this module as mname/mname= # self.module_eval <<-code, __FILE__, __LINE__ - @@ffi_gvar_#{mname} = s + @ffi_gvars = {} unless defined?(@ffi_gvars) + @ffi_gvars[#{mname.inspect}] = s def self.#{mname} - @@ffi_gvar_#{mname}[:gvar] + @ffi_gvars[#{mname.inspect}][:gvar] end def self.#{mname}=(value) - @@ffi_gvar_#{mname}[:gvar] = value + @ffi_gvars[#{mname.inspect}][:gvar] = value end code @@ -539,5 +542,43 @@ module FFI end || FFI.find_type(t) end + + # 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. + # + # @return [Hash< Symbol => [FFI::Function, FFI::VariadicInvoker] >] + def attached_functions + @ffi_functions || {} + end + + # 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 . + # + # @return [Hash< Symbol => ffi_type >] + def attached_variables + ( + (@ffi_gsvars || {}).map do |name, gvar| + [name, gvar.class] + end + + (@ffi_gvars || {}).map do |name, gvar| + [name, gvar.layout[:gvar].type] + end + ).to_h + end + + # 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. + def freeze + instance_variables.each do |name| + var = instance_variable_get(name) + FFI.make_shareable(var) + end + nil + end end end diff --git a/lib/ffi/platform.rb b/lib/ffi/platform.rb index bf01a27..5ac4dd7 100644 --- a/lib/ffi/platform.rb +++ b/lib/ffi/platform.rb @@ -29,13 +29,15 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.# require 'rbconfig' +require_relative 'compat' + module FFI class PlatformError < LoadError; end # This module defines different constants and class methods to play with # various platforms. module Platform - OS = case RbConfig::CONFIG['host_os'].downcase + OS = FFI.make_shareable(case RbConfig::CONFIG['host_os'].downcase when /linux/ "linux" when /darwin/ @@ -54,13 +56,13 @@ module FFI "windows" else RbConfig::CONFIG['host_os'].downcase - end + end) OSVERSION = RbConfig::CONFIG['host_os'].gsub(/[^\d]/, '').to_i - CPU = RbConfig::CONFIG['host_cpu'] + CPU = FFI.make_shareable(RbConfig::CONFIG['host_cpu']) - ARCH = case CPU.downcase + ARCH = FFI.make_shareable(case CPU.downcase when /amd64|x86_64|x64/ "x86_64" when /i\d86|x86|i86pc/ @@ -81,7 +83,7 @@ module FFI end else RbConfig::CONFIG['host_cpu'] - end + end) private # @param [String) os @@ -105,21 +107,21 @@ module FFI # Add the version for known ABI breaks name_version = "12" if IS_FREEBSD && OSVERSION >= 12 # 64-bit inodes - NAME = "#{ARCH}-#{OS}#{name_version}" - CONF_DIR = File.join(File.dirname(__FILE__), 'platform', NAME) + NAME = FFI.make_shareable("#{ARCH}-#{OS}#{name_version}") + CONF_DIR = FFI.make_shareable(File.join(File.dirname(__FILE__), 'platform', NAME)) public - LIBPREFIX = case OS + LIBPREFIX = FFI.make_shareable(case OS when /windows|msys/ '' when /cygwin/ 'cyg' else 'lib' - end + end) - LIBSUFFIX = case OS + LIBSUFFIX = FFI.make_shareable(case OS when /darwin/ 'dylib' when /linux|bsd|solaris/ @@ -129,9 +131,9 @@ module FFI else # Punt and just assume a sane unix (i.e. anything but AIX) 'so' - end + end) - LIBC = if IS_WINDOWS + LIBC = FFI.make_shareable(if IS_WINDOWS crtname = RbConfig::CONFIG["RUBY_SO_NAME"][/msvc\w+/] || 'ucrtbase' "#{crtname}.dll" elsif IS_GNU @@ -143,7 +145,7 @@ module FFI "msys-2.0.dll" else "#{LIBPREFIX}c.#{LIBSUFFIX}" - end + end) LITTLE_ENDIAN = 1234 unless defined?(LITTLE_ENDIAN) BIG_ENDIAN = 4321 unless defined?(BIG_ENDIAN) diff --git a/lib/ffi/struct_layout.rb b/lib/ffi/struct_layout.rb index d5a78a7..3fd68cb 100644 --- a/lib/ffi/struct_layout.rb +++ b/lib/ffi/struct_layout.rb @@ -80,8 +80,8 @@ module FFI class Mapped < Field def initialize(name, offset, type, orig_field) - super(name, offset, type) @orig_field = orig_field + super(name, offset, type) end def get(ptr) diff --git a/lib/ffi/struct_layout_builder.rb b/lib/ffi/struct_layout_builder.rb index 4d6a464..d7d26a2 100644 --- a/lib/ffi/struct_layout_builder.rb +++ b/lib/ffi/struct_layout_builder.rb @@ -112,7 +112,7 @@ module FFI Type::FLOAT64, Type::LONGDOUBLE, Type::BOOL, - ] + ].freeze # @param [String, Symbol] name name of the field # @param [Array, DataConverter, Struct, StructLayout::Field, Symbol, Type] type type of the field diff --git a/lib/ffi/types.rb b/lib/ffi/types.rb index 90f50c1..8f5d897 100644 --- a/lib/ffi/types.rb +++ b/lib/ffi/types.rb @@ -33,12 +33,24 @@ # see {file:README} module FFI + unless defined?(self.custom_typedefs) + # Truffleruby and JRuby don't support Ractor so far. + # So they don't need separation between builtin and custom types. + def self.custom_typedefs + TypeDefs + end + writable_typemap = true + end + # @param [Type, DataConverter, Symbol] old type definition used by {FFI.find_type} # @param [Symbol] add new type definition's name to add # @return [Type] # Add a definition type to type definitions. + # + # The type definition is local per Ractor. def self.typedef(old, add) - TypeDefs[add] = self.find_type(old) + tm = custom_typedefs + tm[add] = self.find_type(old) end # (see FFI.typedef) @@ -46,6 +58,14 @@ module FFI typedef old, add end + class << self + private def __typedef(old, add) + TypeDefs[add] = self.find_type(old) + end + + private :custom_typedefs + end + # @param [Type, DataConverter, Symbol] name # @param [Hash] type_map if nil, {FFI::TypeDefs} is used @@ -57,9 +77,12 @@ module FFI if name.is_a?(Type) name - elsif type_map && type_map.has_key?(name) + elsif type_map&.has_key?(name) type_map[name] + elsif (tm=custom_typedefs).has_key?(name) + tm[name] + elsif TypeDefs.has_key?(name) TypeDefs[name] @@ -168,7 +191,7 @@ module FFI end end - typedef(StrPtrConverter, :strptr) + __typedef(StrPtrConverter, :strptr) # @param type +type+ is an instance of class accepted by {FFI.find_type} # @return [Numeric] @@ -184,11 +207,13 @@ module FFI f.each_line { |line| if line.index(prefix) == 0 new_type, orig_type = line.chomp.slice(prefix.length..-1).split(/\s*=\s*/) - typedef(orig_type.to_sym, new_type.to_sym) + __typedef(orig_type.to_sym, new_type.to_sym) end } end - typedef :pointer, :caddr_t + __typedef :pointer, :caddr_t rescue Errno::ENOENT end + + FFI.make_shareable(TypeDefs) unless writable_typemap end diff --git a/lib/ffi/variadic.rb b/lib/ffi/variadic.rb index 743ce7f..ee33409 100644 --- a/lib/ffi/variadic.rb +++ b/lib/ffi/variadic.rb @@ -54,16 +54,27 @@ module FFI invoker = self params = "*args" call = "call" - mod.module_eval <<-code - @@#{mname} = invoker - def self.#{mname}(#{params}) - @@#{mname}.#{call}(#{params}) - end - def #{mname}(#{params}) - @@#{mname}.#{call}(#{params}) - end + mname = mname.to_sym + mod.module_eval <<-code, __FILE__, __LINE__ + @ffi_functions = {} unless defined?(@ffi_functions) + @ffi_functions[#{mname.inspect}] = invoker + + def self.#{mname}(#{params}) + @ffi_functions[#{mname.inspect}].#{call}(#{params}) + end + + define_method(#{mname.inspect}, &method(#{mname.inspect})) code invoker end + + # Retrieve Array of parameter types + # + # This method returns an Array of FFI types accepted as function parameters. + # + # @return [Array<FFI::Type>] + def param_types + [*@fixed, Type::Builtin::VARARGS] + end end end |