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
.
我正在努力了解 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
.