emonti-ffi_dry 0.1.0
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/.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
|