Ruby FFI для вызова функции C с массивом массивов - PullRequest
2 голосов
/ 12 мая 2019

У меня есть следующий интерфейс C:

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

Что это делает, в основном, это взять массив значений RGB, обработать их и вернуть новый массив значений 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.

1 Ответ

1 голос
/ 13 мая 2019

Чтобы передать данные в функцию, вам нужно использовать MemoryPointer, сначала скопировав в нее данные из массивов Ruby, чтобы они были в правильной форме, когда код C видитЭто.Копировать данные для одномерного массива довольно просто, используя один из методов write_array_of_*.Для многомерного массива это немного сложнее, вам нужно скопировать каждый массив в правильное место в памяти, управляемой в MemoryPointer.

Аналогично для данных, возвращаемых функцией через указатель, вам нужнопредоставьте MemoryPointer правильного размера и затем скопируйте данные в массивы 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]]

Очевидно, вам необходимо адаптировать его для соответствия деталям вашей собственной функции.Возможно, вам также следует добавить проверку ошибок, иначе вы можете получить segfaults.

...