Ruby FFI 用数组调用 C 函数

Ruby FFI to call C function with array of arrays

我有以下 C 接口:

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

基本上,它的作用是获取一个 RGB 值数组,对其进行处理,然后 return 一个新的 RGB 值数组。

我为它编写了以下 Ruby FFI 包装器:

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

但是,我还没有真正成功地将一个 Ruby 数组数组传递给这个 FFI 包装器。在 Ruby 中,我有类似的东西:

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)

我查看了 Ruby FFI 文档,但是我不明白 Ruby 数组应该如何传递给 FFI 包装器。

要将数据传递给您需要使用 MemoryPointer 的函数,首先将数据从 Ruby 数组复制到其中,以便在 C代码看到它。使用 write_array_of_* 方法之一复制一维数组的数据非常简单。对于多维数组,它有点棘手,您需要将每个数组复制到内存管理到 MemoryPointer.

的正确位置

类似地,对于函数通过指针返回的数据,您需要提供大小合适的 MemoryPointer,然后将数据复制到 Ruby 数组中。同样,对于使用 read_array_of* 方法的单维数组来说,这是相当容易的,而对于多维数组来说,这需要更多的工作。

这是一个简单的例子。这里我假设 C 函数的参数总是由三个 three-element 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]]

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