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

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

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

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

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

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

Это также может быть вызвано нажатием кнопки F12, когда у TestCppDllCall.exe есть фокус.

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

Чтобы все было ясно:

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

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

  • Вся необходимая функциональность - это функция Native DLL, которая принимает два параметра по ссылке (байтовый массив и int, In С# [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;
}

Код С#:

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‎‎‎‎««««««««î‏"

И я получил то же значение в коде С#, когда возвращается вызов функции и значение:

52121536

Результат я Получил правильный указатель памяти, и я могу получить значение байтового массива, как освободить эти блоки памяти с помощью этого указателя на С#?

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

Ответ 1

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

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

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

Ответ 2

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

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

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

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

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

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

Ответ 3

  HeapFree(GetProcessHeap(), 0, byteArrayPointer);

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

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

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