diff --git a/ext/fiddle/pointer.c b/ext/fiddle/pointer.c index 1e93a5ab..6a6c97b0 100644 --- a/ext/fiddle/pointer.c +++ b/ext/fiddle/pointer.c @@ -109,13 +109,13 @@ rb_fiddle_ptr_new(void *ptr, long size, freefunc_t func) } static VALUE -rb_fiddle_ptr_malloc(long size, freefunc_t func) +rb_fiddle_ptr_malloc(VALUE klass, long size, freefunc_t func) { void *ptr; ptr = ruby_xmalloc((size_t)size); memset(ptr,0,(size_t)size); - return rb_fiddle_ptr_new(ptr, size, func); + return rb_fiddle_ptr_new2(klass, ptr, size, func); } static void * @@ -268,7 +268,7 @@ rb_fiddle_ptr_s_malloc(int argc, VALUE argv[], VALUE klass) rb_bug("rb_fiddle_ptr_s_malloc"); } - obj = rb_fiddle_ptr_malloc(s,f); + obj = rb_fiddle_ptr_malloc(klass, s,f); if (wrap) RPTR_DATA(obj)->wrap[1] = wrap; if (rb_block_given_p()) { diff --git a/lib/fiddle/struct.rb b/lib/fiddle/struct.rb index 259903d2..318e8314 100644 --- a/lib/fiddle/struct.rb +++ b/lib/fiddle/struct.rb @@ -4,7 +4,7 @@ require 'fiddle/pack' module Fiddle - # C struct shell + # A base class for objects representing a C structure class CStruct # accessor to Fiddle::CStructEntity def CStruct.entity_class @@ -12,7 +12,7 @@ def CStruct.entity_class end end - # C union shell + # A base class for objects representing a C union class CUnion # accessor to Fiddle::CUnionEntity def CUnion.entity_class @@ -62,7 +62,7 @@ module CStructBuilder # Fiddle::Importer#struct and Fiddle::Importer#union wrap this functionality in an # easy-to-use manner. # - # Example: + # Examples: # # require 'fiddle/struct' # require 'fiddle/cparser' @@ -73,6 +73,17 @@ module CStructBuilder # # MyStruct = Fiddle::CStructBuilder.create(Fiddle::CUnion, types, members) # + # MyStruct.malloc(Fiddle::RUBY_FREE) do |obj| + # ... + # end + # + # obj = MyStruct.malloc(Fiddle::RUBY_FREE) + # begin + # ... + # ensure + # obj.call_free + # end + # # obj = MyStruct.malloc # begin # ... @@ -82,8 +93,12 @@ module CStructBuilder # def create(klass, types, members) new_class = Class.new(klass){ - define_method(:initialize){|addr| - @entity = klass.entity_class.new(addr, types) + define_method(:initialize){|addr, func = nil| + if addr.is_a?(self.class.entity_class) + @entity = addr + else + @entity = self.class.entity_class.new(addr, types, func) + end @entity.assign_names(members) } define_method(:[]) { |*args| @entity.send(:[], *args) } @@ -94,23 +109,24 @@ def create(klass, types, members) define_method(name){ @entity[name] } define_method(name + "="){|val| @entity[name] = val } } - } - size = klass.entity_class.size(types) - new_class.module_eval(<<-EOS, __FILE__, __LINE__+1) - def new_class.size() - #{size} - end - def new_class.malloc() - addr = Fiddle.malloc(#{size}) - new(addr) + size = klass.entity_class.size(types) + define_singleton_method(:size) { size } + define_singleton_method(:malloc) do |func=nil| + if block_given? + entity_class.malloc(types, func, size) do |entity| + yield new(entity) + end + else + new(entity_class.malloc(types, func, size)) + end end - EOS + } return new_class end module_function :create end - # A C struct wrapper + # A pointer to a C structure class CStructEntity < Fiddle::Pointer include PackInfo include ValueUtil @@ -118,9 +134,17 @@ class CStructEntity < Fiddle::Pointer # Allocates a C struct with the +types+ provided. # # See Fiddle::Pointer.malloc for memory management issues. - def CStructEntity.malloc(types, func = nil) - addr = Fiddle.malloc(CStructEntity.size(types)) - CStructEntity.new(addr, types, func) + def CStructEntity.malloc(types, func = nil, size = size(types), &block) + if block_given? + super(size, func) do |struct| + struct.set_ctypes types + yield struct + end + else + struct = super(size, func) + struct.set_ctypes types + struct + end end # Returns the offset for the packed sizes for the given +types+. @@ -152,6 +176,9 @@ def CStructEntity.size(types) # # See also Fiddle::Pointer.new def initialize(addr, types, func = nil) + if func && addr.is_a?(Pointer) && addr.free + raise ArgumentError, 'free function specified on both underlying struct Pointer and when creating a CStructEntity - who do you want to free this?' + end set_ctypes(types) super(addr, @size, func) end @@ -261,23 +288,16 @@ def []=(*args) end end + undef_method :size= def to_s() # :nodoc: super(@size) end end - # A C union wrapper + # A pointer to a C union class CUnionEntity < CStructEntity include PackInfo - # Allocates a C union the +types+ provided. - # - # See Fiddle::Pointer.malloc for memory management issues. - def CUnionEntity.malloc(types, func=nil) - addr = Fiddle.malloc(CUnionEntity.size(types)) - CUnionEntity.new(addr, types, func) - end - # Returns the size needed for the union with the given +types+. # # Fiddle::CUnionEntity.size( @@ -300,4 +320,3 @@ def set_ctypes(types) end end end - diff --git a/test/fiddle/test_c_struct_entry.rb b/test/fiddle/test_c_struct_entry.rb index 9ce75cf0..9fd16d71 100644 --- a/test/fiddle/test_c_struct_entry.rb +++ b/test/fiddle/test_c_struct_entry.rb @@ -43,37 +43,122 @@ def test_class_size_with_count end def test_set_ctypes - union = CStructEntity.malloc [TYPE_INT, TYPE_LONG], Fiddle::RUBY_FREE - union.assign_names %w[int long] + CStructEntity.malloc([TYPE_INT, TYPE_LONG], Fiddle::RUBY_FREE) do |struct| + struct.assign_names %w[int long] - # this test is roundabout because the stored ctypes are not accessible - union['long'] = 1 - union['int'] = 2 + # this test is roundabout because the stored ctypes are not accessible + struct['long'] = 1 + struct['int'] = 2 - assert_equal 1, union['long'] - assert_equal 2, union['int'] + assert_equal 1, struct['long'] + assert_equal 2, struct['int'] + end end def test_aref_pointer_array - team = CStructEntity.malloc([[TYPE_VOIDP, 2]], Fiddle::RUBY_FREE) - team.assign_names(["names"]) - Fiddle::Pointer.malloc(6, Fiddle::RUBY_FREE) do |alice| - alice[0, 6] = "Alice\0" - Fiddle::Pointer.malloc(4, Fiddle::RUBY_FREE) do |bob| - bob[0, 4] = "Bob\0" - team["names"] = [alice, bob] - assert_equal(["Alice", "Bob"], team["names"].map(&:to_s)) + CStructEntity.malloc([[TYPE_VOIDP, 2]], Fiddle::RUBY_FREE) do |team| + team.assign_names(["names"]) + Fiddle::Pointer.malloc(6, Fiddle::RUBY_FREE) do |alice| + alice[0, 6] = "Alice\0" + Fiddle::Pointer.malloc(4, Fiddle::RUBY_FREE) do |bob| + bob[0, 4] = "Bob\0" + team["names"] = [alice, bob] + assert_equal(["Alice", "Bob"], team["names"].map(&:to_s)) + end end end end def test_aref_pointer - user = CStructEntity.malloc([TYPE_VOIDP], Fiddle::RUBY_FREE) - user.assign_names(["name"]) - Fiddle::Pointer.malloc(6, Fiddle::RUBY_FREE) do |alice| - alice[0, 6] = "Alice\0" - user["name"] = alice - assert_equal("Alice", user["name"].to_s) + CStructEntity.malloc([TYPE_VOIDP], Fiddle::RUBY_FREE) do |user| + user.assign_names(["name"]) + Fiddle::Pointer.malloc(6, Fiddle::RUBY_FREE) do |alice| + alice[0, 6] = "Alice\0" + user["name"] = alice + assert_equal("Alice", user["name"].to_s) + end + end + end + + def test_new_double_free + types = [TYPE_INT] + Pointer.malloc(CStructEntity.size(types), Fiddle::RUBY_FREE) do |pointer| + assert_raise ArgumentError do + CStructEntity.new(pointer, types, Fiddle::RUBY_FREE) + end + end + end + + def test_malloc_block + escaped_struct = nil + returned = CStructEntity.malloc([TYPE_INT], Fiddle::RUBY_FREE) do |struct| + assert_equal Fiddle::SIZEOF_INT, struct.size + assert_equal Fiddle::RUBY_FREE, struct.free.to_i + escaped_struct = struct + :returned + end + assert_equal :returned, returned + assert escaped_struct.freed? + end + + def test_malloc_block_no_free + assert_raise ArgumentError do + CStructEntity.malloc([TYPE_INT]) { |struct| } + end + end + + def test_free + struct = CStructEntity.malloc([TYPE_INT]) + begin + assert_nil struct.free + ensure + Fiddle.free struct + end + end + + def test_free_with_func + struct = CStructEntity.malloc([TYPE_INT], Fiddle::RUBY_FREE) + refute struct.freed? + struct.call_free + assert struct.freed? + struct.call_free # you can safely run it again + assert struct.freed? + GC.start # you can safely run the GC routine + assert struct.freed? + end + + def test_free_with_no_func + struct = CStructEntity.malloc([TYPE_INT]) + refute struct.freed? + struct.call_free + refute struct.freed? + struct.call_free # you can safely run it again + refute struct.freed? + end + + def test_freed? + struct = CStructEntity.malloc([TYPE_INT], Fiddle::RUBY_FREE) + refute struct.freed? + struct.call_free + assert struct.freed? + end + + def test_null? + struct = CStructEntity.malloc([TYPE_INT], Fiddle::RUBY_FREE) + refute struct.null? + end + + def test_size + CStructEntity.malloc([TYPE_INT], Fiddle::RUBY_FREE) do |struct| + assert_equal Fiddle::SIZEOF_INT, struct.size + end + end + + def test_size= + CStructEntity.malloc([TYPE_INT], Fiddle::RUBY_FREE) do |struct| + assert_raise NoMethodError do + struct.size = 1 + end end end end diff --git a/test/fiddle/test_c_union_entity.rb b/test/fiddle/test_c_union_entity.rb index 93100847..e0a37575 100644 --- a/test/fiddle/test_c_union_entity.rb +++ b/test/fiddle/test_c_union_entity.rb @@ -21,15 +21,16 @@ def test_class_size_with_count end def test_set_ctypes - union = CUnionEntity.malloc [TYPE_INT, TYPE_LONG], Fiddle::RUBY_FREE - union.assign_names %w[int long] + CUnionEntity.malloc([TYPE_INT, TYPE_LONG], Fiddle::RUBY_FREE) do |union| + union.assign_names %w[int long] - # this test is roundabout because the stored ctypes are not accessible - union['long'] = 1 - assert_equal 1, union['long'] + # this test is roundabout because the stored ctypes are not accessible + union['long'] = 1 + assert_equal 1, union['long'] - union['int'] = 1 - assert_equal 1, union['int'] + union['int'] = 1 + assert_equal 1, union['int'] + end end end end if defined?(Fiddle) diff --git a/test/fiddle/test_import.rb b/test/fiddle/test_import.rb index cfa06aa9..ccf8c11c 100644 --- a/test/fiddle/test_import.rb +++ b/test/fiddle/test_import.rb @@ -56,22 +56,18 @@ def test_ensure_call_dlload def test_struct_memory_access() # check memory operations performed directly on struct - my_struct = Fiddle::Importer.struct(['int id']).malloc - begin + Fiddle::Importer.struct(['int id']).malloc(Fiddle::RUBY_FREE) do |my_struct| my_struct[0, Fiddle::SIZEOF_INT] = "\x01".b * Fiddle::SIZEOF_INT assert_equal 0x01010101, my_struct.id my_struct.id = 0 assert_equal "\x00".b * Fiddle::SIZEOF_INT, my_struct[0, Fiddle::SIZEOF_INT] - ensure - Fiddle.free my_struct.to_ptr end end def test_struct_ptr_array_subscript_multiarg() # check memory operations performed on struct#to_ptr - struct = Fiddle::Importer.struct([ 'int x' ]).malloc - begin + Fiddle::Importer.struct([ 'int x' ]).malloc(Fiddle::RUBY_FREE) do |struct| ptr = struct.to_ptr struct.x = 0x02020202 @@ -79,33 +75,22 @@ def test_struct_ptr_array_subscript_multiarg() ptr[0, Fiddle::SIZEOF_INT] = "\x01".b * Fiddle::SIZEOF_INT assert_equal 0x01010101, struct.x - ensure - Fiddle.free struct.to_ptr end end def test_malloc() - s1 = LIBC::Timeval.malloc() - begin - s2 = LIBC::Timeval.malloc() - begin + LIBC::Timeval.malloc(Fiddle::RUBY_FREE) do |s1| + LIBC::Timeval.malloc(Fiddle::RUBY_FREE) do |s2| refute_equal(s1.to_ptr.to_i, s2.to_ptr.to_i) - ensure - Fiddle.free s2.to_ptr end - ensure - Fiddle.free s1.to_ptr end end def test_sizeof() assert_equal(SIZEOF_VOIDP, LIBC.sizeof("FILE*")) assert_equal(LIBC::MyStruct.size(), LIBC.sizeof(LIBC::MyStruct)) - my_struct = LIBC::MyStruct.malloc() - begin + LIBC::MyStruct.malloc(Fiddle::RUBY_FREE) do |my_struct| assert_equal(LIBC::MyStruct.size(), LIBC.sizeof(my_struct)) - ensure - Fiddle.free my_struct.to_ptr end assert_equal(SIZEOF_LONG_LONG, LIBC.sizeof("long long")) if defined?(SIZEOF_LONG_LONG) end @@ -151,8 +136,7 @@ def test_value() end def test_struct_array_assignment() - instance = Fiddle::Importer.struct(["unsigned int stages[3]"]).malloc - begin + Fiddle::Importer.struct(["unsigned int stages[3]"]).malloc(Fiddle::RUBY_FREE) do |instance| instance.stages[0] = 1024 instance.stages[1] = 10 instance.stages[2] = 100 @@ -163,39 +147,28 @@ def test_struct_array_assignment() instance.to_ptr[0, 3 * Fiddle::SIZEOF_INT] assert_raise(IndexError) { instance.stages[-1] = 5 } assert_raise(IndexError) { instance.stages[3] = 5 } - ensure - Fiddle.free instance.to_ptr end end def test_struct() - s = LIBC::MyStruct.malloc() - begin + LIBC::MyStruct.malloc(Fiddle::RUBY_FREE) do |s| s.num = [0,1,2,3,4] s.c = ?a.ord s.buff = "012345\377" assert_equal([0,1,2,3,4], s.num) assert_equal(?a.ord, s.c) assert_equal([?0.ord,?1.ord,?2.ord,?3.ord,?4.ord,?5.ord,?\377.ord], s.buff) - ensure - Fiddle.free s.to_ptr end end def test_gettimeofday() if( defined?(LIBC.gettimeofday) ) - timeval = LIBC::Timeval.malloc() - begin - timezone = LIBC::Timezone.malloc() - begin + LIBC::Timeval.malloc(Fiddle::RUBY_FREE) do |timeval| + LIBC::Timezone.malloc(Fiddle::RUBY_FREE) do |timezone| LIBC.gettimeofday(timeval, timezone) - ensure - Fiddle.free timezone.to_ptr end cur = Time.now() assert(cur.to_i - 2 <= timeval.tv_sec && timeval.tv_sec <= cur.to_i) - ensure - Fiddle.free timeval.to_ptr end end end diff --git a/test/fiddle/test_pointer.rb b/test/fiddle/test_pointer.rb index 865b308d..e685fea5 100644 --- a/test/fiddle/test_pointer.rb +++ b/test/fiddle/test_pointer.rb @@ -50,6 +50,13 @@ def test_malloc_block_no_free end end + def test_malloc_subclass + subclass = Class.new(Pointer) + subclass.malloc(10, Fiddle::RUBY_FREE) do |ptr| + assert ptr.is_a?(subclass) + end + end + def test_to_str str = Marshal.load(Marshal.dump("hello world")) ptr = Pointer[str]