PINVOKE 内存管理单元测试

问题描述

我有一个 C# 应用程序(.net core 3.1),它使用了一些非托管 C++ 代码。互操作是使用 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,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,因为它超出了范围这句话是否正确?这能保证总是发生吗?

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

解决方法

在将它们传递给 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,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模式下,对象可以在作用域结束时收集。