ffi_dry 0.1.9 → 0.1.11
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.
- data/History.txt +7 -0
- data/Rakefile +0 -2
- data/VERSION +1 -1
- data/ffi_dry.gemspec +4 -20
- data/lib/ffi/dry.rb +396 -381
- metadata +22 -59
data/History.txt
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
=== 0.1.11 / 2010-6-2
|
2
|
+
* Added attach_optional_function to FFI::Library for dealing with functions
|
3
|
+
that are not always present.
|
4
|
+
|
5
|
+
=== 0.1.10 / 2010-6-2
|
6
|
+
*** YANKED due to typo in gem
|
7
|
+
|
1
8
|
=== 0.1.9 / 2010-3-5
|
2
9
|
* Workaround peculiar FFI::MemoryPointer.from_string() bugs when using
|
3
10
|
:raw => buf in ruby 1.9.x
|
data/Rakefile
CHANGED
@@ -12,8 +12,6 @@ begin
|
|
12
12
|
gem.homepage = "http://github.com/emonti/ffi_dry"
|
13
13
|
gem.authors = ["Eric Monti"]
|
14
14
|
gem.add_dependency "ffi", ">= 0.5.0"
|
15
|
-
gem.add_development_dependency "rspec"
|
16
|
-
gem.add_development_dependency "yard"
|
17
15
|
end
|
18
16
|
rescue LoadError
|
19
17
|
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.11
|
data/ffi_dry.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{ffi_dry}
|
8
|
-
s.version = "0.1.
|
8
|
+
s.version = "0.1.11"
|
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-
|
12
|
+
s.date = %q{2010-06-02}
|
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 = [
|
@@ -33,11 +33,10 @@ Gem::Specification.new do |s|
|
|
33
33
|
"spec/ffi_dry_spec.rb",
|
34
34
|
"spec/spec_helper.rb"
|
35
35
|
]
|
36
|
-
s.has_rdoc = true
|
37
36
|
s.homepage = %q{http://github.com/emonti/ffi_dry}
|
38
37
|
s.rdoc_options = ["--charset=UTF-8"]
|
39
38
|
s.require_paths = ["lib"]
|
40
|
-
s.rubygems_version = %q{1.3.
|
39
|
+
s.rubygems_version = %q{1.3.6}
|
41
40
|
s.summary = %q{Syntactic sugar and helper utilities for FFI}
|
42
41
|
s.test_files = [
|
43
42
|
"spec/ffi_dry_spec.rb",
|
@@ -46,30 +45,15 @@ Gem::Specification.new do |s|
|
|
46
45
|
|
47
46
|
if s.respond_to? :specification_version then
|
48
47
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
49
|
-
s.specification_version =
|
48
|
+
s.specification_version = 3
|
50
49
|
|
51
50
|
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
52
51
|
s.add_runtime_dependency(%q<ffi>, [">= 0.5.0"])
|
53
|
-
s.add_development_dependency(%q<rspec>, [">= 0"])
|
54
|
-
s.add_development_dependency(%q<yard>, [">= 0"])
|
55
|
-
s.add_runtime_dependency(%q<ffi>, [">= 0.5.0"])
|
56
|
-
s.add_development_dependency(%q<rspec>, [">= 0"])
|
57
|
-
s.add_development_dependency(%q<yard>, [">= 0"])
|
58
52
|
else
|
59
53
|
s.add_dependency(%q<ffi>, [">= 0.5.0"])
|
60
|
-
s.add_dependency(%q<rspec>, [">= 0"])
|
61
|
-
s.add_dependency(%q<yard>, [">= 0"])
|
62
|
-
s.add_dependency(%q<ffi>, [">= 0.5.0"])
|
63
|
-
s.add_dependency(%q<rspec>, [">= 0"])
|
64
|
-
s.add_dependency(%q<yard>, [">= 0"])
|
65
54
|
end
|
66
55
|
else
|
67
56
|
s.add_dependency(%q<ffi>, [">= 0.5.0"])
|
68
|
-
s.add_dependency(%q<rspec>, [">= 0"])
|
69
|
-
s.add_dependency(%q<yard>, [">= 0"])
|
70
|
-
s.add_dependency(%q<ffi>, [">= 0.5.0"])
|
71
|
-
s.add_dependency(%q<rspec>, [">= 0"])
|
72
|
-
s.add_dependency(%q<yard>, [">= 0"])
|
73
57
|
end
|
74
58
|
end
|
75
59
|
|
data/lib/ffi/dry.rb
CHANGED
@@ -6,442 +6,457 @@ unless defined?(FFI::Library::LIBC)
|
|
6
6
|
FFI::Library::LIBC = (RUBY_PLATFORM == 'mswin32' ? 'msvcrt' : 'c')
|
7
7
|
end
|
8
8
|
|
9
|
-
module FFI
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
# ss1=SomeStruct.new :raw => raw_data
|
44
|
-
# ss2=SomeStruct.new :raw => raw_data, :field1 => 1, :field2 => 2
|
45
|
-
# ss3=SomeStruct.new {|x| x.field1=1 }
|
46
|
-
# ss4=SomeStruct.new(:raw => raw_data) {|x| x.field1=1 }
|
47
|
-
#
|
48
|
-
# [ ss0,
|
49
|
-
# ss1,
|
50
|
-
# ss2,
|
51
|
-
# ss3,
|
52
|
-
# ss4].each_with_index {|x,i| pp ["ss#{i}",[x.field1, x.field2]]}
|
53
|
-
#
|
54
|
-
# # produces...
|
55
|
-
# # ["ss0", [0, 0]]
|
56
|
-
# # ["ss1", [0, 65535]]
|
57
|
-
# # ["ss2", [1, 2]]
|
58
|
-
# # ["ss3", [1, 0]]
|
59
|
-
# # ["ss4", [1, 65535]]
|
60
|
-
#
|
61
|
-
module StructHelper
|
62
|
-
|
63
|
-
attr_reader :dsl_metadata
|
64
|
-
|
65
|
-
# Allows setting structure fields on initialization to ::FFI::Struct.new
|
66
|
-
# as well as a "yield(self) if block_given?" at the end.
|
9
|
+
module FFI
|
10
|
+
module Library
|
11
|
+
# Helper method for attaching optional functions which may not be present.
|
12
|
+
# Just catches and ignores any FFI::NotFoundError exceptions.
|
13
|
+
def attach_optional_function(*args)
|
14
|
+
begin
|
15
|
+
attach_function(*args)
|
16
|
+
rescue FFI::NotFoundError => e
|
17
|
+
warn "WARNING: #{e.to_s}" if $DEBUG
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module DRY
|
24
|
+
|
25
|
+
# A module to add syntactic sugar and some nice automatic getter/setter
|
26
|
+
# methods to FFI::Struct, FFI::ManagedStruct, etc.
|
27
|
+
#
|
28
|
+
# For example:
|
29
|
+
# require 'rubygems'
|
30
|
+
# require 'ffi'
|
31
|
+
# require 'ffi/dry'
|
32
|
+
# require 'pp'
|
33
|
+
#
|
34
|
+
# class SomeStruct < FFI::Struct
|
35
|
+
# include FFI::DRY::StructHelper
|
36
|
+
#
|
37
|
+
# # we get a new way of specifying layouts with a 'dsl'-like syntax
|
38
|
+
# dsl_layout do
|
39
|
+
# field :field1, :uint16, :desc => 'this is field 1'
|
40
|
+
# field :field2, :uint16, :desc => 'this is field 2'
|
41
|
+
# end
|
42
|
+
# end
|
67
43
|
#
|
68
|
-
#
|
69
|
-
# a Hash.
|
44
|
+
# ss0=SomeStruct.new
|
70
45
|
#
|
71
|
-
#
|
72
|
-
# keys specify the field names to set.
|
46
|
+
# pp ss0.dsl_metadata # we can look at definition metadata
|
73
47
|
#
|
74
|
-
#
|
75
|
-
#
|
76
|
-
#
|
77
|
-
# string and initialized into a new FFI::MemoryPointer which this Struct
|
78
|
-
# then overlays.
|
48
|
+
# # produces...
|
49
|
+
# # [{:type=>:uint16, :name=>:field1, :desc=>"this is field 1"},
|
50
|
+
# # {:type=>:uint16, :name=>:field2, :desc=>"this is field 2"}]
|
79
51
|
#
|
80
|
-
#
|
81
|
-
#
|
52
|
+
# # And we have additional ways of instantiating and declaring values
|
53
|
+
# # during initialization. (The FFI standard ways still work too)
|
82
54
|
#
|
83
|
-
#
|
84
|
-
# the :raw tag.
|
55
|
+
# raw_data = "\x00\x00\xff\xff"
|
85
56
|
#
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
57
|
+
# ss1=SomeStruct.new :raw => raw_data
|
58
|
+
# ss2=SomeStruct.new :raw => raw_data, :field1 => 1, :field2 => 2
|
59
|
+
# ss3=SomeStruct.new {|x| x.field1=1 }
|
60
|
+
# ss4=SomeStruct.new(:raw => raw_data) {|x| x.field1=1 }
|
61
|
+
#
|
62
|
+
# [ ss0,
|
63
|
+
# ss1,
|
64
|
+
# ss2,
|
65
|
+
# ss3,
|
66
|
+
# ss4].each_with_index {|x,i| pp ["ss#{i}",[x.field1, x.field2]]}
|
67
|
+
#
|
68
|
+
# # produces...
|
69
|
+
# # ["ss0", [0, 0]]
|
70
|
+
# # ["ss1", [0, 65535]]
|
71
|
+
# # ["ss2", [1, 2]]
|
72
|
+
# # ["ss3", [1, 0]]
|
73
|
+
# # ["ss4", [1, 65535]]
|
74
|
+
#
|
75
|
+
module StructHelper
|
76
|
+
|
77
|
+
attr_reader :dsl_metadata
|
78
|
+
|
79
|
+
# Allows setting structure fields on initialization to ::FFI::Struct.new
|
80
|
+
# as well as a "yield(self) if block_given?" at the end.
|
81
|
+
#
|
82
|
+
# Field initialization happens if there is only one argument and it is
|
83
|
+
# a Hash.
|
84
|
+
#
|
85
|
+
# The params hash is taken as a set of values for fields where the hash
|
86
|
+
# keys specify the field names to set.
|
87
|
+
#
|
88
|
+
# @param [Hash] params
|
89
|
+
# Note:
|
90
|
+
# The :raw parameter is a special tag in the hash. The value is taken as a
|
91
|
+
# string and initialized into a new FFI::MemoryPointer which this Struct
|
92
|
+
# then overlays.
|
93
|
+
#
|
94
|
+
# If your struct layout has a field named :raw field, it won't be
|
95
|
+
# assignable through the hash argument.
|
96
|
+
#
|
97
|
+
# See also: set_fields() which is called automatically on the hash, minus
|
98
|
+
# the :raw tag.
|
99
|
+
#
|
100
|
+
def initialize(*args)
|
101
|
+
@dsl_metadata = self.class.dsl_metadata
|
102
|
+
params=nil
|
103
|
+
|
104
|
+
if args.size == 1 and (oparams=args[0]).is_a? Hash
|
105
|
+
params = oparams.dup
|
106
|
+
if raw=params.delete(:raw)
|
107
|
+
super( ::FFI::MemoryPointer.new(raw.size).write_string(raw) )
|
108
|
+
else
|
109
|
+
super()
|
110
|
+
end
|
94
111
|
else
|
95
|
-
super()
|
112
|
+
super(*args)
|
96
113
|
end
|
97
|
-
else
|
98
|
-
super(*args)
|
99
|
-
end
|
100
114
|
|
101
|
-
|
102
|
-
|
103
|
-
|
115
|
+
set_fields(params)
|
116
|
+
yield self if block_given?
|
117
|
+
end
|
104
118
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
119
|
+
# Sets field values in the struct specified by their symbolic name from a
|
120
|
+
# hash of ':field => value' pairs. Uses accessor field wrapper methods
|
121
|
+
# instead of a direct reference to the field (as in "obj.field1 = x",
|
122
|
+
# not "obj[:field] = x"). The difference is subtle, but this allows you
|
123
|
+
# to take advantage of any wrapper methods you override when initializing
|
124
|
+
# a new object. The only caveat is that the wrapper method must be named
|
125
|
+
# the same as the field, and the field must be included in members() from
|
126
|
+
# the layout.
|
127
|
+
#
|
128
|
+
# This method is called automatically if you are using the initialize()
|
129
|
+
# method provided in the Struct class and passing it a Hash as its only
|
130
|
+
# argument.
|
131
|
+
def set_fields(params=nil)
|
132
|
+
(params || {}).keys.each do |p|
|
133
|
+
if members().include?(p)
|
134
|
+
self.__send__(:"#{p}=", params[p])
|
135
|
+
else
|
136
|
+
raise(::ArgumentError, "#{self.class} does not have a '#{p}' field")
|
137
|
+
end
|
123
138
|
end
|
124
139
|
end
|
125
|
-
end
|
126
140
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
141
|
+
# Returns a new instance of self.class containing a seperately allocated
|
142
|
+
# copy of all our data. This abstract method should usually be called
|
143
|
+
# with super() from overridden 'copy' implementations for structures
|
144
|
+
# containing pointers to other memory or variable length data at the end.
|
145
|
+
#
|
146
|
+
# Note also that, by default, this implementation determine's size
|
147
|
+
# automatically based on the structure size. This is comparable to
|
148
|
+
# sizeof(some_struct) in C. However, you can supply a 'grown' parameter
|
149
|
+
# which can be used to add to the size of the copied instance as it is
|
150
|
+
# allocated and copied.
|
151
|
+
def copy(grown=0)
|
152
|
+
self.class.new( :raw => self.to_ptr.read_string(self.copy_size+grown) )
|
153
|
+
end
|
140
154
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
155
|
+
# This method is called when creating a copy of self. It can be overridden
|
156
|
+
# by derived structures to return another size. This is sometimes done
|
157
|
+
# to account for alignment issues, etc.
|
158
|
+
def copy_size
|
159
|
+
self.size
|
160
|
+
end
|
147
161
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
162
|
+
# Returns a pointer to the specified field, which is the name assigned
|
163
|
+
# to a member in the layout.
|
164
|
+
def ptr_to(field)
|
165
|
+
x = self[field] # this is actually a test, to raise if missing
|
166
|
+
return (self.to_ptr + self.offset_of(field))
|
167
|
+
end
|
168
|
+
|
169
|
+
# Contains dsl_layout and some support methods that an 'includee' of
|
170
|
+
# DryStructHelper will have available as class methods.
|
171
|
+
module ClassMethods
|
172
|
+
# returns the structure metadata for this class based on
|
173
|
+
# the dsl_layout definitions
|
174
|
+
def dsl_metadata
|
175
|
+
@dsl_metadata
|
176
|
+
end
|
154
177
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
178
|
+
def define_field_accessor name, &block
|
179
|
+
if instance_methods.include?("#{name}")
|
180
|
+
warn "WARNING: The name '#{name}' is in use for class #{self} "+
|
181
|
+
"Skipping automatic method creation in dsl_layout block."
|
182
|
+
else
|
183
|
+
define_method name, &block
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
# This passes a block to an instance of DSLStructLayoutBuilder, allowing
|
188
|
+
# for a more declarative syntax with additional metadata to be included.
|
189
|
+
#
|
190
|
+
# dsl_layout() a replacement to layout() and stores the dsl_metadata
|
191
|
+
# gathered about structure members locally and automatically creates
|
192
|
+
# accessor methods for each field in the structure.
|
193
|
+
#
|
194
|
+
# NOTE if a structure field name conflicts with another instance method
|
195
|
+
# already defined in the class, the relevant accessor method is not
|
196
|
+
# created and a warning is issued. This does not apply to methods
|
197
|
+
# defined after the dsl_layout block is called. In other words this
|
198
|
+
# does not affect the overriding of accessor methods in any way.
|
199
|
+
def dsl_layout &block
|
200
|
+
builder = DSLStructLayoutBuilder.new(self)
|
201
|
+
builder.instance_eval(&block)
|
202
|
+
@layout = self.layout(*(builder.__layout_args))
|
203
|
+
@size = @layout.size
|
204
|
+
_class_meths_from_dsl_metadata( builder.__metadata )
|
205
|
+
return @layout
|
206
|
+
end
|
207
|
+
|
208
|
+
def _class_meths_from_dsl_metadata(meta)
|
209
|
+
(@dsl_metadata = meta).each do |spec|
|
210
|
+
name = spec[:name]
|
211
|
+
ftype = spec[:type]
|
212
|
+
if p=spec[:p_struct] and p.kind_of?(Class)
|
213
|
+
define_field_accessor(:"#{name}") do
|
214
|
+
p.new(self[name]) unless self[name].null?
|
215
|
+
end
|
216
|
+
else
|
217
|
+
define_field_accessor(:"#{name}") { self[name] }
|
218
|
+
end
|
219
|
+
|
220
|
+
define_field_accessor(:"#{name}=") {|val| self[name]=val }
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
def self.included(base)
|
226
|
+
base.extend(ClassMethods)
|
162
227
|
end
|
163
228
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
229
|
+
end # class StructHelper
|
230
|
+
|
231
|
+
# This class provides the DSL for StructHelper.dsl_layout.
|
232
|
+
# You probably don't want to use this directly but if you do, to use the DSL,
|
233
|
+
# you may either pass a structure definition into 'instance_eval' or
|
234
|
+
# call methods on the object. The methods __metadata and __layout_args return
|
235
|
+
# structure information back to the caller, which can use them to create
|
236
|
+
# a new structure.
|
237
|
+
class DSLStructLayoutBuilder
|
238
|
+
attr_reader :__metadata, :__layout_args
|
239
|
+
|
240
|
+
# Initializes the a new builder class.
|
241
|
+
def initialize(pbind)
|
242
|
+
@pbind = pbind
|
243
|
+
@__layout_args = []
|
244
|
+
@__metadata = []
|
245
|
+
yield self if block_given?
|
246
|
+
end
|
247
|
+
|
248
|
+
# A pointer to a structure. The structure does not allocate the entire
|
249
|
+
# space for the structure pointed to, just a pointer. When calling the
|
250
|
+
# accessor for a p_struct field, a new instance of the FFI::Struct type
|
251
|
+
# for the pointer will be returned.
|
252
|
+
def p_struct(name, klass, o={})
|
253
|
+
unless klass.kind_of?(Class)
|
254
|
+
raise(TypeError, "klass must be a Class")
|
170
255
|
end
|
256
|
+
opts = o.merge(:p_struct => klass)
|
257
|
+
offset = opts[:offset]
|
258
|
+
field(name, :pointer, opts)
|
171
259
|
end
|
172
260
|
|
173
|
-
#
|
174
|
-
#
|
261
|
+
# Declaratively adds a field to the structure.
|
262
|
+
#
|
263
|
+
# Syntax:
|
175
264
|
#
|
176
|
-
#
|
177
|
-
# gathered about structure members locally and automatically creates
|
178
|
-
# accessor methods for each field in the structure.
|
265
|
+
# field field_name, ctype, { ... metadata ... }
|
179
266
|
#
|
180
|
-
#
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
@
|
190
|
-
|
191
|
-
return @layout
|
267
|
+
# :offset is a special key in metadata, specifies the offset of the field.
|
268
|
+
def field(name, type, o={})
|
269
|
+
opts = o.merge(:name => name, :type => type)
|
270
|
+
offset = opts[:offset]
|
271
|
+
|
272
|
+
@__layout_args << name
|
273
|
+
@__layout_args << type
|
274
|
+
@__layout_args << offset if offset
|
275
|
+
|
276
|
+
@__metadata << opts
|
277
|
+
return opts
|
192
278
|
end
|
193
279
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
280
|
+
alias array field
|
281
|
+
alias struct field
|
282
|
+
alias union field
|
283
|
+
|
284
|
+
# Experimental - Allows specifying structure fields by taking a missing
|
285
|
+
# method name as field name for the structure.
|
286
|
+
def method_missing(name, type, *extra)
|
287
|
+
o={}
|
288
|
+
if extra.size > 1
|
289
|
+
raise(ArgumentError,
|
290
|
+
"Bad field syntax. Use: 'name :type, {optional extra parameters}'")
|
291
|
+
elsif h=extra.first
|
292
|
+
if h.kind_of? Hash
|
293
|
+
o=h
|
202
294
|
else
|
203
|
-
|
295
|
+
raise(TypeError, "Options must be provided as a hash.")
|
204
296
|
end
|
205
|
-
|
206
|
-
define_field_accessor(:"#{name}=") {|val| self[name]=val }
|
207
297
|
end
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
def self.included(base)
|
212
|
-
base.extend(ClassMethods)
|
213
|
-
end
|
298
|
+
opts = o.merge(:name => name, :type => type)
|
299
|
+
offset = opts[:offset]
|
214
300
|
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
# You probably don't want to use this directly but if you do, to use the DSL,
|
219
|
-
# you may either pass a structure definition into 'instance_eval' or
|
220
|
-
# call methods on the object. The methods __metadata and __layout_args return
|
221
|
-
# structure information back to the caller, which can use them to create
|
222
|
-
# a new structure.
|
223
|
-
class DSLStructLayoutBuilder
|
224
|
-
attr_reader :__metadata, :__layout_args
|
225
|
-
|
226
|
-
# Initializes the a new builder class.
|
227
|
-
def initialize(pbind)
|
228
|
-
@pbind = pbind
|
229
|
-
@__layout_args = []
|
230
|
-
@__metadata = []
|
231
|
-
yield self if block_given?
|
232
|
-
end
|
301
|
+
@__layout_args << name
|
302
|
+
@__layout_args << type
|
303
|
+
@__layout_args << offset if offset
|
233
304
|
|
234
|
-
|
235
|
-
|
236
|
-
# accessor for a p_struct field, a new instance of the FFI::Struct type
|
237
|
-
# for the pointer will be returned.
|
238
|
-
def p_struct(name, klass, o={})
|
239
|
-
unless klass.kind_of?(Class)
|
240
|
-
raise(TypeError, "klass must be a Class")
|
305
|
+
@__metadata << opts
|
306
|
+
return opts
|
241
307
|
end
|
242
|
-
|
243
|
-
offset = opts[:offset]
|
244
|
-
field(name, :pointer, opts)
|
245
|
-
end
|
308
|
+
end # DSLStructLayoutBuilder
|
246
309
|
|
247
|
-
#
|
248
|
-
#
|
249
|
-
#
|
250
|
-
|
251
|
-
# field field_name, ctype, { ... metadata ... }
|
252
|
-
#
|
253
|
-
# :offset is a special key in metadata, specifies the offset of the field.
|
254
|
-
def field(name, type, o={})
|
255
|
-
opts = o.merge(:name => name, :type => type)
|
256
|
-
offset = opts[:offset]
|
257
|
-
|
258
|
-
@__layout_args << name
|
259
|
-
@__layout_args << type
|
260
|
-
@__layout_args << offset if offset
|
310
|
+
# ConstMap can be used to organize and lookup value to constant mappings and
|
311
|
+
# vice versa. Constants can be imported from a namespace based on a regular
|
312
|
+
# expression or other means.
|
313
|
+
module ConstMap
|
261
314
|
|
262
|
-
|
263
|
-
|
264
|
-
|
315
|
+
def self.included(klass)
|
316
|
+
klass.extend(ConstMap)
|
317
|
+
end
|
265
318
|
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
elsif h=extra.first
|
278
|
-
if h.kind_of? Hash
|
279
|
-
o=h
|
280
|
-
else
|
281
|
-
raise(TypeError, "Options must be provided as a hash.")
|
319
|
+
# A flexible name to value lookup.
|
320
|
+
#
|
321
|
+
# @param [String, Symbol, Integer] arg
|
322
|
+
# Use a Symbol or String as a name to lookup its value. Use an
|
323
|
+
# Integer to lookup the corresponding name.
|
324
|
+
#
|
325
|
+
def [](arg)
|
326
|
+
if arg.is_a? Integer
|
327
|
+
list.invert[arg]
|
328
|
+
elsif arg.is_a? String or arg.is_a? Symbol
|
329
|
+
list[arg.to_s.upcase]
|
282
330
|
end
|
283
331
|
end
|
284
|
-
opts = o.merge(:name => name, :type => type)
|
285
|
-
offset = opts[:offset]
|
286
332
|
|
287
|
-
|
288
|
-
|
289
|
-
|
333
|
+
# Generates a hash of all the constant names mapped to value. Usually,
|
334
|
+
# it's a good idea to override this like so to cache results in
|
335
|
+
# derived modules:
|
336
|
+
#
|
337
|
+
# def list; @@list ||= super() ; end
|
338
|
+
#
|
339
|
+
def list
|
340
|
+
constants.inject({}){|h,c| h.merge! c => const_get(c) }
|
341
|
+
end
|
290
342
|
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
343
|
+
# When called from a module definition or class method, this method
|
344
|
+
# imports all the constants from # namespace 'nspace' that start with
|
345
|
+
# into the local namespace as constants named with whatever follows the
|
346
|
+
# prefix. Only constant names that match [A-Z][A-Z0-9_]+ are imported,
|
347
|
+
# the rest are ignored.
|
348
|
+
#
|
349
|
+
# This method also yields the (short) constant name and value to a block
|
350
|
+
# if one is provided. The block works like [...].select {|c,v| ... } in
|
351
|
+
# that the value is not mapped if the block returns nil or false.
|
352
|
+
def slurp_constants(nspace, prefix)
|
353
|
+
nspace.constants.grep(/^(#{prefix}([A-Z][A-Z0-9_]+))$/) do
|
354
|
+
c = $2
|
355
|
+
v = nspace.const_get($1)
|
356
|
+
next if block_given? and not yield(c,v)
|
357
|
+
const_set c, v
|
358
|
+
end
|
359
|
+
end
|
360
|
+
end # ConstMap
|
295
361
|
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
362
|
+
# Behaves just like ConstFlags, except that it returns a list
|
363
|
+
# of names for the flags. Name string lookups work same way as
|
364
|
+
# ConstFlags.
|
365
|
+
module ConstFlagsMap
|
366
|
+
include ConstMap
|
300
367
|
|
301
|
-
|
302
|
-
|
303
|
-
|
368
|
+
def self.included(klass)
|
369
|
+
klass.extend(ConstFlagsMap)
|
370
|
+
end
|
304
371
|
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
372
|
+
# A flexible lookup method for name to bit-flag mappings.
|
373
|
+
#
|
374
|
+
# @param [String, Symbol, Integer] arg
|
375
|
+
# Symbol or String as a name to lookup a bit-flag value, or an
|
376
|
+
# Integer to lookup a corresponding names for the flags present in it.
|
377
|
+
def [](arg)
|
378
|
+
if arg.is_a? Integer
|
379
|
+
ret = []
|
380
|
+
if arg == 0
|
381
|
+
n = list.invert[0]
|
382
|
+
ret << n if n
|
383
|
+
else
|
384
|
+
list.invert.sort.each {|v,n| ret << n if v !=0 and (v & arg) == v }
|
385
|
+
end
|
386
|
+
return ret
|
387
|
+
elsif arg.is_a? String or arg.is_a? Symbol
|
388
|
+
list[arg.to_s.upcase]
|
389
|
+
end
|
316
390
|
end
|
317
|
-
end
|
391
|
+
end # ConstFlagsMap
|
318
392
|
|
319
|
-
# Generates a hash of all the constant names mapped to value. Usually,
|
320
|
-
# it's a good idea to override this like so to cache results in
|
321
|
-
# derived modules:
|
322
|
-
#
|
323
|
-
# def list; @@list ||= super() ; end
|
324
|
-
#
|
325
|
-
def list
|
326
|
-
constants.inject({}){|h,c| h.merge! c => const_get(c) }
|
327
|
-
end
|
328
393
|
|
329
|
-
|
330
|
-
|
331
|
-
# into the local namespace as constants named with whatever follows the
|
332
|
-
# prefix. Only constant names that match [A-Z][A-Z0-9_]+ are imported,
|
333
|
-
# the rest are ignored.
|
334
|
-
#
|
335
|
-
# This method also yields the (short) constant name and value to a block
|
336
|
-
# if one is provided. The block works like [...].select {|c,v| ... } in
|
337
|
-
# that the value is not mapped if the block returns nil or false.
|
338
|
-
def slurp_constants(nspace, prefix)
|
339
|
-
nspace.constants.grep(/^(#{prefix}([A-Z][A-Z0-9_]+))$/) do
|
340
|
-
c = $2
|
341
|
-
v = nspace.const_get($1)
|
342
|
-
next if block_given? and not yield(c,v)
|
343
|
-
const_set c, v
|
344
|
-
end
|
345
|
-
end
|
346
|
-
end # ConstMap
|
394
|
+
module NetEndian
|
395
|
+
extend ::FFI::Library
|
347
396
|
|
348
|
-
|
349
|
-
|
350
|
-
# ConstFlags.
|
351
|
-
module ConstFlagsMap
|
352
|
-
include ConstMap
|
397
|
+
ffi_lib FFI::Library::LIBC
|
398
|
+
begin; ffi_lib 'wsock32'; rescue LoadError; end
|
353
399
|
|
354
|
-
|
355
|
-
|
356
|
-
|
400
|
+
attach_function :htons, [:uint16], :uint16
|
401
|
+
attach_function :ntohs, [:uint16], :uint16
|
402
|
+
attach_function :htonl, [:uint32], :uint32
|
403
|
+
attach_function :ntohl, [:uint32], :uint32
|
357
404
|
|
358
|
-
|
405
|
+
I16_convert = [method(:ntohs), method(:htons)]
|
406
|
+
I32_convert = [method(:ntohl), method(:htonl)]
|
407
|
+
|
408
|
+
ENDIAN_METHS = {
|
409
|
+
::FFI.find_type(:int16) => I16_convert,
|
410
|
+
::FFI.find_type(:uint16) => I16_convert,
|
411
|
+
::FFI.find_type(:int32) => I32_convert,
|
412
|
+
::FFI.find_type(:uint32) => I32_convert,
|
413
|
+
}
|
414
|
+
end # NetEndian
|
415
|
+
|
416
|
+
|
417
|
+
# A special helper for network packet structures that use big-endian or
|
418
|
+
# "network" byte-order. This helper generates read/write accessors that
|
419
|
+
# automatically call the appropriate byte conversion function, ntohs/ntohl
|
420
|
+
# for 'reading' a 16/32 bit field, and htons/htonl for writing to one.
|
359
421
|
#
|
360
|
-
#
|
361
|
-
#
|
362
|
-
#
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
end
|
372
|
-
return ret
|
373
|
-
elsif arg.is_a? String or arg.is_a? Symbol
|
374
|
-
list[arg.to_s.upcase]
|
422
|
+
# NOTE this helper does not currently do anything special for 64-bit or
|
423
|
+
# higher values but this might be added at some point if the need arises.
|
424
|
+
#
|
425
|
+
# NOTE unlike the StructHelper module, no special relevance is given
|
426
|
+
# to fields with a ":p_struct" option or defined with the p_struct DSL
|
427
|
+
# method. These are ignored and treated like any other field. A net struct
|
428
|
+
# generally doesn't contain pointers into native memory anyway.
|
429
|
+
module NetStructHelper
|
430
|
+
def self.included(base)
|
431
|
+
base.instance_eval { include StructHelper }
|
432
|
+
base.extend(ClassMethods)
|
375
433
|
end
|
376
|
-
end
|
377
|
-
end # ConstFlagsMap
|
378
|
-
|
379
|
-
|
380
|
-
module NetEndian
|
381
|
-
extend ::FFI::Library
|
382
|
-
|
383
|
-
ffi_lib FFI::Library::LIBC
|
384
|
-
begin; ffi_lib 'wsock32'; rescue LoadError; end
|
385
|
-
|
386
|
-
attach_function :htons, [:uint16], :uint16
|
387
|
-
attach_function :ntohs, [:uint16], :uint16
|
388
|
-
attach_function :htonl, [:uint32], :uint32
|
389
|
-
attach_function :ntohl, [:uint32], :uint32
|
390
|
-
|
391
|
-
I16_convert = [method(:ntohs), method(:htons)]
|
392
|
-
I32_convert = [method(:ntohl), method(:htonl)]
|
393
|
-
|
394
|
-
ENDIAN_METHS = {
|
395
|
-
::FFI.find_type(:int16) => I16_convert,
|
396
|
-
::FFI.find_type(:uint16) => I16_convert,
|
397
|
-
::FFI.find_type(:int32) => I32_convert,
|
398
|
-
::FFI.find_type(:uint32) => I32_convert,
|
399
|
-
}
|
400
|
-
end # NetEndian
|
401
|
-
|
402
|
-
|
403
|
-
# A special helper for network packet structures that use big-endian or
|
404
|
-
# "network" byte-order. This helper generates read/write accessors that
|
405
|
-
# automatically call the appropriate byte conversion function, ntohs/ntohl
|
406
|
-
# for 'reading' a 16/32 bit field, and htons/htonl for writing to one.
|
407
|
-
#
|
408
|
-
# NOTE this helper does not currently do anything special for 64-bit or
|
409
|
-
# higher values but this might be added at some point if the need arises.
|
410
|
-
#
|
411
|
-
# NOTE unlike the StructHelper module, no special relevance is given
|
412
|
-
# to fields with a ":p_struct" option or defined with the p_struct DSL
|
413
|
-
# method. These are ignored and treated like any other field. A net struct
|
414
|
-
# generally doesn't contain pointers into native memory anyway.
|
415
|
-
module NetStructHelper
|
416
|
-
def self.included(base)
|
417
|
-
base.instance_eval { include StructHelper }
|
418
|
-
base.extend(ClassMethods)
|
419
|
-
end
|
420
434
|
|
421
|
-
|
435
|
+
module ClassMethods
|
422
436
|
|
423
|
-
|
437
|
+
private
|
424
438
|
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
439
|
+
def _class_meths_from_dsl_metadata(meta)
|
440
|
+
(@dsl_metadata = meta).each do |spec|
|
441
|
+
name = spec[:name]
|
442
|
+
type = spec[:type]
|
429
443
|
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
444
|
+
# Create endian swapper accessors methods for each applicable
|
445
|
+
# field
|
446
|
+
if( type.kind_of?(Symbol) and
|
447
|
+
cnv=NetEndian::ENDIAN_METHS[ ::FFI.find_type(type) ] )
|
448
|
+
define_method(:"#{name}"){ cnv[0].call(self[name]) }
|
449
|
+
define_method(:"#{name}="){|val| self[name] = cnv[1].call(val) }
|
450
|
+
else
|
451
|
+
define_field_accessor(:"#{name}"){ self[name] }
|
452
|
+
define_field_accessor(:"#{name}="){|val| self[name]=val }
|
453
|
+
end
|
440
454
|
|
455
|
+
end
|
441
456
|
end
|
442
457
|
end
|
443
|
-
end
|
444
|
-
end # NetStructHelper
|
458
|
+
end # NetStructHelper
|
445
459
|
|
446
|
-
end #
|
460
|
+
end # DRY
|
461
|
+
end # FFI
|
447
462
|
|
metadata
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ffi_dry
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 11
|
9
|
+
version: 0.1.11
|
5
10
|
platform: ruby
|
6
11
|
authors:
|
7
12
|
- Eric Monti
|
@@ -9,69 +14,23 @@ autorequire:
|
|
9
14
|
bindir: bin
|
10
15
|
cert_chain: []
|
11
16
|
|
12
|
-
date: 2010-
|
17
|
+
date: 2010-06-02 00:00:00 -05:00
|
13
18
|
default_executable:
|
14
19
|
dependencies:
|
15
20
|
- !ruby/object:Gem::Dependency
|
16
21
|
name: ffi
|
17
|
-
|
18
|
-
|
19
|
-
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
24
|
requirements:
|
21
25
|
- - ">="
|
22
26
|
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
- 5
|
30
|
+
- 0
|
23
31
|
version: 0.5.0
|
24
|
-
version:
|
25
|
-
- !ruby/object:Gem::Dependency
|
26
|
-
name: rspec
|
27
|
-
type: :development
|
28
|
-
version_requirement:
|
29
|
-
version_requirements: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - ">="
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: "0"
|
34
|
-
version:
|
35
|
-
- !ruby/object:Gem::Dependency
|
36
|
-
name: yard
|
37
|
-
type: :development
|
38
|
-
version_requirement:
|
39
|
-
version_requirements: !ruby/object:Gem::Requirement
|
40
|
-
requirements:
|
41
|
-
- - ">="
|
42
|
-
- !ruby/object:Gem::Version
|
43
|
-
version: "0"
|
44
|
-
version:
|
45
|
-
- !ruby/object:Gem::Dependency
|
46
|
-
name: ffi
|
47
32
|
type: :runtime
|
48
|
-
|
49
|
-
version_requirements: !ruby/object:Gem::Requirement
|
50
|
-
requirements:
|
51
|
-
- - ">="
|
52
|
-
- !ruby/object:Gem::Version
|
53
|
-
version: 0.5.0
|
54
|
-
version:
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: rspec
|
57
|
-
type: :development
|
58
|
-
version_requirement:
|
59
|
-
version_requirements: !ruby/object:Gem::Requirement
|
60
|
-
requirements:
|
61
|
-
- - ">="
|
62
|
-
- !ruby/object:Gem::Version
|
63
|
-
version: "0"
|
64
|
-
version:
|
65
|
-
- !ruby/object:Gem::Dependency
|
66
|
-
name: yard
|
67
|
-
type: :development
|
68
|
-
version_requirement:
|
69
|
-
version_requirements: !ruby/object:Gem::Requirement
|
70
|
-
requirements:
|
71
|
-
- - ">="
|
72
|
-
- !ruby/object:Gem::Version
|
73
|
-
version: "0"
|
74
|
-
version:
|
33
|
+
version_requirements: *id001
|
75
34
|
description: Provides some useful modules, classes, and methods for FFI bindings as well as a DSL-like syntax for FFI::Struct layouts
|
76
35
|
email: emonti@matasano.com
|
77
36
|
executables: []
|
@@ -99,6 +58,8 @@ files:
|
|
99
58
|
- spec/spec_helper.rb
|
100
59
|
has_rdoc: true
|
101
60
|
homepage: http://github.com/emonti/ffi_dry
|
61
|
+
licenses: []
|
62
|
+
|
102
63
|
post_install_message:
|
103
64
|
rdoc_options:
|
104
65
|
- --charset=UTF-8
|
@@ -108,20 +69,22 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
108
69
|
requirements:
|
109
70
|
- - ">="
|
110
71
|
- !ruby/object:Gem::Version
|
72
|
+
segments:
|
73
|
+
- 0
|
111
74
|
version: "0"
|
112
|
-
version:
|
113
75
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
114
76
|
requirements:
|
115
77
|
- - ">="
|
116
78
|
- !ruby/object:Gem::Version
|
79
|
+
segments:
|
80
|
+
- 0
|
117
81
|
version: "0"
|
118
|
-
version:
|
119
82
|
requirements: []
|
120
83
|
|
121
84
|
rubyforge_project:
|
122
|
-
rubygems_version: 1.3.
|
85
|
+
rubygems_version: 1.3.6
|
123
86
|
signing_key:
|
124
|
-
specification_version:
|
87
|
+
specification_version: 3
|
125
88
|
summary: Syntactic sugar and helper utilities for FFI
|
126
89
|
test_files:
|
127
90
|
- spec/ffi_dry_spec.rb
|