Бесплатное неуправляемое выделение памяти из управляемого кода

A. Сетевое приложение называет C dll. Код C выделяет память для массива символов и возвращает этот массив как результат. Приложения.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.

Любая справка будет цениться.:)

7
задан Alex 19 December 2009 в 08:33
поделиться

6 ответов

Managed string is not the same as char*. What happens undercover is that the marshaling code in the interop layer makes a copy of the unmanaged string in order to convert it to a managed string, but it can't free that memory as it does not know how it was allocated.

However, you could try allocating and returning a BSTR instead of a char*. The interop layer deals way better with automation datatypes than classic unmanaged data types.

The reason why that matters is the way char* and BSTRs are allocated in the memory.

The char* buffers are allocated on the heap of the C++ runtime using private allocation/deallocation routines that the CLR knows nothing about, so there's no way it can delete that memory. And to make things even worse, the buffer that char* points can be allocated by an internal heap implementation of the dll code, or it might even point to a member variable in a private class.

The BSTRs on the other hand are allocated using the WIndows API SysAllocString and are freed by SyFreeStirng and since the CLR interop layer knows about these Windows APIs, it knows how to free a BSTR it got from unmanaged code.

7
ответ дан 6 December 2019 в 07:26
поделиться

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

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

IntPtr ptr = Marshal.AllocHGlobal(100 * sizeof(char));
SomeUnmanagedFunc(ptr);
Marshal.FreeHGlobal(ptr);
6
ответ дан 6 December 2019 в 07:26
поделиться

Другой способ сделать это - передать управляемую строку (экземпляр 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);
3
ответ дан 6 December 2019 в 07:26
поделиться

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

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

-1
ответ дан 6 December 2019 в 07:26
поделиться

Маршаллер 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
ответ дан 6 December 2019 в 07:26
поделиться

Я читал несколько вопросов о 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;  
}  
2
ответ дан 6 December 2019 в 07:26
поделиться
Другие вопросы по тегам:

Похожие вопросы: