在没有 Marshal.Copy 或不安全的情况下更新 C++ 中的 C# 数组

Updating a C# array inside C++ without Marshal.Copy or Unsafe

我想更新在 C# 中创建的数组,然后将指向该数组的指针传递给 C++,让 C++ 填充索引,以便在 C# 中使用。现在我正在使用 Marshal.Copy() 来完成这个任务,但我想避免可能不必要的复制,并回调到 c++ 以释放数组。这甚至可能吗?

这些数组是浮点数和整数,用于几何网格数据。

我目前的使用情况(工作而不是我想使用的) C#

    IntPtr intptr=new IntPtr();
    int count = 0;
    PopulateArray(ref intptr, ref count);
    float[] resultVertices = new float[count];
    Marshal.Copy(intptr, resultVertices, 0, count);

C++

extern "C" __declspec(dllexport) bool PopulateArray(float** resultVerts, int* resultVertLength){

    *resultVerts = new float[5]{0.123f, 3.141529f, 127.001f, 42.42f, 0};
    int myX = 5;
    *resultVertLength = myX;
    return true;
}

让 C++ 代码更新托管 C# 数组的唯一安全方法是固定数组。否则,垃圾收集器可能会在本机代码为 运行 时尝试移动数组。您可以使用 GCHandle 对象执行此操作。

int count = 5; 
float[] resultVertices = new float[count];

GCHandle handle = GCHandle.Alloc(resultVertices, GCHandleType.Pinned);
IntPtr address = handle.AddrOfPinnedObject();

PopulateArray(address, count);

handle.Free();

也可以用不安全的代码来做,读起来和记忆起来更直观一些:

int count = 5; 
float[] resultVertices = new float[count];
unsafe 
{
    fixed(float* ptr = resultVertices)
    {
        PopulateArray(ptr, count);
    }
}

另一种方法是让 C# 分配一个非托管内存块并将其传递给 C++ 方法。这比您正在做的要好,因为您没有将 allocation/deallocation 的责任放在 C++ 库代码中,而是将其全部保留在您的 C# 中。我知道您想避免害羞,但有时复制比固定对象更高效,但这取决于它们有多大。我建议您进行性能测试以确定最适合您的情况。

int count = 5; 
float[] resultVertices = new float[count];
IntPtr unmanagedMemory = Marshal.AllocHGlobal(count * Marshal.SizeOf(typeof(float)));
PopulateArray(unmanagedMemory, count);
Marshal.Copy(unmanagedMemory, resultVertices, 0, count);

在所有这些情况下,您应该将 C++ 代码设置为如下操作:

extern "C" __declspec(dllexport) bool PopulateArray(float* resultVerts, int vertLength)
{
    resultVerts[0] = 0.123f;
    // fill out the rest of them any way you like.
    return true;
}

如果数组大小是可变的,那么我建议使用单独的 C++ 方法来计算大小并 returns 它而不是让 C++ 方法分配内存。

如果您愿意允许 C# 分配数组(可能是更安全的替代方法),那么您可以使用标准 PInvoke 属性执行此操作。

将您的 C++ 声明更改为:

extern "C" __declspec(dllexport) bool PopulateArray(float resultVerts[], int resultVertLength)

以及您的 C# 声明:

[DllImport("Win32Library.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern bool PopulateArray([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] float[] resultVerts, int resultVertLength);

您在 C# 端的用法将更改为:

var resultVertices = new float[5];
PopulateArray(resultVertices, resultVertices.Length);