2012-12-28 41 views
4

我必須缺少一些東西。我一直在閱讀關於FFI的內容,似乎無法得到明確的答案。比方說,我有下面的C++函數:FFI/MemoryPointer內存分配

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是否處理這個問題?我在這裏錯過了什麼?

在此先感謝。

回答

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); 
+0

是的,這正是我最終做的。感謝您的明確的代碼解釋! – Levi

4

我認爲,在許多ffi中,無論您使用何種語言,內建類型(如字符串)的值都必須使用專門提供的運行時函數來構造。 Ruby遵守以下規則:

請參閱此article以獲取關於此問題的快速教程,語言作者爲該語言的1.8版提供了一個快速教程。

如果你堅持在你的代碼中分配數據塊(使用C++或普通C) - 畢竟這是使用擴展的要點 - 最安全的路徑可能是用一個結構包裝它,並使用所謂的這是由ffi提供的一個託管結構設施,它將一個處理函數連接到您的數據(您也必須編寫它),以便ruby知道如何在數據不再需要時釋放數據。 但是,您也可以簡單地將數據聲明爲ruby中的指針(看起來這是您所做的),並要求userland明確釋放該數據(同樣使用擴展提供的dispose函數)。

這裏的another page演示了使用託管結構。

最後,不要忘記限定任何你想出口到ruby的C++函數,如extern "C"(如果你沒有這樣做)。

+0

感謝您的信息。爲簡潔起見,我沒有包含'extern「C'',但爲了清晰起見,我將編輯該問題。再次感謝! – Levi

+0

我可能會遺漏一些東西,但我提供了下面的C方法'void FreeStrings(const char * strings [],const int numberOfStrings)''。在分配內存和調用FreeStrings之後,我沒有發現有任何區別(當觀察ruby.exe的Process Explorer中的工作集時),而不是從ruby調用FreeStrings。或者,也許我誤解了你所說的話,並且因爲我使用了'FFI :: MemoryPointer',我不必擔心它? – Levi

+0

FFI將處理它分配的內容(空間爲請求的指針數量),但對get_strings分配的內存一無所知 - 您需要清除那個 –