From be5ef1134a14ccb4cb31c2e680493bdde6315ea4 Mon Sep 17 00:00:00 2001 From: Colin MacKenzie IV Date: Tue, 21 Jan 2020 21:30:00 -0500 Subject: [PATCH 01/37] Allow access to a struct's underlying memory with `struct[offset, length]`. --- test/fiddle/test_import.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/fiddle/test_import.rb b/test/fiddle/test_import.rb index 88087048..218be5c7 100644 --- a/test/fiddle/test_import.rb +++ b/test/fiddle/test_import.rb @@ -157,6 +157,15 @@ def test_struct_array_assignment() end end + def test_struct_array_subscript_multiarg() + Fiddle::Importer.struct([ 'int x' ]).malloc do |struct| + assert_equal("\x00".b * Fiddle::SIZEOF_INT, struct.to_ptr[0, Fiddle::SIZEOF_INT]) + + struct.to_ptr[0, Fiddle::SIZEOF_INT] = "\x01".b * Fiddle::SIZEOF_INT + assert_equal 16843009, struct.x + end + end + def test_struct() LIBC::MyStruct.malloc(Fiddle::RUBY_FREE) do |s| s.num = [0,1,2,3,4] From 86471b52d44863244cc8979a383e06e0d931a662 Mon Sep 17 00:00:00 2001 From: Colin MacKenzie IV Date: Mon, 29 Oct 2018 21:44:01 -0400 Subject: [PATCH 02/37] Make accessing a struct's underlying memory more convenient. --- test/fiddle/test_import.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/fiddle/test_import.rb b/test/fiddle/test_import.rb index 218be5c7..d4545b28 100644 --- a/test/fiddle/test_import.rb +++ b/test/fiddle/test_import.rb @@ -57,6 +57,9 @@ def test_ensure_call_dlload def test_struct_memory_access() # check memory operations performed directly on struct Fiddle::Importer.struct(['int id']).malloc(Fiddle::RUBY_FREE) do |my_struct| + my_struct['id'] = 1 + assert_equal 1, my_struct.id + my_struct[0, Fiddle::SIZEOF_INT] = "\x01".b * Fiddle::SIZEOF_INT assert_equal 0x01010101, my_struct.id From 34cdbc914e5bcab24be59c63806e7cd3f8909c63 Mon Sep 17 00:00:00 2001 From: Colin MacKenzie IV Date: Tue, 21 Jan 2020 22:17:37 -0500 Subject: [PATCH 03/37] refactor memory access unit tests for improved clarity --- test/fiddle/test_import.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/fiddle/test_import.rb b/test/fiddle/test_import.rb index d4545b28..bda2ad51 100644 --- a/test/fiddle/test_import.rb +++ b/test/fiddle/test_import.rb @@ -81,6 +81,18 @@ def test_struct_ptr_array_subscript_multiarg() end end + def test_struct_ptr_array_subscript_multiarg() + # check memory operations performed on struct#to_ptr + struct = Fiddle::Importer.struct([ 'int x' ]).malloc + ptr = struct.to_ptr + + struct.x = 0x02020202 + assert_equal("\x02".b * Fiddle::SIZEOF_INT, ptr[0, Fiddle::SIZEOF_INT]) + + ptr[0, Fiddle::SIZEOF_INT] = "\x01".b * Fiddle::SIZEOF_INT + assert_equal 0x01010101, struct.x + end + def test_malloc() LIBC::Timeval.malloc(Fiddle::RUBY_FREE) do |s1| LIBC::Timeval.malloc(Fiddle::RUBY_FREE) do |s2| From cb52c5ef3cded08b34eae01c2fc0aa6defff4ddd Mon Sep 17 00:00:00 2001 From: Colin MacKenzie IV Date: Tue, 21 Jan 2020 21:32:51 -0500 Subject: [PATCH 04/37] fix assignment to elements in a member which is an array --- lib/fiddle/struct.rb | 2 +- test/fiddle/test_import.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/fiddle/struct.rb b/lib/fiddle/struct.rb index 318e8314..dfe18ec4 100644 --- a/lib/fiddle/struct.rb +++ b/lib/fiddle/struct.rb @@ -42,7 +42,7 @@ def []=(index, value) raise IndexError, 'index %d outside of array bounds 0...%d' % [index, size] end - to_ptr[index * @size, @size] = [value].pack(@pack_format) + to_ptr[index * @align, @size] = [value].pack(@pack_format) super(index, value) end end diff --git a/test/fiddle/test_import.rb b/test/fiddle/test_import.rb index bda2ad51..b236b1ea 100644 --- a/test/fiddle/test_import.rb +++ b/test/fiddle/test_import.rb @@ -173,7 +173,7 @@ def test_struct_array_assignment() end def test_struct_array_subscript_multiarg() - Fiddle::Importer.struct([ 'int x' ]).malloc do |struct| + Fiddle::Importer.struct([ 'int x' ]).malloc(Fiddle::RUBY_FREE) do |struct| assert_equal("\x00".b * Fiddle::SIZEOF_INT, struct.to_ptr[0, Fiddle::SIZEOF_INT]) struct.to_ptr[0, Fiddle::SIZEOF_INT] = "\x01".b * Fiddle::SIZEOF_INT From 633be4daf4a6273c508f24461dc8745f29a134e1 Mon Sep 17 00:00:00 2001 From: Colin MacKenzie IV Date: Tue, 21 Jan 2020 21:32:51 -0500 Subject: [PATCH 05/37] fix assignment to elements in a member which is an array --- test/fiddle/test_import.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/fiddle/test_import.rb b/test/fiddle/test_import.rb index b236b1ea..3337a65a 100644 --- a/test/fiddle/test_import.rb +++ b/test/fiddle/test_import.rb @@ -181,6 +181,16 @@ def test_struct_array_subscript_multiarg() end end + def test_struct_array_assignment() + instance = Fiddle::Importer.struct(["unsigned int stages[1]"]).malloc + instance.stages[0] = 1024 + assert_equal 1024, instance.stages[0] + assert_equal [1024].pack(Fiddle::PackInfo::PACK_MAP[-Fiddle::TYPE_INT]), + instance.to_ptr[0, Fiddle::SIZEOF_INT] + assert_raise(RangeError) { instance.stages[-1] = 5 } + assert_raise(RangeError) { instance.stages[2] = 5 } + end + def test_struct() LIBC::MyStruct.malloc(Fiddle::RUBY_FREE) do |s| s.num = [0,1,2,3,4] From 4c304b86e073e22fd114e00f103d3dba07a36a37 Mon Sep 17 00:00:00 2001 From: Colin MacKenzie IV Date: Tue, 21 Jan 2020 21:37:52 -0500 Subject: [PATCH 06/37] support nested structs and arrays of nested structs --- ext/fiddle/pointer.c | 22 +++++++++ lib/fiddle/cparser.rb | 17 ++++++- lib/fiddle/struct.rb | 75 +++++++++++++++++++++++----- test/fiddle/test_cparser.rb | 21 ++++++++ test/fiddle/test_import.rb | 97 +++++++++++++++++++++++++++++++++++++ test/fiddle/test_pointer.rb | 16 +++++- 6 files changed, 235 insertions(+), 13 deletions(-) diff --git a/ext/fiddle/pointer.c b/ext/fiddle/pointer.c index e13e1520..764cd799 100644 --- a/ext/fiddle/pointer.c +++ b/ext/fiddle/pointer.c @@ -711,6 +711,27 @@ rb_fiddle_ptr_size_get(VALUE self) return LONG2NUM(RPTR_DATA(self)->size); } +/* + * call-seq: memcpy + * + * Copies the contents of the given pointer into this one. Will copy up to the + * allocated size of the given pointer or the allocated size of this one, + * whichever is smaller (to prevent overflow). + * + * Returns the number of bytes actually copied. + */ +static VALUE +rb_fiddle_ptr_memcpy(VALUE self, VALUE other) +{ + long self_size = NUM2LONG(rb_funcall(self, rb_intern("size"), 0)); + long other_size = NUM2LONG(rb_funcall(other, rb_intern("size"), 0)); + void *self_ptr = NUM2PTR(rb_fiddle_ptr_to_i(self)); + void *other_ptr = NUM2PTR(rb_fiddle_ptr_to_i(other)); + long size = self_size > other_size ? other_size : self_size; + memcpy(self_ptr, other_ptr, size); + return LONG2NUM(size); +} + /* * call-seq: * Fiddle::Pointer[val] => cptr @@ -794,6 +815,7 @@ Init_fiddle_pointer(void) rb_define_method(rb_cPointer, "[]=", rb_fiddle_ptr_aset, -1); rb_define_method(rb_cPointer, "size", rb_fiddle_ptr_size_get, 0); rb_define_method(rb_cPointer, "size=", rb_fiddle_ptr_size_set, 1); + rb_define_method(rb_cPointer, "memcpy", rb_fiddle_ptr_memcpy, 1); /* Document-const: NULL * diff --git a/lib/fiddle/cparser.rb b/lib/fiddle/cparser.rb index fdab7cc2..39f56178 100644 --- a/lib/fiddle/cparser.rb +++ b/lib/fiddle/cparser.rb @@ -35,12 +35,27 @@ module CParser def parse_struct_signature(signature, tymap=nil) if signature.is_a?(String) signature = split_arguments(signature, /[,;]/) + elsif signature.kind_of?(Hash) + signature = [signature] end mems = [] tys = [] signature.each{|msig| - msig = compact(msig) + msig = compact(msig) if msig.is_a?(String) case msig + when Hash + msig.each do |structure_name, struct_signature| + structure_count = nil + if structure_name.to_s =~ /^([\w\*\s]+)\[(\d+)\]$/ + structure_count = $2.to_i + structure_name = $1 + end + structure_parsed = parse_struct_signature(struct_signature, tymap) + structure_types = structure_parsed[0] + structure_members = structure_parsed[1] + mems.push([structure_name.to_s, structure_members]) + tys.push([structure_types, structure_count]) + end when /^[\w\*\s]+[\*\s](\w+)$/ mems.push($1) tys.push(parse_ctype(msig, tymap)) diff --git a/lib/fiddle/struct.rb b/lib/fiddle/struct.rb index dfe18ec4..356fb21d 100644 --- a/lib/fiddle/struct.rb +++ b/lib/fiddle/struct.rb @@ -47,6 +47,13 @@ def []=(index, value) end end + # Wrapper for arrays of structs within a struct + class NestedStructArray < Array + def []=(index, value) + self[index].to_ptr.memcpy(value.to_ptr) + end + end + # Used to construct C classes (CUnion, CStruct, etc) # # Fiddle::Importer#struct and Fiddle::Importer#union wrap this functionality in an @@ -106,6 +113,10 @@ def create(klass, types, members) define_method(:to_ptr){ @entity } define_method(:to_i){ @entity.to_i } members.each{|name| + if name.kind_of?(Array) # name is a nested struct + next if method_defined?(name[0]) + name = name[0] + end define_method(name){ @entity[name] } define_method(name + "="){|val| @entity[name] = val } } @@ -160,9 +171,15 @@ def CStructEntity.size(types) max_align = types.map { |type, count = 1| last_offset = offset - align = PackInfo::ALIGN_MAP[type] - offset = PackInfo.align(last_offset, align) + - (PackInfo::SIZE_MAP[type] * count) + if type.kind_of?(Array) # type is a nested array representing a nested struct + align = CStructEntity.size(type) + offset = PackInfo.align(last_offset, align) + + (align * (count || 1)) + else + align = PackInfo::ALIGN_MAP[type] + offset = PackInfo.align(last_offset, align) + + (PackInfo::SIZE_MAP[type] * count) + end align }.max @@ -179,13 +196,30 @@ 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 + @addr = addr set_ctypes(types) super(addr, @size, func) end # Set the names of the +members+ in this C struct def assign_names(members) - @members = members + @members = members.map { |member| member.kind_of?(Array) ? member[0] : member } + + @nested_structs = {} + @ctypes.each_with_index do |ty, idx| + if ty.kind_of?(Array) && ty[0].kind_of?(Array) + member = members[idx] + member = member[0] if member.kind_of?(Array) + entity_class = CStructBuilder.create(CStruct, ty[0], members[idx][1]) + @nested_structs[member] ||= if ty[1] + NestedStructArray.new(ty[1].times.map do |i| + entity_class.new(@addr + @offset[idx] + i * CStructEntity.size(ty[0])) + end) + else + entity_class.new(@addr + @offset[idx]) + end + end + end end # Calculates the offsets and sizes for the given +types+ in the struct. @@ -196,12 +230,17 @@ def set_ctypes(types) max_align = types.map { |type, count = 1| orig_offset = offset - align = ALIGN_MAP[type] - offset = PackInfo.align(orig_offset, align) - - @offset << offset - - offset += (SIZE_MAP[type] * count) + if type.kind_of?(Array) # type is a nested array representing a nested struct + align = CStructEntity.size(type) + offset = PackInfo.align(orig_offset, align) + @offset << offset + offset += (align * (count || 1)) + else + align = ALIGN_MAP[type] + offset = PackInfo.align(orig_offset, align) + @offset << offset + offset += (SIZE_MAP[type] * (count || 1)) + end align }.max @@ -230,7 +269,11 @@ def [](*args) end ty = @ctypes[idx] if( ty.is_a?(Array) ) - r = super(@offset[idx], SIZE_MAP[ty[0]] * ty[1]) + if ty.first.kind_of?(Array) + return @nested_structs[name] + else + r = super(@offset[idx], SIZE_MAP[ty[0]] * ty[1]) + end else r = super(@offset[idx], SIZE_MAP[ty.abs]) end @@ -274,6 +317,16 @@ def []=(*args) if( idx.nil? ) raise(ArgumentError, "no such member: #{name}") end + if @nested_structs[name] + if @nested_structs[name].kind_of?(Array) + val.size.times do |i| + @nested_structs[name][i].to_ptr.memcpy(val[i].to_ptr) + end + else + @nested_structs[name].to_ptr.memcpy(val.to_ptr) + end + return + end ty = @ctypes[idx] packer = Packer.new([ty]) val = wrap_arg(val, ty, []) diff --git a/test/fiddle/test_cparser.rb b/test/fiddle/test_cparser.rb index 1590a573..8b66347a 100644 --- a/test/fiddle/test_cparser.rb +++ b/test/fiddle/test_cparser.rb @@ -76,6 +76,27 @@ def test_struct_array assert_equal [[[TYPE_CHAR,80],[TYPE_INT,5]], ['buffer','x']], parse_struct_signature(['char buffer[80]', 'int[5] x']) end + def test_struct_nested_struct + assert_equal [[TYPE_INT, [[TYPE_INT, TYPE_CHAR], nil]], ['x', ['inner', ['i', 'c']]]], parse_struct_signature(['int x', {inner: ['int i', 'char c']}]) + end + + def test_struct_double_nested_struct + assert_equal [[TYPE_INT, [[TYPE_INT, [[TYPE_INT, TYPE_CHAR], nil]], nil]], ['x', ['outer', ['y', ['inner', ['i', 'c']]]]]], + parse_struct_signature(['int x', {outer: ['int y', { inner: ['int i', 'char c'] }]}]) + end + + def test_struct_nested_struct_array + assert_equal [[TYPE_INT, [[TYPE_INT, TYPE_CHAR], 2]], ['x', ['inner', ['i', 'c']]]], parse_struct_signature(['int x', {'inner[2]' => ['int i', 'char c']}]) + end + + def test_struct_double_nested_struct_inner_array + assert_equal [[[[TYPE_INT, [[TYPE_INT, TYPE_CHAR], 2]], nil]], [['outer', ['x', ['inner', ['i', 'c']]]]]], parse_struct_signature(outer: ['int x', { 'inner[2]' => ['int i', 'char c'] }]) + end + + def test_struct_double_nested_struct_outer_array + assert_equal [[TYPE_INT, [[[[TYPE_INT, TYPE_CHAR], nil]], 2]], ['x', ['outer', [['inner', ['i', 'c']]]]]], parse_struct_signature(['int x', {'outer[2]' => { inner: ['int i', 'char c'] }}]) + end + def test_struct_array_str assert_equal [[[TYPE_CHAR,80],[TYPE_INT,5]], ['buffer','x']], parse_struct_signature('char buffer[80], int[5] x') end diff --git a/test/fiddle/test_import.rb b/test/fiddle/test_import.rb index 3337a65a..6ce9caf6 100644 --- a/test/fiddle/test_import.rb +++ b/test/fiddle/test_import.rb @@ -36,6 +36,16 @@ module LIBC "char c", "unsigned char buff[7]", ] + NestedStruct = struct [ + { + "vertices[2]" => { + position: [ "float x", "float y", "float z" ], + texcoord: [ "float u", "float v" ] + }, + object: [ "int id" ] + }, + "int id" + ] CallCallback = bind("void call_callback(void*, void*)"){ | ptr1, ptr2| f = Function.new(ptr1.to_i, [TYPE_VOIDP], TYPE_VOID) @@ -108,6 +118,7 @@ def test_sizeof() assert_equal(LIBC::MyStruct.size(), LIBC.sizeof(my_struct)) end assert_equal(SIZEOF_LONG_LONG, LIBC.sizeof("long long")) if defined?(SIZEOF_LONG_LONG) + assert_equal(LIBC::NestedStruct.size(), LIBC.sizeof(LIBC::NestedStruct)) end Fiddle.constants.grep(/\ATYPE_(?!VOID|VARIADIC\z)(.*)/) do @@ -190,6 +201,92 @@ def test_struct_array_assignment() assert_raise(RangeError) { instance.stages[-1] = 5 } assert_raise(RangeError) { instance.stages[2] = 5 } end + + def test_nested_struct_members() + s = LIBC::NestedStruct.malloc + s.vertices[0].position.x = 1 + s.vertices[0].position.y = 2 + s.vertices[0].position.z = 3 + s.vertices[0].texcoord.u = 4 + s.vertices[0].texcoord.v = 5 + s.vertices[1].position.x = 6 + s.vertices[1].position.y = 7 + s.vertices[1].position.z = 8 + s.vertices[1].texcoord.u = 9 + s.vertices[1].texcoord.v = 10 + s.object.id = 100 + s.id = 101 + assert_equal(1, s.vertices[0].position.x) + assert_equal(2, s.vertices[0].position.y) + assert_equal(3, s.vertices[0].position.z) + assert_equal(4, s.vertices[0].texcoord.u) + assert_equal(5, s.vertices[0].texcoord.v) + assert_equal(6, s.vertices[1].position.x) + assert_equal(7, s.vertices[1].position.y) + assert_equal(8, s.vertices[1].position.z) + assert_equal(9, s.vertices[1].texcoord.u) + assert_equal(10, s.vertices[1].texcoord.v) + assert_equal(100, s.object.id) + assert_equal(101, s.id) + end + + def test_nested_struct_replace_array_element() + s = LIBC::NestedStruct.malloc + s.vertices[0].position.x = 5 + + vertex_struct = Fiddle::Importer.struct [{ + position: [ "float x", "float y", "float z" ], + texcoord: [ "float u", "float v" ] + }] + vertex = vertex_struct.malloc + vertex.position.x = 100 + s.vertices[0] = vertex + + # make sure element was copied by value, but things like memory address + # should not be changed + assert_equal(100, s.vertices[0].position.x) + refute_equal(vertex.object_id, s.vertices[0].object_id) + refute_equal(vertex.to_ptr, s.vertices[0].to_ptr) + end + + def test_nested_struct_replace_entire_array() + s = LIBC::NestedStruct.malloc + vertex_struct = Fiddle::Importer.struct [{ + position: [ "float x", "float y", "float z" ], + texcoord: [ "float u", "float v" ] + }] + + different_struct_same_size = Fiddle::Importer.struct [{ + a: [ 'float i', 'float j', 'float k' ], + b: [ 'float l', 'float m' ] + }] + + same = [vertex_struct.malloc, vertex_struct.malloc] + same[0].position.x = 1; same[1].position.x = 6 + same[0].position.y = 2; same[1].position.y = 7 + same[0].position.z = 3; same[1].position.z = 8 + same[0].texcoord.u = 4; same[1].texcoord.u = 9 + same[0].texcoord.v = 5; same[1].texcoord.v = 10 + s.vertices = same + assert_equal(1, s.vertices[0].position.x); assert_equal(6, s.vertices[1].position.x) + assert_equal(2, s.vertices[0].position.y); assert_equal(7, s.vertices[1].position.y) + assert_equal(3, s.vertices[0].position.z); assert_equal(8, s.vertices[1].position.z) + assert_equal(4, s.vertices[0].texcoord.u); assert_equal(9, s.vertices[1].texcoord.u) + assert_equal(5, s.vertices[0].texcoord.v); assert_equal(10, s.vertices[1].texcoord.v) + + different = [different_struct_same_size.malloc, different_struct_same_size.malloc] + different[0].a.i = 11; different[1].a.i = 16 + different[0].a.j = 12; different[1].a.j = 17 + different[0].a.k = 13; different[1].a.k = 18 + different[0].b.l = 14; different[1].b.l = 19 + different[0].b.m = 15; different[1].b.m = 20 + s.vertices = different + assert_equal(11, s.vertices[0].position.x); assert_equal(16, s.vertices[1].position.x) + assert_equal(12, s.vertices[0].position.y); assert_equal(17, s.vertices[1].position.y) + assert_equal(13, s.vertices[0].position.z); assert_equal(18, s.vertices[1].position.z) + assert_equal(14, s.vertices[0].texcoord.u); assert_equal(19, s.vertices[1].texcoord.u) + assert_equal(15, s.vertices[0].texcoord.v); assert_equal(20, s.vertices[1].texcoord.v) + end def test_struct() LIBC::MyStruct.malloc(Fiddle::RUBY_FREE) do |s| diff --git a/test/fiddle/test_pointer.rb b/test/fiddle/test_pointer.rb index e685fea5..656209a6 100644 --- a/test/fiddle/test_pointer.rb +++ b/test/fiddle/test_pointer.rb @@ -45,7 +45,7 @@ def test_malloc_block end def test_malloc_block_no_free - assert_raise ArgumentError do + assert_raise ArgumentError do Pointer.malloc(10) { |ptr| } end end @@ -57,6 +57,20 @@ def test_malloc_subclass end end + def test_memcpy + ptr = Pointer[Marshal.load(Marshal.dump("hello world"))] + smaller = Pointer[Marshal.load(Marshal.dump("1234567890"))] + same = Pointer[Marshal.load(Marshal.dump("12345678901"))] + larger = Pointer[Marshal.load(Marshal.dump("123456789012"))] + + assert_equal(ptr.size - 1, smaller.memcpy(ptr)) + assert_equal(ptr.size , same.memcpy(ptr)) + assert_equal(ptr.size , larger.memcpy(ptr)) + assert_equal("hello worl", smaller.to_s) + assert_equal("hello world", same.to_s) + assert_equal("hello world2", larger.to_s) + end + def test_to_str str = Marshal.load(Marshal.dump("hello world")) ptr = Pointer[str] From 1677038877b910c854395ebb26f7990422f098bb Mon Sep 17 00:00:00 2001 From: Colin MacKenzie IV Date: Tue, 21 Jan 2020 21:38:48 -0500 Subject: [PATCH 07/37] allow an already defined struct to be reused in the definition of another --- lib/fiddle/cparser.rb | 2 ++ lib/fiddle/struct.rb | 2 ++ test/fiddle/test_import.rb | 8 ++++++++ 3 files changed, 12 insertions(+) diff --git a/lib/fiddle/cparser.rb b/lib/fiddle/cparser.rb index 39f56178..8114bfb2 100644 --- a/lib/fiddle/cparser.rb +++ b/lib/fiddle/cparser.rb @@ -37,6 +37,8 @@ def parse_struct_signature(signature, tymap=nil) signature = split_arguments(signature, /[,;]/) elsif signature.kind_of?(Hash) signature = [signature] + elsif signature.respond_to?(:types) && signature.respond_to?(:members) + return signature.types, signature.members end mems = [] tys = [] diff --git a/lib/fiddle/struct.rb b/lib/fiddle/struct.rb index 356fb21d..3187118c 100644 --- a/lib/fiddle/struct.rb +++ b/lib/fiddle/struct.rb @@ -112,6 +112,8 @@ def create(klass, types, members) define_method(:[]=) { |*args| @entity.send(:[]=, *args) } define_method(:to_ptr){ @entity } define_method(:to_i){ @entity.to_i } + define_singleton_method(:types) { types } + define_singleton_method(:members) { members } members.each{|name| if name.kind_of?(Array) # name is a nested struct next if method_defined?(name[0]) diff --git a/test/fiddle/test_import.rb b/test/fiddle/test_import.rb index 6ce9caf6..9d8ccf24 100644 --- a/test/fiddle/test_import.rb +++ b/test/fiddle/test_import.rb @@ -202,6 +202,14 @@ def test_struct_array_assignment() assert_raise(RangeError) { instance.stages[2] = 5 } end + def test_nested_struct_reusing_other_structs() + position_struct = Fiddle::Importer.struct([ 'float x', 'float y', 'float z' ]) + texcoord_struct = Fiddle::Importer.struct([ 'float u', 'float v' ]) + vertex_struct = Fiddle::Importer.struct(position: position_struct, texcoord: texcoord_struct) + mesh_struct = Fiddle::Importer.struct([{"vertices[2]" => vertex_struct, object: [ "int id" ]}, "int id"]) + assert_equal LIBC::NestedStruct.size, mesh_struct.size + end + def test_nested_struct_members() s = LIBC::NestedStruct.malloc s.vertices[0].position.x = 1 From 5398949e8b1ef0533e9a80759012fa2a8d91a3c2 Mon Sep 17 00:00:00 2001 From: Colin MacKenzie IV Date: Mon, 10 Sep 2018 14:04:08 -0400 Subject: [PATCH 08/37] support for structs nested inside of unions --- lib/fiddle/struct.rb | 6 ++++- test/fiddle/test_import.rb | 45 ++++++++++++++++++++++++++++++-------- 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/lib/fiddle/struct.rb b/lib/fiddle/struct.rb index 3187118c..e5af8a16 100644 --- a/lib/fiddle/struct.rb +++ b/lib/fiddle/struct.rb @@ -362,7 +362,11 @@ class CUnionEntity < CStructEntity # Fiddle::TYPE_VOIDP ]) #=> 8 def CUnionEntity.size(types) types.map { |type, count = 1| - PackInfo::SIZE_MAP[type] * count + if type.kind_of?(Array) # type is a nested array representing a nested struct + CStructEntity.size(type) * (count || 1) + else + PackInfo::SIZE_MAP[type] * count + end }.max end diff --git a/test/fiddle/test_import.rb b/test/fiddle/test_import.rb index 9d8ccf24..c8ab929f 100644 --- a/test/fiddle/test_import.rb +++ b/test/fiddle/test_import.rb @@ -36,7 +36,7 @@ module LIBC "char c", "unsigned char buff[7]", ] - NestedStruct = struct [ + StructNestedStruct = struct [ { "vertices[2]" => { position: [ "float x", "float y", "float z" ], @@ -46,6 +46,19 @@ module LIBC }, "int id" ] + UnionNestedStruct = union [ + { + keyboard: [ + 'unsigned int state', + 'char key' + ], + mouse: [ + 'unsigned int button', + 'unsigned short x', + 'unsigned short y' + ] + } + ] CallCallback = bind("void call_callback(void*, void*)"){ | ptr1, ptr2| f = Function.new(ptr1.to_i, [TYPE_VOIDP], TYPE_VOID) @@ -118,7 +131,7 @@ def test_sizeof() assert_equal(LIBC::MyStruct.size(), LIBC.sizeof(my_struct)) end assert_equal(SIZEOF_LONG_LONG, LIBC.sizeof("long long")) if defined?(SIZEOF_LONG_LONG) - assert_equal(LIBC::NestedStruct.size(), LIBC.sizeof(LIBC::NestedStruct)) + assert_equal(LIBC::StructNestedStruct.size(), LIBC.sizeof(LIBC::StructNestedStruct)) end Fiddle.constants.grep(/\ATYPE_(?!VOID|VARIADIC\z)(.*)/) do @@ -207,11 +220,17 @@ def test_nested_struct_reusing_other_structs() texcoord_struct = Fiddle::Importer.struct([ 'float u', 'float v' ]) vertex_struct = Fiddle::Importer.struct(position: position_struct, texcoord: texcoord_struct) mesh_struct = Fiddle::Importer.struct([{"vertices[2]" => vertex_struct, object: [ "int id" ]}, "int id"]) - assert_equal LIBC::NestedStruct.size, mesh_struct.size + assert_equal LIBC::StructNestedStruct.size, mesh_struct.size + + + keyboard_event_struct = Fiddle::Importer.struct([ 'unsigned int state', 'char key' ]) + mouse_event_struct = Fiddle::Importer.struct([ 'unsigned int button', 'unsigned short x', 'unsigned short y' ]) + event_union = Fiddle::Importer.union([{ keboard: keyboard_event_struct, mouse: mouse_event_struct}]) + assert_equal LIBC::UnionNestedStruct.size, event_union.size end - def test_nested_struct_members() - s = LIBC::NestedStruct.malloc + def test_struct_nested_struct_members() + s = LIBC::StructNestedStruct.malloc s.vertices[0].position.x = 1 s.vertices[0].position.y = 2 s.vertices[0].position.z = 3 @@ -238,8 +257,16 @@ def test_nested_struct_members() assert_equal(101, s.id) end - def test_nested_struct_replace_array_element() - s = LIBC::NestedStruct.malloc + def test_union_nested_struct_members() + s = LIBC::UnionNestedStruct.malloc + s.keyboard.state = 100 + s.keyboard.key = 101 + assert_equal(100, s.mouse.button) + refute_equal( 0, s.mouse.x) + end + + def test_struct_nested_struct_replace_array_element() + s = LIBC::StructNestedStruct.malloc s.vertices[0].position.x = 5 vertex_struct = Fiddle::Importer.struct [{ @@ -257,8 +284,8 @@ def test_nested_struct_replace_array_element() refute_equal(vertex.to_ptr, s.vertices[0].to_ptr) end - def test_nested_struct_replace_entire_array() - s = LIBC::NestedStruct.malloc + def test_struct_nested_struct_replace_entire_array() + s = LIBC::StructNestedStruct.malloc vertex_struct = Fiddle::Importer.struct [{ position: [ "float x", "float y", "float z" ], texcoord: [ "float u", "float v" ] From 32668dc1b499486b1eb74d7769b06bffcb130170 Mon Sep 17 00:00:00 2001 From: Colin MacKenzie IV Date: Mon, 29 Oct 2018 21:44:18 -0400 Subject: [PATCH 09/37] Add documentation to README for nested structs. --- README.md | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/README.md b/README.md index 29c21e55..5576c8e4 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,57 @@ floor = Fiddle::Function.new( puts floor.call(3.14159) #=> 3.0 ``` +### Nested Structs + +You can use hashes to create nested structs, where the hash keys are member names and the values are the nested structs: + +```ruby +StudentCollegeDetail = struct [ + 'int college_id', + 'char college_name[50]' +] + +StudentDetail = struct [ + 'int id', + 'char name[20]', + { clg_data: StudentCollegeDetail } +] +``` + +You can also specify an anonymous nested struct, like so: + +```ruby +StudentDetail = struct [ + 'int id', + 'char name[20]', + { + clg_data: struct([ + 'int college_id', + 'char college_name[50]' + ]) + } +] +``` + +The position of a hash (and the order of the keys in the hash, in the case of a hash with multiple values), dictate the offsets of the nested struct in memory. For example, the following are both syntactically valid but will lay out the data in the nested structs differently in memory: + +```ruby +# order of members in memory: position, id, dimensions +Rect = struct [ { position: struct(['float x', 'float y']) }, + 'int id', + { dimensions: struct(['float w', 'float h']) } + ] + +# order of members in memory: id, position, dimensions +Rect = struct [ 'int id', + { + position: struct(['float x', 'float y']), + dimensions: struct(['float w', 'float h']) + } + ] +``` + + ## Development After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. From 7ab7f3c642056dbecbe5ba2402fb5d721f19f559 Mon Sep 17 00:00:00 2001 From: Colin MacKenzie IV Date: Mon, 29 Oct 2018 21:46:20 -0400 Subject: [PATCH 10/37] clarify documentation --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5576c8e4..3026c26c 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ StudentDetail = struct [ ] ``` -The position of a hash (and the order of the keys in the hash, in the case of a hash with multiple values), dictate the offsets of the nested struct in memory. For example, the following are both syntactically valid but will lay out the data in the nested structs differently in memory: +The position of a hash (and the order of the keys in the hash, in the case of a hash with multiple entries), dictate the offsets of the nested struct in memory. The following examples are both syntactically valid but will lay out the structs differently in memory: ```ruby # order of members in memory: position, id, dimensions From a00a03d0925cfa0579aa41394fdf9d55638b1ff6 Mon Sep 17 00:00:00 2001 From: Colin MacKenzie IV Date: Thu, 1 Nov 2018 00:23:51 -0400 Subject: [PATCH 11/37] fix alignment and size when members appear after a nested struct --- lib/fiddle/struct.rb | 23 +++++++++++++++++++---- test/fiddle/test_import.rb | 10 ++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/lib/fiddle/struct.rb b/lib/fiddle/struct.rb index e5af8a16..3e07300d 100644 --- a/lib/fiddle/struct.rb +++ b/lib/fiddle/struct.rb @@ -144,6 +144,19 @@ class CStructEntity < Fiddle::Pointer include PackInfo include ValueUtil + def CStructEntity.alignment(types) + max = 1 + types.each do |type, count = 1| + if type.kind_of?(Array) # nested struct + n = CStructEntity.alignment(type) + else + n = ALIGN_MAP[type] + end + max = n if n > max + end + max + end + # Allocates a C struct with the +types+ provided. # # See Fiddle::Pointer.malloc for memory management issues. @@ -174,9 +187,10 @@ def CStructEntity.size(types) last_offset = offset if type.kind_of?(Array) # type is a nested array representing a nested struct - align = CStructEntity.size(type) + align = CStructEntity.alignment(type) + total_size = CStructEntity.size(type) offset = PackInfo.align(last_offset, align) + - (align * (count || 1)) + (total_size * (count || 1)) else align = PackInfo::ALIGN_MAP[type] offset = PackInfo.align(last_offset, align) + @@ -233,10 +247,11 @@ def set_ctypes(types) max_align = types.map { |type, count = 1| orig_offset = offset if type.kind_of?(Array) # type is a nested array representing a nested struct - align = CStructEntity.size(type) + align = CStructEntity.alignment(type) + total_size = CStructEntity.size(type) offset = PackInfo.align(orig_offset, align) @offset << offset - offset += (align * (count || 1)) + offset += (total_size * (count || 1)) else align = ALIGN_MAP[type] offset = PackInfo.align(orig_offset, align) diff --git a/test/fiddle/test_import.rb b/test/fiddle/test_import.rb index c8ab929f..55cb938a 100644 --- a/test/fiddle/test_import.rb +++ b/test/fiddle/test_import.rb @@ -229,6 +229,16 @@ def test_nested_struct_reusing_other_structs() assert_equal LIBC::UnionNestedStruct.size, event_union.size end + def test_nested_struct_alignment_is_not_its_size() + inner = Fiddle::Importer.struct(['int x', 'int y', 'int z', 'int w']) + outer = Fiddle::Importer.struct(['char a', { 'nested' => inner }, 'char b']) + instance = outer.malloc + offset = instance.to_ptr.instance_variable_get(:"@offset") + assert_equal Fiddle::SIZEOF_INT * 5, offset.last + assert_equal Fiddle::SIZEOF_INT * 6, outer.size + assert_equal instance.to_ptr.size, outer.size + end + def test_struct_nested_struct_members() s = LIBC::StructNestedStruct.malloc s.vertices[0].position.x = 1 From 1783a006efc16bc4cc99ad3e36e7586524cbcdd2 Mon Sep 17 00:00:00 2001 From: Colin MacKenzie IV Date: Tue, 21 Jan 2020 21:43:28 -0500 Subject: [PATCH 12/37] Fix incorrect sizing of structs containing unions and pointers. --- lib/fiddle/cparser.rb | 7 ++++-- lib/fiddle/struct.rb | 51 ++++++++++++++++++++++++++++++-------- test/fiddle/test_import.rb | 29 ++++++++++++++++++++++ 3 files changed, 74 insertions(+), 13 deletions(-) diff --git a/lib/fiddle/cparser.rb b/lib/fiddle/cparser.rb index 8114bfb2..91665a0c 100644 --- a/lib/fiddle/cparser.rb +++ b/lib/fiddle/cparser.rb @@ -38,7 +38,7 @@ def parse_struct_signature(signature, tymap=nil) elsif signature.kind_of?(Hash) signature = [signature] elsif signature.respond_to?(:types) && signature.respond_to?(:members) - return signature.types, signature.members + return signature.types, signature.members, signature.entity_class end mems = [] tys = [] @@ -55,8 +55,11 @@ def parse_struct_signature(signature, tymap=nil) structure_parsed = parse_struct_signature(struct_signature, tymap) structure_types = structure_parsed[0] structure_members = structure_parsed[1] + structure_klass = structure_parsed[2] + ty = [structure_types, structure_count] + ty << structure_klass if structure_klass mems.push([structure_name.to_s, structure_members]) - tys.push([structure_types, structure_count]) + tys.push(ty) end when /^[\w\*\s]+[\*\s](\w+)$/ mems.push($1) diff --git a/lib/fiddle/struct.rb b/lib/fiddle/struct.rb index 3e07300d..b0eba3de 100644 --- a/lib/fiddle/struct.rb +++ b/lib/fiddle/struct.rb @@ -112,8 +112,10 @@ def create(klass, types, members) define_method(:[]=) { |*args| @entity.send(:[]=, *args) } define_method(:to_ptr){ @entity } define_method(:to_i){ @entity.to_i } + define_method(:offset_of) { |*args| @entity.offset_of(*args) } define_singleton_method(:types) { types } define_singleton_method(:members) { members } + define_singleton_method(:offset_of) { |mbr| klass.entity_class.compute_offset(types, members, mbr) } members.each{|name| if name.kind_of?(Array) # name is a nested struct next if method_defined?(name[0]) @@ -157,6 +159,15 @@ def CStructEntity.alignment(types) max end + def CStructEntity.compute_offset(types, members, mbr) + members.each_with_index do |m, idx| + if (m.kind_of?(Array) ? m[0] : m) == mbr.to_s + return idx == 0 ? 0 : CStructEntity.size(types[0...idx]) + end + end + raise(ArgumentError, "no such member: #{mbr}") + end + # Allocates a C struct with the +types+ provided. # # See Fiddle::Pointer.malloc for memory management issues. @@ -183,12 +194,12 @@ def CStructEntity.malloc(types, func = nil, size = size(types), &block) def CStructEntity.size(types) offset = 0 - max_align = types.map { |type, count = 1| + max_align = types.map { |type, count = 1, klass = CStructEntity| last_offset = offset if type.kind_of?(Array) # type is a nested array representing a nested struct - align = CStructEntity.alignment(type) - total_size = CStructEntity.size(type) + align = klass.alignment(type) + total_size = klass.size(type) offset = PackInfo.align(last_offset, align) + (total_size * (count || 1)) else @@ -229,7 +240,7 @@ def assign_names(members) entity_class = CStructBuilder.create(CStruct, ty[0], members[idx][1]) @nested_structs[member] ||= if ty[1] NestedStructArray.new(ty[1].times.map do |i| - entity_class.new(@addr + @offset[idx] + i * CStructEntity.size(ty[0])) + entity_class.new(@addr + @offset[idx] + i * (ty[2] || CStructEntity).size(ty[0])) end) else entity_class.new(@addr + @offset[idx]) @@ -244,11 +255,11 @@ def set_ctypes(types) @offset = [] offset = 0 - max_align = types.map { |type, count = 1| + max_align = types.map { |type, count = 1, klass = CStructEntity| orig_offset = offset if type.kind_of?(Array) # type is a nested array representing a nested struct - align = CStructEntity.alignment(type) - total_size = CStructEntity.size(type) + align = klass.alignment(type) + total_size = klass.size(type) offset = PackInfo.align(orig_offset, align) @offset << offset offset += (total_size * (count || 1)) @@ -265,6 +276,11 @@ def set_ctypes(types) @size = PackInfo.align(offset, max_align) end + def offset_of(mbr) + idx = @members.index(mbr.to_s) || raise(ArgumentError, "no such member: #{mbr}") + @offset[idx] + end + # Fetch struct member +name+ if only one argument is specified. If two # arguments are specified, the first is an offset and the second is a # length and this method returns the string of +length+ bytes beginning at @@ -300,10 +316,11 @@ def [](*args) when Array case ty[0] when TYPE_VOIDP - val = val.collect{|v| Pointer.new(v)} + val = val.collect{|v| v = Pointer.new(v); v.size = SIZEOF_VOIDP; v } end when TYPE_VOIDP val = Pointer.new(val[0]) + val.size = SIZEOF_VOIDP else val = val[0] end @@ -368,7 +385,19 @@ def to_s() # :nodoc: class CUnionEntity < CStructEntity include PackInfo - # Returns the size needed for the union with the given +types+. + def CUnionEntity.compute_offset(types, members, mbr) + # all members begin at offset 0 + 0 + end + + # Allocates a C union the +types+ provided. + # + # When the instance is garbage collected, the C function +func+ is called. + def CUnionEntity.malloc(types, func=nil) + addr = Fiddle.malloc(CUnionEntity.size(types)) + CUnionEntity.new(addr, types, func) + end + # # Fiddle::CUnionEntity.size( # [ Fiddle::TYPE_DOUBLE, @@ -376,9 +405,9 @@ class CUnionEntity < CStructEntity # Fiddle::TYPE_CHAR, # Fiddle::TYPE_VOIDP ]) #=> 8 def CUnionEntity.size(types) - types.map { |type, count = 1| + types.map { |type, count = 1, klass = CStructEntity| if type.kind_of?(Array) # type is a nested array representing a nested struct - CStructEntity.size(type) * (count || 1) + klass.size(type) * (count || 1) else PackInfo::SIZE_MAP[type] * count end diff --git a/test/fiddle/test_import.rb b/test/fiddle/test_import.rb index 55cb938a..c844bb4d 100644 --- a/test/fiddle/test_import.rb +++ b/test/fiddle/test_import.rb @@ -275,6 +275,35 @@ def test_union_nested_struct_members() refute_equal( 0, s.mouse.x) end + def test_size_of_struct_accessor_returning_void_pointer() + s = Fiddle::Importer.struct(['void *x', 'void *y[2]']).malloc + assert_equal Fiddle::SIZEOF_VOIDP, s['x'].size + assert_equal Fiddle::SIZEOF_VOIDP, s['y'][0].size + end + + def test_struct_size_and_offset_of_nested_unions() + a = Fiddle::Importer.union ['float f[4]', 'int i[2]'] + b = Fiddle::Importer.struct ['float x', 'int y'] + c = Fiddle::Importer.struct [ a: a, b: b ] + + assert_equal 0, a.offset_of(:f) + assert_equal 0, a.offset_of(:i) + assert_equal 0, b.offset_of(:x) + assert_equal Fiddle::SIZEOF_FLOAT, b.offset_of(:y) + assert_equal 0, c.offset_of(:a) + assert_equal 4 * Fiddle::SIZEOF_FLOAT, c.offset_of(:b) + + assert_equal a.offset_of(:f), a.malloc.offset_of(:f) + assert_equal a.offset_of(:i), a.malloc.offset_of(:i) + assert_equal b.offset_of(:x), b.malloc.offset_of(:x) + assert_equal b.offset_of(:y), b.malloc.offset_of(:y) + assert_equal c.offset_of(:a), c.malloc.offset_of(:a) + assert_equal c.offset_of(:b), c.malloc.offset_of(:b) + + assert_equal Fiddle::SIZEOF_FLOAT * 4 + Fiddle::SIZEOF_INT * 2, + c.size + end + def test_struct_nested_struct_replace_array_element() s = LIBC::StructNestedStruct.malloc s.vertices[0].position.x = 5 From 8e75b26118eb7d4aceb6fd2a91ad3ef2486f514d Mon Sep 17 00:00:00 2001 From: Colin MacKenzie IV Date: Wed, 22 Jan 2020 13:41:56 -0500 Subject: [PATCH 13/37] remove duplicated test case --- test/fiddle/test_import.rb | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/test/fiddle/test_import.rb b/test/fiddle/test_import.rb index c844bb4d..ded39e78 100644 --- a/test/fiddle/test_import.rb +++ b/test/fiddle/test_import.rb @@ -204,16 +204,6 @@ def test_struct_array_subscript_multiarg() assert_equal 16843009, struct.x end end - - def test_struct_array_assignment() - instance = Fiddle::Importer.struct(["unsigned int stages[1]"]).malloc - instance.stages[0] = 1024 - assert_equal 1024, instance.stages[0] - assert_equal [1024].pack(Fiddle::PackInfo::PACK_MAP[-Fiddle::TYPE_INT]), - instance.to_ptr[0, Fiddle::SIZEOF_INT] - assert_raise(RangeError) { instance.stages[-1] = 5 } - assert_raise(RangeError) { instance.stages[2] = 5 } - end def test_nested_struct_reusing_other_structs() position_struct = Fiddle::Importer.struct([ 'float x', 'float y', 'float z' ]) From d461ae20c13822e264c33a3cfd272a080989ed46 Mon Sep 17 00:00:00 2001 From: Colin MacKenzie IV Date: Wed, 22 Jan 2020 12:49:13 -0500 Subject: [PATCH 14/37] use IndexError instead of RangeError --- test/fiddle/test_import.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/fiddle/test_import.rb b/test/fiddle/test_import.rb index ded39e78..c4b66f47 100644 --- a/test/fiddle/test_import.rb +++ b/test/fiddle/test_import.rb @@ -204,7 +204,7 @@ def test_struct_array_subscript_multiarg() assert_equal 16843009, struct.x end end - + def test_nested_struct_reusing_other_structs() position_struct = Fiddle::Importer.struct([ 'float x', 'float y', 'float z' ]) texcoord_struct = Fiddle::Importer.struct([ 'float u', 'float v' ]) From 6d3d9b0ac6d68dd1baa8906cd6f292c9447d892e Mon Sep 17 00:00:00 2001 From: Colin MacKenzie IV Date: Wed, 22 Jan 2020 12:50:07 -0500 Subject: [PATCH 15/37] use @size, not @align --- lib/fiddle/struct.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fiddle/struct.rb b/lib/fiddle/struct.rb index b0eba3de..0573d276 100644 --- a/lib/fiddle/struct.rb +++ b/lib/fiddle/struct.rb @@ -42,7 +42,7 @@ def []=(index, value) raise IndexError, 'index %d outside of array bounds 0...%d' % [index, size] end - to_ptr[index * @align, @size] = [value].pack(@pack_format) + to_ptr[index * @size, @size] = [value].pack(@pack_format) super(index, value) end end From 553cd8bb921fe79c2121dcf6fd15125ee3a9f4f0 Mon Sep 17 00:00:00 2001 From: Sutou Kouhei Date: Wed, 9 Sep 2020 06:33:05 +0900 Subject: [PATCH 16/37] Fix rebase miss --- lib/fiddle/struct.rb | 9 +-------- test/fiddle/test_import.rb | 24 ------------------------ test/fiddle/test_pointer.rb | 2 +- 3 files changed, 2 insertions(+), 33 deletions(-) diff --git a/lib/fiddle/struct.rb b/lib/fiddle/struct.rb index 0573d276..d517eeea 100644 --- a/lib/fiddle/struct.rb +++ b/lib/fiddle/struct.rb @@ -390,14 +390,7 @@ def CUnionEntity.compute_offset(types, members, mbr) 0 end - # Allocates a C union the +types+ provided. - # - # When the instance is garbage collected, the C function +func+ is called. - 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( # [ Fiddle::TYPE_DOUBLE, diff --git a/test/fiddle/test_import.rb b/test/fiddle/test_import.rb index c4b66f47..9c4f2221 100644 --- a/test/fiddle/test_import.rb +++ b/test/fiddle/test_import.rb @@ -80,9 +80,6 @@ def test_ensure_call_dlload def test_struct_memory_access() # check memory operations performed directly on struct Fiddle::Importer.struct(['int id']).malloc(Fiddle::RUBY_FREE) do |my_struct| - my_struct['id'] = 1 - assert_equal 1, my_struct.id - my_struct[0, Fiddle::SIZEOF_INT] = "\x01".b * Fiddle::SIZEOF_INT assert_equal 0x01010101, my_struct.id @@ -104,18 +101,6 @@ def test_struct_ptr_array_subscript_multiarg() end end - def test_struct_ptr_array_subscript_multiarg() - # check memory operations performed on struct#to_ptr - struct = Fiddle::Importer.struct([ 'int x' ]).malloc - ptr = struct.to_ptr - - struct.x = 0x02020202 - assert_equal("\x02".b * Fiddle::SIZEOF_INT, ptr[0, Fiddle::SIZEOF_INT]) - - ptr[0, Fiddle::SIZEOF_INT] = "\x01".b * Fiddle::SIZEOF_INT - assert_equal 0x01010101, struct.x - end - def test_malloc() LIBC::Timeval.malloc(Fiddle::RUBY_FREE) do |s1| LIBC::Timeval.malloc(Fiddle::RUBY_FREE) do |s2| @@ -196,15 +181,6 @@ def test_struct_array_assignment() end end - def test_struct_array_subscript_multiarg() - Fiddle::Importer.struct([ 'int x' ]).malloc(Fiddle::RUBY_FREE) do |struct| - assert_equal("\x00".b * Fiddle::SIZEOF_INT, struct.to_ptr[0, Fiddle::SIZEOF_INT]) - - struct.to_ptr[0, Fiddle::SIZEOF_INT] = "\x01".b * Fiddle::SIZEOF_INT - assert_equal 16843009, struct.x - end - end - def test_nested_struct_reusing_other_structs() position_struct = Fiddle::Importer.struct([ 'float x', 'float y', 'float z' ]) texcoord_struct = Fiddle::Importer.struct([ 'float u', 'float v' ]) diff --git a/test/fiddle/test_pointer.rb b/test/fiddle/test_pointer.rb index 656209a6..a5cfde22 100644 --- a/test/fiddle/test_pointer.rb +++ b/test/fiddle/test_pointer.rb @@ -45,7 +45,7 @@ def test_malloc_block end def test_malloc_block_no_free - assert_raise ArgumentError do + assert_raise ArgumentError do Pointer.malloc(10) { |ptr| } end end From af25a8998c295c54690b9dc048c0e75f0f10292a Mon Sep 17 00:00:00 2001 From: Sutou Kouhei Date: Wed, 9 Sep 2020 06:45:05 +0900 Subject: [PATCH 17/37] Use existing Pointer#to_i --- lib/fiddle/struct.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/fiddle/struct.rb b/lib/fiddle/struct.rb index d517eeea..6b91684c 100644 --- a/lib/fiddle/struct.rb +++ b/lib/fiddle/struct.rb @@ -223,7 +223,6 @@ 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 - @addr = addr set_ctypes(types) super(addr, @size, func) end @@ -240,10 +239,10 @@ def assign_names(members) entity_class = CStructBuilder.create(CStruct, ty[0], members[idx][1]) @nested_structs[member] ||= if ty[1] NestedStructArray.new(ty[1].times.map do |i| - entity_class.new(@addr + @offset[idx] + i * (ty[2] || CStructEntity).size(ty[0])) + entity_class.new(to_i + @offset[idx] + i * (ty[2] || CStructEntity).size(ty[0])) end) else - entity_class.new(@addr + @offset[idx]) + entity_class.new(to_i + @offset[idx]) end end end From 178fffd334829179a045ed4a93a4386221e716fe Mon Sep 17 00:00:00 2001 From: Sutou Kouhei Date: Thu, 10 Sep 2020 11:20:05 +0900 Subject: [PATCH 18/37] Move memcpy to Fiddle from Fiddle::Pointer --- ext/fiddle/fiddle.c | 89 +++++++++++++++++++++++++++++++++++++ ext/fiddle/pointer.c | 22 --------- lib/fiddle/struct.rb | 6 +-- test/fiddle/test_fiddle.rb | 22 +++++++++ test/fiddle/test_pointer.rb | 14 ------ 5 files changed, 114 insertions(+), 39 deletions(-) diff --git a/ext/fiddle/fiddle.c b/ext/fiddle/fiddle.c index 5635ab32..807185db 100644 --- a/ext/fiddle/fiddle.c +++ b/ext/fiddle/fiddle.c @@ -53,6 +53,94 @@ rb_fiddle_free(VALUE self, VALUE addr) return Qnil; } +static void +rb_fiddle_extract_address(VALUE object, void **address, long *size) +{ + ID id_to_ptr; + ID id_to_i; + ID id_size; + CONST_ID(id_to_ptr, "to_ptr"); + CONST_ID(id_to_i, "to_i"); + CONST_ID(id_size, "size"); + + if (RB_TEST(rb_respond_to(object, id_to_ptr))) { + object = rb_funcall(object, id_to_ptr, 0); + } + *size = -1; + if (!RB_INTEGER_TYPE_P(object)) { + if (RB_TEST(rb_respond_to(object, id_size))) { + *size = NUM2LONG(rb_funcall(object, id_size, 0)); + } + object = rb_funcall(object, id_to_i, 0); + } + *address = NUM2PTR(object); + + if (!*address) { + rb_raise(rb_eArgError, + "must not NULL pointer: %"PRIsVALUE, + object); + } +} + +/* + * call-seq: + * + * Fiddle.memcpy(dest, src) => number + * Fiddle.memcpy(dest, src, n) => number + * + * Copies the contents of the given +src+ pointer like object into the + * given +dest+ pointer like object. If +n+ is specified, +n+ bytes + * data of +src+ is copied to +dest+. If +n+ isn't specified, it will + * copy up to the allocated size of the given +src+ pointer like + * object or the allocated size of the given +src+ pointer like + * object, whichever is smaller (to prevent overflow). + * + * Returns the number of bytes actually copied. + */ +static VALUE +rb_fiddle_memcpy(int argc, VALUE *argv, VALUE self) +{ + VALUE dest; + VALUE src; + VALUE n; + + rb_scan_args(argc, argv, "21", &dest, &src, &n); + + void *dest_address = NULL; + long dest_size = -1; + void *src_address = NULL; + long src_size = -1; + rb_fiddle_extract_address(dest, &dest_address, &dest_size); + rb_fiddle_extract_address(src, &src_address, &src_size); + + size_t memcpy_size = 0; + if (NIL_P(n)) { + if (dest_size < 0 && src_size < 0) { + rb_raise(rb_eArgError, + "must specify copy size for raw pointers: " + "dest: %"PRIsVALUE ", src: %" PRIsVALUE, + dest, src); + } + if (dest_size < 0) { + memcpy_size = src_size; + } + else if (src_size < 0) { + memcpy_size = src_size; + } + else + { + memcpy_size = dest_size > src_size ? src_size : dest_size; + } + } + else { + memcpy_size = NUM2SIZET(n); + } + + memcpy(dest_address, src_address, memcpy_size); + + return SIZET2NUM(memcpy_size); +} + /* * call-seq: Fiddle.dlunwrap(addr) * @@ -457,6 +545,7 @@ Init_fiddle(void) rb_define_module_function(mFiddle, "malloc", rb_fiddle_malloc, 1); rb_define_module_function(mFiddle, "realloc", rb_fiddle_realloc, 2); rb_define_module_function(mFiddle, "free", rb_fiddle_free, 1); + rb_define_module_function(mFiddle, "memcpy", rb_fiddle_memcpy, -1); Init_fiddle_function(); Init_fiddle_closure(); diff --git a/ext/fiddle/pointer.c b/ext/fiddle/pointer.c index 764cd799..e13e1520 100644 --- a/ext/fiddle/pointer.c +++ b/ext/fiddle/pointer.c @@ -711,27 +711,6 @@ rb_fiddle_ptr_size_get(VALUE self) return LONG2NUM(RPTR_DATA(self)->size); } -/* - * call-seq: memcpy - * - * Copies the contents of the given pointer into this one. Will copy up to the - * allocated size of the given pointer or the allocated size of this one, - * whichever is smaller (to prevent overflow). - * - * Returns the number of bytes actually copied. - */ -static VALUE -rb_fiddle_ptr_memcpy(VALUE self, VALUE other) -{ - long self_size = NUM2LONG(rb_funcall(self, rb_intern("size"), 0)); - long other_size = NUM2LONG(rb_funcall(other, rb_intern("size"), 0)); - void *self_ptr = NUM2PTR(rb_fiddle_ptr_to_i(self)); - void *other_ptr = NUM2PTR(rb_fiddle_ptr_to_i(other)); - long size = self_size > other_size ? other_size : self_size; - memcpy(self_ptr, other_ptr, size); - return LONG2NUM(size); -} - /* * call-seq: * Fiddle::Pointer[val] => cptr @@ -815,7 +794,6 @@ Init_fiddle_pointer(void) rb_define_method(rb_cPointer, "[]=", rb_fiddle_ptr_aset, -1); rb_define_method(rb_cPointer, "size", rb_fiddle_ptr_size_get, 0); rb_define_method(rb_cPointer, "size=", rb_fiddle_ptr_size_set, 1); - rb_define_method(rb_cPointer, "memcpy", rb_fiddle_ptr_memcpy, 1); /* Document-const: NULL * diff --git a/lib/fiddle/struct.rb b/lib/fiddle/struct.rb index 6b91684c..65d23c86 100644 --- a/lib/fiddle/struct.rb +++ b/lib/fiddle/struct.rb @@ -50,7 +50,7 @@ def []=(index, value) # Wrapper for arrays of structs within a struct class NestedStructArray < Array def []=(index, value) - self[index].to_ptr.memcpy(value.to_ptr) + Fiddle.memcpy(self[index], value) end end @@ -353,10 +353,10 @@ def []=(*args) if @nested_structs[name] if @nested_structs[name].kind_of?(Array) val.size.times do |i| - @nested_structs[name][i].to_ptr.memcpy(val[i].to_ptr) + Fiddle.memcpy(@nested_structs[name][i], val[i]) end else - @nested_structs[name].to_ptr.memcpy(val.to_ptr) + Fiddle.memcpy(@nested_structs[name], val) end return end diff --git a/test/fiddle/test_fiddle.rb b/test/fiddle/test_fiddle.rb index 8751d969..84cbae41 100644 --- a/test/fiddle/test_fiddle.rb +++ b/test/fiddle/test_fiddle.rb @@ -14,4 +14,26 @@ def test_windows_constant end end + def test_memcpy_pointer + src = Fiddle::Pointer[Marshal.load(Marshal.dump("hello world"))] + smaller = Fiddle::Pointer[Marshal.load(Marshal.dump("1234567890"))] + same = Fiddle::Pointer[Marshal.load(Marshal.dump("12345678901"))] + larger = Fiddle::Pointer[Marshal.load(Marshal.dump("123456789012"))] + + assert_equal(src.size - 1, Fiddle.memcpy(smaller, src)) + assert_equal(src.size , Fiddle.memcpy(same, src)) + assert_equal(src.size , Fiddle.memcpy(larger, src)) + assert_equal("hello worl", smaller.to_s) + assert_equal("hello world", same.to_s) + assert_equal("hello world2", larger.to_s) + end + + def test_memcpy_address + src = Fiddle::Pointer[Marshal.load(Marshal.dump("hello world"))] + dest = Fiddle::Pointer[Marshal.load(Marshal.dump("12345678901"))] + + assert_equal(src.size - 1, + Fiddle.memcpy(dest.to_i, src.to_i, src.size - 1)) + assert_equal("hello worl1", dest.to_s) + end end if defined?(Fiddle) diff --git a/test/fiddle/test_pointer.rb b/test/fiddle/test_pointer.rb index a5cfde22..e685fea5 100644 --- a/test/fiddle/test_pointer.rb +++ b/test/fiddle/test_pointer.rb @@ -57,20 +57,6 @@ def test_malloc_subclass end end - def test_memcpy - ptr = Pointer[Marshal.load(Marshal.dump("hello world"))] - smaller = Pointer[Marshal.load(Marshal.dump("1234567890"))] - same = Pointer[Marshal.load(Marshal.dump("12345678901"))] - larger = Pointer[Marshal.load(Marshal.dump("123456789012"))] - - assert_equal(ptr.size - 1, smaller.memcpy(ptr)) - assert_equal(ptr.size , same.memcpy(ptr)) - assert_equal(ptr.size , larger.memcpy(ptr)) - assert_equal("hello worl", smaller.to_s) - assert_equal("hello world", same.to_s) - assert_equal("hello world2", larger.to_s) - end - def test_to_str str = Marshal.load(Marshal.dump("hello world")) ptr = Pointer[str] From cf4698727ebc3176c408b6341869b80a4bcff152 Mon Sep 17 00:00:00 2001 From: Sutou Kouhei Date: Sat, 12 Sep 2020 06:53:44 +0900 Subject: [PATCH 19/37] Add missing compact(struct_name) --- lib/fiddle/cparser.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/fiddle/cparser.rb b/lib/fiddle/cparser.rb index 91665a0c..13f38a13 100644 --- a/lib/fiddle/cparser.rb +++ b/lib/fiddle/cparser.rb @@ -47,8 +47,10 @@ def parse_struct_signature(signature, tymap=nil) case msig when Hash msig.each do |structure_name, struct_signature| + structure_name = structure_name.to_s if structure_name.is_a?(Symbol) + structure_name = compact(structure_name) structure_count = nil - if structure_name.to_s =~ /^([\w\*\s]+)\[(\d+)\]$/ + if structure_name =~ /^([\w\*\s]+)\[(\d+)\]$/ structure_count = $2.to_i structure_name = $1 end @@ -58,7 +60,7 @@ def parse_struct_signature(signature, tymap=nil) structure_klass = structure_parsed[2] ty = [structure_types, structure_count] ty << structure_klass if structure_klass - mems.push([structure_name.to_s, structure_members]) + mems.push([structure_name, structure_members]) tys.push(ty) end when /^[\w\*\s]+[\*\s](\w+)$/ From 7b61ad56a9ed17b680ad0ad37a8c017a765ca233 Mon Sep 17 00:00:00 2001 From: Sutou Kouhei Date: Sat, 12 Sep 2020 06:55:44 +0900 Subject: [PATCH 20/37] Extract existence method check --- lib/fiddle/struct.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/fiddle/struct.rb b/lib/fiddle/struct.rb index 65d23c86..f77a3d6c 100644 --- a/lib/fiddle/struct.rb +++ b/lib/fiddle/struct.rb @@ -117,10 +117,8 @@ def create(klass, types, members) define_singleton_method(:members) { members } define_singleton_method(:offset_of) { |mbr| klass.entity_class.compute_offset(types, members, mbr) } members.each{|name| - if name.kind_of?(Array) # name is a nested struct - next if method_defined?(name[0]) - name = name[0] - end + name = name[0] if name.kind_of?(Array) # name is a nested struct + next if method_defined?(name) define_method(name){ @entity[name] } define_method(name + "="){|val| @entity[name] = val } } From cc2978fab6b5b2aec4f6951655f0415e93bc825e Mon Sep 17 00:00:00 2001 From: Sutou Kouhei Date: Sat, 12 Sep 2020 06:57:50 +0900 Subject: [PATCH 21/37] Use is_a? instead of kind_of? for consistency --- lib/fiddle/cparser.rb | 2 +- lib/fiddle/struct.rb | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/fiddle/cparser.rb b/lib/fiddle/cparser.rb index 13f38a13..5ac8a46d 100644 --- a/lib/fiddle/cparser.rb +++ b/lib/fiddle/cparser.rb @@ -35,7 +35,7 @@ module CParser def parse_struct_signature(signature, tymap=nil) if signature.is_a?(String) signature = split_arguments(signature, /[,;]/) - elsif signature.kind_of?(Hash) + elsif signature.is_a?(Hash) signature = [signature] elsif signature.respond_to?(:types) && signature.respond_to?(:members) return signature.types, signature.members, signature.entity_class diff --git a/lib/fiddle/struct.rb b/lib/fiddle/struct.rb index f77a3d6c..84877de1 100644 --- a/lib/fiddle/struct.rb +++ b/lib/fiddle/struct.rb @@ -117,7 +117,7 @@ def create(klass, types, members) define_singleton_method(:members) { members } define_singleton_method(:offset_of) { |mbr| klass.entity_class.compute_offset(types, members, mbr) } members.each{|name| - name = name[0] if name.kind_of?(Array) # name is a nested struct + name = name[0] if name.is_a?(Array) # name is a nested struct next if method_defined?(name) define_method(name){ @entity[name] } define_method(name + "="){|val| @entity[name] = val } @@ -147,7 +147,7 @@ class CStructEntity < Fiddle::Pointer def CStructEntity.alignment(types) max = 1 types.each do |type, count = 1| - if type.kind_of?(Array) # nested struct + if type.is_a?(Array) # nested struct n = CStructEntity.alignment(type) else n = ALIGN_MAP[type] @@ -159,7 +159,7 @@ def CStructEntity.alignment(types) def CStructEntity.compute_offset(types, members, mbr) members.each_with_index do |m, idx| - if (m.kind_of?(Array) ? m[0] : m) == mbr.to_s + if (m.is_a?(Array) ? m[0] : m) == mbr.to_s return idx == 0 ? 0 : CStructEntity.size(types[0...idx]) end end @@ -195,7 +195,7 @@ def CStructEntity.size(types) max_align = types.map { |type, count = 1, klass = CStructEntity| last_offset = offset - if type.kind_of?(Array) # type is a nested array representing a nested struct + if type.is_a?(Array) # type is a nested array representing a nested struct align = klass.alignment(type) total_size = klass.size(type) offset = PackInfo.align(last_offset, align) + @@ -227,13 +227,13 @@ def initialize(addr, types, func = nil) # Set the names of the +members+ in this C struct def assign_names(members) - @members = members.map { |member| member.kind_of?(Array) ? member[0] : member } + @members = members.map { |member| member.is_a?(Array) ? member[0] : member } @nested_structs = {} @ctypes.each_with_index do |ty, idx| - if ty.kind_of?(Array) && ty[0].kind_of?(Array) + if ty.is_a?(Array) && ty[0].is_a?(Array) member = members[idx] - member = member[0] if member.kind_of?(Array) + member = member[0] if member.is_a?(Array) entity_class = CStructBuilder.create(CStruct, ty[0], members[idx][1]) @nested_structs[member] ||= if ty[1] NestedStructArray.new(ty[1].times.map do |i| @@ -254,7 +254,7 @@ def set_ctypes(types) max_align = types.map { |type, count = 1, klass = CStructEntity| orig_offset = offset - if type.kind_of?(Array) # type is a nested array representing a nested struct + if type.is_a?(Array) # type is a nested array representing a nested struct align = klass.alignment(type) total_size = klass.size(type) offset = PackInfo.align(orig_offset, align) @@ -299,7 +299,7 @@ def [](*args) end ty = @ctypes[idx] if( ty.is_a?(Array) ) - if ty.first.kind_of?(Array) + if ty.first.is_a?(Array) return @nested_structs[name] else r = super(@offset[idx], SIZE_MAP[ty[0]] * ty[1]) @@ -349,7 +349,7 @@ def []=(*args) raise(ArgumentError, "no such member: #{name}") end if @nested_structs[name] - if @nested_structs[name].kind_of?(Array) + if @nested_structs[name].is_a?(Array) val.size.times do |i| Fiddle.memcpy(@nested_structs[name][i], val[i]) end @@ -396,7 +396,7 @@ def CUnionEntity.compute_offset(types, members, mbr) # Fiddle::TYPE_VOIDP ]) #=> 8 def CUnionEntity.size(types) types.map { |type, count = 1, klass = CStructEntity| - if type.kind_of?(Array) # type is a nested array representing a nested struct + if type.is_a?(Array) # type is a nested array representing a nested struct klass.size(type) * (count || 1) else PackInfo::SIZE_MAP[type] * count From 483625a523f8264c6197b8c05ba2484ed0cb3140 Mon Sep 17 00:00:00 2001 From: Sutou Kouhei Date: Sat, 12 Sep 2020 07:13:40 +0900 Subject: [PATCH 22/37] Don't provide nil as count for signature --- lib/fiddle/cparser.rb | 8 +++-- lib/fiddle/struct.rb | 8 ++--- test/fiddle/test_cparser.rb | 69 +++++++++++++++++++++++++++++++++---- 3 files changed, 73 insertions(+), 12 deletions(-) diff --git a/lib/fiddle/cparser.rb b/lib/fiddle/cparser.rb index 5ac8a46d..dc6ec872 100644 --- a/lib/fiddle/cparser.rb +++ b/lib/fiddle/cparser.rb @@ -58,8 +58,12 @@ def parse_struct_signature(signature, tymap=nil) structure_types = structure_parsed[0] structure_members = structure_parsed[1] structure_klass = structure_parsed[2] - ty = [structure_types, structure_count] - ty << structure_klass if structure_klass + ty = [structure_types] + ty << structure_count if structure_count + if structure_klass + ty[2] = structure_klass + ty[1] ||= 1 + end mems.push([structure_name, structure_members]) tys.push(ty) end diff --git a/lib/fiddle/struct.rb b/lib/fiddle/struct.rb index 84877de1..10ec0308 100644 --- a/lib/fiddle/struct.rb +++ b/lib/fiddle/struct.rb @@ -199,7 +199,7 @@ def CStructEntity.size(types) align = klass.alignment(type) total_size = klass.size(type) offset = PackInfo.align(last_offset, align) + - (total_size * (count || 1)) + (total_size * count) else align = PackInfo::ALIGN_MAP[type] offset = PackInfo.align(last_offset, align) + @@ -259,12 +259,12 @@ def set_ctypes(types) total_size = klass.size(type) offset = PackInfo.align(orig_offset, align) @offset << offset - offset += (total_size * (count || 1)) + offset += (total_size * count) else align = ALIGN_MAP[type] offset = PackInfo.align(orig_offset, align) @offset << offset - offset += (SIZE_MAP[type] * (count || 1)) + offset += (SIZE_MAP[type] * count) end align @@ -397,7 +397,7 @@ def CUnionEntity.compute_offset(types, members, mbr) def CUnionEntity.size(types) types.map { |type, count = 1, klass = CStructEntity| if type.is_a?(Array) # type is a nested array representing a nested struct - klass.size(type) * (count || 1) + klass.size(type) * count else PackInfo::SIZE_MAP[type] * count end diff --git a/test/fiddle/test_cparser.rb b/test/fiddle/test_cparser.rb index 8b66347a..cb711794 100644 --- a/test/fiddle/test_cparser.rb +++ b/test/fiddle/test_cparser.rb @@ -2,6 +2,7 @@ begin require_relative 'helper' require 'fiddle/cparser' + require 'fiddle/import' rescue LoadError end @@ -77,24 +78,80 @@ def test_struct_array end def test_struct_nested_struct - assert_equal [[TYPE_INT, [[TYPE_INT, TYPE_CHAR], nil]], ['x', ['inner', ['i', 'c']]]], parse_struct_signature(['int x', {inner: ['int i', 'char c']}]) + assert_equal [[TYPE_INT, [[TYPE_INT, TYPE_CHAR]]], + ['x', ['inner', ['i', 'c']]]], + parse_struct_signature([ + 'int x', + {inner: ['int i', 'char c']}, + ]) + end + + def test_struct_nested_defined_struct + inner = Fiddle::Importer.struct(['int i', 'char c']) + assert_equal [[TYPE_INT, [[TYPE_INT, TYPE_CHAR], 1, inner.entity_class]], + ['x', ['inner', ['i', 'c']]]], + parse_struct_signature([ + 'int x', + {inner: inner}, + ]) end def test_struct_double_nested_struct - assert_equal [[TYPE_INT, [[TYPE_INT, [[TYPE_INT, TYPE_CHAR], nil]], nil]], ['x', ['outer', ['y', ['inner', ['i', 'c']]]]]], - parse_struct_signature(['int x', {outer: ['int y', { inner: ['int i', 'char c'] }]}]) + assert_equal [[TYPE_INT, [[TYPE_INT, [[TYPE_INT, TYPE_CHAR]]]]], + ['x', ['outer', ['y', ['inner', ['i', 'c']]]]]], + parse_struct_signature([ + 'int x', + { + outer: [ + 'int y', + {inner: ['int i', 'char c']}, + ], + }, + ]) end def test_struct_nested_struct_array - assert_equal [[TYPE_INT, [[TYPE_INT, TYPE_CHAR], 2]], ['x', ['inner', ['i', 'c']]]], parse_struct_signature(['int x', {'inner[2]' => ['int i', 'char c']}]) + assert_equal [[TYPE_INT, [[TYPE_INT, TYPE_CHAR], 2]], + ['x', ['inner', ['i', 'c']]]], + parse_struct_signature([ + 'int x', + { + 'inner[2]' => [ + 'int i', + 'char c', + ], + }, + ]) end def test_struct_double_nested_struct_inner_array - assert_equal [[[[TYPE_INT, [[TYPE_INT, TYPE_CHAR], 2]], nil]], [['outer', ['x', ['inner', ['i', 'c']]]]]], parse_struct_signature(outer: ['int x', { 'inner[2]' => ['int i', 'char c'] }]) + assert_equal [[[[TYPE_INT, [[TYPE_INT, TYPE_CHAR], 2]]]], + [['outer', ['x', ['inner', ['i', 'c']]]]]], + parse_struct_signature(outer: [ + 'int x', + { + 'inner[2]' => [ + 'int i', + 'char c', + ], + }, + ]) end def test_struct_double_nested_struct_outer_array - assert_equal [[TYPE_INT, [[[[TYPE_INT, TYPE_CHAR], nil]], 2]], ['x', ['outer', [['inner', ['i', 'c']]]]]], parse_struct_signature(['int x', {'outer[2]' => { inner: ['int i', 'char c'] }}]) + assert_equal [[TYPE_INT, [[[[TYPE_INT, TYPE_CHAR]]], 2]], + ['x', ['outer', [['inner', ['i', 'c']]]]]], + parse_struct_signature([ + 'int x', + { + 'outer[2]' => { + inner: [ + 'int i', + 'char c', + ], + }, + }, + ]) end def test_struct_array_str From 240199ef9a710344f025418154428ad136abd9fa Mon Sep 17 00:00:00 2001 From: Sutou Kouhei Date: Sat, 12 Sep 2020 07:28:11 +0900 Subject: [PATCH 23/37] Simplify --- lib/fiddle/struct.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/fiddle/struct.rb b/lib/fiddle/struct.rb index 10ec0308..606ce28b 100644 --- a/lib/fiddle/struct.rb +++ b/lib/fiddle/struct.rb @@ -148,7 +148,7 @@ def CStructEntity.alignment(types) max = 1 types.each do |type, count = 1| if type.is_a?(Array) # nested struct - n = CStructEntity.alignment(type) + n = alignment(type) else n = ALIGN_MAP[type] end @@ -160,7 +160,7 @@ def CStructEntity.alignment(types) def CStructEntity.compute_offset(types, members, mbr) members.each_with_index do |m, idx| if (m.is_a?(Array) ? m[0] : m) == mbr.to_s - return idx == 0 ? 0 : CStructEntity.size(types[0...idx]) + return idx == 0 ? 0 : size(types[0...idx]) end end raise(ArgumentError, "no such member: #{mbr}") From 358d40ae624c1e3f0fa1a8b81d97ad2f1bd1aef2 Mon Sep 17 00:00:00 2001 From: Sutou Kouhei Date: Sat, 12 Sep 2020 07:30:26 +0900 Subject: [PATCH 24/37] Unify common code --- lib/fiddle/struct.rb | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/lib/fiddle/struct.rb b/lib/fiddle/struct.rb index 606ce28b..c411ecd6 100644 --- a/lib/fiddle/struct.rb +++ b/lib/fiddle/struct.rb @@ -197,14 +197,13 @@ def CStructEntity.size(types) if type.is_a?(Array) # type is a nested array representing a nested struct align = klass.alignment(type) - total_size = klass.size(type) - offset = PackInfo.align(last_offset, align) + - (total_size * count) + type_size = klass.size(type) else align = PackInfo::ALIGN_MAP[type] - offset = PackInfo.align(last_offset, align) + - (PackInfo::SIZE_MAP[type] * count) + type_size = PackInfo::SIZE_MAP[type] end + offset = PackInfo.align(last_offset, align) + + (type_size * count) align }.max @@ -256,16 +255,14 @@ def set_ctypes(types) orig_offset = offset if type.is_a?(Array) # type is a nested array representing a nested struct align = klass.alignment(type) - total_size = klass.size(type) - offset = PackInfo.align(orig_offset, align) - @offset << offset - offset += (total_size * count) + type_size = klass.size(type) else align = ALIGN_MAP[type] - offset = PackInfo.align(orig_offset, align) - @offset << offset - offset += (SIZE_MAP[type] * count) + type_size = SIZE_MAP[type] end + offset = PackInfo.align(orig_offset, align) + @offset << offset + offset += (type_size * count) align }.max From 642a4502e1445f51a40dd2391cdf073821d50e58 Mon Sep 17 00:00:00 2001 From: Sutou Kouhei Date: Sat, 12 Sep 2020 07:33:46 +0900 Subject: [PATCH 25/37] Respect entity class in types --- lib/fiddle/struct.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/fiddle/struct.rb b/lib/fiddle/struct.rb index c411ecd6..d7d2917a 100644 --- a/lib/fiddle/struct.rb +++ b/lib/fiddle/struct.rb @@ -146,9 +146,9 @@ class CStructEntity < Fiddle::Pointer def CStructEntity.alignment(types) max = 1 - types.each do |type, count = 1| + types.each do |type, count = 1, klass = CStructEntity| if type.is_a?(Array) # nested struct - n = alignment(type) + n = klass.alignment(type) else n = ALIGN_MAP[type] end From 48481861818098ef5e75313fc25b9d5d871a019e Mon Sep 17 00:00:00 2001 From: Sutou Kouhei Date: Sat, 12 Sep 2020 07:55:12 +0900 Subject: [PATCH 26/37] Simplify --- lib/fiddle/struct.rb | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/lib/fiddle/struct.rb b/lib/fiddle/struct.rb index d7d2917a..6ff592b1 100644 --- a/lib/fiddle/struct.rb +++ b/lib/fiddle/struct.rb @@ -226,22 +226,33 @@ def initialize(addr, types, func = nil) # Set the names of the +members+ in this C struct def assign_names(members) - @members = members.map { |member| member.is_a?(Array) ? member[0] : member } - + @members = [] @nested_structs = {} - @ctypes.each_with_index do |ty, idx| - if ty.is_a?(Array) && ty[0].is_a?(Array) - member = members[idx] - member = member[0] if member.is_a?(Array) - entity_class = CStructBuilder.create(CStruct, ty[0], members[idx][1]) - @nested_structs[member] ||= if ty[1] - NestedStructArray.new(ty[1].times.map do |i| - entity_class.new(to_i + @offset[idx] + i * (ty[2] || CStructEntity).size(ty[0])) - end) + members.each_with_index do |member, index| + if member.is_a?(Array) # nested struct + member_name = member[0] + struct_member_names = member[1] + type = @ctypes[index] + struct_types = type[0] + count = type[1] || 1 + entity_class = type[2] || CStructEntity + struct_class = CStructBuilder.create(CStruct, + struct_types, + struct_member_names) + if count == 1 + struct = struct_class.new(to_i + @offset[index]) else - entity_class.new(to_i + @offset[idx]) + entity_size = entity_class.size(struct_types) + structs = count.times.map do |i| + struct_class.new(to_i + @offset[index] + i * entity_size) + end + struct = NestedStructArray.new(structs) end + @nested_structs[member_name] = struct + else + member_name = member end + @members << member_name end end From 40669d3703b5fb46c48268cb9f2698f703d3fb44 Mon Sep 17 00:00:00 2001 From: Sutou Kouhei Date: Sat, 12 Sep 2020 09:46:34 +0900 Subject: [PATCH 27/37] Don't use Pointer#size as the size of address --- lib/fiddle/struct.rb | 3 +-- test/fiddle/test_import.rb | 22 ++++++++++++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/lib/fiddle/struct.rb b/lib/fiddle/struct.rb index 6ff592b1..1a92cd69 100644 --- a/lib/fiddle/struct.rb +++ b/lib/fiddle/struct.rb @@ -321,11 +321,10 @@ def [](*args) when Array case ty[0] when TYPE_VOIDP - val = val.collect{|v| v = Pointer.new(v); v.size = SIZEOF_VOIDP; v } + val = val.collect{|v| Pointer.new(v) } end when TYPE_VOIDP val = Pointer.new(val[0]) - val.size = SIZEOF_VOIDP else val = val[0] end diff --git a/test/fiddle/test_import.rb b/test/fiddle/test_import.rb index 9c4f2221..7a32b427 100644 --- a/test/fiddle/test_import.rb +++ b/test/fiddle/test_import.rb @@ -42,7 +42,7 @@ module LIBC position: [ "float x", "float y", "float z" ], texcoord: [ "float u", "float v" ] }, - object: [ "int id" ] + object: [ "int id", "void *user_data" ], }, "int id" ] @@ -185,7 +185,16 @@ def test_nested_struct_reusing_other_structs() position_struct = Fiddle::Importer.struct([ 'float x', 'float y', 'float z' ]) texcoord_struct = Fiddle::Importer.struct([ 'float u', 'float v' ]) vertex_struct = Fiddle::Importer.struct(position: position_struct, texcoord: texcoord_struct) - mesh_struct = Fiddle::Importer.struct([{"vertices[2]" => vertex_struct, object: [ "int id" ]}, "int id"]) + mesh_struct = Fiddle::Importer.struct([ + { + "vertices[2]" => vertex_struct, + object: [ + "int id", + "void *user_data", + ], + }, + "int id", + ]) assert_equal LIBC::StructNestedStruct.size, mesh_struct.size @@ -218,6 +227,8 @@ def test_struct_nested_struct_members() s.vertices[1].texcoord.u = 9 s.vertices[1].texcoord.v = 10 s.object.id = 100 + user_data = Fiddle::Pointer.malloc(24) + s.object.user_data = user_data s.id = 101 assert_equal(1, s.vertices[0].position.x) assert_equal(2, s.vertices[0].position.y) @@ -230,6 +241,7 @@ def test_struct_nested_struct_members() assert_equal(9, s.vertices[1].texcoord.u) assert_equal(10, s.vertices[1].texcoord.v) assert_equal(100, s.object.id) + assert_equal(user_data, s.object.user_data) assert_equal(101, s.id) end @@ -241,12 +253,6 @@ def test_union_nested_struct_members() refute_equal( 0, s.mouse.x) end - def test_size_of_struct_accessor_returning_void_pointer() - s = Fiddle::Importer.struct(['void *x', 'void *y[2]']).malloc - assert_equal Fiddle::SIZEOF_VOIDP, s['x'].size - assert_equal Fiddle::SIZEOF_VOIDP, s['y'][0].size - end - def test_struct_size_and_offset_of_nested_unions() a = Fiddle::Importer.union ['float f[4]', 'int i[2]'] b = Fiddle::Importer.struct ['float x', 'int y'] From 67a040e33af3c51f1e652378a4c8282dc875e4a0 Mon Sep 17 00:00:00 2001 From: Sutou Kouhei Date: Sat, 12 Sep 2020 11:02:27 +0900 Subject: [PATCH 28/37] Assign struct member value one by one --- lib/fiddle/struct.rb | 74 +++++++++++++++++++---- test/fiddle/test_import.rb | 117 ++++++++++++++++++++++++++++++++----- 2 files changed, 164 insertions(+), 27 deletions(-) diff --git a/lib/fiddle/struct.rb b/lib/fiddle/struct.rb index 1a92cd69..a84725f4 100644 --- a/lib/fiddle/struct.rb +++ b/lib/fiddle/struct.rb @@ -6,10 +6,54 @@ module Fiddle # A base class for objects representing a C structure class CStruct + include Enumerable + # accessor to Fiddle::CStructEntity def CStruct.entity_class CStructEntity end + + def each + return enum_for(__function__) unless block_given? + + self.class.members.each do |name,| + yield(self[name]) + end + end + + def each_pair + return enum_for(__function__) unless block_given? + + self.class.members.each do |name,| + yield(name, self[name]) + end + end + + def to_h + hash = {} + each_pair do |name, value| + value = value.to_h if value.is_a?(CStruct) + hash[name] = value + end + hash + end + + def replace(another) + if another.nil? + self.class.members.each do |name,| + self[name] = nil + end + elsif another.respond_to?(:each_pair) + another.each_pair do |name, value| + self[name] = value + end + else + another.each do |name, value| + self[name] = value + end + end + self + end end # A base class for objects representing a C union @@ -50,7 +94,7 @@ def []=(index, value) # Wrapper for arrays of structs within a struct class NestedStructArray < Array def []=(index, value) - Fiddle.memcpy(self[index], value) + self[index].replace(value) end end @@ -351,19 +395,27 @@ def [](*args) def []=(*args) return super(*args) if args.size > 2 name, val = *args - idx = @members.index(name) - if( idx.nil? ) - raise(ArgumentError, "no such member: #{name}") - end - if @nested_structs[name] - if @nested_structs[name].is_a?(Array) - val.size.times do |i| - Fiddle.memcpy(@nested_structs[name][i], val[i]) + name = name.to_s if name.is_a?(Symbol) + nested_struct = @nested_structs[name] + if nested_struct + if nested_struct.is_a?(NestedStructArray) + if val.nil? + nested_struct.each do |s| + s.replace(nil) + end + else + val.each_with_index do |v, i| + nested_struct[i] = v + end end else - Fiddle.memcpy(@nested_structs[name], val) + nested_struct.replace(val) end - return + return val + end + idx = @members.index(name) + if( idx.nil? ) + raise(ArgumentError, "no such member: #{name}") end ty = @ctypes[idx] packer = Packer.new([ty]) diff --git a/test/fiddle/test_import.rb b/test/fiddle/test_import.rb index 7a32b427..29b2badf 100644 --- a/test/fiddle/test_import.rb +++ b/test/fiddle/test_import.rb @@ -295,6 +295,47 @@ def test_struct_nested_struct_replace_array_element() refute_equal(vertex.to_ptr, s.vertices[0].to_ptr) end + def test_struct_nested_struct_replace_array_element_nil() + s = LIBC::StructNestedStruct.malloc + s.vertices[0].position.x = 5 + s.vertices[0] = nil + assert_equal({ + "position" => { + "x" => 0.0, + "y" => 0.0, + "z" => 0.0, + }, + "texcoord" => { + "u" => 0.0, + "v" => 0.0, + }, + }, + s.vertices[0].to_h) + end + + def test_struct_nested_struct_replace_array_element_hash() + s = LIBC::StructNestedStruct.malloc + s.vertices[0].position.x = 5 + s.vertices[0] = { + position: { + x: 10, + y: 100, + } + } + assert_equal({ + "position" => { + "x" => 10.0, + "y" => 100.0, + "z" => 0.0, + }, + "texcoord" => { + "u" => 0.0, + "v" => 0.0, + }, + }, + s.vertices[0].to_h) + end + def test_struct_nested_struct_replace_entire_array() s = LIBC::StructNestedStruct.malloc vertex_struct = Fiddle::Importer.struct [{ @@ -302,11 +343,6 @@ def test_struct_nested_struct_replace_entire_array() texcoord: [ "float u", "float v" ] }] - different_struct_same_size = Fiddle::Importer.struct [{ - a: [ 'float i', 'float j', 'float k' ], - b: [ 'float l', 'float m' ] - }] - same = [vertex_struct.malloc, vertex_struct.malloc] same[0].position.x = 1; same[1].position.x = 6 same[0].position.y = 2; same[1].position.y = 7 @@ -314,11 +350,39 @@ def test_struct_nested_struct_replace_entire_array() same[0].texcoord.u = 4; same[1].texcoord.u = 9 same[0].texcoord.v = 5; same[1].texcoord.v = 10 s.vertices = same - assert_equal(1, s.vertices[0].position.x); assert_equal(6, s.vertices[1].position.x) - assert_equal(2, s.vertices[0].position.y); assert_equal(7, s.vertices[1].position.y) - assert_equal(3, s.vertices[0].position.z); assert_equal(8, s.vertices[1].position.z) - assert_equal(4, s.vertices[0].texcoord.u); assert_equal(9, s.vertices[1].texcoord.u) - assert_equal(5, s.vertices[0].texcoord.v); assert_equal(10, s.vertices[1].texcoord.v) + assert_equal([ + { + "position" => { + "x" => 1.0, + "y" => 2.0, + "z" => 3.0, + }, + "texcoord" => { + "u" => 4.0, + "v" => 5.0, + }, + }, + { + "position" => { + "x" => 6.0, + "y" => 7.0, + "z" => 8.0, + }, + "texcoord" => { + "u" => 9.0, + "v" => 10.0, + }, + } + ], + s.vertices.collect(&:to_h)) + end + + def test_struct_nested_struct_replace_entire_array_with_different_struct() + s = LIBC::StructNestedStruct.malloc + different_struct_same_size = Fiddle::Importer.struct [{ + a: [ 'float i', 'float j', 'float k' ], + b: [ 'float l', 'float m' ] + }] different = [different_struct_same_size.malloc, different_struct_same_size.malloc] different[0].a.i = 11; different[1].a.i = 16 @@ -326,12 +390,33 @@ def test_struct_nested_struct_replace_entire_array() different[0].a.k = 13; different[1].a.k = 18 different[0].b.l = 14; different[1].b.l = 19 different[0].b.m = 15; different[1].b.m = 20 - s.vertices = different - assert_equal(11, s.vertices[0].position.x); assert_equal(16, s.vertices[1].position.x) - assert_equal(12, s.vertices[0].position.y); assert_equal(17, s.vertices[1].position.y) - assert_equal(13, s.vertices[0].position.z); assert_equal(18, s.vertices[1].position.z) - assert_equal(14, s.vertices[0].texcoord.u); assert_equal(19, s.vertices[1].texcoord.u) - assert_equal(15, s.vertices[0].texcoord.v); assert_equal(20, s.vertices[1].texcoord.v) + s.vertices[0][0, s.vertices[0].class.size] = different[0].to_ptr + s.vertices[1][0, s.vertices[1].class.size] = different[1].to_ptr + assert_equal([ + { + "position" => { + "x" => 11.0, + "y" => 12.0, + "z" => 13.0, + }, + "texcoord" => { + "u" => 14.0, + "v" => 15.0, + }, + }, + { + "position" => { + "x" => 16.0, + "y" => 17.0, + "z" => 18.0, + }, + "texcoord" => { + "u" => 19.0, + "v" => 20.0, + }, + } + ], + s.vertices.collect(&:to_h)) end def test_struct() From 288b11b5d8d3521204fc7d20f28e7bb4a40e0ea8 Mon Sep 17 00:00:00 2001 From: Sutou Kouhei Date: Sat, 12 Sep 2020 11:13:29 +0900 Subject: [PATCH 29/37] Adjust style --- test/fiddle/test_import.rb | 376 ++++++++++++++++++++----------------- 1 file changed, 206 insertions(+), 170 deletions(-) diff --git a/test/fiddle/test_import.rb b/test/fiddle/test_import.rb index 29b2badf..60a9da33 100644 --- a/test/fiddle/test_import.rb +++ b/test/fiddle/test_import.rb @@ -39,10 +39,10 @@ module LIBC StructNestedStruct = struct [ { "vertices[2]" => { - position: [ "float x", "float y", "float z" ], - texcoord: [ "float u", "float v" ] + position: ["float x", "float y", "float z"], + texcoord: ["float u", "float v"] }, - object: [ "int id", "void *user_data" ], + object: ["int id", "void *user_data"], }, "int id" ] @@ -182,8 +182,8 @@ def test_struct_array_assignment() end def test_nested_struct_reusing_other_structs() - position_struct = Fiddle::Importer.struct([ 'float x', 'float y', 'float z' ]) - texcoord_struct = Fiddle::Importer.struct([ 'float u', 'float v' ]) + position_struct = Fiddle::Importer.struct(['float x', 'float y', 'float z']) + texcoord_struct = Fiddle::Importer.struct(['float u', 'float v']) vertex_struct = Fiddle::Importer.struct(position: position_struct, texcoord: texcoord_struct) mesh_struct = Fiddle::Importer.struct([ { @@ -198,8 +198,8 @@ def test_nested_struct_reusing_other_structs() assert_equal LIBC::StructNestedStruct.size, mesh_struct.size - keyboard_event_struct = Fiddle::Importer.struct([ 'unsigned int state', 'char key' ]) - mouse_event_struct = Fiddle::Importer.struct([ 'unsigned int button', 'unsigned short x', 'unsigned short y' ]) + keyboard_event_struct = Fiddle::Importer.struct(['unsigned int state', 'char key']) + mouse_event_struct = Fiddle::Importer.struct(['unsigned int button', 'unsigned short x', 'unsigned short y']) event_union = Fiddle::Importer.union([{ keboard: keyboard_event_struct, mouse: mouse_event_struct}]) assert_equal LIBC::UnionNestedStruct.size, event_union.size end @@ -207,56 +207,79 @@ def test_nested_struct_reusing_other_structs() def test_nested_struct_alignment_is_not_its_size() inner = Fiddle::Importer.struct(['int x', 'int y', 'int z', 'int w']) outer = Fiddle::Importer.struct(['char a', { 'nested' => inner }, 'char b']) - instance = outer.malloc - offset = instance.to_ptr.instance_variable_get(:"@offset") - assert_equal Fiddle::SIZEOF_INT * 5, offset.last - assert_equal Fiddle::SIZEOF_INT * 6, outer.size - assert_equal instance.to_ptr.size, outer.size + outer.malloc(Fiddle::RUBY_FREE) do |instance| + offset = instance.to_ptr.instance_variable_get(:"@offset") + assert_equal Fiddle::SIZEOF_INT * 5, offset.last + assert_equal Fiddle::SIZEOF_INT * 6, outer.size + assert_equal instance.to_ptr.size, outer.size + end end def test_struct_nested_struct_members() - s = LIBC::StructNestedStruct.malloc - s.vertices[0].position.x = 1 - s.vertices[0].position.y = 2 - s.vertices[0].position.z = 3 - s.vertices[0].texcoord.u = 4 - s.vertices[0].texcoord.v = 5 - s.vertices[1].position.x = 6 - s.vertices[1].position.y = 7 - s.vertices[1].position.z = 8 - s.vertices[1].texcoord.u = 9 - s.vertices[1].texcoord.v = 10 - s.object.id = 100 - user_data = Fiddle::Pointer.malloc(24) - s.object.user_data = user_data - s.id = 101 - assert_equal(1, s.vertices[0].position.x) - assert_equal(2, s.vertices[0].position.y) - assert_equal(3, s.vertices[0].position.z) - assert_equal(4, s.vertices[0].texcoord.u) - assert_equal(5, s.vertices[0].texcoord.v) - assert_equal(6, s.vertices[1].position.x) - assert_equal(7, s.vertices[1].position.y) - assert_equal(8, s.vertices[1].position.z) - assert_equal(9, s.vertices[1].texcoord.u) - assert_equal(10, s.vertices[1].texcoord.v) - assert_equal(100, s.object.id) - assert_equal(user_data, s.object.user_data) - assert_equal(101, s.id) + LIBC::StructNestedStruct.malloc(Fiddle::RUBY_FREE) do |s| + Fiddle::Pointer.malloc(24, Fiddle::RUBY_FREE) do |user_data| + s.vertices[0].position.x = 1 + s.vertices[0].position.y = 2 + s.vertices[0].position.z = 3 + s.vertices[0].texcoord.u = 4 + s.vertices[0].texcoord.v = 5 + s.vertices[1].position.x = 6 + s.vertices[1].position.y = 7 + s.vertices[1].position.z = 8 + s.vertices[1].texcoord.u = 9 + s.vertices[1].texcoord.v = 10 + s.object.id = 100 + s.object.user_data = user_data + s.id = 101 + assert_equal({ + "vertices" => [ + { + "position" => { + "x" => 1, + "y" => 2, + "z" => 3, + }, + "texcoord" => { + "u" => 4, + "v" => 5, + }, + }, + { + "position" => { + "x" => 6, + "y" => 7, + "z" => 8, + }, + "texcoord" => { + "u" => 9, + "v" => 10, + }, + }, + ], + "object" => { + "id" => 100, + "user_data" => user_data, + }, + "id" => 101, + }, + s.to_h) + end + end end def test_union_nested_struct_members() - s = LIBC::UnionNestedStruct.malloc - s.keyboard.state = 100 - s.keyboard.key = 101 - assert_equal(100, s.mouse.button) - refute_equal( 0, s.mouse.x) + LIBC::UnionNestedStruct.malloc(Fiddle::RUBY_FREE) do |s| + s.keyboard.state = 100 + s.keyboard.key = 101 + assert_equal(100, s.mouse.button) + refute_equal( 0, s.mouse.x) + end end def test_struct_size_and_offset_of_nested_unions() a = Fiddle::Importer.union ['float f[4]', 'int i[2]'] b = Fiddle::Importer.struct ['float x', 'int y'] - c = Fiddle::Importer.struct [ a: a, b: b ] + c = Fiddle::Importer.struct [a: a, b: b] assert_equal 0, a.offset_of(:f) assert_equal 0, a.offset_of(:i) @@ -277,146 +300,159 @@ def test_struct_size_and_offset_of_nested_unions() end def test_struct_nested_struct_replace_array_element() - s = LIBC::StructNestedStruct.malloc - s.vertices[0].position.x = 5 - - vertex_struct = Fiddle::Importer.struct [{ - position: [ "float x", "float y", "float z" ], - texcoord: [ "float u", "float v" ] - }] - vertex = vertex_struct.malloc - vertex.position.x = 100 - s.vertices[0] = vertex - - # make sure element was copied by value, but things like memory address - # should not be changed - assert_equal(100, s.vertices[0].position.x) - refute_equal(vertex.object_id, s.vertices[0].object_id) - refute_equal(vertex.to_ptr, s.vertices[0].to_ptr) + LIBC::StructNestedStruct.malloc(Fiddle::RUBY_FREE) do |s| + s.vertices[0].position.x = 5 + + vertex_struct = Fiddle::Importer.struct [{ + position: ["float x", "float y", "float z"], + texcoord: ["float u", "float v"] + }] + vertex_struct.malloc(Fiddle::RUBY_FREE) do |vertex| + vertex.position.x = 100 + s.vertices[0] = vertex + + # make sure element was copied by value, but things like memory address + # should not be changed + assert_equal(100, s.vertices[0].position.x) + refute_equal(vertex.object_id, s.vertices[0].object_id) + refute_equal(vertex.to_ptr, s.vertices[0].to_ptr) + end + end end def test_struct_nested_struct_replace_array_element_nil() - s = LIBC::StructNestedStruct.malloc - s.vertices[0].position.x = 5 - s.vertices[0] = nil - assert_equal({ - "position" => { - "x" => 0.0, - "y" => 0.0, - "z" => 0.0, - }, - "texcoord" => { - "u" => 0.0, - "v" => 0.0, + LIBC::StructNestedStruct.malloc(Fiddle::RUBY_FREE) do |s| + s.vertices[0].position.x = 5 + s.vertices[0] = nil + assert_equal({ + "position" => { + "x" => 0.0, + "y" => 0.0, + "z" => 0.0, + }, + "texcoord" => { + "u" => 0.0, + "v" => 0.0, + }, }, - }, - s.vertices[0].to_h) + s.vertices[0].to_h) + end end def test_struct_nested_struct_replace_array_element_hash() - s = LIBC::StructNestedStruct.malloc - s.vertices[0].position.x = 5 - s.vertices[0] = { - position: { - x: 10, - y: 100, + LIBC::StructNestedStruct.malloc(Fiddle::RUBY_FREE) do |s| + s.vertices[0] = { + position: { + x: 10, + y: 100, + } } - } - assert_equal({ - "position" => { - "x" => 10.0, - "y" => 100.0, - "z" => 0.0, - }, - "texcoord" => { - "u" => 0.0, - "v" => 0.0, - }, - }, - s.vertices[0].to_h) - end - - def test_struct_nested_struct_replace_entire_array() - s = LIBC::StructNestedStruct.malloc - vertex_struct = Fiddle::Importer.struct [{ - position: [ "float x", "float y", "float z" ], - texcoord: [ "float u", "float v" ] - }] - - same = [vertex_struct.malloc, vertex_struct.malloc] - same[0].position.x = 1; same[1].position.x = 6 - same[0].position.y = 2; same[1].position.y = 7 - same[0].position.z = 3; same[1].position.z = 8 - same[0].texcoord.u = 4; same[1].texcoord.u = 9 - same[0].texcoord.v = 5; same[1].texcoord.v = 10 - s.vertices = same - assert_equal([ - { + assert_equal({ "position" => { - "x" => 1.0, - "y" => 2.0, - "z" => 3.0, + "x" => 10.0, + "y" => 100.0, + "z" => 0.0, }, "texcoord" => { - "u" => 4.0, - "v" => 5.0, + "u" => 0.0, + "v" => 0.0, }, }, - { - "position" => { - "x" => 6.0, - "y" => 7.0, - "z" => 8.0, - }, - "texcoord" => { - "u" => 9.0, - "v" => 10.0, - }, - } - ], - s.vertices.collect(&:to_h)) + s.vertices[0].to_h) + end + end + + def test_struct_nested_struct_replace_entire_array() + LIBC::StructNestedStruct.malloc(Fiddle::RUBY_FREE) do |s| + vertex_struct = Fiddle::Importer.struct [{ + position: ["float x", "float y", "float z"], + texcoord: ["float u", "float v"] + }] + + vertex_struct.malloc(Fiddle::RUBY_FREE) do |same0| + vertex_struct.malloc(Fiddle::RUBY_FREE) do |same1| + same = [same0, same1] + same[0].position.x = 1; same[1].position.x = 6 + same[0].position.y = 2; same[1].position.y = 7 + same[0].position.z = 3; same[1].position.z = 8 + same[0].texcoord.u = 4; same[1].texcoord.u = 9 + same[0].texcoord.v = 5; same[1].texcoord.v = 10 + s.vertices = same + assert_equal([ + { + "position" => { + "x" => 1.0, + "y" => 2.0, + "z" => 3.0, + }, + "texcoord" => { + "u" => 4.0, + "v" => 5.0, + }, + }, + { + "position" => { + "x" => 6.0, + "y" => 7.0, + "z" => 8.0, + }, + "texcoord" => { + "u" => 9.0, + "v" => 10.0, + }, + } + ], + s.vertices.collect(&:to_h)) + end + end + end end def test_struct_nested_struct_replace_entire_array_with_different_struct() - s = LIBC::StructNestedStruct.malloc - different_struct_same_size = Fiddle::Importer.struct [{ - a: [ 'float i', 'float j', 'float k' ], - b: [ 'float l', 'float m' ] - }] - - different = [different_struct_same_size.malloc, different_struct_same_size.malloc] - different[0].a.i = 11; different[1].a.i = 16 - different[0].a.j = 12; different[1].a.j = 17 - different[0].a.k = 13; different[1].a.k = 18 - different[0].b.l = 14; different[1].b.l = 19 - different[0].b.m = 15; different[1].b.m = 20 - s.vertices[0][0, s.vertices[0].class.size] = different[0].to_ptr - s.vertices[1][0, s.vertices[1].class.size] = different[1].to_ptr - assert_equal([ - { - "position" => { - "x" => 11.0, - "y" => 12.0, - "z" => 13.0, - }, - "texcoord" => { - "u" => 14.0, - "v" => 15.0, - }, - }, - { - "position" => { - "x" => 16.0, - "y" => 17.0, - "z" => 18.0, - }, - "texcoord" => { - "u" => 19.0, - "v" => 20.0, - }, - } - ], - s.vertices.collect(&:to_h)) + LIBC::StructNestedStruct.malloc(Fiddle::RUBY_FREE) do |s| + different_struct_same_size = Fiddle::Importer.struct [{ + a: ['float i', 'float j', 'float k'], + b: ['float l', 'float m'] + }] + + different_struct_same_size.malloc(Fiddle::RUBY_FREE) do |different0| + different_struct_same_size.malloc(Fiddle::RUBY_FREE) do |different1| + different = [different0, different1] + different[0].a.i = 11; different[1].a.i = 16 + different[0].a.j = 12; different[1].a.j = 17 + different[0].a.k = 13; different[1].a.k = 18 + different[0].b.l = 14; different[1].b.l = 19 + different[0].b.m = 15; different[1].b.m = 20 + s.vertices[0][0, s.vertices[0].class.size] = different[0].to_ptr + s.vertices[1][0, s.vertices[1].class.size] = different[1].to_ptr + assert_equal([ + { + "position" => { + "x" => 11.0, + "y" => 12.0, + "z" => 13.0, + }, + "texcoord" => { + "u" => 14.0, + "v" => 15.0, + }, + }, + { + "position" => { + "x" => 16.0, + "y" => 17.0, + "z" => 18.0, + }, + "texcoord" => { + "u" => 19.0, + "v" => 20.0, + }, + } + ], + s.vertices.collect(&:to_h)) + end + end + end end def test_struct() From bffce703a8a3b71bc806f8f7e7980bf5059c398c Mon Sep 17 00:00:00 2001 From: Sutou Kouhei Date: Sun, 13 Sep 2020 06:09:12 +0900 Subject: [PATCH 30/37] Remove memcpy because we have "Pointer[offset, length] = pointer" --- ext/fiddle/fiddle.c | 89 -------------------------------------- test/fiddle/test_fiddle.rb | 23 ---------- 2 files changed, 112 deletions(-) diff --git a/ext/fiddle/fiddle.c b/ext/fiddle/fiddle.c index 807185db..5635ab32 100644 --- a/ext/fiddle/fiddle.c +++ b/ext/fiddle/fiddle.c @@ -53,94 +53,6 @@ rb_fiddle_free(VALUE self, VALUE addr) return Qnil; } -static void -rb_fiddle_extract_address(VALUE object, void **address, long *size) -{ - ID id_to_ptr; - ID id_to_i; - ID id_size; - CONST_ID(id_to_ptr, "to_ptr"); - CONST_ID(id_to_i, "to_i"); - CONST_ID(id_size, "size"); - - if (RB_TEST(rb_respond_to(object, id_to_ptr))) { - object = rb_funcall(object, id_to_ptr, 0); - } - *size = -1; - if (!RB_INTEGER_TYPE_P(object)) { - if (RB_TEST(rb_respond_to(object, id_size))) { - *size = NUM2LONG(rb_funcall(object, id_size, 0)); - } - object = rb_funcall(object, id_to_i, 0); - } - *address = NUM2PTR(object); - - if (!*address) { - rb_raise(rb_eArgError, - "must not NULL pointer: %"PRIsVALUE, - object); - } -} - -/* - * call-seq: - * - * Fiddle.memcpy(dest, src) => number - * Fiddle.memcpy(dest, src, n) => number - * - * Copies the contents of the given +src+ pointer like object into the - * given +dest+ pointer like object. If +n+ is specified, +n+ bytes - * data of +src+ is copied to +dest+. If +n+ isn't specified, it will - * copy up to the allocated size of the given +src+ pointer like - * object or the allocated size of the given +src+ pointer like - * object, whichever is smaller (to prevent overflow). - * - * Returns the number of bytes actually copied. - */ -static VALUE -rb_fiddle_memcpy(int argc, VALUE *argv, VALUE self) -{ - VALUE dest; - VALUE src; - VALUE n; - - rb_scan_args(argc, argv, "21", &dest, &src, &n); - - void *dest_address = NULL; - long dest_size = -1; - void *src_address = NULL; - long src_size = -1; - rb_fiddle_extract_address(dest, &dest_address, &dest_size); - rb_fiddle_extract_address(src, &src_address, &src_size); - - size_t memcpy_size = 0; - if (NIL_P(n)) { - if (dest_size < 0 && src_size < 0) { - rb_raise(rb_eArgError, - "must specify copy size for raw pointers: " - "dest: %"PRIsVALUE ", src: %" PRIsVALUE, - dest, src); - } - if (dest_size < 0) { - memcpy_size = src_size; - } - else if (src_size < 0) { - memcpy_size = src_size; - } - else - { - memcpy_size = dest_size > src_size ? src_size : dest_size; - } - } - else { - memcpy_size = NUM2SIZET(n); - } - - memcpy(dest_address, src_address, memcpy_size); - - return SIZET2NUM(memcpy_size); -} - /* * call-seq: Fiddle.dlunwrap(addr) * @@ -545,7 +457,6 @@ Init_fiddle(void) rb_define_module_function(mFiddle, "malloc", rb_fiddle_malloc, 1); rb_define_module_function(mFiddle, "realloc", rb_fiddle_realloc, 2); rb_define_module_function(mFiddle, "free", rb_fiddle_free, 1); - rb_define_module_function(mFiddle, "memcpy", rb_fiddle_memcpy, -1); Init_fiddle_function(); Init_fiddle_closure(); diff --git a/test/fiddle/test_fiddle.rb b/test/fiddle/test_fiddle.rb index 84cbae41..f2f56925 100644 --- a/test/fiddle/test_fiddle.rb +++ b/test/fiddle/test_fiddle.rb @@ -13,27 +13,4 @@ def test_windows_constant refute Fiddle::WINDOWS, "Fiddle::WINDOWS should be 'false' on non-Windows platforms" end end - - def test_memcpy_pointer - src = Fiddle::Pointer[Marshal.load(Marshal.dump("hello world"))] - smaller = Fiddle::Pointer[Marshal.load(Marshal.dump("1234567890"))] - same = Fiddle::Pointer[Marshal.load(Marshal.dump("12345678901"))] - larger = Fiddle::Pointer[Marshal.load(Marshal.dump("123456789012"))] - - assert_equal(src.size - 1, Fiddle.memcpy(smaller, src)) - assert_equal(src.size , Fiddle.memcpy(same, src)) - assert_equal(src.size , Fiddle.memcpy(larger, src)) - assert_equal("hello worl", smaller.to_s) - assert_equal("hello world", same.to_s) - assert_equal("hello world2", larger.to_s) - end - - def test_memcpy_address - src = Fiddle::Pointer[Marshal.load(Marshal.dump("hello world"))] - dest = Fiddle::Pointer[Marshal.load(Marshal.dump("12345678901"))] - - assert_equal(src.size - 1, - Fiddle.memcpy(dest.to_i, src.to_i, src.size - 1)) - assert_equal("hello worl1", dest.to_s) - end end if defined?(Fiddle) From ec7c0ee909f6f9c3cabf01769994743fc2190c3f Mon Sep 17 00:00:00 2001 From: Sutou Kouhei Date: Sun, 13 Sep 2020 06:11:53 +0900 Subject: [PATCH 31/37] Remove offset_of because it's not used --- lib/fiddle/struct.rb | 21 --------------------- test/fiddle/test_import.rb | 23 ----------------------- 2 files changed, 44 deletions(-) diff --git a/lib/fiddle/struct.rb b/lib/fiddle/struct.rb index a84725f4..cd50376b 100644 --- a/lib/fiddle/struct.rb +++ b/lib/fiddle/struct.rb @@ -156,10 +156,8 @@ def create(klass, types, members) define_method(:[]=) { |*args| @entity.send(:[]=, *args) } define_method(:to_ptr){ @entity } define_method(:to_i){ @entity.to_i } - define_method(:offset_of) { |*args| @entity.offset_of(*args) } define_singleton_method(:types) { types } define_singleton_method(:members) { members } - define_singleton_method(:offset_of) { |mbr| klass.entity_class.compute_offset(types, members, mbr) } members.each{|name| name = name[0] if name.is_a?(Array) # name is a nested struct next if method_defined?(name) @@ -201,15 +199,6 @@ def CStructEntity.alignment(types) max end - def CStructEntity.compute_offset(types, members, mbr) - members.each_with_index do |m, idx| - if (m.is_a?(Array) ? m[0] : m) == mbr.to_s - return idx == 0 ? 0 : size(types[0...idx]) - end - end - raise(ArgumentError, "no such member: #{mbr}") - end - # Allocates a C struct with the +types+ provided. # # See Fiddle::Pointer.malloc for memory management issues. @@ -325,11 +314,6 @@ def set_ctypes(types) @size = PackInfo.align(offset, max_align) end - def offset_of(mbr) - idx = @members.index(mbr.to_s) || raise(ArgumentError, "no such member: #{mbr}") - @offset[idx] - end - # Fetch struct member +name+ if only one argument is specified. If two # arguments are specified, the first is an offset and the second is a # length and this method returns the string of +length+ bytes beginning at @@ -441,11 +425,6 @@ def to_s() # :nodoc: class CUnionEntity < CStructEntity include PackInfo - def CUnionEntity.compute_offset(types, members, mbr) - # all members begin at offset 0 - 0 - end - # Returns the size needed for the union with the given +types+. # # Fiddle::CUnionEntity.size( diff --git a/test/fiddle/test_import.rb b/test/fiddle/test_import.rb index 60a9da33..61074506 100644 --- a/test/fiddle/test_import.rb +++ b/test/fiddle/test_import.rb @@ -276,29 +276,6 @@ def test_union_nested_struct_members() end end - def test_struct_size_and_offset_of_nested_unions() - a = Fiddle::Importer.union ['float f[4]', 'int i[2]'] - b = Fiddle::Importer.struct ['float x', 'int y'] - c = Fiddle::Importer.struct [a: a, b: b] - - assert_equal 0, a.offset_of(:f) - assert_equal 0, a.offset_of(:i) - assert_equal 0, b.offset_of(:x) - assert_equal Fiddle::SIZEOF_FLOAT, b.offset_of(:y) - assert_equal 0, c.offset_of(:a) - assert_equal 4 * Fiddle::SIZEOF_FLOAT, c.offset_of(:b) - - assert_equal a.offset_of(:f), a.malloc.offset_of(:f) - assert_equal a.offset_of(:i), a.malloc.offset_of(:i) - assert_equal b.offset_of(:x), b.malloc.offset_of(:x) - assert_equal b.offset_of(:y), b.malloc.offset_of(:y) - assert_equal c.offset_of(:a), c.malloc.offset_of(:a) - assert_equal c.offset_of(:b), c.malloc.offset_of(:b) - - assert_equal Fiddle::SIZEOF_FLOAT * 4 + Fiddle::SIZEOF_INT * 2, - c.size - end - def test_struct_nested_struct_replace_array_element() LIBC::StructNestedStruct.malloc(Fiddle::RUBY_FREE) do |s| s.vertices[0].position.x = 5 From a6e643823b7cf46b199c6c5510b0f3f1a0971e58 Mon Sep 17 00:00:00 2001 From: Sutou Kouhei Date: Sun, 13 Sep 2020 07:50:58 +0900 Subject: [PATCH 32/37] Add support for an array of struct --- lib/fiddle/struct.rb | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/fiddle/struct.rb b/lib/fiddle/struct.rb index cd50376b..fa48f690 100644 --- a/lib/fiddle/struct.rb +++ b/lib/fiddle/struct.rb @@ -32,8 +32,7 @@ def each_pair def to_h hash = {} each_pair do |name, value| - value = value.to_h if value.is_a?(CStruct) - hash[name] = value + hash[name] = unstruct(value) end hash end @@ -54,6 +53,20 @@ def replace(another) end self end + + private + def unstruct(value) + case value + when CStruct + value.to_h + when Array + value.collect do |v| + unstruct(v) + end + else + value + end + end end # A base class for objects representing a C union From b85692d4e57dc66ed1a7d9a66ee2d836d7c80f3a Mon Sep 17 00:00:00 2001 From: Sutou Kouhei Date: Sun, 13 Sep 2020 07:53:53 +0900 Subject: [PATCH 33/37] Use CStruct as struct signature --- lib/fiddle/cparser.rb | 38 +++++------ lib/fiddle/struct.rb | 55 ++++++++-------- test/fiddle/test_cparser.rb | 121 +++++++++++++++++++++--------------- 3 files changed, 117 insertions(+), 97 deletions(-) diff --git a/lib/fiddle/cparser.rb b/lib/fiddle/cparser.rb index dc6ec872..86f2e42b 100644 --- a/lib/fiddle/cparser.rb +++ b/lib/fiddle/cparser.rb @@ -37,8 +37,6 @@ def parse_struct_signature(signature, tymap=nil) signature = split_arguments(signature, /[,;]/) elsif signature.is_a?(Hash) signature = [signature] - elsif signature.respond_to?(:types) && signature.respond_to?(:members) - return signature.types, signature.members, signature.entity_class end mems = [] tys = [] @@ -46,25 +44,27 @@ def parse_struct_signature(signature, tymap=nil) msig = compact(msig) if msig.is_a?(String) case msig when Hash - msig.each do |structure_name, struct_signature| - structure_name = structure_name.to_s if structure_name.is_a?(Symbol) - structure_name = compact(structure_name) - structure_count = nil - if structure_name =~ /^([\w\*\s]+)\[(\d+)\]$/ - structure_count = $2.to_i - structure_name = $1 + msig.each do |struct_name, struct_signature| + struct_name = struct_name.to_s if struct_name.is_a?(Symbol) + struct_name = compact(struct_name) + struct_count = nil + if struct_name =~ /^([\w\*\s]+)\[(\d+)\]$/ + struct_count = $2.to_i + struct_name = $1 end - structure_parsed = parse_struct_signature(struct_signature, tymap) - structure_types = structure_parsed[0] - structure_members = structure_parsed[1] - structure_klass = structure_parsed[2] - ty = [structure_types] - ty << structure_count if structure_count - if structure_klass - ty[2] = structure_klass - ty[1] ||= 1 + if struct_signature.respond_to?(:types) && + struct_signature.respond_to?(:members) + struct_type = struct_signature + else + parsed_struct = parse_struct_signature(struct_signature, tymap) + struct_type = CStructBuilder.create(CStruct, *parsed_struct) end - mems.push([structure_name, structure_members]) + if struct_count + ty = [struct_type, struct_count] + else + ty = struct_type + end + mems.push([struct_name, struct_type.members]) tys.push(ty) end when /^[\w\*\s]+[\*\s](\w+)$/ diff --git a/lib/fiddle/struct.rb b/lib/fiddle/struct.rb index fa48f690..c3ede9dd 100644 --- a/lib/fiddle/struct.rb +++ b/lib/fiddle/struct.rb @@ -177,7 +177,10 @@ def create(klass, types, members) define_method(name){ @entity[name] } define_method(name + "="){|val| @entity[name] = val } } - size = klass.entity_class.size(types) + entity_class = klass.entity_class + alignment = entity_class.alignment(types) + size = entity_class.size(types) + define_singleton_method(:alignment) { alignment } define_singleton_method(:size) { size } define_singleton_method(:malloc) do |func=nil| if block_given? @@ -201,9 +204,9 @@ class CStructEntity < Fiddle::Pointer def CStructEntity.alignment(types) max = 1 - types.each do |type, count = 1, klass = CStructEntity| - if type.is_a?(Array) # nested struct - n = klass.alignment(type) + types.each do |type, count = 1| + if type.respond_to?(:alignment) + n = type.alignment else n = ALIGN_MAP[type] end @@ -238,12 +241,12 @@ def CStructEntity.malloc(types, func = nil, size = size(types), &block) def CStructEntity.size(types) offset = 0 - max_align = types.map { |type, count = 1, klass = CStructEntity| + max_align = types.map { |type, count = 1| last_offset = offset - if type.is_a?(Array) # type is a nested array representing a nested struct - align = klass.alignment(type) - type_size = klass.size(type) + if type.respond_to?(:alignment) + align = type.alignment + type_size = type.size else align = PackInfo::ALIGN_MAP[type] type_size = PackInfo::SIZE_MAP[type] @@ -277,20 +280,12 @@ def assign_names(members) members.each_with_index do |member, index| if member.is_a?(Array) # nested struct member_name = member[0] - struct_member_names = member[1] - type = @ctypes[index] - struct_types = type[0] - count = type[1] || 1 - entity_class = type[2] || CStructEntity - struct_class = CStructBuilder.create(CStruct, - struct_types, - struct_member_names) - if count == 1 - struct = struct_class.new(to_i + @offset[index]) + struct_type, struct_count = @ctypes[index] + if struct_count.nil? + struct = struct_type.new(to_i + @offset[index]) else - entity_size = entity_class.size(struct_types) - structs = count.times.map do |i| - struct_class.new(to_i + @offset[index] + i * entity_size) + structs = struct_count.times.map do |i| + struct_type.new(to_i + @offset[index] + i * struct_type.size) end struct = NestedStructArray.new(structs) end @@ -308,11 +303,11 @@ def set_ctypes(types) @offset = [] offset = 0 - max_align = types.map { |type, count = 1, klass = CStructEntity| + max_align = types.map { |type, count = 1| orig_offset = offset - if type.is_a?(Array) # type is a nested array representing a nested struct - align = klass.alignment(type) - type_size = klass.size(type) + if type.respond_to?(:alignment) + align = type.alignment + type_size = type.size else align = ALIGN_MAP[type] type_size = SIZE_MAP[type] @@ -348,11 +343,13 @@ def [](*args) end ty = @ctypes[idx] if( ty.is_a?(Array) ) - if ty.first.is_a?(Array) + if ty.first.respond_to?(:alignment) return @nested_structs[name] else r = super(@offset[idx], SIZE_MAP[ty[0]] * ty[1]) end + elsif ty.respond_to?(:alignment) + return @nested_structs[name] else r = super(@offset[idx], SIZE_MAP[ty.abs]) end @@ -446,9 +443,9 @@ class CUnionEntity < CStructEntity # Fiddle::TYPE_CHAR, # Fiddle::TYPE_VOIDP ]) #=> 8 def CUnionEntity.size(types) - types.map { |type, count = 1, klass = CStructEntity| - if type.is_a?(Array) # type is a nested array representing a nested struct - klass.size(type) * count + types.map { |type, count = 1| + if type.respond_to?(:alignment) + type.size * count else PackInfo::SIZE_MAP[type] * count end diff --git a/test/fiddle/test_cparser.rb b/test/fiddle/test_cparser.rb index cb711794..ef8cec5d 100644 --- a/test/fiddle/test_cparser.rb +++ b/test/fiddle/test_cparser.rb @@ -69,6 +69,19 @@ def test_undefined_ctype_with_type_alias assert_equal(-TYPE_LONG, parse_ctype('DWORD', {"DWORD" => "unsigned long"})) end + def expand_struct_types(types) + types.collect do |type| + case type + when Class + [expand_struct_types(type.types)] + when Array + [expand_struct_types([type[0]])[0][0], type[1]] + else + type + end + end + end + def test_struct_basic assert_equal [[TYPE_INT, TYPE_CHAR], ['i', 'c']], parse_struct_signature(['int i', 'char c']) end @@ -78,80 +91,90 @@ def test_struct_array end def test_struct_nested_struct - assert_equal [[TYPE_INT, [[TYPE_INT, TYPE_CHAR]]], + types, members = parse_struct_signature([ + 'int x', + {inner: ['int i', 'char c']}, + ]) + assert_equal([[TYPE_INT, [[TYPE_INT, TYPE_CHAR]]], ['x', ['inner', ['i', 'c']]]], - parse_struct_signature([ - 'int x', - {inner: ['int i', 'char c']}, - ]) + [expand_struct_types(types), + members]) end def test_struct_nested_defined_struct inner = Fiddle::Importer.struct(['int i', 'char c']) - assert_equal [[TYPE_INT, [[TYPE_INT, TYPE_CHAR], 1, inner.entity_class]], + assert_equal([[TYPE_INT, inner], ['x', ['inner', ['i', 'c']]]], parse_struct_signature([ 'int x', {inner: inner}, - ]) + ])) end def test_struct_double_nested_struct - assert_equal [[TYPE_INT, [[TYPE_INT, [[TYPE_INT, TYPE_CHAR]]]]], + types, members = parse_struct_signature([ + 'int x', + { + outer: [ + 'int y', + {inner: ['int i', 'char c']}, + ], + }, + ]) + assert_equal([[TYPE_INT, [[TYPE_INT, [[TYPE_INT, TYPE_CHAR]]]]], ['x', ['outer', ['y', ['inner', ['i', 'c']]]]]], - parse_struct_signature([ - 'int x', - { - outer: [ - 'int y', - {inner: ['int i', 'char c']}, - ], - }, - ]) + [expand_struct_types(types), + members]) end def test_struct_nested_struct_array - assert_equal [[TYPE_INT, [[TYPE_INT, TYPE_CHAR], 2]], + types, members = parse_struct_signature([ + 'int x', + { + 'inner[2]' => [ + 'int i', + 'char c', + ], + }, + ]) + assert_equal([[TYPE_INT, [[TYPE_INT, TYPE_CHAR], 2]], ['x', ['inner', ['i', 'c']]]], - parse_struct_signature([ - 'int x', - { - 'inner[2]' => [ - 'int i', - 'char c', - ], - }, - ]) + [expand_struct_types(types), + members]) end def test_struct_double_nested_struct_inner_array - assert_equal [[[[TYPE_INT, [[TYPE_INT, TYPE_CHAR], 2]]]], + types, members = parse_struct_signature(outer: [ + 'int x', + { + 'inner[2]' => [ + 'int i', + 'char c', + ], + }, + ]) + assert_equal([[[[TYPE_INT, [[TYPE_INT, TYPE_CHAR], 2]]]], [['outer', ['x', ['inner', ['i', 'c']]]]]], - parse_struct_signature(outer: [ - 'int x', - { - 'inner[2]' => [ - 'int i', - 'char c', - ], - }, - ]) + [expand_struct_types(types), + members]) end def test_struct_double_nested_struct_outer_array - assert_equal [[TYPE_INT, [[[[TYPE_INT, TYPE_CHAR]]], 2]], + types, members = parse_struct_signature([ + 'int x', + { + 'outer[2]' => { + inner: [ + 'int i', + 'char c', + ], + }, + }, + ]) + assert_equal([[TYPE_INT, [[[[TYPE_INT, TYPE_CHAR]]], 2]], ['x', ['outer', [['inner', ['i', 'c']]]]]], - parse_struct_signature([ - 'int x', - { - 'outer[2]' => { - inner: [ - 'int i', - 'char c', - ], - }, - }, - ]) + [expand_struct_types(types), + members]) end def test_struct_array_str From 4fb2dc9338002f0505ff41bc5ed73dba9b36d620 Mon Sep 17 00:00:00 2001 From: Sutou Kouhei Date: Sun, 13 Sep 2020 07:54:24 +0900 Subject: [PATCH 34/37] Reuse StructArray for an array of nested struct --- lib/fiddle/struct.rb | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/lib/fiddle/struct.rb b/lib/fiddle/struct.rb index c3ede9dd..8c0857b1 100644 --- a/lib/fiddle/struct.rb +++ b/lib/fiddle/struct.rb @@ -84,10 +84,14 @@ class StructArray < Array def initialize(ptr, type, initial_values) @ptr = ptr @type = type - @align = PackInfo::ALIGN_MAP[type] - @size = Fiddle::PackInfo::SIZE_MAP[type] - @pack_format = Fiddle::PackInfo::PACK_MAP[type] - super(initial_values.collect { |v| unsigned_value(v, type) }) + @is_struct = @type.respond_to?(:alignment) + if @is_struct + super(initial_values) + else + @size = Fiddle::PackInfo::SIZE_MAP[type] + @pack_format = Fiddle::PackInfo::PACK_MAP[type] + super(initial_values.collect { |v| unsigned_value(v, type) }) + end end def to_ptr @@ -99,15 +103,12 @@ def []=(index, value) raise IndexError, 'index %d outside of array bounds 0...%d' % [index, size] end - to_ptr[index * @size, @size] = [value].pack(@pack_format) - super(index, value) - end - end - - # Wrapper for arrays of structs within a struct - class NestedStructArray < Array - def []=(index, value) - self[index].replace(value) + if @is_struct + self[index].replace(value) + else + to_ptr[index * @size, @size] = [value].pack(@pack_format) + super(index, value) + end end end @@ -287,7 +288,9 @@ def assign_names(members) structs = struct_count.times.map do |i| struct_type.new(to_i + @offset[index] + i * struct_type.size) end - struct = NestedStructArray.new(structs) + struct = StructArray.new(to_i + @offset[index], + struct_type, + structs) end @nested_structs[member_name] = struct else @@ -392,7 +395,7 @@ def []=(*args) name = name.to_s if name.is_a?(Symbol) nested_struct = @nested_structs[name] if nested_struct - if nested_struct.is_a?(NestedStructArray) + if nested_struct.is_a?(StructArray) if val.nil? nested_struct.each do |s| s.replace(nil) From 43a5c6faefd7bd2073c942692aef9549075357e7 Mon Sep 17 00:00:00 2001 From: Sutou Kouhei Date: Sun, 13 Sep 2020 07:29:29 +0900 Subject: [PATCH 35/37] Fix a bug that struct.malloc {} doesn't call block --- lib/fiddle/struct.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/fiddle/struct.rb b/lib/fiddle/struct.rb index 8c0857b1..1607becc 100644 --- a/lib/fiddle/struct.rb +++ b/lib/fiddle/struct.rb @@ -183,10 +183,10 @@ def create(klass, types, members) size = entity_class.size(types) define_singleton_method(:alignment) { alignment } define_singleton_method(:size) { size } - define_singleton_method(:malloc) do |func=nil| - if block_given? + define_singleton_method(:malloc) do |func=nil, &block| + if block entity_class.malloc(types, func, size) do |entity| - yield new(entity) + block.call(new(entity)) end else new(entity_class.malloc(types, func, size)) From 64b4682d963adb26007df8bbf1327a7d1503af1e Mon Sep 17 00:00:00 2001 From: Sutou Kouhei Date: Sun, 13 Sep 2020 07:56:55 +0900 Subject: [PATCH 36/37] Use .entity_class to detect CStruct --- lib/fiddle/cparser.rb | 3 +-- lib/fiddle/struct.rb | 14 +++++++------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/fiddle/cparser.rb b/lib/fiddle/cparser.rb index 86f2e42b..25ffe25d 100644 --- a/lib/fiddle/cparser.rb +++ b/lib/fiddle/cparser.rb @@ -52,8 +52,7 @@ def parse_struct_signature(signature, tymap=nil) struct_count = $2.to_i struct_name = $1 end - if struct_signature.respond_to?(:types) && - struct_signature.respond_to?(:members) + if struct_signature.respond_to?(:entity_class) struct_type = struct_signature else parsed_struct = parse_struct_signature(struct_signature, tymap) diff --git a/lib/fiddle/struct.rb b/lib/fiddle/struct.rb index 1607becc..dd64abf7 100644 --- a/lib/fiddle/struct.rb +++ b/lib/fiddle/struct.rb @@ -84,7 +84,7 @@ class StructArray < Array def initialize(ptr, type, initial_values) @ptr = ptr @type = type - @is_struct = @type.respond_to?(:alignment) + @is_struct = @type.respond_to?(:entity_class) if @is_struct super(initial_values) else @@ -206,7 +206,7 @@ class CStructEntity < Fiddle::Pointer def CStructEntity.alignment(types) max = 1 types.each do |type, count = 1| - if type.respond_to?(:alignment) + if type.respond_to?(:entity_class) n = type.alignment else n = ALIGN_MAP[type] @@ -245,7 +245,7 @@ def CStructEntity.size(types) max_align = types.map { |type, count = 1| last_offset = offset - if type.respond_to?(:alignment) + if type.respond_to?(:entity_class) align = type.alignment type_size = type.size else @@ -308,7 +308,7 @@ def set_ctypes(types) max_align = types.map { |type, count = 1| orig_offset = offset - if type.respond_to?(:alignment) + if type.respond_to?(:entity_class) align = type.alignment type_size = type.size else @@ -346,12 +346,12 @@ def [](*args) end ty = @ctypes[idx] if( ty.is_a?(Array) ) - if ty.first.respond_to?(:alignment) + if ty.first.respond_to?(:entity_class) return @nested_structs[name] else r = super(@offset[idx], SIZE_MAP[ty[0]] * ty[1]) end - elsif ty.respond_to?(:alignment) + elsif ty.respond_to?(:entity_class) return @nested_structs[name] else r = super(@offset[idx], SIZE_MAP[ty.abs]) @@ -447,7 +447,7 @@ class CUnionEntity < CStructEntity # Fiddle::TYPE_VOIDP ]) #=> 8 def CUnionEntity.size(types) types.map { |type, count = 1| - if type.respond_to?(:alignment) + if type.respond_to?(:entity_class) type.size * count else PackInfo::SIZE_MAP[type] * count From aae0663c18354069cea6fa6e4e34f72d353d6869 Mon Sep 17 00:00:00 2001 From: Sutou Kouhei Date: Sun, 13 Sep 2020 08:11:10 +0900 Subject: [PATCH 37/37] Reduce diff --- lib/fiddle/struct.rb | 4 +++- test/fiddle/test_fiddle.rb | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/fiddle/struct.rb b/lib/fiddle/struct.rb index dd64abf7..a766eba8 100644 --- a/lib/fiddle/struct.rb +++ b/lib/fiddle/struct.rb @@ -316,7 +316,9 @@ def set_ctypes(types) type_size = SIZE_MAP[type] end offset = PackInfo.align(orig_offset, align) + @offset << offset + offset += (type_size * count) align @@ -362,7 +364,7 @@ def [](*args) when Array case ty[0] when TYPE_VOIDP - val = val.collect{|v| Pointer.new(v) } + val = val.collect{|v| Pointer.new(v)} end when TYPE_VOIDP val = Pointer.new(val[0]) diff --git a/test/fiddle/test_fiddle.rb b/test/fiddle/test_fiddle.rb index f2f56925..8751d969 100644 --- a/test/fiddle/test_fiddle.rb +++ b/test/fiddle/test_fiddle.rb @@ -13,4 +13,5 @@ def test_windows_constant refute Fiddle::WINDOWS, "Fiddle::WINDOWS should be 'false' on non-Windows platforms" end end + end if defined?(Fiddle)