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.

Files changed (62) hide show
  1. data/Rakefile +52 -29
  2. data/ext/AbstractMemory.c +72 -28
  3. data/ext/AutoPointer.c +54 -0
  4. data/ext/AutoPointer.h +18 -0
  5. data/ext/Buffer.c +21 -17
  6. data/ext/Callback.c +81 -43
  7. data/ext/Callback.h +1 -1
  8. data/ext/Invoker.c +465 -108
  9. data/ext/MemoryPointer.c +25 -90
  10. data/ext/NativeLibrary.c +90 -0
  11. data/ext/NativeLibrary.h +22 -0
  12. data/ext/Platform.c +21 -2
  13. data/ext/Pointer.c +107 -0
  14. data/ext/Pointer.h +21 -0
  15. data/ext/Types.c +16 -5
  16. data/ext/Types.h +3 -1
  17. data/ext/compat.h +14 -0
  18. data/ext/extconf.rb +13 -1
  19. data/ext/ffi.c +11 -1
  20. data/ext/ffi.mk +3 -3
  21. data/ext/libffi.darwin.mk +19 -8
  22. data/gen/Rakefile +12 -0
  23. data/lib/ffi/autopointer.rb +61 -0
  24. data/lib/ffi/errno.rb +8 -0
  25. data/lib/ffi/ffi.rb +38 -201
  26. data/lib/ffi/io.rb +7 -0
  27. data/lib/ffi/library.rb +116 -0
  28. data/lib/ffi/managedstruct.rb +55 -0
  29. data/lib/ffi/memorypointer.rb +3 -96
  30. data/lib/ffi/platform.rb +8 -5
  31. data/lib/ffi/pointer.rb +105 -0
  32. data/lib/ffi/struct.rb +97 -42
  33. data/lib/ffi/tools/const_generator.rb +177 -0
  34. data/lib/ffi/tools/generator.rb +58 -0
  35. data/lib/ffi/tools/generator_task.rb +35 -0
  36. data/lib/ffi/tools/struct_generator.rb +194 -0
  37. data/lib/ffi/tools/types_generator.rb +123 -0
  38. data/lib/ffi/types.rb +150 -0
  39. data/lib/ffi/variadic.rb +30 -0
  40. data/nbproject/Makefile-Default.mk +6 -3
  41. data/nbproject/Makefile-impl.mk +5 -5
  42. data/nbproject/Package-Default.bash +72 -0
  43. data/nbproject/configurations.xml +139 -25
  44. data/nbproject/private/configurations.xml +1 -1
  45. data/nbproject/project.xml +4 -0
  46. data/samples/gettimeofday.rb +6 -2
  47. data/samples/inotify.rb +59 -0
  48. data/samples/pty.rb +75 -0
  49. data/specs/buffer_spec.rb +64 -9
  50. data/specs/callback_spec.rb +308 -4
  51. data/specs/errno_spec.rb +13 -0
  52. data/specs/library_spec.rb +55 -0
  53. data/specs/managed_struct_spec.rb +40 -0
  54. data/specs/number_spec.rb +183 -0
  55. data/specs/pointer_spec.rb +126 -0
  56. data/specs/rbx/memory_pointer_spec.rb +7 -7
  57. data/specs/spec_helper.rb +7 -0
  58. data/specs/string_spec.rb +34 -0
  59. data/specs/struct_spec.rb +223 -0
  60. data/specs/typedef_spec.rb +48 -0
  61. data/specs/variadic_spec.rb +84 -0
  62. 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)
@@ -1,2 +1,9 @@
1
1
  $:.unshift File.join(File.dirname(__FILE__), "..", "lib") if ENV["MRI_FFI"]
2
2
  require "ffi"
3
+ module TestLibrary
4
+ PATH = "#{Dir.getwd}/build/libtest.#{FFI::Platform::LIBSUFFIX}"
5
+ end
6
+ module LibTest
7
+ extend FFI::Library
8
+ ffi_lib TestLibrary::PATH
9
+ end
@@ -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