fiddle 1.0.0.beta1

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.
@@ -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