ffi_dry 0.1.5 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/History.txt +23 -1
  2. data/VERSION +1 -1
  3. data/ffi_dry.gemspec +5 -4
  4. data/lib/ffi/dry.rb +117 -162
  5. metadata +2 -2
@@ -1,3 +1,24 @@
1
+ === 0.1.7 / 2010-2-05
2
+ * Fixed bugs with FFI::Unions
3
+ * Revamped the dsl_layout builder so that it just wraps layout() instead of
4
+ attempting to duplicate its functionlity. This will hopefully stop causing
5
+ compatibility issues with FFI as it changes its internals and new versions
6
+ are released.
7
+ * :p_struct is an attribute that can be used on any field with a type of
8
+ :pointer.
9
+ * Added new experimental feature for the DSL where new fields can be defined
10
+ by their name as a method name in dsl_layout {} instead of having to
11
+ prefix field/array/struct for each.
12
+ * A warning is now issued when a struct field name cannot be mapped to an
13
+ accessor method because it would cause a conflict. These used to just be
14
+ silently skipped. Now the warning is issued if warnings are enabled.
15
+
16
+ === 0.1.6 / 2010-1-18
17
+ * support win32 with wsock32.dll for NetEndian helper stuff.
18
+ * support StructHelper for FFI::Unions
19
+ * support passing arguments to StructLayoutBuilder in dsl_layout()
20
+ * allow overriding copy_size to address incorrect alignment issues in FFI
21
+
1
22
  === 0.1.5 / 2010-1-4
2
23
  * Added p_struct dsl directive sugar for creating FFI::Struct accessors to
3
24
  handle pointers to structs.
@@ -5,5 +26,6 @@
5
26
  === 0.1.4 / 2010-1-1
6
27
  * Support for FFI 0.5.0 final and up.
7
28
  * Added NetStructHelper with accessors to provide automatic network byte
8
- order conversion on network packet structures.
29
+ order conversion on network packet structures.
30
+
9
31
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.5
1
+ 0.1.7
@@ -1,15 +1,15 @@
1
1
  # Generated by jeweler
2
- # DO NOT EDIT THIS FILE
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
4
  # -*- encoding: utf-8 -*-
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{ffi_dry}
8
- s.version = "0.1.5"
8
+ s.version = "0.1.7"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Eric Monti"]
12
- s.date = %q{2010-01-15}
12
+ s.date = %q{2010-02-05}
13
13
  s.description = %q{Provides some useful modules, classes, and methods for FFI bindings as well as a DSL-like syntax for FFI::Struct layouts}
14
14
  s.email = %q{emonti@matasano.com}
15
15
  s.extra_rdoc_files = [
@@ -62,3 +62,4 @@ Gem::Specification.new do |s|
62
62
  s.add_dependency(%q<yard>, [">= 0"])
63
63
  end
64
64
  end
65
+
@@ -131,7 +131,14 @@ module FFI::DRY
131
131
  # which can be used to add to the size of the copied instance as it is
132
132
  # allocated and copied.
133
133
  def copy(grown=0)
134
- self.class.new( :raw => self.to_ptr.read_string(self.size+grown) )
134
+ self.class.new( :raw => self.to_ptr.read_string(self.copy_size+grown) )
135
+ end
136
+
137
+ # This method is called when creating a copy of self. It can be overridden
138
+ # by derived structures to return another size. This is sometimes done
139
+ # to account for alignment issues, etc.
140
+ def copy_size
141
+ self.size
135
142
  end
136
143
 
137
144
  # Returns a pointer to the specified field, which is the name assigned
@@ -150,19 +157,33 @@ module FFI::DRY
150
157
  @dsl_metadata
151
158
  end
152
159
 
153
- private
160
+ def define_field_accessor name, &block
161
+ if instance_methods.include?("#{name}")
162
+ warn "WARNING: The name '#{name}' is in use for class #{self} "+
163
+ "Skipping automatic method creation in dsl_layout block."
164
+ else
165
+ define_method name, &block
166
+ end
167
+ end
154
168
 
155
169
  # This passes a block to an instance of DSLStructLayoutBuilder, allowing
156
- # for a more declarative syntax with extra metadata.
170
+ # for a more declarative syntax with additional metadata to be included.
157
171
  #
158
- # It is a replacement to layout() and stores the dsl_metadata gathered
159
- # about structure members locally.
172
+ # dsl_layout() a replacement to layout() and stores the dsl_metadata
173
+ # gathered about structure members locally and automatically creates
174
+ # accessor methods for each field in the structure.
175
+ #
176
+ # NOTE if a structure field name conflicts with another instance method
177
+ # already defined in the class, the relevant accessor method is not
178
+ # created and a warning is issued. This does not apply to methods
179
+ # defined after the dsl_layout block is called. In other words this
180
+ # does not affect the overriding of accessor methods in any way.
160
181
  def dsl_layout &block
161
182
  builder = DSLStructLayoutBuilder.new(self)
162
183
  builder.instance_eval(&block)
163
- @layout = builder.build
184
+ @layout = self.layout(*(builder.__layout_args))
164
185
  @size = @layout.size
165
- _class_meths_from_dsl_metadata( builder.metadata )
186
+ _class_meths_from_dsl_metadata( builder.__metadata )
166
187
  return @layout
167
188
  end
168
189
 
@@ -170,19 +191,15 @@ module FFI::DRY
170
191
  (@dsl_metadata = meta).each do |spec|
171
192
  name = spec[:name]
172
193
  ftype = spec[:type]
173
- unless instance_methods.include?(:"#{name}")
174
- if p=spec[:p_struct] and p.kind_of?(Class)
175
- define_method(:"#{name}") do
176
- p.new(self[name]) unless self[name].null?
177
- end
178
- else
179
- define_method(:"#{name}") { self[name] }
194
+ if p=spec[:p_struct] and p.kind_of?(Class)
195
+ define_field_accessor(:"#{name}") do
196
+ p.new(self[name]) unless self[name].null?
180
197
  end
198
+ else
199
+ define_field_accessor(:"#{name}") { self[name] }
181
200
  end
182
201
 
183
- unless instance_methods.include?(:"#{name}=")
184
- define_method(:"#{name}=") {|val| self[name]=val }
185
- end
202
+ define_field_accessor(:"#{name}=") {|val| self[name]=val }
186
203
  end
187
204
  end
188
205
  end
@@ -191,73 +208,29 @@ module FFI::DRY
191
208
  base.extend(ClassMethods)
192
209
  end
193
210
 
194
- alias inspect_orig inspect
195
-
196
- # Overrides inspect to show field names and values
197
- def inspect
198
- ret = "#<#{self.class}"
199
- if not @_inspecting_in_progress
200
- @_inspecting_in_progress = true
201
- ret << " " << members.map {|m| "#{m}=#{self[m].inspect}"}.join(', ')
202
- @_inspecting_in_progress = nil
203
- end
204
- ret << ">"
205
- end
206
211
  end # class StructHelper
207
212
 
208
- # This is a wrapper around the FFI::StructLayoutBuilder. Its goal is to
209
- # provides a more declarative syntax for defining structures and include
210
- # the ability to attach arbitrary dsl_metadata information to structure
211
- # fields during definition.
212
- #
213
- # The "DSL" (and that's really very in-quotes) supplies 3 ways to
214
- # define a field (for now):
215
- #
216
- # field()
217
- # array()
218
- # struct()
219
- #
220
- # See the individual method descriptions for more info.
221
- #
213
+ # This class provides the DSL for StructHelper.dsl_layout.
214
+ # You probably don't want to use this directly but if you do, to use the DSL,
215
+ # you may either pass a structure definition into 'instance_eval' or
216
+ # call methods on the object. The methods __metadata and __layout_args return
217
+ # structure information back to the caller, which can use them to create
218
+ # a new structure.
222
219
  class DSLStructLayoutBuilder
223
- attr_reader :builder, :metadata
220
+ attr_reader :__metadata, :__layout_args
224
221
 
225
- # Initializes the builder with a reference to the structure using it
226
- # Instead of duplicating Struct features, we just call back to them.
222
+ # Initializes the a new builder class.
227
223
  def initialize(pbind)
228
224
  @pbind = pbind
229
- @builder = ::FFI::StructLayoutBuilder.new
230
- @metadata = []
231
- super()
232
- end
233
-
234
- # calls StructLayoutBuider.build() on the bulder and returns its
235
- # result.
236
- def build
237
- @builder.build
238
- end
239
-
240
- # Calls StructLayoutBuilder.add_struct() on the builder and stores
241
- # a metadata hash entry (the opts hash with name and type overridden)
242
- #
243
- # struct field_name, RubyClass, { ... metadata ... }
244
- #
245
- # :offset is a special key in metadata, specifies the offset of the field.
246
- def struct(name, klass, o={})
247
- unless klass.kind_of?(Class) and klass < ::FFI::Struct
248
- raise(::ArgumentError, "klass must be a struct")
249
- end
250
-
251
- opts = o.merge(:name => name, :type => klass)
252
- offset = opts[:offset]
253
- ret=@builder.add_struct(name, klass, offset)
254
- @metadata << opts
255
- return ret
225
+ @__layout_args = []
226
+ @__metadata = []
227
+ yield self if block_given?
256
228
  end
257
229
 
258
230
  # A pointer to a structure. The structure does not allocate the entire
259
- # space for the structure, just a pointer. When calling the accessors for
260
- # a p_struct field, a new instance of the FFI::Struct will be returned.
231
+ # space for the structure pointed to, just a pointer. When calling the
232
+ # accessor for a p_struct field, a new instance of the FFI::Struct type
233
+ # for the pointer will be returned.
261
234
  def p_struct(name, klass, o={})
262
235
  unless klass.kind_of?(Class)
263
236
  raise(::ArgumentError, "klass must be a Class")
@@ -267,67 +240,54 @@ module FFI::DRY
267
240
  field(name, :pointer, opts)
268
241
  end
269
242
 
270
- # Calls StructLayoutBuider.add_array() on the builder and stores
271
- # a metadata hash entry (the opts hash with name and type overridden)
243
+ # Declaratively adds a field to the structure.
272
244
  #
273
245
  # Syntax:
274
246
  #
275
- # array field_name, [ctype, N], { ... metadata ... }
247
+ # field field_name, ctype, { ... metadata ... }
276
248
  #
277
249
  # :offset is a special key in metadata, specifies the offset of the field.
278
- def array(name, type, o={})
279
- unless type.kind_of?(::Array)
280
- raise(::ArgumentError, "type must be an array")
281
- end
282
-
250
+ def field(name, type, o={})
283
251
  opts = o.merge(:name => name, :type => type)
284
252
  offset = opts[:offset]
285
- mod = enclosing_module
286
- ret=
287
- if @builder.respond_to?(:add_array)
288
- @builder.add_array(name, find_type(type[0], mod), type[1], offset)
289
- else
290
- @builder.add_field(name, type, offset)
291
- end
292
253
 
293
- @metadata << opts
294
- return ret
254
+ @__layout_args << name
255
+ @__layout_args << type
256
+ @__layout_args << offset if offset
257
+
258
+ @__metadata << opts
259
+ return opts
295
260
  end
296
261
 
297
- # Calls StructLayoutBuider.add_field() on the builder and stores
298
- # a metadata hash entry (the opts hash with name and type overridden)
299
- #
300
- # Syntax:
301
- #
302
- # field field_name, ctype, { ... metadata ... }
303
- #
304
- # :offset is a special key in metadata, specifies the offset of the field.
305
- def field(name, type, o={})
262
+ alias array field
263
+ alias struct field
264
+ alias union field
265
+
266
+ # Experimental - Allows specifying structure fields by taking a missing
267
+ # method name as field name for the structure.
268
+ def method_missing(name, type, *extra)
269
+ o={}
270
+ if extra.size > 1
271
+ raise(ArgumentError,
272
+ "Bad field definition. Use: name :type, {optional extra parameters}")
273
+ elsif h=extra.first
274
+ if h.kind_of? Hash
275
+ o=h
276
+ else
277
+ raise(ArgumentError, "Options must be provided as a hash.")
278
+ end
279
+ end
306
280
  opts = o.merge(:name => name, :type => type)
307
281
  offset = opts[:offset]
308
- mod = enclosing_module
309
- ret= @builder.add_field(name, find_type(type, mod), offset)
310
- @metadata << opts
311
- return ret
312
- end
313
282
 
314
- # This is a support method used similarly as in the actual builder.
315
- # XXX Note, as of 0.5.0-final, this became a protected method in the
316
- # FFI builder. We probably need to figure out another, *supported*, way to
317
- # do this without meddling into back-end FFI implementations.
318
- def find_type(*args)
319
- @pbind.instance_eval { find_type(*args) }
320
- end
283
+ @__layout_args << name
284
+ @__layout_args << type
285
+ @__layout_args << offset if offset
321
286
 
322
- # This is a support method used similarly as in the actual builder.
323
- # XXX Note, as of 0.5.0-final, this became a protected method in the
324
- # FFI builder. We probably need to figure out another, *supported*, way to
325
- # do this without meddling into back-end FFI implementations.
326
- def enclosing_module(*args)
327
- @pbind.instance_eval { enclosing_module(*args) }
287
+ @__metadata << opts
288
+ return opts
328
289
  end
329
-
330
- end
290
+ end # DSLStructLayoutBuilder
331
291
 
332
292
  # ConstMap can be used to organize and lookup value to constant mappings and
333
293
  # vice versa. Constants can be imported from a namespace based on a regular
@@ -362,25 +322,24 @@ module FFI::DRY
362
322
  constants.inject({}){|h,c| h.merge! c => const_get(c) }
363
323
  end
364
324
 
365
- private
366
- # When called from a module definition or class method, this method
367
- # imports all the constants from # namespace 'nspace' that start with
368
- # into the local namespace as constants named with whatever follows the
369
- # prefix. Only constant names that match [A-Z][A-Z0-9_]+ are imported,
370
- # the rest are ignored.
371
- #
372
- # This method also yields the (short) constant name and value to a block
373
- # if one is provided. The block works like [...].select {|c,v| ... } in
374
- # that the value is not mapped if the block returns nil or false.
375
- def slurp_constants(nspace, prefix)
376
- nspace.constants.grep(/^(#{prefix}([A-Z][A-Z0-9_]+))$/) do
377
- c = $2
378
- v = nspace.const_get($1)
379
- next if block_given? and not yield(c,v)
380
- const_set c, v
381
- end
325
+ # When called from a module definition or class method, this method
326
+ # imports all the constants from # namespace 'nspace' that start with
327
+ # into the local namespace as constants named with whatever follows the
328
+ # prefix. Only constant names that match [A-Z][A-Z0-9_]+ are imported,
329
+ # the rest are ignored.
330
+ #
331
+ # This method also yields the (short) constant name and value to a block
332
+ # if one is provided. The block works like [...].select {|c,v| ... } in
333
+ # that the value is not mapped if the block returns nil or false.
334
+ def slurp_constants(nspace, prefix)
335
+ nspace.constants.grep(/^(#{prefix}([A-Z][A-Z0-9_]+))$/) do
336
+ c = $2
337
+ v = nspace.const_get($1)
338
+ next if block_given? and not yield(c,v)
339
+ const_set c, v
382
340
  end
383
- end
341
+ end
342
+ end # ConstMap
384
343
 
385
344
  # Behaves just like ConstFlags, except that it returns a list
386
345
  # of names for the flags. Name string lookups work same way as
@@ -411,12 +370,17 @@ module FFI::DRY
411
370
  list[arg.to_s.upcase]
412
371
  end
413
372
  end
414
- end
373
+ end # ConstFlagsMap
415
374
 
416
375
 
417
376
  module NetEndian
418
377
  extend ::FFI::Library
419
378
 
379
+ begin
380
+ ffi_lib 'wsock32'
381
+ rescue LoadError
382
+ end
383
+
420
384
  attach_function :htons, [:uint16], :uint16
421
385
  attach_function :ntohs, [:uint16], :uint16
422
386
  attach_function :htonl, [:uint32], :uint32
@@ -431,7 +395,7 @@ module FFI::DRY
431
395
  ::FFI.find_type(:int32) => I32_convert,
432
396
  ::FFI.find_type(:uint32) => I32_convert,
433
397
  }
434
- end
398
+ end # NetEndian
435
399
 
436
400
 
437
401
  # A special helper for network packet structures that use big-endian or
@@ -440,8 +404,12 @@ module FFI::DRY
440
404
  # for 'reading' a 16/32 bit field, and htons/htonl for writing to one.
441
405
  #
442
406
  # NOTE this helper does not currently do anything special for 64-bit or
443
- # higher values.
407
+ # higher values but this might be added at some point if the need arises.
444
408
  #
409
+ # NOTE unlike the StructHelper module, no special relevance is given
410
+ # to fields with a ":p_struct" option or defined with the p_struct DSL
411
+ # method. These are ignored and treated like any other field. A net struct
412
+ # generally doesn't contain pointers into native memory anyway.
445
413
  module NetStructHelper
446
414
  def self.included(base)
447
415
  base.instance_eval { include StructHelper }
@@ -460,31 +428,18 @@ module FFI::DRY
460
428
  # Create endian swapper accessors methods for each applicable
461
429
  # field
462
430
  if( type.kind_of?(Symbol) and
463
- cnv=NetEndian::ENDIAN_METHS[ ::FFI.find_type(type) ] )
464
-
465
- unless instance_methods.include?(:"#{name}")
466
- define_method(:"#{name}"){ cnv[0].call(self[name]) }
467
- end
468
- unless instance_methods.include?(:"#{name}=")
469
- define_method(:"#{name}="){|val| self[name] = cnv[1].call(val) }
470
- end
471
-
431
+ cnv=NetEndian::ENDIAN_METHS[ ::FFI.find_type(type) ] )
432
+ define_method(:"#{name}"){ cnv[0].call(self[name]) }
433
+ define_method(:"#{name}="){|val| self[name] = cnv[1].call(val) }
472
434
  else
473
-
474
- unless instance_methods.include?(:"#{name}")
475
- define_method(:"#{name}"){ self[name] }
476
- end
477
- unless instance_methods.include?(:"#{name}=")
478
- define_method(:"#{name}="){|val| self[name]=val }
479
- end
480
-
435
+ define_field_accessor(:"#{name}"){ self[name] }
436
+ define_field_accessor(:"#{name}="){|val| self[name]=val }
481
437
  end
482
438
 
483
439
  end
484
440
  end
485
441
  end
486
- end
487
-
488
- end
442
+ end # NetStructHelper
489
443
 
444
+ end # FFI::DRY
490
445
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ffi_dry
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Monti
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2010-01-15 00:00:00 -06:00
12
+ date: 2010-02-05 00:00:00 -06:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency