summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/ffi/autopointer.rb1
-rw-r--r--lib/ffi/compat.rb43
-rw-r--r--lib/ffi/dynamic_library.rb2
-rw-r--r--lib/ffi/ffi.rb2
-rw-r--r--lib/ffi/function.rb71
-rw-r--r--lib/ffi/library.rb55
-rw-r--r--lib/ffi/platform.rb28
-rw-r--r--lib/ffi/struct_layout.rb2
-rw-r--r--lib/ffi/struct_layout_builder.rb2
-rw-r--r--lib/ffi/types.rb35
-rw-r--r--lib/ffi/variadic.rb27
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