linkyndy
linkyndy

Reputation: 17900

Ruby FFI to call C function with array of arrays

I have the following C interface:

int foo(void* bar, void* baz);

What this does, basically, is take an array of RGB values, process them, and return a new array of RGB values.

I wrote the following Ruby FFI wrapper for it:

module MyLibrary
  extend FFI::Library
  ffi_lib "path/to/mylibrary.so"
  attach_function :foo, [:pointer, :pointer], :int
end

However, I have not really succeeded to pass a Ruby array-of-arrays to this FFI wrapper. In Ruby, I have something like:

pixels = [[3, 34, 123], [32, 253, 34], ..., [1, 1, 34]]
result = [[0, 0, 0], [0, 0, 0], ..., [0, 0, 0]]

# This does not work!
MyLibrary.foo(pixels, result)

I've looked in the Ruby FFI docs, however I did not get how the Ruby arrays should be passed to the FFI wrapper.

Upvotes: 2

Views: 534

Answers (1)

matt
matt

Reputation: 79733

To pass the data in to the function you need to to use a MemoryPointer, first copying the data from the Ruby arrays into it so that it is in the correct form when the C code sees it. Copying the data for a single dimensional array is fairly straightforward using one of the write_array_of_* methods. For a multi dimensional array it is a little trickier, you need to copy each array into the correct place int the memory managed to the MemoryPointer.

Similarly for data returned by the function through a pointer you need to provide a MemoryPointer of the right size and then copy the data out into Ruby arrays. Again this is fairly easy for a single dimension array with the read_array_of* methods and is a little more work for a multi dimension array.

Here is a simple example. Here I assume the arguments to the C function always consists of three three-element int arrays – int[3][3].

The C function:

int foo(void* bar, void* baz) {
    // assume both arrays are [3][3]
    int(*bar_)[3] = (int (*)[3]) bar;
    int(*baz_)[3] = (int (*)[3]) baz;

    // Highly complex processing - double each entry.
    for (int i = 0; i< 3; i++) {
        for (int j = 0; j < 3; j++) {
            baz_[i][j] = 2 * bar_[i][j];
        }
    }

    return 0;
}

Here is the Ruby code to access it:

require 'ffi'

module MyLibrary
  extend FFI::Library
  ffi_lib "path/to/mylibrary.so"

  # Give function a different name. You might also want to make
  # it private.
  attach_function(:ffi_foo, :foo, [:pointer, :pointer], :int)

  # Wrap the C function with a friendly method that packages
  # and unpackages the data.
  def self.foo(pixels)
    # Create the MemoryPointers for input and output. They are
    # both 9 entry (3 * 3) arrays of uint32.
    input = FFI::MemoryPointer.new(:uint32, 9)
    output = FFI::MemoryPointer.new(:uint32, 9)

    # Copy the input data into the input MemoryPointer
    pixels.each_with_index do |ary, idx|
      # The offset here is in bytes. int32 is 4 bytes, each
      # array is three elements so total is 3 * 4 = 12.
      input.put_array_of_int32(idx * 12, ary)
    end

    # Call the C function.
    ffi_foo(input, output)

    result = []

    # Copy the data back into a Ruby array.
    3.times do |idx|
      result << output.get_array_of_int32(idx * 12, 3)
    end

    # Return the final result
    result

  end
end

You can then use it like this:

pixels = [[3, 34, 123], [32, 253, 34], [1, 1, 34]]
p MyLibrary.foo(pixels) #=>[[6, 68, 246], [64, 506, 68], [2, 2, 68]]

Obviously you will need to adapt this to match the details of your own function. You should probably also add error checking, otherwise you could be liable to getting segfaults.

Upvotes: 2

Related Questions