Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
be5ef11
Allow access to a struct's underlying memory with `struct[offset, len…
sinisterchipmunk Jan 22, 2020
86471b5
Make accessing a struct's underlying memory more convenient.
sinisterchipmunk Oct 30, 2018
34cdbc9
refactor memory access unit tests for improved clarity
sinisterchipmunk Jan 22, 2020
cb52c5e
fix assignment to elements in a member which is an array
sinisterchipmunk Jan 22, 2020
633be4d
fix assignment to elements in a member which is an array
sinisterchipmunk Jan 22, 2020
4c304b8
support nested structs and arrays of nested structs
sinisterchipmunk Jan 22, 2020
1677038
allow an already defined struct to be reused in the definition of ano…
sinisterchipmunk Jan 22, 2020
5398949
support for structs nested inside of unions
sinisterchipmunk Sep 10, 2018
32668dc
Add documentation to README for nested structs.
sinisterchipmunk Oct 30, 2018
7ab7f3c
clarify documentation
sinisterchipmunk Oct 30, 2018
a00a03d
fix alignment and size when members appear after a nested struct
sinisterchipmunk Nov 1, 2018
1783a00
Fix incorrect sizing of structs containing unions and pointers.
sinisterchipmunk Jan 22, 2020
8e75b26
remove duplicated test case
sinisterchipmunk Jan 22, 2020
d461ae2
use IndexError instead of RangeError
sinisterchipmunk Jan 22, 2020
6d3d9b0
use @size, not @align
sinisterchipmunk Jan 22, 2020
553cd8b
Fix rebase miss
kou Sep 8, 2020
af25a89
Use existing Pointer#to_i
kou Sep 8, 2020
178fffd
Move memcpy to Fiddle from Fiddle::Pointer
kou Sep 10, 2020
cf46987
Add missing compact(struct_name)
kou Sep 11, 2020
7b61ad5
Extract existence method check
kou Sep 11, 2020
cc2978f
Use is_a? instead of kind_of? for consistency
kou Sep 11, 2020
483625a
Don't provide nil as count for signature
kou Sep 11, 2020
240199e
Simplify
kou Sep 11, 2020
358d40a
Unify common code
kou Sep 11, 2020
642a450
Respect entity class in types
kou Sep 11, 2020
4848186
Simplify
kou Sep 11, 2020
40669d3
Don't use Pointer#size as the size of address
kou Sep 12, 2020
67a040e
Assign struct member value one by one
kou Sep 12, 2020
288b11b
Adjust style
kou Sep 12, 2020
bffce70
Remove memcpy because we have "Pointer[offset, length] = pointer"
kou Sep 12, 2020
ec7c0ee
Remove offset_of because it's not used
kou Sep 12, 2020
a6e6438
Add support for an array of struct
kou Sep 12, 2020
b85692d
Use CStruct as struct signature
kou Sep 12, 2020
4fb2dc9
Reuse StructArray for an array of nested struct
kou Sep 12, 2020
43a5c6f
Fix a bug that struct.malloc {} doesn't call block
kou Sep 12, 2020
64b4682
Use .entity_class to detect CStruct
kou Sep 12, 2020
aae0663
Reduce diff
kou Sep 12, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,57 @@ floor = Fiddle::Function.new(
puts floor.call(3.14159) #=> 3.0
```

### Nested Structs

You can use hashes to create nested structs, where the hash keys are member names and the values are the nested structs:

```ruby
StudentCollegeDetail = struct [
'int college_id',
'char college_name[50]'
]

StudentDetail = struct [
'int id',
'char name[20]',
{ clg_data: StudentCollegeDetail }
]
```

You can also specify an anonymous nested struct, like so:

```ruby
StudentDetail = struct [
'int id',
'char name[20]',
{
clg_data: struct([
'int college_id',
'char college_name[50]'
])
}
]
```

The position of a hash (and the order of the keys in the hash, in the case of a hash with multiple entries), dictate the offsets of the nested struct in memory. The following examples are both syntactically valid but will lay out the structs differently in memory:

```ruby
# order of members in memory: position, id, dimensions
Rect = struct [ { position: struct(['float x', 'float y']) },
'int id',
{ dimensions: struct(['float w', 'float h']) }
]

# order of members in memory: id, position, dimensions
Rect = struct [ 'int id',
{
position: struct(['float x', 'float y']),
dimensions: struct(['float w', 'float h'])
}
]
```


## Development

After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
Expand Down
27 changes: 26 additions & 1 deletion lib/fiddle/cparser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,37 @@ module CParser
def parse_struct_signature(signature, tymap=nil)
if signature.is_a?(String)
signature = split_arguments(signature, /[,;]/)
elsif signature.is_a?(Hash)
signature = [signature]
end
mems = []
tys = []
signature.each{|msig|
msig = compact(msig)
msig = compact(msig) if msig.is_a?(String)
case msig
when Hash
msig.each do |struct_name, struct_signature|
struct_name = struct_name.to_s if struct_name.is_a?(Symbol)
struct_name = compact(struct_name)
struct_count = nil
if struct_name =~ /^([\w\*\s]+)\[(\d+)\]$/
struct_count = $2.to_i
struct_name = $1
end
if struct_signature.respond_to?(:entity_class)
struct_type = struct_signature
else
parsed_struct = parse_struct_signature(struct_signature, tymap)
struct_type = CStructBuilder.create(CStruct, *parsed_struct)
end
if struct_count
ty = [struct_type, struct_count]
else
ty = struct_type
end
mems.push([struct_name, struct_type.members])
tys.push(ty)
end
when /^[\w\*\s]+[\*\s](\w+)$/
mems.push($1)
tys.push(parse_ctype(msig, tymap))
Expand Down
180 changes: 163 additions & 17 deletions lib/fiddle/struct.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,67 @@
module Fiddle
# A base class for objects representing a C structure
class CStruct
include Enumerable

# accessor to Fiddle::CStructEntity
def CStruct.entity_class
CStructEntity
end

def each
return enum_for(__function__) unless block_given?

self.class.members.each do |name,|
yield(self[name])
end
end

def each_pair
return enum_for(__function__) unless block_given?

self.class.members.each do |name,|
yield(name, self[name])
end
end

def to_h
hash = {}
each_pair do |name, value|
hash[name] = unstruct(value)
end
hash
end

def replace(another)
if another.nil?
self.class.members.each do |name,|
self[name] = nil
end
elsif another.respond_to?(:each_pair)
another.each_pair do |name, value|
self[name] = value
end
else
another.each do |name, value|
self[name] = value
end
end
self
end

private
def unstruct(value)
case value
when CStruct
value.to_h
when Array
value.collect do |v|
unstruct(v)
end
else
value
end
end
end

# A base class for objects representing a C union
Expand All @@ -27,10 +84,14 @@ class StructArray < Array
def initialize(ptr, type, initial_values)
@ptr = ptr
@type = type
@align = PackInfo::ALIGN_MAP[type]
@size = Fiddle::PackInfo::SIZE_MAP[type]
@pack_format = Fiddle::PackInfo::PACK_MAP[type]
super(initial_values.collect { |v| unsigned_value(v, type) })
@is_struct = @type.respond_to?(:entity_class)
if @is_struct
super(initial_values)
else
@size = Fiddle::PackInfo::SIZE_MAP[type]
@pack_format = Fiddle::PackInfo::PACK_MAP[type]
super(initial_values.collect { |v| unsigned_value(v, type) })
end
end

def to_ptr
Expand All @@ -42,8 +103,12 @@ def []=(index, value)
raise IndexError, 'index %d outside of array bounds 0...%d' % [index, size]
end

to_ptr[index * @size, @size] = [value].pack(@pack_format)
super(index, value)
if @is_struct
self[index].replace(value)
else
to_ptr[index * @size, @size] = [value].pack(@pack_format)
super(index, value)
end
end
end

Expand Down Expand Up @@ -105,16 +170,23 @@ def create(klass, types, members)
define_method(:[]=) { |*args| @entity.send(:[]=, *args) }
define_method(:to_ptr){ @entity }
define_method(:to_i){ @entity.to_i }
define_singleton_method(:types) { types }
define_singleton_method(:members) { members }
members.each{|name|
name = name[0] if name.is_a?(Array) # name is a nested struct
next if method_defined?(name)
define_method(name){ @entity[name] }
define_method(name + "="){|val| @entity[name] = val }
}
size = klass.entity_class.size(types)
entity_class = klass.entity_class
alignment = entity_class.alignment(types)
size = entity_class.size(types)
define_singleton_method(:alignment) { alignment }
define_singleton_method(:size) { size }
define_singleton_method(:malloc) do |func=nil|
if block_given?
define_singleton_method(:malloc) do |func=nil, &block|
if block
entity_class.malloc(types, func, size) do |entity|
yield new(entity)
block.call(new(entity))
end
else
new(entity_class.malloc(types, func, size))
Expand All @@ -131,6 +203,19 @@ class CStructEntity < Fiddle::Pointer
include PackInfo
include ValueUtil

def CStructEntity.alignment(types)
max = 1
types.each do |type, count = 1|
if type.respond_to?(:entity_class)
n = type.alignment
else
n = ALIGN_MAP[type]
end
max = n if n > max
end
max
end

# Allocates a C struct with the +types+ provided.
#
# See Fiddle::Pointer.malloc for memory management issues.
Expand Down Expand Up @@ -160,9 +245,15 @@ def CStructEntity.size(types)
max_align = types.map { |type, count = 1|
last_offset = offset

align = PackInfo::ALIGN_MAP[type]
if type.respond_to?(:entity_class)
align = type.alignment
type_size = type.size
else
align = PackInfo::ALIGN_MAP[type]
type_size = PackInfo::SIZE_MAP[type]
end
offset = PackInfo.align(last_offset, align) +
(PackInfo::SIZE_MAP[type] * count)
(type_size * count)

align
}.max
Expand All @@ -185,7 +276,28 @@ def initialize(addr, types, func = nil)

# Set the names of the +members+ in this C struct
def assign_names(members)
@members = members
@members = []
@nested_structs = {}
members.each_with_index do |member, index|
if member.is_a?(Array) # nested struct
member_name = member[0]
struct_type, struct_count = @ctypes[index]
if struct_count.nil?
struct = struct_type.new(to_i + @offset[index])
else
structs = struct_count.times.map do |i|
struct_type.new(to_i + @offset[index] + i * struct_type.size)
end
struct = StructArray.new(to_i + @offset[index],
struct_type,
structs)
end
@nested_structs[member_name] = struct
else
member_name = member
end
@members << member_name
end
end

# Calculates the offsets and sizes for the given +types+ in the struct.
Expand All @@ -196,12 +308,18 @@ def set_ctypes(types)

max_align = types.map { |type, count = 1|
orig_offset = offset
align = ALIGN_MAP[type]
if type.respond_to?(:entity_class)
align = type.alignment
type_size = type.size
else
align = ALIGN_MAP[type]
type_size = SIZE_MAP[type]
end
offset = PackInfo.align(orig_offset, align)

@offset << offset

offset += (SIZE_MAP[type] * count)
offset += (type_size * count)

align
}.max
Expand Down Expand Up @@ -230,7 +348,13 @@ def [](*args)
end
ty = @ctypes[idx]
if( ty.is_a?(Array) )
r = super(@offset[idx], SIZE_MAP[ty[0]] * ty[1])
if ty.first.respond_to?(:entity_class)
return @nested_structs[name]
else
r = super(@offset[idx], SIZE_MAP[ty[0]] * ty[1])
end
elsif ty.respond_to?(:entity_class)
return @nested_structs[name]
else
r = super(@offset[idx], SIZE_MAP[ty.abs])
end
Expand Down Expand Up @@ -270,6 +394,24 @@ def [](*args)
def []=(*args)
return super(*args) if args.size > 2
name, val = *args
name = name.to_s if name.is_a?(Symbol)
nested_struct = @nested_structs[name]
if nested_struct
if nested_struct.is_a?(StructArray)
if val.nil?
nested_struct.each do |s|
s.replace(nil)
end
else
val.each_with_index do |v, i|
nested_struct[i] = v
end
end
else
nested_struct.replace(val)
end
return val
end
idx = @members.index(name)
if( idx.nil? )
raise(ArgumentError, "no such member: #{name}")
Expand Down Expand Up @@ -307,7 +449,11 @@ class CUnionEntity < CStructEntity
# Fiddle::TYPE_VOIDP ]) #=> 8
def CUnionEntity.size(types)
types.map { |type, count = 1|
PackInfo::SIZE_MAP[type] * count
if type.respond_to?(:entity_class)
type.size * count
else
PackInfo::SIZE_MAP[type] * count
end
}.max
end

Expand Down
Loading