ffi_dry 0.1.5 → 0.1.7

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