Class: CStruct
- Inherits:
-
Object
- Object
- CStruct
- Defined in:
- lib/cstruct.rb
Overview
Struct does some trickery with custom allocators so we can’t subclass it without writing C. Instead we define a CStruct class that does something similar enough for our purpose. It is subclassed just like any other class. A nice side-effect of this syntax is that it is always clear that a CStruct is just a class and instances of the struct are objects.
Some light metaprogramming is used to make the following syntax possible:
class MachHeader < CStruct
uint :magic
int :cputype
int :cpusubtype
...
int :flags
end
Inheritance works as you would expect.
class LoadCommand < CStruct
uint32 :cmd
uint32 :cmdsize
end
# inherits cmd and cmdsize as the first 2 fields class SegmentCommand < LoadCommand
string :segname, 16
uint32 :vmaddr
uint32
end
Nothing tricky or confusing there. Members of a CStruct class are declared in the class definition. A different definition using a more static approach probably wouldn’t be very hard… if performance is critical … but then why are you using Ruby? ;-)
TODO support bit fields
Bit fields should be supported by passing the number of bits a field should occupy. Perhaps we could use the size ‘pack’ for the rest of the field.
class RelocationInfo < CStruct
int32 :address
uint32 :symbolnum, 24
pack :pcrel, 1
pack :length, 2
pack :extern, 1
pack :type, 4
end
Direct Known Subclasses
MachHeader, MachO::FatArch, MachO::FatHeader, MachO::MachHeader, MachO::MachHeader64
Constant Summary collapse
- SizeMap =
Size in bytes.
{ :int8 => 1, :uint8 => 1, :int16 => 2, :uint16 => 2, :int32 => 4, :uint32 => 4, :int64 => 8, :uint64 => 8, :string => lambda { |*opts| opts.first }, # first opt is size # the last 3 are to make the language more C-like :int => 4, :uint => 4, :char => 1 }
- PackMap =
32-bit
{ :int8 => 'c', :uint8 => 'C', :int16 => 's', :uint16 => 'S', :int32 => 'i', :uint32 => 'I', :int64 => 'q', :uint64 => 'Q', :string => lambda do |str, *opts| len = opts.first str.ljust(len, "\0")[0, len] end, # a few C-like names :int => 'i', :uint => 'I', :char => 'C' }
- UnpackMap =
Only needed when unpacking is different from packing, i.e. strings w/ lambdas in PackMap.
{ :string => lambda do |str, *opts| len = opts.first val = str[0, len-1].sub(/\0*$/, '') str.slice!((len-1)..-1) val end }
Instance Attribute Summary collapse
-
#values ⇒ Object
(also: #to_a)
readonly
Instance Methods #.
Class Method Summary collapse
-
.bytesize ⇒ Object
Return the number of bytes occupied in memory or on disk.
- .inherited(subclass) ⇒ Object
-
.length ⇒ Object
Return the number of members.
- .new_from_bin(bin) ⇒ Object
-
.size ⇒ Object
Return the number of members.
- .sizeof(name) ⇒ Object
Instance Method Summary collapse
- #==(other) ⇒ Object
- #[](name_or_idx) ⇒ Object
- #[]=(name_or_idx, value) ⇒ Object
- #bytesize ⇒ Object
-
#each(&block) ⇒ Object
Some of these are just to quack like Ruby’s built-in Struct.
- #each_pair(&block) ⇒ Object
-
#initialize(*args) ⇒ CStruct
constructor
A new instance of CStruct.
- #member_index ⇒ Object
- #member_options ⇒ Object
- #member_sizes ⇒ Object
-
#members ⇒ Object
A few convenience methods.
- #pack_pattern ⇒ Object
- #serialize ⇒ Object
- #size ⇒ Object (also: #length)
- #sizeof(name) ⇒ Object
- #unpack_pattern ⇒ Object
- #unserialize(bin) ⇒ Object
Constructor Details
#initialize(*args) ⇒ CStruct
Returns a new instance of CStruct.
193 194 195 |
# File 'lib/cstruct.rb', line 193 def initialize(*args) @values = args end |
Instance Attribute Details
#values ⇒ Object (readonly) Also known as: to_a
Instance Methods #
191 192 193 |
# File 'lib/cstruct.rb', line 191 def values @values end |
Class Method Details
.bytesize ⇒ Object
Return the number of bytes occupied in memory or on disk.
170 171 172 |
# File 'lib/cstruct.rb', line 170 def bytesize const_get(:Members).inject(0) { |size, name| size + sizeof(name) } end |
.inherited(subclass) ⇒ Object
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/cstruct.rb', line 122 def inherited(subclass) subclass.instance_eval do # These "constants" are only constant references. Structs can # be modified. After the struct is defined it is still open, # but good practice would be not to change a struct after it # has been defined. # # To support inheritance properly we try to get these # constants from the enclosing scope (and clone them before # modifying them!), and default to empty, er, defaults. members = const_get(:Members).clone rescue [] member_index = const_get(:MemberIndex).clone rescue {} member_sizes = const_get(:MemberSizes).clone rescue {} member_opts = const_get(:MemberOptions).clone rescue {} const_set(:Members, members) const_set(:MemberIndex, member_index) const_set(:MemberSizes, member_sizes) const_set(:MemberOptions, member_opts) end end |
.length ⇒ Object
Return the number of members.
167 168 169 |
# File 'lib/cstruct.rb', line 167 def size const_get(:Members).size end |
.new_from_bin(bin) ⇒ Object
179 180 181 182 |
# File 'lib/cstruct.rb', line 179 def new_from_bin(bin) new_struct = new new_struct.unserialize(bin) end |
.size ⇒ Object
Return the number of members.
164 165 166 |
# File 'lib/cstruct.rb', line 164 def size const_get(:Members).size end |
.sizeof(name) ⇒ Object
174 175 176 177 |
# File 'lib/cstruct.rb', line 174 def sizeof(name) value = SizeMap[const_get(:MemberSizes)[name]] value.respond_to?(:call) ? value.call(*const_get(:MemberOptions)[name]) : value end |
Instance Method Details
#==(other) ⇒ Object
266 267 268 269 270 |
# File 'lib/cstruct.rb', line 266 def ==(other) puts @values.inspect puts other.values.inspect other.is_a?(self.class) && other.values == @values end |
#[](name_or_idx) ⇒ Object
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 |
# File 'lib/cstruct.rb', line 234 def [](name_or_idx) case name_or_idx when Numeric idx = name_or_idx @values[idx] when String, Symbol name = name_or_idx.to_sym @values[member_index[name]] else raise ArgumentError, "expected name or index, got #{name_or_idx.inspect}" end end |
#[]=(name_or_idx, value) ⇒ Object
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 |
# File 'lib/cstruct.rb', line 250 def []=(name_or_idx, value) case name_or_idx when Numeric idx = name_or_idx @values[idx] = value when String, Symbol name = name_or_idx.to_sym @values[member_index[name]] = value else raise ArgumentError, "expected name or index, got #{name_or_idx.inspect}" end end |
#bytesize ⇒ Object
291 292 293 |
# File 'lib/cstruct.rb', line 291 def bytesize self.class.bytesize end |
#each(&block) ⇒ Object
Some of these are just to quack like Ruby’s built-in Struct. YAGNI, but can’t hurt either.
274 275 276 |
# File 'lib/cstruct.rb', line 274 def each(&block) @values.each(&block) end |
#each_pair(&block) ⇒ Object
278 279 280 |
# File 'lib/cstruct.rb', line 278 def each_pair(&block) members.zip(@values).each(&block) end |
#member_index ⇒ Object
304 305 306 |
# File 'lib/cstruct.rb', line 304 def member_index self.class::MemberIndex end |
#member_options ⇒ Object
312 313 314 |
# File 'lib/cstruct.rb', line 312 def self.class::MemberOptions end |
#member_sizes ⇒ Object
308 309 310 |
# File 'lib/cstruct.rb', line 308 def member_sizes self.class::MemberSizes end |
#members ⇒ Object
A few convenience methods.
300 301 302 |
# File 'lib/cstruct.rb', line 300 def members self.class::Members end |
#pack_pattern ⇒ Object
226 227 228 |
# File 'lib/cstruct.rb', line 226 def pack_pattern members.map { |name| PackMap[member_sizes[name]] } end |
#serialize ⇒ Object
197 198 199 200 201 202 203 204 205 206 207 208 |
# File 'lib/cstruct.rb', line 197 def serialize vals = @values.clone membs = members.clone pack_pattern.map do |patt| name = membs.shift if patt.is_a?(String) [vals.shift].pack(patt) else patt.call(vals.shift, *[name]) end end.join end |
#size ⇒ Object Also known as: length
282 283 284 |
# File 'lib/cstruct.rb', line 282 def size members.size end |
#sizeof(name) ⇒ Object
287 288 289 |
# File 'lib/cstruct.rb', line 287 def sizeof(name) self.class.sizeof(name) end |
#unpack_pattern ⇒ Object
230 231 232 |
# File 'lib/cstruct.rb', line 230 def unpack_pattern members.map { |name| UnpackMap[member_sizes[name]] || PackMap[member_sizes[name]] } end |
#unserialize(bin) ⇒ Object
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'lib/cstruct.rb', line 210 def unserialize(bin) bin = bin.clone @values = [] membs = members.clone unpack_pattern.each do |patt| name = membs.shift if patt.is_a?(String) @values += bin.unpack(patt) bin.slice!(0, sizeof(name)) else @values << patt.call(bin, *[name]) end end self end |