123456789_123456789_123456789_123456789_123456789_

This page details how to calculate platform-specific struct layouts.

This technique is probably no longer needed

The Problem

Imagine a C API contains a struct defined as follows:

struct MyObject {
  void               *app_data ;
  struct MyObject    *next ;
  unsigned long long  size ;
  short               age ;
};

The problem with building a ::FFI::Struct layout is that the data members are at different offsets depending on whether you’re running on a 32-bit platform or a 64-bit platform.

32-bit:

layout :app_data, :pointer, 0,
       :next, :pointer, 4,      # pointers are 4 bytes
       :size, :long_long, 8,
       :age, :short, 12         # long longs are same as longs - 4 bytes
# total size 14 bytes

64-bit:

layout :app_data, :pointer, 0,
       :next, :pointer, 8,      # pointers are 8 bytes
       :size, :long_long, 16,
       :age, :short, 24         # long longs are 8 bytes, too
# total size 26 bytes

How do you make sure your code will use the correct offsets on any platform?

The Solution: Struct Generator

FFI comes with some clever code (originating with the Rubinius project) to calculate these offsets at build-time (not at runtime!) and to retrieve constant values from header files.

Struct Generator machine-generates C code to access the data members, compiles it, and analyzes its output to determine each member’s offset.

First step: write your FFI struct template to a file named my_object.rb.ffi.

module MyLibrary
  @@@
  constants do |c|
    c.include "my_library.h"  # the C header file which defines required constants
    c.const :LIB_VER_MAJOR    # the C constant to be defined
    c.const :LIB_VER_MINOR
  end
  @@@

  class MyObject < FFI::Struct # note this can be also be used with FFI::ManagedStruct
    @@@
    struct do |s|
      s.name 'struct MyObject'  # the C typedef
      s.include 'my_library.h'  # the C header file
      s.field :app_data, :pointer
      s.field :next,     :pointer
      s.field :size,     :long_long
      s.field :age,      :short
    end
    @@@
  end
end

Second step: add a task to your project’s Rakefile to generate these structs:

require "ffi/tools/generator_task"
FFI::Generator::Task.new ["my_object.rb"], cflags: "-I/usr/local/mylibrary"
desc "generate FFI structs"
task :ffi_generate => ["my_object.rb"]

The result will be a file, “my_object.rb” that looks like this (on 32-bit):

module MyLibrary
  LIB_VER_MAJOR = 3
  LIB_VER_MINOR = 1
  class MyObject < FFI::Struct # note this can be also be used with FFI::ManagedStruct
    layout :app_data, :pointer, 0,
           :next, :pointer, 4,
           :size, :long_long, 8,
           :age, :short, 12
    end
  end
end