none
Release Unmanaged Memory (char array from C#) RRS feed

  • Question

  • Hello everyone,

    this is my question. I have to relese memory in a C# app, previously allocated by a Natve DLL function, as depicted below:

    /// memory allocation in Cpp Native DLL
    int TestOutputListStrings(char*** outBuffer, UINT* NumOfStrings)
    {
    
    	*outBuffer = new char * [7];
    	
    	for (int i = 0; i < 7; i++)
    	{
    		(*outBuffer)[i] = new char[11];
    		memset((*outBuffer)[i], '\0', 10);
    		strcpy((*outBuffer)[i], "my string.");
    	}
    
    	*NumOfStrings = 7;
    	return 0;
    }
    
    /// memory release in Cpp Native DLL
    int TestDeleteOutputListStrings(char** outBuffer, UINT* NumOfString)
    {
    	if ( *NumOfString > 0)
    	{
                      for(int i=0; i< *NumOfString; i++)
    	               delete(outBuffer[i]);
    
    		delete(outBuffer);
    	}
    	return 0;
    }
     

    I tested these function in C++ and no  problems/memory leaks arise.

    So, in C# iI call these methods through PInvoke, and I'm not able to properly release the memory (crash and memory leaks persist!!). Below the signature and how I use of the methods:

    [DllImport("TestPInvoke.dll", CharSet = CharSet.Ansi)]
    private static extern int TestOutputListStrings(out IntPtr buffer, out uint NumOfStrings);
    
    [DllImport("TestPInvoke.dll", CharSet = CharSet.Ansi)]
    static extern int TestDeleteOutputListStrings( ref IntPtr[] buffer, ref uint NunOfStrings);
    
    /// or also
    
    [DllImport("TestPInvoke.dll", CharSet = CharSet.Ansi)]
    static extern int TestDeleteOutputListStrings( ref IntPtr buffer, ref uint NunOfStrings);

    Using the delete mothod in the follwing way:

    uint count;
    IntPtr bufPtr;
                     
    
    TestOutputListStrings(out bufPtr, out count);
    
    IntPtr[] strPtrVec = new IntPtr[count];
    ///or
    // IntPtr strPtr =IntPtr.Zero; 
                   
    string[] values = new string[count];
    for (int n = 0; n < count; n++)
    {
            strPtrVec [n] = Marshal.ReadIntPtr(bufPtr);
            values[n] = Marshal.PtrToStringAnsi(strPtrVec [n]);
            bufPtr = (IntPtr)(((long)bufPtr) + IntPtr.Size);        
    //or
            strPtr = Marshal.ReadIntPtr(bufPtr);
            values[n] = Marshal.PtrToStringAnsi(strPtr);
            bufPtr = (IntPtr)(((long)bufPtr) + IntPtr.Size);           
    }           
    
    TestDeleteOutputListStrings(ref strPtrVec , ref count);
    
    //or
    
    TestDeleteOutputListStrings(ref strPtr, ref count);

     Note that I cannot change the memory allocator in C++ (from new/delete to CoTaskMemAlloc/CoTaskMemFree), because it is a third-party Dll.

    Any Ideas for solving this problem?

    Thank you in advance!!

    Andrea

     

    Tuesday, March 30, 2010 4:53 PM

Answers

  • Sorry, I didn't read this post carefully. Your managed code needs to look like this:

    [DllImport("TestPInvoke.dll", CharSet = CharSet.Ansi)]
    private static extern int TestOutputListStrings(out IntPtr buffer, out uint NumOfStrings);
    [DllImport("TestPInvoke.dll", CharSet = CharSet.Ansi)]
    static extern int TestDeleteOutputListStrings(IntPtr buffer, ref uint NumOfStrings);

    uint count;
    IntPtr bufPtr;

    TestOutputListStrings(out bufPtr, out count);

    IntPtr bufPtrTmp = bufPtr;
    string[] values = new string[count];
    for (int n = 0; n < count; n++)
    {
        IntPtr strPtr = Marshal.ReadIntPtr(bufPtrTmp);
        values[n] = Marshal.PtrToStringAnsi(strPtr);
        bufPtrTmp = (IntPtr)(((long)bufPtrTmp) + IntPtr.Size);
    }

    TestDeleteOutputListStrings(bufPtr, ref count);

    • Marked as answer by ItaDev75 Saturday, April 3, 2010 2:42 PM
    Saturday, April 3, 2010 1:47 PM

All replies

  • you need to change the parameters to ref type. e.g.
    public static extern  int TestOutputListStrings(ref System.IntPtr outBuffer, ref uint NumOfStrings) ;

    and

    public static extern  int TestDeleteOutputListStrings(ref System.IntPtr outBuffer, ref uint NumOfString) ;

    because you don't own the returning memory (owned by the DLL's C++ heap). For a comparison of ref and out, check http://msdn.microsoft.com/en-us/magazine/cc164193.aspx .

    IntPtr is the managed version of void*, so either you need to deference it or marshal further to get data out of the pointer.

    You also have a buffer overrun bug in TestOutputListStrings. a char in Visual C++ is 1 byte, it cannot store a pointer, which is 4 or 8 bytes..

     

     



    The following is signature, not part of post
    Please mark the post answered your question as the answer, and mark other helpful posts as helpful.
    Visual C++ MVP
    Tuesday, March 30, 2010 8:14 PM
  • Hi,

    TestOutputListStrings has parameter [out] (the callee allocates the memory), whereas TestDeleteOutputListStrings already has attribure [ref] . However I tried your solution (both method with ref keyword) without success.

    *outBuffer = new char * [7]; // is an array of 7 pointers to char*.....not char.
    
    

    Bye

    Andrea

    Tuesday, March 30, 2010 9:49 PM
  • Unless you fix the buffer overrun bug nothing isn't going to be success. You cannot put a pointer in a char like you did in (*outBuffer)[i] = new char[11];



    The following is signature, not part of post
    Please mark the post answered your question as the answer, and mark other helpful posts as helpful.
    Visual C++ MVP
    Wednesday, March 31, 2010 12:54 AM
  • It looks like you want to change the delete function signature to

    static extern int TestDeleteOutputListStrings(IntPtr buffer, ref uint NunOfStrings);


    Mattias, C# MVP
    Wednesday, March 31, 2010 8:50 AM
    Moderator
  • Hi Mattias,

    I dont understand your observation: the method already has the signature with ref keyword, but I' m not able to address correctly the momory. Morevoer I'm not able to perform deep debug in order to understand what happen when IntPtr is Marshaled by the default Marshaler. Any ideas? Could you explain more in detail your comment?

    Thank you.

    Andrea                                                                                                                                                                                                                                                                                                                                                                                                                             

    Wednesday, March 31, 2010 2:59 PM
  • Pinvoke Interop Assistant tool may help you in this case.

    http://clrinterop.codeplex.com/releases/view/14120

     

    Friday, April 2, 2010 4:02 PM
  • Thank you Shantanup,

    I have already instlalled PInvoke assistant, and the suggested signature is the same that I have used.

    I think that i will move my developments in C++ CLI which is less complicated, but if I find the solution I will chage idea! :)

    Thank you very much.

    Andrea

     

    Friday, April 2, 2010 4:48 PM
  • Curious.. This is a continuation of this thread:
    http://social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/1d2aef69-d2ea-4afb-988b-5eafa2618e26

    And the buffer overrun was pointed out and acknowledged in that thread too.

    You cannot free up this memory from the managed app. You need to do one of two things:

    1) Pass the received IntPtr back to the unmanaged DLL to have it free it.
    2) Pass a delegate to the unmanaged DLL when calling that function. This delegate can be a pointer to Marshal.AllocHGlobal. Now you can Marshal.FreeHGlobal when done with the returned pointer.

     

    Friday, April 2, 2010 4:52 PM
  • There is a 3rd option: Allocate the block of memory before calling into the DLL. This of course means you have to know how big it will be, so you have to first ask. A common pattern is something like this:

    long Foo(char * buffer, ...)
    {
        if (buffer == NULL)
        {
            // calculate size of buffer and return it
            return requiredSize;
        }
    }

     

    Friday, April 2, 2010 4:59 PM
  • Hi Tergiver,

    yes, quite similar post, but now I have to free memory :). However it seemed more appropiate this forum on CLR and so I "moved" the question to this forum.

    Regarding buffer overrun, in the example of this thread I don't think there are error and I dont append a '\0' too. (However if someone find the correct piece of code can post it....)

    Regarding your comment: 

    1) "Pass the received IntPtr back to the unmanaged DLL to have it free it."

    I use in the signature of the method:

    IntPtr bufPtr;                 
    
    TestOutputListStrings(out bufPtr, out count);
    
    

     [out]  keyword means that IntPtr came back to manage code and it is passed to method:

    TestDeleteOutputListStrings(ref strPtr, ref count);
    
    // also with [in] in keyword in this case I am able to delete string but not the *outbuffer...
    TestDeleteOutputListStrings(strPtr, ref count);

    2) "Pass a delegate to the unmanaged DLL when calling that function...."

    I cannot modify the signature of the unmanaged method because it is a third-party DLL.

    3) "Allocate the block of memory before calling into the DLL..."

    The memory will be allocated by the API and so, I dont know the size of the memory. :)

     

    Thank you in advance!

    Andrea

    Saturday, April 3, 2010 9:18 AM
  • Sorry, I didn't read this post carefully. Your managed code needs to look like this:

    [DllImport("TestPInvoke.dll", CharSet = CharSet.Ansi)]
    private static extern int TestOutputListStrings(out IntPtr buffer, out uint NumOfStrings);
    [DllImport("TestPInvoke.dll", CharSet = CharSet.Ansi)]
    static extern int TestDeleteOutputListStrings(IntPtr buffer, ref uint NumOfStrings);

    uint count;
    IntPtr bufPtr;

    TestOutputListStrings(out bufPtr, out count);

    IntPtr bufPtrTmp = bufPtr;
    string[] values = new string[count];
    for (int n = 0; n < count; n++)
    {
        IntPtr strPtr = Marshal.ReadIntPtr(bufPtrTmp);
        values[n] = Marshal.PtrToStringAnsi(strPtr);
        bufPtrTmp = (IntPtr)(((long)bufPtrTmp) + IntPtr.Size);
    }

    TestDeleteOutputListStrings(bufPtr, ref count);

    • Marked as answer by ItaDev75 Saturday, April 3, 2010 2:42 PM
    Saturday, April 3, 2010 1:47 PM
  • ok,

    perfect answer, as usual! :)

    Andrea

    Saturday, April 3, 2010 2:44 PM
  • Hi, a tip for Sheng Jiang,

    just a necessary reading for every developer:

     

    The C++ Programming Language: Special Edition - Bjarne Stroustrup (Author)

    http://www.amazon.com/C-Programming-Language-Special/dp/0201700735/ref=sr_1_1?ie=UTF8&s=books&qid=1270636605&sr=8-1

     

    I suggest you, to reserve particular attention to chapters on pointers and memory allocation.

    Bye

    Andrea 

    Wednesday, April 7, 2010 10:42 AM