如何将托管结构放入 C++/本机

How to get a managed struct into C++/native

我正在尝试理解这种 "IT JUST WORKS" C#/C++ 互操作的魔力,但目前它只是一场噩梦。

我正在玩 Mandelbrot 计算,想将计算核心卸载到本机 C++ 和 SSE2。这是有效的,P/Invoke。现在我想改用 IJW,以提高类型安全性,因为我想了解它。但那是几十年前我触及 C++ 表面的时候了。

我有一个 struct Complex { double real; double imag; } 来保存 Mandelbrot 循环的起始值,我想调用这样的函数:

Compute(int vectorSize, Complex[] points, double maxValue, int maxLoops, int[] result)

现在我使用 VS Express 2013 创建了一个 CLR Class 库并将其放入头文件中:

public value struct Complex
{
    double real;
    double imag;
};

public ref class Computations
{
public:
    static void Basic(int vectorSize, array<Complex,1>^ points, double maxRadius, int maxLoops, array<int,1>^ result);
};

class NativeComputations
{
public:
    static void Basic(int vectorSize, Complex* points, double maxRadius, int maxLoops, int* result);
};

在 CPP 文件中:

#pragma managed
void Mandelbrot::Computations::Basic(int vectorSize, array<Complex,1>^ points, double maxRadius, int maxLoops, array<int,1>^ result)
{
    pin_ptr<Complex> pPoints = &points[0];
    pin_ptr<int> pResult = &result[0];
    NativeComputations::Basic(vectorSize, pPoints, maxRadius, maxLoops, pResult);
}

#pragma unmanaged
void Mandelbrot::NativeComputations::Basic(int vectorSize, Complex* points, double maxRadius, int maxLoops, int* result)
{
    double foo = points[0].real;
}

此时我卡住了 - 错误 C3821:'points':托管类型或函数不能用于非托管函数

所以我需要使用非托管的东西。我可以重复我的代码并声明一个 ComplexNative 结构(通过省略 "value" 关键字)。这是可行的,但重复代码?即使是这样,将 Complex[] 转换为固定的 ComplexNative* 有什么必要?

拜托,我不想将结构拆分为 double[] real,double[] imag。这可能会导致更简单的解决方法,但我想知道如何正确地做到这一点。

这是托管代码的基石,禁止托管编译器对类型布局做出任何假设。只有这样,代码才能在不同的体系结构中实现可验证和类型安全。事实上,CLR 会在其中耍花招,故意对类型的成员重新排序,如果这样可以产生更好的布局。

因此托管 Complex 结构无法转换为可比较的 NativeComplex,编译器根本无法假定这些类型在任何方面都是相同的。这迫使您 复制 数组,从 array<Complex>NativeComplex[],一次一个元素和一个成员。

嗯,这很不愉快。但是你可以作弊。这样做并非完全不合理,无论如何本机代码都不可验证。而你的结构声明有一个特殊的属性,它是一个blittable类型。这是一个昂贵的词,意味着 CLR 没有充分的理由实际选择不同的布局。一个结构是否实际上是 blittable 也是在运行时确定的,pinvoke 编组器需要知道。谁的主要工作是做你想做的事,从托管程序调用本机代码并在必要时转换函数参数。

但是您没有使用 pinvoke 编组器,像这样的复杂类型编组不是 C++ Interop(又名 IJW)的内置功能。您必须自己调用它:

void Mandelbrot::Computations::Basic(int vectorSize, array<Complex,1>^ points, double maxRadius, int maxLoops, array<int,1>^ result)
{
    pin_ptr<Complex> pPoints = &points[0];
    NativeComplex* pNative = (NativeComplex*)pPoints;    // cheat
    pin_ptr<int> pResult = &result[0];
    NativeComputations::Basic(vectorSize, pNative, maxRadius, maxLoops, pResult);
}

这不是很漂亮,但你会摆脱它,如果你想要快速的代码,那么你必须这样做。请记住,这绝不是对在所有情况下盲目投射指针的认可。惊喜确实存在,一个很好的例子是 this question.