From 44008dbc422d34e73cbb739b7ed701c3d9d599c6 Mon Sep 17 00:00:00 2001 From: Lars Kanis Date: Wed, 8 Jan 2020 12:55:24 +0100 Subject: [PATCH] Disallow struct layout changes This seems like not only an odd feature, but a bad/dangerous one. When the layout has been used for any existing objects, changing it will produce very difficult-to-diagnose bugs (at least) or potentially memory violations if the layout is re-retrieved from the class and used for previous pointers. It was heavily used in the specs to overwrite one and the same class multiple times to change the layout immediatelly before the test. However this use case is specific to testing and should not be used anyway. Fixes #734 --- lib/ffi/struct.rb | 2 +- spec/ffi/callback_spec.rb | 1451 +++++++++++++------------- spec/ffi/library_spec.rb | 10 +- spec/ffi/rbx/attach_function_spec.rb | 42 +- spec/ffi/rbx/struct_spec.rb | 18 +- spec/ffi/struct_spec.rb | 768 +++++++------- 6 files changed, 1161 insertions(+), 1130 deletions(-) diff --git a/lib/ffi/struct.rb b/lib/ffi/struct.rb index 126d437ce..bfb607142 100644 --- a/lib/ffi/struct.rb +++ b/lib/ffi/struct.rb @@ -204,7 +204,7 @@ class << self # end # @note Creating a layout from a hash +spec+ is supported only for Ruby 1.9. def layout(*spec) - #raise RuntimeError, "struct layout already defined for #{self.inspect}" if defined?(@layout) + raise RuntimeError, "struct layout already defined for #{self.inspect}" if defined?(@layout) return @layout if spec.size == 0 builder = StructLayoutBuilder.new diff --git a/spec/ffi/callback_spec.rb b/spec/ffi/callback_spec.rb index 400364397..b6ddafc48 100644 --- a/spec/ffi/callback_spec.rb +++ b/spec/ffi/callback_spec.rb @@ -5,876 +5,879 @@ require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper")) -describe "Callback" do -# module LibC -# extend FFI::Library -# callback :qsort_cmp, [ :pointer, :pointer ], :int -# attach_function :qsort, [ :pointer, :int, :int, :qsort_cmp ], :int -# end -# it "arguments get passed correctly" do -# p = MemoryPointer.new(:int, 2) -# p.put_array_of_int32(0, [ 1 , 2 ]) -# args = [] -# cmp = proc do |p1, p2| args.push(p1.get_int(0)); args.push(p2.get_int(0)); 0; end -# # this is a bit dodgey, as it relies on qsort passing the args in order -# LibC.qsort(p, 2, 4, cmp) -# args.should == [ 1, 2 ] -# end -# -# it "Block can be substituted for Callback as last argument" do -# p = MemoryPointer.new(:int, 2) -# p.put_array_of_int32(0, [ 1 , 2 ]) -# args = [] -# # this is a bit dodgey, as it relies on qsort passing the args in order -# LibC.qsort(p, 2, 4) do |p1, p2| -# args.push(p1.get_int(0)) -# args.push(p2.get_int(0)) -# 0 -# end -# args.should == [ 1, 2 ] -# end - module LibTest - extend FFI::Library - ffi_lib TestLibrary::PATH - class S8F32S32 < FFI::Struct - layout :s8, :char, :f32, :float, :s32, :int - end - - callback :cbVrS8, [ ], :char - callback :cbVrU8, [ ], :uchar - callback :cbVrS16, [ ], :short - callback :cbVrU16, [ ], :ushort - callback :cbVrS32, [ ], :int - callback :cbVrU32, [ ], :uint - callback :cbVrL, [ ], :long - callback :cbVrUL, [ ], :ulong - callback :cbVrS64, [ ], :long_long - callback :cbVrU64, [ ], :ulong_long - callback :cbVrP, [], :pointer - callback :cbVrZ, [], :bool - callback :cbCrV, [ :char ], :void - callback :cbSrV, [ :short ], :void - callback :cbIrV, [ :int ], :void - callback :cbLrV, [ :long ], :void - callback :cbULrV, [ :ulong ], :void - callback :cbLrV, [ :long_long ], :void - callback :cbVrT, [ ], S8F32S32.by_value - callback :cbTrV, [ S8F32S32.by_value ], :void - callback :cbYrV, [ S8F32S32.ptr ], :void - callback :cbVrY, [ ], S8F32S32.ptr - - attach_function :testCallbackVrS8, :testClosureVrB, [ :cbVrS8 ], :char - attach_function :testCallbackVrU8, :testClosureVrB, [ :cbVrU8 ], :uchar - attach_function :testCallbackVrS16, :testClosureVrS, [ :cbVrS16 ], :short - attach_function :testCallbackVrU16, :testClosureVrS, [ :cbVrU16 ], :ushort - attach_function :testCallbackVrS32, :testClosureVrI, [ :cbVrS32 ], :int - attach_function :testCallbackVrU32, :testClosureVrI, [ :cbVrU32 ], :uint - attach_function :testCallbackVrL, :testClosureVrL, [ :cbVrL ], :long - attach_function :testCallbackVrZ, :testClosureVrZ, [ :cbVrZ ], :bool - attach_function :testCallbackVrUL, :testClosureVrL, [ :cbVrUL ], :ulong - attach_function :testCallbackVrS64, :testClosureVrLL, [ :cbVrS64 ], :long_long - attach_function :testCallbackVrU64, :testClosureVrLL, [ :cbVrU64 ], :ulong_long - attach_function :testCallbackVrP, :testClosureVrP, [ :cbVrP ], :pointer - attach_function :testCallbackReturningFunction, :testClosureVrP, [ :cbVrP ], :cbVrP - attach_function :testCallbackVrY, :testClosureVrP, [ :cbVrY ], S8F32S32.ptr - attach_function :testCallbackVrT, :testClosureVrT, [ :cbVrT ], S8F32S32.by_value - attach_function :testCallbackTrV, :testClosureTrV, [ :cbTrV, S8F32S32.ptr ], :void - attach_variable :cbVrS8, :gvar_pointer, :cbVrS8 - attach_variable :pVrS8, :gvar_pointer, :pointer - attach_function :testGVarCallbackVrS8, :testClosureVrB, [ :pointer ], :char - attach_function :testOptionalCallbackCrV, :testOptionalClosureBrV, [ :cbCrV, :char ], :void - - end +module CallbackSpecs + describe "Callback" do + # module LibC + # extend FFI::Library + # callback :qsort_cmp, [ :pointer, :pointer ], :int + # attach_function :qsort, [ :pointer, :int, :int, :qsort_cmp ], :int + # end + # it "arguments get passed correctly" do + # p = MemoryPointer.new(:int, 2) + # p.put_array_of_int32(0, [ 1 , 2 ]) + # args = [] + # cmp = proc do |p1, p2| args.push(p1.get_int(0)); args.push(p2.get_int(0)); 0; end + # # this is a bit dodgey, as it relies on qsort passing the args in order + # LibC.qsort(p, 2, 4, cmp) + # args.should == [ 1, 2 ] + # end + # + # it "Block can be substituted for Callback as last argument" do + # p = MemoryPointer.new(:int, 2) + # p.put_array_of_int32(0, [ 1 , 2 ]) + # args = [] + # # this is a bit dodgey, as it relies on qsort passing the args in order + # LibC.qsort(p, 2, 4) do |p1, p2| + # args.push(p1.get_int(0)) + # args.push(p2.get_int(0)) + # 0 + # end + # args.should == [ 1, 2 ] + # end + module LibTest + extend FFI::Library + ffi_lib TestLibrary::PATH + class S8F32S32 < FFI::Struct + layout :s8, :char, :f32, :float, :s32, :int + end - it "returning :char (0)" do - expect(LibTest.testCallbackVrS8 { 0 }).to eq(0) - end + callback :cbVrS8, [ ], :char + callback :cbVrU8, [ ], :uchar + callback :cbVrS16, [ ], :short + callback :cbVrU16, [ ], :ushort + callback :cbVrS32, [ ], :int + callback :cbVrU32, [ ], :uint + callback :cbVrL, [ ], :long + callback :cbVrUL, [ ], :ulong + callback :cbVrS64, [ ], :long_long + callback :cbVrU64, [ ], :ulong_long + callback :cbVrP, [], :pointer + callback :cbVrZ, [], :bool + callback :cbCrV, [ :char ], :void + callback :cbSrV, [ :short ], :void + callback :cbIrV, [ :int ], :void + callback :cbLrV, [ :long ], :void + callback :cbULrV, [ :ulong ], :void + callback :cbLrV, [ :long_long ], :void + callback :cbVrT, [ ], S8F32S32.by_value + callback :cbTrV, [ S8F32S32.by_value ], :void + callback :cbYrV, [ S8F32S32.ptr ], :void + callback :cbVrY, [ ], S8F32S32.ptr + + attach_function :testCallbackVrS8, :testClosureVrB, [ :cbVrS8 ], :char + attach_function :testCallbackVrU8, :testClosureVrB, [ :cbVrU8 ], :uchar + attach_function :testCallbackVrS16, :testClosureVrS, [ :cbVrS16 ], :short + attach_function :testCallbackVrU16, :testClosureVrS, [ :cbVrU16 ], :ushort + attach_function :testCallbackVrS32, :testClosureVrI, [ :cbVrS32 ], :int + attach_function :testCallbackVrU32, :testClosureVrI, [ :cbVrU32 ], :uint + attach_function :testCallbackVrL, :testClosureVrL, [ :cbVrL ], :long + attach_function :testCallbackVrZ, :testClosureVrZ, [ :cbVrZ ], :bool + attach_function :testCallbackVrUL, :testClosureVrL, [ :cbVrUL ], :ulong + attach_function :testCallbackVrS64, :testClosureVrLL, [ :cbVrS64 ], :long_long + attach_function :testCallbackVrU64, :testClosureVrLL, [ :cbVrU64 ], :ulong_long + attach_function :testCallbackVrP, :testClosureVrP, [ :cbVrP ], :pointer + attach_function :testCallbackReturningFunction, :testClosureVrP, [ :cbVrP ], :cbVrP + attach_function :testCallbackVrY, :testClosureVrP, [ :cbVrY ], S8F32S32.ptr + attach_function :testCallbackVrT, :testClosureVrT, [ :cbVrT ], S8F32S32.by_value + attach_function :testCallbackTrV, :testClosureTrV, [ :cbTrV, S8F32S32.ptr ], :void + attach_variable :cbVrS8, :gvar_pointer, :cbVrS8 + attach_variable :pVrS8, :gvar_pointer, :pointer + attach_function :testGVarCallbackVrS8, :testClosureVrB, [ :pointer ], :char + attach_function :testOptionalCallbackCrV, :testOptionalClosureBrV, [ :cbCrV, :char ], :void - it "returning :char (127)" do - expect(LibTest.testCallbackVrS8 { 127 }).to eq(127) - end + end - it "returning :char (-128)" do - expect(LibTest.testCallbackVrS8 { -128 }).to eq(-128) - end - # test wrap around - it "returning :char (128)" do - expect(LibTest.testCallbackVrS8 { 128 }).to eq(-128) - end + it "returning :char (0)" do + expect(LibTest.testCallbackVrS8 { 0 }).to eq(0) + end - it "returning :char (255)" do - expect(LibTest.testCallbackVrS8 { 0xff }).to eq(-1) - end + it "returning :char (127)" do + expect(LibTest.testCallbackVrS8 { 127 }).to eq(127) + end - it "returning :uchar (0)" do - expect(LibTest.testCallbackVrU8 { 0 }).to eq(0) - end + it "returning :char (-128)" do + expect(LibTest.testCallbackVrS8 { -128 }).to eq(-128) + end + # test wrap around + it "returning :char (128)" do + expect(LibTest.testCallbackVrS8 { 128 }).to eq(-128) + end - it "returning :uchar (0xff)" do - expect(LibTest.testCallbackVrU8 { 0xff }).to eq(0xff) - end + it "returning :char (255)" do + expect(LibTest.testCallbackVrS8 { 0xff }).to eq(-1) + end - it "returning :uchar (-1)" do - expect(LibTest.testCallbackVrU8 { -1 }).to eq(0xff) - end + it "returning :uchar (0)" do + expect(LibTest.testCallbackVrU8 { 0 }).to eq(0) + end - it "returning :uchar (128)" do - expect(LibTest.testCallbackVrU8 { 128 }).to eq(128) - end + it "returning :uchar (0xff)" do + expect(LibTest.testCallbackVrU8 { 0xff }).to eq(0xff) + end - it "returning :uchar (-128)" do - expect(LibTest.testCallbackVrU8 { -128 }).to eq(128) - end + it "returning :uchar (-1)" do + expect(LibTest.testCallbackVrU8 { -1 }).to eq(0xff) + end - it "returning :short (0)" do - expect(LibTest.testCallbackVrS16 { 0 }).to eq(0) - end + it "returning :uchar (128)" do + expect(LibTest.testCallbackVrU8 { 128 }).to eq(128) + end - it "returning :short (0x7fff)" do - expect(LibTest.testCallbackVrS16 { 0x7fff }).to eq(0x7fff) - end - # test wrap around - it "returning :short (0x8000)" do - expect(LibTest.testCallbackVrS16 { 0x8000 }).to eq(-0x8000) - end + it "returning :uchar (-128)" do + expect(LibTest.testCallbackVrU8 { -128 }).to eq(128) + end - it "returning :short (0xffff)" do - expect(LibTest.testCallbackVrS16 { 0xffff }).to eq(-1) - end + it "returning :short (0)" do + expect(LibTest.testCallbackVrS16 { 0 }).to eq(0) + end - it "returning :ushort (0)" do - expect(LibTest.testCallbackVrU16 { 0 }).to eq(0) - end + it "returning :short (0x7fff)" do + expect(LibTest.testCallbackVrS16 { 0x7fff }).to eq(0x7fff) + end + # test wrap around + it "returning :short (0x8000)" do + expect(LibTest.testCallbackVrS16 { 0x8000 }).to eq(-0x8000) + end - it "returning :ushort (0x7fff)" do - expect(LibTest.testCallbackVrU16 { 0x7fff }).to eq(0x7fff) - end + it "returning :short (0xffff)" do + expect(LibTest.testCallbackVrS16 { 0xffff }).to eq(-1) + end - it "returning :ushort (0x8000)" do - expect(LibTest.testCallbackVrU16 { 0x8000 }).to eq(0x8000) - end + it "returning :ushort (0)" do + expect(LibTest.testCallbackVrU16 { 0 }).to eq(0) + end - it "returning :ushort (0xffff)" do - expect(LibTest.testCallbackVrU16 { 0xffff }).to eq(0xffff) - end + it "returning :ushort (0x7fff)" do + expect(LibTest.testCallbackVrU16 { 0x7fff }).to eq(0x7fff) + end - it "returning :ushort (-1)" do - expect(LibTest.testCallbackVrU16 { -1 }).to eq(0xffff) - end + it "returning :ushort (0x8000)" do + expect(LibTest.testCallbackVrU16 { 0x8000 }).to eq(0x8000) + end - it "returning :int (0)" do - expect(LibTest.testCallbackVrS32 { 0 }).to eq(0) - end + it "returning :ushort (0xffff)" do + expect(LibTest.testCallbackVrU16 { 0xffff }).to eq(0xffff) + end - it "returning :int (0x7fffffff)" do - expect(LibTest.testCallbackVrS32 { 0x7fffffff }).to eq(0x7fffffff) - end - # test wrap around - it "returning :int (-0x80000000)" do - expect(LibTest.testCallbackVrS32 { -0x80000000 }).to eq(-0x80000000) - end + it "returning :ushort (-1)" do + expect(LibTest.testCallbackVrU16 { -1 }).to eq(0xffff) + end - it "returning :int (-1)" do - expect(LibTest.testCallbackVrS32 { -1 }).to eq(-1) - end + it "returning :int (0)" do + expect(LibTest.testCallbackVrS32 { 0 }).to eq(0) + end - it "returning :uint (0)" do - expect(LibTest.testCallbackVrU32 { 0 }).to eq(0) - end + it "returning :int (0x7fffffff)" do + expect(LibTest.testCallbackVrS32 { 0x7fffffff }).to eq(0x7fffffff) + end + # test wrap around + it "returning :int (-0x80000000)" do + expect(LibTest.testCallbackVrS32 { -0x80000000 }).to eq(-0x80000000) + end - it "returning :uint (0x7fffffff)" do - expect(LibTest.testCallbackVrU32 { 0x7fffffff }).to eq(0x7fffffff) - end - # test wrap around - it "returning :uint (0x80000000)" do - expect(LibTest.testCallbackVrU32 { 0x80000000 }).to eq(0x80000000) - end + it "returning :int (-1)" do + expect(LibTest.testCallbackVrS32 { -1 }).to eq(-1) + end - it "returning :uint (0xffffffff)" do - expect(LibTest.testCallbackVrU32 { 0xffffffff }).to eq(0xffffffff) - end + it "returning :uint (0)" do + expect(LibTest.testCallbackVrU32 { 0 }).to eq(0) + end - it "returning :uint (-1)" do - expect(LibTest.testCallbackVrU32 { -1 }).to eq(0xffffffff) - end + it "returning :uint (0x7fffffff)" do + expect(LibTest.testCallbackVrU32 { 0x7fffffff }).to eq(0x7fffffff) + end + # test wrap around + it "returning :uint (0x80000000)" do + expect(LibTest.testCallbackVrU32 { 0x80000000 }).to eq(0x80000000) + end - it "returning :long (0)" do - expect(LibTest.testCallbackVrL { 0 }).to eq(0) - end + it "returning :uint (0xffffffff)" do + expect(LibTest.testCallbackVrU32 { 0xffffffff }).to eq(0xffffffff) + end - it "returning :long (0x7fffffff)" do - expect(LibTest.testCallbackVrL { 0x7fffffff }).to eq(0x7fffffff) - end - # test wrap around - it "returning :long (-0x80000000)" do - expect(LibTest.testCallbackVrL { -0x80000000 }).to eq(-0x80000000) - end + it "returning :uint (-1)" do + expect(LibTest.testCallbackVrU32 { -1 }).to eq(0xffffffff) + end - it "returning :long (-1)" do - expect(LibTest.testCallbackVrL { -1 }).to eq(-1) - end + it "returning :long (0)" do + expect(LibTest.testCallbackVrL { 0 }).to eq(0) + end - it "returning :ulong (0)" do - expect(LibTest.testCallbackVrUL { 0 }).to eq(0) - end + it "returning :long (0x7fffffff)" do + expect(LibTest.testCallbackVrL { 0x7fffffff }).to eq(0x7fffffff) + end + # test wrap around + it "returning :long (-0x80000000)" do + expect(LibTest.testCallbackVrL { -0x80000000 }).to eq(-0x80000000) + end - it "returning :ulong (0x7fffffff)" do - expect(LibTest.testCallbackVrUL { 0x7fffffff }).to eq(0x7fffffff) - end - # test wrap around - it "returning :ulong (0x80000000)" do - expect(LibTest.testCallbackVrUL { 0x80000000 }).to eq(0x80000000) - end + it "returning :long (-1)" do + expect(LibTest.testCallbackVrL { -1 }).to eq(-1) + end - it "returning :ulong (0xffffffff)" do - expect(LibTest.testCallbackVrUL { 0xffffffff }).to eq(0xffffffff) - end + it "returning :ulong (0)" do + expect(LibTest.testCallbackVrUL { 0 }).to eq(0) + end - it "Callback returning :ulong (-1)" do - if FFI::Platform::LONG_SIZE == 32 - expect(LibTest.testCallbackVrUL { -1 }).to eq(0xffffffff) - else - expect(LibTest.testCallbackVrUL { -1 }).to eq(0xffffffffffffffff) + it "returning :ulong (0x7fffffff)" do + expect(LibTest.testCallbackVrUL { 0x7fffffff }).to eq(0x7fffffff) + end + # test wrap around + it "returning :ulong (0x80000000)" do + expect(LibTest.testCallbackVrUL { 0x80000000 }).to eq(0x80000000) end - end - it "returning :long_long (0)" do - expect(LibTest.testCallbackVrS64 { 0 }).to eq(0) - end + it "returning :ulong (0xffffffff)" do + expect(LibTest.testCallbackVrUL { 0xffffffff }).to eq(0xffffffff) + end - it "returning :long_long (0x7fffffffffffffff)" do - expect(LibTest.testCallbackVrS64 { 0x7fffffffffffffff }).to eq(0x7fffffffffffffff) - end - # test wrap around - it "returning :long_long (-0x8000000000000000)" do - expect(LibTest.testCallbackVrS64 { -0x8000000000000000 }).to eq(-0x8000000000000000) - end + it "Callback returning :ulong (-1)" do + if FFI::Platform::LONG_SIZE == 32 + expect(LibTest.testCallbackVrUL { -1 }).to eq(0xffffffff) + else + expect(LibTest.testCallbackVrUL { -1 }).to eq(0xffffffffffffffff) + end + end - it "returning :long_long (-1)" do - expect(LibTest.testCallbackVrS64 { -1 }).to eq(-1) - end + it "returning :long_long (0)" do + expect(LibTest.testCallbackVrS64 { 0 }).to eq(0) + end - it "returning bool" do - expect(LibTest.testCallbackVrZ { true }).to be true - end + it "returning :long_long (0x7fffffffffffffff)" do + expect(LibTest.testCallbackVrS64 { 0x7fffffffffffffff }).to eq(0x7fffffffffffffff) + end + # test wrap around + it "returning :long_long (-0x8000000000000000)" do + expect(LibTest.testCallbackVrS64 { -0x8000000000000000 }).to eq(-0x8000000000000000) + end - it "returning :pointer (nil)" do - expect(LibTest.testCallbackVrP { nil }).to be_null - end + it "returning :long_long (-1)" do + expect(LibTest.testCallbackVrS64 { -1 }).to eq(-1) + end - it "returning :pointer (MemoryPointer)" do - p = FFI::MemoryPointer.new :long - expect(LibTest.testCallbackVrP { p }).to eq(p) - end + it "returning bool" do + expect(LibTest.testCallbackVrZ { true }).to be true + end - it "returning a callback function" do - ret = LibTest.testCallbackReturningFunction { FFI::Pointer.new(42) } - expect(ret).to be_kind_of(FFI::Function) - expect(ret.address).to eq(42) - end + it "returning :pointer (nil)" do + expect(LibTest.testCallbackVrP { nil }).to be_null + end - it "returning struct by value" do - skip "Segfault on 32 bit MINGW" if RUBY_PLATFORM == 'i386-mingw32' - s = LibTest::S8F32S32.new - s[:s8] = 0x12 - s[:s32] = 0x1eefbeef - s[:f32] = 1.234567 - ret = LibTest.testCallbackVrT { s } - expect(ret[:s8]).to eq(s[:s8]) - expect(ret[:f32]).to eq(s[:f32]) - expect(ret[:s32]).to eq(s[:s32]) + it "returning :pointer (MemoryPointer)" do + p = FFI::MemoryPointer.new :long + expect(LibTest.testCallbackVrP { p }).to eq(p) + end - end + it "returning a callback function" do + ret = LibTest.testCallbackReturningFunction { FFI::Pointer.new(42) } + expect(ret).to be_kind_of(FFI::Function) + expect(ret.address).to eq(42) + end - it "struct by value parameter" do - s = LibTest::S8F32S32.new - s[:s8] = 0x12 - s[:s32] = 0x1eefbeef - s[:f32] = 1.234567 - s2 = LibTest::S8F32S32.new + it "returning struct by value" do + skip "Segfault on 32 bit MINGW" if RUBY_PLATFORM == 'i386-mingw32' + s = LibTest::S8F32S32.new + s[:s8] = 0x12 + s[:s32] = 0x1eefbeef + s[:f32] = 1.234567 + ret = LibTest.testCallbackVrT { s } + expect(ret[:s8]).to eq(s[:s8]) + expect(ret[:f32]).to eq(s[:f32]) + expect(ret[:s32]).to eq(s[:s32]) - LibTest.testCallbackTrV(s) do |struct| - s2[:s8] = struct[:s8] - s2[:f32] = struct[:f32] - s2[:s32] = struct[:s32] end - expect(s2[:s8]).to eql 0x12 - expect(s2[:s32]).to eql 0x1eefbeef - expect(s2[:f32]).to be_within(0.0000001).of 1.234567 - end + it "struct by value parameter" do + s = LibTest::S8F32S32.new + s[:s8] = 0x12 + s[:s32] = 0x1eefbeef + s[:f32] = 1.234567 + s2 = LibTest::S8F32S32.new + + LibTest.testCallbackTrV(s) do |struct| + s2[:s8] = struct[:s8] + s2[:f32] = struct[:f32] + s2[:s32] = struct[:s32] + end + + expect(s2[:s8]).to eql 0x12 + expect(s2[:s32]).to eql 0x1eefbeef + expect(s2[:f32]).to be_within(0.0000001).of 1.234567 + end - - it "global variable" do - proc = Proc.new { 0x1e } - LibTest.cbVrS8 = proc - expect(LibTest.testGVarCallbackVrS8(LibTest.pVrS8)).to eq(0x1e) - end - describe "When the callback is considered optional by the underlying library" do - it "should handle receiving 'nil' in place of the closure" do - expect(LibTest.testOptionalCallbackCrV(nil, 13)).to be_nil + it "global variable" do + proc = Proc.new { 0x1e } + LibTest.cbVrS8 = proc + expect(LibTest.testGVarCallbackVrS8(LibTest.pVrS8)).to eq(0x1e) end - end - describe 'when inlined' do - it 'could be anonymous' do - module LibTest - extend FFI::Library - ffi_lib TestLibrary::PATH - attach_function :testAnonymousCallbackVrS8, :testClosureVrB, [ callback([ ], :char) ], :char + describe "When the callback is considered optional by the underlying library" do + it "should handle receiving 'nil' in place of the closure" do + expect(LibTest.testOptionalCallbackCrV(nil, 13)).to be_nil end - expect(LibTest.testAnonymousCallbackVrS8 { 0 }).to eq(0) end - end - - describe "as return value" do - it "should not blow up when a callback is defined that returns a callback" do - expect(module LibTest - extend FFI::Library - ffi_lib TestLibrary::PATH - callback :cb_return_type_1, [ :short ], :short - callback :cb_lookup_1, [ :short ], :cb_return_type_1 - attach_function :testReturnsCallback_1, :testReturnsClosure, [ :cb_lookup_1, :short ], :cb_return_type_1 - end).to be_an_instance_of FFI::Function + describe 'when inlined' do + it 'could be anonymous' do + module LibTest + extend FFI::Library + ffi_lib TestLibrary::PATH + attach_function :testAnonymousCallbackVrS8, :testClosureVrB, [ callback([ ], :char) ], :char + end + expect(LibTest.testAnonymousCallbackVrS8 { 0 }).to eq(0) + end end - it "should return a callback" do - module LibTest - extend FFI::Library - ffi_lib TestLibrary::PATH - callback :cb_return_type, [ :int ], :int - callback :cb_lookup, [ ], :cb_return_type - attach_function :testReturnsCallback, :testReturnsClosure, [ :cb_lookup, :int ], :int - end - - lookup_proc_called = false - return_proc_called = false + describe "as return value" do - return_proc = Proc.new do |a| - return_proc_called = true - a * 2 - end - lookup_proc = Proc.new do - lookup_proc_called = true - return_proc + it "should not blow up when a callback is defined that returns a callback" do + expect(module LibTest + extend FFI::Library + ffi_lib TestLibrary::PATH + callback :cb_return_type_1, [ :short ], :short + callback :cb_lookup_1, [ :short ], :cb_return_type_1 + attach_function :testReturnsCallback_1, :testReturnsClosure, [ :cb_lookup_1, :short ], :cb_return_type_1 + end).to be_an_instance_of FFI::Function end - val = LibTest.testReturnsCallback(lookup_proc, 0x1234) - expect(val).to eq(0x1234 * 2) - expect(lookup_proc_called).to be true - expect(return_proc_called).to be true - end + it "should return a callback" do + module LibTest + extend FFI::Library + ffi_lib TestLibrary::PATH + callback :cb_return_type, [ :int ], :int + callback :cb_lookup, [ ], :cb_return_type + attach_function :testReturnsCallback, :testReturnsClosure, [ :cb_lookup, :int ], :int + end - it "should return a method callback" do - module LibTest - extend FFI::Library - ffi_lib TestLibrary::PATH - callback :cb_return_type, [ :int ], :int - callback :cb_lookup, [ ], :cb_return_type - attach_function :testReturnsCallback_2, :testReturnsClosure, [ :cb_lookup, :int ], :int - end - module MethodCallback - def self.lookup - method(:perform) + lookup_proc_called = false + return_proc_called = false + + return_proc = Proc.new do |a| + return_proc_called = true + a * 2 end - def self.perform num - num * 2 + lookup_proc = Proc.new do + lookup_proc_called = true + return_proc end + + val = LibTest.testReturnsCallback(lookup_proc, 0x1234) + expect(val).to eq(0x1234 * 2) + expect(lookup_proc_called).to be true + expect(return_proc_called).to be true end - expect(LibTest.testReturnsCallback_2(MethodCallback.method(:lookup), 0x1234)).to eq(0x2468) - end + it "should return a method callback" do + module LibTest + extend FFI::Library + ffi_lib TestLibrary::PATH + callback :cb_return_type, [ :int ], :int + callback :cb_lookup, [ ], :cb_return_type + attach_function :testReturnsCallback_2, :testReturnsClosure, [ :cb_lookup, :int ], :int + end + module MethodCallback + def self.lookup + method(:perform) + end + def self.perform num + num * 2 + end + end - it 'should not blow up when a callback takes a callback as argument' do - expect(module LibTest - extend FFI::Library - ffi_lib TestLibrary::PATH - callback :cb_argument, [ :int ], :int - callback :cb_with_cb_argument, [ :cb_argument, :int ], :int - attach_function :testCallbackAsArgument_2, :testArgumentClosure, [ :cb_with_cb_argument, :int ], :int - end).to be_an_instance_of FFI::Function - end - it 'should be able to use the callback argument' do - module LibTest - extend FFI::Library - ffi_lib TestLibrary::PATH - callback :cb_argument, [ :int ], :int - callback :cb_with_cb_argument, [ :cb_argument, :int ], :int - attach_function :testCallbackAsArgument, :testArgumentClosure, [ :cb_with_cb_argument, :cb_argument, :int ], :int - end - callback_arg_called = false - callback_with_callback_arg_called = false - callback_arg = Proc.new do |val| - callback_arg_called = true - val * 2 + expect(LibTest.testReturnsCallback_2(MethodCallback.method(:lookup), 0x1234)).to eq(0x2468) end - callback_with_callback_arg = Proc.new do |cb, val| - callback_with_callback_arg_called = true - cb.call(val) + + it 'should not blow up when a callback takes a callback as argument' do + expect(module LibTest + extend FFI::Library + ffi_lib TestLibrary::PATH + callback :cb_argument, [ :int ], :int + callback :cb_with_cb_argument, [ :cb_argument, :int ], :int + attach_function :testCallbackAsArgument_2, :testArgumentClosure, [ :cb_with_cb_argument, :int ], :int + end).to be_an_instance_of FFI::Function end - val = LibTest.testCallbackAsArgument(callback_with_callback_arg, callback_arg, 0xff1) - expect(val).to eq(0xff1 * 2) - expect(callback_arg_called).to be true - expect(callback_with_callback_arg_called).to be true - end - it 'function returns callable object' do - module LibTest - extend FFI::Library - ffi_lib TestLibrary::PATH - callback :funcptr, [ :int ], :int - attach_function :testReturnsFunctionPointer, [ ], :funcptr + it 'should be able to use the callback argument' do + module LibTest + extend FFI::Library + ffi_lib TestLibrary::PATH + callback :cb_argument, [ :int ], :int + callback :cb_with_cb_argument, [ :cb_argument, :int ], :int + attach_function :testCallbackAsArgument, :testArgumentClosure, [ :cb_with_cb_argument, :cb_argument, :int ], :int + end + callback_arg_called = false + callback_with_callback_arg_called = false + callback_arg = Proc.new do |val| + callback_arg_called = true + val * 2 + end + callback_with_callback_arg = Proc.new do |cb, val| + callback_with_callback_arg_called = true + cb.call(val) + end + val = LibTest.testCallbackAsArgument(callback_with_callback_arg, callback_arg, 0xff1) + expect(val).to eq(0xff1 * 2) + expect(callback_arg_called).to be true + expect(callback_with_callback_arg_called).to be true + end + it 'function returns callable object' do + module LibTest + extend FFI::Library + ffi_lib TestLibrary::PATH + callback :funcptr, [ :int ], :int + attach_function :testReturnsFunctionPointer, [ ], :funcptr + end + f = LibTest.testReturnsFunctionPointer + expect(f.call(3)).to eq(6) end - f = LibTest.testReturnsFunctionPointer - expect(f.call(3)).to eq(6) end end - end +module CallbackWithSpecs + describe "Callback with " do + # + # Test callbacks that take an argument, returning void + # + module LibTest + extend FFI::Library + ffi_lib TestLibrary::PATH + class S8F32S32 < FFI::Struct + layout :s8, :char, :f32, :float, :s32, :int + end -describe "Callback with " do - # - # Test callbacks that take an argument, returning void - # - module LibTest - extend FFI::Library - ffi_lib TestLibrary::PATH - - class S8F32S32 < FFI::Struct - layout :s8, :char, :f32, :float, :s32, :int - end - - callback :cbS8rV, [ :char ], :void - callback :cbU8rV, [ :uchar ], :void - callback :cbS16rV, [ :short ], :void - callback :cbU16rV, [ :ushort ], :void - - callback :cbZrV, [ :bool ], :void - callback :cbS32rV, [ :int ], :void - callback :cbU32rV, [ :uint ], :void - - callback :cbLrV, [ :long ], :void - callback :cbULrV, [ :ulong ], :void - callback :cbArV, [ :string ], :void - callback :cbPrV, [ :pointer], :void - callback :cbYrV, [ S8F32S32.ptr ], :void - - callback :cbS64rV, [ :long_long ], :void - attach_function :testCallbackCrV, :testClosureBrV, [ :cbS8rV, :char ], :void - attach_function :testCallbackU8rV, :testClosureBrV, [ :cbU8rV, :uchar ], :void - attach_function :testCallbackSrV, :testClosureSrV, [ :cbS16rV, :short ], :void - attach_function :testCallbackU16rV, :testClosureSrV, [ :cbU16rV, :ushort ], :void - attach_function :testCallbackZrV, :testClosureZrV, [ :cbZrV, :bool ], :void - attach_function :testCallbackIrV, :testClosureIrV, [ :cbS32rV, :int ], :void - attach_function :testCallbackU32rV, :testClosureIrV, [ :cbU32rV, :uint ], :void - - attach_function :testCallbackLrV, :testClosureLrV, [ :cbLrV, :long ], :void - attach_function :testCallbackULrV, :testClosureULrV, [ :cbULrV, :ulong ], :void - - attach_function :testCallbackLLrV, :testClosureLLrV, [ :cbS64rV, :long_long ], :void - attach_function :testCallbackArV, :testClosurePrV, [ :cbArV, :string ], :void - attach_function :testCallbackPrV, :testClosurePrV, [ :cbPrV, :pointer], :void - attach_function :testCallbackYrV, :testClosurePrV, [ :cbYrV, S8F32S32.in ], :void - end + callback :cbS8rV, [ :char ], :void + callback :cbU8rV, [ :uchar ], :void + callback :cbS16rV, [ :short ], :void + callback :cbU16rV, [ :ushort ], :void + + callback :cbZrV, [ :bool ], :void + callback :cbS32rV, [ :int ], :void + callback :cbU32rV, [ :uint ], :void + + callback :cbLrV, [ :long ], :void + callback :cbULrV, [ :ulong ], :void + callback :cbArV, [ :string ], :void + callback :cbPrV, [ :pointer], :void + callback :cbYrV, [ S8F32S32.ptr ], :void + + callback :cbS64rV, [ :long_long ], :void + attach_function :testCallbackCrV, :testClosureBrV, [ :cbS8rV, :char ], :void + attach_function :testCallbackU8rV, :testClosureBrV, [ :cbU8rV, :uchar ], :void + attach_function :testCallbackSrV, :testClosureSrV, [ :cbS16rV, :short ], :void + attach_function :testCallbackU16rV, :testClosureSrV, [ :cbU16rV, :ushort ], :void + attach_function :testCallbackZrV, :testClosureZrV, [ :cbZrV, :bool ], :void + attach_function :testCallbackIrV, :testClosureIrV, [ :cbS32rV, :int ], :void + attach_function :testCallbackU32rV, :testClosureIrV, [ :cbU32rV, :uint ], :void + + attach_function :testCallbackLrV, :testClosureLrV, [ :cbLrV, :long ], :void + attach_function :testCallbackULrV, :testClosureULrV, [ :cbULrV, :ulong ], :void + + attach_function :testCallbackLLrV, :testClosureLLrV, [ :cbS64rV, :long_long ], :void + attach_function :testCallbackArV, :testClosurePrV, [ :cbArV, :string ], :void + attach_function :testCallbackPrV, :testClosurePrV, [ :cbPrV, :pointer], :void + attach_function :testCallbackYrV, :testClosurePrV, [ :cbYrV, S8F32S32.in ], :void + end - it "function with Callback plus another arg should raise error if no arg given" do - expect { LibTest.testCallbackCrV { |*a| }}.to raise_error(ArgumentError) - end + it "function with Callback plus another arg should raise error if no arg given" do + expect { LibTest.testCallbackCrV { |*a| }}.to raise_error(ArgumentError) + end - it ":char (0) argument" do - v = 0xdeadbeef - LibTest.testCallbackCrV(0) { |i| v = i } - expect(v).to eq(0) - end + it ":char (0) argument" do + v = 0xdeadbeef + LibTest.testCallbackCrV(0) { |i| v = i } + expect(v).to eq(0) + end - it ":char (127) argument" do - v = 0xdeadbeef - LibTest.testCallbackCrV(127) { |i| v = i } - expect(v).to eq(127) - end + it ":char (127) argument" do + v = 0xdeadbeef + LibTest.testCallbackCrV(127) { |i| v = i } + expect(v).to eq(127) + end - it ":char (-128) argument" do - v = 0xdeadbeef - LibTest.testCallbackCrV(-128) { |i| v = i } - expect(v).to eq(-128) - end + it ":char (-128) argument" do + v = 0xdeadbeef + LibTest.testCallbackCrV(-128) { |i| v = i } + expect(v).to eq(-128) + end - it ":char (-1) argument" do - v = 0xdeadbeef - LibTest.testCallbackCrV(-1) { |i| v = i } - expect(v).to eq(-1) - end + it ":char (-1) argument" do + v = 0xdeadbeef + LibTest.testCallbackCrV(-1) { |i| v = i } + expect(v).to eq(-1) + end - def testCallbackU8rV(value) - v1 = 0xdeadbeef - LibTest.testCallbackU8rV(value) { |i| v1 = i } - expect(v1).to eq(value) + def testCallbackU8rV(value) + v1 = 0xdeadbeef + LibTest.testCallbackU8rV(value) { |i| v1 = i } + expect(v1).to eq(value) - # Using a FFI::Function (v2) should be consistent with the direct callback (v1) - v2 = 0xdeadbeef - fun = FFI::Function.new(:void, [:uchar]) { |i| v2 = i } - LibTest.testCallbackU8rV(fun, value) - expect(v2).to eq(value) - end + # Using a FFI::Function (v2) should be consistent with the direct callback (v1) + v2 = 0xdeadbeef + fun = FFI::Function.new(:void, [:uchar]) { |i| v2 = i } + LibTest.testCallbackU8rV(fun, value) + expect(v2).to eq(value) + end - it ":uchar (0) argument" do - testCallbackU8rV(0) - end + it ":uchar (0) argument" do + testCallbackU8rV(0) + end - it ":uchar (127) argument" do - testCallbackU8rV(127) - end + it ":uchar (127) argument" do + testCallbackU8rV(127) + end - it ":uchar (128) argument" do - testCallbackU8rV(128) - end + it ":uchar (128) argument" do + testCallbackU8rV(128) + end - it ":uchar (255) argument" do - testCallbackU8rV(255) - end + it ":uchar (255) argument" do + testCallbackU8rV(255) + end - it ":short (0) argument" do - v = 0xdeadbeef - LibTest.testCallbackSrV(0) { |i| v = i } - expect(v).to eq(0) - end + it ":short (0) argument" do + v = 0xdeadbeef + LibTest.testCallbackSrV(0) { |i| v = i } + expect(v).to eq(0) + end - it ":short (0x7fff) argument" do - v = 0xdeadbeef - LibTest.testCallbackSrV(0x7fff) { |i| v = i } - expect(v).to eq(0x7fff) - end + it ":short (0x7fff) argument" do + v = 0xdeadbeef + LibTest.testCallbackSrV(0x7fff) { |i| v = i } + expect(v).to eq(0x7fff) + end - it ":short (-0x8000) argument" do - v = 0xdeadbeef - LibTest.testCallbackSrV(-0x8000) { |i| v = i } - expect(v).to eq(-0x8000) - end + it ":short (-0x8000) argument" do + v = 0xdeadbeef + LibTest.testCallbackSrV(-0x8000) { |i| v = i } + expect(v).to eq(-0x8000) + end - it ":short (-1) argument" do - v = 0xdeadbeef - LibTest.testCallbackSrV(-1) { |i| v = i } - expect(v).to eq(-1) - end + it ":short (-1) argument" do + v = 0xdeadbeef + LibTest.testCallbackSrV(-1) { |i| v = i } + expect(v).to eq(-1) + end - it ":ushort (0) argument" do - v = 0xdeadbeef - LibTest.testCallbackU16rV(0) { |i| v = i } - expect(v).to eq(0) - end + it ":ushort (0) argument" do + v = 0xdeadbeef + LibTest.testCallbackU16rV(0) { |i| v = i } + expect(v).to eq(0) + end - it ":ushort (0x7fff) argument" do - v = 0xdeadbeef - LibTest.testCallbackU16rV(0x7fff) { |i| v = i } - expect(v).to eq(0x7fff) - end + it ":ushort (0x7fff) argument" do + v = 0xdeadbeef + LibTest.testCallbackU16rV(0x7fff) { |i| v = i } + expect(v).to eq(0x7fff) + end - it ":ushort (0x8000) argument" do - v = 0xdeadbeef - LibTest.testCallbackU16rV(0x8000) { |i| v = i } - expect(v).to eq(0x8000) - end + it ":ushort (0x8000) argument" do + v = 0xdeadbeef + LibTest.testCallbackU16rV(0x8000) { |i| v = i } + expect(v).to eq(0x8000) + end - it ":ushort (0xffff) argument" do - v = 0xdeadbeef - LibTest.testCallbackU16rV(0xffff) { |i| v = i } - expect(v).to eq(0xffff) - end + it ":ushort (0xffff) argument" do + v = 0xdeadbeef + LibTest.testCallbackU16rV(0xffff) { |i| v = i } + expect(v).to eq(0xffff) + end - it ":bool (true) argument" do - v = false - LibTest.testCallbackZrV(true) { |i| v = i } - expect(v).to be true - end + it ":bool (true) argument" do + v = false + LibTest.testCallbackZrV(true) { |i| v = i } + expect(v).to be true + end - it ":int (0) argument" do - v = 0xdeadbeef - LibTest.testCallbackIrV(0) { |i| v = i } - expect(v).to eq(0) - end + it ":int (0) argument" do + v = 0xdeadbeef + LibTest.testCallbackIrV(0) { |i| v = i } + expect(v).to eq(0) + end - it ":int (0x7fffffff) argument" do - v = 0xdeadbeef - LibTest.testCallbackIrV(0x7fffffff) { |i| v = i } - expect(v).to eq(0x7fffffff) - end + it ":int (0x7fffffff) argument" do + v = 0xdeadbeef + LibTest.testCallbackIrV(0x7fffffff) { |i| v = i } + expect(v).to eq(0x7fffffff) + end - it ":int (-0x80000000) argument" do - v = 0xdeadbeef - LibTest.testCallbackIrV(-0x80000000) { |i| v = i } - expect(v).to eq(-0x80000000) - end + it ":int (-0x80000000) argument" do + v = 0xdeadbeef + LibTest.testCallbackIrV(-0x80000000) { |i| v = i } + expect(v).to eq(-0x80000000) + end - it ":int (-1) argument" do - v = 0xdeadbeef - LibTest.testCallbackIrV(-1) { |i| v = i } - expect(v).to eq(-1) - end + it ":int (-1) argument" do + v = 0xdeadbeef + LibTest.testCallbackIrV(-1) { |i| v = i } + expect(v).to eq(-1) + end - it ":uint (0) argument" do - v = 0xdeadbeef - LibTest.testCallbackU32rV(0) { |i| v = i } - expect(v).to eq(0) - end + it ":uint (0) argument" do + v = 0xdeadbeef + LibTest.testCallbackU32rV(0) { |i| v = i } + expect(v).to eq(0) + end - it ":uint (0x7fffffff) argument" do - v = 0xdeadbeef - LibTest.testCallbackU32rV(0x7fffffff) { |i| v = i } - expect(v).to eq(0x7fffffff) - end + it ":uint (0x7fffffff) argument" do + v = 0xdeadbeef + LibTest.testCallbackU32rV(0x7fffffff) { |i| v = i } + expect(v).to eq(0x7fffffff) + end - it ":uint (0x80000000) argument" do - v = 0xdeadbeef - LibTest.testCallbackU32rV(0x80000000) { |i| v = i } - expect(v).to eq(0x80000000) - end + it ":uint (0x80000000) argument" do + v = 0xdeadbeef + LibTest.testCallbackU32rV(0x80000000) { |i| v = i } + expect(v).to eq(0x80000000) + end - it ":uint (0xffffffff) argument" do - v = 0xdeadbeef - LibTest.testCallbackU32rV(0xffffffff) { |i| v = i } - expect(v).to eq(0xffffffff) - end + it ":uint (0xffffffff) argument" do + v = 0xdeadbeef + LibTest.testCallbackU32rV(0xffffffff) { |i| v = i } + expect(v).to eq(0xffffffff) + end - it ":long (0) argument" do - v = 0xdeadbeef - LibTest.testCallbackLrV(0) { |i| v = i } - expect(v).to eq(0) - end + it ":long (0) argument" do + v = 0xdeadbeef + LibTest.testCallbackLrV(0) { |i| v = i } + expect(v).to eq(0) + end - it ":long (0x7fffffff) argument" do - v = 0xdeadbeef - LibTest.testCallbackLrV(0x7fffffff) { |i| v = i } - expect(v).to eq(0x7fffffff) - end + it ":long (0x7fffffff) argument" do + v = 0xdeadbeef + LibTest.testCallbackLrV(0x7fffffff) { |i| v = i } + expect(v).to eq(0x7fffffff) + end - it ":long (-0x80000000) argument" do - v = 0xdeadbeef - LibTest.testCallbackLrV(-0x80000000) { |i| v = i } - expect(v).to eq(-0x80000000) - end + it ":long (-0x80000000) argument" do + v = 0xdeadbeef + LibTest.testCallbackLrV(-0x80000000) { |i| v = i } + expect(v).to eq(-0x80000000) + end - it ":long (-1) argument" do - v = 0xdeadbeef - LibTest.testCallbackLrV(-1) { |i| v = i } - expect(v).to eq(-1) - end + it ":long (-1) argument" do + v = 0xdeadbeef + LibTest.testCallbackLrV(-1) { |i| v = i } + expect(v).to eq(-1) + end - it ":ulong (0) argument" do - v = 0xdeadbeef - LibTest.testCallbackULrV(0) { |i| v = i } - expect(v).to eq(0) - end + it ":ulong (0) argument" do + v = 0xdeadbeef + LibTest.testCallbackULrV(0) { |i| v = i } + expect(v).to eq(0) + end - it ":ulong (0x7fffffff) argument" do - v = 0xdeadbeef - LibTest.testCallbackULrV(0x7fffffff) { |i| v = i } - expect(v).to eq(0x7fffffff) - end + it ":ulong (0x7fffffff) argument" do + v = 0xdeadbeef + LibTest.testCallbackULrV(0x7fffffff) { |i| v = i } + expect(v).to eq(0x7fffffff) + end - it ":ulong (0x80000000) argument" do - v = 0xdeadbeef - LibTest.testCallbackULrV(0x80000000) { |i| v = i } - expect(v).to eq(0x80000000) - end + it ":ulong (0x80000000) argument" do + v = 0xdeadbeef + LibTest.testCallbackULrV(0x80000000) { |i| v = i } + expect(v).to eq(0x80000000) + end - it ":ulong (0xffffffff) argument" do - v = 0xdeadbeef - LibTest.testCallbackULrV(0xffffffff) { |i| v = i } - expect(v).to eq(0xffffffff) - end + it ":ulong (0xffffffff) argument" do + v = 0xdeadbeef + LibTest.testCallbackULrV(0xffffffff) { |i| v = i } + expect(v).to eq(0xffffffff) + end - it ":long_long (0) argument" do - v = 0xdeadbeef - LibTest.testCallbackLLrV(0) { |i| v = i } - expect(v).to eq(0) - end + it ":long_long (0) argument" do + v = 0xdeadbeef + LibTest.testCallbackLLrV(0) { |i| v = i } + expect(v).to eq(0) + end - it ":long_long (0x7fffffffffffffff) argument" do - v = 0xdeadbeef - LibTest.testCallbackLLrV(0x7fffffffffffffff) { |i| v = i } - expect(v).to eq(0x7fffffffffffffff) - end + it ":long_long (0x7fffffffffffffff) argument" do + v = 0xdeadbeef + LibTest.testCallbackLLrV(0x7fffffffffffffff) { |i| v = i } + expect(v).to eq(0x7fffffffffffffff) + end - it ":long_long (-0x8000000000000000) argument" do - v = 0xdeadbeef - LibTest.testCallbackLLrV(-0x8000000000000000) { |i| v = i } - expect(v).to eq(-0x8000000000000000) - end + it ":long_long (-0x8000000000000000) argument" do + v = 0xdeadbeef + LibTest.testCallbackLLrV(-0x8000000000000000) { |i| v = i } + expect(v).to eq(-0x8000000000000000) + end - it ":long_long (-1) argument" do - v = 0xdeadbeef - LibTest.testCallbackLLrV(-1) { |i| v = i } - expect(v).to eq(-1) - end + it ":long_long (-1) argument" do + v = 0xdeadbeef + LibTest.testCallbackLLrV(-1) { |i| v = i } + expect(v).to eq(-1) + end - it ":string argument" do - v = nil - LibTest.testCallbackArV("Hello, World") { |i| v = i } - expect(v).to eq("Hello, World") - end + it ":string argument" do + v = nil + LibTest.testCallbackArV("Hello, World") { |i| v = i } + expect(v).to eq("Hello, World") + end - it ":string (nil) argument" do - v = "Hello, World" - LibTest.testCallbackArV(nil) { |i| v = i } - expect(v).to be_nil - end + it ":string (nil) argument" do + v = "Hello, World" + LibTest.testCallbackArV(nil) { |i| v = i } + expect(v).to be_nil + end - it ":pointer argument" do - v = nil - magic = FFI::Pointer.new(0xdeadbeef) - LibTest.testCallbackPrV(magic) { |i| v = i } - expect(v).to eq(magic) - end + it ":pointer argument" do + v = nil + magic = FFI::Pointer.new(0xdeadbeef) + LibTest.testCallbackPrV(magic) { |i| v = i } + expect(v).to eq(magic) + end - it ":pointer (nil) argument" do - v = "Hello, World" - LibTest.testCallbackPrV(nil) { |i| v = i } - expect(v).to eq(FFI::Pointer::NULL) - end + it ":pointer (nil) argument" do + v = "Hello, World" + LibTest.testCallbackPrV(nil) { |i| v = i } + expect(v).to eq(FFI::Pointer::NULL) + end - it "struct by reference argument" do - v = nil - magic = LibTest::S8F32S32.new - LibTest.testCallbackYrV(magic) { |i| v = i } - expect(v.class).to eq(magic.class) - expect(v.pointer).to eq(magic.pointer) - end + it "struct by reference argument" do + v = nil + magic = LibTest::S8F32S32.new + LibTest.testCallbackYrV(magic) { |i| v = i } + expect(v.class).to eq(magic.class) + expect(v.pointer).to eq(magic.pointer) + end - it "struct by reference argument with nil value" do - v = LibTest::S8F32S32.new - LibTest.testCallbackYrV(nil) { |i| v = i } - expect(v.is_a?(FFI::Struct)).to be true - expect(v.pointer).to eq(FFI::Pointer::NULL) - end + it "struct by reference argument with nil value" do + v = LibTest::S8F32S32.new + LibTest.testCallbackYrV(nil) { |i| v = i } + expect(v.is_a?(FFI::Struct)).to be true + expect(v.pointer).to eq(FFI::Pointer::NULL) + end + + it "varargs parameters are rejected" do + expect { + Module.new do + extend FFI::Library + ffi_lib TestLibrary::PATH + callback :cbVrL, [ :varargs ], :long + end + }.to raise_error(ArgumentError) + end - it "varargs parameters are rejected" do - expect { - Module.new do + # + # Test stdcall convention with function and callback. + # This is Windows 32-bit only. + # + if FFI::Platform::OS =~ /windows|cygwin/ && FFI::Platform::ARCH == 'i386' + module LibTestStdcall extend FFI::Library ffi_lib TestLibrary::PATH - callback :cbVrL, [ :varargs ], :long - end - }.to raise_error(ArgumentError) - end + ffi_convention :stdcall - # - # Test stdcall convention with function and callback. - # This is Windows 32-bit only. - # - if FFI::Platform::OS =~ /windows|cygwin/ && FFI::Platform::ARCH == 'i386' - module LibTestStdcall - extend FFI::Library - ffi_lib TestLibrary::PATH - ffi_convention :stdcall - - callback :cbStdcall, [ :pointer, :long ], :void - attach_function :testCallbackStdcall, 'testClosureStdcall', [ :pointer, :cbStdcall, :long ], :bool - end + callback :cbStdcall, [ :pointer, :long ], :void + attach_function :testCallbackStdcall, 'testClosureStdcall', [ :pointer, :cbStdcall, :long ], :bool + end - it "stdcall convention" do - v = 0xdeadbeef - po = FFI::MemoryPointer.new :long - pr = proc{|a,i| v = a,i; i } - res = LibTestStdcall.testCallbackStdcall(po, pr, 0x7fffffff) - expect(v).to eq([po, 0x7fffffff]) - expect(res).to be true + it "stdcall convention" do + v = 0xdeadbeef + po = FFI::MemoryPointer.new :long + pr = proc{|a,i| v = a,i; i } + res = LibTestStdcall.testCallbackStdcall(po, pr, 0x7fffffff) + expect(v).to eq([po, 0x7fffffff]) + expect(res).to be true + end end end end -describe "Callback interop" do - require 'fiddle' - require 'fiddle/import' - require 'timeout' +module CallbackInteropSpecs + describe "Callback interop" do + require 'fiddle' + require 'fiddle/import' + require 'timeout' - module LibTestFFI - extend FFI::Library - ffi_lib TestLibrary::PATH - attach_function :testCallbackVrV, :testClosureVrV, [ :pointer ], :void - attach_function :testCallbackVrV_blocking, :testClosureVrV, [ :pointer ], :void, blocking: true - end - - module LibTestFiddle - extend Fiddle::Importer - dlload TestLibrary::PATH - extern 'void testClosureVrV(void *fp)' - end + module LibTestFFI + extend FFI::Library + ffi_lib TestLibrary::PATH + attach_function :testCallbackVrV, :testClosureVrV, [ :pointer ], :void + attach_function :testCallbackVrV_blocking, :testClosureVrV, [ :pointer ], :void, blocking: true + end - def assert_callback_in_same_thread_called_once - called = 0 - thread = nil - yield proc { - called += 1 - thread = Thread.current - } - expect(called).to eq(1) - expect(thread).to eq(Thread.current) - end + module LibTestFiddle + extend Fiddle::Importer + dlload TestLibrary::PATH + extern 'void testClosureVrV(void *fp)' + end - it "from ffi to ffi" do - assert_callback_in_same_thread_called_once do |block| - func = FFI::Function.new(:void, [:pointer], &block) - LibTestFFI.testCallbackVrV(FFI::Pointer.new(func.to_i)) + def assert_callback_in_same_thread_called_once + called = 0 + thread = nil + yield proc { + called += 1 + thread = Thread.current + } + expect(called).to eq(1) + expect(thread).to eq(Thread.current) end - end - it "from ffi to ffi with blocking:true" do - assert_callback_in_same_thread_called_once do |block| - func = FFI::Function.new(:void, [:pointer], &block) - LibTestFFI.testCallbackVrV_blocking(FFI::Pointer.new(func.to_i)) + it "from ffi to ffi" do + assert_callback_in_same_thread_called_once do |block| + func = FFI::Function.new(:void, [:pointer], &block) + LibTestFFI.testCallbackVrV(FFI::Pointer.new(func.to_i)) + end end - end - # https://github.com/ffi/ffi/issues/527 - if RUBY_VERSION.split('.').map(&:to_i).pack("C*") >= [2,3,0].pack("C*") || RUBY_PLATFORM =~ /java/ - it "from fiddle to ffi" do + it "from ffi to ffi with blocking:true" do assert_callback_in_same_thread_called_once do |block| func = FFI::Function.new(:void, [:pointer], &block) - LibTestFiddle.testClosureVrV(Fiddle::Pointer[func.to_i]) + LibTestFFI.testCallbackVrV_blocking(FFI::Pointer.new(func.to_i)) end end - end - it "from ffi to fiddle" do - assert_callback_in_same_thread_called_once do |block| - func = LibTestFiddle.bind_function(:cbVrV, Fiddle::TYPE_VOID, [], &block) - LibTestFFI.testCallbackVrV(FFI::Pointer.new(func.to_i)) + # https://github.com/ffi/ffi/issues/527 + if RUBY_VERSION.split('.').map(&:to_i).pack("C*") >= [2,3,0].pack("C*") || RUBY_PLATFORM =~ /java/ + it "from fiddle to ffi" do + assert_callback_in_same_thread_called_once do |block| + func = FFI::Function.new(:void, [:pointer], &block) + LibTestFiddle.testClosureVrV(Fiddle::Pointer[func.to_i]) + end + end end - end - it "from ffi to fiddle with blocking:true" do - assert_callback_in_same_thread_called_once do |block| - func = LibTestFiddle.bind_function(:cbVrV, Fiddle::TYPE_VOID, [], &block) - LibTestFFI.testCallbackVrV_blocking(FFI::Pointer.new(func.to_i)) + it "from ffi to fiddle" do + assert_callback_in_same_thread_called_once do |block| + func = LibTestFiddle.bind_function(:cbVrV, Fiddle::TYPE_VOID, [], &block) + LibTestFFI.testCallbackVrV(FFI::Pointer.new(func.to_i)) + end end - end - it "from fiddle to fiddle" do - assert_callback_in_same_thread_called_once do |block| - func = LibTestFiddle.bind_function(:cbVrV, Fiddle::TYPE_VOID, [], &block) - LibTestFiddle.testClosureVrV(Fiddle::Pointer[func.to_i]) + it "from ffi to fiddle with blocking:true" do + assert_callback_in_same_thread_called_once do |block| + func = LibTestFiddle.bind_function(:cbVrV, Fiddle::TYPE_VOID, [], &block) + LibTestFFI.testCallbackVrV_blocking(FFI::Pointer.new(func.to_i)) + end end - end - # https://github.com/ffi/ffi/issues/527 - if RUBY_ENGINE == 'ruby' && RUBY_VERSION.split('.').map(&:to_i).pack("C*") >= [2,3,0].pack("C*") - it "C outside ffi call stack does not deadlock [#527]" do - path = File.join(File.dirname(__FILE__), "embed-test/embed-test.rb") - pid = spawn(RbConfig.ruby, "-Ilib", path, { [:out, :err] => "embed-test.log" }) - begin - Timeout.timeout(10){ Process.wait(pid) } - rescue Timeout::Error - Process.kill(9, pid) - raise - else - if $?.exitstatus != 0 - raise "external process failed:\n#{ File.read("embed-test.log") }" - end + it "from fiddle to fiddle" do + assert_callback_in_same_thread_called_once do |block| + func = LibTestFiddle.bind_function(:cbVrV, Fiddle::TYPE_VOID, [], &block) + LibTestFiddle.testClosureVrV(Fiddle::Pointer[func.to_i]) end + end + + # https://github.com/ffi/ffi/issues/527 + if RUBY_ENGINE == 'ruby' && RUBY_VERSION.split('.').map(&:to_i).pack("C*") >= [2,3,0].pack("C*") + it "C outside ffi call stack does not deadlock [#527]" do + path = File.join(File.dirname(__FILE__), "embed-test/embed-test.rb") + pid = spawn(RbConfig.ruby, "-Ilib", path, { [:out, :err] => "embed-test.log" }) + begin + Timeout.timeout(10){ Process.wait(pid) } + rescue Timeout::Error + Process.kill(9, pid) + raise + else + if $?.exitstatus != 0 + raise "external process failed:\n#{ File.read("embed-test.log") }" + end + end - expect(File.read("embed-test.log")).to match(/callback called with \["hello", 5, 0\]/) + expect(File.read("embed-test.log")).to match(/callback called with \["hello", 5, 0\]/) + end end end end diff --git a/spec/ffi/library_spec.rb b/spec/ffi/library_spec.rb index 0863904b5..ff18f9b71 100644 --- a/spec/ffi/library_spec.rb +++ b/spec/ffi/library_spec.rb @@ -73,7 +73,7 @@ class StructUCDP < FFI::Struct end }.to raise_error(LoadError) end - + end unless RbConfig::CONFIG['target_os'] =~ /mswin|mingw/ @@ -259,12 +259,12 @@ def gvar_test(name, type, val) expect(lib.get).to eq(val) end + class GlobalStruct < FFI::Struct + layout :data, :long + end + [ 0, 0x7fffffff, -0x80000000, -1 ].each do |i| it "structure" do - class GlobalStruct < FFI::Struct - layout :data, :long - end - lib = Module.new do |m| m.extend FFI::Library ffi_lib TestLibrary::PATH diff --git a/spec/ffi/rbx/attach_function_spec.rb b/spec/ffi/rbx/attach_function_spec.rb index 7593662db..eda640fbe 100644 --- a/spec/ffi/rbx/attach_function_spec.rb +++ b/spec/ffi/rbx/attach_function_spec.rb @@ -5,30 +5,32 @@ require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper")) -unless FFI::Platform.windows? - class Timeval < FFI::Struct - layout :tv_sec, :ulong, 0, :tv_usec, :ulong, 4 - end +module RbxAttachFunctionSpecs + unless FFI::Platform.windows? + class Timeval < FFI::Struct + layout :tv_sec, :ulong, 0, :tv_usec, :ulong, 4 + end - module LibC - extend FFI::Library - ffi_lib FFI::Library::LIBC + module LibC + extend FFI::Library + ffi_lib FFI::Library::LIBC - attach_function :gettimeofday, [:pointer, :pointer], :int - end - - describe FFI::Library, "#attach_function" do - it "correctly returns a value for gettimeofday" do - t = Timeval.new - time = LibC.gettimeofday(t.pointer, nil) - expect(time).to be_kind_of(Integer) + attach_function :gettimeofday, [:pointer, :pointer], :int end - it "correctly populates a struct for gettimeofday" do - t = Timeval.new - LibC.gettimeofday(t.pointer, nil) - expect(t[:tv_sec]).to be_kind_of(Numeric) - expect(t[:tv_usec]).to be_kind_of(Numeric) + describe FFI::Library, "#attach_function" do + it "correctly returns a value for gettimeofday" do + t = Timeval.new + time = LibC.gettimeofday(t.pointer, nil) + expect(time).to be_kind_of(Integer) + end + + it "correctly populates a struct for gettimeofday" do + t = Timeval.new + LibC.gettimeofday(t.pointer, nil) + expect(t[:tv_sec]).to be_kind_of(Numeric) + expect(t[:tv_usec]).to be_kind_of(Numeric) + end end end end diff --git a/spec/ffi/rbx/struct_spec.rb b/spec/ffi/rbx/struct_spec.rb index a7ed85b44..c2a51fa48 100644 --- a/spec/ffi/rbx/struct_spec.rb +++ b/spec/ffi/rbx/struct_spec.rb @@ -5,14 +5,16 @@ require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper")) -class Timeval < FFI::Struct - layout :tv_sec, :ulong, 0, :tv_usec, :ulong, 4 -end +module RbxStructSpecs + class Timeval < FFI::Struct + layout :tv_sec, :ulong, 0, :tv_usec, :ulong, 4 + end -describe FFI::Struct do - it "allows setting fields" do - t = Timeval.new - t[:tv_sec] = 12 - expect(t[:tv_sec]).to eq(12) + describe FFI::Struct do + it "allows setting fields" do + t = Timeval.new + t[:tv_sec] = 12 + expect(t[:tv_sec]).to eq(12) + end end end diff --git a/spec/ffi/struct_spec.rb b/spec/ffi/struct_spec.rb index cbcdb0936..b86b0d979 100644 --- a/spec/ffi/struct_spec.rb +++ b/spec/ffi/struct_spec.rb @@ -5,6 +5,7 @@ require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper")) +module StructSpecs describe "Struct aligns fields correctly" do it "char, followed by an int" do class CIStruct < FFI::Struct @@ -35,430 +36,452 @@ class LLIStruct < FFI::Struct 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 +module StructSpecsStructTests + 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 - 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 + + 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 + s = Class.new(FFI::Struct) do + 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 + expect(s.size).to eq((ll_off + 8)) + mp = FFI::MemoryPointer.new(s.size) + s = s.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 + it "Struct#layout works with :name, :type, offset tuples" do + s = Class.new(FFI::Struct) do + layout :a, :int, 0, :b, :long_long, 4 end - + expect(s.size).to eq((FFI::TYPE_UINT64.alignment == 4 ? 12 : 16)) + mp = FFI::MemoryPointer.new(s.size) + s = s.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 - 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 + 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 - attach_function :struct_field_s8, [ TestStruct.in ], :char - end).to be_an_instance_of FFI::Function - 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 - 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 + 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 - attach_function :struct_field_s8, [ TestStruct.in ], :char - end).to be_an_instance_of FFI::Function - 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 - 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 + s = InitializeWithoutSuper.new(0x1eefbeef, 0xdeadcafebabe) + expect(s[:a]).to eq(0x1eefbeef) + expect(s[:b]).to eq(0xdeadcafebabe) + end - it "can be passed directly as a :pointer parameter" do - class TestStruct < FFI::Struct - layout :i, :int + 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 - 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 + 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 - 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 + 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 - 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 + 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 - 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 + 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 - 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 + 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 - 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 + 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 - 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 + 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 - 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 + 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 - 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) + 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 - 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 + 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 - s = klass.new - value = 1.23456 - s[:v] = value - expect((s.pointer.get_float(0) - value).abs).to be < 0.0001 - 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 ":double field r/w" do - klass = Class.new(FFI::Struct) - klass.layout :v, :double, :dummy, :long + it ":enum field r/w" do + s = EnumFields::TestStruct.new + s[:c] = :c3 - 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 ] + expect(s.pointer.get_uint(FFI::Type::INT32.size)).to eq(30) + expect(s[:c]).to eq(:c3) end - end - it ":enum field r/w" do - s = EnumFields::TestStruct.new - s[:c] = :c3 + it "array of :enum field" do + s = EnumFields::TestStruct.new + EnumFields::TestEnum.symbols.each_with_index do |val, i| + s[:d][i] = val + end - expect(s.pointer.get_uint(FFI::Type::INT32.size)).to eq(30) - expect(s[:c]).to eq(:c3) - 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 - it "array of :enum field" do - s = EnumFields::TestStruct.new - EnumFields::TestEnum.symbols.each_with_index do |val, i| - s[:d][i] = val + s[:d].each_with_index do |val, i| + expect(val).to eq(EnumFields::TestEnum.symbols[i]) + end 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]) + 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 - s[:d].each_with_index do |val, i| - expect(val).to eq(EnumFields::TestEnum.symbols[i]) + 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 - 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 + it "Can return its members as a list" do + s = Class.new(FFI::Struct) do + layout :a, :int, :b, :int, :c, :int + end + expect(s.members).to include(:a, :b, :c) 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 instance members and values as lists" do + s = Class.new(FFI::Struct) do + layout :a, :int, :b, :int, :c, :int + end + s = s.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 "Can return its members as a list" do - class TestStruct < FFI::Struct - layout :a, :int, :b, :int, :c, :int + it 'should return an ordered field/offset pairs array' do + s = Class.new(FFI::Struct) do + layout :a, :int, :b, :int, :c, :int + end + s = s.new + expect(s.offsets).to eq([[:a, 0], [:b, 4], [:c, 8]]) + expect(s.offsets).to eq([[:a, 0], [:b, 4], [:c, 8]]) 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 + it "Struct#offset_of returns offset of field within struct" do + s = Class.new(FFI::Struct) do + layout :a, :int, :b, :int, :c, :int + end + expect(s.offset_of(:a)).to eq(0) + expect(s.offset_of(:b)).to eq(4) + expect(s.offset_of(:c)).to eq(8) 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 + it "denies redefinition of struct layouts" do + expect do + Class.new(FFI::Struct) do + layout :a, :int + layout :a, :int + end + end.to raise_error(/struct layout already defined/) 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 + it "allows redefinition of struct layouts in derived classes" do + a = Class.new(FFI::Struct) do + layout :a, :char + end + b = Class.new(a) do + layout :a, :char, :b, :char + end + expect(a.members).to eq([:a]) + expect(b.members).to eq([:a, :b]) 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 @@ -793,40 +816,40 @@ class BuggedStruct < FFI::Struct describe "Struct allocation" do it "MemoryPointer.new(Struct, 2)" do - class S < FFI::Struct + s = Class.new(FFI::Struct) do layout :i, :uint end - p = FFI::MemoryPointer.new(S, 2) + 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(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 + s = Class.new(FFI::Struct) do layout :i, :uint end - p = FFI::Buffer.new(S, 2) + 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) + 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 + s = Class.new(FFI::Struct) do layout :i, :uint end - expect(S.new(FFI::Pointer::NULL)).to be_null + 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 + s = Class.new(FFI::Struct) do layout :i, :uint end - expect(S.new(FFI::MemoryPointer.new(S))).not_to be_null + expect(s.new(FFI::MemoryPointer.new(s))).not_to be_null end it "supports :bool as a struct member" do @@ -880,3 +903,4 @@ class S < FFI::Struct expect { expect(s[:data][1]).to == 0x12345678 }.to raise_error(IndexError) end end +end