2

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.

1 Answer 1

2

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.

Sign up to request clarification or add additional context in comments.

3 Comments

Awesome! Thanks for pointing this out. One more thing: is it possible to use the already allocated Ruby arrays instead of allocating a separate pointer and dumping the arrays there as well? Currently arrays are copied twice (both at input and output), and if they are big, this can cause some significant overhead.
@linkyndy I don’t think that would be possible. Even though a Ruby array is backed by a C array (in MRI Ruby at least), it is an array of Ruby objects not the appropriate C type. You could possibly wrap a MemoryPointer in a class and give it a suitable API, letting you build up the C array directly and avoiding the Ruby array.
I understand. Seems like too much hassle, at least for now. Thanks!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.