Search code examples
c++rubywindowsffi

FFI / MemoryPointer Memory Allocation


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.


Solution

  • 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);