locked
Memory leak using pinvoke RRS feed

  • Question

  • I have a problem with code that seems to leak memory when I use pinvoke to call into an unmanaged dll. Following is some example code written to highlight my problem.
    C#:

    using System;  
    using System.Collections.Generic;  
    using System.Linq;  
    using System.Text;  
    using System.Runtime.InteropServices;  
     
    namespace leakapp {  
     
        [StructLayout(LayoutKind.Sequential, CharSetCharSet = CharSet.Unicode)]  
        internal class DataContainer {  
            public Int32 m_arrayBindIndex = 12;  
        }  
              
        class Program {  
              
            static void Main(string[] args) {  
                Console.WriteLine("Begin...");  
                Console.ReadLine();  
                DataContainer ctx = new DataContainer();  
     
                    long count = 100000;  
                    for (long q = 0; q < 5; q++) {  
                        for (long i = 0; i < count; i++) {  
                            LeakClass(ref ctx);  
                        }  
                        Console.WriteLine("Outer: " + q.ToString());  
                        GC.Collect();  
                        GC.WaitForPendingFinalizers();  
                        GC.Collect();  
                    }              
                  
                Console.WriteLine("Done");  
                Console.ReadLine();  
            }  
     
     
            /// <summary> 
            /// Call the dumy dll with a reference to the DataContainer.  
            /// </summary> 
            [DllImport("foo.dll", EntryPoint = "LeakClass"CallingConventionCallingConvention = CallingConvention.Cdecl)]  
            public static extern int LeakClass(ref DataContainer cl);  
        
        }  
    }  
     


    On the unmanaged side:
    foo.h:

    // foo.h  
     
    typedef struct _DataContainer {  
            int m_arrayBindIndex;      
    } DataContainer;  
     
    extern "C" {  
        // Declaration of the compression method  
        __declspec(dllexport) void LeakClass(DataContainer **flum);  
    };  
     

    foo.cpp:
    // This is the main DLL file.  
     
     
    #include <objbase.h> 
    #include "foo.h"  
     
    void LeakClass(DataContainer ** flum) {  



    Running the above code will leak 4 bytes on the default process heap for each all to LeakClass (the size of the leak is related to the size of the class that is passed as an argument, if I use a class with no fields the leak will be 1 byte per call). The leak is identified using DebugDiag. DebugDiag reports that allocating function is CoTaskMemAlloc which is called from
    mscorwks!BlittablePtrMarshalerBase::ConvertSpaceCLRToNative +23.
    This seems to be the interop marshaler. The program above is of course just constructed to highlight my point, but the exact same "leak-signature" is reported from our production code. This leak causes the default process heap to be fragmented and eventually the app will stop. I have tried a several different approaches to identify why the code above leaks, but I can't figure it out. Changing the DataContainer from a class to a struct on the managed side removes the
    leak, but that is not really the point. I would like to know what why the code above leaks (I know the leak is not managed but I included the GC.Collect()-sequence to stop all replys starting with "The GC
    won't have time run, of course it seems like a leak..." etc.).
    I hope I'm very stupid and have forgotten something obvoius, please enlighten me.
    /Erik



     

    Wednesday, July 2, 2008 9:11 AM

Answers

  • Well, makes sense but doesn't explain the leak.  Use the Connect web site to record the bug.
    Hans Passant.
    • Marked as answer by Zhi-Xin Ye Monday, July 14, 2008 6:20 AM
    Wednesday, July 2, 2008 1:42 PM

All replies

  • I can't really phantom what the P/Invoke marshaler would do with a reference type that is passed by reference and also observe the layout requirements demanded by [StructLayout].  It making a copy with CoTaskMemAlloc() is the logical assumption.  It also has to deal with the possibility that the unmanaged code changes the reference.  Somebody is going to have to call CoTaskMemFree() to release the copy.  Are you?  
    Hans Passant.
    Wednesday, July 2, 2008 12:05 PM
  • If I were to change the reference I can see that I should first call CoTaskMemFree() and then set the pointer to a newly allocated buffer. Then I would have to rely on the CLR to copy the newly created structure to the managed object and then freeing the unmanaged structure, same as with any non-blittable type. This is supported by what is stated in http://msdn.microsoft.com/en-us/magazine/cc164193.aspx (under Memory Mangement). My image of what ought to happend is.
    1) The CLR recognizes that a call is made with a non-blittable type. The marshaller allocates memory (CoTaskMemAlloc()) and copies the contents of the object from the managed representation to the new buffer.
    2)
     a) I want to changed the reference: I call CoTaskMemFree(), allocate a new object with CoTaskMemAlloc() and overwrite the pointer passed to me.
     b) I do nothing: The pointer still points to the memory allocated by the CLR.
    3) The unmanaged call returns control to the caller (CLR).
     a, b) The marshaler copies the contents of the buffer it points to (the new one in a) and the old one in b)) to the managed object and then frees the buffer it points to (either the one I created in a) or the one created by the marshaler in b)).

    My example is not far from the example given in http://crawlmsdn.microsoft.com/en-us/magazine/cc163910.aspx (under Non-Blittable Marshaling).

    Wednesday, July 2, 2008 1:18 PM
  • Well, makes sense but doesn't explain the leak.  Use the Connect web site to record the bug.
    Hans Passant.
    • Marked as answer by Zhi-Xin Ye Monday, July 14, 2008 6:20 AM
    Wednesday, July 2, 2008 1:42 PM