Ruby FFI 回调 return 值

Ruby FFI callback return values

我正在努力了解 ruby 中的 FFI。没有办法从 FFI 回调中使用 return 吗?

这是我的最小示例:

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 中,它也以完全相同的方式失败。 (我也不保证我写的很棒)

// 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 或回调过程的 "return" 值传回 C? FFI 文档似乎总是将回调设置为 :void,我猜答案在指针页面上的某个地方——但我画的是空白(很像我的回调)

如评论中所述,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

同样,不确定这可能会导致什么内存泄漏,或者 GC 是否会使该内存指针地址无效 - 现在就去做我的研究!

如果您使用 :pointer 作为 return 类型而不是 :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 函数不保存指针并稍后尝试使用它(即 GC 不会在 C 函数完成之前发生)。如果是这样,您需要确保函数复制了该字符串,或者查看 wrapping and using malloc and free.