【发布时间】:2011-08-05 10:47:10
【问题描述】:
我最近发现了一个非常奇怪的(对我来说)内存泄漏,用于 C# 中使用的 IEnumString COM 对象。具体来说,使用一个字符串数组调用 IEnumString.Next 方法,该字符串数组已经包含先前调用产生的值会导致内存泄漏。
IEnumString 在 C# 端看起来像这样:
[InterfaceType(1)]
[Guid("00000101-0000-0000-C000-000000000046")]
public interface IEnumString
{
void Clone(out IEnumString ppenum);
void RemoteNext(int celt, string[] rgelt, out int pceltFetched);
void Reset();
void Skip(int celt);
}
像这样调用RemoteNext(Next)方法会导致泄漏,通过长时间重复运行并看到“Private Bytes”计数器不断上升来验证。
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;
}
}
但是为每个调用创建一个新的字符串 item[] 数组以某种方式使泄漏消失了。
for (; ; )
{
int fetched;
string[] item = new string[100]; // Create a new instance for each call.
enumString.RemoteNext(item.Length, item, out fetched);
if (fetched > 0)
{
for (int i = 0; i < fetched; ++i)
{
// do something with item[i]
}
}
else
{
break;
}
}
在第一种情况下出了什么问题?我猜发生的情况是为 IEnumString.Next 的 rgelt 参数分配的 COM 内存,应该由调用者释放,不知何故不是。
但第二种情况却很奇怪。
编辑:有关一些附加信息,这是 RemoteNext 方法在 ILDASM 和 .NET Reflector 中的“实现”。
.method public hidebysig newslot abstract virtual
instance void RemoteNext([in] int32 celt,
[in][out] string[] marshal( lpwstr[ + 0]) rgelt,
[out] int32& pceltFetched) runtime managed internalcall
{
} // end of method IEnumString::RemoteNext
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType=MethodCodeType.Runtime)]
void RemoteNext(
[In] int celt,
[In, Out, MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPWStr, SizeParamIndex=0)] string[] rgelt,
out int pceltFetched);
编辑 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);
所以看起来项目数组必须为空,编组层才能正确释放复制到它的非托管字符串。即便如此,数组将返回相同的值,即具有非空数组不会导致返回错误的字符串值,它只是泄漏一些。
【问题讨论】:
-
注意综合测试,这种测试会运行相同的代码 sn-p 数百万次。使用 Perfmon.exe 并观察测试进程的垃圾回收次数。 COM 使用非托管内存,不会对 GC 造成太大压力。
-
这不是综合测试,我不仅运行了显示的代码 sn-p,而是运行了它所使用的整个程序段。(这是一个检索命名空间(分支和叶) 并简单地将它们打印出来。这是一遍又一遍地完成。如果使用第一个代码 sn-p 运行该进程将很快消耗数百 MB 内存,而第二个它将稳定在大约20MB)。
-
我还用 Process Explorer 检查了 GC 堆内存的使用情况,在这两种情况下它都没有消耗太多内存,所以我很确定泄漏的是非托管 COM 内存。跨度>
标签: c# .net memory-leaks interop