# # This file is part of ruby-ffi. # For licensing, see LICENSE.SPECS # 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 :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 it "returning :char (0)" do expect(LibTest.testCallbackVrS8 { 0 }).to eq(0) end it "returning :char (127)" do expect(LibTest.testCallbackVrS8 { 127 }).to eq(127) 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 (255)" do expect(LibTest.testCallbackVrS8 { 0xff }).to eq(-1) end it "returning :uchar (0)" do expect(LibTest.testCallbackVrU8 { 0 }).to eq(0) end it "returning :uchar (0xff)" do expect(LibTest.testCallbackVrU8 { 0xff }).to eq(0xff) end it "returning :uchar (-1)" do expect(LibTest.testCallbackVrU8 { -1 }).to eq(0xff) end it "returning :uchar (128)" do expect(LibTest.testCallbackVrU8 { 128 }).to eq(128) end it "returning :uchar (-128)" do expect(LibTest.testCallbackVrU8 { -128 }).to eq(128) end it "returning :short (0)" do expect(LibTest.testCallbackVrS16 { 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 :short (0xffff)" do expect(LibTest.testCallbackVrS16 { 0xffff }).to eq(-1) end it "returning :ushort (0)" do expect(LibTest.testCallbackVrU16 { 0 }).to eq(0) end it "returning :ushort (0x7fff)" do expect(LibTest.testCallbackVrU16 { 0x7fff }).to eq(0x7fff) end it "returning :ushort (0x8000)" do expect(LibTest.testCallbackVrU16 { 0x8000 }).to eq(0x8000) end it "returning :ushort (0xffff)" do expect(LibTest.testCallbackVrU16 { 0xffff }).to eq(0xffff) end it "returning :ushort (-1)" do expect(LibTest.testCallbackVrU16 { -1 }).to eq(0xffff) end it "returning :int (0)" do expect(LibTest.testCallbackVrS32 { 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 :int (-1)" do expect(LibTest.testCallbackVrS32 { -1 }).to eq(-1) end it "returning :uint (0)" do expect(LibTest.testCallbackVrU32 { 0 }).to eq(0) 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 :uint (0xffffffff)" do expect(LibTest.testCallbackVrU32 { 0xffffffff }).to eq(0xffffffff) end it "returning :uint (-1)" do expect(LibTest.testCallbackVrU32 { -1 }).to eq(0xffffffff) end it "returning :long (0)" do expect(LibTest.testCallbackVrL { 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 :long (-1)" do expect(LibTest.testCallbackVrL { -1 }).to eq(-1) end it "returning :ulong (0)" do expect(LibTest.testCallbackVrUL { 0 }).to eq(0) 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 :ulong (0xffffffff)" do expect(LibTest.testCallbackVrUL { 0xffffffff }).to eq(0xffffffff) 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 (0)" do expect(LibTest.testCallbackVrS64 { 0 }).to eq(0) 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 :long_long (-1)" do expect(LibTest.testCallbackVrS64 { -1 }).to eq(-1) end it "returning bool" do expect(LibTest.testCallbackVrZ { true }).to be true end it "returning :pointer (nil)" do expect(LibTest.testCallbackVrP { nil }).to be_null end it "returning :pointer (MemoryPointer)" do p = FFI::MemoryPointer.new :long expect(LibTest.testCallbackVrP { p }).to eq(p) end it "returning struct by value" do 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]) 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 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 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 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 return_proc = Proc.new do |a| return_proc_called = true a * 2 end 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 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 expect(LibTest.testReturnsCallback_2(MethodCallback.method(:lookup), 0x1234)).to eq(0x2468) 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 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 end 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 it "function with Callback plus another arg should raise error if no arg given" do expect { LibTest.testCallbackCrV { |*a| }}.to raise_error 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 (-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 ":uchar (0) argument" do v = 0xdeadbeef LibTest.testCallbackU8rV(0) { |i| v = i } expect(v).to eq(0) end it ":uchar (127) argument" do v = 0xdeadbeef LibTest.testCallbackU8rV(127) { |i| v = i } expect(v).to eq(127) end it ":uchar (128) argument" do v = 0xdeadbeef LibTest.testCallbackU8rV(128) { |i| v = i } expect(v).to eq(128) end it ":uchar (255) argument" do v = 0xdeadbeef LibTest.testCallbackU8rV(255) { |i| v = i } expect(v).to eq(255) 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 (-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 ":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 (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 ":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 (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 (-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 (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 (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 (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 (-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 (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 (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 (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 (-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 (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 (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 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 # # 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 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