diff options
-rw-r--r-- | ext/fiddle/closure.c | 55 | ||||
-rw-r--r-- | ext/fiddle/lib/fiddle/closure.rb | 25 | ||||
-rw-r--r-- | test/fiddle/test_closure.rb | 98 |
3 files changed, 131 insertions, 47 deletions
diff --git a/ext/fiddle/closure.c b/ext/fiddle/closure.c index 2d340297d5..694ef90b7f 100644 --- a/ext/fiddle/closure.c +++ b/ext/fiddle/closure.c @@ -224,6 +224,17 @@ allocate(VALUE klass) return i; } +static fiddle_closure * +get_raw(VALUE self) +{ + fiddle_closure *closure; + TypedData_Get_Struct(self, fiddle_closure, &closure_data_type, closure); + if (!closure) { + rb_raise(rb_eArgError, "already freed: %+"PRIsVALUE, self); + } + return closure; +} + static VALUE initialize(int rbargc, VALUE argv[], VALUE self) { @@ -293,14 +304,28 @@ initialize(int rbargc, VALUE argv[], VALUE self) static VALUE to_i(VALUE self) { - fiddle_closure * cl; - void *code; - - TypedData_Get_Struct(self, fiddle_closure, &closure_data_type, cl); + fiddle_closure *closure = get_raw(self); + return PTR2NUM(closure->code); +} - code = cl->code; +static VALUE +closure_free(VALUE self) +{ + fiddle_closure *closure; + TypedData_Get_Struct(self, fiddle_closure, &closure_data_type, closure); + if (closure) { + dealloc(closure); + RTYPEDDATA_DATA(self) = NULL; + } + return RUBY_Qnil; +} - return PTR2NUM(code); +static VALUE +closure_freed_p(VALUE self) +{ + fiddle_closure *closure; + TypedData_Get_Struct(self, fiddle_closure, &closure_data_type, closure); + return closure ? RUBY_Qfalse : RUBY_Qtrue; } void @@ -353,8 +378,24 @@ Init_fiddle_closure(void) /* * Document-method: to_i * - * Returns the memory address for this closure + * Returns the memory address for this closure. */ rb_define_method(cFiddleClosure, "to_i", to_i, 0); + + /* + * Document-method: free + * + * Free this closure explicitly. You can't use this closure anymore. + * + * If this closure is already freed, this does nothing. + */ + rb_define_method(cFiddleClosure, "free", closure_free, 0); + + /* + * Document-method: freed? + * + * Whether this closure was freed explicitly. + */ + rb_define_method(cFiddleClosure, "freed?", closure_freed_p, 0); } /* vim: set noet sw=4 sts=4 */ diff --git a/ext/fiddle/lib/fiddle/closure.rb b/ext/fiddle/lib/fiddle/closure.rb index c865a63c20..7e0077ea52 100644 --- a/ext/fiddle/lib/fiddle/closure.rb +++ b/ext/fiddle/lib/fiddle/closure.rb @@ -1,6 +1,31 @@ # frozen_string_literal: true module Fiddle class Closure + class << self + # Create a new closure. If a block is given, the created closure + # is automatically freed after the given block is executed. + # + # The all given arguments are passed to Fiddle::Closure.new. So + # using this method without block equals to Fiddle::Closure.new. + # + # == Example + # + # Fiddle::Closure.create(TYPE_INT, [TYPE_INT]) do |closure| + # # closure is freed automatically when this block is finished. + # end + def create(*args) + if block_given? + closure = new(*args) + begin + yield(closure) + ensure + closure.free + end + else + new(*args) + end + end + end # the C type of the return of the FFI closure attr_reader :ctype diff --git a/test/fiddle/test_closure.rb b/test/fiddle/test_closure.rb index 1a7c41fd95..d248b9cbdd 100644 --- a/test/fiddle/test_closure.rb +++ b/test/fiddle/test_closure.rb @@ -29,37 +29,40 @@ module Fiddle end def test_type_symbol - closure = Closure.new(:int, [:void]) - assert_equal([ - TYPE_INT, - [TYPE_VOID], - ], - [ - closure.instance_variable_get(:@ctype), - closure.instance_variable_get(:@args), - ]) + Closure.create(:int, [:void]) do |closure| + assert_equal([ + TYPE_INT, + [TYPE_VOID], + ], + [ + closure.instance_variable_get(:@ctype), + closure.instance_variable_get(:@args), + ]) + end end def test_call - closure = Class.new(Closure) { + closure_class = Class.new(Closure) do def call 10 end - }.new(TYPE_INT, []) - - func = Function.new(closure, [], TYPE_INT) - assert_equal 10, func.call + end + closure_class.create(TYPE_INT, []) do |closure| + func = Function.new(closure, [], TYPE_INT) + assert_equal 10, func.call + end end def test_returner - closure = Class.new(Closure) { + closure_class = Class.new(Closure) do def call thing thing end - }.new(TYPE_INT, [TYPE_INT]) - - func = Function.new(closure, [TYPE_INT], TYPE_INT) - assert_equal 10, func.call(10) + end + closure_class.create(TYPE_INT, [TYPE_INT]) do |closure| + func = Function.new(closure, [TYPE_INT], TYPE_INT) + assert_equal 10, func.call(10) + end end def test_const_string @@ -69,18 +72,35 @@ module Fiddle @return_string end end - closure = closure_class.new(:const_string, [:const_string]) + closure_class.create(:const_string, [:const_string]) do |closure| + func = Function.new(closure, [:const_string], :const_string) + assert_equal("Hello! World!", func.call("World!")) + end + end - func = Function.new(closure, [:const_string], :const_string) - assert_equal("Hello! World!", func.call("World!")) + def test_free + Closure.create(:int, [:void]) do |closure| + assert do + not closure.freed? + end + closure.free + assert do + closure.freed? + end + closure.free + end end def test_block_caller cb = Closure::BlockCaller.new(TYPE_INT, [TYPE_INT]) do |one| one end - func = Function.new(cb, [TYPE_INT], TYPE_INT) - assert_equal 11, func.call(11) + begin + func = Function.new(cb, [TYPE_INT], TYPE_INT) + assert_equal 11, func.call(11) + ensure + cb.free + end end def test_memsize @@ -97,23 +117,21 @@ module Fiddle define_method("test_conversion_#{n.downcase}") do arg = nil - clos = Class.new(Closure) do + closure_class = Class.new(Closure) do define_method(:call) {|x| arg = x} - end.new(t, [t]) - - v = ~(~0 << (8*s)) - - arg = nil - assert_equal(v, clos.call(v)) - assert_equal(arg, v, n) - - arg = nil - func = Function.new(clos, [t], t) - assert_equal(v, func.call(v)) - assert_equal(arg, v, n) - ensure - clos = nil - func = nil + end + closure_class.create(t, [t]) do |closure| + v = ~(~0 << (8*s)) + + arg = nil + assert_equal(v, closure.call(v)) + assert_equal(arg, v, n) + + arg = nil + func = Function.new(closure, [t], t) + assert_equal(v, func.call(v)) + assert_equal(arg, v, n) + end end end end |