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,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
|
+
|