summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Kanis <lars@greiz-reinsdorf.de>2023-04-21 08:34:07 +0200
committerLars Kanis <lars@greiz-reinsdorf.de>2023-04-27 13:47:57 +0200
commite987ab50366a4b08617a20568eabdaa1fb761317 (patch)
tree0f10e6887c41ae27c0514ddd7c5b0d1b55a4f8ea
parentad1a2e0cd5970e5d1618782a6ac2d5328811370d (diff)
downloadffi-e987ab50366a4b08617a20568eabdaa1fb761317.tar.gz
Add the possibility to query attached funtions and variables
-rw-r--r--ext/ffi_c/Function.c21
-rw-r--r--ext/ffi_c/MappedType.c10
-rw-r--r--ext/ffi_c/Variadic.c9
-rw-r--r--lib/ffi/ffi.rb1
-rw-r--r--lib/ffi/function.rb44
-rw-r--r--lib/ffi/library.rb27
-rw-r--r--lib/ffi/variadic.rb19
-rw-r--r--spec/ffi/function_spec.rb2
-rw-r--r--spec/ffi/library_spec.rb15
-rw-r--r--spec/ffi/struct_by_ref_spec.rb5
-rw-r--r--spec/ffi/variadic_spec.rb29
11 files changed, 166 insertions, 16 deletions
diff --git a/ext/ffi_c/Function.c b/ext/ffi_c/Function.c
index f88bc93..40d613e 100644
--- a/ext/ffi_c/Function.c
+++ b/ext/ffi_c/Function.c
@@ -492,8 +492,8 @@ static VALUE
function_attach(VALUE self, VALUE module, VALUE name)
{
Function* fn;
- char var[1024];
+ StringValue(name);
TypedData_Get_Struct(self, Function, &function_data_type, fn);
if (fn->info->parameterCount == -1) {
@@ -513,8 +513,12 @@ function_attach(VALUE self, VALUE module, VALUE name)
/*
* Stash the Function in a module variable so it does not get garbage collected
*/
- snprintf(var, sizeof(var), "@@%s", StringValueCStr(name));
- rb_cv_set(module, var, self);
+ VALUE funcs = rb_iv_get(module, "@ffi_functions");
+ if (RB_NIL_P(funcs)) {
+ funcs = rb_hash_new();
+ rb_iv_set(module, "@ffi_functions", funcs);
+ }
+ rb_hash_aset(funcs, name, self);
rb_define_singleton_method(module, StringValueCStr(name),
rbffi_MethodHandle_CodeAddress(fn->methodHandle), -1);
@@ -555,6 +559,16 @@ function_autorelease_p(VALUE self)
return fn->autorelease ? Qtrue : Qfalse;
}
+static VALUE
+function_type(VALUE self)
+{
+ Function* fn;
+
+ TypedData_Get_Struct(self, Function, &function_data_type, fn);
+
+ return fn->rbFunctionInfo;
+}
+
/*
* call-seq: free
* @return [self]
@@ -1027,6 +1041,7 @@ rbffi_Function_Init(VALUE moduleFFI)
rb_define_method(rbffi_FunctionClass, "attach", function_attach, 2);
rb_define_method(rbffi_FunctionClass, "free", function_release, 0);
rb_define_method(rbffi_FunctionClass, "autorelease=", function_set_autorelease, 1);
+ rb_define_private_method(rbffi_FunctionClass, "type", function_type, 0);
/*
* call-seq: autorelease
* @return [Boolean]
diff --git a/ext/ffi_c/MappedType.c b/ext/ffi_c/MappedType.c
index e61af30..20b14e9 100644
--- a/ext/ffi_c/MappedType.c
+++ b/ext/ffi_c/MappedType.c
@@ -175,6 +175,15 @@ mapped_from_native(int argc, VALUE* argv, VALUE self)
return rb_funcall2(m->rbConverter, id_from_native, argc, argv);
}
+static VALUE
+mapped_converter(VALUE self)
+{
+ MappedType*m = NULL;
+ TypedData_Get_Struct(self, MappedType, &mapped_type_data_type, m);
+
+ return m->rbConverter;
+}
+
void
rbffi_MappedType_Init(VALUE moduleFFI)
{
@@ -195,5 +204,6 @@ rbffi_MappedType_Init(VALUE moduleFFI)
rb_define_method(rbffi_MappedTypeClass, "native_type", mapped_native_type, 0);
rb_define_method(rbffi_MappedTypeClass, "to_native", mapped_to_native, -1);
rb_define_method(rbffi_MappedTypeClass, "from_native", mapped_from_native, -1);
+ rb_define_method(rbffi_MappedTypeClass, "converter", mapped_converter, 0);
}
diff --git a/ext/ffi_c/Variadic.c b/ext/ffi_c/Variadic.c
index 2e9d790..fb37194 100644
--- a/ext/ffi_c/Variadic.c
+++ b/ext/ffi_c/Variadic.c
@@ -318,6 +318,14 @@ variadic_invoke(VALUE self, VALUE parameterTypes, VALUE parameterValues)
return rbffi_NativeValue_ToRuby(invoker->returnType, invoker->rbReturnType, retval);
}
+static VALUE
+variadic_result_type(VALUE self)
+{
+ VariadicInvoker* invoker;
+
+ TypedData_Get_Struct(self, VariadicInvoker, &variadic_data_type, invoker);
+ return invoker->rbReturnType;
+}
void
rbffi_Variadic_Init(VALUE moduleFFI)
@@ -329,5 +337,6 @@ rbffi_Variadic_Init(VALUE moduleFFI)
rb_define_method(classVariadicInvoker, "initialize", variadic_initialize, 4);
rb_define_method(classVariadicInvoker, "invoke", variadic_invoke, 2);
+ rb_define_method(classVariadicInvoker, "result_type", variadic_result_type, 0);
}
diff --git a/lib/ffi/ffi.rb b/lib/ffi/ffi.rb
index 31f25d1..bc1b2e6 100644
--- a/lib/ffi/ffi.rb
+++ b/lib/ffi/ffi.rb
@@ -46,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..5996d96
--- /dev/null
+++ b/lib/ffi/function.rb
@@ -0,0 +1,44 @@
+#
+# 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)
+ def result_type
+ type.result_type
+ end
+
+ def param_types
+ type.param_types
+ end
+ end
+ end
+end
diff --git a/lib/ffi/library.rb b/lib/ffi/library.rb
index 21dce60..5656b2d 100644
--- a/lib/ffi/library.rb
+++ b/lib/ffi/library.rb
@@ -295,9 +295,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_gvars = {} unless defined?(@ffi_gvars)
+ @ffi_gvars[#{mname.inspect}] = s
def self.#{mname}
- @@ffi_gvar_#{mname}
+ @ffi_gvars[#{mname.inspect}]
end
code
@@ -309,12 +310,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 +541,20 @@ module FFI
end || FFI.find_type(t)
end
+
+ def attached_functions
+ @ffi_functions || {}
+ end
+
+ def attached_variables
+ (@ffi_gvars || {}).map do |name, gvar|
+ [name, gvar.class]
+ end.to_h
+ end
+
+ def freeze
+ super
+ FFI.make_shareable(@ffi_functions)
+ end
end
end
diff --git a/lib/ffi/variadic.rb b/lib/ffi/variadic.rb
index 743ce7f..42c5549 100644
--- a/lib/ffi/variadic.rb
+++ b/lib/ffi/variadic.rb
@@ -55,15 +55,20 @@ module FFI
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
+ @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}))
code
invoker
end
+
+ def param_types
+ [*@fixed, Type::Builtin::VARARGS]
+ end
end
end
diff --git a/spec/ffi/function_spec.rb b/spec/ffi/function_spec.rb
index 0d89a7d..badd240 100644
--- a/spec/ffi/function_spec.rb
+++ b/spec/ffi/function_spec.rb
@@ -64,7 +64,7 @@ describe FFI::Function do
end
it "should be usable with Ractor", :ractor do
- res = Ractor.new(@conninfo) do |conninfo|
+ res = Ractor.new do
function_add = FFI::Function.new(:int, [:int, :int]) { |a, b| a + b }
LibTest.testFunctionAdd(10, 10, function_add)
end.take
diff --git a/spec/ffi/library_spec.rb b/spec/ffi/library_spec.rb
index f223d26..97790e5 100644
--- a/spec/ffi/library_spec.rb
+++ b/spec/ffi/library_spec.rb
@@ -193,6 +193,19 @@ describe "Library" do
end
expect(mod.bool_return_true).to be true
end
+
+ it "can reveal the function type" do
+ mod = Module.new do |m|
+ m.extend FFI::Library
+ ffi_lib File.expand_path(TestLibrary::PATH)
+ attach_function :bool_return_true, [ :string ], :bool
+ end
+
+ fun = mod.attached_functions
+ expect(fun.keys).to eq(["bool_return_true"])
+ expect(fun["bool_return_true"].param_types).to eq([FFI::Type::STRING])
+ expect(fun["bool_return_true"].result_type).to eq(FFI::Type::BOOL)
+ end
end
def gvar_lib(name, type)
@@ -320,6 +333,8 @@ describe "Library" do
lib.gvar[:data] = i
val = GlobalStruct.new(lib.get)
expect(val[:data]).to eq(i)
+
+ expect(lib.attached_variables).to eq({ gvar: GlobalStruct })
end
end
diff --git a/spec/ffi/struct_by_ref_spec.rb b/spec/ffi/struct_by_ref_spec.rb
index 0858423..0f48fbb 100644
--- a/spec/ffi/struct_by_ref_spec.rb
+++ b/spec/ffi/struct_by_ref_spec.rb
@@ -39,5 +39,10 @@ describe FFI::Struct, ' by_ref' do
expect { @api.struct_test(other_class.new) }.to raise_error(TypeError)
end
+
+ it "can reveal the mapped type converter" do
+ param_type = @api.attached_functions["struct_test"].param_types[0]
+ expect(param_type.converter).to be_a(FFI::StructByReference)
+ end
end
diff --git a/spec/ffi/variadic_spec.rb b/spec/ffi/variadic_spec.rb
index f379ed4..fdaf31c 100644
--- a/spec/ffi/variadic_spec.rb
+++ b/spec/ffi/variadic_spec.rb
@@ -23,6 +23,8 @@ describe "Function with variadic arguments" do
attach_function :testBlockingClose, [ :pointer ], :void
attach_function :testCallbackVrDva, :testClosureVrDva, [ :double, :varargs ], :double
attach_function :testCallbackVrILva, :testClosureVrILva, [ :int, :long, :varargs ], :long
+
+ freeze
end
it "takes enum arguments" do
@@ -37,6 +39,12 @@ describe "Function with variadic arguments" do
expect(LibTest.pack_varargs2(buf, :c1, "ii", :int, :c3, :int, :c4)).to eq(:c2)
end
+ it "can reveal its return and parameters" do
+ fun = LibTest.attached_functions["testBlockingWRva"]
+ expect(fun.param_types).to eq([FFI::Type::POINTER, FFI::Type::CHAR, FFI::Type::VARARGS])
+ expect(fun.result_type).to eq(FFI::Type::INT8)
+ end
+
it 'can wrap a blocking function with varargs' do
handle = LibTest.testBlockingOpen
expect(handle).not_to be_null
@@ -87,12 +95,33 @@ describe "Function with variadic arguments" do
expect(LibTest.testCallbackVrDva(3.0, :cbVrD, pr)).to be_within(0.0000001).of(45.0)
end
+ it "can be called as instance method" do
+ kl = Class.new do
+ include LibTest
+ def call
+ pr = proc { 42.0 }
+ testCallbackVrDva(3.0, :cbVrD, pr)
+ end
+ end
+ expect(kl.new.call).to be_within(0.0000001).of(45.0)
+ end
+
it "call variadic with several callback arguments" do
pr1 = proc { |i| i + 1 }
pr2 = proc { |l| l + 2 }
expect(LibTest.testCallbackVrILva(5, 6, :cbVrI, pr1, :cbVrL, pr2)).to eq(14)
end
+ it "should be usable with Ractor", :ractor do
+ res = Ractor.new do
+ pr = proc { 42.0 }
+ LibTest.testCallbackVrDva(3.0, :cbVrD, pr)
+ end.take
+
+ expect(res).to be_within(0.0000001).of(45.0)
+ end
+
+
module Varargs
PACK_VALUES = {
'c' => [ 0x12 ],