我正在尝试包装一些旧的C代码,以与在.NET Core上运行的C#一起使用。我正在使用the approach given here创建一个C ++包装程序,该程序可以编译为纯MSIL。它对于简单的函数运行良好,但是我发现,如果我的代码曾经使用指针到指针或指针数组,它将因内存冲突而崩溃。通常它会使Visual Studio崩溃,而我必须重新启动一切,这很乏味。
例如,以下代码将导致崩溃:
public ref class example
{
public:
static void test() {
Console::WriteLine("\nTesting pointers.");
double a[5] = {5,6,7,8,9}; //Array.
double *b = a; //Pointer to first element in array.
Console::WriteLine("\nTesting bare pointers.");
Console::WriteLine(a[0]); //Prints 5.
Console::WriteLine(b[0]); //Prints 5.
Console::WriteLine("\nTesting pointer-to-pointer.");
double **c = &b;
Console::WriteLine(c == &b); //Prints true.
Console::WriteLine(b[0]); //Works, prints 5.
Console::WriteLine(**c); //Crashes with memory access violation.
Console::WriteLine("\nTesting array of pointers.");
double* d[1];
d[0] = b;
Console::WriteLine(d[0] == b); //Prints false???
Console::WriteLine(b[0]); //Works, prints 5.
Console::WriteLine(d[0][0]); //Crashes with memory access violation.
Console::WriteLine("\nTesting CLI array of pointers.");
cli::array<double*> ^e = gcnew cli::array<double*> (5);
e[0] = b;
Console::WriteLine(e[0] == b); //Prints false???
Console::WriteLine(b[0]); //Works, prints 5.
Console::WriteLine(e[0][0]); //Crashes with memory access violation.
}
}
请注意,仅使用指针不会引起任何问题。仅当存在额外的间接级别时。
如果我将代码放在CLR C ++控制台应用程序中,它将完全按预期运行,并且不会崩溃。仅当使用clr:pure
将代码编译为MSIL程序集并从.NET核心应用程序运行时,才会发生崩溃。
怎么回事?
更新1::这是Visual Studio文件:https://app.box.com/s/xejfm4s46r9hs0inted2kzhkh9qzmjpb是两个项目。 MSIL程序集称为library
,而CoreApp
是将调用该库的C#控制台应用程序。警告,运行Visual Studio时很可能使它崩溃。
更新2:我也注意到了这一点:
double a[5] = { 5,6,7,8,9 };
double* d[1];
d[0] = a;
Console::WriteLine(d[0] == a); //Prints true.
Console::WriteLine(IntPtr(a)); //Prints a number.
Console::WriteLine(IntPtr(d[0])); //Prints a completely different number.
答案 0 :(得分:3)
这似乎为test
方法生成了IL。在崩溃时,我们正在读取**c
,而c
是本地号码5。
IL_00a5 11 05 ldloc.s 0x5
IL_00a7 4a ldind.i4
IL_00a8 4f ldind.r8
IL_00a9 28 11 00 00 0a call 0xA000011
因此,在这里我们看到IL表示要加载c
的值,然后加载4字节有符号整数,然后将该整数视为指针并加载8字节实型(双精度)。
在64位平台上,指针应为大小无关或64位。因此ldind.i4
存在问题,因为基础地址为8个字节。并且由于IL指定仅读取4个字节,因此jit必须扩展结果以获取8个字节的值。在这里,它选择对扩展签名。
library.h @ 27:
00007ffd`b0cf2119 488b45a8 mov rax,qword ptr [rbp-58h]
00007ffd`b0cf211d 8b00 mov eax,dword ptr [rax]
00007ffd`b0cf211f 4863c0 movsxd rax,eax // **** sign extend ****
>>> 00007ffd`b0cf2122 c4e17b1000 vmovsd xmm0,qword ptr [rax]
00007ffd`b0cf2127 e854f6ffff call System.Console.WriteLine(Double) (00007ffd`b0cf1780)
当在完整框架上运行时,您显然很幸运,因为数组地址很小并且可以容纳31位或更少,因此读取4个字节然后将符号扩展为8个字节仍然可以得到正确的地址。但是在Core上却没有,所以这就是应用程序在那里崩溃的原因。
似乎您使用Win32目标生成了库。如果使用x64目标重建它,则IL将对*c
使用64位加载:
IL_00ab: ldloc.s V_5
IL_00ad: ldind.i8
IL_00ae: ldind.r8
IL_00af: call void [mscorlib]System.Console::WriteLine(float64)
应用运行正常。
看来这是C ++ / CLI的功能-即使在纯模式下,它生成的二进制文件也隐式依赖于体系结构。只有/clr:safe
可以生成与体系结构无关的程序集,并且您不能在此代码中使用它,因为它包含指针等无法验证的构造。
还请注意,.Net Core 2.x不支持C ++ / CLI的所有功能。这个特定的示例避免了不支持的位,但是更复杂的位可能不会。