Release unmanaged memory from managed C# with poin

2020-02-01 07:14发布

The question in short words is : How to free memory returned from Native DLL as ItrPtr in managed code?

Details : Assume we have simple function takes two parameters as OUTPUT, The first one is Reference Pointer to byte array and the second one is Reference Int . The function will allocate amount of bytes based on some rules and return the pointer of memory and the size of bytes and the return value (1 for success and 0 for fail) .

The code below works fine and I can get the byte array correctly and the count of bytes and the return value, but when I try to free the memory using the pointer (IntPtr) I get exception :

Windows has triggered a breakpoint in TestCppDllCall.exe.

This may be due to a corruption of the heap, which indicates a bug in TestCppDllCall.exe or any of the DLLs it has loaded.

This may also be due to the user pressing F12 while TestCppDllCall.exe has focus.

The output window may have more diagnostic information.

To make things clear :

  1. The next C# code work correctly with other DLL function have the same signature and freeing the memory works without any problem .

  2. Any modification in (C) code accepted if you need to change allocation memory method or adding any other code .

  3. All the functionality I need is Native DLL function accept Two Parameter by reference (Byte array and int , In c# [IntPtr of byte array and int]) fill them with some values based on some rules and return the function result (Success or 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# code :

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();
        }
    }
}

When I debug this code i set break point in the line (return 1) and the value of the buffer was :

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

And I got the same value in C# code when the function call return and the value was :

52121536

The result I Got the correct Memory pointer and i am able to get the byte array value , how to free these memory blocks with this pointer in C# ?

Please let me know if there anything is not clear or if there any typo, I am not native English speaker .

3条回答
倾城 Initia
2楼-- · 2020-02-01 07:34
  HeapFree(GetProcessHeap(), 0, byteArrayPointer);

No, that can't work. The heap handle is wrong, the CRT creates its own heap with HeapCreate(). It's buried in the CRT data, you can't get to it. You could technically find the handle back from GetProcessHeaps(), except you don't know which one it is.

The pointer can be wrong too, the CRT may have added some extra info from the pointer returned by HeapAlloc() to store debugging data.

You'll need to export a function that calls delete[] to release the buffer. Or write a C++/CLI wrapper so you can use delete[] in the wrapper. With the extra requirement that the C++ code and the wrapper use the exact same version of the CRT DLL (/MD required). This almost always requires that you can recompile the C++ code.

查看更多
在下西门庆
3楼-- · 2020-02-01 07:38

If you are allocating your own memory in native code, use CoTaskMemAlloc and you can free the pointer in managed code with Marshal.FreeCoTaskMem. CoTaskMemAlloc is described as "the only way to share memory in a COM-based application" (see http://msdn.microsoft.com/en-us/library/windows/desktop/aa366533(v=vs.85).aspx )

if you need to use the memory allocated with CoTaskMemAlloc with a native C++ object, you can use placement new to initialize the memory as if the new operator was used. For example:

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

This doesn't allocate memory with new just calls the constructor on the pre-allocated memory.

Calling Marshal.FreeCoTaskMem does not call the destructor of the type (which isn't needed if you just need to free memory); if you need to do more than free memory by calling the destructor you'll have to provide a native method that does that and P/Invoke it. Passing native class instances to managed code is not supported anyway.

If you need to allocate memory with some other API, you'll need to expose it in managed code through P/Invoke in order to free it in managed code.

查看更多
女痞
4楼-- · 2020-02-01 07:47

Short answer: you should add a separate method in the DLL that frees the memory for you.

Long answer: there are different ways in which the memory can be allocated inside your DLL implementation. The way you free the memory must match the way in which you have allocated the memory. For example, memory allocated with new[] (with square brackets) needs to be freed with delete[] (as opposed to delete or free). C# does not provide a mechanism for you to do it; you need to send the pointer back to C++.

extern "C" CPPDLL_API void freeBuffer(unsigned char* myBuffer) {
    delete[] myBuffer;
}
查看更多
登录 后发表回答