C# ref string[]内存泄漏与NET/COM互操作
我最近发现了一个非常奇怪的(对我来说)从C#使用的COM对象内存泄漏。具体地说,使用字符串数组调用IEnumString.Next方法,该字符串数组已经包含由上一次调用产生的值,从而导致内存泄漏 IEnumString在C#端看起来像这样: 像这样调用RemoteNext(Next)方法会导致泄漏,通过长时间重复运行并看到“Private Bytes”计数器不断上升来验证C# ref string[]内存泄漏与NET/COM互操作,c#,.net,memory-leaks,interop,C#,.net,Memory Leaks,Interop,我最近发现了一个非常奇怪的(对我来说)从C#使用的COM对象内存泄漏。具体地说,使用字符串数组调用IEnumString.Next方法,该字符串数组已经包含由上一次调用产生的值,从而导致内存泄漏 IEnumString在C#端看起来像这样: 像这样调用RemoteNext(Next)方法会导致泄漏,通过长时间重复运行并看到“Private Bytes”计数器不断上升来验证 string[] item = new string[100]; // OBS! Will be re-used for e
string[] item = new string[100]; // OBS! Will be re-used for each call!
for (; ; )
{
int fetched;
enumString.RemoteNext(item.Length, item, out fetched);
if (fetched > 0)
{
for (int i = 0; i < fetched; ++i)
{
// do something with item[i]
}
}
else
{
break;
}
}
编辑2:在调用RemoteNext之前,只需向字符串数组添加一个字符串值(仅包含空值),也可以在第二个非泄漏情况下显示泄漏
string[] item = new string[100]; // Create a new instance for each call.
item[0] = "some string value"; // THIS WILL CAUSE A LEAK
enumString.RemoteNext(item.Length, item, out fetched);
因此,似乎项数组必须为空,封送层才能正确释放复制到其中的未管理字符串。即使如此,数组仍将返回相同的值,即使用非空数组不会导致返回错误的字符串值,它只是泄漏了一些值。
IEnumString
-它只是一个接口。底层COM对象是什么?这是主要嫌疑犯
查看IEnumString
的非托管声明:
HRESULT Next(
[in] ULONG celt,
[out] LPOLESTR *rgelt,
[out] ULONG *pceltFetched
);
如您所见,第二个参数rgelt
只是一个指向字符串数组的指针-没有什么特别的,但是当您执行托管调用时
string[] item = new string[100]; // Create a new instance for each call.
item[0] = "some string value"; // THIS WILL CAUSE A LEAK
enumString.RemoteNext(item.Length, item, out fetched);
项[0]
中的字符串似乎已转换为未正确释放的LPOLESTR。因此,请尝试以下方法:
string[] item = new string[1];
for (; ; )
{
int fetched;
item[0] = null;
enumString.RemoteNext(1, item, out fetched);
if (fetched == 1)
{
// do something with item[0]
}
else
{
break;
}
}
注意合成测试,这种测试会运行相同的代码片段数百万次。使用Perfmon.exe并观察测试进程的垃圾回收次数。COM使用非托管内存,它不会给GC带来很大压力。这不是一个综合测试,我不仅运行了显示的代码片段,还运行了它所使用的整个程序段。(这是一个小型测试应用程序,检索服务器应用程序的名称空间(分支和叶)并简单地打印出来。这是一次又一次地完成的。如果让第一个代码段运行,进程很快将消耗数百MB的内存,而第二个代码段将稳定在20MB左右)。我还使用Process Explorer检查了GC堆内存的使用情况,在这两种情况下都没有消耗太多内存,因此我非常确信是未管理的COM内存泄漏。是的,你当然是对的!我没有考虑编组层将已经存在于项目(RGELT)数组中的托管字符串转换为需要释放的非托管COM内存。错误不在于IEnumString实现对象,但rgelt参数明确定义为[out]参数,因此它不必为其释放任何内存。只是这个“输出信息”在.NET端丢失了,所以当调用方出错时没有警告或错误。我想最好总是使用包装器进行COM互操作,即使它们看起来像“真正的”网络公民!
HRESULT Next(
[in] ULONG celt,
[out] LPOLESTR *rgelt,
[out] ULONG *pceltFetched
);
string[] item = new string[100]; // Create a new instance for each call.
item[0] = "some string value"; // THIS WILL CAUSE A LEAK
enumString.RemoteNext(item.Length, item, out fetched);
string[] item = new string[1];
for (; ; )
{
int fetched;
item[0] = null;
enumString.RemoteNext(1, item, out fetched);
if (fetched == 1)
{
// do something with item[0]
}
else
{
break;
}
}