Свободное выделение неуправляемой памяти из управляемого кода - PullRequest
7 голосов
/ 19 декабря 2009

Приложение .NET вызывает C dll. Код C выделяет память для массива char и возвращает этот массив в качестве результата. Приложения .NET получают этот результат в виде строки.

Код C:

extern "C" __declspec(dllexport) char* __cdecl Run()
{
    char* result = (char*)malloc(100 * sizeof(char));
    // fill the array with data
    return result;
}

Код C #:

[DllImport("Unmanaged.dll")]
private static extern string Run();

...
string result = Run();
// do something useful with the result and than leave it out of scope

Некоторые тесты показывают, что сборщик мусора не освобождает память, выделенную кодом C.

Любая помощь будет оценена. :)

Ответы [ 6 ]

7 голосов
/ 19 декабря 2009

Маршаллер P / Invoke предположит, что память для возвращаемого типа была выделена с помощью CoTaskMemAlloc (), и вызовет CoTaskMemFree () для ее освобождения. Если это не было сделано, программа завершится с ошибкой, за исключением Vista и Win7, но тихо утечка памяти в XP. Использование SysAllocString () можно заставить работать, но вы должны аннотировать возвращаемый тип в атрибуте [DllImport]. Несоблюдение этого требования приведет к утечке без диагностики на Win7. BSTR является , а не указателем на блок памяти, выделенный CoTaskMemAlloc, перед указанным адресом имеется 4 байта, в которых хранится размер строки.

Любая из следующих комбинаций будет работать правильно:

extern "C" __declspec(dllexport)
BSTR __stdcall ReturnsAString() {
  return SysAllocString(L"Hello world");
}

[DllImport(@"c:\projects\cpptemp1\debug\cpptemp1.dll")]
[return: MarshalAs(UnmanagedType.BStr)]   // NOTE: required!
private static extern string ReturnsAString();

Или:

extern "C" __declspec(dllexport)
const wchar_t* __stdcall ReturnsAString() {
  const wchar_t* str = L"Hello world";
  wchar_t* retval = (wchar_t*)CoTaskMemAlloc((wcslen(str)+1) * sizeof(wchar_t));
  wcscpy(retval, str);
  return retval;
}

[DllImport(@"c:\projects\cpptemp1\debug\cpptemp1.dll", CharSet=CharSet.Auto)]
private static extern string ReturnsAString();

Вы должны разрешить клиентскому коду передавать буфер, чтобы не было проблем с управлением памятью. Это должно выглядеть примерно так:

extern "C" __declspec(dllexport)
void __stdcall ReturnsAString(wchar_t* buffer, size_t buflen) {
  wcscpy_s(buffer, buflen, L"Hello world");
}

[DllImport(@"c:\projects\cpptemp1\debug\cpptemp1.dll", CharSet=CharSet.Auto)]
private static extern void ReturnsAString(StringBuilder buffer, int buflen);
...
    StringBuilder sb = new StringBuilder(256);
    ReturnsAString(sb, sb.Capacity);
    string s = sb.ToString();
7 голосов
/ 19 декабря 2009

Управляемая строка не совпадает с char *. Под прикрытием происходит то, что код маршалинга на уровне взаимодействия создает копию неуправляемой строки, чтобы преобразовать ее в управляемую строку, но он не может освободить эту память, поскольку не знает, как она была выделена.

Однако вы можете попробовать выделить и вернуть BSTR вместо символа *. Уровень взаимодействия лучше работает с типами данных автоматизации, чем с классическими неуправляемыми типами данных.

Причина, по которой это имеет значение, заключается в том, как char * и BSTR выделяются в памяти.

Буферы char * выделяются в куче среды выполнения C ++ с использованием частных процедур выделения / освобождения, о которых CLR ничего не знает, поэтому нет способа удалить эту память. И что еще хуже, буфер, на который указывает char *, может быть выделен внутренней реализацией кучи кода dll или даже может указывать на переменную-член в закрытом классе.

С другой стороны, BSTR выделяются с помощью API-интерфейса WIndows SysAllocString и освобождаются SyFreeStirng, а поскольку уровень взаимодействия CLR знает об этих API-интерфейсах Windows, он знает, как освободить BSTR, полученный из неуправляемого кода.

6 голосов
/ 19 декабря 2009

Вы не можете освободить неуправляемую память из управляемого кода. Вам нужно написать подпрограмму в C, которая вызывает free для указателя, возвращенного функцией Run, и выполняет P / Invoke из .NET.

Другой вариант - выделить неуправляемую память в .NET, передать указатель на функцию C, которая заполнит ее данными и, наконец, освободит этот указатель:

IntPtr ptr = Marshal.AllocHGlobal(100 * sizeof(char));
SomeUnmanagedFunc(ptr);
Marshal.FreeHGlobal(ptr);
3 голосов
/ 19 декабря 2009

Другой способ сделать это - передать управляемую строку (экземпляр StringBuilder) через P / Invoke (в качестве параметра функции Run).

Таким образом, на неуправляемой стороне не производится распределение.

Другими словами, у вас будет что-то вроде:

extern "C" __declspec(dllexport) void __cdecl Run(char* data)
{
    // fill the array with data
    // no return value (void)
}

и назовите это так:

[DllImport("Unmanaged.dll", CharSet = CharSet.Ansi)]
static extern void Run(StringBuilder result);

StringBuilder result = new StringBuilder(100);
Run(result);
2 голосов
/ 26 февраля 2010

Я читал несколько вопросов о PInvoke и остановился здесь. Я не знаю, относится ли проблема к вам, но я решил опубликовать свой ответ для будущих читателей.

Это ваш последний комментарий к ответу Дарина Димитрова. Когда размер выделенной памяти неизвестен, типичным решением является вызов неуправляемой функции с нулевым указателем и получение размера в параметре out. Затем мы выделяем необходимое пространство и снова вызываем неуправляемую функцию.

Пример ниже:

//MANAGED SIDE  
IntPtr ptr = IntPtr.Zero;  
int size = 0;  
myFunc(ptr, out size);  
ptr = Marshal.AllocHGlobal(size);  
myFunc(ptr, out size);  
//Do your work..  
Marshal.FreeHGlobal(ptr);  



//UNMANEGED SIDE  
int myFunc(void* dest, size_t& size){  
   if(dest==NULL)  
       //calculate de size..  
       size = 'resul'  
       return 0;  
    }  
    // create the array and copy all elements   
    memcopy(dest, ... , size);  
    //free all allocated space in unmanaged and return success  
    return 0;  
}  
0 голосов
/ 19 декабря 2009

.NET память ДОЛЖНА быть выделена в CLR для очистки GC. Вам нужно добавить функцию для освобождения блока в C DLL.

Не забудьте освободить память в том же экземпляре C DLL, который создал память. Вы не можете смешивать и сочетать.

...