Skip to content

Automatic Struct Layout

remogatto edited this page Sep 13, 2010 · 13 revisions

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

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

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 object.rb.ffi.

module MyLibrary
  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:

desc "generate FFI structs"
task :ffi_generate do
  require 'ffi'
  require 'ffi/tools/generator'
  require 'ffi/tools/struct_generator'

  ffi_files = ["my_object.rb.ffi", ...]
  ffi_options = "-I/usr/local/mylibrary"
  ffi_files.each do |ffi_file|
    ruby_file = ffi_file.gsub(/\.ffi$/,'')
    unless uptodate?(ruby_file, ffi_file)
      puts "generating: #{ffi_file} => #{ruby_file}"
      FFI::Generator.new ffi_file, ruby_file, ffi_options    
    end
  end

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

module MyLibrary
  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
Clone this wiki locally