ffi 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of ffi might be problematic. Click here for more details.
- data/Rakefile +52 -29
- data/ext/AbstractMemory.c +72 -28
- data/ext/AutoPointer.c +54 -0
- data/ext/AutoPointer.h +18 -0
- data/ext/Buffer.c +21 -17
- data/ext/Callback.c +81 -43
- data/ext/Callback.h +1 -1
- data/ext/Invoker.c +465 -108
- data/ext/MemoryPointer.c +25 -90
- data/ext/NativeLibrary.c +90 -0
- data/ext/NativeLibrary.h +22 -0
- data/ext/Platform.c +21 -2
- data/ext/Pointer.c +107 -0
- data/ext/Pointer.h +21 -0
- data/ext/Types.c +16 -5
- data/ext/Types.h +3 -1
- data/ext/compat.h +14 -0
- data/ext/extconf.rb +13 -1
- data/ext/ffi.c +11 -1
- data/ext/ffi.mk +3 -3
- data/ext/libffi.darwin.mk +19 -8
- data/gen/Rakefile +12 -0
- data/lib/ffi/autopointer.rb +61 -0
- data/lib/ffi/errno.rb +8 -0
- data/lib/ffi/ffi.rb +38 -201
- data/lib/ffi/io.rb +7 -0
- data/lib/ffi/library.rb +116 -0
- data/lib/ffi/managedstruct.rb +55 -0
- data/lib/ffi/memorypointer.rb +3 -96
- data/lib/ffi/platform.rb +8 -5
- data/lib/ffi/pointer.rb +105 -0
- data/lib/ffi/struct.rb +97 -42
- data/lib/ffi/tools/const_generator.rb +177 -0
- data/lib/ffi/tools/generator.rb +58 -0
- data/lib/ffi/tools/generator_task.rb +35 -0
- data/lib/ffi/tools/struct_generator.rb +194 -0
- data/lib/ffi/tools/types_generator.rb +123 -0
- data/lib/ffi/types.rb +150 -0
- data/lib/ffi/variadic.rb +30 -0
- data/nbproject/Makefile-Default.mk +6 -3
- data/nbproject/Makefile-impl.mk +5 -5
- data/nbproject/Package-Default.bash +72 -0
- data/nbproject/configurations.xml +139 -25
- data/nbproject/private/configurations.xml +1 -1
- data/nbproject/project.xml +4 -0
- data/samples/gettimeofday.rb +6 -2
- data/samples/inotify.rb +59 -0
- data/samples/pty.rb +75 -0
- data/specs/buffer_spec.rb +64 -9
- data/specs/callback_spec.rb +308 -4
- data/specs/errno_spec.rb +13 -0
- data/specs/library_spec.rb +55 -0
- data/specs/managed_struct_spec.rb +40 -0
- data/specs/number_spec.rb +183 -0
- data/specs/pointer_spec.rb +126 -0
- data/specs/rbx/memory_pointer_spec.rb +7 -7
- data/specs/spec_helper.rb +7 -0
- data/specs/string_spec.rb +34 -0
- data/specs/struct_spec.rb +223 -0
- data/specs/typedef_spec.rb +48 -0
- data/specs/variadic_spec.rb +84 -0
- metadata +270 -237
@@ -0,0 +1,40 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper"))
|
2
|
+
|
3
|
+
describe "Managed Struct" do
|
4
|
+
include FFI
|
5
|
+
module LibTest
|
6
|
+
extend FFI::Library
|
7
|
+
ffi_lib TestLibrary::PATH
|
8
|
+
attach_function :ptr_from_address, [ FFI::Platform::ADDRESS_SIZE == 32 ? :uint : :ulong_long ], :pointer
|
9
|
+
end
|
10
|
+
it "should raise an error if release() is not defined" do
|
11
|
+
class NoRelease < FFI::ManagedStruct ; end
|
12
|
+
lambda { NoRelease.new(LibTest.ptr_from_address(0x12345678)) }.should raise_error(NoMethodError)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should release memory properly" do
|
16
|
+
class PleaseReleaseMe < FFI::ManagedStruct
|
17
|
+
@@count = 0
|
18
|
+
def self.release
|
19
|
+
@@count += 1
|
20
|
+
end
|
21
|
+
def self.wait_gc(count)
|
22
|
+
loop = 5
|
23
|
+
while loop > 0 && @@count < count
|
24
|
+
loop -= 1
|
25
|
+
GC.start
|
26
|
+
sleep 0.05 if @@count < count
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
loop_count = 30
|
32
|
+
wiggle_room = 2
|
33
|
+
|
34
|
+
PleaseReleaseMe.should_receive(:release).at_least(loop_count-wiggle_room).times
|
35
|
+
loop_count.times do
|
36
|
+
s = PleaseReleaseMe.new(LibTest.ptr_from_address(0x12345678))
|
37
|
+
end
|
38
|
+
PleaseReleaseMe.wait_gc loop_count
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper"))
|
2
|
+
describe "Function with primitive integer arguments" do
|
3
|
+
module LibTest
|
4
|
+
extend FFI::Library
|
5
|
+
ffi_lib TestLibrary::PATH
|
6
|
+
attach_function :ret_int8_t, [ :char ], :char
|
7
|
+
attach_function :ret_u_int8_t, [ :uchar ], :uchar
|
8
|
+
attach_function :ret_int16_t, [ :short ], :short
|
9
|
+
attach_function :ret_u_int16_t, [ :ushort ], :ushort
|
10
|
+
attach_function :ret_int32_t, [ :int ], :int
|
11
|
+
attach_function :ret_u_int32_t, [ :uint ], :uint
|
12
|
+
attach_function :ret_int64_t, [ :long_long ], :long_long
|
13
|
+
attach_function :ret_u_int64_t, [ :ulong_long ], :ulong_long
|
14
|
+
attach_function :ret_long, [ :long ], :long
|
15
|
+
attach_function :ret_ulong, [ :ulong ], :ulong
|
16
|
+
end
|
17
|
+
[ 0, 127, -128, -1 ].each do |i|
|
18
|
+
it ":char call(:char (#{i}))" do
|
19
|
+
LibTest.ret_int8_t(i).should == i
|
20
|
+
end
|
21
|
+
end
|
22
|
+
[ 0, 0x7f, 0x80, 0xff ].each do |i|
|
23
|
+
it ":uchar call(:uchar (#{i}))" do
|
24
|
+
LibTest.ret_u_int8_t(i).should == i
|
25
|
+
end
|
26
|
+
end
|
27
|
+
[ 0, 0x7fff, -0x8000, -1 ].each do |i|
|
28
|
+
it ":short call(:short (#{i}))" do
|
29
|
+
LibTest.ret_int16_t(i).should == i
|
30
|
+
end
|
31
|
+
end
|
32
|
+
[ 0, 0x7fff, 0x8000, 0xffff ].each do |i|
|
33
|
+
it ":ushort call(:ushort (#{i}))" do
|
34
|
+
LibTest.ret_u_int16_t(i).should == i
|
35
|
+
end
|
36
|
+
end
|
37
|
+
[ 0, 0x7fffffff, -0x80000000, -1 ].each do |i|
|
38
|
+
it ":int call(:int (#{i}))" do
|
39
|
+
LibTest.ret_int32_t(i).should == i
|
40
|
+
end
|
41
|
+
end
|
42
|
+
[ 0, 0x7fffffff, 0x80000000, 0xffffffff ].each do |i|
|
43
|
+
it ":uint call(:uint (#{i}))" do
|
44
|
+
LibTest.ret_u_int32_t(i).should == i
|
45
|
+
end
|
46
|
+
end
|
47
|
+
[ 0, 0x7fffffffffffffff, -0x8000000000000000, -1 ].each do |i|
|
48
|
+
it ":long_long call(:long_long (#{i}))" do
|
49
|
+
LibTest.ret_int64_t(i).should == i
|
50
|
+
end
|
51
|
+
end
|
52
|
+
[ 0, 0x7fffffffffffffff, 0x8000000000000000, 0xffffffffffffffff ].each do |i|
|
53
|
+
it ":ulong_long call(:ulong_long (#{i}))" do
|
54
|
+
LibTest.ret_u_int64_t(i).should == i
|
55
|
+
end
|
56
|
+
end
|
57
|
+
if FFI::Platform::LONG_SIZE == 32
|
58
|
+
[ 0, 0x7fffffff, -0x80000000, -1 ].each do |i|
|
59
|
+
it ":long call(:long (#{i}))" do
|
60
|
+
LibTest.ret_long(i).should == i
|
61
|
+
end
|
62
|
+
end
|
63
|
+
[ 0, 0x7fffffff, 0x80000000, 0xffffffff ].each do |i|
|
64
|
+
it ":ulong call(:ulong (#{i}))" do
|
65
|
+
LibTest.ret_ulong(i).should == i
|
66
|
+
end
|
67
|
+
end
|
68
|
+
else
|
69
|
+
[ 0, 0x7fffffffffffffff, -0x8000000000000000, -1 ].each do |i|
|
70
|
+
it ":long call(:long (#{i}))" do
|
71
|
+
LibTest.ret_long(i).should == i
|
72
|
+
end
|
73
|
+
end
|
74
|
+
[ 0, 0x7fffffffffffffff, 0x8000000000000000, 0xffffffffffffffff ].each do |i|
|
75
|
+
it ":ulong call(:ulong (#{i}))" do
|
76
|
+
LibTest.ret_ulong(i).should == i
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
describe "Integer parameter range checking" do
|
82
|
+
[ 128, -129 ].each do |i|
|
83
|
+
it ":char call(:char (#{i}))" do
|
84
|
+
lambda { LibTest.ret_int8_t(i).should == i }.should raise_error
|
85
|
+
end
|
86
|
+
end
|
87
|
+
[ -1, 256 ].each do |i|
|
88
|
+
it ":uchar call(:uchar (#{i}))" do
|
89
|
+
lambda { LibTest.ret_u_int8_t(i).should == i }.should raise_error
|
90
|
+
end
|
91
|
+
end
|
92
|
+
[ 0x8000, -0x8001 ].each do |i|
|
93
|
+
it ":short call(:short (#{i}))" do
|
94
|
+
lambda { LibTest.ret_int16_t(i).should == i }.should raise_error
|
95
|
+
end
|
96
|
+
end
|
97
|
+
[ -1, 0x10000 ].each do |i|
|
98
|
+
it ":ushort call(:ushort (#{i}))" do
|
99
|
+
lambda { LibTest.ret_u_int16_t(i).should == i }.should raise_error
|
100
|
+
end
|
101
|
+
end
|
102
|
+
[ 0x80000000, -0x80000001 ].each do |i|
|
103
|
+
it ":int call(:int (#{i}))" do
|
104
|
+
lambda { LibTest.ret_int32_t(i).should == i }.should raise_error
|
105
|
+
end
|
106
|
+
end
|
107
|
+
[ -1, 0x100000000 ].each do |i|
|
108
|
+
it ":ushort call(:ushort (#{i}))" do
|
109
|
+
lambda { LibTest.ret_u_int32_t(i).should == i }.should raise_error
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
describe "Three different size Integer arguments" do
|
114
|
+
TYPE_MAP = {
|
115
|
+
's8' => :char, 'u8' => :uchar, 's16' => :short, 'u16' => :ushort,
|
116
|
+
's32' => :int, 'u32' => :uint, 's64' => :long_long, 'u64' => :ulong_long,
|
117
|
+
'sL' => :long, 'uL' => :ulong, 'f32' => :float, 'f64' => :double
|
118
|
+
}
|
119
|
+
TYPES = TYPE_MAP.keys
|
120
|
+
module LibTest
|
121
|
+
extend FFI::Library
|
122
|
+
ffi_lib TestLibrary::PATH
|
123
|
+
|
124
|
+
|
125
|
+
[ 's32', 'u32', 's64', 'u64' ].each do |rt|
|
126
|
+
TYPES.each do |t1|
|
127
|
+
TYPES.each do |t2|
|
128
|
+
TYPES.each do |t3|
|
129
|
+
begin
|
130
|
+
attach_function "pack_#{t1}#{t2}#{t3}_#{rt}",
|
131
|
+
[ TYPE_MAP[t1], TYPE_MAP[t2], TYPE_MAP[t3], :buffer_out ], :void
|
132
|
+
rescue FFI::NotFoundError
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
PACK_VALUES = {
|
141
|
+
's8' => [ 0x12 ],
|
142
|
+
'u8' => [ 0x34 ],
|
143
|
+
's16' => [ 0x5678 ],
|
144
|
+
'u16' => [ 0x9abc ],
|
145
|
+
's32' => [ 0x7654321f ],
|
146
|
+
'u32' => [ 0xfee1babe ],
|
147
|
+
'sL' => [ 0x1f2e3d4c ],
|
148
|
+
'uL' => [ 0xf7e8d9ca ],
|
149
|
+
's64' => [ 0x1eafdeadbeefa1b2 ],
|
150
|
+
# 'f32' => [ 1.234567 ],
|
151
|
+
'f64' => [ 9.87654321 ]
|
152
|
+
}
|
153
|
+
module Number
|
154
|
+
def self.verify(p, off, t, v)
|
155
|
+
if t == 'f32'
|
156
|
+
p.get_float32(off).should == v
|
157
|
+
elsif t == 'f64'
|
158
|
+
p.get_float64(off).should == v
|
159
|
+
else
|
160
|
+
p.get_int64(off).should == v
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
PACK_VALUES.keys.each do |t1|
|
165
|
+
PACK_VALUES.keys.each do |t2|
|
166
|
+
PACK_VALUES.keys.each do |t3|
|
167
|
+
PACK_VALUES[t1].each do |v1|
|
168
|
+
PACK_VALUES[t2].each do |v2|
|
169
|
+
PACK_VALUES[t3].each do |v3|
|
170
|
+
it "call(#{TYPE_MAP[t1]} (#{v1}), #{TYPE_MAP[t2]} (#{v2}), #{TYPE_MAP[t3]} (#{v3}))" do
|
171
|
+
p = FFI::Buffer.new :long_long, 3
|
172
|
+
LibTest.send("pack_#{t1}#{t2}#{t3}_s64", v1, v2, v3, p)
|
173
|
+
Number.verify(p, 0, t1, v1)
|
174
|
+
Number.verify(p, 8, t2, v2)
|
175
|
+
Number.verify(p, 16, t3, v3)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper"))
|
2
|
+
require 'delegate'
|
3
|
+
module LibTest
|
4
|
+
attach_function :ptr_ret_int32_t, [ :pointer, :int ], :int
|
5
|
+
attach_function :ptr_from_address, [ FFI::Platform::ADDRESS_SIZE == 32 ? :uint : :ulong_long ], :pointer
|
6
|
+
end
|
7
|
+
describe "Pointer" do
|
8
|
+
include FFI
|
9
|
+
class ToPtrTest
|
10
|
+
def initialize(ptr)
|
11
|
+
@ptr = ptr
|
12
|
+
end
|
13
|
+
def to_ptr
|
14
|
+
@ptr
|
15
|
+
end
|
16
|
+
end
|
17
|
+
it "Any object implementing #to_ptr can be passed as a :pointer parameter" do
|
18
|
+
memory = MemoryPointer.new :long_long
|
19
|
+
magic = 0x12345678
|
20
|
+
memory.put_int32(0, magic)
|
21
|
+
tp = ToPtrTest.new(memory)
|
22
|
+
LibTest.ptr_ret_int32_t(tp, 0).should == magic
|
23
|
+
end
|
24
|
+
class PointerDelegate < DelegateClass(FFI::Pointer)
|
25
|
+
def initialize(ptr)
|
26
|
+
super
|
27
|
+
@ptr = ptr
|
28
|
+
end
|
29
|
+
def to_ptr
|
30
|
+
@ptr
|
31
|
+
end
|
32
|
+
end
|
33
|
+
it "A DelegateClass(Pointer) can be passed as a :pointer parameter" do
|
34
|
+
memory = MemoryPointer.new :long_long
|
35
|
+
magic = 0x12345678
|
36
|
+
memory.put_int32(0, magic)
|
37
|
+
ptr = PointerDelegate.new(memory)
|
38
|
+
LibTest.ptr_ret_int32_t(ptr, 0).should == magic
|
39
|
+
end
|
40
|
+
it "Fixnum cannot be used as a Pointer argument" do
|
41
|
+
lambda { LibTest.ptr_ret_int32(0, 0) }.should raise_error
|
42
|
+
end
|
43
|
+
it "Bignum cannot be used as a Pointer argument" do
|
44
|
+
lambda { LibTest.ptr_ret_int32(0xfee1deadbeefcafebabe, 0) }.should raise_error
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "AutoPointer" do
|
49
|
+
loop_count = 30
|
50
|
+
wiggle_room = 2 # GC rarely cleans up all objects. we can get most of them, and that's enough to determine if the basic functionality is working.
|
51
|
+
magic = 0x12345678
|
52
|
+
|
53
|
+
class AutoPointerTestHelper
|
54
|
+
@@count = 0
|
55
|
+
def self.release
|
56
|
+
@@count += 1 if @@count > 0
|
57
|
+
end
|
58
|
+
def self.reset
|
59
|
+
@@count = 0
|
60
|
+
end
|
61
|
+
def self.gc_everything(count)
|
62
|
+
loop = 5
|
63
|
+
while @@count < count && loop > 0
|
64
|
+
loop -= 1
|
65
|
+
GC.start
|
66
|
+
sleep 0.05 unless @@count == count
|
67
|
+
end
|
68
|
+
@@count = 0
|
69
|
+
end
|
70
|
+
def self.finalizer
|
71
|
+
self.method(:release).to_proc
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
it "cleanup via default release method" do
|
76
|
+
FFI::AutoPointer.should_receive(:release).at_least(loop_count-wiggle_room).times
|
77
|
+
AutoPointerTestHelper.reset
|
78
|
+
loop_count.times do
|
79
|
+
# note that if we called
|
80
|
+
# AutoPointerTestHelper.method(:release).to_proc inline, we'd
|
81
|
+
# have a reference to the pointer and it would never get GC'd.
|
82
|
+
ap = FFI::AutoPointer.new(LibTest.ptr_from_address(magic))
|
83
|
+
end
|
84
|
+
AutoPointerTestHelper.gc_everything loop_count
|
85
|
+
end
|
86
|
+
|
87
|
+
it "cleanup when passed a proc" do
|
88
|
+
# NOTE: passing a proc is touchy, because it's so easy to create a memory leak.
|
89
|
+
#
|
90
|
+
# specifically, if we made an inline call to
|
91
|
+
#
|
92
|
+
# AutoPointerTestHelper.method(:release).to_proc
|
93
|
+
#
|
94
|
+
# we'd have a reference to the pointer and it would
|
95
|
+
# never get GC'd.
|
96
|
+
AutoPointerTestHelper.should_receive(:release).at_least(loop_count-wiggle_room).times
|
97
|
+
AutoPointerTestHelper.reset
|
98
|
+
loop_count.times do
|
99
|
+
ap = FFI::AutoPointer.new(LibTest.ptr_from_address(magic),
|
100
|
+
AutoPointerTestHelper.finalizer)
|
101
|
+
end
|
102
|
+
AutoPointerTestHelper.gc_everything loop_count
|
103
|
+
end
|
104
|
+
|
105
|
+
it "cleanup when passed a method" do
|
106
|
+
AutoPointerTestHelper.should_receive(:release).at_least(loop_count-wiggle_room).times
|
107
|
+
AutoPointerTestHelper.reset
|
108
|
+
loop_count.times do
|
109
|
+
ap = FFI::AutoPointer.new(LibTest.ptr_from_address(magic),
|
110
|
+
AutoPointerTestHelper.method(:release))
|
111
|
+
end
|
112
|
+
AutoPointerTestHelper.gc_everything loop_count
|
113
|
+
end
|
114
|
+
end
|
115
|
+
describe "AutoPointer#new" do
|
116
|
+
it "MemoryPointer argument raises ArgumentError" do
|
117
|
+
lambda { FFI::AutoPointer.new(FFI::MemoryPointer.new(:int))}.should raise_error(ArgumentError)
|
118
|
+
end
|
119
|
+
it "AutoPointer argument raises ArgumentError" do
|
120
|
+
lambda { FFI::AutoPointer.new(FFI::AutoPointer.new(LibTest.ptr_from_address(0))) }.should raise_error(ArgumentError)
|
121
|
+
end
|
122
|
+
it "Buffer argument raises ArgumentError" do
|
123
|
+
lambda { FFI::AutoPointer.new(FFI::Buffer.new(:int))}.should raise_error(ArgumentError)
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
@@ -58,13 +58,13 @@ describe MemoryPointer do
|
|
58
58
|
m = MemoryPointer.new(1)
|
59
59
|
lambda { m.write_int(10) }.should raise_error
|
60
60
|
end
|
61
|
-
it "does not raise IndexError for opaque pointers" do
|
62
|
-
m = MemoryPointer.new(8)
|
63
|
-
p2 = MemoryPointer.new(1024)
|
64
|
-
m.write_long(p2.address)
|
65
|
-
p = m.read_pointer
|
66
|
-
lambda { p.write_int(10) }.should_not raise_error
|
67
|
-
end
|
61
|
+
# it "does not raise IndexError for opaque pointers" do
|
62
|
+
# m = MemoryPointer.new(8)
|
63
|
+
# p2 = MemoryPointer.new(1024)
|
64
|
+
# m.write_long(p2.address)
|
65
|
+
# p = m.read_pointer
|
66
|
+
# lambda { p.write_int(10) }.should_not raise_error
|
67
|
+
# end
|
68
68
|
|
69
69
|
it "makes a pointer for a certain type" do
|
70
70
|
m = MemoryPointer.new(:int)
|
data/specs/spec_helper.rb
CHANGED
@@ -0,0 +1,34 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper"))
|
2
|
+
describe "String tests" do
|
3
|
+
include FFI
|
4
|
+
module LibTest
|
5
|
+
extend FFI::Library
|
6
|
+
ffi_lib TestLibrary::PATH
|
7
|
+
attach_function :ptr_ret_pointer, [ :pointer, :int], :string
|
8
|
+
attach_function :string_equals, [ :string, :string ], :int
|
9
|
+
end
|
10
|
+
it "MemoryPointer#get_string returns a tainted string" do
|
11
|
+
mp = MemoryPointer.new 1024
|
12
|
+
mp.put_string(0, "test\0")
|
13
|
+
str = mp.get_string(0)
|
14
|
+
str.tainted?.should == true
|
15
|
+
end
|
16
|
+
it "String returned by a method is tainted" do
|
17
|
+
mp = MemoryPointer.new :pointer
|
18
|
+
sp = MemoryPointer.new 1024
|
19
|
+
sp.put_string(0, "test")
|
20
|
+
mp.put_pointer(0, sp)
|
21
|
+
str = LibTest.ptr_ret_pointer(mp, 0)
|
22
|
+
str.should == "test"
|
23
|
+
str.tainted?.should == true
|
24
|
+
end
|
25
|
+
it "Tainted String parameter should throw a SecurityError" do
|
26
|
+
$SAFE = 1
|
27
|
+
str = "test"
|
28
|
+
str.taint
|
29
|
+
begin
|
30
|
+
LibTest.string_equals(str, str).should == false
|
31
|
+
rescue SecurityError => e
|
32
|
+
end
|
33
|
+
end if false
|
34
|
+
end
|
@@ -0,0 +1,223 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper"))
|
2
|
+
describe "Struct tests" do
|
3
|
+
include FFI
|
4
|
+
StructTypes = {
|
5
|
+
's8' => :char,
|
6
|
+
's16' => :short,
|
7
|
+
's32' => :int,
|
8
|
+
's64' => :long_long,
|
9
|
+
'long' => :long,
|
10
|
+
'f32' => :float,
|
11
|
+
'f64' => :double
|
12
|
+
}
|
13
|
+
module LibTest
|
14
|
+
extend FFI::Library
|
15
|
+
ffi_lib TestLibrary::PATH
|
16
|
+
attach_function :ptr_ret_pointer, [ :pointer, :int], :string
|
17
|
+
attach_function :ptr_from_address, [ :ulong ], :pointer
|
18
|
+
attach_function :string_equals, [ :string, :string ], :int
|
19
|
+
[ 's8', 's16', 's32', 's64', 'f32', 'f64', 'long' ].each do |t|
|
20
|
+
attach_function "struct_align_#{t}", [ :pointer ], StructTypes[t]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
class PointerMember < FFI::Struct
|
24
|
+
layout :pointer, :pointer
|
25
|
+
end
|
26
|
+
class StringMember < FFI::Struct
|
27
|
+
layout :string, :string
|
28
|
+
end
|
29
|
+
it "Struct#[:pointer]" do
|
30
|
+
magic = 0x12345678
|
31
|
+
mp = MemoryPointer.new :long
|
32
|
+
mp.put_long(0, magic)
|
33
|
+
smp = MemoryPointer.new :pointer
|
34
|
+
smp.put_pointer(0, mp)
|
35
|
+
s = PointerMember.new smp
|
36
|
+
s[:pointer].should == mp
|
37
|
+
end
|
38
|
+
it "Struct#[:pointer].nil? for NULL value" do
|
39
|
+
magic = 0x12345678
|
40
|
+
mp = MemoryPointer.new :long
|
41
|
+
mp.put_long(0, magic)
|
42
|
+
smp = MemoryPointer.new :pointer
|
43
|
+
smp.put_pointer(0, nil)
|
44
|
+
s = PointerMember.new smp
|
45
|
+
s[:pointer].null?.should == true
|
46
|
+
end
|
47
|
+
it "Struct#[:pointer]=" do
|
48
|
+
magic = 0x12345678
|
49
|
+
mp = MemoryPointer.new :long
|
50
|
+
mp.put_long(0, magic)
|
51
|
+
smp = MemoryPointer.new :pointer
|
52
|
+
s = PointerMember.new smp
|
53
|
+
s[:pointer] = mp
|
54
|
+
smp.get_pointer(0).should == mp
|
55
|
+
end
|
56
|
+
it "Struct#[:pointer]=nil" do
|
57
|
+
smp = MemoryPointer.new :pointer
|
58
|
+
s = PointerMember.new smp
|
59
|
+
s[:pointer] = nil
|
60
|
+
smp.get_pointer(0).null?.should == true
|
61
|
+
end
|
62
|
+
it "Struct#[:string]" do
|
63
|
+
magic = "test"
|
64
|
+
mp = MemoryPointer.new 1024
|
65
|
+
mp.put_string(0, magic)
|
66
|
+
smp = MemoryPointer.new :pointer
|
67
|
+
smp.put_pointer(0, mp)
|
68
|
+
s = StringMember.new smp
|
69
|
+
s[:string].should == magic
|
70
|
+
end
|
71
|
+
it "Struct#[:string].nil? for NULL value" do
|
72
|
+
smp = MemoryPointer.new :pointer
|
73
|
+
smp.put_pointer(0, nil)
|
74
|
+
s = StringMember.new smp
|
75
|
+
s[:string].nil?.should == true
|
76
|
+
end
|
77
|
+
it "Struct#layout works with :name, :type pairs" do
|
78
|
+
class PairLayout < FFI::Struct
|
79
|
+
layout :a, :int, :b, :long_long
|
80
|
+
end
|
81
|
+
PairLayout.size.should == 12
|
82
|
+
mp = MemoryPointer.new(12)
|
83
|
+
s = PairLayout.new mp
|
84
|
+
s[:a] = 0x12345678
|
85
|
+
mp.get_int(0).should == 0x12345678
|
86
|
+
s[:b] = 0xfee1deadbeef
|
87
|
+
mp.get_int64(4).should == 0xfee1deadbeef
|
88
|
+
end
|
89
|
+
it "Struct#layout works with :name, :type, offset tuples" do
|
90
|
+
class PairLayout < FFI::Struct
|
91
|
+
layout :a, :int, 0, :b, :long_long, 4
|
92
|
+
end
|
93
|
+
PairLayout.size.should == 12
|
94
|
+
mp = MemoryPointer.new(12)
|
95
|
+
s = PairLayout.new mp
|
96
|
+
s[:a] = 0x12345678
|
97
|
+
mp.get_int(0).should == 0x12345678
|
98
|
+
s[:b] = 0xfee1deadbeef
|
99
|
+
mp.get_int64(4).should == 0xfee1deadbeef
|
100
|
+
end
|
101
|
+
it "Struct#layout works with mixed :name,:type and :name,:type,offset" do
|
102
|
+
class MixedLayout < FFI::Struct
|
103
|
+
layout :a, :int, :b, :long_long, 4
|
104
|
+
end
|
105
|
+
MixedLayout.size.should == 12
|
106
|
+
mp = MemoryPointer.new(12)
|
107
|
+
s = MixedLayout.new mp
|
108
|
+
s[:a] = 0x12345678
|
109
|
+
mp.get_int(0).should == 0x12345678
|
110
|
+
s[:b] = 0xfee1deadbeef
|
111
|
+
mp.get_int64(4).should == 0xfee1deadbeef
|
112
|
+
end
|
113
|
+
rb_maj, rb_min = RUBY_VERSION.split('.')
|
114
|
+
if rb_maj.to_i >= 1 && rb_min.to_i >= 9 || RUBY_PLATFORM =~ /java/
|
115
|
+
it "Struct#layout withs with a hash of :name => type" do
|
116
|
+
class HashLayout < FFI::Struct
|
117
|
+
layout :a => :int, :b => :long_long
|
118
|
+
end
|
119
|
+
HashLayout.size.should == 12
|
120
|
+
mp = MemoryPointer.new(12)
|
121
|
+
s = HashLayout.new mp
|
122
|
+
s[:a] = 0x12345678
|
123
|
+
mp.get_int(0).should == 0x12345678
|
124
|
+
s[:b] = 0xfee1deadbeef
|
125
|
+
mp.get_int64(4).should == 0xfee1deadbeef
|
126
|
+
end
|
127
|
+
end
|
128
|
+
it "Can use Struct subclass as parameter type" do
|
129
|
+
module StructParam
|
130
|
+
extend FFI::Library
|
131
|
+
ffi_lib TestLibrary::PATH
|
132
|
+
class TestStruct < FFI::Struct
|
133
|
+
layout :c, :char
|
134
|
+
end
|
135
|
+
attach_function :struct_field_s8, [ TestStruct ], :char
|
136
|
+
end
|
137
|
+
end
|
138
|
+
it "Can use Struct subclass as IN parameter type" do
|
139
|
+
module StructParam
|
140
|
+
extend FFI::Library
|
141
|
+
ffi_lib TestLibrary::PATH
|
142
|
+
class TestStruct < FFI::Struct
|
143
|
+
layout :c, :char
|
144
|
+
end
|
145
|
+
attach_function :struct_field_s8, [ TestStruct.in ], :char
|
146
|
+
end
|
147
|
+
end
|
148
|
+
it "Can use Struct subclass as OUT parameter type" do
|
149
|
+
module StructParam
|
150
|
+
extend FFI::Library
|
151
|
+
ffi_lib TestLibrary::PATH
|
152
|
+
class TestStruct < FFI::Struct
|
153
|
+
layout :c, :char
|
154
|
+
end
|
155
|
+
attach_function :struct_field_s8, [ TestStruct.out ], :char
|
156
|
+
end
|
157
|
+
end
|
158
|
+
it ":char member aligned correctly" do
|
159
|
+
class AlignChar < FFI::Struct
|
160
|
+
layout :c, :char, :v, :char
|
161
|
+
end
|
162
|
+
s = AlignChar.new
|
163
|
+
s[:v] = 0x12
|
164
|
+
LibTest.struct_align_s8(s.pointer).should == 0x12
|
165
|
+
end
|
166
|
+
it ":short member aligned correctly" do
|
167
|
+
class AlignShort < FFI::Struct
|
168
|
+
layout :c, :char, :v, :short
|
169
|
+
end
|
170
|
+
s = AlignShort.alloc_in
|
171
|
+
s[:v] = 0x1234
|
172
|
+
LibTest.struct_align_s16(s.pointer).should == 0x1234
|
173
|
+
end
|
174
|
+
it ":int member aligned correctly" do
|
175
|
+
class AlignInt < FFI::Struct
|
176
|
+
layout :c, :char, :v, :int
|
177
|
+
end
|
178
|
+
s = AlignInt.alloc_in
|
179
|
+
s[:v] = 0x12345678
|
180
|
+
LibTest.struct_align_s32(s.pointer).should == 0x12345678
|
181
|
+
end
|
182
|
+
it ":long_long member aligned correctly" do
|
183
|
+
class AlignLongLong < FFI::Struct
|
184
|
+
layout :c, :char, :v, :long_long
|
185
|
+
end
|
186
|
+
s = AlignLongLong.alloc_in
|
187
|
+
s[:v] = 0x123456789abcdef0
|
188
|
+
LibTest.struct_align_s64(s.pointer).should == 0x123456789abcdef0
|
189
|
+
end
|
190
|
+
it ":long member aligned correctly" do
|
191
|
+
class AlignLong < FFI::Struct
|
192
|
+
layout :c, :char, :v, :long
|
193
|
+
end
|
194
|
+
s = AlignLong.alloc_in
|
195
|
+
s[:v] = 0x12345678
|
196
|
+
LibTest.struct_align_long(s.pointer).should == 0x12345678
|
197
|
+
end
|
198
|
+
it ":float member aligned correctly" do
|
199
|
+
class AlignFloat < FFI::Struct
|
200
|
+
layout :c, :char, :v, :float
|
201
|
+
end
|
202
|
+
s = AlignFloat.alloc_in
|
203
|
+
s[:v] = 1.23456
|
204
|
+
(LibTest.struct_align_f32(s.pointer) - 1.23456).abs.should < 0.00001
|
205
|
+
end
|
206
|
+
it ":double member aligned correctly" do
|
207
|
+
class AlignDouble < FFI::Struct
|
208
|
+
layout :c, :char, :v, :double
|
209
|
+
end
|
210
|
+
s = AlignDouble.alloc_in
|
211
|
+
s[:v] = 1.23456789
|
212
|
+
(LibTest.struct_align_f64(s.pointer) - 1.23456789).abs.should < 0.00000001
|
213
|
+
end
|
214
|
+
it ":ulong, :pointer struct" do
|
215
|
+
class ULPStruct < FFI::Struct
|
216
|
+
layout :ul, :ulong, :p, :pointer
|
217
|
+
end
|
218
|
+
s = ULPStruct.alloc_in
|
219
|
+
s[:ul] = 0xdeadbeef
|
220
|
+
s[:p] = LibTest.ptr_from_address(0x12345678)
|
221
|
+
s.pointer.get_ulong(0).should == 0xdeadbeef
|
222
|
+
end
|
223
|
+
end
|