Skip to content
candlerb edited this page May 8, 2011 · 49 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 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.

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 pointers

There’s a tutorial on the examples page that has some nested pointers.

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, 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

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 places in the underlying storage. The following example uses struct iov 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

Booleans

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

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.

Clone this wiki locally