ffi-inline 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Rakefile +10 -0
- data/examples/ex_1.rb +10 -0
- data/ffi-inline.gemspec +21 -0
- data/lib/ffi/inliner/builders/c.rb +146 -0
- data/lib/ffi/inliner/builders/cpp.rb +27 -0
- data/lib/ffi/inliner/builders.rb +98 -0
- data/lib/ffi/inliner/compilers/gcc.rb +57 -0
- data/lib/ffi/inliner/compilers/gxx.rb +23 -0
- data/lib/ffi/inliner/compilers/tcc.rb +57 -0
- data/lib/ffi/inliner/compilers.rb +53 -0
- data/lib/ffi/inliner/error.rb +11 -0
- data/lib/ffi/inliner/inliner.rb +73 -0
- data/lib/ffi/inliner/version.rb +7 -0
- data/lib/ffi/inliner.rb +8 -0
- data/spec/inliner_spec.rb +273 -0
- metadata +93 -0
data/Rakefile
ADDED
data/examples/ex_1.rb
ADDED
data/ffi-inline.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Kernel.load 'lib/ffi/inliner/version.rb'
|
2
|
+
|
3
|
+
Gem::Specification.new {|s|
|
4
|
+
s.name = 'ffi-inline'
|
5
|
+
s.version = FFI::Inliner::Version
|
6
|
+
s.authors = 'meh.'
|
7
|
+
s.email = 'meh@paranoici.org'
|
8
|
+
s.homepage = 'http://github.com/meh/ruby-ffi-inline'
|
9
|
+
s.platform = Gem::Platform::RUBY
|
10
|
+
s.summary = 'Inline C/C++ in Ruby easily and cleanly.'
|
11
|
+
|
12
|
+
s.files = `git ls-files`.split("\n")
|
13
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
14
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
15
|
+
s.require_paths = ['lib']
|
16
|
+
|
17
|
+
s.add_dependency 'ffi', '>=0.4.0'
|
18
|
+
|
19
|
+
s.add_development_dependency 'rake'
|
20
|
+
s.add_development_dependency 'rspec'
|
21
|
+
}
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'ffi/inliner/compilers/tcc'
|
2
|
+
require 'ffi/inliner/compilers/gcc'
|
3
|
+
|
4
|
+
module FFI; module Inliner
|
5
|
+
|
6
|
+
Builder.define :c do
|
7
|
+
ToFFI = {
|
8
|
+
'void' => :void,
|
9
|
+
'char' => :char,
|
10
|
+
'unsigned char' => :uchar,
|
11
|
+
'int' => :int,
|
12
|
+
'unsigned int' => :uint,
|
13
|
+
'long' => :long,
|
14
|
+
'unsigned long' => :ulong,
|
15
|
+
'float' => :float,
|
16
|
+
'double' => :double,
|
17
|
+
}
|
18
|
+
|
19
|
+
attr_reader :code, :compiler, :libraries
|
20
|
+
|
21
|
+
def initialize(code = nil, options = {})
|
22
|
+
super(code)
|
23
|
+
|
24
|
+
@types = ToFFI.dup
|
25
|
+
@libraries = options[:libraries] || []
|
26
|
+
|
27
|
+
@signatures = []
|
28
|
+
|
29
|
+
use_compiler options[:use_compiler] || options[:compiler] || :gcc
|
30
|
+
|
31
|
+
@signatures << parse_signature(code) if code && !code.empty?
|
32
|
+
end
|
33
|
+
|
34
|
+
def libraries(*libraries)
|
35
|
+
@libraries.concat(libraries)
|
36
|
+
end
|
37
|
+
|
38
|
+
def types(map = nil)
|
39
|
+
map ? @types.merge!(map) : @types
|
40
|
+
end; alias map types
|
41
|
+
|
42
|
+
def raw(code, no_line = false)
|
43
|
+
return super(code) if no_line
|
44
|
+
|
45
|
+
whole, path, line = caller.find { |line| line !~ /ffi-inliner/ }.match(/^(.*?):(\d+):in/).to_a
|
46
|
+
|
47
|
+
super "\n#line #{line.to_i} #{path.inspect}\n" << code
|
48
|
+
end; alias c_raw raw
|
49
|
+
|
50
|
+
def include(path, options = {})
|
51
|
+
delimiter = (options[:quoted] || options[:local]) ? ['"', '"'] : ['<', '>']
|
52
|
+
|
53
|
+
raw "#include #{delimiter.first}#{path}#{delimiter.last}\n", true
|
54
|
+
end
|
55
|
+
|
56
|
+
def typedef (from, to)
|
57
|
+
raw "typedef #{from} #{to};"
|
58
|
+
end
|
59
|
+
|
60
|
+
def function(code, signature = nil)
|
61
|
+
parsed = parse_signature(code)
|
62
|
+
|
63
|
+
if signature
|
64
|
+
parsed[:arguments] = signature[:arguments] if signature[:arguments]
|
65
|
+
parsed[:return] = signature[:return] if signature[:return]
|
66
|
+
end
|
67
|
+
|
68
|
+
@signatures << parsed
|
69
|
+
|
70
|
+
raw code
|
71
|
+
end; alias c function
|
72
|
+
|
73
|
+
def struct(ffi_struct)
|
74
|
+
raw %{
|
75
|
+
typedef struct {#{
|
76
|
+
ffi_struct.layout.fields.map {|field|
|
77
|
+
"#{field} #{field.name};"
|
78
|
+
}.join("\n")
|
79
|
+
}} #{ffi_struct.class.name}
|
80
|
+
}, true
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_ffi_type(type, mod = nil)
|
84
|
+
raise ArgumentError, 'type is nil' if type.nil?
|
85
|
+
|
86
|
+
if type.is_a?(Symbol) || type.is_a?(FFI::Type) || (type.is_a?(Class) && type.ancestors.include?(FFI::Struct))
|
87
|
+
type
|
88
|
+
elsif @types[type]
|
89
|
+
@types[type]
|
90
|
+
elsif type.to_s.include? ?*
|
91
|
+
:pointer
|
92
|
+
elsif ((mod || FFI).find_type(type.to_sym) rescue false)
|
93
|
+
type.to_sym
|
94
|
+
else
|
95
|
+
raise "type #{type} not supported"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def shared_object
|
100
|
+
@compiler.compile(@code, @libraries)
|
101
|
+
end
|
102
|
+
|
103
|
+
def signatures
|
104
|
+
@signatures
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
# Based on RubyInline code by Ryan Davis
|
110
|
+
# Copyright (c) 2001-2007 Ryan Davis, Zen Spider Software
|
111
|
+
def strip_comments(code)
|
112
|
+
code.gsub(%r(\s*/\*.*?\*/)m, '').
|
113
|
+
gsub(%r(^\s*//.*?\n), '').
|
114
|
+
gsub(%r([ \t]*//[^\n]*), '')
|
115
|
+
end
|
116
|
+
|
117
|
+
# Based on RubyInline code by Ryan Davis
|
118
|
+
# Copyright (c) 2001-2007 Ryan Davis, Zen Spider Software
|
119
|
+
def parse_signature(code)
|
120
|
+
sig = strip_comments(code)
|
121
|
+
|
122
|
+
sig.gsub!(/^\s*\#.*(\\\n.*)*/, '') # strip preprocessor directives
|
123
|
+
sig.gsub!(/\s*\{.*/m, '') # strip function body
|
124
|
+
sig.gsub!(/\s+/, ' ') # clean and collapse whitespace
|
125
|
+
sig.gsub!(/\s*\*\s*/, ' * ') # clean pointers
|
126
|
+
sig.gsub!(/\s*const\s*/, '') # remove const
|
127
|
+
sig.strip!
|
128
|
+
|
129
|
+
whole, return_type, function_name, arg_string = sig.match(/(.*?(?:\ \*)?)\s*(\w+)\s*\(([^)]*)\)/).to_a
|
130
|
+
|
131
|
+
raise SyntaxError, "cannot parse signature: #{sig}" unless whole
|
132
|
+
|
133
|
+
args = arg_string.split(',').map {|arg|
|
134
|
+
# helps normalize into 'char * varname' form
|
135
|
+
arg = arg.gsub(/\s*\*\s*/, ' * ').strip
|
136
|
+
|
137
|
+
whole, type = arg.gsub(/\s*\*\s*/, ' * ').strip.match(/(((.*?(?:\ \*)?)\s*\*?)+)\s+(\w+)\s*$/).to_a
|
138
|
+
|
139
|
+
type
|
140
|
+
}
|
141
|
+
|
142
|
+
Signature.new(return_type, function_name, args, args.empty? ? -1 : args.length)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
end; end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'ffi/inliner/builders/c'
|
2
|
+
require 'ffi/inliner/compilers/gxx'
|
3
|
+
|
4
|
+
module FFI; module Inliner
|
5
|
+
|
6
|
+
Builder.define Builder[:c], :cplusplus, :cxx, :cpp, 'c++' do
|
7
|
+
def initialize(code = "", options = {})
|
8
|
+
super(code, options) rescue nil
|
9
|
+
|
10
|
+
use_compiler options[:use_compiler] || options[:compiler] || :gxx
|
11
|
+
end
|
12
|
+
|
13
|
+
def function(code, signature = nil)
|
14
|
+
parsed = parse_signature(code)
|
15
|
+
|
16
|
+
if signature
|
17
|
+
parsed[:arguments] = signature[:arguments] if signature[:arguments]
|
18
|
+
parsed[:return] = signature[:return] if signature[:return]
|
19
|
+
end
|
20
|
+
|
21
|
+
@signatures << parsed
|
22
|
+
|
23
|
+
raw 'extern "C" {' << code << '}' << "\n"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end; end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module FFI; module Inliner
|
2
|
+
|
3
|
+
Signature = ::Struct.new(:return, :name, :arguments, :arity)
|
4
|
+
|
5
|
+
class Builder
|
6
|
+
@builders = []
|
7
|
+
|
8
|
+
def self.[](name)
|
9
|
+
return name if name.is_a?(Builder)
|
10
|
+
|
11
|
+
@builders.find {|builder|
|
12
|
+
builder.name.downcase == name.downcase ||
|
13
|
+
builder.aliases.any? { |ali| ali.downcase == name.downcase }
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.define (name, *aliases, &block)
|
18
|
+
inherit_from = self
|
19
|
+
|
20
|
+
if name.is_a?(Builder)
|
21
|
+
name = name.class
|
22
|
+
end
|
23
|
+
|
24
|
+
if name.is_a?(Class)
|
25
|
+
inherit_from = name
|
26
|
+
name = aliases.shift
|
27
|
+
end
|
28
|
+
|
29
|
+
@builders << Class.new(inherit_from, &block).tap {|k|
|
30
|
+
k.instance_eval {
|
31
|
+
define_singleton_method :name do name end
|
32
|
+
define_singleton_method :aliases do aliases end
|
33
|
+
}
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
attr_reader :code, :compiler
|
38
|
+
|
39
|
+
def initialize(code = "")
|
40
|
+
@code = code
|
41
|
+
@evals = []
|
42
|
+
end
|
43
|
+
|
44
|
+
def use_compiler(compiler)
|
45
|
+
@compiler = Compiler[compiler]
|
46
|
+
end
|
47
|
+
|
48
|
+
def raw(code)
|
49
|
+
@code << code
|
50
|
+
end
|
51
|
+
|
52
|
+
def eval(&block)
|
53
|
+
@evals << block
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_ffi_type(type)
|
57
|
+
raise 'the Builder has not been specialized'
|
58
|
+
end
|
59
|
+
|
60
|
+
def shared_object
|
61
|
+
raise 'the Builder has not been specialized'
|
62
|
+
end
|
63
|
+
|
64
|
+
def signatures
|
65
|
+
raise 'the Builder has not been specialized'
|
66
|
+
end
|
67
|
+
|
68
|
+
def symbols
|
69
|
+
signatures.map { |s| s.name.to_sym }
|
70
|
+
end
|
71
|
+
|
72
|
+
def build
|
73
|
+
builder = self
|
74
|
+
blocks = @evals
|
75
|
+
|
76
|
+
mod = Module.new
|
77
|
+
mod.instance_eval {
|
78
|
+
extend FFI::Library
|
79
|
+
|
80
|
+
ffi_lib builder.shared_object
|
81
|
+
|
82
|
+
blocks.each { |block| instance_eval &block }
|
83
|
+
|
84
|
+
builder.signatures.each {|s|
|
85
|
+
attach_function s.name, s.arguments.compact.map {|a|
|
86
|
+
builder.to_ffi_type(a, self)
|
87
|
+
}, builder.to_ffi_type(s.return, self)
|
88
|
+
}
|
89
|
+
}
|
90
|
+
|
91
|
+
mod
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
end; end
|
96
|
+
|
97
|
+
require 'ffi/inliner/builders/c'
|
98
|
+
require 'ffi/inliner/builders/cpp'
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module FFI; module Inliner
|
2
|
+
|
3
|
+
Compiler.define :gcc do
|
4
|
+
def exists?
|
5
|
+
`gcc -v 2>&1'`; $?.success?
|
6
|
+
end
|
7
|
+
|
8
|
+
def compile (code, libraries = [])
|
9
|
+
@code = code
|
10
|
+
@libraries = libraries
|
11
|
+
|
12
|
+
return output if File.exists?(output)
|
13
|
+
|
14
|
+
unless system(if RbConfig::CONFIG['target_os'] =~ /mswin|mingw/
|
15
|
+
"sh -c '#{ldshared} -o #{output.shellescape} #{input.shellescape} #{libs}' 2>#{log.shellescape}"
|
16
|
+
else
|
17
|
+
"#{ldshared} -o #{output.shellescape} #{input.shellescape} #{libs} 2>#{log.shellescape}"
|
18
|
+
end)
|
19
|
+
raise CompilationError.new(log)
|
20
|
+
end
|
21
|
+
|
22
|
+
output
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def digest
|
27
|
+
Digest::SHA1.hexdigest(@code + @libraries.to_s + @options.to_s)
|
28
|
+
end
|
29
|
+
|
30
|
+
def input
|
31
|
+
File.join(Inliner.directory, "#{digest}.c").tap {|path|
|
32
|
+
File.open(path, 'w') { |f| f.write(@code) } unless File.exists?(path)
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def output
|
37
|
+
File.join(Inliner.directory, "#{digest}.#{Compiler::Extension}")
|
38
|
+
end
|
39
|
+
|
40
|
+
def log
|
41
|
+
File.join(Inliner.directory, "#{digest}.log")
|
42
|
+
end
|
43
|
+
|
44
|
+
def ldshared
|
45
|
+
if RbConfig::CONFIG['target_os'] =~ /darwin/
|
46
|
+
"gcc -dynamic -bundle -fPIC #{options}"
|
47
|
+
else
|
48
|
+
"gcc -shared -fPIC #{options}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def libs
|
53
|
+
@libraries.map { |lib| "-l#{lib}".shellescape }.join(' ')
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end; end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module FFI; module Inliner
|
2
|
+
|
3
|
+
Compiler.define Compiler[:gcc], :gxx, 'g++' do
|
4
|
+
def exists?
|
5
|
+
`g++ -v 2>&1'`; $?.success?
|
6
|
+
end
|
7
|
+
|
8
|
+
def input
|
9
|
+
File.join(Inliner.directory, "#{digest}.cpp").tap {|path|
|
10
|
+
File.open(path, 'w') { |f| f.write(@code) } unless File.exists?(path)
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
def ldshared
|
15
|
+
if RbConfig::CONFIG['target_os'] =~ /darwin/
|
16
|
+
"g++ -dynamic -bundle -fPIC #{options}"
|
17
|
+
else
|
18
|
+
"g++ -shared -fPIC #{options}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end; end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module FFI; module Inliner
|
2
|
+
|
3
|
+
Compiler.define :tcc do
|
4
|
+
def exists?
|
5
|
+
`tcc -v 2>&1'`; $?.success?
|
6
|
+
end
|
7
|
+
|
8
|
+
def compile (code, libraries = [])
|
9
|
+
@code = code
|
10
|
+
@libraries = libraries
|
11
|
+
|
12
|
+
return output if File.exists?(output)
|
13
|
+
|
14
|
+
unless system(if RbConfig::CONFIG['target_os'] =~ /mswin|mingw/
|
15
|
+
"sh -c '#{ldshared} #{libs} -o #{output.shellescape} #{input.shellescape}' 2>#{log.shellescape}"
|
16
|
+
else
|
17
|
+
"#{ldshared} #{libs} -o #{output.shellescape} #{input.shellescape} 2>#{log.shellescape}"
|
18
|
+
end)
|
19
|
+
raise CompilationError.new(log)
|
20
|
+
end
|
21
|
+
|
22
|
+
output
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def digest
|
27
|
+
Digest::SHA1.hexdigest(@code + @libraries.to_s + @options.to_s)
|
28
|
+
end
|
29
|
+
|
30
|
+
def input
|
31
|
+
File.join(Inliner.directory, "#{digest}.c").tap {|path|
|
32
|
+
File.open(path, 'w') { |f| f.write(@code) } unless File.exists?(path)
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def output
|
37
|
+
File.join(Inliner.directory, "#{digest}.#{Compiler::Extension}")
|
38
|
+
end
|
39
|
+
|
40
|
+
def log
|
41
|
+
File.join(Inliner.directory, "#{digest}.log")
|
42
|
+
end
|
43
|
+
|
44
|
+
def ldshared
|
45
|
+
if RbConfig::CONFIG['target_os'] =~ /mswin|mingw/
|
46
|
+
"tcc -rdynamic -shared -fPIC #{options}"
|
47
|
+
else
|
48
|
+
"tcc -shared #{options}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def libs
|
53
|
+
@libraries.map { |lib| "-l#{lib}".shellescape }.join(' ')
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end; end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module FFI; module Inliner
|
2
|
+
|
3
|
+
class Compiler
|
4
|
+
Extension = case RbConfig::CONFIG['target_os']
|
5
|
+
when /darwin/ then 'dylib'
|
6
|
+
when /mswin|mingw/ then 'dll'
|
7
|
+
else 'so'
|
8
|
+
end
|
9
|
+
|
10
|
+
@compilers = []
|
11
|
+
|
12
|
+
def self.[] (name)
|
13
|
+
return name if name.is_a?(Compiler)
|
14
|
+
|
15
|
+
@compilers.find {|compiler|
|
16
|
+
compiler.name.downcase == name.downcase ||
|
17
|
+
compiler.aliases.any? { |ali| ali.downcase == name.downcase }
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.define (name, *aliases, &block)
|
22
|
+
inherit_from = self
|
23
|
+
|
24
|
+
if name.is_a?(Compiler)
|
25
|
+
name = name.class
|
26
|
+
end
|
27
|
+
|
28
|
+
if name.is_a?(Class)
|
29
|
+
inherit_from = name
|
30
|
+
name = aliases.shift
|
31
|
+
end
|
32
|
+
|
33
|
+
@compilers << Class.new(inherit_from, &block).new(name, *aliases)
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_reader :name, :aliases
|
37
|
+
attr_accessor :options
|
38
|
+
|
39
|
+
def initialize(name, *aliases)
|
40
|
+
@name = name
|
41
|
+
@aliases = aliases
|
42
|
+
end
|
43
|
+
|
44
|
+
def exists?
|
45
|
+
raise 'the Compiler has not been specialized'
|
46
|
+
end
|
47
|
+
|
48
|
+
def compile
|
49
|
+
raise 'the Compiler has not been specialized'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end; end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'ffi/inliner/error'
|
2
|
+
|
3
|
+
module FFI
|
4
|
+
|
5
|
+
module Inliner
|
6
|
+
def self.directory
|
7
|
+
if ENV['FFI_INLINER_PATH'] && !ENV['FFI_INLINER_PATH'].empty?
|
8
|
+
@directory = ENV['FFI_INLINER_PATH']
|
9
|
+
else
|
10
|
+
require 'tmpdir'
|
11
|
+
@directory ||= File.expand_path(File.join(Dir.tmpdir, ".ffi-inliner-#{Process.uid}"))
|
12
|
+
end
|
13
|
+
|
14
|
+
if File.exists?(@directory) && !File.directory?(@directory)
|
15
|
+
raise 'the FFI_INLINER_PATH exists and is not a directory'
|
16
|
+
end
|
17
|
+
|
18
|
+
if !File.exists?(@directory)
|
19
|
+
FileUtils.mkdir(@directory)
|
20
|
+
end
|
21
|
+
|
22
|
+
@directory
|
23
|
+
end
|
24
|
+
|
25
|
+
def inline(*args, &block)
|
26
|
+
if self.class == Class
|
27
|
+
instance_inline(*args, &block)
|
28
|
+
else
|
29
|
+
singleton_inline(*args, &block)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def singleton_inline(*args)
|
34
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
35
|
+
|
36
|
+
language, code = if args.length == 2
|
37
|
+
args
|
38
|
+
else
|
39
|
+
block_given? ? [args.shift || :c, ''] : [:c, args.shift || '']
|
40
|
+
end
|
41
|
+
|
42
|
+
builder = Builder[language].new(code, options)
|
43
|
+
yield builder if block_given?
|
44
|
+
mod = builder.build
|
45
|
+
|
46
|
+
builder.symbols.each {|sym|
|
47
|
+
define_singleton_method sym, &mod.method(sym)
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
def instance_inline(*args)
|
52
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
53
|
+
|
54
|
+
language, code = if args.length == 2
|
55
|
+
args
|
56
|
+
else
|
57
|
+
block_given? ? [args.shift || :c, ''] : [:c, args.shift || '']
|
58
|
+
end
|
59
|
+
|
60
|
+
builder = Builder[language].new(code, options)
|
61
|
+
yield builder if block_given?
|
62
|
+
mod = builder.build
|
63
|
+
|
64
|
+
builder.symbols.each {|sym|
|
65
|
+
define_method sym, &mod.method(sym)
|
66
|
+
}
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
require 'ffi/inliner/compilers'
|
73
|
+
require 'ffi/inliner/builders'
|
data/lib/ffi/inliner.rb
ADDED
@@ -0,0 +1,273 @@
|
|
1
|
+
require 'ffi/inliner'
|
2
|
+
|
3
|
+
describe FFI::Inliner do
|
4
|
+
before do
|
5
|
+
module Foo
|
6
|
+
extend FFI::Inliner
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'should extend the module with inline methods' do
|
11
|
+
module Foo
|
12
|
+
inline %{
|
13
|
+
long factorial (int max) {
|
14
|
+
int i = max, result = 1;
|
15
|
+
|
16
|
+
while (i >= 2) {
|
17
|
+
result *= i--;
|
18
|
+
}
|
19
|
+
|
20
|
+
return result;
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
inline 'int simple_math() { return 1 + 1; }'
|
25
|
+
end
|
26
|
+
|
27
|
+
Foo.factorial(4).should == 24
|
28
|
+
Foo.simple_math.should == 2
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should correctly parse function signature' do
|
32
|
+
module Foo
|
33
|
+
inline %{
|
34
|
+
void* func_1 (void* ptr, unsigned int i, unsigned long l, char *c) {
|
35
|
+
return ptr;
|
36
|
+
}
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
ptr = FFI::MemoryPointer.new(:int)
|
41
|
+
Foo.func_1(ptr, 0xff, 0xffff, FFI::MemoryPointer.from_string('c')).should == ptr
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should load cached libraries' do
|
45
|
+
File.should_receive(:open).once
|
46
|
+
|
47
|
+
module Foo
|
48
|
+
inline "void* cached_func() {}"
|
49
|
+
end
|
50
|
+
|
51
|
+
module Foo
|
52
|
+
inline "void* cached_func() {}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'should recompile if the code is updated' do
|
57
|
+
module Foo
|
58
|
+
inline "int updated_func() { return 1 + 1; }"
|
59
|
+
end
|
60
|
+
|
61
|
+
Foo.updated_func.should == 2
|
62
|
+
|
63
|
+
module Foo
|
64
|
+
inline "int updated_func() { return 2 + 2; }"
|
65
|
+
end
|
66
|
+
|
67
|
+
Foo.updated_func.should == 4
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'should recompile if the code is changed after a failure' do
|
71
|
+
# unfortunately this doesn't check the real functionality, which is that if a dll is deleted, it isn't re-produced
|
72
|
+
begin
|
73
|
+
module Foo
|
74
|
+
inline "int updated_func2() { asdf }"
|
75
|
+
end
|
76
|
+
rescue
|
77
|
+
begin
|
78
|
+
Foo.updated_func2
|
79
|
+
raise 'should have failed'
|
80
|
+
rescue NoMethodError
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
module Foo
|
85
|
+
inline "int updated_func2() { return 2 + 2; }"
|
86
|
+
end
|
87
|
+
|
88
|
+
Foo.updated_func2.should == 4
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'should be configured using the block form' do
|
92
|
+
module Foo
|
93
|
+
inline do |builder|
|
94
|
+
builder.function %{
|
95
|
+
int func_1 () {
|
96
|
+
return 0;
|
97
|
+
}
|
98
|
+
}
|
99
|
+
|
100
|
+
builder.function %{
|
101
|
+
int func_2 () {
|
102
|
+
return 1;
|
103
|
+
}
|
104
|
+
}
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
Foo.func_1.should == 0
|
109
|
+
Foo.func_2.should == 1
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'should allow users to add type maps' do
|
113
|
+
class MyStruct < FFI::Struct
|
114
|
+
layout :dummy, :int
|
115
|
+
end
|
116
|
+
module Foo
|
117
|
+
inline do |builder|
|
118
|
+
builder.map 'my_struct_t *' => 'pointer'
|
119
|
+
|
120
|
+
builder.raw %q{
|
121
|
+
typedef struct {
|
122
|
+
int dummy;
|
123
|
+
} my_struct_t;
|
124
|
+
}
|
125
|
+
|
126
|
+
builder.function 'my_struct_t* use_my_struct (my_struct_t* my_struct) { return my_struct; }'
|
127
|
+
end
|
128
|
+
end
|
129
|
+
my_struct = MyStruct.new
|
130
|
+
Foo.use_my_struct(my_struct).should == my_struct.to_ptr
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'should allow users to include header files' do
|
134
|
+
module Foo
|
135
|
+
inline do |builder|
|
136
|
+
builder.include "stdio.h"
|
137
|
+
builder.include "local_header.h", :quoted => true
|
138
|
+
builder.code.should == "#include <stdio.h>\n#include \"local_header.h\"\n"
|
139
|
+
builder.stub!(:build)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'should allow users to add external libraries' do
|
145
|
+
module Foo
|
146
|
+
inline do |builder|
|
147
|
+
builder.libraries 'foolib1', 'foolib2'
|
148
|
+
builder.stub!(:build)
|
149
|
+
builder.stub!(:symbols) { [] }
|
150
|
+
builder.libraries.should == ['foolib1', 'foolib2']
|
151
|
+
end
|
152
|
+
|
153
|
+
inline "int func() { return 0; }", :libraries => ['foolib1', 'foolib2'] do |builder|
|
154
|
+
builder.stub!(:build)
|
155
|
+
builder.stub!(:symbols) { [] }
|
156
|
+
builder.libraries.should == ['foolib1', 'foolib2']
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
if RbConfig::CONFIG['target_os'] =~ /mswin|mingw/
|
162
|
+
it "should put library links at the end in mingw" do
|
163
|
+
module Foo
|
164
|
+
|
165
|
+
code = <<-CODE
|
166
|
+
#include <windows.h>
|
167
|
+
#include <mmsystem.h>
|
168
|
+
int go() {
|
169
|
+
mixerOpen(0, 0,0,0,0);
|
170
|
+
return 3;
|
171
|
+
}
|
172
|
+
CODE
|
173
|
+
|
174
|
+
inline do |builder|
|
175
|
+
builder.library 'Winmm'
|
176
|
+
builder.raw code
|
177
|
+
end
|
178
|
+
|
179
|
+
inline do |builder|
|
180
|
+
builder.use_compiler :gxx
|
181
|
+
builder.library 'Winmm'
|
182
|
+
builder.raw code
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
it 'should generate C struct from FFI::Struct' do
|
189
|
+
pending do
|
190
|
+
class MyStruct < FFI::Struct
|
191
|
+
layout :a, :int, \
|
192
|
+
:b, :char,
|
193
|
+
:c, :pointer
|
194
|
+
end
|
195
|
+
module Foo
|
196
|
+
extend FFI::Inliner
|
197
|
+
inline do |builder|
|
198
|
+
builder.struct MyStruct
|
199
|
+
builder.code.should == <<EOC
|
200
|
+
typedef struct
|
201
|
+
{
|
202
|
+
int a;
|
203
|
+
char b;
|
204
|
+
void* c;
|
205
|
+
} my_struct_t;
|
206
|
+
|
207
|
+
EOC
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
it 'should return the current compiler' do
|
214
|
+
module Foo
|
215
|
+
inline do |builder|
|
216
|
+
builder.compiler.should == FFI::Inliner::Compiler[:gcc]
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
it 'should raise errors' do
|
222
|
+
proc {
|
223
|
+
module Foo
|
224
|
+
inline "int boom("
|
225
|
+
end
|
226
|
+
}.should raise_error(/cannot parse/)
|
227
|
+
|
228
|
+
proc {
|
229
|
+
module Foo
|
230
|
+
inline 'int boom() { printf "Hello" }'
|
231
|
+
end
|
232
|
+
}.should raise_error(/compile error/)
|
233
|
+
end
|
234
|
+
|
235
|
+
describe 'GXX compiler' do
|
236
|
+
it 'should compile and link a shim C library that encapsulates C++ code' do
|
237
|
+
module Foo
|
238
|
+
inline :cpp do |builder|
|
239
|
+
builder.raw %{
|
240
|
+
#include <iostream>
|
241
|
+
#include <string>
|
242
|
+
|
243
|
+
using namespace std;
|
244
|
+
|
245
|
+
class Greeter
|
246
|
+
{
|
247
|
+
public:
|
248
|
+
Greeter();
|
249
|
+
string say_hello();
|
250
|
+
};
|
251
|
+
|
252
|
+
Greeter::Greeter () { };
|
253
|
+
string Greeter::say_hello ()
|
254
|
+
{
|
255
|
+
return "Hello foos!";
|
256
|
+
};
|
257
|
+
}
|
258
|
+
|
259
|
+
builder.function %{
|
260
|
+
const char* say_hello () {
|
261
|
+
Greeter greeter;
|
262
|
+
|
263
|
+
return greeter.say_hello().c_str();
|
264
|
+
}
|
265
|
+
}, return: :string
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
Foo.say_hello.should == 'Hello foos!'
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
metadata
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ffi-inline
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- meh.
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-15 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: ffi
|
16
|
+
requirement: &10074040 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.4.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *10074040
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rake
|
27
|
+
requirement: &10073120 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *10073120
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rspec
|
38
|
+
requirement: &10072380 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *10072380
|
47
|
+
description:
|
48
|
+
email: meh@paranoici.org
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- Rakefile
|
54
|
+
- examples/ex_1.rb
|
55
|
+
- ffi-inline.gemspec
|
56
|
+
- lib/ffi/inliner.rb
|
57
|
+
- lib/ffi/inliner/builders.rb
|
58
|
+
- lib/ffi/inliner/builders/c.rb
|
59
|
+
- lib/ffi/inliner/builders/cpp.rb
|
60
|
+
- lib/ffi/inliner/compilers.rb
|
61
|
+
- lib/ffi/inliner/compilers/gcc.rb
|
62
|
+
- lib/ffi/inliner/compilers/gxx.rb
|
63
|
+
- lib/ffi/inliner/compilers/tcc.rb
|
64
|
+
- lib/ffi/inliner/error.rb
|
65
|
+
- lib/ffi/inliner/inliner.rb
|
66
|
+
- lib/ffi/inliner/version.rb
|
67
|
+
- spec/inliner_spec.rb
|
68
|
+
homepage: http://github.com/meh/ruby-ffi-inline
|
69
|
+
licenses: []
|
70
|
+
post_install_message:
|
71
|
+
rdoc_options: []
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ! '>='
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '0'
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
requirements: []
|
87
|
+
rubyforge_project:
|
88
|
+
rubygems_version: 1.8.15
|
89
|
+
signing_key:
|
90
|
+
specification_version: 3
|
91
|
+
summary: Inline C/C++ in Ruby easily and cleanly.
|
92
|
+
test_files:
|
93
|
+
- spec/inliner_spec.rb
|