我试图理解 C#/C++ 互操作的“它就是有效”的魔力,但目前这只是一场噩梦。
我正在使用 Mandelbrot 计算,并希望将计算核心卸载到 native C++ 和 SSE2。使用 P/Invoke 可以实现这一点。现在我想换成 IJW,为了类型更安全,因为我想理解它。但当我触及 C++ 的表面时,已经是几十年前的事了。
我有一个struct Complex { double real;双像; }
保存 Mandelbrot 循环的起始值,我想调用这样的函数:
计算(int vectorSize,Complex[]点,double maxValue,int maxLoops,int[]结果)
现在我使用 VS Express 2013 创建了一个 CLR 类库,并将其放入头文件中:
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[]
,一次一个元素和一个成员。
嗯,这很不愉快。但你可以作弊。这样做并不是完全不合理,无论如何 native 代码都是不可验证的。并且你的结构声明有一个特殊的属性,它是一个blittable类型。这是一个昂贵的词,意味着 CLR 没有充分的理由实际选择不同的布局。 pinvoke 编码器需要知道结构是否实际上是 blittable 也是在运行时确定的。他的主要工作是做你想做的事情,从托管程序调用 native 代码并在必要时转换函数参数。
但是您没有使用 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 .
关于c# - 如何将托管结构放入 C++/native 中,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29219839/