class FFI::StructGenerator

Generates an FFI Struct layout.

Given the @@@ portion in:

class 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

Public Class Methods

# 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
# File lib/ffi/tools/struct_generator.rb, line 55
def self.options
  @options
end
# File lib/ffi/tools/struct_generator.rb, line 52
def self.options=(options)
  @options = options
end

Public Instance Methods

# 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 %[  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

        cc = ENV['CC'] || 'gcc'
        output = `#{cc} #{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 = `#{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
# File lib/ffi/tools/struct_generator.rb, line 125
def dump_config(io)
  io.puts "rbx.platform.#{@name}.sizeof = #{@size}"

  @fields.each { |field| io.puts field.to_config(@name) }
end
# File lib/ffi/tools/struct_generator.rb, line 115
def field(name, type=nil)
  field = Field.new(name, type)
  @fields << field
  return field
end
# File lib/ffi/tools/struct_generator.rb, line 121
def found?
  @found
end
# File lib/ffi/tools/struct_generator.rb, line 131
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
# File lib/ffi/tools/struct_generator.rb, line 149
def get_field(name)
  @fields.find { |f| name == f.name }
end
# File lib/ffi/tools/struct_generator.rb, line 153
def include(i)
  @includes << i
end
# File lib/ffi/tools/struct_generator.rb, line 157
def name(n)
  @struct_name = n
end