From fe556f176d8239887fb583db2f3bb78f92a8cec2 Mon Sep 17 00:00:00 2001 From: Lars Kanis Date: Fri, 5 May 2023 23:56:47 +0200 Subject: Freeze immutable objects and add explicit make_shareable to make library definitions Ractor usable Freeze objects which are immutable from the start are now freezed. This allows these objects to be used by Ractor without make_shareable. This partially reverts commit 4fc6a8c5ec8a9a720330946af9d1103015c62942 in such a way, that functions are stored in a module variable @ffi_functions. Enums are implemented per FFI::Library not per attached function. To make them shareable they would have to be copied and freezed per function. This would increase memory footprint for the sake of Ractor support. IMHO it's better to mark the module explicit as finished by .freeze to allow its use in a Ractor. This also enables querying the enum definitions from a Ractor. Using a Hash instead of per-function variables allow to use foo! and bar? methods as wished in #971. Fixes #971 --- ext/ffi_c/DynamicLibrary.c | 7 +++-- ext/ffi_c/Function.c | 12 ++++++--- ext/ffi_c/FunctionInfo.c | 2 ++ ext/ffi_c/MappedType.c | 2 ++ ext/ffi_c/StructLayout.c | 7 +++++ ext/ffi_c/Type.c | 4 +++ ext/ffi_c/Variadic.c | 5 ++-- ext/ffi_c/rbffi.h | 2 +- lib/ffi/library.rb | 36 ++++++++++++++++--------- lib/ffi/struct.rb | 2 +- lib/ffi/struct_layout.rb | 2 +- lib/ffi/struct_layout_builder.rb | 4 +-- lib/ffi/variadic.rb | 7 ++--- spec/ffi/dynamic_library_spec.rb | 6 ++++- spec/ffi/enum_spec.rb | 24 +++++++++++------ spec/ffi/function_spec.rb | 2 +- spec/ffi/library_spec.rb | 57 ++++++++++++++++++++++++++-------------- spec/ffi/struct_by_ref_spec.rb | 2 +- spec/ffi/struct_spec.rb | 16 ++++++++++- spec/ffi/type_spec.rb | 35 +++++++++++++++++++++++- spec/ffi/variadic_spec.rb | 2 ++ 21 files changed, 175 insertions(+), 61 deletions(-) diff --git a/ext/ffi_c/DynamicLibrary.c b/ext/ffi_c/DynamicLibrary.c index 9abafc7..1d83940 100644 --- a/ext/ffi_c/DynamicLibrary.c +++ b/ext/ffi_c/DynamicLibrary.c @@ -161,7 +161,9 @@ library_initialize(VALUE self, VALUE libname, VALUE libflags) library->handle = RTLD_DEFAULT; } #endif - rb_iv_set(self, "@name", libname != Qnil ? libname : rb_str_new2("[current process]")); + rb_iv_set(self, "@name", libname != Qnil ? rb_str_new_frozen(libname) : rb_str_new2("[current process]")); + + rb_obj_freeze(self); return self; } @@ -266,8 +268,9 @@ symbol_new(VALUE library, void* address, VALUE name) sym->base.memory.typeSize = 1; sym->base.memory.flags = MEM_RD | MEM_WR; RB_OBJ_WRITE(obj, &sym->base.rbParent, library); - RB_OBJ_WRITE(obj, &sym->name, name); + RB_OBJ_WRITE(obj, &sym->name, rb_str_new_frozen(name)); + rb_obj_freeze(obj); return obj; } diff --git a/ext/ffi_c/Function.c b/ext/ffi_c/Function.c index 9da6b37..d0b65fb 100644 --- a/ext/ffi_c/Function.c +++ b/ext/ffi_c/Function.c @@ -492,7 +492,7 @@ static VALUE function_attach(VALUE self, VALUE module, VALUE name) { Function* fn; - char var[1024]; + VALUE funcs; StringValue(name); TypedData_Get_Struct(self, Function, &function_data_type, fn); @@ -514,9 +514,13 @@ function_attach(VALUE self, VALUE module, VALUE name) /* * Stash the Function in a module variable so it does not get garbage collected and can be inspected by attached_functions */ - snprintf(var, sizeof(var), "@ffi_function_%s", StringValueCStr(name)); - rb_ractor_make_shareable(self); - rb_iv_set(module, var, self); + + 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, rb_str_intern(name), self); rb_define_singleton_method(module, StringValueCStr(name), rbffi_MethodHandle_CodeAddress(fn->methodHandle), -1); diff --git a/ext/ffi_c/FunctionInfo.c b/ext/ffi_c/FunctionInfo.c index 7495215..b5150d8 100644 --- a/ext/ffi_c/FunctionInfo.c +++ b/ext/ffi_c/FunctionInfo.c @@ -251,6 +251,8 @@ fntype_initialize(int argc, VALUE* argv, VALUE self) fnInfo->invoke = rbffi_GetInvoker(fnInfo); + rb_obj_freeze(fnInfo->rbParameterTypes); + rb_obj_freeze(self); return self; } diff --git a/ext/ffi_c/MappedType.c b/ext/ffi_c/MappedType.c index 20b14e9..2e506f2 100644 --- a/ext/ffi_c/MappedType.c +++ b/ext/ffi_c/MappedType.c @@ -110,6 +110,8 @@ mapped_initialize(VALUE self, VALUE rbConverter) TypedData_Get_Struct(m->rbType, Type, &rbffi_type_data_type, m->type); m->base.ffiType = m->type->ffiType; + rb_obj_freeze(self); + return self; } diff --git a/ext/ffi_c/StructLayout.c b/ext/ffi_c/StructLayout.c index 613394a..a56d48f 100644 --- a/ext/ffi_c/StructLayout.c +++ b/ext/ffi_c/StructLayout.c @@ -181,6 +181,8 @@ struct_field_initialize(int argc, VALUE* argv, VALUE self) break; } + rb_obj_freeze(self); + return self; } @@ -538,6 +540,11 @@ struct_layout_initialize(VALUE self, VALUE fields, VALUE size, VALUE align) rb_raise(rb_eRuntimeError, "Struct size is zero"); } + rb_obj_freeze(layout->rbFieldMap); + rb_obj_freeze(layout->rbFields); + rb_obj_freeze(layout->rbFieldNames); + rb_obj_freeze(self); + return self; } diff --git a/ext/ffi_c/Type.c b/ext/ffi_c/Type.c index 9bf5681..a94c009 100644 --- a/ext/ffi_c/Type.c +++ b/ext/ffi_c/Type.c @@ -128,6 +128,8 @@ type_initialize(VALUE self, VALUE value) rb_raise(rb_eArgError, "wrong type"); } + rb_obj_freeze(self); + return self; } @@ -192,6 +194,8 @@ builtin_type_new(VALUE klass, int nativeType, ffi_type* ffiType, const char* nam type->type.nativeType = nativeType; type->type.ffiType = ffiType; + rb_obj_freeze(obj); + return obj; } diff --git a/ext/ffi_c/Variadic.c b/ext/ffi_c/Variadic.c index 0f321d9..09d7ce8 100644 --- a/ext/ffi_c/Variadic.c +++ b/ext/ffi_c/Variadic.c @@ -183,9 +183,8 @@ variadic_initialize(VALUE self, VALUE rbFunction, VALUE rbParameterTypes, VALUE /* * @fixed and @type_map are used by the parameter mangling ruby code */ - rb_iv_set(self, "@fixed", fixed); - rb_iv_set(self, "@type_map", rb_obj_dup(rb_hash_aref(options, ID2SYM(rb_intern("type_map"))))); - rb_ractor_make_shareable(self); + rb_iv_set(self, "@fixed", rb_obj_freeze(fixed)); + rb_iv_set(self, "@type_map", rb_hash_aref(options, ID2SYM(rb_intern("type_map")))); return retval; } diff --git a/ext/ffi_c/rbffi.h b/ext/ffi_c/rbffi.h index 89b3e32..0e4e91a 100644 --- a/ext/ffi_c/rbffi.h +++ b/ext/ffi_c/rbffi.h @@ -39,7 +39,7 @@ extern "C" { #define MAX_PARAMETERS (32) extern VALUE rbffi_FFIModule; - + extern void rbffi_Type_Init(VALUE ffiModule); extern void rbffi_Buffer_Init(VALUE ffiModule); extern void rbffi_Invoker_Init(VALUE ffiModule); diff --git a/lib/ffi/library.rb b/lib/ffi/library.rb index e681b3e..8af457d 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_gsvar_#{mname} = s + @ffi_gsvars = {} unless defined?(@ffi_gsvars) + @ffi_gsvars[#{mname.inspect}] = s def self.#{mname} - @ffi_gsvar_#{mname} + @ffi_gsvars[#{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 @@ -541,20 +543,30 @@ module FFI end def attached_functions - instance_variables.grep(/\A@ffi_function_(.*)/) do |m| - [$1, instance_variable_get(m)] - end.to_h + @ffi_functions || {} end def attached_variables ( - instance_variables.grep(/\A@ffi_gsvar_(.*)/) do |m| - [$1, instance_variable_get(m).class] + (@ffi_gsvars || {}).map do |name, gvar| + [name, gvar.class] end + - instance_variables.grep(/\A@ffi_gvar_(.*)/) do |m| - [$1, instance_variable_get(m).layout[:gvar].type] + (@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/struct.rb b/lib/ffi/struct.rb index 1283d63..725b9cb 100644 --- a/lib/ffi/struct.rb +++ b/lib/ffi/struct.rb @@ -219,7 +219,7 @@ module FFI end builder.size = @size if defined?(@size) && @size > builder.size cspec = builder.build - @layout = FFI.make_shareable(cspec) unless self == Struct + @layout = cspec unless self == Struct @size = cspec.size return cspec end 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 5488033..d7d26a2 100644 --- a/lib/ffi/struct_layout_builder.rb +++ b/lib/ffi/struct_layout_builder.rb @@ -97,7 +97,7 @@ module FFI # List of number types - NUMBER_TYPES = FFI.make_shareable([ + NUMBER_TYPES = [ Type::INT8, Type::UINT8, Type::INT16, @@ -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/variadic.rb b/lib/ffi/variadic.rb index 800d121..246b52f 100644 --- a/lib/ffi/variadic.rb +++ b/lib/ffi/variadic.rb @@ -54,11 +54,12 @@ module FFI invoker = self params = "*args" call = "call" - mod.module_eval <<-code - @ffi_function_#{mname} = invoker + mod.module_eval <<-code, __FILE__, __LINE__ + @ffi_functions = {} unless defined?(@ffi_functions) + @ffi_functions[#{mname.inspect}] = invoker def self.#{mname}(#{params}) - @ffi_function_#{mname}.#{call}(#{params}) + @ffi_functions[#{mname.inspect}].#{call}(#{params}) end define_method(#{mname.inspect}, &method(:#{mname})) diff --git a/spec/ffi/dynamic_library_spec.rb b/spec/ffi/dynamic_library_spec.rb index a6ea2a9..088a822 100644 --- a/spec/ffi/dynamic_library_spec.rb +++ b/spec/ffi/dynamic_library_spec.rb @@ -9,7 +9,6 @@ describe FFI::DynamicLibrary do it "should be shareable for Ractor", :ractor do libtest = FFI::DynamicLibrary.open(TestLibrary::PATH, FFI::DynamicLibrary::RTLD_LAZY | FFI::DynamicLibrary::RTLD_GLOBAL) - Ractor.make_shareable(libtest) res = Ractor.new(libtest) do |libtest2| libtest2.find_symbol("testClosureVrV").address @@ -52,5 +51,10 @@ describe FFI::DynamicLibrary do size = ObjectSpace.memsize_of(symbol) expect(size).to be > base_size end + + it "should be shareable for Ractor", :ractor do + symbol = @libtest.find_symbol("gvar_gstruct_set") + expect(Ractor.shareable?(symbol)).to be true + end end end diff --git a/spec/ffi/enum_spec.rb b/spec/ffi/enum_spec.rb index 1fba2cb..7a55b07 100644 --- a/spec/ffi/enum_spec.rb +++ b/spec/ffi/enum_spec.rb @@ -26,14 +26,18 @@ module TestEnum3 ffi_lib TestLibrary::PATH enum :enum_type1, [:c1, :c2, :c3, :c4] - enum :enum_type2, [:c5, 42, :c6, :c7, :c8] - enum :enum_type3, [:c9, 42, :c10, :c11, 4242, :c12] - enum :enum_type4, [:c13, 42, :c14, 4242, :c15, 424242, :c16, 42424242] - attach_function :test_tagged_typedef_enum1, [:enum_type1], :enum_type1 + + enum :enum_type2, [:c5, 42, :c6, :c7, :c8] attach_function :test_tagged_typedef_enum2, [:enum_type2], :enum_type2 + + enum :enum_type3, [:c9, 42, :c10, :c11, 4242, :c12] attach_function :test_tagged_typedef_enum3, [:enum_type3], :enum_type3 + + enum :enum_type4, [:c13, 42, :c14, 4242, :c15, 424242, :c16, 42424242] attach_function :test_tagged_typedef_enum4, [:enum_type4], :enum_type4 + + freeze end module TestEnum4 @@ -44,18 +48,22 @@ module TestEnum4 enum :enum_type1, [:c5, 0x42, :c6, :c7, :c8] enum :enum_type2, [:c9, 0x42, :c10, :c11, 0x4242, :c12] enum :enum_type3, [:c13, 0x42, :c14, 0x4242, :c15, 0x42424242, :c16, 0x4242424242424242] - enum FFI::Type::UINT16, :enum_type4, [:c17, 0x42, :c18, :c19, :c20] - enum FFI::Type::UINT32, :enum_type5, [:c21, 0x42, :c22, :c23, 0x4242, :c24] - enum FFI::Type::UINT64, :enum_type6, [:c25, 0x42, :c26, 0x4242, :c27, 0x42424242, :c28, 0x4242424242424242] - enum FFI::Type::UINT64, [:c29, 0x4242424242424242, :c30, :c31, :c32] attach_function :test_untagged_nonint_enum, [:uint8], :uint8 attach_function :test_tagged_nonint_enum1, [:uint16], :uint16 attach_function :test_tagged_nonint_enum2, [:uint32], :uint32 attach_function :test_tagged_nonint_enum3, [:uint64], :uint64 + + enum FFI::Type::UINT16, :enum_type4, [:c17, 0x42, :c18, :c19, :c20] + enum FFI::Type::UINT32, :enum_type5, [:c21, 0x42, :c22, :c23, 0x4242, :c24] + enum FFI::Type::UINT64, :enum_type6, [:c25, 0x42, :c26, 0x4242, :c27, 0x42424242, :c28, 0x4242424242424242] + enum FFI::Type::UINT64, [:c29, 0x4242424242424242, :c30, :c31, :c32] + attach_function :test_tagged_nonint_enum4, :test_tagged_nonint_enum1, [:enum_type4], :enum_type4 attach_function :test_tagged_nonint_enum5, :test_tagged_nonint_enum2, [:enum_type5], :enum_type5 attach_function :test_tagged_nonint_enum6, :test_tagged_nonint_enum3, [:enum_type6], :enum_type6 + + freeze end describe "A library with no enum defined" do diff --git a/spec/ffi/function_spec.rb b/spec/ffi/function_spec.rb index badd240..77d09d8 100644 --- a/spec/ffi/function_spec.rb +++ b/spec/ffi/function_spec.rb @@ -52,7 +52,7 @@ describe FFI::Function do a + b end - it "should be shareable for Ractor", :ractor do + it "can be made shareable for Ractor", :ractor do add = FFI::Function.new(:int, [:int, :int], &method(:adder)) Ractor.make_shareable(add) diff --git a/spec/ffi/library_spec.rb b/spec/ffi/library_spec.rb index 52a961f..1b8e8c1 100644 --- a/spec/ffi/library_spec.rb +++ b/spec/ffi/library_spec.rb @@ -8,6 +8,7 @@ require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper")) module TestEnumValueRactor extend FFI::Library enum :something, [:one, :two] + freeze end describe "Library" do @@ -197,28 +198,46 @@ describe "Library" do end.getpid).to eq(Process.pid) }.to raise_error(LoadError) end + end - it "attach_function :bool_return_true from [ File.expand_path(#{TestLibrary::PATH.inspect}) ]" do - mod = Module.new do |m| - m.extend FFI::Library - ffi_lib File.expand_path(TestLibrary::PATH) - attach_function :bool_return_true, [ ], :bool - end - expect(mod.bool_return_true).to be true + it "attach_function :bool_return_true from [ File.expand_path(#{TestLibrary::PATH.inspect}) ]" do + mod = Module.new do |m| + m.extend FFI::Library + ffi_lib File.expand_path(TestLibrary::PATH) + attach_function :bool_return_true, [ ], :bool 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 + it "can define a foo! function" do + mod = Module.new do |m| + m.extend FFI::Library + ffi_lib File.expand_path(TestLibrary::PATH) + attach_function :foo!, :bool_return_true, [], :bool + end + expect(mod.foo!).to be true + end + + it "can define a foo? function" do + mod = Module.new do |m| + m.extend FFI::Library + ffi_lib File.expand_path(TestLibrary::PATH) + attach_function :foo?, :bool_return_true, [], :bool + end + expect(mod.foo?).to be true + 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"].return_type).to eq(FFI::Type::BOOL) + 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].return_type).to eq(FFI::Type::BOOL) end def gvar_lib(name, type) @@ -355,7 +374,7 @@ describe "Library" do ffi_lib TestLibrary::PATH attach_variable :gvari, "gvar_gstruct", GlobalStruct end - expect(lib.attached_variables).to eq({ "gvari" => GlobalStruct }) + expect(lib.attached_variables).to eq({ gvari: GlobalStruct }) end it "can reveal its attached global variables" do @@ -364,7 +383,7 @@ describe "Library" do ffi_lib TestLibrary::PATH attach_variable :gvaro, "gvar_u32", :uint32 end - expect(lib.attached_variables).to eq({ "gvaro" => FFI::Type::UINT32 }) + expect(lib.attached_variables).to eq({ gvaro: FFI::Type::UINT32 }) end it "should have shareable constants for Ractor", :ractor do diff --git a/spec/ffi/struct_by_ref_spec.rb b/spec/ffi/struct_by_ref_spec.rb index 0c01137..f664937 100644 --- a/spec/ffi/struct_by_ref_spec.rb +++ b/spec/ffi/struct_by_ref_spec.rb @@ -41,7 +41,7 @@ describe FFI::Struct, ' by_ref' do end it "can reveal the mapped type converter" do - param_type = @api.attached_functions["struct_test"].param_types[0] + param_type = @api.attached_functions[:struct_test].param_types[0] expect(param_type).to be_a(FFI::Type::Mapped) expect(param_type.converter).to be_a(FFI::StructByReference) end diff --git a/spec/ffi/struct_spec.rb b/spec/ffi/struct_spec.rb index d09b7b6..ab62da1 100644 --- a/spec/ffi/struct_spec.rb +++ b/spec/ffi/struct_spec.rb @@ -545,7 +545,7 @@ module StructSpecsStructTests expect(b.members).to eq([:a, :b]) end - it "should be shareable for Ractor", :ractor do + it "can be made shareable for Ractor", :ractor do a = Class.new(FFI::Struct) do layout :a, :char end.new @@ -1109,6 +1109,20 @@ describe "Struct memsize functions", skip: RUBY_ENGINE != "ruby" do size = ObjectSpace.memsize_of(layout[:pointer]) expect(size).to be > base_size end + + it "StructLayout should be shareable with Ractor", :ractor do + kl = Class.new(FFI::Struct) do + layout :ptr, :pointer + end + expect(Ractor.shareable?(kl.layout)).to eq(true) + end + + it "StructField should be shareable with Ractor", :ractor do + kl = Class.new(FFI::Struct) do + layout :ptr, :pointer + end + expect(Ractor.shareable?(kl.layout[:ptr])).to eq(true) + end end diff --git a/spec/ffi/type_spec.rb b/spec/ffi/type_spec.rb index eb48a43..e880c83 100644 --- a/spec/ffi/type_spec.rb +++ b/spec/ffi/type_spec.rb @@ -5,7 +5,7 @@ require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper")) -describe "FFI::Type" do +describe FFI::Type do it 'has a memsize function', skip: RUBY_ENGINE != "ruby" do base_size = ObjectSpace.memsize_of(Object.new) @@ -40,4 +40,37 @@ describe "FFI::Type" do size = ObjectSpace.memsize_of(FFI::Type::Builtin::CHAR) expect(size).to be > base_size end + + it "should be shareable with Ractor", :ractor do + expect(Ractor.shareable?(FFI::Type.new(5))).to eq(true) + end + + describe :Builtin do + it "should be shareable with Ractor", :ractor do + expect(Ractor.shareable?(FFI::Type::INT32)).to eq(true) + end + end + + describe :Mapped do + it "should be shareable with Ractor", :ractor do + converter = Module.new do + extend FFI::DataConverter + + def self.native_type + FFI::Type::INT32 + end + + def self.to_native(val, ctx) + ToNativeMap[val] + end + + def self.from_native(val, ctx) + FromNativeMap[val] + end + end + expect(Ractor.shareable?(converter)).to eq(true) + type = FFI::Type::Mapped.new(converter) + expect(Ractor.shareable?(type)).to eq(true) + end + end end diff --git a/spec/ffi/variadic_spec.rb b/spec/ffi/variadic_spec.rb index 917212a..21a803f 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 -- cgit v1.2.1