I must be missing something. I've been reading about FFI and cannot seem to get a clear answer on this. Let's say I have the following C++ function:
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();
}
}
From what I can tell, if using this from FFI, all you would have to do is the following:
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
My question is this: who cleans up the memory? The C++ method is new
'ing up the char* but never freeing it. Does FFI handle this? What am I missing here?
Thanks in advance.
Ruby FFI tries to be symmetric about who owns memory - if you allocate it (i.e. the C code), you have to free it. Conversely, if FFI allocates it, it alone can free it.
You didn't post your FreeStrings() function, but assuming it looks a bit like:
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
}
And you use it thusly:
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)
Then that should work.
The equivalent C code would be:
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);