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