class FFI::StructGenerator

Generates an FFI Struct layout.

Given the @@@ portion in:

module Zlib::ZStream < FFI::Struct
  @@@
  name "struct z_stream_s"
  include "zlib.h"

  field :next_in,   :pointer
  field :avail_in,  :uint
  field :total_in,  :ulong

  # ...
  @@@
end

StructGenerator will create the layout:

layout :next_in, :pointer, 0,
       :avail_in, :uint, 4,
       :total_in, :ulong, 8,
       # ...

StructGenerator does its best to pad the layout it produces to preserve line numbers. Place the struct definition as close to the top of the file for best results.

Attributes

fields[R]
size[RW]

Public Class Methods

new(name, options = {}) { |self| ... } click to toggle source
# File lib/ffi/tools/struct_generator.rb, line 39
def initialize(name, options = {})
  @name = name
  @struct_name = nil
  @includes = []
  @fields = []
  @found = false
  @size = nil

  if block_given? then
    yield self
    calculate self.class.options.merge(options)
  end
end
options() click to toggle source
# File lib/ffi/tools/struct_generator.rb, line 55
def self.options
  @options
end
options=(options) click to toggle source
# File lib/ffi/tools/struct_generator.rb, line 52
def self.options=(options)
  @options = options
end

Public Instance Methods

calculate(options = {}) click to toggle source
# File lib/ffi/tools/struct_generator.rb, line 58
  def calculate(options = {})
    binary = File.join Dir.tmpdir, "rb_struct_gen_bin_#{Process.pid}"

    raise "struct name not set" if @struct_name.nil?

    Tempfile.open("#{@name}.struct_generator") do |f|
      f.puts "#include <stdio.h>"

      @includes.each do |inc|
        f.puts "#include <#{inc}>"
      end

      f.puts "#include <stddef.h>\n\n"
      f.puts "int main(int argc, char **argv)\n{"
      f.puts "  #{@struct_name} s;"
      f.puts %Q[  printf("sizeof(#{@struct_name}) %u\\n", (unsigned int) sizeof(#{@struct_name}));]

      @fields.each do |field|
        f.puts <<-EOF
  printf("#{field.name} %u %u\\n", (unsigned int) offsetof(#{@struct_name}, #{field.name}),
         (unsigned int) sizeof(s.#{field.name}));
EOF
      end

      f.puts "\n  return 0;\n}"
      f.flush

      output = %x`gcc #{options[:cppflags]} #{options[:cflags]} -D_DARWIN_USE_64_BIT_INODE -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -x c -Wall -Werror #{f.path} -o #{binary} 2>&1`

      unless $?.success? then
        @found = false
        output = output.split("\n").map { |l| "\t#{l}" }.join "\n"
        raise "Compilation error generating struct #{@name} (#{@struct_name}):\n#{output}"
      end
    end
    
    output = %x`#{binary}`.split "\n"
    File.unlink(binary + (FFI::Platform.windows? ? ".exe" : ""))
    sizeof = output.shift
    unless @size
      m = /\s*sizeof\([^)]+\) (\d+)/.match sizeof
      @size = m[1]
    end

    line_no = 0
    output.each do |line|
      md = line.match(/.+ (\d+) (\d+)/)
      @fields[line_no].offset = md[1].to_i
      @fields[line_no].size   = md[2].to_i

      line_no += 1
    end

    @found = true
  end
dump_config(io) click to toggle source
# File lib/ffi/tools/struct_generator.rb, line 124
def dump_config(io)
  io.puts "rbx.platform.#{@name}.sizeof = #{@size}"

  @fields.each { |field| io.puts field.to_config(@name) }
end
field(name, type=nil) click to toggle source
# File lib/ffi/tools/struct_generator.rb, line 114
def field(name, type=nil)
  field = Field.new(name, type)
  @fields << field
  return field
end
found?() click to toggle source
# File lib/ffi/tools/struct_generator.rb, line 120
def found?
  @found
end
generate_layout() click to toggle source
# File lib/ffi/tools/struct_generator.rb, line 130
def generate_layout
  buf = ""

  @fields.each_with_index do |field, i|
    if buf.empty?
      buf << "layout :#{field.name}, :#{field.type}, #{field.offset}"
    else
      buf << "       :#{field.name}, :#{field.type}, #{field.offset}"
    end

    if i < @fields.length - 1
      buf << ",\n"
    end
  end

  buf
end
get_field(name) click to toggle source
# File lib/ffi/tools/struct_generator.rb, line 148
def get_field(name)
  @fields.find { |f| name == f.name }
end
include(i) click to toggle source
# File lib/ffi/tools/struct_generator.rb, line 152
def include(i)
  @includes << i
end
name(n) click to toggle source
# File lib/ffi/tools/struct_generator.rb, line 156
def name(n)
  @struct_name = n
end