snow-data 1.0.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.
- checksums.yaml +7 -0
- data/COPYING +26 -0
- data/README.md +135 -0
- data/ext/extconf.rb +58 -0
- data/ext/snow-data/snow-data.c +1867 -0
- data/lib/snow-data.rb +8 -0
- data/lib/snow-data/c_struct.rb +724 -0
- data/lib/snow-data/memory.rb +92 -0
- data/lib/snow-data/version.rb +12 -0
- metadata +66 -0
data/lib/snow-data.rb
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
# This file is part of ruby-snowdata.
|
2
|
+
# Copyright (c) 2013 Noel Raymond Cower. All rights reserved.
|
3
|
+
# See COPYING for license details.
|
4
|
+
|
5
|
+
require 'snow-data/version'
|
6
|
+
require 'snow-data/snowdata_bindings'
|
7
|
+
require 'snow-data/c_struct'
|
8
|
+
require 'snow-data/memory'
|
@@ -0,0 +1,724 @@
|
|
1
|
+
# This file is part of ruby-snowdata.
|
2
|
+
# Copyright (c) 2013 Noel Raymond Cower. All rights reserved.
|
3
|
+
# See COPYING for license details.
|
4
|
+
|
5
|
+
require 'snow-data/snowdata_bindings'
|
6
|
+
require 'snow-data/memory'
|
7
|
+
|
8
|
+
module Snow
|
9
|
+
|
10
|
+
|
11
|
+
class CStruct
|
12
|
+
|
13
|
+
#
|
14
|
+
# Info for a struct member. Defines a member's name, type, size, length,
|
15
|
+
# alignment, and offset.
|
16
|
+
#
|
17
|
+
StructMemberInfo = Struct.new(:name, :type, :size, :length, :alignment, :offset)
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
#
|
22
|
+
# Struct base class. Not to be used directly, as it does not provide all the
|
23
|
+
# constants and methods necessary for a struct.
|
24
|
+
#
|
25
|
+
class StructBase < ::Snow::Memory
|
26
|
+
|
27
|
+
@@long_inspect = false
|
28
|
+
|
29
|
+
#
|
30
|
+
# Whether long inspect strings are enabled. By default, they are disabled.
|
31
|
+
#
|
32
|
+
# Long inspect strings can be useful for debugging, sepcially if you want to
|
33
|
+
# see the value, length, and alignment of every struct member in inspect
|
34
|
+
# strings. Otherwise, you can safely leave this disabled.
|
35
|
+
#
|
36
|
+
def self.long_inspect=(enabled)
|
37
|
+
@@long_inspect = !!enabled
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
def self.long_inspect
|
42
|
+
@@long_inspect
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
#
|
47
|
+
# Returns the offset of a member.
|
48
|
+
#
|
49
|
+
def self.offset_of(member)
|
50
|
+
self.MEMBERS_HASH[member].offset
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
#
|
55
|
+
# Returns the type name of a member.
|
56
|
+
#
|
57
|
+
def self.type_of(member)
|
58
|
+
self.MEMBERS_HASH[member].type
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
#
|
63
|
+
# Returns the size in bytes of a member.
|
64
|
+
#
|
65
|
+
def self.bytesize_of(member)
|
66
|
+
self.MEMBERS_HASH[member].size
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
#
|
71
|
+
# Returns the alignment of a member.
|
72
|
+
#
|
73
|
+
def self.alignment_of(member)
|
74
|
+
self.MEMBERS_HASH[member].alignment
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
#
|
79
|
+
# Returns the length of a member.
|
80
|
+
#
|
81
|
+
def self.length_of(member)
|
82
|
+
self.MEMBERS_HASH[member].length
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
|
87
|
+
#
|
88
|
+
# Returns the address of a member. This address is only valid for the
|
89
|
+
# receiver.
|
90
|
+
#
|
91
|
+
def address_of(member)
|
92
|
+
self.address + self.class.offset_of(member)
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
#
|
97
|
+
# Returns the offset of a member.
|
98
|
+
#
|
99
|
+
def offset_of(member)
|
100
|
+
self.class.offset_of(member)
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
#
|
105
|
+
# Returns the size in bytes of a member.
|
106
|
+
#
|
107
|
+
def bytesize_of(member)
|
108
|
+
self.class.bytesize_of(member)
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
#
|
113
|
+
# Returns the alignment of a member.
|
114
|
+
#
|
115
|
+
def alignment_of(member)
|
116
|
+
self.class.alignment_of(member)
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
#
|
121
|
+
# Returns the length of a member.
|
122
|
+
#
|
123
|
+
def length_of(member)
|
124
|
+
self.class.length_of(member)
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
#
|
129
|
+
# Returns the type name of a member.
|
130
|
+
#
|
131
|
+
def type_of(member)
|
132
|
+
self.class.type_of(member)
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
#
|
137
|
+
# Returns a hash of all of the struct's member names to their values.
|
138
|
+
#
|
139
|
+
def to_h
|
140
|
+
self.class::MEMBERS.inject({}) do |hash, member|
|
141
|
+
length = member.length
|
142
|
+
name = member.name
|
143
|
+
|
144
|
+
hash[name] = if length > 1
|
145
|
+
(0 ... length).map { |index| __send__(name, index) }
|
146
|
+
else
|
147
|
+
__send__(name)
|
148
|
+
end
|
149
|
+
|
150
|
+
hash
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
|
155
|
+
#
|
156
|
+
# Returns a string describing the struct, including its classname, object
|
157
|
+
# ID, address, size, and alignment. In addition, if long_inspect is enabled,
|
158
|
+
# then it will also include the values of the struct's members.
|
159
|
+
#
|
160
|
+
def inspect
|
161
|
+
id_text = __id__.to_s(16).rjust(14, ?0)
|
162
|
+
addr_text = self.address.to_s(16).rjust(14, ?0)
|
163
|
+
|
164
|
+
member_text = if ! null? && self.class.long_inspect
|
165
|
+
# Get member text
|
166
|
+
all_members_text = (self.class::MEMBERS.map do |member|
|
167
|
+
name = member.name
|
168
|
+
type = member.type
|
169
|
+
align = member.alignment
|
170
|
+
length = member.length
|
171
|
+
length_decl = length > 1 ? "[#{length}]" : ''
|
172
|
+
|
173
|
+
values_text = if length > 1
|
174
|
+
single_member_text = (0 ... length).map { |index|
|
175
|
+
"[#{ index }]=#{ self.__send__(member.name, index).inspect }"
|
176
|
+
}.join(', ')
|
177
|
+
|
178
|
+
"{ #{ single_member_text } }"
|
179
|
+
else
|
180
|
+
self.__send__(member.name).inspect
|
181
|
+
end
|
182
|
+
|
183
|
+
"#{ name }:#{ type }#{ length_decl }:#{ align }=#{ values_text }"
|
184
|
+
|
185
|
+
end).join('; ')
|
186
|
+
|
187
|
+
" #{all_members_text}"
|
188
|
+
else # member_text = if ...
|
189
|
+
# Skip members
|
190
|
+
''
|
191
|
+
end # member_text = if ...
|
192
|
+
|
193
|
+
"<#{ self.class }:0x#{ id_text } *0x#{ addr_text }:#{ self.bytesize }:#{ self.alignment }#{member_text}>"
|
194
|
+
end
|
195
|
+
|
196
|
+
|
197
|
+
end # class StructBase
|
198
|
+
|
199
|
+
|
200
|
+
#
|
201
|
+
# Returns whether num is a power of two and nonzero.
|
202
|
+
#
|
203
|
+
def self.power_of_two?(num)
|
204
|
+
((num & (num - 1)) == 0) && (num != 0)
|
205
|
+
end
|
206
|
+
|
207
|
+
|
208
|
+
#
|
209
|
+
# Returns an encoding for a struct member with the given name, type,
|
210
|
+
# length, and alignment. The type must be a string or symbol, not a Class or
|
211
|
+
# other object. If no alignment is provided, it uses the default alignment for
|
212
|
+
# the type or the size of a pointer if no alignment can be found.
|
213
|
+
#
|
214
|
+
# #### Example
|
215
|
+
# CStruct.member_encoding(:foo, :float, 32, nil) # => "foo:float[32]:4"
|
216
|
+
#
|
217
|
+
def self.member_encoding(name, type, length: 1, alignment: nil)
|
218
|
+
type = type.intern
|
219
|
+
alignment = alignment || ALIGNMENTS[type] || ALIGNMENTS[:*]
|
220
|
+
raise ArgumentError, "Invalid length: #{length}. Must be > 0." if length < 1
|
221
|
+
raise ArgumentError, "Invalid alignment: #{alignment}. Must be a power of two." if ! power_of_two?(alignment)
|
222
|
+
"#{name}:#{type}[#{length}]:#{alignment}"
|
223
|
+
end
|
224
|
+
|
225
|
+
|
226
|
+
# The encoding regex. Just used to make reading encoding strings easy. Do not
|
227
|
+
# touch this.
|
228
|
+
#--
|
229
|
+
# Ordinarily I'd write a lexer for this sort of thing, but regex actually
|
230
|
+
# seems to work fine.
|
231
|
+
#++
|
232
|
+
ENCODING_REGEX = %r{
|
233
|
+
(?<name> # 0
|
234
|
+
[_a-zA-Z][_a-zA-Z\d]*
|
235
|
+
)
|
236
|
+
\s* \: \s*
|
237
|
+
(?<type> # 1
|
238
|
+
# Named struct type encoding - must match previously defined type
|
239
|
+
\* | [a-zA-Z_][a-zA-Z_0-9]*
|
240
|
+
)
|
241
|
+
(?<type_array_decl> \s* \[ \s* # 2
|
242
|
+
(?<type_array_count> \d+ ) # 3
|
243
|
+
\s* \] )?
|
244
|
+
(?<type_alignment_decl> \s* \: \s* # 4
|
245
|
+
(?<type_alignment> \d+ ) # 5
|
246
|
+
)?
|
247
|
+
\s* (?: ; | $ | \n) # terminator
|
248
|
+
}mx
|
249
|
+
|
250
|
+
# Alignemnts for default types.
|
251
|
+
ALIGNMENTS = {
|
252
|
+
:char => 1,
|
253
|
+
:signed_char => 1,
|
254
|
+
:unsigned_char => 1,
|
255
|
+
:uint8_t => Memory::SIZEOF_UINT8_T,
|
256
|
+
:int8_t => Memory::SIZEOF_INT8_T,
|
257
|
+
:short => Memory::SIZEOF_SHORT,
|
258
|
+
:unsigned_short => Memory::SIZEOF_SHORT,
|
259
|
+
:uint16_t => Memory::SIZEOF_UINT16_T,
|
260
|
+
:int16_t => Memory::SIZEOF_INT16_T,
|
261
|
+
:int32_t => Memory::SIZEOF_INT32_T,
|
262
|
+
:uint32_t => Memory::SIZEOF_UINT32_T,
|
263
|
+
:uint64_t => Memory::SIZEOF_UINT64_T,
|
264
|
+
:int64_t => Memory::SIZEOF_INT64_T,
|
265
|
+
:unsigned_long => Memory::SIZEOF_LONG,
|
266
|
+
:unsigned_long_long => Memory::SIZEOF_LONG_LONG,
|
267
|
+
:long => Memory::SIZEOF_LONG,
|
268
|
+
:long_long => Memory::SIZEOF_LONG_LONG,
|
269
|
+
:int => Memory::SIZEOF_INT,
|
270
|
+
:unsigned_int => Memory::SIZEOF_INT,
|
271
|
+
:float => Memory::SIZEOF_FLOAT,
|
272
|
+
:double => Memory::SIZEOF_DOUBLE,
|
273
|
+
:size_t => Memory::SIZEOF_SIZE_T,
|
274
|
+
:ptrdiff_t => Memory::SIZEOF_PTRDIFF_T,
|
275
|
+
:intptr_t => Memory::SIZEOF_INTPTR_T,
|
276
|
+
:uintptr_t => Memory::SIZEOF_UINTPTR_T
|
277
|
+
}
|
278
|
+
|
279
|
+
# Sizes of default types.
|
280
|
+
SIZES = {
|
281
|
+
:char => 1,
|
282
|
+
:signed_char => 1,
|
283
|
+
:unsigned_char => 1,
|
284
|
+
:uint8_t => Memory::SIZEOF_UINT8_T,
|
285
|
+
:int8_t => Memory::SIZEOF_INT8_T,
|
286
|
+
:short => Memory::SIZEOF_SHORT,
|
287
|
+
:unsigned_short => Memory::SIZEOF_SHORT,
|
288
|
+
:uint16_t => Memory::SIZEOF_UINT16_T,
|
289
|
+
:int16_t => Memory::SIZEOF_INT16_T,
|
290
|
+
:int32_t => Memory::SIZEOF_INT32_T,
|
291
|
+
:uint32_t => Memory::SIZEOF_UINT32_T,
|
292
|
+
:uint64_t => Memory::SIZEOF_UINT64_T,
|
293
|
+
:int64_t => Memory::SIZEOF_INT64_T,
|
294
|
+
:unsigned_long => Memory::SIZEOF_LONG,
|
295
|
+
:unsigned_long_long => Memory::SIZEOF_LONG_LONG,
|
296
|
+
:long => Memory::SIZEOF_LONG,
|
297
|
+
:long_long => Memory::SIZEOF_LONG_LONG,
|
298
|
+
:int => Memory::SIZEOF_INT,
|
299
|
+
:unsigned_int => Memory::SIZEOF_INT,
|
300
|
+
:float => Memory::SIZEOF_FLOAT,
|
301
|
+
:double => Memory::SIZEOF_DOUBLE,
|
302
|
+
:size_t => Memory::SIZEOF_SIZE_T,
|
303
|
+
:ptrdiff_t => Memory::SIZEOF_PTRDIFF_T,
|
304
|
+
:intptr_t => Memory::SIZEOF_INTPTR_T,
|
305
|
+
:uintptr_t => Memory::SIZEOF_UINTPTR_T
|
306
|
+
}
|
307
|
+
|
308
|
+
# Used for getters/setters on Memory objects. Simply maps short type names to
|
309
|
+
# their long-form type names.
|
310
|
+
LONG_NAMES = {
|
311
|
+
# char
|
312
|
+
:c => :char,
|
313
|
+
:sc => :signed_char,
|
314
|
+
:uc => :unsigned_char,
|
315
|
+
:ui8 => :uint8_t,
|
316
|
+
:i8 => :int8_t,
|
317
|
+
# short (uint16_t)
|
318
|
+
:s => :short,
|
319
|
+
:us => :unsigned_short,
|
320
|
+
:ui16 => :uint16_t,
|
321
|
+
:i16 => :int16_t,
|
322
|
+
# int32
|
323
|
+
:i32 => :int32_t,
|
324
|
+
:ui32 => :uint32_t,
|
325
|
+
:ui64 => :uint64_t,
|
326
|
+
:i64 => :int64_t,
|
327
|
+
:ul => :unsigned_long,
|
328
|
+
:ull => :unsigned_long_long,
|
329
|
+
:l => :long,
|
330
|
+
:ll => :long_long,
|
331
|
+
:i => :int,
|
332
|
+
:ui => :unsigned_int,
|
333
|
+
:f => :float,
|
334
|
+
:d => :double,
|
335
|
+
:zu => :size_t,
|
336
|
+
:td => :ptrdiff_t,
|
337
|
+
:ip => :intptr_t,
|
338
|
+
:uip => :uintptr_t,
|
339
|
+
:* => :intptr_t # pointers always stored at intptr_t
|
340
|
+
}
|
341
|
+
|
342
|
+
|
343
|
+
#
|
344
|
+
# call-seq:
|
345
|
+
# add_type(name, klass) => klass
|
346
|
+
#
|
347
|
+
# Adds a type as a possible member type for structs. Types registered this way
|
348
|
+
# can be used as a member type by using the name provided to #add_type in
|
349
|
+
# struct encodings.
|
350
|
+
#
|
351
|
+
def self.add_type(name, klass)
|
352
|
+
name = name.intern
|
353
|
+
|
354
|
+
raise "No type name provided" if !name
|
355
|
+
|
356
|
+
ALIGNMENTS[name] = klass::ALIGNMENT
|
357
|
+
SIZES[name] = klass::SIZE
|
358
|
+
|
359
|
+
getter = :"get_#{name}"
|
360
|
+
setter = :"set_#{name}"
|
361
|
+
|
362
|
+
Memory.class_exec do
|
363
|
+
|
364
|
+
define_method(getter) do |offset|
|
365
|
+
wrapper = klass.__wrap__(self.address + offset, klass::SIZE, klass::ALIGNMENT)
|
366
|
+
wrapper.instance_variable_set(:@__base_memory__, self)
|
367
|
+
wrapper
|
368
|
+
end # getter
|
369
|
+
|
370
|
+
define_method(setter) do |offset, data|
|
371
|
+
raise "Invalid value type, must be Data, but got #{data.class}" if ! data.kind_of?(Data)
|
372
|
+
local_addr = self.address + offset
|
373
|
+
if !data.respond_to?(:address) || local_addr != data.address
|
374
|
+
copy!(data, offset, 0, klass::SIZE)
|
375
|
+
end
|
376
|
+
|
377
|
+
data
|
378
|
+
end # setter
|
379
|
+
|
380
|
+
end # class_exec
|
381
|
+
|
382
|
+
klass
|
383
|
+
end
|
384
|
+
|
385
|
+
|
386
|
+
#
|
387
|
+
# call-seq:
|
388
|
+
# new(name, encoding) => Class
|
389
|
+
# new(encoding) => Class
|
390
|
+
#
|
391
|
+
# Creates a new C-struct class and returns it. Optionally, if a name is
|
392
|
+
# provided, it is also added as a class under the CStruct class.
|
393
|
+
#
|
394
|
+
# In the first form when a name is provided, the name must be valid for a
|
395
|
+
# constant and be unique among CStruct types. The resulting type will be set
|
396
|
+
# as a constant under the CStruct class. So, for example:
|
397
|
+
#
|
398
|
+
# CStruct.new(:SomeStruct, 'member: float') # => Snow::CStruct::SomeStruct
|
399
|
+
# CStruct::SomeStruct.new # => <Snow::CStruct::SomeStruct:...>
|
400
|
+
#
|
401
|
+
# Additionally, this will register it as a possible member type for other
|
402
|
+
# structs, though struct types must be defined before they are used in other
|
403
|
+
# structs, otherwise there is no data for determining size, alignment, and so
|
404
|
+
# on for those structs and as such will likely result in an error.
|
405
|
+
#
|
406
|
+
# If no name is provided, the new class isn't set as a constant or usable as
|
407
|
+
# a member of another struct. To add it as a possible member type, you need to
|
408
|
+
# call ::add_type(name, klass). This will not register it as a constant under
|
409
|
+
# CStruct.
|
410
|
+
#
|
411
|
+
#
|
412
|
+
# ### Encodings
|
413
|
+
#
|
414
|
+
# Encodings are how you define C structs using Snow::CStruct. It's a fairly
|
415
|
+
# simple string format, defined as such:
|
416
|
+
#
|
417
|
+
# length ::= '[' integer ']'
|
418
|
+
# alignment ::= ':' integer
|
419
|
+
# typename ::= ':' Name
|
420
|
+
# member_name ::= Name
|
421
|
+
# member_decl ::= member_name typename [ length ] [ alignment ]
|
422
|
+
#
|
423
|
+
# So, for example, the encoding string "foo: float[4]:8" defines a C struct
|
424
|
+
# with a single member, `foo`, which is an array of 4 32-bit floats with an
|
425
|
+
# alignment of 8 bytes. By default, all types are aligned to their base type's
|
426
|
+
# size (e.g., "foo: float" would be algiend to 4 bytes) and all members have a
|
427
|
+
# length of 1 unless specified otherwise.
|
428
|
+
#
|
429
|
+
# A list of all types follows, including their short and long names, and their
|
430
|
+
# corresponding types in C. Short names are only provided for convenience and
|
431
|
+
# are generally not too useful except for reducing string length. They're
|
432
|
+
# expanded to their long-form names when the class is created.
|
433
|
+
#
|
434
|
+
# - `c / char => char`
|
435
|
+
# - `sc / signed_char => signed char`
|
436
|
+
# - `uc / unsigned_char => unsigned char`
|
437
|
+
# - `ui8 / uint8_t => uint8_t`
|
438
|
+
# - `i8 / int8_t => int8_t`
|
439
|
+
# - `s / short => short`
|
440
|
+
# - `us / unsigned_short => unsigned short`
|
441
|
+
# - `ui16 / uint16_t => uint16_t`
|
442
|
+
# - `i16 / int16_t => int16_t`
|
443
|
+
# - `i32 / int32_t => int32_t`
|
444
|
+
# - `ui32 / uint32_t => uint32_t`
|
445
|
+
# - `ui64 / uint64_t => uint64_t`
|
446
|
+
# - `i64 / int64_t => int64_t`
|
447
|
+
# - `ul / unsigned_long => unsigned long`
|
448
|
+
# - `ull / unsigned_long_long => unsigned long long`
|
449
|
+
# - `l / long => long`
|
450
|
+
# - `ll / long_long => long long`
|
451
|
+
# - `i / int => int`
|
452
|
+
# - `ui / unsigned_int => unsigned int`
|
453
|
+
# - `f / float => float`
|
454
|
+
# - `d / double => double`
|
455
|
+
# - `zu / size_t => size_t`
|
456
|
+
# - `td / ptrdiff_t => ptrdiff_t`
|
457
|
+
# - `ip / intptr_t => intptr_t`
|
458
|
+
# - `uip / uintptr_t => uintptr_t`
|
459
|
+
# - `* / intptr_t => void *` (stored as an `intptr_t`)
|
460
|
+
#
|
461
|
+
# In addition, any structs created with a name or added with #add_type are
|
462
|
+
# also valid typenames. So, if a struct with the name :Foo is created, then
|
463
|
+
# you can then use it in an encoding, like "bar: Foo [8]" to declare a member
|
464
|
+
# that is 8 Foo structs long.
|
465
|
+
#
|
466
|
+
# Structs, by default, are aligned to their largest member alignemnt. So, if
|
467
|
+
# a struct has four members with alignments of 8, 16, 32, and 4, the struct's
|
468
|
+
# overall alignment is 32 bytes.
|
469
|
+
#
|
470
|
+
# Endianness is not handled by structs and must be checked for and handled in
|
471
|
+
# your code.
|
472
|
+
#
|
473
|
+
#
|
474
|
+
# ### Struct Classes
|
475
|
+
#
|
476
|
+
# Struct classes all declare methods for reading and writing their members.
|
477
|
+
# Member access is provided via `#get_<member_name>(index = 0)` and modifying
|
478
|
+
# members is through `#set_<member_name>(value, index = 0)`. These are also
|
479
|
+
# aliased as `#<member_name>` and `#<member_name>=` for convenience,
|
480
|
+
# particularly with members whose lengths are 1.
|
481
|
+
#
|
482
|
+
# Struct members will always return a new instance of the member type that
|
483
|
+
# wraps the member at its address -- a copy of the memory at that location is
|
484
|
+
# not created.
|
485
|
+
#
|
486
|
+
# All struct classes also have an Array class (as `StructKlass::Array`) as
|
487
|
+
# well that provides simple access to resizable arrays. Tehse provide both
|
488
|
+
# `fetch(index)` and `store(index, value)` methods, both aliased to `[]` and
|
489
|
+
# `[]=` respectively.
|
490
|
+
#
|
491
|
+
def self.new(*args)
|
492
|
+
encoding, klass_name = case args.length
|
493
|
+
when 1 then args
|
494
|
+
when 2 then args.reverse
|
495
|
+
else
|
496
|
+
raise ArgumentError, "Invalid arguments to CStruct::new"
|
497
|
+
end
|
498
|
+
|
499
|
+
klass_name = klass_name.intern if klass_name
|
500
|
+
|
501
|
+
members = []
|
502
|
+
|
503
|
+
encoding.scan(ENCODING_REGEX) do
|
504
|
+
|match|
|
505
|
+
name = match[0].intern
|
506
|
+
type = match[1].intern
|
507
|
+
type = LONG_NAMES[type] if LONG_NAMES.include?(type)
|
508
|
+
length = (match[3] || 1).to_i
|
509
|
+
align = (match[5] || ALIGNMENTS[type] || 1).to_i
|
510
|
+
offset = 0
|
511
|
+
|
512
|
+
last_type = members.last
|
513
|
+
if last_type
|
514
|
+
offset += Memory.align_size(last_type.offset + last_type.size, align)
|
515
|
+
end
|
516
|
+
|
517
|
+
members << StructMemberInfo[name, type, SIZES[type] * length, length, align, offset].freeze
|
518
|
+
end
|
519
|
+
|
520
|
+
raise "No valid members found in encoding" if members.empty?
|
521
|
+
|
522
|
+
alignment = members.map { |member| member.alignment }.max { |lhs, rhs| lhs <=> rhs }
|
523
|
+
size = members.last.size + members.last.offset
|
524
|
+
aligned_size = Memory.align_size(size, alignment)
|
525
|
+
|
526
|
+
members.freeze
|
527
|
+
|
528
|
+
klass = Class.new(StructBase) do |struct_klass|
|
529
|
+
const_set(:ENCODING, String.new(encoding).freeze)
|
530
|
+
const_set(:MEMBERS, members)
|
531
|
+
const_set(:SIZE, size)
|
532
|
+
const_set(:ALIGNED_SIZE, aligned_size)
|
533
|
+
const_set(:ALIGNMENT, alignment)
|
534
|
+
const_set(:MEMBERS_HASH, members.reduce({}) { |offs, member| offs[member.name] = member ; offs })
|
535
|
+
|
536
|
+
def self.new(&block)
|
537
|
+
inst = __malloc__(self::SIZE, self::ALIGNMENT)
|
538
|
+
yield(inst) if block_given?
|
539
|
+
inst
|
540
|
+
end
|
541
|
+
|
542
|
+
self::MEMBERS.each do
|
543
|
+
|member|
|
544
|
+
|
545
|
+
name = member.name
|
546
|
+
index_range = (0...member.length)
|
547
|
+
type_name = member.type
|
548
|
+
type_size = ::Snow::CStruct::SIZES[member.type]
|
549
|
+
offset = member.offset
|
550
|
+
getter = :"get_#{type_name}"
|
551
|
+
setter = :"set_#{type_name}"
|
552
|
+
get_name = :"get_#{name}"
|
553
|
+
set_name = :"set_#{name}"
|
554
|
+
|
555
|
+
define_method(get_name) do |index = 0|
|
556
|
+
if index === index_range
|
557
|
+
raise RangeError, "Index #{index} for #{name} is out of range: must be in #{index_range}"
|
558
|
+
end
|
559
|
+
off = offset + index * type_size
|
560
|
+
__send__(getter, off)
|
561
|
+
end # get_name
|
562
|
+
|
563
|
+
|
564
|
+
define_method(set_name) do |value, index = 0|
|
565
|
+
if index === index_range
|
566
|
+
raise RangeError, "Index #{index} for #{name} is out of range: must be in #{index_range}"
|
567
|
+
end
|
568
|
+
off = offset + index * type_size
|
569
|
+
__send__(setter, off, value)
|
570
|
+
value
|
571
|
+
end # set_name
|
572
|
+
|
573
|
+
|
574
|
+
alias_method :"#{name}", get_name
|
575
|
+
alias_method :"#{name}=", set_name
|
576
|
+
|
577
|
+
end # define member get / set methods
|
578
|
+
|
579
|
+
|
580
|
+
# Array inner class (not a subclass of StructBase because it has no members itself)
|
581
|
+
const_set(:Array, Class.new(Memory) do |array_klass|
|
582
|
+
|
583
|
+
const_set(:BASE, struct_klass)
|
584
|
+
|
585
|
+
# The length of the array.
|
586
|
+
attr_reader :length
|
587
|
+
|
588
|
+
|
589
|
+
include Enumerable
|
590
|
+
|
591
|
+
|
592
|
+
def self.wrap(address, length_in_elements) # :nodoc:
|
593
|
+
__wrap__(address, length_in_elements * self::BASE::SIZE)
|
594
|
+
end
|
595
|
+
|
596
|
+
|
597
|
+
def self.new(length) # :nodoc:
|
598
|
+
length = length.to_i
|
599
|
+
raise ArgumentError, "Length must be greater than zero" if length < 1
|
600
|
+
inst = __malloc__(length * self::BASE::SIZE, self::BASE::ALIGNMENT)
|
601
|
+
inst.instance_variable_set(:@length, length)
|
602
|
+
inst.instance_variable_set(:@__cache__, nil)
|
603
|
+
inst
|
604
|
+
end
|
605
|
+
|
606
|
+
|
607
|
+
def resize!(new_length) # :nodoc:
|
608
|
+
raise ArgumentError, "Length must be greater than zero" if new_length < 1
|
609
|
+
realloc!(new_length * self.class::BASE::SIZE, self.class::BASE::ALIGNMENT)
|
610
|
+
@length = new_length
|
611
|
+
__free_cache__
|
612
|
+
self
|
613
|
+
end
|
614
|
+
|
615
|
+
|
616
|
+
def each(&block) # :nodoc:
|
617
|
+
return to_enum(:each) unless block_given?
|
618
|
+
(0 ... self.length).each { |index| yield fetch(index) }
|
619
|
+
self
|
620
|
+
end
|
621
|
+
|
622
|
+
|
623
|
+
def map(&block) # :nodoc:
|
624
|
+
return to_enum(:map) unless block_given?
|
625
|
+
self.dup.map!(&block)
|
626
|
+
end
|
627
|
+
|
628
|
+
|
629
|
+
def map!(&block) # :nodoc:
|
630
|
+
return to_enum(:map!) unless block_given?
|
631
|
+
(0 ... self.length).each { |index| store(index, yield(fetch(index))) }
|
632
|
+
self
|
633
|
+
end
|
634
|
+
|
635
|
+
|
636
|
+
def to_a # :nodoc:
|
637
|
+
(0 ... self.length).map { |index| fetch(index) }
|
638
|
+
end
|
639
|
+
|
640
|
+
|
641
|
+
def fetch(index) # :nodoc:
|
642
|
+
raise RuntimeError, "Attempt to access deallocated array" if @length == 0
|
643
|
+
raise RangeError, "Attempt to access out-of-bounds index in #{self.class}" if index < 0 || @length <= index
|
644
|
+
__build_cache__ if ! @__cache__
|
645
|
+
@__cache__[index]
|
646
|
+
end
|
647
|
+
alias_method :[], :fetch
|
648
|
+
|
649
|
+
|
650
|
+
#
|
651
|
+
# You can use this to assign _any_ Data subclass to an array value, but
|
652
|
+
# keep in mind that the data assigned MUST -- again, MUST -- be at least
|
653
|
+
# as large as the array's base struct type in bytes or the assigned
|
654
|
+
# data object MUST respond to a bytesize message to get its size in
|
655
|
+
# bytes.
|
656
|
+
#
|
657
|
+
def store(index, data) # :nodoc:
|
658
|
+
raise RuntimeError, "Attempt to access deallocated array" if @length == 0
|
659
|
+
raise TypeError, "Invalid value type, must be Data, but got #{data.class}" if ! data.kind_of?(Data)
|
660
|
+
raise RangeError, "Attempt to access out-of-bounds index in #{self.class}" if index < 0 || @length <= index
|
661
|
+
@__cache__[index].copy!(data)
|
662
|
+
data
|
663
|
+
end
|
664
|
+
alias_method :[]=, :store
|
665
|
+
|
666
|
+
|
667
|
+
|
668
|
+
def free! # :nodoc:
|
669
|
+
__free_cache__
|
670
|
+
@length = 0
|
671
|
+
super
|
672
|
+
end
|
673
|
+
|
674
|
+
|
675
|
+
private
|
676
|
+
|
677
|
+
def __free_cache__ # :nodoc:
|
678
|
+
if @__cache__
|
679
|
+
@__cache__.each { |entry|
|
680
|
+
entry.free!
|
681
|
+
entry.remove_instance_variable(:@__base_memory__)
|
682
|
+
} # zeroes address, making it NULL
|
683
|
+
@__cache__ = nil
|
684
|
+
end
|
685
|
+
end
|
686
|
+
|
687
|
+
|
688
|
+
def __build_cache__ # :nodoc:
|
689
|
+
addr = self.address
|
690
|
+
@__cache__ = (0...length).map { |index|
|
691
|
+
wrapper = self.class::BASE.__wrap__(addr + index * self.class::BASE::SIZE, self.class::BASE::SIZE)
|
692
|
+
# Make sure the wrapped object keeps the memory from being collected while it's in use
|
693
|
+
wrapper.instance_variable_set(:@__base_memory__, self)
|
694
|
+
wrapper
|
695
|
+
}
|
696
|
+
end
|
697
|
+
|
698
|
+
|
699
|
+
class <<self # :nodoc: all
|
700
|
+
alias_method :[], :new
|
701
|
+
end
|
702
|
+
|
703
|
+
end)
|
704
|
+
|
705
|
+
def self.[](length) # :nodoc:
|
706
|
+
self::Array.new(length)
|
707
|
+
end
|
708
|
+
|
709
|
+
end
|
710
|
+
|
711
|
+
if klass_name
|
712
|
+
const_set(klass_name, klass)
|
713
|
+
add_type(klass_name, klass)
|
714
|
+
end
|
715
|
+
|
716
|
+
klass
|
717
|
+
end
|
718
|
+
|
719
|
+
class <<self ; alias_method :[], :new ; end
|
720
|
+
|
721
|
+
end # class CStruct
|
722
|
+
|
723
|
+
|
724
|
+
end # module Snow
|