FFI / MemoryPointer Разпределение на паметта

Сигурно пропускам нещо. Четох за 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 справя ли се с това? Какво ми липсва тук?

Благодаря предварително.


person Levi    schedule 28.12.2012    source източник


Отговори (2)


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);
person Community    schedule 28.12.2012
comment
да, точно това направих в крайна сметка. Благодаря за ясното обяснение на кода! - person Levi; 29.12.2012

Мисля, че в много ffi, независимо от езика, който използвате, стойностите на вградените типове (като низове) трябва да бъдат конструирани с помощта на специално предоставени функции за изпълнение. Ruby спазва това правило:

Вижте тази статия за бърз урок по този въпрос от автор на езика, за версия 1.8 на езика.

Ако настоявате да разпределите тази част от данни във вашия код (използвайки C++ или обикновен C) - в края на краищата това е смисълът на използването на това разширение - най-безопасният път вероятно е да го обвиете със структура и да използвате така нареченото предоставено средство за управлявана структура от ffi, за да свържете функция за изхвърляне към вашите данни (която ще трябва да напишете също), така че ruby ​​да знае как да освободи данните, след като вече не са необходими. Но можете също така просто да декларирате вашите данни като указател в ruby ​​(изглежда, че това сте направили) и да поискате от userland изрично да освободи тези данни (отново с помощта на функция за изхвърляне, предоставена от вашето разширение).

Ето друга страница, демонстрираща използването на управлявани структури.

И накрая, не забравяйте да квалифицирате всяка C++ функция, която искате да експортирате в ruby ​​като extern "C" (ако вече не сте го направили).

person didierc    schedule 28.12.2012
comment
Благодаря за информацията. Не включих extern "C" за краткост, но ще редактирам въпроса за яснота. Благодаря отново! - person Levi; 28.12.2012
comment
Може да пропускам нещо, но предоставих следния C метод void FreeStrings(const char* strings[], const int numberOfStrings). Не видях разлика (когато наблюдавах моя работен набор в Process Explorer на ruby.exe) между това да не извикам FreeStrings от ruby ​​след разпределяне на паметта и извикването на FreeStrings. Или може би не съм разбрал какво казахте и тъй като използвам FFI::MemoryPointer, не трябва да се тревожа за това? - person Levi; 28.12.2012
comment
FFI ще се погрижи за това, което е разпределил (място за заявения брой указатели), но не знае нищо за паметта, разпределена от get_strings - трябва да изчистите това - person Frederick Cheung; 28.12.2012