From e987ab50366a4b08617a20568eabdaa1fb761317 Mon Sep 17 00:00:00 2001 From: Lars Kanis Date: Fri, 21 Apr 2023 08:34:07 +0200 Subject: Add the possibility to query attached funtions and variables --- ext/ffi_c/Function.c | 21 +++++++++++++++++--- ext/ffi_c/MappedType.c | 10 ++++++++++ ext/ffi_c/Variadic.c | 9 +++++++++ lib/ffi/ffi.rb | 1 + lib/ffi/function.rb | 44 ++++++++++++++++++++++++++++++++++++++++++ lib/ffi/library.rb | 27 +++++++++++++++++++++----- lib/ffi/variadic.rb | 19 +++++++++++------- spec/ffi/function_spec.rb | 2 +- spec/ffi/library_spec.rb | 15 ++++++++++++++ spec/ffi/struct_by_ref_spec.rb | 5 +++++ spec/ffi/variadic_spec.rb | 29 ++++++++++++++++++++++++++++ 11 files changed, 166 insertions(+), 16 deletions(-) create mode 100644 lib/ffi/function.rb 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 ], -- cgit v1.2.1