Могу ли я передать указатель объекта ruby ​​на обратный вызов ruby-ffi?

Я мог бы действительно использовать толчок в правильном направлении на этом.

Учитывая этот код C:

typedef void cbfunc(void *data);
void set_callback(cbfunc* cb);
//do_stuff calls the callback multiple times with data as argument
void do_stuff(void *data);

Этот код Ruby:

module Lib
    extend FFI::Library
    # ...
    callback :cbfunc, [:pointer], :void
    attach_function :set_callback, [:cbfunc], :void
    attach_function :do_stuff, [:pointer], :void
end

Есть ли способ, которым я могу передать массив ruby ​​в качестве данных обратного вызова, что-то вроде:

proc = Proc.new do |data|
    # somehow cast FFI::Pointer to be a ruby array here?
    data.push(5)
end
Lib::set_callback(proc)
Lib::do_stuff(ptr_to_a_ruby_obj_here)

Проблема в том, что обратный вызов будет вызываться несколько раз, и мне нужен способ простого создания массива различных объектов ruby.

Может быть, я немного устал, но мне кажется, что есть простой способ сделать это, и я просто не вижу этого.

2 ответа

Решение

После публикации я понял, что могу карри Proc и использовать его в качестве обратного вызова.

Так что-то вроде:

proc = Proc.new do |results, data|
    results.push(5)
end
results = []
callback = proc[results]
Lib::set_callback(callback)
Lib::do_stuff(nil) # Not concerned with this pointer anymore

Я просто переключился на игнорирование параметра данных void* (который является обязательным для стороны C) здесь. Там должно быть несколько других способов, и мне интересно услышать о них, если кто-то хочет поделиться.

Вы можете создать FFI::Pointer из объекта Ruby, используя этот объект object_id:

# @param obj [any] Any value
# @return [::FFI::Pointer] a pointer to the given value
def ruby_to_pointer(obj)
  require 'ffi'
  address = obj.object_id << 1
  ffi_pointer = ::FFI::Pointer.new(:pointer, address)
end

Проблема заключается в получении полезного значения Ruby из этого указателя, когда он возвращается к Ruby в обратном вызове. FFI не предоставляет естественный интерфейс для этого, но модуль Ruby standardlib fiddle делает, с указателем типа:

# @param ffi_pointer [FFI::Pointer, #to_i]
# @return [any] Ruby object
def pointer_to_ruby(ffi_pointer)
  require 'fiddle'
  address = ffi_pointer.to_i
  fiddle = ::Fiddle::Pointer.new(address)
  obj = fiddle.to_value
end

Это небезопасная операция! Вы должны быть предельно осторожны, чтобы ваш исходный объект Ruby не был сборщиком мусора, прежде чем преобразовывать его представление FFI::Pointer обратно в Ruby.

Другие вопросы по тегам