emonti-ffi_dry 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +5 -0
- data/LICENSE +20 -0
- data/README.rdoc +150 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/lib/ffi/dry.rb +350 -0
- data/lib/ffi_dry.rb +1 -0
- data/samples/afmap.rb +28 -0
- data/samples/basic.rb +35 -0
- data/samples/describer.rb +48 -0
- data/spec/ffi_dry_spec.rb +7 -0
- data/spec/spec_helper.rb +9 -0
- metadata +96 -0
data/.document
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Eric Monti
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
= ffi_dry
|
2
|
+
|
3
|
+
Helpers, sugar methods, and new features over Ruby FFI to do some common
|
4
|
+
things and add support for some uncommon ones.
|
5
|
+
|
6
|
+
== Requirements
|
7
|
+
|
8
|
+
* ffi-ffi (>= 0.5.0) - github.com/ffi/ffi
|
9
|
+
|
10
|
+
|
11
|
+
== Synopsis
|
12
|
+
|
13
|
+
|
14
|
+
One major feature is a 'dsl'-like syntax for declaring structure members
|
15
|
+
in FFI::Struct or FFI::ManagedStruct definitions.
|
16
|
+
|
17
|
+
require 'rubygems'
|
18
|
+
require 'ffi'
|
19
|
+
require 'ffi/dry'
|
20
|
+
|
21
|
+
class SomeStruct < FFI::Struct
|
22
|
+
include FFI::DRY::StructHelper
|
23
|
+
|
24
|
+
# we get a new way of specifying layouts with a 'dsl'-like syntax
|
25
|
+
# The :desc => ... part is arbitrary and can be used however we like.
|
26
|
+
dsl_layout do
|
27
|
+
field :field1, :uint16, :desc => 'this is field 1'
|
28
|
+
field :field2, :uint16, :desc => 'this is field 2'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
ss0=SomeStruct.new
|
34
|
+
|
35
|
+
With the declaration above, we specified :desc hash value, which was stored
|
36
|
+
in metadata along with the field name and type.
|
37
|
+
|
38
|
+
pp ss0.dsl_metadata
|
39
|
+
[{:type=>:uint16, :name=>:field1, :desc=>"this is field 1"},
|
40
|
+
{:type=>:uint16, :name=>:field2, :desc=>"this is field 2"}]
|
41
|
+
# => nil
|
42
|
+
|
43
|
+
|
44
|
+
And we get free additional ways of instantiating and declaring values during
|
45
|
+
initialization. (The FFI standard ways still work too)
|
46
|
+
|
47
|
+
raw_data = "\x00\x00\xff\xff"
|
48
|
+
|
49
|
+
ss1=SomeStruct.new :raw => raw_data
|
50
|
+
ss2=SomeStruct.new :raw => raw_data, :field1 => 1, :field2 => 2
|
51
|
+
ss3=SomeStruct.new {|x| x.field1=1 }
|
52
|
+
ss4=SomeStruct.new(:raw => raw_data) {|x| x.field1=1 }
|
53
|
+
|
54
|
+
[ ss0,
|
55
|
+
ss1,
|
56
|
+
ss2,
|
57
|
+
ss3,
|
58
|
+
ss4].each_with_index {|x,i| p ["ss#{i}",[x.field1, x.field2]]}
|
59
|
+
|
60
|
+
Here's an example which utilizes that arbitrary ':desc' parameter in a
|
61
|
+
"neighborly" way.
|
62
|
+
|
63
|
+
require 'rubygems'
|
64
|
+
require 'ffi'
|
65
|
+
require 'ffi/dry'
|
66
|
+
|
67
|
+
class NeighborlyStruct < ::FFI::Struct
|
68
|
+
include ::FFI::DRY::StructHelper
|
69
|
+
|
70
|
+
def self.describe
|
71
|
+
print "Struct: #{self.class}"
|
72
|
+
dsl_metadata().each_with_index do |spec, i|
|
73
|
+
print " Field #{i}\n"
|
74
|
+
print " name: #{spec[:name].inspect}\n"
|
75
|
+
print " type: #{spec[:type].inspect}\n"
|
76
|
+
print " desc: #{spec[:desc]}\n\n"
|
77
|
+
end
|
78
|
+
print "\n"
|
79
|
+
end
|
80
|
+
def describe; self.class.describe; end
|
81
|
+
end
|
82
|
+
|
83
|
+
class TestStruct < NeighborlyStruct
|
84
|
+
dsl_layout do
|
85
|
+
field :field1, :uint8, :desc => "test field 1"
|
86
|
+
field :field2, :uint8, :desc => "test field 2"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
class SomeStruct < NeighborlyStruct
|
91
|
+
dsl_layout do
|
92
|
+
field :kind, :uint8, :desc => "a type identifier"
|
93
|
+
struct :tst, TestStruct, :desc => "a nested TestStruct"
|
94
|
+
field :len, :uint8, :desc => "8-bit size value (>= self.size+2)"
|
95
|
+
array :str, [:char,255],
|
96
|
+
:desc => "a string up to 255 bytes bound by :len"
|
97
|
+
end
|
98
|
+
|
99
|
+
# override kind getter method
|
100
|
+
def kind
|
101
|
+
[:default, :bar, :baz][ self[:kind] ]
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
s1=TestStruct.new
|
106
|
+
s2=SomeStruct.new
|
107
|
+
|
108
|
+
puts "*"*70
|
109
|
+
s1.describe
|
110
|
+
# we get a dump of metadata
|
111
|
+
puts "*"*70
|
112
|
+
s2.describe
|
113
|
+
# we get a dump of metadata
|
114
|
+
|
115
|
+
|
116
|
+
There's also some helpers for collecting lookup maps for constants, a common
|
117
|
+
and handy thing when porting various libraries.
|
118
|
+
|
119
|
+
require 'ffi/dry'
|
120
|
+
require 'socket'
|
121
|
+
|
122
|
+
module AddressFamily
|
123
|
+
include FFI::DRY::ConstMap
|
124
|
+
slurp_constants ::Socket, "AF_"
|
125
|
+
def list ; @@list ||= super() ; end
|
126
|
+
end
|
127
|
+
|
128
|
+
AddressFamily now has all the constants it found for Socket::AF_*
|
129
|
+
|
130
|
+
AddressFamily::INET
|
131
|
+
AddressFamily::LINK
|
132
|
+
AddressFamily::INET6
|
133
|
+
etc...
|
134
|
+
|
135
|
+
We can do type and value lookups using []
|
136
|
+
AddressFamily[2] # => "INET"
|
137
|
+
AddressFamily["INET"] # => 2
|
138
|
+
|
139
|
+
We can get a hash of all key-value pairs with .list
|
140
|
+
AddressFamily.list
|
141
|
+
# => {"NATM"=>31, "DLI"=>13, "UNIX"=>1, "NETBIOS"=>33, ...}
|
142
|
+
|
143
|
+
... which can be inverted for a reverse mapping
|
144
|
+
AddressFamily.list.invert
|
145
|
+
# => {16=>"APPLETALK", 5=>"CHAOS", 27=>"NDRV", 0=>"UNSPEC", ...}
|
146
|
+
|
147
|
+
|
148
|
+
== Copyright
|
149
|
+
|
150
|
+
Copyright (c) 2009 Eric Monti. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/clean'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'jeweler'
|
7
|
+
Jeweler::Tasks.new do |gem|
|
8
|
+
gem.name = "ffi_dry"
|
9
|
+
gem.summary = %Q{Syntactic sugar and helper utilities for FFI}
|
10
|
+
gem.description = %Q{Provides a some useful modules, classes, and methods as well as a DSL-like syntax for FFI::Struct layouts}
|
11
|
+
gem.email = "emonti@matasano.com"
|
12
|
+
gem.homepage = "http://github.com/emonti/ffi_dry"
|
13
|
+
gem.authors = ["Eric Monti"]
|
14
|
+
gem.add_dependency "ffi-ffi", ">= 0.5.0"
|
15
|
+
gem.add_development_dependency "rspec"
|
16
|
+
gem.add_development_dependency "yard"
|
17
|
+
end
|
18
|
+
rescue LoadError
|
19
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'spec/rake/spectask'
|
23
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
24
|
+
spec.libs << 'lib' << 'spec'
|
25
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
26
|
+
end
|
27
|
+
|
28
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
29
|
+
spec.libs << 'lib' << 'spec'
|
30
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
31
|
+
spec.rcov = true
|
32
|
+
end
|
33
|
+
|
34
|
+
task :spec => :check_dependencies
|
35
|
+
|
36
|
+
task :default => :spec
|
37
|
+
|
38
|
+
begin
|
39
|
+
require 'yard'
|
40
|
+
YARD::Rake::YardocTask.new
|
41
|
+
rescue LoadError
|
42
|
+
task :yardoc do
|
43
|
+
abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
|
44
|
+
end
|
45
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/lib/ffi/dry.rb
ADDED
@@ -0,0 +1,350 @@
|
|
1
|
+
begin; require 'rubygems'; rescue LoadError; end
|
2
|
+
|
3
|
+
require 'ffi'
|
4
|
+
|
5
|
+
module FFI::DRY
|
6
|
+
|
7
|
+
# A module to add syntactic sugar and some nice automatic getter/setter
|
8
|
+
# logic to FFI::Struct, FFI::ManagedStruct, etc.
|
9
|
+
#
|
10
|
+
# For example:
|
11
|
+
# require 'rubygems'
|
12
|
+
# require 'ffi'
|
13
|
+
# require 'ffi/dry'
|
14
|
+
# require 'pp'
|
15
|
+
#
|
16
|
+
# class SomeStruct < FFI::Struct
|
17
|
+
# include FFI::DRY::StructHelper
|
18
|
+
#
|
19
|
+
# # we get a new way of specifying layouts with a 'dsl'-like syntax
|
20
|
+
# dsl_layout do
|
21
|
+
# field :field1, :uint16, :desc => 'this is field 1'
|
22
|
+
# field :field2, :uint16, :desc => 'this is field 2'
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# ss0=SomeStruct.new
|
27
|
+
#
|
28
|
+
# pp ss0.dsl_metadata # we can look at definition metadata
|
29
|
+
#
|
30
|
+
# # produces...
|
31
|
+
# # [{:type=>:uint16, :name=>:field1, :desc=>"this is field 1"},
|
32
|
+
# # {:type=>:uint16, :name=>:field2, :desc=>"this is field 2"}]
|
33
|
+
#
|
34
|
+
# # And we have additional ways of instantiating and declaring values
|
35
|
+
# # during initialization. (The FFI standard ways still work too)
|
36
|
+
#
|
37
|
+
# raw_data = "\x00\x00\xff\xff"
|
38
|
+
#
|
39
|
+
# ss1=SomeStruct.new :raw => raw_data
|
40
|
+
# ss2=SomeStruct.new :raw => raw_data, :field1 => 1, :field2 => 2
|
41
|
+
# ss3=SomeStruct.new {|x| x.field1=1 }
|
42
|
+
# ss4=SomeStruct.new(:raw => raw_data) {|x| x.field1=1 }
|
43
|
+
#
|
44
|
+
# [ ss0,
|
45
|
+
# ss1,
|
46
|
+
# ss2,
|
47
|
+
# ss3,
|
48
|
+
# ss4].each_with_index {|x,i| pp ["ss#{i}",[x.field1, x.field2]]}
|
49
|
+
#
|
50
|
+
# # produces...
|
51
|
+
# # ["ss0", [0, 0]]
|
52
|
+
# # ["ss1", [0, 65535]]
|
53
|
+
# # ["ss2", [1, 2]]
|
54
|
+
# # ["ss3", [1, 0]]
|
55
|
+
# # ["ss4", [1, 65535]]
|
56
|
+
#
|
57
|
+
module StructHelper #< ::FFI::Struct
|
58
|
+
|
59
|
+
attr_reader :dsl_metadata
|
60
|
+
|
61
|
+
# Adds field setting on initialization to ::FFI::Struct.new as well as
|
62
|
+
# a "yield(self) if block_given?" at the end.
|
63
|
+
#
|
64
|
+
# The field initialization kicks in if there is only one argument, and it
|
65
|
+
# is a Hash.
|
66
|
+
#
|
67
|
+
# Note:
|
68
|
+
# The :raw parameter is a special tag in the hash. The value is taken as a
|
69
|
+
# string and initialized into a new FFI::MemoryPointer which this Struct
|
70
|
+
# then overlays.
|
71
|
+
#
|
72
|
+
# If your struct layout has a field named :raw field, it won't be
|
73
|
+
# assignable through the hash argument.
|
74
|
+
#
|
75
|
+
# See also: set_fields() which is called automatically on the hash, minus
|
76
|
+
# the :raw tag.
|
77
|
+
#
|
78
|
+
def initialize(*args)
|
79
|
+
if args.size == 1 and (oparams=args[0]).is_a? Hash
|
80
|
+
params = oparams.dup
|
81
|
+
if raw=params.delete(:raw)
|
82
|
+
super( ::FFI::MemoryPointer.from_string(raw) )
|
83
|
+
else
|
84
|
+
super()
|
85
|
+
end
|
86
|
+
set_fields(params)
|
87
|
+
else
|
88
|
+
super(*args)
|
89
|
+
end
|
90
|
+
@dsl_metadata = self.class.dsl_metadata
|
91
|
+
yield self if block_given?
|
92
|
+
end
|
93
|
+
|
94
|
+
# Sets field values in the struct specified by their symbolic name from a
|
95
|
+
# hash of ':field => value' pairs. Uses accessor field wrapper methods
|
96
|
+
# instead of a direct reference to the field (as in "obj.field1 = x",
|
97
|
+
# not "obj[:field] = x"). The difference is subtle, but this allows you
|
98
|
+
# to take advantage of any wrapper methods you override when initializing
|
99
|
+
# a new object. The only caveat is that the wrapper method must be named
|
100
|
+
# the same as the field, and the field must be included in members() from
|
101
|
+
# the layout.
|
102
|
+
#
|
103
|
+
# This method is called automatically if you are using the initialize()
|
104
|
+
# method provided in the DryStruct class and passing it a Hash as its only
|
105
|
+
# argument.
|
106
|
+
def set_fields(params)
|
107
|
+
params.keys.each do |p|
|
108
|
+
if members().include?(p)
|
109
|
+
self.__send__(:"#{p}=", params[p])
|
110
|
+
else
|
111
|
+
raise(::ArgumentError, "#{self.class} does not have a '#{p}' field")
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Returns a new instance of self.class containing a seperately allocated
|
117
|
+
# copy of all our data. This abstract method should usually be called
|
118
|
+
# with super() from overridden 'copy' implementations for structures
|
119
|
+
# containing pointers to other memory or variable length data at the end.
|
120
|
+
#
|
121
|
+
# Note also that, by default, this implementation determine's size
|
122
|
+
# automatically based on the structure size. This is comparable to
|
123
|
+
# sizeof(some_struct) in C. However, you can supply a 'grown' parameter
|
124
|
+
# which can be used to add to the size of the copied instance as it is
|
125
|
+
# allocated and copied.
|
126
|
+
def copy(grown=0)
|
127
|
+
self.class.new( :raw => self.to_ptr.read_string(self.size+grown) )
|
128
|
+
end
|
129
|
+
|
130
|
+
# Returns a pointer to the specified field, which is the name assigned
|
131
|
+
# to a member in the layout.
|
132
|
+
def ptr_to(field)
|
133
|
+
x = self[field] # this is actually a test, to raise if missing
|
134
|
+
return (self.to_ptr + self.offset_of(field))
|
135
|
+
end
|
136
|
+
|
137
|
+
# Contains dsl_layout and some support methods that an 'includee' of
|
138
|
+
# DryStructHelper will have available as class methods.
|
139
|
+
module ClassMethods
|
140
|
+
# returns the structure metadata for this class based on
|
141
|
+
# the dsl_layout definitions
|
142
|
+
def dsl_metadata
|
143
|
+
@dsl_metadata
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
|
148
|
+
# This passes a block to an instance of DSL_StructLayoutBuilder, allowing
|
149
|
+
# for a more declarative syntax.
|
150
|
+
#
|
151
|
+
# It is a replacement to layout() and stores the dsl_metadata gathered
|
152
|
+
# about structure members locally.
|
153
|
+
#
|
154
|
+
#
|
155
|
+
def dsl_layout &block
|
156
|
+
builder = DSL_StructLayoutBuilder.new(self)
|
157
|
+
builder.instance_eval(&block)
|
158
|
+
@layout = builder.build
|
159
|
+
@size = @layout.size
|
160
|
+
_class_do_dsl_metadata( builder.metadata )
|
161
|
+
return @layout
|
162
|
+
end
|
163
|
+
|
164
|
+
def _class_do_dsl_metadata(meta)
|
165
|
+
(@dsl_metadata = meta).each do |spec|
|
166
|
+
name = spec[:name]
|
167
|
+
type = spec[:type]
|
168
|
+
define_method(:"#{name}") do
|
169
|
+
self[name]
|
170
|
+
end unless instance_methods.include?(:"#{name}")
|
171
|
+
define_method(:"#{name}=") do |val|
|
172
|
+
self[name]=val
|
173
|
+
end unless instance_methods.include?(:"#{name}=")
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def self.included(base)
|
179
|
+
base.extend(ClassMethods)
|
180
|
+
end
|
181
|
+
end # class StructHelper
|
182
|
+
|
183
|
+
# This is a wrapper around the FFI::StructLayoutBuilder. Its goal is to
|
184
|
+
# provides a more declarative syntax for defining structures and include
|
185
|
+
# the ability to attach arbitrary dsl_metadata information to structure
|
186
|
+
# fields during definition.
|
187
|
+
#
|
188
|
+
# The "DSL" (and that's really very in-quotes) supplies 3 ways to
|
189
|
+
# define a field (for now):
|
190
|
+
#
|
191
|
+
# field()
|
192
|
+
# array()
|
193
|
+
# struct()
|
194
|
+
#
|
195
|
+
# See the individual method descriptions for more info.
|
196
|
+
#
|
197
|
+
class DSL_StructLayoutBuilder
|
198
|
+
attr_reader :builder, :metadata
|
199
|
+
|
200
|
+
# Initializes the builder with a reference to the structure using it
|
201
|
+
# Instead of duplicating Struct features, we just call back to them.
|
202
|
+
def initialize(pbind)
|
203
|
+
@pbind = pbind
|
204
|
+
@builder = ::FFI::StructLayoutBuilder.new
|
205
|
+
@metadata = []
|
206
|
+
super()
|
207
|
+
end
|
208
|
+
|
209
|
+
# calls StructLayoutBuider.build() on the bulder and returns its
|
210
|
+
# result.
|
211
|
+
def build
|
212
|
+
@builder.build
|
213
|
+
end
|
214
|
+
|
215
|
+
# Calls StructLayoutBuilder.add_struct() on the builder and stores
|
216
|
+
# a metadata hash entry (the opts hash with name and type overridden)
|
217
|
+
#
|
218
|
+
# struct field_name, RubyClass, { ... metadata ... }
|
219
|
+
#
|
220
|
+
def struct(name, klass, o={})
|
221
|
+
unless klass.kind_of?(Class) and klass < ::FFI::Struct
|
222
|
+
raise(::ArgumentError, "klass must be a struct")
|
223
|
+
end
|
224
|
+
|
225
|
+
opts = o.merge(:name => name, :type => klass)
|
226
|
+
offset = opts[:offset]
|
227
|
+
ret=@builder.add_struct(name, klass, offset)
|
228
|
+
@metadata << opts
|
229
|
+
return ret
|
230
|
+
end
|
231
|
+
|
232
|
+
# Calls StructLayoutBuider.add_array() on the builder and stores
|
233
|
+
# a metadata hash entry (the opts hash with name and type overridden)
|
234
|
+
#
|
235
|
+
# Syntax:
|
236
|
+
#
|
237
|
+
# array field_name, [ctype, N], { ... metadata ... }
|
238
|
+
#
|
239
|
+
def array(name, type, o={})
|
240
|
+
unless type.kind_of?(::Array)
|
241
|
+
raise(::ArgumentError, "type must be an array")
|
242
|
+
end
|
243
|
+
|
244
|
+
opts = o.merge(:name => name, :type => type)
|
245
|
+
offset = opts[:offset]
|
246
|
+
mod = enclosing_module
|
247
|
+
ret=@builder.add_array(name, find_type(type[0], mod), type[1], offset)
|
248
|
+
@metadata << opts
|
249
|
+
return ret
|
250
|
+
end
|
251
|
+
|
252
|
+
# Calls StructLayoutBuider.add_field() on the builder and stores
|
253
|
+
# a metadata hash entry (the opts hash with name and type overridden)
|
254
|
+
#
|
255
|
+
# Syntax:
|
256
|
+
#
|
257
|
+
# field field_name, ctype, { ... metadata ... }
|
258
|
+
#
|
259
|
+
def field(name, type, o={})
|
260
|
+
opts = o.merge(:name => name, :type => type)
|
261
|
+
offset = opts[:offset]
|
262
|
+
mod = enclosing_module
|
263
|
+
ret= @builder.add_field(name, find_type(type, mod), offset)
|
264
|
+
@metadata << opts
|
265
|
+
return ret
|
266
|
+
end
|
267
|
+
|
268
|
+
def find_type(*args)
|
269
|
+
@pbind.find_type(*args)
|
270
|
+
end
|
271
|
+
|
272
|
+
def enclosing_module(*args)
|
273
|
+
@pbind.enclosing_module(*args)
|
274
|
+
end
|
275
|
+
|
276
|
+
end
|
277
|
+
|
278
|
+
# Used for creating various value <=> constant mapping modules such as
|
279
|
+
# Ip::Hdr::Proto for IP protocols.
|
280
|
+
module ConstMap
|
281
|
+
|
282
|
+
def self.included(klass)
|
283
|
+
klass.extend(ConstMap)
|
284
|
+
end
|
285
|
+
|
286
|
+
# A flexible lookup. Takes 'arg' as a Symbol or String as a name to lookup
|
287
|
+
# a value, or an Integer to lookup a corresponding name.
|
288
|
+
def [](arg)
|
289
|
+
if arg.is_a? Integer
|
290
|
+
list.invert[arg]
|
291
|
+
elsif arg.is_a? String or arg.is_a? Symbol
|
292
|
+
list[arg.to_s.upcase]
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
# Generates a hash of all the constant names mapped to value. Usually,
|
297
|
+
# it's a good idea to override this like so in derived modules:
|
298
|
+
#
|
299
|
+
# def list; @@list = super() ; end
|
300
|
+
#
|
301
|
+
def list
|
302
|
+
constants.inject({}){|h,c| h.merge! c => const_get(c) }
|
303
|
+
end
|
304
|
+
|
305
|
+
private
|
306
|
+
# When called from a module definition or class method, this method
|
307
|
+
# imports all the constants from # namespace 'nspace' that start with
|
308
|
+
# into the local namespace as constants named with whatever follows the
|
309
|
+
# prefix. Only constant names that match [A-Z][A-Z0-9_]+ are imported,
|
310
|
+
# the rest are ignored.
|
311
|
+
#
|
312
|
+
# This method also yields the (short) constant name and value to a block
|
313
|
+
# if one is provided. The block works like [...].select {|c,v| ... } in
|
314
|
+
# that the value is not mapped if the block returns nil or false.
|
315
|
+
def slurp_constants(nspace, prefix)
|
316
|
+
nspace.constants.grep(/^(#{prefix}([A-Z][A-Z0-9_]+))$/) do
|
317
|
+
c = $2
|
318
|
+
v = nspace.const_get($1)
|
319
|
+
next if block_given? and not yield(c,v)
|
320
|
+
const_set c, v
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
# Behaves just like ConstFlags, except that the [nnn] returns a list
|
326
|
+
# of names for the flags set on nnn. Name string lookups work same way as
|
327
|
+
# ConstFlags.
|
328
|
+
module ConstFlagsMap
|
329
|
+
include ConstMap
|
330
|
+
|
331
|
+
def self.included(klass)
|
332
|
+
klass.extend(ConstFlagsMap)
|
333
|
+
end
|
334
|
+
|
335
|
+
# A flexible lookup. Takes 'arg' as a Symbol or String as a name to lookup
|
336
|
+
# a value, or an Integer to lookup a corresponding names for the flags
|
337
|
+
# present in it.
|
338
|
+
def [](arg)
|
339
|
+
if arg.is_a? Integer
|
340
|
+
ret = []
|
341
|
+
list.invert.sort.each {|v,n| ret << n if (v & arg) == v }
|
342
|
+
return ret
|
343
|
+
elsif arg.is_a? String or arg.is_a? Symbol
|
344
|
+
list[arg.to_s.upcase]
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
|
data/lib/ffi_dry.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'ffi/dry'
|
data/samples/afmap.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'ffi/dry'
|
2
|
+
require 'socket'
|
3
|
+
|
4
|
+
module AddressFamily
|
5
|
+
include FFI::DRY::ConstMap
|
6
|
+
slurp_constants ::Socket, "AF_"
|
7
|
+
def list ; @@list ||= super() ; end
|
8
|
+
end
|
9
|
+
|
10
|
+
# AddressFamily now has all the constants it found for Socket::AF_*
|
11
|
+
#
|
12
|
+
# i.e. AddressFamily::INET
|
13
|
+
# AddressFamily::LINK
|
14
|
+
# AddressFamily::INET6
|
15
|
+
# etc...
|
16
|
+
#
|
17
|
+
# We can do quick lookups
|
18
|
+
# AddressFamily[2] # => "INET"
|
19
|
+
# AddressFamily["INET"] # => 2
|
20
|
+
#
|
21
|
+
# We can get a hash of all key-value pairs with .list
|
22
|
+
# AddressFamily.list
|
23
|
+
# # => {"NATM"=>31, "DLI"=>13, "UNIX"=>1, "NETBIOS"=>33, ...}
|
24
|
+
#
|
25
|
+
# ... which can be inverted for a reverse mapping
|
26
|
+
# AddressFamily.list.invert
|
27
|
+
# # => {16=>"APPLETALK", 5=>"CHAOS", 27=>"NDRV", 0=>"UNSPEC", ...}
|
28
|
+
#
|
data/samples/basic.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'ffi'
|
3
|
+
require 'ffi/dry'
|
4
|
+
|
5
|
+
class SomeStruct < FFI::Struct
|
6
|
+
include FFI::DRY::StructHelper
|
7
|
+
|
8
|
+
# we get a new way of specifying layouts with a 'dsl'-like syntax
|
9
|
+
dsl_layout do
|
10
|
+
field :field1, :uint16, :desc => 'this is field 1'
|
11
|
+
field :field2, :uint16, :desc => 'this is field 2'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
ss0=SomeStruct.new
|
17
|
+
|
18
|
+
p ss0.dsl_metadata # we can look at definition metadata
|
19
|
+
|
20
|
+
# And we have additional ways of instantiating and declaring values
|
21
|
+
# during initialization. (The FFI standard ways still work too)
|
22
|
+
|
23
|
+
raw_data = "\x00\x00\xff\xff"
|
24
|
+
|
25
|
+
ss1=SomeStruct.new :raw => raw_data
|
26
|
+
ss2=SomeStruct.new :raw => raw_data, :field1 => 1, :field2 => 2
|
27
|
+
ss3=SomeStruct.new {|x| x.field1=1 }
|
28
|
+
ss4=SomeStruct.new(:raw => raw_data) {|x| x.field1=1 }
|
29
|
+
|
30
|
+
[ ss0,
|
31
|
+
ss1,
|
32
|
+
ss2,
|
33
|
+
ss3,
|
34
|
+
ss4].each_with_index {|x,i| p ["ss#{i}",[x.field1, x.field2]]}
|
35
|
+
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'ffi'
|
3
|
+
require 'ffi/dry'
|
4
|
+
|
5
|
+
class NeighborlyStruct < ::FFI::Struct
|
6
|
+
include ::FFI::DRY::StructHelper
|
7
|
+
|
8
|
+
def self.describe
|
9
|
+
print "Struct: #{self.class}"
|
10
|
+
dsl_metadata().each_with_index do |spec, i|
|
11
|
+
print " Field #{i}\n"+
|
12
|
+
" name: #{spec[:name].inspect}\n"+
|
13
|
+
" type: #{spec[:type].inspect}\n"+
|
14
|
+
" desc: #{spec[:desc]}\n\n"
|
15
|
+
end
|
16
|
+
print "\n"
|
17
|
+
end
|
18
|
+
def describe; self.class.describe; end
|
19
|
+
end
|
20
|
+
|
21
|
+
class TestStruct < NeighborlyStruct
|
22
|
+
dsl_layout do
|
23
|
+
field :field1, :uint8, :desc => "test field 1"
|
24
|
+
field :field2, :uint8, :desc => "test field 2"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class SomeStruct < NeighborlyStruct
|
29
|
+
dsl_layout do
|
30
|
+
field :kind, :uint8, :desc => "a type identifier"
|
31
|
+
struct :tst, TestStruct, :desc => "a nested TestStruct"
|
32
|
+
field :len, :uint8, :desc => "8-bit size value (>= self.size+2)"
|
33
|
+
array :str, [:char,255], :desc => "a string up to 255 bytes bound by :len"
|
34
|
+
end
|
35
|
+
|
36
|
+
# override kind getter method
|
37
|
+
def kind
|
38
|
+
[:default, :bar, :baz][ self[:kind] ]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
s1=TestStruct.new
|
43
|
+
s2=SomeStruct.new
|
44
|
+
|
45
|
+
puts "*"*70
|
46
|
+
s1.describe
|
47
|
+
puts "*"*70
|
48
|
+
s2.describe
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: emonti-ffi_dry
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Eric Monti
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-09-12 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: ffi-ffi
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
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
|
+
description: Provides a some useful modules, classes, and methods as well as a DSL-like syntax for FFI::Struct layouts
|
46
|
+
email: emonti@matasano.com
|
47
|
+
executables: []
|
48
|
+
|
49
|
+
extensions: []
|
50
|
+
|
51
|
+
extra_rdoc_files:
|
52
|
+
- LICENSE
|
53
|
+
- README.rdoc
|
54
|
+
files:
|
55
|
+
- .document
|
56
|
+
- .gitignore
|
57
|
+
- LICENSE
|
58
|
+
- README.rdoc
|
59
|
+
- Rakefile
|
60
|
+
- VERSION
|
61
|
+
- lib/ffi/dry.rb
|
62
|
+
- lib/ffi_dry.rb
|
63
|
+
- samples/afmap.rb
|
64
|
+
- samples/basic.rb
|
65
|
+
- samples/describer.rb
|
66
|
+
- spec/ffi_dry_spec.rb
|
67
|
+
- spec/spec_helper.rb
|
68
|
+
has_rdoc: false
|
69
|
+
homepage: http://github.com/emonti/ffi_dry
|
70
|
+
post_install_message:
|
71
|
+
rdoc_options:
|
72
|
+
- --charset=UTF-8
|
73
|
+
require_paths:
|
74
|
+
- lib
|
75
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: "0"
|
80
|
+
version:
|
81
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: "0"
|
86
|
+
version:
|
87
|
+
requirements: []
|
88
|
+
|
89
|
+
rubyforge_project:
|
90
|
+
rubygems_version: 1.2.0
|
91
|
+
signing_key:
|
92
|
+
specification_version: 3
|
93
|
+
summary: Syntactic sugar and helper utilities for FFI
|
94
|
+
test_files:
|
95
|
+
- spec/ffi_dry_spec.rb
|
96
|
+
- spec/spec_helper.rb
|