考虑以下我们可以在C#中定义的最简单的COM对象示例(使用带有.NET Framework 4.0的Visual Studio 2010 SP1构建):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
namespace CcwTestLib
{
[ComVisible(true)]
[Guid("8ABD40E2-05E2-4436-9EAD-073911357155")]
public class CcwTestObject
{
}
}
我们使用regasm(或Visual Studio中的内置选项)编译此程序集并为COM interop注册它。
现在我们只需在C ++中编写一个非托管的Win32控制台应用程序,除了创建此对象的实例并释放100,000次之外什么都不做。例如,使用以下程序:
#include "stdafx.h"
// {8ABD40E2-05E2-4436-9EAD-073911357155}
static const GUID CLSID_CcwTestObject =
{ 0x8abd40e2, 0x5e2, 0x4436, { 0x9e, 0xad, 0x7, 0x39, 0x11, 0x35, 0x71, 0x55 } };
int _tmain(int argc, _TCHAR* argv[])
{
CoInitializeEx(NULL, COINIT_MULTITHREADED);
IUnknown *pTestObject = NULL;
const int iCount = 100000;
wprintf(L"Allocating COM instance %i times...\n", iCount);
for (int i = 0; i < iCount; i++)
{
HRESULT hr = CoCreateInstance(CLSID_CcwTestObject,
NULL,
CLSCTX_INPROC_SERVER,
IID_IUnknown,
(LPVOID*)&pTestObject);
if (FAILED(hr))
{
wprintf(L"Error: %i", hr);
return -1;
}
pTestObject->Release();
}
CoUninitialize();
return 0;
}
在我们的本地系统上运行此应用程序时,它在大约820毫秒内完成并消耗大约32MB内存。将iCount
增加到10,000,000会使程序花费更长的时间来完成(当然),但是考虑到内存消耗,它会增加到大约92MB,并在程序执行的剩余时间内保持不变。到目前为止没什么太奇怪的。
现在是有趣的部分,直到我的问题。让我们从.NET类中删除Guid
属性(如果启用则禁用自动COM注册,以便先前的注册在注册表中保持不变)并重建程序集。
我们再次运行测试程序,iCount
设置为100,000。这次程序以 90,000ms 完成! 比以前慢 100倍!
当我们将iCount
增加到10,000,000并启动程序时,更加有趣和麻烦。如果我们使用Process Explorer或VMMap或类似的程序监视其内存消耗,我们可以看到它慢慢增加,但它并没有像我们预期的那样停在92MB。相反,它似乎永远持续下去。据推测,当虚拟内存空间耗尽大约2GB时,应用程序将崩溃(因为它是一个x86进程),但由于它移动得如此之慢,我们不会等待在此测试中发生这种情况,而是退出大约1,200MB。
应该注意的是,使用COM对象,调用它的方法等(如果我们已经定义了任何)工作得很好,因为它应该是因为创建对象的所有必要信息都存储在注册表中。我们系统中的一部分如下所示:
[HKEY_CLASSES_ROOT\CLSID\{8ABD40E2-05E2-4436-9EAD-073911357155}\InprocServer32]
@="mscoree.dll"
"ThreadingModel"="Both"
"Class"="CcwTestLib.CcwTestObject"
"Assembly"="CcwTestLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
"RuntimeVersion"="v4.0.30319"
"CodeBase"=file:///D:/Coding/Projects/CcwTest/CcwTestLib/bin/Debug/CcwTestLib.dll
CLSID正确指向程序集及其代码库,以及程序集中的显式类型。
我们还发现,将属性中的Guid更改为注册时的Guid会产生同样的问题。
那为什么会这样呢?这是.NET中的错误吗?这个问题有解决办法吗?
我很高兴能够对这个问题有所了解,这让我们花了大约一个星期的时间从我们产品中发现的内存泄漏缩小到这个简化的场景。
答案 0 :(得分:0)
似乎内存泄漏是.NET 4.5中已修复的错误。
有关详细信息,请参阅Microsoft Connect上的问题。
引用他们的回答:
内存泄漏已经修复(.NET Framework 4.5 Developer Preview是第一个使用此修复程序的公开构建版本)。至于速度慢,Guid不匹配会有效地停用我们的内部缓存,因此我们会在每次激活时执行一系列注册表查找和按名称类型查找。由于这不是主线方案,并且应该仅限于在没有更新注册的情况下重新引导类的情况,我们暂时优先考虑这个问题。它仍然可以跟踪未来版本。