Освободить неуправляемую память из управляемого C # с указателем на нее

Вопрос вкратце:
Как освободить память, возвращаемую из Native DLL как ItrPtr в управляемом коде?

Подробности :
Предположим, что у нас есть простая функция, которая принимает два параметра как OUTPUT. Первый — это Reference Pointer to byte array, а второй — Reference Int.
Функция будет распределять количество байтов на основе некоторых правил и возвращать указатель памяти, а также размер байтов и возвращаемое значение (1 для успеха и 0 для сбоя).

Приведенный ниже код работает нормально, и я могу правильно получить массив байтов, а также количество байтов и возвращаемое значение, но когда я пытаюсь освободить память с помощью указателя (IntPtr), я получаю исключение:

Windows запустила точку останова в TestCppDllCall.exe.

Это может быть связано с повреждением кучи, что указывает на ошибку в TestCppDllCall.exe или любой из загруженных им библиотек DLL.

Это также может быть связано с тем, что пользователь нажимает клавишу F12, в то время как TestCppDllCall.exe имеет фокус.

Окно вывода может иметь больше диагностической информации.

Чтобы прояснить ситуацию:

  1. Следующий код C # работает корректно с другими функциями DLL, имеет ту же сигнатуру и освобождение памяти работает без проблем.

  2. Любые изменения в (C) коде принимаются, если вам нужно изменить метод выделения памяти или добавить любой другой код.

  3. Все, что мне нужно, это собственная функция DLL, принимающая два параметра по ссылке (байтовый массив и int, в c # [IntPtr байтового массива и int]), заполняют их некоторыми значениями на основе некоторых правил и возвращают результат функции (Success или Fail) ,


CppDll.h

#ifdef CPPDLL_EXPORTS
#define CPPDLL_API __declspec(dllexport)
#else
#define CPPDLL_API __declspec(dllimport)
#endif

extern "C" CPPDLL_API int writeToBuffer(unsigned char *&myBuffer, int& mySize);

CppDll.cpp

#include "stdafx.h"#include "CppDll.h"
extern "C" CPPDLL_API int writeToBuffer(unsigned char*& myBuffer, int& mySize)
{
mySize = 26;

unsigned char* pTemp = new unsigned char[26];
for(int i = 0; i < 26; i++)
{
pTemp[i] = 65 + i;
}
myBuffer = pTemp;
return 1;
}

Код C #:

using System;
using System.Text;
using System.Runtime.InteropServices;

namespace TestCppDllCall
{
class Program
{
const string KERNEL32 = @"kernel32.dll";
const string _dllLocation = @"D:\CppDll\Bin\CppDll.dll";
const string funEntryPoint = @"writeToBuffer";

[DllImport(KERNEL32, SetLastError = true)]
public static extern IntPtr GetProcessHeap();
[DllImport(KERNEL32, SetLastError = true)]
public static extern bool HeapFree(IntPtr hHeap, uint dwFlags, IntPtr lpMem);
[DllImport(_dllLocation, EntryPoint = funEntryPoint, CallingConvention = CallingConvention.Cdecl)]
public static extern int writeToBuffer(out IntPtr myBuffer, out int mySize);

static void Main(string[] args)
{
IntPtr byteArrayPointer = IntPtr.Zero;
int arraySize;
try
{
int retValue = writeToBuffer(out byteArrayPointer, out arraySize);
if (retValue == 1 && byteArrayPointer != IntPtr.Zero)
{
byte[] byteArrayBuffer = new byte[arraySize];
Marshal.Copy(byteArrayPointer, byteArrayBuffer, 0, byteArrayBuffer.Length);
string strMyBuffer = Encoding.Default.GetString(byteArrayBuffer);
Console.WriteLine("Return Value : {0}\r\nArray Size : {1}\r\nReturn String : {2}",
retValue, arraySize, strMyBuffer);
}
}
catch (Exception ex)
{
Console.WriteLine("Error calling DLL \r\n {0}", ex.Message);
}
finally
{
if (byteArrayPointer != IntPtr.Zero)
HeapFree(GetProcessHeap(), 0, byteArrayPointer);
}
Console.ReadKey();
}
}
}

Когда я отлаживаю этот код, я устанавливаю точку останова в строке (return 1), и значение буфера было:

myBuffer = 0x031b4fc0 "ABCDEFGHIJKLMNOPQRSTUVWXYZ‎‎‎‎««««««««î‏"

И я получил то же значение в коде C #, когда вызов функции return и значение было:

52121536

В результате я получил правильный указатель памяти и я могу получить значение байтового массива, как освободить эти блоки памяти с этим указателем в C #?

Пожалуйста, дайте мне знать, если что-то не ясно или если есть опечатка, я не являюсь носителем английского языка.

10

Решение

Краткий ответ: вы должны добавить в DLL отдельный метод, который освободит память для вас.

Длинный ответ: существуют разные способы распределения памяти внутри вашей реализации DLL. Способ освобождения памяти должен соответствовать способу выделения памяти. Например, память, выделенная с new[] (в квадратных скобках) должен быть освобожден с delete[] (в отличие от delete или же free). C # не предоставляет вам механизма для этого; вам нужно отправить указатель обратно в C ++.

extern "C" CPPDLL_API void freeBuffer(unsigned char* myBuffer) {
delete[] myBuffer;
}
13

Другие решения

Если вы выделяете собственную память в нативном коде, используйте CoTaskMemAlloc и вы можете освободить указатель в управляемом коде с помощью Marshal.FreeCoTaskMem. CoTaskMemAlloc описывается как «единственный способ разделить память в приложении на основе COM» (см. http://msdn.microsoft.com/en-us/library/windows/desktop/aa366533(v=vs.85).aspx )

если вам нужно использовать память, выделенную с CoTaskMemAlloc с родным объектом C ++, вы можете использовать размещение нового инициализировать память, как будто new оператор был использован. Например:

void * p = CoTaskMemAlloc(sizeof(MyType));
MyType * pMyType = new (p) MyType;

Это не выделяет память new просто вызывает конструктор в заранее выделенной памяти.

призвание Marshal.FreeCoTaskMem не вызывает деструктор типа (который не нужен, если вам просто нужно освободить память); если вам нужно сделать больше, чем просто освободить память, вызвав деструктор, вам придется предоставить собственный метод, который это делает, и P / Invoke. Передача экземпляров собственного класса в управляемый код в любом случае не поддерживается.

Если вам нужно выделить память с помощью какого-либо другого API, вам нужно будет представить ее в управляемом коде через P / Invoke, чтобы освободить ее в управляемом коде.

7

  HeapFree(GetProcessHeap(), 0, byteArrayPointer);

Нет, это не может работать. Дескриптор кучи неправильный, CRT создает свою собственную кучу с помощью HeapCreate (). Он похоронен в данных ЭЛТ, вы не можете добраться до него. Технически вы можете найти дескриптор в GetProcessHeaps (), за исключением того, что вы не знаете, какой это.

Указатель также может быть неправильным, CRT, возможно, добавил некоторую дополнительную информацию из указателя, возвращенного HeapAlloc () для хранения данных отладки.

Вам нужно будет экспортировать функцию, которая вызывает delete [], чтобы освободить буфер. Или напишите оболочку C ++ / CLI, чтобы вы могли использовать delete [] в оболочке. С дополнительным требованием, чтобы код C ++ и оболочка использовали точный та же версия библиотеки CRT (требуется / MD). Это почти всегда требует, чтобы вы могли перекомпилировать код C ++.

3
По вопросам рекламы [email protected]