首页 > 解决方案 > PINVOKE 内存管理的单元测试

问题描述

我有一个使用一些非托管 C++ 代码的 C# 应用程序(.net core 3.1)。互操作是使用 PINVOKE 实现的。

我的代码做这样的事情(简化):

double[] managedArray = new[] { 1.0, 2.0, 3.0 };

// This is the first PINVOKE call
// The unmanaged object is expected to copy the array internally.
UnmanagedObject unmanaged = CreateUnmanagedObject(managedArray);

[...]

// This is another PINVOKE call which uses the data passed in to the constructor.
// This call can also happen in a different method.
// At this point managedArray could already have been garbage collected.
unmanaged.DoStuff();

我遵循这种模式,即 C++ 代码应先在内部复制数组,然后再将其存储为 UnmanagedObject 状态。

现在我想为命题写一个单元测试非托管对象应该在内部复制数组。 我的尝试如下所示:

UnmanagedObject unmanaged;
{
    double[] managedArray = new[] { 1.0, 2.0, 3.0 };
    unmanaged = CreateUnmanagedObject(managedArray);
}
// With this call I'm relying on the fact, that the GC will collect
// managedArray, because it's gone out of scope.
System.GC.Collect();

// If DoStuff works properly here, it means that the C++ code
// has copied the array internally.
unmanaged.DoStuff();

GC 将收集managedArray的短语是否正确,因为它超出了范围?这保证总是发生吗?

[我通过删除一些不相关的内容来编辑问题以使其更加清晰。有些评论不再适用。]

标签: c#unit-testing.net-coregarbage-collectionpinvoke

解决方案


您不需要在将原始类型数组传递给 pinvoke 之前对其进行固定/修复。您可以直接传递它们,.NET 将在方法调用期间为您固定它们(因此当 pinvoke 返回时它们将被取消固定)

[DllImport("somedll.dll")]
private static extern UnmanagedObject CreateUnmanagedObject(double[] array, int lenArray);

如果您想检查您的数据是否真的被 C++ 内部复制,您可以:

UnmanagedObject unmanaged;
{
    double[] managedArray = new[] { 1.0, 2.0, 3.0 };

    unmanaged = CreateUnmanagedObject(managedArray);

    for (int i = 0; i < managedArray.Length; i++)
    {
        managedArray[i] = double.NaN;
    }
}

// At this point the GC should collect the managedArray, if I trigger it.
System.GC.Collect();

// If DoStuff works properly here, it means that the C++ code
// has copied the array internally
unmanaged.DoStuff();

因此,您可以将数组的值设置为其他值。

你甚至可以检查对象是否真的被收集了:

WeakReference wr;

{
    double[] managedArray = new[] { 1.0, 2.0, 3.0 };
    wr = new WeakReference(managedArray);

    for (int i = 0; i < managedArray.Length; i++)
    {
        managedArray[i] = double.NaN;
    }
}

// At this point the GC should collect the managedArray, if I trigger it.
System.GC.Collect();

if (wr.IsAlive)
{
    Console.WriteLine("Warning: the object hasn't been collected");
}
else
{
    Console.WriteLine("The object has been collected");
}

请注意,如果程序已在 Debug 模式下编译,则 GC 直到方法结束时才会收集对象,而在 Release 模式下,可以在范围结束时收集对象。


推荐阅读