Class: CTypes::Struct
Overview
fields are not automatically aligned based on size; if there are gaps present between c struct fields, you'll need to manually add padding in the layout to reflect that alignment.
This class is used to represent c structures in ruby. It provides methods for converting structs between their byte representation and a ruby representation that can be modified.
Defined Under Namespace
Classes: Builder
Instance Attribute Summary
Attributes included from Type
Class Method Summary collapse
-
.==(other) ⇒ Object
check if another Struct subclass has the same attributes as this Struct.
-
._new ⇒ Struct
private
allocate an uninitialized instance of the struct.
- .builder ⇒ Object
- .export_type(q) ⇒ Object
-
.field_layout ⇒ Object
return the list of fields with their associated types.
-
.fields ⇒ Object
return the list of fields in this structure.
-
.greedy? ⇒ Boolean
private
check if this type is greedy.
-
.has_field?(k) ⇒ Boolean
check if the struct has a given attribute.
-
.layout(&block) ⇒ Object
define the layout for this structure.
-
.new(fields = nil) ⇒ Struct
allocate an instance of the Struct and initialize default values.
-
.offsetof(attr) ⇒ Integer
get the offset of a field within the structure in bytes.
-
.pack(value, endian: default_endian, validate: true) ⇒ ::String
encode a ruby Hash into a String containing the binary representation of the c type.
-
.pretty_print(q) ⇒ Object
:nodoc:.
-
.size ⇒ Integer
get the minimum size of the structure.
-
.type_name ⇒ Object
return the struct name if supplied.
-
.unpack_one(buf, endian: default_endian) ⇒ ::Array(Struct, ::String)
convert a String containing the binary represention of a c struct into a ruby type.
Instance Method Summary collapse
-
#==(other) ⇒ Object
determine if this instance of the struct is equal to another instance.
-
#[](k) ⇒ Object
get an attribute value.
-
#[]=(k, v) ⇒ Object
set an attribute value.
-
#has_key?(name) ⇒ Boolean
check if the Struct has a specific attribute name.
-
#pretty_print(q) ⇒ Object
:nodoc:.
-
#to_binstr(endian: @endian) ⇒ String
return the binary representation of this Struct instance.
-
#to_h(shallow: false) ⇒ Hash
(also: #to_hash)
return a Hash representation of the data type.
Methods included from Type
default_endian, default_value, fixed_size?, greedy?, pack, pread, read, unpack, unpack_all, unpack_one, with_endian, without_endian
Class Method Details
.==(other) ⇒ Object
this method does not handle dynamic sized Structs correctly, but the current implementation is sufficient for testing
check if another Struct subclass has the same attributes as this Struct
392 393 394 395 396 397 398 |
# File 'lib/ctypes/struct.rb', line 392 def self.==(other) return true if super return false unless other.is_a?(Class) && other < Struct other.field_layout == @fields && other.default_endian == default_endian && other.size == size end |
._new ⇒ Struct
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
allocate an uninitialized instance of the struct
377 |
# File 'lib/ctypes/struct.rb', line 377 alias_method :_new, :new |
.export_type(q) ⇒ Object
341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 |
# File 'lib/ctypes/struct.rb', line 341 def self.export_type(q) q << "CTypes::Struct.builder()" q.break q.nest(2) do q << ".name(%p)\n" % [@name] if @name q << ".endian(%p)\n" % [@endian] if @endian @fields.each do |name, type| case name when Symbol q << ".attribute(%p, " % [name] q << type q << ")" q.break when ::Array q << ".attribute(" q << type q << ")" q.break when Pad q << type q.break else raise Error, "unsupported field name type: %p" % [name] end end q << ".build()" end end |
.field_layout ⇒ Object
return the list of fields with their associated types
310 311 312 |
# File 'lib/ctypes/struct.rb', line 310 def self.field_layout @fields end |
.fields ⇒ Object
return the list of fields in this structure
304 305 306 |
# File 'lib/ctypes/struct.rb', line 304 def self.fields @field_accessors.keys end |
.greedy? ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
check if this type is greedy
279 280 281 |
# File 'lib/ctypes/struct.rb', line 279 def self.greedy? @greedy end |
.has_field?(k) ⇒ Boolean
check if the struct has a given attribute
298 299 300 |
# File 'lib/ctypes/struct.rb', line 298 def self.has_field?(k) @field_accessors.has_key?(k) end |
.layout(&block) ⇒ Object
define the layout for this structure
96 97 98 99 100 101 |
# File 'lib/ctypes/struct.rb', line 96 def self.layout(&block) raise Error, "no block given" unless block builder = Builder.new builder.instance_eval(&block) apply_layout(builder) end |
.new(fields = nil) ⇒ Struct
allocate an instance of the Struct and initialize default values
384 385 386 387 |
# File 'lib/ctypes/struct.rb', line 384 def self.new(fields = nil) buf = fields.nil? ? ("\0" * size) : pack(fields) unpack(buf) end |
.offsetof(attr) ⇒ Integer
get the offset of a field within the structure in bytes
267 268 269 270 271 272 273 274 |
# File 'lib/ctypes/struct.rb', line 267 def self.offsetof(attr) @offsets ||= @fields.inject([0, {}]) do |(offset, o), (key, type)| o[key] = offset [type.size ? offset + type.size : nil, o] end.last @offsets[attr] end |
.pack(value, endian: default_endian, validate: true) ⇒ ::String
encode a ruby Hash into a String containing the binary representation of the c type
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/ctypes/struct.rb', line 164 def self.pack(value, endian: default_endian, validate: true) value = value.to_hash.freeze value = @dry_type[value] unless validate == false buf = ::String.new @fields.each do |(name, type)| case name when Pad buf << type.pack(nil) when Symbol buf << type.pack(value[name], endian: type.endian || endian, validate: false) when ::Array buf << type.pack(value.slice(*name), endian: type.endian || endian, validate: false) else raise Error, "unsupported field name type: %p" % [name] end end return buf if fixed_size? || @size.nil? size = instance_exec(value, &@size) if size > buf.size buf << "\0" * (size - buf.size) elsif size < buf.size buf[0, size] else buf end end |
.pretty_print(q) ⇒ Object
:nodoc:
320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 |
# File 'lib/ctypes/struct.rb', line 320 def self.pretty_print(q) # :nodoc: q.ctype("struct", @endian) do q.line("name %p" % [@name]) if @name q.seplist(@fields, -> { q.breakable(";") }) do |name, type| case name when Symbol q.text("attribute %p, " % name) q.pp(type) when ::Array q.text("attribute ") q.pp(type) when Pad q.pp(type) else raise Error, "unsupported field name type: %p" % [name] end end end end |
.size ⇒ Integer
get the minimum size of the structure
For fixed-size structures, this will return the size of the structure. For dynamic length structures, this will return the minimum size of the structure
290 291 292 293 294 |
# File 'lib/ctypes/struct.rb', line 290 def self.size return @size if @size.is_a?(Integer) @min_size ||= @fields&.inject(0) { |s, (_, t)| s + t.size } || 0 end |
.type_name ⇒ Object
return the struct name if supplied
316 317 318 |
# File 'lib/ctypes/struct.rb', line 316 def self.type_name @name end |
.unpack_one(buf, endian: default_endian) ⇒ ::Array(Struct, ::String)
convert a String containing the binary represention of a c struct into a ruby type
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 |
# File 'lib/ctypes/struct.rb', line 218 def self.unpack_one(buf, endian: default_endian) rest = buf trimmed = nil # set to the unused portion of buf when we have @size out = _new # output structure instance out.instance_variable_set(:@endian, endian) @fields.each do |(name, type)| # if the type is greedy, and we have a dynamic size, and we haven't # already trimmed the input buffer, let's do so now. # # note: we do this while unpacking because the @size proc may require # some of the unpacked fields to determine the size of the struct such # as in TLV structs if type.greedy? && @size && !trimmed # caluclate the total size of the struct from the decoded fields size = instance_exec(out, &@size) raise missing_bytes_error(input: buf, need: size) if size > buf.size # adjust the size for how much we've already unpacked size -= offsetof(name.is_a?(Array) ? name[0] : name) # split the remaining buffer; we stick the part we aren't going to # use in trimmed, and update rest to point at our buffer trimmed = rest.byteslice(size..) rest = rest.byteslice(0, size) end value, rest = type.unpack_one(rest, endian: type.endian || endian) case name when Symbol out[name] = value when ::Array name.each { |n| out[n] = value[n] } when Pad # no op else raise Error, "unsupported field name type: %p" % [name] end end [out, trimmed || rest] end |
Instance Method Details
#==(other) ⇒ Object
this implementation also supports Hash equality through #to_h
determine if this instance of the struct is equal to another instance
495 496 497 498 499 500 501 502 503 504 505 506 |
# File 'lib/ctypes/struct.rb', line 495 def ==(other) case other when self.class self.class.field_layout.all? do |field, _| instance_variable_get(:"@#{field}") == other[field] end when Hash other == to_h else super end end |
#[](k) ⇒ Object
get an attribute value
427 428 429 430 |
# File 'lib/ctypes/struct.rb', line 427 def [](k) has_attribute!(k) instance_variable_get(:"@#{k}") end |
#[]=(k, v) ⇒ Object
set an attribute value
412 413 414 415 |
# File 'lib/ctypes/struct.rb', line 412 def []=(k, v) has_attribute!(k) instance_variable_set(:"@#{k}", v) end |
#has_key?(name) ⇒ Boolean
check if the CTypes::Struct has a specific attribute name
433 434 435 |
# File 'lib/ctypes/struct.rb', line 433 def has_key?(name) self.class.has_field?(name) end |
#pretty_print(q) ⇒ Object
:nodoc:
508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 |
# File 'lib/ctypes/struct.rb', line 508 def pretty_print(q) # :nodoc: open = if (name = self.class.type_name || self.class.name) "struct #{name} {" else "struct {" end q.group(4, open, "}") do # strip out pad fields fields = self.class.field_layout.reject do |(_, type)| type.is_a?(CTypes::Pad) end q.seplist(fields, -> { q.breakable("") }) do |name, _| names = name.is_a?(::Array) ? name : [name] names.each do |name| next if name.is_a?(CTypes::Pad) q.text(".#{name} = ") q.pp(instance_variable_get(:"@#{name}")) q.text(", ") end end end end |
#to_binstr(endian: @endian) ⇒ String
return the binary representation of this Struct instance
488 489 490 |
# File 'lib/ctypes/struct.rb', line 488 def to_binstr(endian: @endian) self.class.pack(to_h, endian:) end |
#to_h(shallow: false) ⇒ Hash Also known as: to_hash
return a Hash representation of the data type
465 466 467 468 469 470 471 472 473 474 475 |
# File 'lib/ctypes/struct.rb', line 465 def to_h(shallow: false) out = {} self.class.fields.each do |field| value = send(field) unless shallow || value.is_a?(::Array) || !value.respond_to?(:to_h) value = value.to_h end out[field] = value end out end |