非托管代码中的内存泄漏?

时间:2013-03-28 21:04:32

标签: c# memory-leaks unmanaged

我已经找到了泄漏但我自己似乎无法理解(修复)它。我首先使用ANTS内存分析器来确保我的代码实际上是堆叠内存。它从使用25 MB开始,但在一个小时左右的时间内使用超过100 MB。我正在编写此代码的我的一位朋友实际上已经使用了这个错误的程序,并且他得到了花掉他整个18 GB的ram而得到了内存不足的例外。

泄漏部分对程序来说并不重要,但如果没有RefreshSessions()方法,它就没用了。

我一直在从代码项目扩展项目Vista Core Audio API Master Volume Control

这是似乎泄漏的部分。通过不使用它进行测试,然后它不会泄漏。

更新:

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

(从这里删除了类代码)

我没有编写太多代码,所以我可能错过了一些东西,但是如果需要更多细节,你可以实际下载源代码,或者我可以回答我最好的能力。

(在这里删除了不必要的代码)

使用这个简单的控制台应用程序测试了泄漏:

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

更新2

我想我已经修好了。必须运行一些更长的测试,但至少看起来内存使用已经稳定。这个想法来自拨号器,他发现了c ++漏洞的修复方法。

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

这是SessionCollection中的部分:

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

这不完全是代码拨号器建议的(我最终还是继续使用),但仍然如此。 正如他所说,这可能不是实现这一目标的最佳方法,但我会继续使用它,因为它似乎对我的应用程序没有任何不利影响。

3 个答案:

答案 0 :(得分:2)

另一个编辑

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

上面的代码明确释放了SessionEnum,并修复了C#中的泄漏。 这应该以更好的方式处理。

修改

以下C ++程序与您在循环测试程序中所做的相同。 for循环结束时的Release调用修复了泄漏。我今天需要去,也许你可以玩一下,并尝试自己解决它。或者也许其他人可以找出并解释为什么CLR垃圾收集器不会在上面的C#程序中的某个时刻自动调用Release

#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

我的猜测

_AudioSessionManager.GetSessionEnumerator(out _SessionEnum)产生一个枚举器。当您调用构造函数SessionCollection(_SessionEnum)时,正在枚举_SessionEnum。每个枚举步骤都会检索一个实际的非托管对象。

如果它是值类型,那么实际将被复制到会话集合中(请记住List(IEnumerable e)构造函数复制每个元素)。然后,副本将被垃圾收集,但原始对象是从非托管代码分配的并且会导致泄漏。如果是这种情况,你应该在使用一些unmanage memory free函数调用Collection构造函数后立即释放内存。

如果它是引用类型,它也不会被释放,因为内存中的实际对象不是垃圾回收,因为它是从非托管代码中分配的。如果是这种情况,则需要在不再需要时使用非托管库函数释放对象的内存。

答案 1 :(得分:1)

如果您有非托管代码,_Sessions内存何时发布?如果你只是重新分配私有字段,那么永远不会释放内存。

这是一个例子: http://social.msdn.microsoft.com/forums/en-US/clr/thread/b2162d42-0d7a-4513-b02c-afd6cdd854bd

你需要使用dll的方法来释放内存(在C ++中删除[])

答案 2 :(得分:0)

.NET一直有能力轻松泄露内存 - 或者更确切地说,它可以用于未收集的垃圾,因为GC认为它们正在使用中,所以它们永远不会被清除。最着名的事件是DARPA挑战团队,他们相信炒作并认为漏洞是他们的C驱动程序代码中的穷人。

从那时起,出现了相当多的内存泄漏分析器。我认为Redgate's ANTS中最着名的一个,但还有很多其他的。运行你的应用程序,看看哪些对象是他们的欢迎,看看哪些对象有引用它们,把一些代码放在正确位置的引用它们(例如一些Dispose和/或使用语句)。