fiddle 1.0.0 → 1.0.5

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.
@@ -10,6 +10,11 @@ module Fiddle
10
10
  # The name of this function
11
11
  attr_reader :name
12
12
 
13
+ # Whether GVL is needed to call this function
14
+ def need_gvl?
15
+ @need_gvl
16
+ end
17
+
13
18
  # The integer memory location of this function
14
19
  def to_i
15
20
  ptr.to_i
@@ -83,11 +83,7 @@ module Fiddle
83
83
  when Importer
84
84
  lib.handlers
85
85
  else
86
- begin
87
- Fiddle.dlopen(lib)
88
- rescue DLError
89
- raise(DLError, "can't load #{lib}")
90
- end
86
+ Fiddle.dlopen(lib)
91
87
  end
92
88
  }.flatten()
93
89
  @handler = CompositeHandler.new(handles)
@@ -115,16 +111,21 @@ module Fiddle
115
111
  return SIZEOF_INT
116
112
  when TYPE_LONG
117
113
  return SIZEOF_LONG
118
- when TYPE_LONG_LONG
119
- return SIZEOF_LONG_LONG
120
114
  when TYPE_FLOAT
121
115
  return SIZEOF_FLOAT
122
116
  when TYPE_DOUBLE
123
117
  return SIZEOF_DOUBLE
124
118
  when TYPE_VOIDP
125
119
  return SIZEOF_VOIDP
120
+ when TYPE_CONST_STRING
121
+ return SIZEOF_CONST_STRING
126
122
  else
127
- raise(DLError, "unknown type: #{ty}")
123
+ if defined?(TYPE_LONG_LONG) and
124
+ ty == TYPE_LONG_LONG
125
+ return SIZEOF_LONG_LONG
126
+ else
127
+ raise(DLError, "unknown type: #{ty}")
128
+ end
128
129
  end
129
130
  when Class
130
131
  if( ty.instance_methods().include?(:to_ptr) )
@@ -154,7 +155,8 @@ module Fiddle
154
155
  # :stopdoc:
155
156
  CALL_TYPE_TO_ABI = Hash.new { |h, k|
156
157
  raise RuntimeError, "unsupported call type: #{k}"
157
- }.merge({ :stdcall => (Function::STDCALL rescue Function::DEFAULT),
158
+ }.merge({ :stdcall => Function.const_defined?(:STDCALL) ? Function::STDCALL :
159
+ Function::DEFAULT,
158
160
  :cdecl => Function::DEFAULT,
159
161
  nil => Function::DEFAULT
160
162
  }).freeze
@@ -18,7 +18,7 @@ module Fiddle
18
18
  }
19
19
 
20
20
  PACK_MAP = {
21
- TYPE_VOIDP => ((SIZEOF_VOIDP == SIZEOF_LONG_LONG) ? "q" : "l!"),
21
+ TYPE_VOIDP => "l!",
22
22
  TYPE_CHAR => "c",
23
23
  TYPE_SHORT => "s!",
24
24
  TYPE_INT => "i!",
@@ -48,6 +48,7 @@ module Fiddle
48
48
  ALIGN_MAP[TYPE_LONG_LONG] = ALIGN_MAP[-TYPE_LONG_LONG] = ALIGN_LONG_LONG
49
49
  PACK_MAP[TYPE_LONG_LONG] = PACK_MAP[-TYPE_LONG_LONG] = "q"
50
50
  SIZE_MAP[TYPE_LONG_LONG] = SIZE_MAP[-TYPE_LONG_LONG] = SIZEOF_LONG_LONG
51
+ PACK_MAP[TYPE_VOIDP] = "q" if SIZEOF_LONG_LONG == SIZEOF_VOIDP
51
52
  end
52
53
 
53
54
  def align(addr, align)
@@ -80,10 +81,13 @@ module Fiddle
80
81
  case SIZEOF_VOIDP
81
82
  when SIZEOF_LONG
82
83
  ary.pack(@template)
83
- when SIZEOF_LONG_LONG
84
- ary.pack(@template)
85
84
  else
86
- raise(RuntimeError, "sizeof(void*)?")
85
+ if defined?(TYPE_LONG_LONG) and
86
+ SIZEOF_VOIDP == SIZEOF_LONG_LONG
87
+ ary.pack(@template)
88
+ else
89
+ raise(RuntimeError, "sizeof(void*)?")
90
+ end
87
91
  end
88
92
  end
89
93
 
@@ -91,10 +95,13 @@ module Fiddle
91
95
  case SIZEOF_VOIDP
92
96
  when SIZEOF_LONG
93
97
  ary.join().unpack(@template)
94
- when SIZEOF_LONG_LONG
95
- ary.join().unpack(@template)
96
98
  else
97
- raise(RuntimeError, "sizeof(void*)?")
99
+ if defined?(TYPE_LONG_LONG) and
100
+ SIZEOF_VOIDP == SIZEOF_LONG_LONG
101
+ ary.join().unpack(@template)
102
+ else
103
+ raise(RuntimeError, "sizeof(void*)?")
104
+ end
98
105
  end
99
106
  end
100
107
 
@@ -4,15 +4,72 @@ require 'fiddle/value'
4
4
  require 'fiddle/pack'
5
5
 
6
6
  module Fiddle
7
- # C struct shell
7
+ # A base class for objects representing a C structure
8
8
  class CStruct
9
+ include Enumerable
10
+
9
11
  # accessor to Fiddle::CStructEntity
10
12
  def CStruct.entity_class
11
13
  CStructEntity
12
14
  end
15
+
16
+ def each
17
+ return enum_for(__function__) unless block_given?
18
+
19
+ self.class.members.each do |name,|
20
+ yield(self[name])
21
+ end
22
+ end
23
+
24
+ def each_pair
25
+ return enum_for(__function__) unless block_given?
26
+
27
+ self.class.members.each do |name,|
28
+ yield(name, self[name])
29
+ end
30
+ end
31
+
32
+ def to_h
33
+ hash = {}
34
+ each_pair do |name, value|
35
+ hash[name] = unstruct(value)
36
+ end
37
+ hash
38
+ end
39
+
40
+ def replace(another)
41
+ if another.nil?
42
+ self.class.members.each do |name,|
43
+ self[name] = nil
44
+ end
45
+ elsif another.respond_to?(:each_pair)
46
+ another.each_pair do |name, value|
47
+ self[name] = value
48
+ end
49
+ else
50
+ another.each do |name, value|
51
+ self[name] = value
52
+ end
53
+ end
54
+ self
55
+ end
56
+
57
+ private
58
+ def unstruct(value)
59
+ case value
60
+ when CStruct
61
+ value.to_h
62
+ when Array
63
+ value.collect do |v|
64
+ unstruct(v)
65
+ end
66
+ else
67
+ value
68
+ end
69
+ end
13
70
  end
14
71
 
15
- # C union shell
72
+ # A base class for objects representing a C union
16
73
  class CUnion
17
74
  # accessor to Fiddle::CUnionEntity
18
75
  def CUnion.entity_class
@@ -20,6 +77,41 @@ module Fiddle
20
77
  end
21
78
  end
22
79
 
80
+ # Wrapper for arrays within a struct
81
+ class StructArray < Array
82
+ include ValueUtil
83
+
84
+ def initialize(ptr, type, initial_values)
85
+ @ptr = ptr
86
+ @type = type
87
+ @is_struct = @type.respond_to?(:entity_class)
88
+ if @is_struct
89
+ super(initial_values)
90
+ else
91
+ @size = Fiddle::PackInfo::SIZE_MAP[type]
92
+ @pack_format = Fiddle::PackInfo::PACK_MAP[type]
93
+ super(initial_values.collect { |v| unsigned_value(v, type) })
94
+ end
95
+ end
96
+
97
+ def to_ptr
98
+ @ptr
99
+ end
100
+
101
+ def []=(index, value)
102
+ if index < 0 || index >= size
103
+ raise IndexError, 'index %d outside of array bounds 0...%d' % [index, size]
104
+ end
105
+
106
+ if @is_struct
107
+ self[index].replace(value)
108
+ else
109
+ to_ptr[index * @size, @size] = [value].pack(@pack_format)
110
+ super(index, value)
111
+ end
112
+ end
113
+ end
114
+
23
115
  # Used to construct C classes (CUnion, CStruct, etc)
24
116
  #
25
117
  # Fiddle::Importer#struct and Fiddle::Importer#union wrap this functionality in an
@@ -35,7 +127,7 @@ module Fiddle
35
127
  # Fiddle::Importer#struct and Fiddle::Importer#union wrap this functionality in an
36
128
  # easy-to-use manner.
37
129
  #
38
- # Example:
130
+ # Examples:
39
131
  #
40
132
  # require 'fiddle/struct'
41
133
  # require 'fiddle/cparser'
@@ -46,47 +138,98 @@ module Fiddle
46
138
  #
47
139
  # MyStruct = Fiddle::CStructBuilder.create(Fiddle::CUnion, types, members)
48
140
  #
49
- # obj = MyStruct.allocate
141
+ # MyStruct.malloc(Fiddle::RUBY_FREE) do |obj|
142
+ # ...
143
+ # end
144
+ #
145
+ # obj = MyStruct.malloc(Fiddle::RUBY_FREE)
146
+ # begin
147
+ # ...
148
+ # ensure
149
+ # obj.call_free
150
+ # end
151
+ #
152
+ # obj = MyStruct.malloc
153
+ # begin
154
+ # ...
155
+ # ensure
156
+ # Fiddle.free obj.to_ptr
157
+ # end
50
158
  #
51
159
  def create(klass, types, members)
52
160
  new_class = Class.new(klass){
53
- define_method(:initialize){|addr|
54
- @entity = klass.entity_class.new(addr, types)
161
+ define_method(:initialize){|addr, func = nil|
162
+ if addr.is_a?(self.class.entity_class)
163
+ @entity = addr
164
+ else
165
+ @entity = self.class.entity_class.new(addr, types, func)
166
+ end
55
167
  @entity.assign_names(members)
56
168
  }
169
+ define_method(:[]) { |*args| @entity.send(:[], *args) }
170
+ define_method(:[]=) { |*args| @entity.send(:[]=, *args) }
57
171
  define_method(:to_ptr){ @entity }
58
172
  define_method(:to_i){ @entity.to_i }
173
+ define_singleton_method(:types) { types }
174
+ define_singleton_method(:members) { members }
59
175
  members.each{|name|
176
+ name = name[0] if name.is_a?(Array) # name is a nested struct
177
+ next if method_defined?(name)
60
178
  define_method(name){ @entity[name] }
61
179
  define_method(name + "="){|val| @entity[name] = val }
62
180
  }
63
- }
64
- size = klass.entity_class.size(types)
65
- new_class.module_eval(<<-EOS, __FILE__, __LINE__+1)
66
- def new_class.size()
67
- #{size}
181
+ entity_class = klass.entity_class
182
+ alignment = entity_class.alignment(types)
183
+ size = entity_class.size(types)
184
+ define_singleton_method(:alignment) { alignment }
185
+ define_singleton_method(:size) { size }
186
+ define_singleton_method(:malloc) do |func=nil, &block|
187
+ if block
188
+ entity_class.malloc(types, func, size) do |entity|
189
+ block.call(new(entity))
190
+ end
191
+ else
192
+ new(entity_class.malloc(types, func, size))
193
+ end
68
194
  end
69
- def new_class.malloc()
70
- addr = Fiddle.malloc(#{size})
71
- new(addr)
72
- end
73
- EOS
195
+ }
74
196
  return new_class
75
197
  end
76
198
  module_function :create
77
199
  end
78
200
 
79
- # A C struct wrapper
201
+ # A pointer to a C structure
80
202
  class CStructEntity < Fiddle::Pointer
81
203
  include PackInfo
82
204
  include ValueUtil
83
205
 
206
+ def CStructEntity.alignment(types)
207
+ max = 1
208
+ types.each do |type, count = 1|
209
+ if type.respond_to?(:entity_class)
210
+ n = type.alignment
211
+ else
212
+ n = ALIGN_MAP[type]
213
+ end
214
+ max = n if n > max
215
+ end
216
+ max
217
+ end
218
+
84
219
  # Allocates a C struct with the +types+ provided.
85
220
  #
86
- # When the instance is garbage collected, the C function +func+ is called.
87
- def CStructEntity.malloc(types, func = nil)
88
- addr = Fiddle.malloc(CStructEntity.size(types))
89
- CStructEntity.new(addr, types, func)
221
+ # See Fiddle::Pointer.malloc for memory management issues.
222
+ def CStructEntity.malloc(types, func = nil, size = size(types), &block)
223
+ if block_given?
224
+ super(size, func) do |struct|
225
+ struct.set_ctypes types
226
+ yield struct
227
+ end
228
+ else
229
+ struct = super(size, func)
230
+ struct.set_ctypes types
231
+ struct
232
+ end
90
233
  end
91
234
 
92
235
  # Returns the offset for the packed sizes for the given +types+.
@@ -102,9 +245,15 @@ module Fiddle
102
245
  max_align = types.map { |type, count = 1|
103
246
  last_offset = offset
104
247
 
105
- align = PackInfo::ALIGN_MAP[type]
248
+ if type.respond_to?(:entity_class)
249
+ align = type.alignment
250
+ type_size = type.size
251
+ else
252
+ align = PackInfo::ALIGN_MAP[type]
253
+ type_size = PackInfo::SIZE_MAP[type]
254
+ end
106
255
  offset = PackInfo.align(last_offset, align) +
107
- (PackInfo::SIZE_MAP[type] * count)
256
+ (type_size * count)
108
257
 
109
258
  align
110
259
  }.max
@@ -118,13 +267,37 @@ module Fiddle
118
267
  #
119
268
  # See also Fiddle::Pointer.new
120
269
  def initialize(addr, types, func = nil)
270
+ if func && addr.is_a?(Pointer) && addr.free
271
+ raise ArgumentError, 'free function specified on both underlying struct Pointer and when creating a CStructEntity - who do you want to free this?'
272
+ end
121
273
  set_ctypes(types)
122
274
  super(addr, @size, func)
123
275
  end
124
276
 
125
277
  # Set the names of the +members+ in this C struct
126
278
  def assign_names(members)
127
- @members = members
279
+ @members = []
280
+ @nested_structs = {}
281
+ members.each_with_index do |member, index|
282
+ if member.is_a?(Array) # nested struct
283
+ member_name = member[0]
284
+ struct_type, struct_count = @ctypes[index]
285
+ if struct_count.nil?
286
+ struct = struct_type.new(to_i + @offset[index])
287
+ else
288
+ structs = struct_count.times.map do |i|
289
+ struct_type.new(to_i + @offset[index] + i * struct_type.size)
290
+ end
291
+ struct = StructArray.new(to_i + @offset[index],
292
+ struct_type,
293
+ structs)
294
+ end
295
+ @nested_structs[member_name] = struct
296
+ else
297
+ member_name = member
298
+ end
299
+ @members << member_name
300
+ end
128
301
  end
129
302
 
130
303
  # Calculates the offsets and sizes for the given +types+ in the struct.
@@ -135,12 +308,18 @@ module Fiddle
135
308
 
136
309
  max_align = types.map { |type, count = 1|
137
310
  orig_offset = offset
138
- align = ALIGN_MAP[type]
311
+ if type.respond_to?(:entity_class)
312
+ align = type.alignment
313
+ type_size = type.size
314
+ else
315
+ align = ALIGN_MAP[type]
316
+ type_size = SIZE_MAP[type]
317
+ end
139
318
  offset = PackInfo.align(orig_offset, align)
140
319
 
141
320
  @offset << offset
142
321
 
143
- offset += (SIZE_MAP[type] * count)
322
+ offset += (type_size * count)
144
323
 
145
324
  align
146
325
  }.max
@@ -148,15 +327,34 @@ module Fiddle
148
327
  @size = PackInfo.align(offset, max_align)
149
328
  end
150
329
 
151
- # Fetch struct member +name+
152
- def [](name)
330
+ # Fetch struct member +name+ if only one argument is specified. If two
331
+ # arguments are specified, the first is an offset and the second is a
332
+ # length and this method returns the string of +length+ bytes beginning at
333
+ # +offset+.
334
+ #
335
+ # Examples:
336
+ #
337
+ # my_struct = struct(['int id']).malloc
338
+ # my_struct.id = 1
339
+ # my_struct['id'] # => 1
340
+ # my_struct[0, 4] # => "\x01\x00\x00\x00".b
341
+ #
342
+ def [](*args)
343
+ return super(*args) if args.size > 1
344
+ name = args[0]
153
345
  idx = @members.index(name)
154
346
  if( idx.nil? )
155
347
  raise(ArgumentError, "no such member: #{name}")
156
348
  end
157
349
  ty = @ctypes[idx]
158
350
  if( ty.is_a?(Array) )
159
- r = super(@offset[idx], SIZE_MAP[ty[0]] * ty[1])
351
+ if ty.first.respond_to?(:entity_class)
352
+ return @nested_structs[name]
353
+ else
354
+ r = super(@offset[idx], SIZE_MAP[ty[0]] * ty[1])
355
+ end
356
+ elsif ty.respond_to?(:entity_class)
357
+ return @nested_structs[name]
160
358
  else
161
359
  r = super(@offset[idx], SIZE_MAP[ty.abs])
162
360
  end
@@ -176,14 +374,44 @@ module Fiddle
176
374
  if( ty.is_a?(Integer) && (ty < 0) )
177
375
  return unsigned_value(val, ty)
178
376
  elsif( ty.is_a?(Array) && (ty[0] < 0) )
179
- return val.collect{|v| unsigned_value(v,ty[0])}
377
+ return StructArray.new(self + @offset[idx], ty[0], val)
180
378
  else
181
379
  return val
182
380
  end
183
381
  end
184
382
 
185
- # Set struct member +name+, to value +val+
186
- def []=(name, val)
383
+ # Set struct member +name+, to value +val+. If more arguments are
384
+ # specified, writes the string of bytes to the memory at the given
385
+ # +offset+ and +length+.
386
+ #
387
+ # Examples:
388
+ #
389
+ # my_struct = struct(['int id']).malloc
390
+ # my_struct['id'] = 1
391
+ # my_struct[0, 4] = "\x01\x00\x00\x00".b
392
+ # my_struct.id # => 1
393
+ #
394
+ def []=(*args)
395
+ return super(*args) if args.size > 2
396
+ name, val = *args
397
+ name = name.to_s if name.is_a?(Symbol)
398
+ nested_struct = @nested_structs[name]
399
+ if nested_struct
400
+ if nested_struct.is_a?(StructArray)
401
+ if val.nil?
402
+ nested_struct.each do |s|
403
+ s.replace(nil)
404
+ end
405
+ else
406
+ val.each_with_index do |v, i|
407
+ nested_struct[i] = v
408
+ end
409
+ end
410
+ else
411
+ nested_struct.replace(val)
412
+ end
413
+ return val
414
+ end
187
415
  idx = @members.index(name)
188
416
  if( idx.nil? )
189
417
  raise(ArgumentError, "no such member: #{name}")
@@ -202,23 +430,16 @@ module Fiddle
202
430
  end
203
431
  end
204
432
 
433
+ undef_method :size=
205
434
  def to_s() # :nodoc:
206
435
  super(@size)
207
436
  end
208
437
  end
209
438
 
210
- # A C union wrapper
439
+ # A pointer to a C union
211
440
  class CUnionEntity < CStructEntity
212
441
  include PackInfo
213
442
 
214
- # Allocates a C union the +types+ provided.
215
- #
216
- # When the instance is garbage collected, the C function +func+ is called.
217
- def CUnionEntity.malloc(types, func=nil)
218
- addr = Fiddle.malloc(CUnionEntity.size(types))
219
- CUnionEntity.new(addr, types, func)
220
- end
221
-
222
443
  # Returns the size needed for the union with the given +types+.
223
444
  #
224
445
  # Fiddle::CUnionEntity.size(
@@ -228,7 +449,11 @@ module Fiddle
228
449
  # Fiddle::TYPE_VOIDP ]) #=> 8
229
450
  def CUnionEntity.size(types)
230
451
  types.map { |type, count = 1|
231
- PackInfo::SIZE_MAP[type] * count
452
+ if type.respond_to?(:entity_class)
453
+ type.size * count
454
+ else
455
+ PackInfo::SIZE_MAP[type] * count
456
+ end
232
457
  }.max
233
458
  end
234
459
 
@@ -241,4 +466,3 @@ module Fiddle
241
466
  end
242
467
  end
243
468
  end
244
-