Structs

npomf edited this page Dec 29, 2016 · 36 revisions

an example

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 allocated

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 Pointer 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 #pointer method.

Casting

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]

Nested Structs

It’s possible to nest structs within one another by direct reference. Simply use the name of the struct type you wish to nest within another. If instead you need a pointer to a struct of that type, use the next section, “Nested pointers” for more information.

class InnerStruct < FFI::Struct
  layout :messageLength,  :uint32,
         :pMessage,       :string
end

class OuterStruct < FFI::Struct
  layout :value,  :uint32,
         :inner,  InnerStruct  # this sets up our inner struct!
end

Nested pointers

You can define nested pointers to structs within structs using FFI::Struct’s `.ptr` method:

class Struct2 < FFI::Struct
  layout :age, :int
end

class Struct1 < FFI::Struct
  layout :id, :int,
    :data, Struct2.ptr
end

# you need to initialize the inner struct, unless a C library does it for you
struct = Struct1.new
struct[:data] = Struct2.new
struct[:data][:age] = 27

Pointers to Functions

Some libraries have factory functions that return structures containing pointers to other functions in the library. Instead of modeling these pointers using the normal :pointer type, you can simply create an anonymous callback type using the callback() method.

class FunctionTable < FFI::Struct
  layout :function1, callback([:pointer, :int], :void)  # A pointer to a function that takes a pointer and an integer
end

attach_function :factory, [], :FunctionTable.by_ref

Then, once the factory function has filled in the pointers, you can call them using the regular call() method.

vtable = factory()        # Creates and returns a new structure

# Call the function that the library provided
vtable[:function1].call(my_pointer, my_integer)

Char arrays

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")

custom packed structs

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, or attribute((packed)) on gcc, then you’ll need to specify the struct packing boundary.

class CustomPacked < FFI::Struct
  pack 1  # pack all members on a 1 byte boundary
  layout :a, :char,
         :b, :int, # starts at offset=1, not offset=4
end

Array of Structs

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

Simpler struct arrays

An easier way to handle arrays of structs is to use a single memory pointer.

class SomeStruct < FFI::Struct
  # . . .
end

# where length is the size of the array
ary = FFI::MemoryPointer.new( :uchar, SomeStruct.size() * length )

This can be passed directly into a C-function and will have the requisite memory allocated to it to contain all instances of the struct you’re using.

To read out of it again, simply do the following:

# determine the length of the data
bytes = SomeStruct.size()

# read a struct from a single index; this requires two steps:
# 1. first we declare the container pointer.
# 2. then we cast it.
data = ary.slice( bytes * index, bytes )
instance_of_somestruct = SomeStruct.new( data )

If you would rather read the entirety of the pointer as a ruby Array, you can instead do this:

# determine the length of the data
bytes = SomeStruct.size()

# create an array
instances_of_somestruct = Array.new( ary.size / bytes ){|index|
  # set this index to the new SomeStruct instance
  SomeStruct.new( ary.slice( index * bytes, bytes ) )
}

Gotchas

multidimentional arrays aren’t yet fully supported

any type of class descendancy/hierarchy is not supported, at least it appears to not be.

Helpers

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

Helper example via “method_missing” builtin

You can make a simple helper method to access struct members as if they were created with `attr_accessor` with the following code, which exploits the `method_missing()` builtin method for most Ruby objects:

class FFI::Struct

  # Use the symbol passed from the method for accessing the analogous field.
  # This method can also take a &block, but we would not use it.
  def method_missing( sym, *args )
    # convert symbol to a string to allow regex checks
    str = sym.to_s
    
    # derive the member's symbolic name
    member = str.match( /^([a-z0-9_]+)/i )[1].to_sym

    # raise an exception via the default behavior if that symbol isn't a member!
    super unless members.include? member

    # this ternary checks for the presence of an equals sign (=) to indicate an
    # assignment operation and changes which method we invoke and whether or not
    # to send the splatted arguments as well.
    (str =~ /=$/) ? send( :[]=, member, *args ) : send( :[], member )
  end
end