4

我肯定错过了什么。我一直在阅读有关 FFI 的信息,但似乎无法得到明确的答案。假设我有以下 C++ 函数:

extern "C" {
  int ReturnAnArrayOfStrings(const char* arrayOfStrings[]) {
    if( NULL == arrayOfStrings ) return someCharList.size();

    for(auto iter = someCharList.begin(), auto index = 0; iter != someCharList.end(); ++iter, ++index) {
        char* allocatedHere = new char[strlen(*iter)]; // note that this is not freed
        strcpy_s(allocatedHere, strlen(*iter), *iter);
        arrayOfStrings[index] = allocatedHere;
    }

    return someCharList.size();
  }
}

据我所知,如果从 FFI 使用它,您只需执行以下操作:

module SomeDll
  extend FFI::Library
  ffi_lib 'SomeDll.dll'
  attach_function :get_strings, :ReturnAnArrayOfStrings, [:pointer], :int
end

include SomeDll
pointer = FFI::MemoryPointer.new :pointer, get_strings(nil)  # how many strings are there?
get_strings pointer
pointer.get_array_of_string(0).each do |value|
  puts value
end

我的问题是:谁清理内存?C++ 方法正在new启动 char* 但从未释放它。FFI 会处理这个问题吗?我在这里想念什么?

提前致谢。

4

2 回答 2

5

Ruby FFI 试图对谁拥有内存保持对称——如果你分配它(即 C 代码),你必须释放它。相反,如果 FFI 分配它,它就可以单独释放它。

您没有发布您的 FreeStrings() 函数,但假设它看起来有点像:

void FreeStringArray(char **strings, int len) {
    for (int i = 0; i < len; ++i) {
        delete[] strings[i];
    }
    // Do _NOT_ free 'strings' itself, that is managed by FFI
}

你就这样使用它:

module SomeDll
  extend FFI::Library
  ffi_lib 'SomeDll.dll'
  attach_function :get_strings, :ReturnAnArrayOfStrings, [:pointer], :int
  attach_function :free_strings, :FreeStringArray, [ :pointer, :int ], :void
end

include SomeDll

count = get_strings(nil)
strings = FFI::MemoryPointer.new :pointer, count
get_strings strings
strings.get_array_of_string(0, count).each do |value|
  puts value
end

# free each element of the array
free_strings(strings, count)

那么这应该工作。

等效的 C 代码将是:

int count = ReturnArrayOfStrings(NULL);

// Allocate an array for the pointers.  i.e. FFI::MemoryPointer.new :pointer, count
char **ptr_array = (char **) calloc(count, sizeof(char *));

ReturnArrayOfStrings(ptr_array);
for (int i = 0; i < count; ++i) {
    printf("string[%d]=%s\n", i, ptr_array[i]);
}

// Free each element of the array (but not the array itself)
FreeStringArray(ptr_array, count);

// free the array itself. i.e FFI::MemoryPointer garbage-collecting its  memory
free(ptr_array);
于 2012-12-28T21:41:28.690 回答
4

我认为在许多 ffi 中,无论您使用哪种语言,内置类型(如字符串)的值都必须使用专门提供的运行时函数来构造。Ruby 遵守这条规则:

有关该问题的快速教程,请参阅这篇文章,该教程来自语言作者,用于该语言的 1.8 版。

如果您坚持在代码中分配该数据块(使用 C++ 或纯 C)——毕竟这就是使用该扩展的目的——最安全的路径可能是用结构包装它,并使用所谓的托管结构设施提供通过 ffi 将 dispose 函数连接到您的数据(您也必须编写),以便 ruby​​ 知道如何在不再需要数据时释放数据。但是您也可以简单地将您的数据声明为 ruby​​ 中的指针(看起来这就是您所做的),并要求用户区明确释放该数据(再次使用您的扩展提供的 dispose 函数)。

这是另一个页面,展示了托管结构的使用。

最后,不要忘记将您希望导出到 ruby​​ 的任何 C++ 函数限定为extern "C"(如果您还没有这样做的话)。

于 2012-12-28T09:37:03.407 回答