-
Notifications
You must be signed in to change notification settings - Fork 0
Structs
This struct (meant to mirror a C struct), has a single value, named “value”, of type “double”
class SimpleStruct < FFI::Struct layout :value, :double end a = SimpleStruct.new # does a malloc on one of those structs a[:value] = 32 # sets its internal value to 32
When you call Struct.new
, it allocs an “internal bytes” worth of memory right then. When you then do a set, like struct[:member] = 3
, the bits within that struct are set right then. The inverse is also true; x = struct[:member]
does a read from the raw memory (and translation into a ruby object), each time you access it. Memory is “zeroed out” when it is first allocated, unless you pass in your own MemoryPointer or specify it to not clear memory (additional notes). If you pass it a Pointer it basically uses that as its base instead of allocating anything.
# Pass false as a 3rd argument to skip memory initialization
pointer = FFI::MemoryPointer.new :byte, SimpleStruct.size, false
a = SimpleStruct.new pointer
Note that you can get the actual address of the struct by using the #to_ptr
method, since it is actually a wrapper for its “internal bytes” of memory.
The same technique can be used to cast a “formless” blob of memory to a struct.
class ComplexStruct < FFI::Struct
layout :context, :pointer,
:value1, :int32,
:value2, :uint64,
:value3, :char,
:next, :pointer
end
def cast_to_complex_struct pointer
ComplexStruct.new pointer
end
Calling cast_to_complex_struct
and passing it a blob of memory will return a struct object mapped to that memory location. Code can then directly access the struct fields and operate on them.
my_struct = cast_to_complex_struct(FFI::MemoryPointer.new :char, ComplexStruct.size)
my_struct[:value1] = rand(1000)
my_struct[:value2] = my_struct[:value1] * my_struct[:value3]
There’s a tutorial on the examples page that has some nested pointers.
from C
struct {
uint8 Value;
uint8 String[SIZE_OF_ARRAY];
} MyArray_t;
to Ruby
class MyArray_t < FFI::Struct
layout :Value, :uint8,
:String, [:uint8, SIZE_OF_ARRAY]
end
use via:
my_array_struct[:String].to_ptr.read_string
Set them like
a = MyArray_t.new a[:String][0] = 33 # set a single byte within the struct
or
a[:String].to_ptr.put_string(0, "foo")
By default, ffi will assume the structs you mentioned are to be packed “like normal for your system” — if this is not the case, as in if you use #pragma pack
in msvc, then you’ll need to specify the offsets manually
class CustomPacked < FFI::Struct
layout :name, :type, 3, # we gave it a custom size
:name2, :type, 4 # another custom size, in bytes
You can access an array of structs by stepping through the array in increments of the struct size, and casting each blob of memory using FFI::Pointer#.
Here is an example of a parent struct that contains an array of structs, and each member of the array also contains a union.
From c:
// parent struct contains an array of child structs in *val
typedef struct {
uint len;
F_TextItemT *val;
} F_TextItemsT;
typedef struct {
int offset;
int dataType; // indicates what part of the union is valid
union {
char *sdata; // string data
int idata; // int data
} u;
} F_TextItemT;
In Ruby the structs and union look like this:
class FTextItemU < FFI::Union
layout :sdata, :string,
:idata, :int
end
class FTextItemT < FFI::Struct
layout :offset, :int,
:dataType, :int,
:u, FTextItemU
end
class FTextItemsT < FFI::Struct
layout :len, :uint,
:val, :pointer
end
This code accesses members of the array using FFI::Pointer# by stepping through the array at increments of the struct size:
tis = FM.FApiGetText(docid, flowid, (FM.FTIString | FM.FTILineEnd));
# Traverse text items and print strings and line ends.
0.upto(tis[:len]-1) do |i|
s = FM::FTextItemT.new(tis[:val] + (i * FM::FTextItemT.size))
if s[:dataType] == FM.FTIString
puts s[:u][:sdata]
end
end
Alternatively, you can use Pointer.new() to create a new pointer with a different type_size (the size used to step
along it using the [] method).
e.g.
val_array = FFI::Pointer.new(FM::FTextItemT, tis[:val])
0.upto(tis[:len]) do |i|
s = FM::FTextItemT.new(val_array[i])
# do stuff with s struct here
end
If you need to pass an array of structs to a function, you can use the same approach: first pre-allocate some memory for the array, and then step through it. If you wish, you can create an array of Ruby objects, each of which points to the correct place in the underlying storage. The following example uses struct iovec as used by sendmsg(2) and recvmsg(2)
class IOVec < FFI::Struct layout :base, :pointer, :len, :size_t end iovlen = 3 iov = FFI::MemoryPointer.new(IOVec, iovlen) iovs = iovlen.times.collect do |i| IOVec.new(iov + i * IOVec.size) end iovs[0][:base] = ... iovs[0][:len] = ... iovs[1][:base] = ... iovs[1][:len] = ... iovs[2][:base] = ... iovs[2][:len] = ... msghdr.msg_iov = iov msghdr.msg_iovlen = iovlen
Currently if you want to pass a boolean value through a struct, you basically have to use an int or char, set it to something to represent true, something else to represent false, then “re-interpret” this value where needed (like if struct[:boolean] == 0; else; (was positive); end).
multidimentional arrays aren’t yet fully supported
any type of class descendancy/hierarchy is not supported, at least it appears to not be.
Currently structs only allow access “like a Hash” via instance[:member]
. If you desire methods for access, you can use ffi swig, or nice-ffi or roll your own