首页 > 解决方案 > 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.

标签: arraysrubyimagepointersffi

解决方案


要将数据传递给您需要使用的函数MemoryPointer,首先将 Ruby 数组中的数据复制到其中,以便在 C 代码看到它时它的格式正确。使用其中一种方法复制一维数组的数据相当简单write_array_of_*。对于多维数组,它有点棘手,您需要将每个数组复制到管理到MemoryPointer.

同样,对于函数通过指针返回的数据,您需要提供MemoryPointer正确大小的 a,然后将数据复制到 Ruby 数组中。同样,对于具有方法的单维数组来说,这相当容易,而对于多维数组来说,这read_array_of*需要更多的工作。

这是一个简单的例子。在这里,我假设 C 函数的参数始终由三个三元素 int 数组组成 - int[3][3]

C函数:

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;
}

这是访问它的 Ruby 代码:

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

然后你可以像这样使用它:

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

显然,您需要对其进行调整以匹配您自己功能的细节。您可能还应该添加错误检查,否则您可能会遇到段错误。


推荐阅读