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,177 @@
1
+ require 'tempfile'
2
+ require 'open3'
3
+
4
+ module FFI
5
+
6
+ ##
7
+ # ConstGenerator turns C constants into ruby values.
8
+
9
+ class ConstGenerator
10
+ @options = {}
11
+ attr_reader :constants
12
+
13
+ ##
14
+ # Creates a new constant generator that uses +prefix+ as a name, and an
15
+ # options hash.
16
+ #
17
+ # The only option is :required, which if set to true raises an error if a
18
+ # constant you have requested was not found.
19
+ #
20
+ # When passed a block, #calculate is automatically called at the end of
21
+ # the block, otherwise you must call it yourself.
22
+
23
+ def initialize(prefix = nil, options = {})
24
+ @includes = []
25
+ @constants = {}
26
+ @prefix = prefix
27
+
28
+ @required = options[:required]
29
+ @options = options
30
+
31
+ if block_given? then
32
+ yield self
33
+ calculate self.class.options.merge(options)
34
+ end
35
+ end
36
+ def self.options=(options)
37
+ @options = options
38
+ end
39
+ def self.options
40
+ @options
41
+ end
42
+ def [](name)
43
+ @constants[name].value
44
+ end
45
+
46
+ ##
47
+ # Request the value for C constant +name+. +format+ is a printf format
48
+ # string to print the value out, and +cast+ is a C cast for the value.
49
+ # +ruby_name+ allows you to give the constant an alternate ruby name for
50
+ # #to_ruby. +converter+ or +converter_proc+ allow you to convert the
51
+ # value from a string to the appropriate type for #to_ruby.
52
+
53
+ def const(name, format = nil, cast = '', ruby_name = nil, converter = nil,
54
+ &converter_proc)
55
+ format ||= '%d'
56
+ cast ||= ''
57
+
58
+ if converter_proc and converter then
59
+ raise ArgumentError, "Supply only converter or converter block"
60
+ end
61
+
62
+ converter = converter_proc if converter.nil?
63
+
64
+ const = Constant.new name, format, cast, ruby_name, converter
65
+ @constants[name.to_s] = const
66
+ return const
67
+ end
68
+
69
+ def calculate(options = {})
70
+ binary = File.join Dir.tmpdir, "rb_const_gen_bin_#{Process.pid}"
71
+
72
+ Tempfile.open("#{@prefix}.const_generator") do |f|
73
+ f.puts "#include <stdio.h>"
74
+
75
+ @includes.each do |inc|
76
+ f.puts "#include <#{inc}>"
77
+ end
78
+
79
+ f.puts "#include <stddef.h>\n\n"
80
+ f.puts "int main(int argc, char **argv)\n{"
81
+
82
+ @constants.each_value do |const|
83
+ f.puts <<-EOF
84
+ #ifdef #{const.name}
85
+ printf("#{const.name} #{const.format}\\n", #{const.cast}#{const.name});
86
+ #endif
87
+ EOF
88
+ end
89
+
90
+ f.puts "\n\treturn 0;\n}"
91
+ f.flush
92
+
93
+ output = `gcc #{options[:cppflags]} -D_DARWIN_USE_64_BIT_INODE -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -x c -Wall -Werror #{f.path} -o #{binary} 2>&1`
94
+
95
+ unless $?.success? then
96
+ output = output.split("\n").map { |l| "\t#{l}" }.join "\n"
97
+ raise "Compilation error generating constants #{@prefix}:\n#{output}"
98
+ end
99
+ end
100
+
101
+ output = `#{binary}`
102
+ File.unlink(binary + (FFI::Platform.windows? ? ".exe" : ""))
103
+ output.each_line do |line|
104
+ line =~ /^(\S+)\s(.*)$/
105
+ const = @constants[$1]
106
+ const.value = $2
107
+ end
108
+
109
+ missing_constants = @constants.select do |name, constant|
110
+ constant.value.nil?
111
+ end.map { |name,| name }
112
+
113
+ if @required and not missing_constants.empty? then
114
+ raise "Missing required constants for #{@prefix}: #{missing_constants.join ', '}"
115
+ end
116
+ end
117
+
118
+ def dump_constants(io)
119
+ @constants.each do |name, constant|
120
+ name = [@prefix, name].join '.'
121
+ io.puts "#{name} = #{constant.converted_value}"
122
+ end
123
+ end
124
+
125
+ ##
126
+ # Outputs values for discovered constants. If the constant's value was
127
+ # not discovered it is not omitted.
128
+
129
+ def to_ruby
130
+ @constants.sort_by { |name,| name }.map do |name, constant|
131
+ if constant.value.nil? then
132
+ "# #{name} not available"
133
+ else
134
+ constant.to_ruby
135
+ end
136
+ end.join "\n"
137
+ end
138
+
139
+ def include(i)
140
+ @includes << i
141
+ end
142
+
143
+ end
144
+
145
+ class ConstGenerator::Constant
146
+
147
+ attr_reader :name, :format, :cast
148
+ attr_accessor :value
149
+
150
+ def initialize(name, format, cast, ruby_name = nil, converter=nil)
151
+ @name = name
152
+ @format = format
153
+ @cast = cast
154
+ @ruby_name = ruby_name
155
+ @converter = converter
156
+ @value = nil
157
+ end
158
+
159
+ def converted_value
160
+ if @converter
161
+ @converter.call(@value)
162
+ else
163
+ @value
164
+ end
165
+ end
166
+
167
+ def ruby_name
168
+ @ruby_name || @name
169
+ end
170
+
171
+ def to_ruby
172
+ "#{ruby_name} = #{converted_value}"
173
+ end
174
+
175
+ end
176
+
177
+ end
@@ -0,0 +1,58 @@
1
+ module FFI
2
+ class Generator
3
+
4
+ def initialize(ffi_name, rb_name, options = {})
5
+ @ffi_name = ffi_name
6
+ @rb_name = rb_name
7
+ @options = options
8
+ @name = File.basename rb_name, '.rb'
9
+
10
+ file = File.read @ffi_name
11
+
12
+ new_file = file.gsub(/^( *)@@@(.*?)@@@/m) do
13
+ @constants = []
14
+ @structs = []
15
+
16
+ indent = $1
17
+ original_lines = $2.count "\n"
18
+
19
+ instance_eval $2
20
+
21
+ new_lines = []
22
+ @constants.each { |c| new_lines << c.to_ruby }
23
+ @structs.each { |s| new_lines << s.generate_layout }
24
+
25
+ new_lines = new_lines.join("\n").split "\n" # expand multiline blocks
26
+ new_lines = new_lines.map { |line| indent + line }
27
+
28
+ padding = original_lines - new_lines.length
29
+ new_lines += [nil] * padding if padding >= 0
30
+
31
+ new_lines.join "\n"
32
+ end
33
+
34
+ open @rb_name, 'w' do |f|
35
+ f.puts "# This file is generated by rake. Do not edit."
36
+ f.puts
37
+ f.puts new_file
38
+ end
39
+ end
40
+
41
+ def constants(options = {}, &block)
42
+ @constants << FFI::ConstGenerator.new(@name, @options.merge(options), &block)
43
+ end
44
+
45
+ def struct(options = {}, &block)
46
+ @structs << FFI::StructGenerator.new(@name, @options.merge(options), &block)
47
+ end
48
+
49
+ ##
50
+ # Utility converter for constants
51
+
52
+ def to_s
53
+ proc { |obj| obj.to_s.inspect }
54
+ end
55
+
56
+ end
57
+ end
58
+
@@ -0,0 +1,35 @@
1
+ begin
2
+ require 'ffi/struct_generator'
3
+ require 'ffi/const_generator'
4
+ require 'ffi/generator'
5
+ rescue LoadError
6
+ # from Rakefile
7
+ require 'lib/ffi/struct_generator'
8
+ require 'lib/ffi/const_generator'
9
+ require 'lib/ffi/generator'
10
+ end
11
+
12
+ require 'rake'
13
+ require 'rake/tasklib'
14
+ require 'tempfile'
15
+
16
+ ##
17
+ # Rake task that calculates C structs for FFI::Struct.
18
+
19
+ class FFI::Generator::Task < Rake::TaskLib
20
+
21
+ def initialize(rb_names)
22
+ task :clean do rm_f rb_names end
23
+
24
+ rb_names.each do |rb_name|
25
+ ffi_name = "#{rb_name}.ffi"
26
+
27
+ file rb_name => ffi_name do |t|
28
+ puts "Generating #{rb_name}..." if Rake.application.options.trace
29
+
30
+ FFI::Generator.new ffi_name, rb_name
31
+ end
32
+ end
33
+ end
34
+
35
+ end
@@ -0,0 +1,194 @@
1
+ require 'tempfile'
2
+
3
+ module FFI
4
+
5
+ ##
6
+ # Generates an FFI Struct layout.
7
+ #
8
+ # Given the @@@ portion in:
9
+ #
10
+ # module Zlib::ZStream < FFI::Struct
11
+ # @@@
12
+ # name "struct z_stream_s"
13
+ # include "zlib.h"
14
+ #
15
+ # field :next_in, :pointer
16
+ # field :avail_in, :uint
17
+ # field :total_in, :ulong
18
+ #
19
+ # # ...
20
+ # @@@
21
+ # end
22
+ #
23
+ # StructGenerator will create the layout:
24
+ #
25
+ # layout :next_in, :pointer, 0,
26
+ # :avail_in, :uint, 4,
27
+ # :total_in, :ulong, 8,
28
+ # # ...
29
+ #
30
+ # StructGenerator does its best to pad the layout it produces to preserve
31
+ # line numbers. Place the struct definition as close to the top of the file
32
+ # for best results.
33
+
34
+ class StructGenerator
35
+ @options = {}
36
+ attr_accessor :size
37
+ attr_reader :fields
38
+
39
+ def initialize(name, options = {})
40
+ @name = name
41
+ @struct_name = nil
42
+ @includes = []
43
+ @fields = []
44
+ @found = false
45
+ @size = nil
46
+
47
+ if block_given? then
48
+ yield self
49
+ calculate self.class.options.merge(options)
50
+ end
51
+ end
52
+ def self.options=(options)
53
+ @options = options
54
+ end
55
+ def self.options
56
+ @options
57
+ end
58
+ def calculate(options = {})
59
+ binary = File.join Dir.tmpdir, "rb_struct_gen_bin_#{Process.pid}"
60
+
61
+ raise "struct name not set" if @struct_name.nil?
62
+
63
+ Tempfile.open("#{@name}.struct_generator") do |f|
64
+ f.puts "#include <stdio.h>"
65
+
66
+ @includes.each do |inc|
67
+ f.puts "#include <#{inc}>"
68
+ end
69
+
70
+ f.puts "#include <stddef.h>\n\n"
71
+ f.puts "int main(int argc, char **argv)\n{"
72
+ f.puts " #{@struct_name} s;"
73
+ f.puts %[ printf("sizeof(#{@struct_name}) %u\\n", (unsigned int) sizeof(#{@struct_name}));]
74
+
75
+ @fields.each do |field|
76
+ f.puts <<-EOF
77
+ printf("#{field.name} %u %u\\n", (unsigned int) offsetof(#{@struct_name}, #{field.name}),
78
+ (unsigned int) sizeof(s.#{field.name}));
79
+ EOF
80
+ end
81
+
82
+ f.puts "\n return 0;\n}"
83
+ f.flush
84
+
85
+ output = `gcc #{options[:cppflags]} #{options[:cflags]} -D_DARWIN_USE_64_BIT_INODE -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -x c -Wall -Werror #{f.path} -o #{binary} 2>&1`
86
+
87
+ unless $?.success? then
88
+ @found = false
89
+ output = output.split("\n").map { |l| "\t#{l}" }.join "\n"
90
+ raise "Compilation error generating struct #{@name} (#{@struct_name}):\n#{output}"
91
+ end
92
+ end
93
+
94
+ output = `#{binary}`.split "\n"
95
+ File.unlink(binary + (FFI::Platform.windows? ? ".exe" : ""))
96
+ sizeof = output.shift
97
+ unless @size
98
+ m = /\s*sizeof\([^)]+\) (\d+)/.match sizeof
99
+ @size = m[1]
100
+ end
101
+
102
+ line_no = 0
103
+ output.each do |line|
104
+ md = line.match(/.+ (\d+) (\d+)/)
105
+ @fields[line_no].offset = md[1].to_i
106
+ @fields[line_no].size = md[2].to_i
107
+
108
+ line_no += 1
109
+ end
110
+
111
+ @found = true
112
+ end
113
+
114
+ def field(name, type=nil)
115
+ field = Field.new(name, type)
116
+ @fields << field
117
+ return field
118
+ end
119
+
120
+ def found?
121
+ @found
122
+ end
123
+
124
+ def dump_config(io)
125
+ io.puts "rbx.platform.#{@name}.sizeof = #{@size}"
126
+
127
+ @fields.each { |field| io.puts field.to_config(@name) }
128
+ end
129
+
130
+ def generate_layout
131
+ buf = ""
132
+
133
+ @fields.each_with_index do |field, i|
134
+ if buf.empty?
135
+ buf << "layout :#{field.name}, :#{field.type}, #{field.offset}"
136
+ else
137
+ buf << " :#{field.name}, :#{field.type}, #{field.offset}"
138
+ end
139
+
140
+ if i < @fields.length - 1
141
+ buf << ",\n"
142
+ end
143
+ end
144
+
145
+ buf
146
+ end
147
+
148
+ def get_field(name)
149
+ @fields.find { |f| name == f.name }
150
+ end
151
+
152
+ def include(i)
153
+ @includes << i
154
+ end
155
+
156
+ def name(n)
157
+ @struct_name = n
158
+ end
159
+
160
+ end
161
+
162
+ ##
163
+ # A field in a Struct.
164
+
165
+ class StructGenerator::Field
166
+
167
+ attr_reader :name
168
+ attr_reader :type
169
+ attr_reader :offset
170
+ attr_accessor :size
171
+
172
+ def initialize(name, type)
173
+ @name = name
174
+ @type = type
175
+ @offset = nil
176
+ @size = nil
177
+ end
178
+
179
+ def offset=(o)
180
+ @offset = o
181
+ end
182
+
183
+ def to_config(name)
184
+ buf = []
185
+ buf << "rbx.platform.#{name}.#{@name}.offset = #{@offset}"
186
+ buf << "rbx.platform.#{name}.#{@name}.size = #{@size}"
187
+ buf << "rbx.platform.#{name}.#{@name}.type = #{@type}" if @type
188
+ buf
189
+ end
190
+
191
+ end
192
+
193
+ end
194
+