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