| # |
| # This file is part of ruby-ffi. |
| # For licensing, see LICENSE.SPECS |
| # |
| |
| require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper")) |
| |
| describe "Struct aligns fields correctly" do |
| it "char, followed by an int" do |
| pending("not supported in 1.8") if RUBY_VERSION =~ /^1\.8\..*/ |
| class CIStruct < FFI::Struct |
| layout :c => :char, :i => :int |
| end |
| expect(CIStruct.size).to eq(8) |
| end |
| |
| it "short, followed by an int" do |
| pending("not supported in 1.8") if RUBY_VERSION =~ /^1\.8\..*/ |
| class SIStruct < FFI::Struct |
| layout :s => :short, :i => :int |
| end |
| expect(SIStruct.size).to eq(8) |
| end |
| |
| it "int, followed by an int" do |
| pending("not supported in 1.8") if RUBY_VERSION =~ /^1\.8\..*/ |
| class IIStruct < FFI::Struct |
| layout :i1 => :int, :i => :int |
| end |
| expect(IIStruct.size).to eq(8) |
| end |
| |
| it "long long, followed by an int" do |
| pending("not supported in 1.8") if RUBY_VERSION =~ /^1\.8\..*/ |
| class LLIStruct < FFI::Struct |
| layout :l => :long_long, :i => :int |
| end |
| expect(LLIStruct.size).to eq(FFI::TYPE_UINT64.alignment == 4 ? 12 : 16) |
| end |
| end |
| |
| describe "Struct tests" do |
| StructTypes = { |
| 's8' => :char, |
| 's16' => :short, |
| 's32' => :int, |
| 's64' => :long_long, |
| 'long' => :long, |
| 'f32' => :float, |
| 'f64' => :double |
| } |
| module LibTest |
| extend FFI::Library |
| ffi_lib TestLibrary::PATH |
| attach_function :ptr_ret_pointer, [ :pointer, :int], :string |
| begin |
| attach_function :ptr_ret_int32_t, [ :pointer, :int ], :int |
| rescue FFI::NotFoundError |
| # NetBSD uses #define instead of typedef for these |
| attach_function :ptr_ret_int32_t, :ptr_ret___int32_t, [ :pointer, :int ], :int |
| end |
| attach_function :ptr_from_address, [ :ulong ], :pointer |
| attach_function :string_equals, [ :string, :string ], :int |
| [ 's8', 's16', 's32', 's64', 'f32', 'f64', 'long' ].each do |t| |
| attach_function "struct_align_#{t}", [ :pointer ], StructTypes[t] |
| end |
| end |
| class PointerMember < FFI::Struct |
| layout :pointer, :pointer |
| end |
| class StringMember < FFI::Struct |
| layout :string, :string |
| end |
| |
| it "Struct#[:pointer]" do |
| magic = 0x12345678 |
| mp = FFI::MemoryPointer.new :long |
| mp.put_long(0, magic) |
| smp = FFI::MemoryPointer.new :pointer |
| smp.put_pointer(0, mp) |
| s = PointerMember.new smp |
| expect(s[:pointer]).to eq(mp) |
| end |
| |
| it "Struct#[:pointer].nil? for NULL value" do |
| magic = 0x12345678 |
| mp = FFI::MemoryPointer.new :long |
| mp.put_long(0, magic) |
| smp = FFI::MemoryPointer.new :pointer |
| smp.put_pointer(0, nil) |
| s = PointerMember.new smp |
| expect(s[:pointer].null?).to be true |
| end |
| |
| it "Struct#[:pointer]=" do |
| magic = 0x12345678 |
| mp = FFI::MemoryPointer.new :long |
| mp.put_long(0, magic) |
| smp = FFI::MemoryPointer.new :pointer |
| s = PointerMember.new smp |
| s[:pointer] = mp |
| expect(smp.get_pointer(0)).to eq(mp) |
| end |
| |
| it "Struct#[:pointer]=struct" do |
| smp = FFI::MemoryPointer.new :pointer |
| s = PointerMember.new smp |
| expect { s[:pointer] = s }.not_to raise_error Exception |
| expect { s[:pointer].nil? }.not_to raise_error Exception |
| end |
| |
| it "Struct#[:pointer]=nil" do |
| smp = FFI::MemoryPointer.new :pointer |
| s = PointerMember.new smp |
| s[:pointer] = nil |
| expect(smp.get_pointer(0)).to be_null |
| end |
| |
| it "Struct#[:string]" do |
| magic = "test" |
| mp = FFI::MemoryPointer.new 1024 |
| mp.put_string(0, magic) |
| smp = FFI::MemoryPointer.new :pointer |
| smp.put_pointer(0, mp) |
| s = StringMember.new smp |
| expect(s[:string]).to eq(magic) |
| end |
| |
| it "Struct#[:string].nil? for NULL value" do |
| smp = FFI::MemoryPointer.new :pointer |
| smp.put_pointer(0, nil) |
| s = StringMember.new smp |
| expect(s[:string]).to be_nil |
| end |
| |
| it "Struct#layout works with :name, :type pairs" do |
| class PairLayout < FFI::Struct |
| layout :a, :int, :b, :long_long |
| end |
| ll_off = (FFI::TYPE_UINT64.alignment == 4 ? 4 : 8) |
| expect(PairLayout.size).to eq((ll_off + 8)) |
| mp = FFI::MemoryPointer.new(PairLayout.size) |
| s = PairLayout.new mp |
| s[:a] = 0x12345678 |
| expect(mp.get_int(0)).to eq(0x12345678) |
| s[:b] = 0xfee1deadbeef |
| expect(mp.get_int64(ll_off)).to eq(0xfee1deadbeef) |
| end |
| |
| it "Struct#layout works with :name, :type, offset tuples" do |
| class PairLayout < FFI::Struct |
| layout :a, :int, 0, :b, :long_long, 4 |
| end |
| expect(PairLayout.size).to eq((FFI::TYPE_UINT64.alignment == 4 ? 12 : 16)) |
| mp = FFI::MemoryPointer.new(PairLayout.size) |
| s = PairLayout.new mp |
| s[:a] = 0x12345678 |
| expect(mp.get_int(0)).to eq(0x12345678) |
| s[:b] = 0xfee1deadbeef |
| expect(mp.get_int64(4)).to eq(0xfee1deadbeef) |
| end |
| |
| it "Struct#layout works with mixed :name,:type and :name,:type,offset" do |
| class MixedLayout < FFI::Struct |
| layout :a, :int, :b, :long_long, 4 |
| end |
| expect(MixedLayout.size).to eq((FFI::TYPE_UINT64.alignment == 4 ? 12 : 16)) |
| mp = FFI::MemoryPointer.new(MixedLayout.size) |
| s = MixedLayout.new mp |
| s[:a] = 0x12345678 |
| expect(mp.get_int(0)).to eq(0x12345678) |
| s[:b] = 0xfee1deadbeef |
| expect(mp.get_int64(4)).to eq(0xfee1deadbeef) |
| end |
| |
| rb_maj, rb_min = RUBY_VERSION.split('.') |
| if rb_maj.to_i >= 1 && rb_min.to_i >= 9 || RUBY_PLATFORM =~ /java/ |
| it "Struct#layout withs with a hash of :name => type" do |
| class HashLayout < FFI::Struct |
| layout :a => :int, :b => :long_long |
| end |
| ll_off = (FFI::TYPE_UINT64.alignment == 4 ? 4 : 8) |
| expect(HashLayout.size).to eq(ll_off + 8) |
| mp = FFI::MemoryPointer.new(HashLayout.size) |
| s = HashLayout.new mp |
| s[:a] = 0x12345678 |
| expect(mp.get_int(0)).to eq(0x12345678) |
| s[:b] = 0xfee1deadbeef |
| expect(mp.get_int64(ll_off)).to eq(0xfee1deadbeef) |
| end |
| end |
| |
| it "subclass overrides initialize without calling super" do |
| class InitializeWithoutSuper < FFI::Struct |
| layout :a, :int, :b, :long_long, :d, [:double, 2] |
| |
| def initialize(a, b) |
| self[:a] = a |
| self[:b] = b |
| self[:d][0] = 1.2 |
| self[:d][1] = 3.4 |
| end |
| |
| end |
| s = InitializeWithoutSuper.new(0x1eefbeef, 0xdeadcafebabe) |
| expect(s[:a]).to eq(0x1eefbeef) |
| expect(s[:b]).to eq(0xdeadcafebabe) |
| end |
| |
| it "Can use Struct subclass as parameter type" do |
| expect(module StructParam |
| extend FFI::Library |
| ffi_lib TestLibrary::PATH |
| class TestStruct < FFI::Struct |
| layout :c, :char |
| end |
| attach_function :struct_field_s8, [ TestStruct.in ], :char |
| end).to be_an_instance_of FFI::Function |
| end |
| |
| it "Can use Struct subclass as IN parameter type" do |
| expect(module StructParam2 |
| extend FFI::Library |
| ffi_lib TestLibrary::PATH |
| class TestStruct < FFI::Struct |
| layout :c, :char |
| end |
| attach_function :struct_field_s8, [ TestStruct.in ], :char |
| end).to be_an_instance_of FFI::Function |
| end |
| |
| it "Can use Struct subclass as OUT parameter type" do |
| expect(module StructParam3 |
| extend FFI::Library |
| ffi_lib TestLibrary::PATH |
| class TestStruct < FFI::Struct |
| layout :c, :char |
| end |
| attach_function :struct_field_s8, [ TestStruct.out ], :char |
| end).to be_an_instance_of FFI::Function |
| end |
| |
| it "can be passed directly as a :pointer parameter" do |
| class TestStruct < FFI::Struct |
| layout :i, :int |
| end |
| s = TestStruct.new |
| s[:i] = 0x12 |
| expect(LibTest.ptr_ret_int32_t(s, 0)).to eq(0x12) |
| end |
| |
| it ":char member aligned correctly" do |
| class AlignChar < FFI::Struct |
| layout :c, :char, :v, :char |
| end |
| s = AlignChar.new |
| s[:v] = 0x12 |
| expect(LibTest.struct_align_s8(s.pointer)).to eq(0x12) |
| end |
| |
| it ":short member aligned correctly" do |
| class AlignShort < FFI::Struct |
| layout :c, :char, :v, :short |
| end |
| s = AlignShort.alloc_in |
| s[:v] = 0x1234 |
| expect(LibTest.struct_align_s16(s.pointer)).to eq(0x1234) |
| end |
| |
| it ":int member aligned correctly" do |
| class AlignInt < FFI::Struct |
| layout :c, :char, :v, :int |
| end |
| s = AlignInt.alloc_in |
| s[:v] = 0x12345678 |
| expect(LibTest.struct_align_s32(s.pointer)).to eq(0x12345678) |
| end |
| |
| it ":long_long member aligned correctly" do |
| class AlignLongLong < FFI::Struct |
| layout :c, :char, :v, :long_long |
| end |
| s = AlignLongLong.alloc_in |
| s[:v] = 0x123456789abcdef0 |
| expect(LibTest.struct_align_s64(s.pointer)).to eq(0x123456789abcdef0) |
| end |
| |
| it ":long member aligned correctly" do |
| class AlignLong < FFI::Struct |
| layout :c, :char, :v, :long |
| end |
| s = AlignLong.alloc_in |
| s[:v] = 0x12345678 |
| expect(LibTest.struct_align_long(s.pointer)).to eq(0x12345678) |
| end |
| |
| it ":float member aligned correctly" do |
| class AlignFloat < FFI::Struct |
| layout :c, :char, :v, :float |
| end |
| s = AlignFloat.alloc_in |
| s[:v] = 1.23456 |
| expect((LibTest.struct_align_f32(s.pointer) - 1.23456).abs).to be < 0.00001 |
| end |
| |
| it ":double member aligned correctly" do |
| class AlignDouble < FFI::Struct |
| layout :c, :char, :v, :double |
| end |
| s = AlignDouble.alloc_in |
| s[:v] = 1.23456789 |
| expect((LibTest.struct_align_f64(s.pointer) - 1.23456789).abs).to be < 0.00000001 |
| end |
| |
| it ":ulong, :pointer struct" do |
| class ULPStruct < FFI::Struct |
| layout :ul, :ulong, :p, :pointer |
| end |
| s = ULPStruct.alloc_in |
| s[:ul] = 0xdeadbeef |
| s[:p] = LibTest.ptr_from_address(0x12345678) |
| expect(s.pointer.get_ulong(0)).to eq(0xdeadbeef) |
| end |
| def test_num_field(type, v) |
| klass = Class.new(FFI::Struct) |
| klass.layout :v, type, :dummy, :long |
| |
| s = klass.new |
| s[:v] = v |
| expect(s.pointer.send("get_#{type.to_s}", 0)).to eq(v) |
| s.pointer.send("put_#{type.to_s}", 0, 0) |
| expect(s[:v]).to eq(0) |
| end |
| def self.int_field_test(type, values) |
| values.each do |v| |
| it "#{type} field r/w (#{v.to_s(16)})" do |
| test_num_field(type, v) |
| end |
| end |
| end |
| int_field_test(:char, [ 0, 127, -128, -1 ]) |
| int_field_test(:uchar, [ 0, 0x7f, 0x80, 0xff ]) |
| int_field_test(:short, [ 0, 0x7fff, -0x8000, -1 ]) |
| int_field_test(:ushort, [ 0, 0x7fff, 0x8000, 0xffff ]) |
| int_field_test(:int, [ 0, 0x7fffffff, -0x80000000, -1 ]) |
| int_field_test(:uint, [ 0, 0x7fffffff, 0x80000000, 0xffffffff ]) |
| int_field_test(:long_long, [ 0, 0x7fffffffffffffff, -0x8000000000000000, -1 ]) |
| int_field_test(:ulong_long, [ 0, 0x7fffffffffffffff, 0x8000000000000000, 0xffffffffffffffff ]) |
| if FFI::Platform::LONG_SIZE == 32 |
| int_field_test(:long, [ 0, 0x7fffffff, -0x80000000, -1 ]) |
| int_field_test(:ulong, [ 0, 0x7fffffff, 0x80000000, 0xffffffff ]) |
| else |
| int_field_test(:long, [ 0, 0x7fffffffffffffff, -0x8000000000000000, -1 ]) |
| int_field_test(:ulong, [ 0, 0x7fffffffffffffff, 0x8000000000000000, 0xffffffffffffffff ]) |
| end |
| |
| it ":float field r/w" do |
| klass = Class.new(FFI::Struct) |
| klass.layout :v, :float, :dummy, :long |
| |
| s = klass.new |
| value = 1.23456 |
| s[:v] = value |
| expect((s.pointer.get_float(0) - value).abs).to be < 0.0001 |
| end |
| |
| it ":double field r/w" do |
| klass = Class.new(FFI::Struct) |
| klass.layout :v, :double, :dummy, :long |
| |
| s = klass.new |
| value = 1.23456 |
| s[:v] = value |
| expect((s.pointer.get_double(0) - value).abs).to be < 0.0001 |
| end |
| module EnumFields |
| extend FFI::Library |
| TestEnum = enum :test_enum, [:c1, 10, :c2, 20, :c3, 30, :c4, 40] |
| class TestStruct < FFI::Struct |
| layout :a, :int, :c, :test_enum, |
| :d, [ TestEnum, TestEnum.symbols.length ] |
| end |
| end |
| |
| it ":enum field r/w" do |
| s = EnumFields::TestStruct.new |
| s[:c] = :c3 |
| |
| expect(s.pointer.get_uint(FFI::Type::INT32.size)).to eq(30) |
| expect(s[:c]).to eq(:c3) |
| end |
| |
| it "array of :enum field" do |
| s = EnumFields::TestStruct.new |
| EnumFields::TestEnum.symbols.each_with_index do |val, i| |
| s[:d][i] = val |
| end |
| |
| EnumFields::TestEnum.symbols.each_with_index do |val, i| |
| expect(s.pointer.get_uint(FFI::Type::INT32.size * (2 + i))).to eq(EnumFields::TestEnum[val]) |
| end |
| |
| s[:d].each_with_index do |val, i| |
| expect(val).to eq(EnumFields::TestEnum.symbols[i]) |
| end |
| end |
| |
| module CallbackMember |
| extend FFI::Library |
| ffi_lib TestLibrary::PATH |
| callback :add, [ :int, :int ], :int |
| callback :sub, [ :int, :int ], :int |
| class TestStruct < FFI::Struct |
| layout :add, :add, |
| :sub, :sub |
| end |
| attach_function :struct_call_add_cb, [TestStruct.in, :int, :int], :int |
| attach_function :struct_call_sub_cb, [TestStruct.in, :int, :int], :int |
| end |
| |
| it "Can have CallbackInfo struct field" do |
| s = CallbackMember::TestStruct.new |
| add_proc = lambda { |a, b| a+b } |
| sub_proc = lambda { |a, b| a-b } |
| s[:add] = add_proc |
| s[:sub] = sub_proc |
| expect(CallbackMember.struct_call_add_cb(s, 40, 2)).to eq(42) |
| expect(CallbackMember.struct_call_sub_cb(s, 44, 2)).to eq(42) |
| end |
| |
| it "Can return its members as a list" do |
| class TestStruct < FFI::Struct |
| layout :a, :int, :b, :int, :c, :int |
| end |
| expect(TestStruct.members).to include(:a, :b, :c) |
| end |
| |
| it "Can return its instance members and values as lists" do |
| class TestStruct < FFI::Struct |
| layout :a, :int, :b, :int, :c, :int |
| end |
| s = TestStruct.new |
| expect(s.members).to include(:a, :b, :c) |
| s[:a] = 1 |
| s[:b] = 2 |
| s[:c] = 3 |
| expect(s.values).to include(1, 2, 3) |
| end |
| |
| it 'should return an ordered field/offset pairs array' do |
| class TestStruct < FFI::Struct |
| layout :a, :int, :b, :int, :c, :int |
| end |
| s = TestStruct.new |
| expect(s.offsets).to eq([[:a, 0], [:b, 4], [:c, 8]]) |
| expect(TestStruct.offsets).to eq([[:a, 0], [:b, 4], [:c, 8]]) |
| end |
| |
| it "Struct#offset_of returns offset of field within struct" do |
| class TestStruct < FFI::Struct |
| layout :a, :int, :b, :int, :c, :int |
| end |
| expect(TestStruct.offset_of(:a)).to eq(0) |
| expect(TestStruct.offset_of(:b)).to eq(4) |
| expect(TestStruct.offset_of(:c)).to eq(8) |
| end |
| end |
| |
| describe FFI::Struct, ".layout" do |
| module FFISpecs |
| module LibTest |
| extend FFI::Library |
| ffi_lib TestLibrary::PATH |
| begin |
| attach_function :ptr_ret_int32_t, [ :pointer, :int ], :int |
| rescue FFI::NotFoundError |
| # NetBSD uses #define instead of typedef for these |
| attach_function :ptr_ret_int32_t, :ptr_ret___int32_t, [ :pointer, :int ], :int |
| end |
| end |
| end |
| |
| describe "when derived class is not assigned to any constant" do |
| it "resolves a built-in type" do |
| klass = Class.new FFI::Struct |
| klass.layout :number, :int |
| |
| instance = klass.new |
| instance[:number] = 0xA1 |
| expect(FFISpecs::LibTest.ptr_ret_int32_t(instance, 0)).to eq(0xA1) |
| end |
| end |
| |
| describe "when derived class is assigned to a constant" do |
| it "resolves a built-in type" do |
| class FFISpecs::TestStruct < FFI::Struct |
| layout :number, :int |
| end |
| |
| instance = FFISpecs::TestStruct.new |
| instance[:number] = 0xA1 |
| expect(FFISpecs::LibTest.ptr_ret_int32_t(instance, 0)).to eq(0xA1) |
| end |
| |
| it "resolves a type from the enclosing module" do |
| module FFISpecs::LibTest |
| typedef :uint, :custom_int |
| |
| class TestStruct < FFI::Struct |
| layout :number, :custom_int |
| end |
| end |
| |
| instance = FFISpecs::LibTest::TestStruct.new |
| instance[:number] = 0xA1 |
| expect(FFISpecs::LibTest.ptr_ret_int32_t(instance, 0)).to eq(0xA1) |
| end |
| end |
| end |
| |
| describe FFI::Struct, ' with a nested struct field' do |
| module LibTest |
| extend FFI::Library |
| ffi_lib TestLibrary::PATH |
| class NestedStruct < FFI::Struct |
| layout :i, :int |
| end |
| class ContainerStruct < FFI::Struct |
| layout :first, :char, :ns, NestedStruct |
| end |
| attach_function :struct_align_nested_struct, [ :pointer ], :int |
| attach_function :struct_make_container_struct, [ :int ], :pointer |
| end |
| before do |
| @cs = LibTest::ContainerStruct.new |
| end |
| |
| it 'should align correctly nested struct field' do |
| @cs[:ns][:i] = 123 |
| expect(LibTest.struct_align_nested_struct(@cs.to_ptr)).to eq(123) |
| end |
| |
| it 'should correctly calculate Container size (in bytes)' do |
| expect(LibTest::ContainerStruct.size).to eq(8) |
| end |
| |
| it 'should return a Struct object when the field is accessed' do |
| expect(@cs[:ns].is_a?(FFI::Struct)).to be true |
| end |
| |
| it 'should read a value from memory' do |
| @cs = LibTest::ContainerStruct.new(LibTest.struct_make_container_struct(123)) |
| expect(@cs[:ns][:i]).to eq(123) |
| end |
| |
| it 'should write a value to memory' do |
| @cs = LibTest::ContainerStruct.new(LibTest.struct_make_container_struct(123)) |
| @cs[:ns][:i] = 456 |
| expect(LibTest.struct_align_nested_struct(@cs.to_ptr)).to eq(456) |
| end |
| |
| it 'should be able to assign struct instance to nested field' do |
| cs = LibTest::ContainerStruct.new(LibTest.struct_make_container_struct(123)) |
| ns = LibTest::NestedStruct.new |
| ns[:i] = 567 |
| cs[:ns] = ns |
| expect(cs[:ns][:i]).to eq(567) |
| expect(LibTest.struct_align_nested_struct(cs.to_ptr)).to eq(567) |
| end |
| end |
| |
| describe FFI::Struct, ' with a nested array of structs' do |
| module InlineArrayOfStructs |
| extend FFI::Library |
| ffi_lib TestLibrary::PATH |
| class NestedStruct < FFI::Struct |
| layout :i, :int |
| end |
| class ContainerStruct < FFI::Struct |
| layout :first, :char, :ns, [ NestedStruct, 1 ] |
| end |
| attach_function :struct_align_nested_struct, [ :pointer ], :int |
| attach_function :struct_make_container_struct, [ :int ], :pointer |
| end |
| |
| before do |
| @cs = InlineArrayOfStructs::ContainerStruct.new |
| end |
| |
| it 'should align correctly nested struct field' do |
| @cs[:ns][0][:i] = 123 |
| expect(InlineArrayOfStructs.struct_align_nested_struct(@cs.to_ptr)).to eq(123) |
| end |
| |
| it 'should correctly calculate Container size (in bytes)' do |
| expect(InlineArrayOfStructs::ContainerStruct.size).to eq(8) |
| end |
| |
| it 'should return a Struct object when the field is accessed' do |
| expect(@cs[:ns][0].is_a?(FFI::Struct)).to be true |
| end |
| |
| it 'should read a value from memory' do |
| @cs = InlineArrayOfStructs::ContainerStruct.new(InlineArrayOfStructs.struct_make_container_struct(123)) |
| expect(@cs[:ns][0][:i]).to eq(123) |
| end |
| |
| it 'should write a value to memory' do |
| @cs = InlineArrayOfStructs::ContainerStruct.new(InlineArrayOfStructs.struct_make_container_struct(123)) |
| @cs[:ns][0][:i] = 456 |
| expect(InlineArrayOfStructs.struct_align_nested_struct(@cs.to_ptr)).to eq(456) |
| end |
| |
| it 'should support Enumerable#each' do |
| @cs = InlineArrayOfStructs::ContainerStruct.new(InlineArrayOfStructs.struct_make_container_struct(123)) |
| ints = [] |
| @cs[:ns].each { |s| ints << s[:i] } |
| expect(ints[0]).to eq(123) |
| end |
| end |
| |
| describe FFI::Struct, ' by value' do |
| module LibTest |
| extend FFI::Library |
| ffi_lib TestLibrary::PATH |
| |
| class S8S32 < FFI::Struct |
| layout :s8, :char, :s32, :int |
| end |
| |
| class StructString < FFI::Struct |
| layout :bytes, :string, :len, :int |
| end |
| |
| attach_function :struct_return_s8s32, [ ], S8S32.by_value |
| attach_function :struct_s8s32_set, [ :char, :int ], S8S32.by_value |
| attach_function :struct_s8s32_get_s8, [ S8S32.by_value ], :char |
| attach_function :struct_s8s32_get_s32, [ S8S32.by_value ], :int |
| attach_function :struct_s8s32_s32_ret_s32, [ S8S32.by_value, :int ], :int |
| attach_function :struct_s8s32_s64_ret_s64, [ S8S32.by_value, :long_long ], :long_long |
| attach_function :struct_s8s32_ret_s8s32, [ S8S32.by_value ], S8S32.by_value |
| attach_function :struct_s32_ptr_s32_s8s32_ret_s32, [ :int, :pointer, :int, S8S32.by_value ], :int |
| attach_function :struct_varargs_ret_struct_string, [ :int, :varargs ], StructString.by_value |
| end |
| |
| it 'return using pre-set values' do |
| s = LibTest.struct_return_s8s32 |
| expect(s[:s8]).to eq(0x7f) |
| expect(s[:s32]).to eq(0x12345678) |
| end |
| |
| it 'return using passed in values' do |
| s = LibTest.struct_s8s32_set(123, 456789) |
| expect(s[:s8]).to eq(123) |
| expect(s[:s32]).to eq(456789) |
| end |
| |
| it 'parameter' do |
| s = LibTest::S8S32.new |
| s[:s8] = 0x12 |
| s[:s32] = 0x34567890 |
| expect(LibTest.struct_s8s32_get_s8(s)).to eq(0x12) |
| expect(LibTest.struct_s8s32_get_s32(s)).to eq(0x34567890) |
| end |
| |
| it 'parameter with following s32' do |
| s = LibTest::S8S32.new |
| s[:s8] = 0x12 |
| s[:s32] = 0x34567890 |
| |
| expect(LibTest.struct_s8s32_s32_ret_s32(s, 0x1eefdead)).to eq(0x1eefdead) |
| end |
| |
| # it 'parameter with following s64' do |
| # s = LibTest::S8S64.new |
| # s[:s8] = 0x12 |
| # s[:s64] = 0x34567890 |
| # |
| # |
| # LibTest.struct_s8s64_s64_ret_s64(s, 0x1eefdead1eefdead).should == 0x1eefdead1eefdead |
| # end |
| |
| it 'parameter with preceding s32,ptr,s32' do |
| s = LibTest::S8S32.new |
| s[:s8] = 0x12 |
| s[:s32] = 0x34567890 |
| out = LibTest::S8S32.new |
| expect(LibTest.struct_s32_ptr_s32_s8s32_ret_s32(0x1000000, out, 0x1eafbeef, s)).to eq(0x34567890) |
| expect(out[:s8]).to eq(s[:s8]) |
| expect(out[:s32]).to eq(s[:s32]) |
| end |
| |
| it 'parameter with preceding s32,string,s32' do |
| s = LibTest::S8S32.new |
| s[:s8] = 0x12 |
| s[:s32] = 0x34567890 |
| out = 0.chr * 32 |
| expect(LibTest.struct_s32_ptr_s32_s8s32_ret_s32(0x1000000, out, 0x1eafbeef, s)).to eq(0x34567890) |
| end |
| |
| it 'parameter, returning struct by value' do |
| s = LibTest::S8S32.new |
| s[:s8] = 0x12 |
| s[:s32] = 0x34567890 |
| |
| ret = LibTest.struct_s8s32_ret_s8s32(s) |
| expect(ret[:s8]).to eq(s[:s8]) |
| expect(ret[:s32]).to eq(s[:s32]) |
| end |
| |
| it 'varargs returning a struct' do |
| string = "test" |
| s = LibTest.struct_varargs_ret_struct_string(4, :string, string) |
| expect(s[:len]).to eq(string.length) |
| expect(s[:bytes]).to eq(string) |
| end |
| end |
| |
| describe FFI::Struct, ' with an array field' do |
| module LibTest |
| extend FFI::Library |
| ffi_lib TestLibrary::PATH |
| class StructWithArray < FFI::Struct |
| layout :first, :char, :a, [:int, 5] |
| end |
| attach_function :struct_make_struct_with_array, [:int, :int, :int, :int, :int], :pointer |
| attach_function :struct_field_array, [:pointer], :pointer |
| end |
| before do |
| @s = LibTest::StructWithArray.new |
| end |
| |
| it 'should correctly calculate StructWithArray size (in bytes)' do |
| expect(LibTest::StructWithArray.size).to eq(24) |
| end |
| |
| it 'should read values from memory' do |
| @s = LibTest::StructWithArray.new(LibTest.struct_make_struct_with_array(0, 1, 2, 3, 4)) |
| expect(@s[:a].to_a).to eq([0, 1, 2, 3, 4]) |
| end |
| # it 'should cache array object for successive calls' do |
| # @s[:a].object_id.should == @s[:a].object_id |
| # end |
| |
| it 'should return the number of elements in the array field' do |
| @s = LibTest::StructWithArray.new(LibTest.struct_make_struct_with_array(0, 1, 2, 3, 4)) |
| expect(@s[:a].size).to eq(5) |
| end |
| |
| it 'should allow iteration through the array elements' do |
| @s = LibTest::StructWithArray.new(LibTest.struct_make_struct_with_array(0, 1, 2, 3, 4)) |
| @s[:a].each_with_index { |elem, i| expect(elem).to eq(i) } |
| end |
| |
| it 'should return the pointer to the array' do |
| @s = LibTest::StructWithArray.new(LibTest.struct_make_struct_with_array(0, 1, 2, 3, 4)) |
| expect(@s[:a].to_ptr).to eq(LibTest::struct_field_array(@s.to_ptr)) |
| end |
| end |
| |
| describe 'BuggedStruct' do |
| module LibTest |
| extend FFI::Library |
| ffi_lib TestLibrary::PATH |
| class BuggedStruct < FFI::Struct |
| layout :visible, :uchar, |
| :x, :uint, |
| :y, :uint, |
| :rx, :short, |
| :ry, :short, |
| :order, :uchar, |
| :size, :uchar |
| end |
| attach_function :bugged_struct_size, [], :uint |
| end |
| |
| it 'should return its correct size' do |
| expect(LibTest::BuggedStruct.size).to eq(LibTest.bugged_struct_size) |
| end |
| |
| it "offsets within struct should be correct" do |
| expect(LibTest::BuggedStruct.offset_of(:visible)).to eq(0) |
| expect(LibTest::BuggedStruct.offset_of(:x)).to eq(4) |
| expect(LibTest::BuggedStruct.offset_of(:y)).to eq(8) |
| expect(LibTest::BuggedStruct.offset_of(:rx)).to eq(12) |
| expect(LibTest::BuggedStruct.offset_of(:ry)).to eq(14) |
| expect(LibTest::BuggedStruct.offset_of(:order)).to eq(16) |
| expect(LibTest::BuggedStruct.offset_of(:size)).to eq(17) |
| end |
| |
| it 'should return correct field/offset pairs' do |
| expect(LibTest::BuggedStruct.offsets.sort do |a, b| |
| a[1] <=> b[1] |
| end).to eq([[:visible, 0], [:x, 4], [:y, 8], [:rx, 12], [:ry, 14], [:order, 16], [:size, 17]]) |
| end |
| end |
| |
| describe "Struct allocation" do |
| it "MemoryPointer.new(Struct, 2)" do |
| class S < FFI::Struct |
| layout :i, :uint |
| end |
| p = FFI::MemoryPointer.new(S, 2) |
| expect(p.total).to eq(8) |
| expect(p.type_size).to eq(4) |
| p.put_uint(4, 0xdeadbeef) |
| expect(S.new(p[1])[:i]).to eq(0xdeadbeef) |
| expect(p[1].address).to eq((p[0].address + 4)) |
| end |
| |
| it "Buffer.new(Struct, 2)" do |
| class S < FFI::Struct |
| layout :i, :uint |
| end |
| p = FFI::Buffer.new(S, 2) |
| expect(p.total).to eq(8) |
| expect(p.type_size).to eq(4) |
| p.put_uint(4, 0xdeadbeef) |
| expect(S.new(p[1])[:i]).to eq(0xdeadbeef) |
| end |
| |
| it "null? should be true when initialized with NULL pointer" do |
| class S < FFI::Struct |
| layout :i, :uint |
| end |
| expect(S.new(FFI::Pointer::NULL)).to be_null |
| end |
| |
| it "null? should be false when initialized with non-NULL pointer" do |
| class S < FFI::Struct |
| layout :i, :uint |
| end |
| expect(S.new(FFI::MemoryPointer.new(S))).not_to be_null |
| end |
| |
| it "supports :bool as a struct member" do |
| expect do |
| c = Class.new(FFI::Struct) do |
| layout :b, :bool |
| end |
| struct = c.new |
| struct[:b] = ! struct[:b] |
| end.not_to raise_error Exception |
| end |
| |
| end |
| |
| describe "variable-length arrays" do |
| it "zero length array should be accepted as last field" do |
| expect { |
| Class.new(FFI::Struct) do |
| layout :count, :int, :data, [ :char, 0 ] |
| end |
| }.not_to raise_error Exception |
| end |
| |
| it "zero length array before last element should raise error" do |
| expect { |
| Class.new(FFI::Struct) do |
| layout :data, [ :char, 0 ], :count, :int |
| end |
| }.to raise_error |
| end |
| |
| it "can access elements of array" do |
| struct_class = Class.new(FFI::Struct) do |
| layout :count, :int, :data, [ :long, 0 ] |
| end |
| s = struct_class.new(FFI::MemoryPointer.new(1024)) |
| s[:data][0] = 0x1eadbeef |
| s[:data][1] = 0x12345678 |
| expect(s[:data][0]).to eq(0x1eadbeef) |
| expect(s[:data][1]).to eq(0x12345678) |
| end |
| |
| it "non-variable length array is bounds checked" do |
| struct_class = Class.new(FFI::Struct) do |
| layout :count, :int, :data, [ :long, 1 ] |
| end |
| s = struct_class.new(FFI::MemoryPointer.new(1024)) |
| s[:data][0] = 0x1eadbeef |
| expect { s[:data][1] = 0x12345678 }.to raise_error |
| expect(s[:data][0]).to eq(0x1eadbeef) |
| expect { expect(s[:data][1]).to == 0x12345678 }.to raise_error |
| end |
| end |