简体   繁体   中英

Memory leak in unmanaged code?

I have traced a leak pretty far but I can't seem to understand (fix) it by myself. I used ANTS memory profiler first to make sure my code actually is stacking memory. It starts from using 25 MB but within an hour or so it is using over 100 MB. A friend of mine for whom I'm coding this for has actually been using this faulty program and he got it to spend his whole 18 GB of ram and got a out of memory exception.

The leaking part is not vital for the program, but it just is pretty much useless without the RefreshSessions() method.

I have been extending the project Vista Core Audio API Master Volume Control from Code Project.

This is the part which seems to leak. Tested by not using it and then it doesn't leak.

Updated:

public void RefreshSessions()
    {
        Marshal.ThrowExceptionForHR(_AudioSessionManager.GetSessionEnumerator(out _SessionEnum));
        _Sessions.Refresh(_SessionEnum);
    }

(Removed the class code from here)

I have not been coding too much so I may have missed something, but if more details are needed you can actually download the source or I can just answer to my best ability.

(Removed unnecessary code here)

The leak was tested with this simple console app:

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            MMDeviceEnumerator DevEnum = new MMDeviceEnumerator();
            MMDevice device = DevEnum.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia);

            Console.ReadKey();
            int i = 0;
            while (i < 10000)
            {
                device.AudioSessionManager.RefreshSessions();
                i++;
            }
            Console.ReadKey();
        }
   }
}

Update 2

I think I got it fixed. Have to run some longer tests, but at least it seems like the memory usage has stabilized. The idea came from dialer who found a fix for the leak in c++.

public void RefreshSessions()
        {
            _Sessions.Release(); //added this
            IAudioSessionEnumerator _SessionEnum;
            Marshal.ThrowExceptionForHR(_AudioSessionManager.GetSessionEnumerator(out _SessionEnum));
            _Sessions.Refresh(_SessionEnum);
        }

This is the part in SessionCollection :

public void Release()
        {
            Marshal.ReleaseComObject(_AudioSessionEnumerator);
        }

This is not exactly the code dialer suggested (which I ended up using anyways), but still. And as he said as well this might not be the best way to achieve this but I will go with it since it does not seem to have any adverse effects on my app.

ANOTHER EDIT

public void RefreshSessions()
{
    if (_SessionEnum != null)
    {
        Marshal.ReleaseComObject(_SessionEnum);
    }
    Marshal.ThrowExceptionForHR(_AudioSessionManager.GetSessionEnumerator(out _SessionEnum));
}

Above code releases the SessionEnum explicitly and also fixed the leak in C#. This should probably be taken care of in a better way though.

EDIT:

The following C++ program is equivalent to what you did in the loop test program. The Release call at the end of the for loop fixes the leak. I need to go for today, maybe you can play around a bit and try to fix it yourself. Or maybe someone else can find out and explain why the CLR garbage collector does not call the Release automatically at some point in the C# program above.

#include <stdio.h>
#include <tchar.h>
#include <audiopolicy.h>
#include <mmdeviceapi.h>

#define SAFE_RELEASE(p) { if ( (p) ) { (p)->Release(); (p) = 0; } }
#define CHECK_HR(hr) if (FAILED(hr)) { goto done; }

const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
const IID IID_IAudioSessionManager2 = __uuidof(IAudioSessionManager2);

int _tmain(int argc, _TCHAR* argv[])
{
    HRESULT hr;

    CoInitialize(0);

    IMMDeviceEnumerator *deviceEnum = 0;
    CHECK_HR(hr = CoCreateInstance(
        CLSID_MMDeviceEnumerator, NULL,
        CLSCTX_ALL, IID_IMMDeviceEnumerator,
        (void**)&deviceEnum));;

    IMMDevice *endpoint = 0;
    CHECK_HR(deviceEnum->GetDefaultAudioEndpoint(eRender, eMultimedia, &endpoint));

    getchar();

    // lazy initialization as found in MMDevice.AudioSessionManager..get
    IAudioSessionManager2 *m = 0;
    CHECK_HR(endpoint->Activate(IID_IAudioSessionManager2, CLSCTX_ALL, 0, (void **)&m));

    for (int i = 0; i < 100000; i++)
    {
        IAudioSessionEnumerator *sessEnum = 0;
        m->GetSessionEnumerator(&sessEnum);
        sessEnum->Release(); // leak
    }

    getchar();

    printf("success");
    return 0;

done:
    printf("failure");
    return 1;
}

OLD

My guess :

_AudioSessionManager.GetSessionEnumerator(out _SessionEnum) yields an enumerator. When you call the constructor SessionCollection(_SessionEnum) , then _SessionEnum is being enumerated over. Each enumeration step retrieves an actual unmanaged object.

If it's a value type, then it would actually be copied into session collection (remember that the List(IEnumerable e) constructor copies each element). The copy would then be garbage collected, but the original object was allocated from unmanaged code and procudes a leak. If this is the case, you should free the memory immediately after calling the Collection constructor using some unmanage memory free function.

If it's a reference type, it wouldn't be freed either because the actual object in the memory isn't garbage collected, since it was allocated from within unmanaged code. If this is the case, you need to free the memory of the objects with unmanaged library functions when you no longer need them.

If you have unmanaged code, when is the _Sessions memory released? If you simply reassign the private field, then the memory is never released.

Here's an example: http://social.msdn.microsoft.com/forums/en-US/clr/thread/b2162d42-0d7a-4513-b02c-afd6cdd854bd

You need to use the dll's method for freeing up the memory (delete[] in C++)

.NET has always had the ability to easily leak memory - or rather, its doen to un-collected garbage that never gets cleaned up as the GC thinks they're in use. The most famous incident was the DARPA challenge team who believed the hype and thought the leak bug was in their C driver code, poor people.

Since those days, there have been quite a few memory leak profilers appear. I think the most famous one in Redgate's ANTS , but there are loads of others. run your app, see which objects out live their welcome, see which objects have a reference to them, put some code in unreferencing them at the right places (eg a few more Dispose and/or using statements).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM