ffi_dry 0.1.9 → 0.1.11
Sign up to get free protection for your applications and to get access to all the features.
- 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
|