-
Notifications
You must be signed in to change notification settings - Fork 0
Automatic Struct Layout
This page details how to calculate platform-specific struct layouts.
This technique is proably 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!).
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 = { :cflags => "-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