1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
|
#
# This file is part of ruby-ffi.
# For licensing, see LICENSE.SPECS
#
require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper"))
describe FFI::Function do
module LibTest
extend FFI::Library
ffi_lib TestLibrary::PATH
attach_function :testFunctionAdd, [:int, :int, :pointer], :int
end
before do
@libtest = FFI::DynamicLibrary.open(TestLibrary::PATH,
FFI::DynamicLibrary::RTLD_LAZY | FFI::DynamicLibrary::RTLD_GLOBAL)
end
it 'is initialized with a signature and a block' do
fn = FFI::Function.new(:int, []) { 5 }
expect(fn.call).to eql 5
end
context 'when called with a block' do
it 'creates a thread for dispatching callbacks and sets its name' do
skip 'this is MRI-specific' if RUBY_ENGINE == 'truffleruby' || RUBY_ENGINE == 'jruby'
FFI::Function.new(:int, []) { 5 } # Trigger initialization
expect(Thread.list.map(&:name)).to include('FFI Callback Dispatcher')
end
end
it 'raises an error when passing a wrong signature' do
expect { FFI::Function.new([], :int).new { } }.to raise_error TypeError
end
it 'returns a native pointer' do
expect(FFI::Function.new(:int, []) { }).to be_a_kind_of FFI::Pointer
end
it 'can be used as callback from C passing to it a block' do
function_add = FFI::Function.new(:int, [:int, :int]) { |a, b| a + b }
expect(LibTest.testFunctionAdd(10, 10, function_add)).to eq(20)
end
it 'can be used as callback from C passing to it a Proc object' do
function_add = FFI::Function.new(:int, [:int, :int], Proc.new { |a, b| a + b })
expect(LibTest.testFunctionAdd(10, 10, function_add)).to eq(20)
end
def adder(a, b)
a + b
end
it "can be made shareable for Ractor", :ractor do
add = FFI::Function.new(:int, [:int, :int], &method(:adder))
Ractor.make_shareable(add)
res = Ractor.new(add) do |add2|
LibTest.testFunctionAdd(10, 10, add2)
end.take
expect( res ).to eq(20)
end
it "should be usable with Ractor", :ractor do
res = Ractor.new do
function_add = FFI::Function.new(:int, [:int, :int]) { |a, b| a + b }
LibTest.testFunctionAdd(10, 10, function_add)
end.take
expect( res ).to eq(20)
end
it 'can be used to wrap an existing function pointer' do
expect(FFI::Function.new(:int, [:int, :int], @libtest.find_function('testAdd')).call(10, 10)).to eq(20)
end
it 'can be attached to a module' do
module Foo; end
fp = FFI::Function.new(:int, [:int, :int], @libtest.find_function('testAdd'))
fp.attach(Foo, 'add')
expect(Foo.add(10, 10)).to eq(20)
end
it 'can be attached to two modules' do
module Foo1; end
module Foo2; end
fp = FFI::Function.new(:int, [:int, :int], @libtest.find_function('testAdd'))
fp.attach(Foo1, 'add')
fp.attach(Foo2, 'add')
expect(Foo1.add(11, 11)).to eq(22)
expect(Foo2.add(12, 12)).to eq(24)
end
it 'can be used to extend an object' do
fp = FFI::Function.new(:int, [:int, :int], @libtest.find_function('testAdd'))
foo = Object.new
class << foo
def singleton_class
class << self; self; end
end
end
fp.attach(foo.singleton_class, 'add')
expect(foo.add(10, 10)).to eq(20)
end
it 'can wrap a blocking function' do
fpOpen = FFI::Function.new(:pointer, [ ], @libtest.find_function('testBlockingOpen'))
fpRW = FFI::Function.new(:char, [ :pointer, :char ], @libtest.find_function('testBlockingRW'), :blocking => true)
fpWR = FFI::Function.new(:char, [ :pointer, :char ], @libtest.find_function('testBlockingWR'), :blocking => true)
fpClose = FFI::Function.new(:void, [ :pointer ], @libtest.find_function('testBlockingClose'))
handle = fpOpen.call
expect(handle).not_to be_null
begin
thWR = Thread.new { fpWR.call(handle, 63) }
thRW = Thread.new { fpRW.call(handle, 64) }
expect(thWR.value).to eq(64)
expect(thRW.value).to eq(63)
ensure
fpClose.call(handle)
end
end
it 'autorelease flag is set to true by default' do
fp = FFI::Function.new(:int, [:int, :int], @libtest.find_function('testAdd'))
expect(fp.autorelease?).to be true
end
it 'can explicity free itself' do
fp = FFI::Function.new(:int, []) { }
fp.free
expect { fp.free }.to raise_error RuntimeError
end
it 'can\'t explicity free itself if not previously allocated' do
fp = FFI::Function.new(:int, [:int, :int], @libtest.find_function('testAdd'))
expect { fp.free }.to raise_error RuntimeError
end
it 'has a memsize function', skip: RUBY_ENGINE != "ruby" do
base_size = ObjectSpace.memsize_of(Object.new)
function = FFI::Function.new(:int, [:int, :int], @libtest.find_function('testAdd'))
size = ObjectSpace.memsize_of(function)
expect(size).to be > base_size
end
end
|