fiddle 1.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: false
2
+ module Fiddle
3
+ class Closure
4
+
5
+ # the C type of the return of the FFI closure
6
+ attr_reader :ctype
7
+
8
+ # arguments of the FFI closure
9
+ attr_reader :args
10
+
11
+ # Extends Fiddle::Closure to allow for building the closure in a block
12
+ class BlockCaller < Fiddle::Closure
13
+
14
+ # == Description
15
+ #
16
+ # Construct a new BlockCaller object.
17
+ #
18
+ # * +ctype+ is the C type to be returned
19
+ # * +args+ are passed the callback
20
+ # * +abi+ is the abi of the closure
21
+ #
22
+ # If there is an error in preparing the +ffi_cif+ or +ffi_prep_closure+,
23
+ # then a RuntimeError will be raised.
24
+ #
25
+ # == Example
26
+ #
27
+ # include Fiddle
28
+ #
29
+ # cb = Closure::BlockCaller.new(TYPE_INT, [TYPE_INT]) do |one|
30
+ # one
31
+ # end
32
+ #
33
+ # func = Function.new(cb, [TYPE_INT], TYPE_INT)
34
+ #
35
+ def initialize ctype, args, abi = Fiddle::Function::DEFAULT, &block
36
+ super(ctype, args, abi)
37
+ @block = block
38
+ end
39
+
40
+ # Calls the constructed BlockCaller, with +args+
41
+ #
42
+ # For an example see Fiddle::Closure::BlockCaller.new
43
+ #
44
+ def call *args
45
+ @block.call(*args)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,194 @@
1
+ # frozen_string_literal: false
2
+ module Fiddle
3
+ # A mixin that provides methods for parsing C struct and prototype signatures.
4
+ #
5
+ # == Example
6
+ # require 'fiddle/import'
7
+ #
8
+ # include Fiddle::CParser
9
+ # #=> Object
10
+ #
11
+ # parse_ctype('int')
12
+ # #=> Fiddle::TYPE_INT
13
+ #
14
+ # parse_struct_signature(['int i', 'char c'])
15
+ # #=> [[Fiddle::TYPE_INT, Fiddle::TYPE_CHAR], ["i", "c"]]
16
+ #
17
+ # parse_signature('double sum(double, double)')
18
+ # #=> ["sum", Fiddle::TYPE_DOUBLE, [Fiddle::TYPE_DOUBLE, Fiddle::TYPE_DOUBLE]]
19
+ #
20
+ module CParser
21
+ # Parses a C struct's members
22
+ #
23
+ # Example:
24
+ #
25
+ # include Fiddle::CParser
26
+ # #=> Object
27
+ #
28
+ # parse_struct_signature(['int i', 'char c'])
29
+ # #=> [[Fiddle::TYPE_INT, Fiddle::TYPE_CHAR], ["i", "c"]]
30
+ #
31
+ # parse_struct_signature(['char buffer[80]'])
32
+ # #=> [[[Fiddle::TYPE_CHAR, 80]], ["buffer"]]
33
+ #
34
+ def parse_struct_signature(signature, tymap=nil)
35
+ if signature.is_a?(String)
36
+ signature = split_arguments(signature, /[,;]/)
37
+ end
38
+ mems = []
39
+ tys = []
40
+ signature.each{|msig|
41
+ msig = compact(msig)
42
+ case msig
43
+ when /^[\w\*\s]+[\*\s](\w+)$/
44
+ mems.push($1)
45
+ tys.push(parse_ctype(msig, tymap))
46
+ when /^[\w\*\s]+\(\*(\w+)\)\(.*?\)$/
47
+ mems.push($1)
48
+ tys.push(parse_ctype(msig, tymap))
49
+ when /^([\w\*\s]+[\*\s])(\w+)\[(\d+)\]$/
50
+ mems.push($2)
51
+ tys.push([parse_ctype($1.strip, tymap), $3.to_i])
52
+ when /^([\w\*\s]+)\[(\d+)\](\w+)$/
53
+ mems.push($3)
54
+ tys.push([parse_ctype($1.strip, tymap), $2.to_i])
55
+ else
56
+ raise(RuntimeError,"can't parse the struct member: #{msig}")
57
+ end
58
+ }
59
+ return tys, mems
60
+ end
61
+
62
+ # Parses a C prototype signature
63
+ #
64
+ # If Hash +tymap+ is provided, the return value and the arguments from the
65
+ # +signature+ are expected to be keys, and the value will be the C type to
66
+ # be looked up.
67
+ #
68
+ # Example:
69
+ #
70
+ # include Fiddle::CParser
71
+ # #=> Object
72
+ #
73
+ # parse_signature('double sum(double, double)')
74
+ # #=> ["sum", Fiddle::TYPE_DOUBLE, [Fiddle::TYPE_DOUBLE, Fiddle::TYPE_DOUBLE]]
75
+ #
76
+ # parse_signature('void update(void (*cb)(int code))')
77
+ # #=> ["update", Fiddle::TYPE_VOID, [Fiddle::TYPE_VOIDP]]
78
+ #
79
+ # parse_signature('char (*getbuffer(void))[80]')
80
+ # #=> ["getbuffer", Fiddle::TYPE_VOIDP, []]
81
+ #
82
+ def parse_signature(signature, tymap=nil)
83
+ tymap ||= {}
84
+ case compact(signature)
85
+ when /^(?:[\w\*\s]+)\(\*(\w+)\((.*?)\)\)(?:\[\w*\]|\(.*?\));?$/
86
+ func, args = $1, $2
87
+ return [func, TYPE_VOIDP, split_arguments(args).collect {|arg| parse_ctype(arg, tymap)}]
88
+ when /^([\w\*\s]+[\*\s])(\w+)\((.*?)\);?$/
89
+ ret, func, args = $1.strip, $2, $3
90
+ return [func, parse_ctype(ret, tymap), split_arguments(args).collect {|arg| parse_ctype(arg, tymap)}]
91
+ else
92
+ raise(RuntimeError,"can't parse the function prototype: #{signature}")
93
+ end
94
+ end
95
+
96
+ # Given a String of C type +ty+, returns the corresponding Fiddle constant.
97
+ #
98
+ # +ty+ can also accept an Array of C type Strings, and will be returned in
99
+ # a corresponding Array.
100
+ #
101
+ # If Hash +tymap+ is provided, +ty+ is expected to be the key, and the
102
+ # value will be the C type to be looked up.
103
+ #
104
+ # Example:
105
+ #
106
+ # include Fiddle::CParser
107
+ # #=> Object
108
+ #
109
+ # parse_ctype('int')
110
+ # #=> Fiddle::TYPE_INT
111
+ #
112
+ # parse_ctype('double diff')
113
+ # #=> Fiddle::TYPE_DOUBLE
114
+ #
115
+ # parse_ctype('unsigned char byte')
116
+ # #=> -Fiddle::TYPE_CHAR
117
+ #
118
+ # parse_ctype('const char* const argv[]')
119
+ # #=> -Fiddle::TYPE_VOIDP
120
+ #
121
+ def parse_ctype(ty, tymap=nil)
122
+ tymap ||= {}
123
+ case ty
124
+ when Array
125
+ return [parse_ctype(ty[0], tymap), ty[1]]
126
+ when 'void'
127
+ return TYPE_VOID
128
+ when /^(?:(?:signed\s+)?long\s+long(?:\s+int\s+)?|int64_t)(?:\s+\w+)?$/
129
+ if( defined?(TYPE_LONG_LONG) )
130
+ return TYPE_LONG_LONG
131
+ else
132
+ raise(RuntimeError, "unsupported type: #{ty}")
133
+ end
134
+ when /^(?:unsigned\s+long\s+long(?:\s+int\s+)?|uint64_t)(?:\s+\w+)?$/
135
+ if( defined?(TYPE_LONG_LONG) )
136
+ return -TYPE_LONG_LONG
137
+ else
138
+ raise(RuntimeError, "unsupported type: #{ty}")
139
+ end
140
+ when /^(?:signed\s+)?long(?:\s+int\s+)?(?:\s+\w+)?$/
141
+ return TYPE_LONG
142
+ when /^unsigned\s+long(?:\s+int\s+)?(?:\s+\w+)?$/
143
+ return -TYPE_LONG
144
+ when /^(?:signed\s+)?int(?:\s+\w+)?$/
145
+ return TYPE_INT
146
+ when /^(?:unsigned\s+int|uint)(?:\s+\w+)?$/
147
+ return -TYPE_INT
148
+ when /^(?:signed\s+)?short(?:\s+int\s+)?(?:\s+\w+)?$/
149
+ return TYPE_SHORT
150
+ when /^unsigned\s+short(?:\s+int\s+)?(?:\s+\w+)?$/
151
+ return -TYPE_SHORT
152
+ when /^(?:signed\s+)?char(?:\s+\w+)?$/
153
+ return TYPE_CHAR
154
+ when /^unsigned\s+char(?:\s+\w+)?$/
155
+ return -TYPE_CHAR
156
+ when /^float(?:\s+\w+)?$/
157
+ return TYPE_FLOAT
158
+ when /^double(?:\s+\w+)?$/
159
+ return TYPE_DOUBLE
160
+ when /^size_t(?:\s+\w+)?$/
161
+ return TYPE_SIZE_T
162
+ when /^ssize_t(?:\s+\w+)?$/
163
+ return TYPE_SSIZE_T
164
+ when /^ptrdiff_t(?:\s+\w+)?$/
165
+ return TYPE_PTRDIFF_T
166
+ when /^intptr_t(?:\s+\w+)?$/
167
+ return TYPE_INTPTR_T
168
+ when /^uintptr_t(?:\s+\w+)?$/
169
+ return TYPE_UINTPTR_T
170
+ when /\*/, /\[[\s\d]*\]/
171
+ return TYPE_VOIDP
172
+ else
173
+ ty = ty.split(' ', 2)[0]
174
+ if( tymap[ty] )
175
+ return parse_ctype(tymap[ty], tymap)
176
+ else
177
+ raise(DLError, "unknown type: #{ty}")
178
+ end
179
+ end
180
+ end
181
+
182
+ private
183
+
184
+ def split_arguments(arguments, sep=',')
185
+ return [] if arguments.strip == 'void'
186
+ arguments.scan(/([\w\*\s]+\(\*\w*\)\(.*?\)|[\w\*\s\[\]]+)(?:#{sep}\s*|$)/).collect {|m| m[0]}
187
+ end
188
+
189
+ def compact(signature)
190
+ signature.gsub(/\s+/, ' ').gsub(/\s*([\(\)\[\]\*,;])\s*/, '\1').strip
191
+ end
192
+
193
+ end
194
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: false
2
+ module Fiddle
3
+ class Function
4
+ # The ABI of the Function.
5
+ attr_reader :abi
6
+
7
+ # The address of this function
8
+ attr_reader :ptr
9
+
10
+ # The name of this function
11
+ attr_reader :name
12
+
13
+ # The integer memory location of this function
14
+ def to_i
15
+ ptr.to_i
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,318 @@
1
+ # frozen_string_literal: false
2
+ require 'fiddle'
3
+ require 'fiddle/struct'
4
+ require 'fiddle/cparser'
5
+
6
+ module Fiddle
7
+
8
+ # Used internally by Fiddle::Importer
9
+ class CompositeHandler
10
+ # Create a new handler with the open +handlers+
11
+ #
12
+ # Used internally by Fiddle::Importer.dlload
13
+ def initialize(handlers)
14
+ @handlers = handlers
15
+ end
16
+
17
+ # Array of the currently loaded libraries.
18
+ def handlers()
19
+ @handlers
20
+ end
21
+
22
+ # Returns the address as an Integer from any handlers with the function
23
+ # named +symbol+.
24
+ #
25
+ # Raises a DLError if the handle is closed.
26
+ def sym(symbol)
27
+ @handlers.each{|handle|
28
+ if( handle )
29
+ begin
30
+ addr = handle.sym(symbol)
31
+ return addr
32
+ rescue DLError
33
+ end
34
+ end
35
+ }
36
+ return nil
37
+ end
38
+
39
+ # See Fiddle::CompositeHandler.sym
40
+ def [](symbol)
41
+ sym(symbol)
42
+ end
43
+ end
44
+
45
+ # A DSL that provides the means to dynamically load libraries and build
46
+ # modules around them including calling extern functions within the C
47
+ # library that has been loaded.
48
+ #
49
+ # == Example
50
+ #
51
+ # require 'fiddle'
52
+ # require 'fiddle/import'
53
+ #
54
+ # module LibSum
55
+ # extend Fiddle::Importer
56
+ # dlload './libsum.so'
57
+ # extern 'double sum(double*, int)'
58
+ # extern 'double split(double)'
59
+ # end
60
+ #
61
+ module Importer
62
+ include Fiddle
63
+ include CParser
64
+ extend Importer
65
+
66
+ attr_reader :type_alias
67
+ private :type_alias
68
+
69
+ # Creates an array of handlers for the given +libs+, can be an instance of
70
+ # Fiddle::Handle, Fiddle::Importer, or will create a new instance of
71
+ # Fiddle::Handle using Fiddle.dlopen
72
+ #
73
+ # Raises a DLError if the library cannot be loaded.
74
+ #
75
+ # See Fiddle.dlopen
76
+ def dlload(*libs)
77
+ handles = libs.collect{|lib|
78
+ case lib
79
+ when nil
80
+ nil
81
+ when Handle
82
+ lib
83
+ when Importer
84
+ lib.handlers
85
+ else
86
+ begin
87
+ Fiddle.dlopen(lib)
88
+ rescue DLError
89
+ raise(DLError, "can't load #{lib}")
90
+ end
91
+ end
92
+ }.flatten()
93
+ @handler = CompositeHandler.new(handles)
94
+ @func_map = {}
95
+ @type_alias = {}
96
+ end
97
+
98
+ # Sets the type alias for +alias_type+ as +orig_type+
99
+ def typealias(alias_type, orig_type)
100
+ @type_alias[alias_type] = orig_type
101
+ end
102
+
103
+ # Returns the sizeof +ty+, using Fiddle::Importer.parse_ctype to determine
104
+ # the C type and the appropriate Fiddle constant.
105
+ def sizeof(ty)
106
+ case ty
107
+ when String
108
+ ty = parse_ctype(ty, type_alias).abs()
109
+ case ty
110
+ when TYPE_CHAR
111
+ return SIZEOF_CHAR
112
+ when TYPE_SHORT
113
+ return SIZEOF_SHORT
114
+ when TYPE_INT
115
+ return SIZEOF_INT
116
+ when TYPE_LONG
117
+ return SIZEOF_LONG
118
+ when TYPE_LONG_LONG
119
+ return SIZEOF_LONG_LONG
120
+ when TYPE_FLOAT
121
+ return SIZEOF_FLOAT
122
+ when TYPE_DOUBLE
123
+ return SIZEOF_DOUBLE
124
+ when TYPE_VOIDP
125
+ return SIZEOF_VOIDP
126
+ else
127
+ raise(DLError, "unknown type: #{ty}")
128
+ end
129
+ when Class
130
+ if( ty.instance_methods().include?(:to_ptr) )
131
+ return ty.size()
132
+ end
133
+ end
134
+ return Pointer[ty].size()
135
+ end
136
+
137
+ def parse_bind_options(opts)
138
+ h = {}
139
+ while( opt = opts.shift() )
140
+ case opt
141
+ when :stdcall, :cdecl
142
+ h[:call_type] = opt
143
+ when :carried, :temp, :temporal, :bind
144
+ h[:callback_type] = opt
145
+ h[:carrier] = opts.shift()
146
+ else
147
+ h[opt] = true
148
+ end
149
+ end
150
+ h
151
+ end
152
+ private :parse_bind_options
153
+
154
+ # :stopdoc:
155
+ CALL_TYPE_TO_ABI = Hash.new { |h, k|
156
+ raise RuntimeError, "unsupported call type: #{k}"
157
+ }.merge({ :stdcall => (Function::STDCALL rescue Function::DEFAULT),
158
+ :cdecl => Function::DEFAULT,
159
+ nil => Function::DEFAULT
160
+ }).freeze
161
+ private_constant :CALL_TYPE_TO_ABI
162
+ # :startdoc:
163
+
164
+ # Creates a global method from the given C +signature+.
165
+ def extern(signature, *opts)
166
+ symname, ctype, argtype = parse_signature(signature, type_alias)
167
+ opt = parse_bind_options(opts)
168
+ f = import_function(symname, ctype, argtype, opt[:call_type])
169
+ name = symname.gsub(/@.+/,'')
170
+ @func_map[name] = f
171
+ # define_method(name){|*args,&block| f.call(*args,&block)}
172
+ begin
173
+ /^(.+?):(\d+)/ =~ caller.first
174
+ file, line = $1, $2.to_i
175
+ rescue
176
+ file, line = __FILE__, __LINE__+3
177
+ end
178
+ module_eval(<<-EOS, file, line)
179
+ def #{name}(*args, &block)
180
+ @func_map['#{name}'].call(*args,&block)
181
+ end
182
+ EOS
183
+ module_function(name)
184
+ f
185
+ end
186
+
187
+ # Creates a global method from the given C +signature+ using the given
188
+ # +opts+ as bind parameters with the given block.
189
+ def bind(signature, *opts, &blk)
190
+ name, ctype, argtype = parse_signature(signature, type_alias)
191
+ h = parse_bind_options(opts)
192
+ case h[:callback_type]
193
+ when :bind, nil
194
+ f = bind_function(name, ctype, argtype, h[:call_type], &blk)
195
+ else
196
+ raise(RuntimeError, "unknown callback type: #{h[:callback_type]}")
197
+ end
198
+ @func_map[name] = f
199
+ #define_method(name){|*args,&block| f.call(*args,&block)}
200
+ begin
201
+ /^(.+?):(\d+)/ =~ caller.first
202
+ file, line = $1, $2.to_i
203
+ rescue
204
+ file, line = __FILE__, __LINE__+3
205
+ end
206
+ module_eval(<<-EOS, file, line)
207
+ def #{name}(*args,&block)
208
+ @func_map['#{name}'].call(*args,&block)
209
+ end
210
+ EOS
211
+ module_function(name)
212
+ f
213
+ end
214
+
215
+ # Creates a class to wrap the C struct described by +signature+.
216
+ #
217
+ # MyStruct = struct ['int i', 'char c']
218
+ def struct(signature)
219
+ tys, mems = parse_struct_signature(signature, type_alias)
220
+ Fiddle::CStructBuilder.create(CStruct, tys, mems)
221
+ end
222
+
223
+ # Creates a class to wrap the C union described by +signature+.
224
+ #
225
+ # MyUnion = union ['int i', 'char c']
226
+ def union(signature)
227
+ tys, mems = parse_struct_signature(signature, type_alias)
228
+ Fiddle::CStructBuilder.create(CUnion, tys, mems)
229
+ end
230
+
231
+ # Returns the function mapped to +name+, that was created by either
232
+ # Fiddle::Importer.extern or Fiddle::Importer.bind
233
+ def [](name)
234
+ @func_map[name]
235
+ end
236
+
237
+ # Creates a class to wrap the C struct with the value +ty+
238
+ #
239
+ # See also Fiddle::Importer.struct
240
+ def create_value(ty, val=nil)
241
+ s = struct([ty + " value"])
242
+ ptr = s.malloc()
243
+ if( val )
244
+ ptr.value = val
245
+ end
246
+ return ptr
247
+ end
248
+ alias value create_value
249
+
250
+ # Returns a new instance of the C struct with the value +ty+ at the +addr+
251
+ # address.
252
+ def import_value(ty, addr)
253
+ s = struct([ty + " value"])
254
+ ptr = s.new(addr)
255
+ return ptr
256
+ end
257
+
258
+
259
+ # The Fiddle::CompositeHandler instance
260
+ #
261
+ # Will raise an error if no handlers are open.
262
+ def handler
263
+ (@handler ||= nil) or raise "call dlload before importing symbols and functions"
264
+ end
265
+
266
+ # Returns a new Fiddle::Pointer instance at the memory address of the given
267
+ # +name+ symbol.
268
+ #
269
+ # Raises a DLError if the +name+ doesn't exist.
270
+ #
271
+ # See Fiddle::CompositeHandler.sym and Fiddle::Handle.sym
272
+ def import_symbol(name)
273
+ addr = handler.sym(name)
274
+ if( !addr )
275
+ raise(DLError, "cannot find the symbol: #{name}")
276
+ end
277
+ Pointer.new(addr)
278
+ end
279
+
280
+ # Returns a new Fiddle::Function instance at the memory address of the given
281
+ # +name+ function.
282
+ #
283
+ # Raises a DLError if the +name+ doesn't exist.
284
+ #
285
+ # * +argtype+ is an Array of arguments, passed to the +name+ function.
286
+ # * +ctype+ is the return type of the function
287
+ # * +call_type+ is the ABI of the function
288
+ #
289
+ # See also Fiddle:Function.new
290
+ #
291
+ # See Fiddle::CompositeHandler.sym and Fiddle::Handler.sym
292
+ def import_function(name, ctype, argtype, call_type = nil)
293
+ addr = handler.sym(name)
294
+ if( !addr )
295
+ raise(DLError, "cannot find the function: #{name}()")
296
+ end
297
+ Function.new(addr, argtype, ctype, CALL_TYPE_TO_ABI[call_type],
298
+ name: name)
299
+ end
300
+
301
+ # Returns a new closure wrapper for the +name+ function.
302
+ #
303
+ # * +ctype+ is the return type of the function
304
+ # * +argtype+ is an Array of arguments, passed to the callback function
305
+ # * +call_type+ is the abi of the closure
306
+ # * +block+ is passed to the callback
307
+ #
308
+ # See Fiddle::Closure
309
+ def bind_function(name, ctype, argtype, call_type = nil, &block)
310
+ abi = CALL_TYPE_TO_ABI[call_type]
311
+ closure = Class.new(Fiddle::Closure) {
312
+ define_method(:call, block)
313
+ }.new(ctype, argtype, abi)
314
+
315
+ Function.new(closure, argtype, ctype, abi, name: name)
316
+ end
317
+ end
318
+ end