Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Release unmanaged memory from managed C# with pointer of it

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 .

like image 692
khaled Avatar asked Sep 05 '12 03:09

khaled


People also ask

How do you free unmanaged resources?

Dispose implementation directly to free memory used by unmanaged resources. When you properly implement a Dispose method, either your safe handle's Finalize method or your own override of the Object. Finalize method becomes a safeguard to clean up resources in the event that the Dispose method is not called.

How do you clear unmanaged objects in C#?

To clear all the unmanaged resources held by a class, we need to inherit that class from the IDisposable interface and implement the Dispose method. We have to write all the cleanup code in DisposeMethod. Whenever we want to free the resources held by the object, we can call the Dispose method.

Can garbage collector clean unmanaged objects?

Now, it is important to note that the garbage collector cleans and reclaims unused managed objects only. It does not clean unmanaged objects.

Which interface is used to clean up unmanaged resources?

There are different ways to cleanup unmanaged resources: Implement IDisposable interface and Dispose method. 'using' block is also used to clean unmanaged resources.


3 Answers

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;
}
like image 74
Sergey Kalinichenko Avatar answered Nov 09 '22 01:11

Sergey Kalinichenko


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.

like image 43
Peter Ritchie Avatar answered Nov 09 '22 00:11

Peter Ritchie


  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.

like image 45
Hans Passant Avatar answered Nov 08 '22 23:11

Hans Passant