Ruby возвращаемые значения обратного вызова FFI - PullRequest
2 голосов
/ 15 марта 2020

Я пытаюсь разобраться с FFI в ruby. Нет ли способа использовать возврат из обратного вызова FFI?

Вот мой минимальный пример:

require 'ffi'

class Foo
  extend FFI::Library
  ffi_lib File.expand_path('fun.o')
  callback :incoming_rpc, [:string], :string
  attach_function :do_some_work, [:incoming_rpc, :string], :string, blocking: true

  def initialize
    @callback = build_callback_runner
    output = do_some_work(@callback, "Ruby init...")
    puts "Output: #{output.inspect}"
  end

  def build_callback_runner
    FFI::Function.new(:string, [:string]) do |name|
      puts "Inside runner: #{name}"
      "DO YOU READ ME?"
    end
  end

end

Foo.new

Вот функция C, которую я вызываю: да, я знаю, что это не блестяще C, мой оригинальный приемник был в go, который также не работает точно так же. (Я не буду обещать, что пишу блестяще go, либо)

// Name: fun.c
// Compiled with: gcc -shared -o fun.o fun.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef char* (*callbkfn)(char*);

extern char* do_some_work(callbkfn fn, char* name);

char* do_some_work(callbkfn fn, char* userdata) {
  printf("do_some_work param: %s\n", userdata);
  printf("callback output: %s\n", fn(strdup("Hello from C")));
  return strdup("Returned from C");
}

Вывод:

do_some_work param: Ruby init...
Inside runner: Hello from C
callback output: (null)
Output: "Returned from C"

Это тот ноль в выводе обратного вызова, который я, кажется, не могу трясти. Как вы передаете «возвращаемое» значение обратного вызова FFI :: Function или callback pro c обратно в C? Документация FFI всегда, кажется, устанавливает обратные вызовы как :void, и я предполагаю, что ответ находится где-то на странице указателей - но я рисую пробел (так же, как мой обратный вызов)

Ответы [ 2 ]

1 голос
/ 16 марта 2020

Вы можете заставить это работать, если вы используете :pointer в качестве типа возврата вместо :string (который в любом случае является просто указателем на char):

class Foo
  extend FFI::Library
  ffi_lib File.expand_path('fun.o')
  callback :incoming_rpc, [:string], :pointer # <- change here
  attach_function :do_some_work, [:incoming_rpc, :string], :string, blocking: true

  def initialize
    @callback = build_callback_runner
    output = do_some_work(@callback, "Ruby init...")
    puts "Output: #{output.inspect}"
  end

  def build_callback_runner
    FFI::Function.new(:pointer, [:string]) do |name|  # <- and here
      puts "Inside runner: #{name}"
      FFI::MemoryPointer.from_string("DO YOU READ ME?") # <- and here
    end
  end

end

Foo.new

С C код из вопроса, это производит:

do_some_work param: Ruby init...
Inside runner: Hello from C
callback output: DO YOU READ ME?
Output: "Returned from C"

Основная проблема - выделение и освобождение памяти. В этом случае MemoryPointer подвергается сборке мусора, который освобождает строковые данные. Я вполне уверен, что это безопасно, пока функция C не сохраняет указатель и не пытается использовать его позже (то есть G C не произойдет до завершения функции C). Если это произойдет, вам нужно будет убедиться, что строка скопирована функцией, или посмотрите на перенос и использование malloc и free.

0 голосов
/ 16 марта 2020

Как уже упоминалось в комментариях, похоже, что эта функция отсутствует в библиотеке FFI (boo)

Но это довольно легко исправить с помощью указателя на указатель:

// Name: fun.c
// Compiled with: gcc -shared -o fun.o fun.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef char* (*callbkfn)(char**, char*); // note the char**

extern char* do_some_work(callbkfn fn, char* name);

char* do_some_work(callbkfn fn, char* userdata) {
  char* callback_output;
  printf("do_some_work param: %s\n", userdata);
  fn(&callback_output, strdup("Hello from C"));
  printf("callback output: %s\n", callback_output);
  return strdup("Returned from C");
}

Затем измените адрес указателя в rubyland:

require 'ffi'

class Foo
  extend FFI::Library
  ffi_lib File.expand_path('fun.o')
  callback :incoming_rpc, [:pointer, :string], :void
  attach_function :do_some_work, [:incoming_rpc, :string], :string, blocking: true

  def initialize
    @callback = build_callback_runner
    output = do_some_work(@callback, "Ruby init...")
    puts "Output: #{output.inspect}"
  end

  def build_callback_runner
    FFI::Function.new(:void, [:pointer, :string]) do |output, input|
      puts "Inside runner: #{input}"
      new_string = FFI::MemoryPointer.from_string("This is my output: #{input.reverse}")
      output.write_pointer new_string.address
    end
  end

end

Foo.new

Опять же, не уверены, какие утечки памяти это может вызвать, или G C сделает этот адрес указателя памяти недействительным - go и сделай мое исследование сейчас!

...