Automatic Struct Layout
This page details how to calculate platform-specific struct layouts.
This technique is probably no longer needed
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?
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