C ++ / CLI MSIL汇编中的指针数组 [英] Array of pointers in C++/CLI MSIL assembly
问题描述
我正在尝试包装一些旧的C代码,以与在.NET Core上运行的C#结合使用。我正在使用此处提供的方法来创建一个C ++包装程序,该程序可以编译为纯MSIL。它适用于简单的函数,但是我发现,如果我的代码曾经使用指针到指针或指针数组,它将因内存冲突而崩溃。通常它会使Visual Studio崩溃,而我必须重新启动一切,这很乏味。
I'm trying to wrap some legacy C code for use with C# running on .NET Core. I'm using the approach given here to create a C++ wrapper that compiles to pure MSIL. It's working well for simple functions, but I've found that if my code ever uses pointers-to-pointers or arrays of pointers it will crash with a memory violation. Often it crashes Visual Studio and I have to restart everything, which is tedious.
例如,以下代码将导致崩溃:
For example, the following code will cause the crashes:
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.
}
}
请注意,仅使用指针不会引起任何问题。
Note that simply using pointers doesn't cause any problem. It's only when there is that extra level of indirection.
如果我将代码放到CLR C ++控制台应用程序中,它将完全按预期运行,并且不会崩溃。仅当使用 clr:pure
将代码编译为MSIL程序集并从.NET核心应用程序运行时,才会发生崩溃。
If I drop the code in a CLR C++ console app, it works exactly as expected and doesn't crash. The crash only happens when compiling the code into an MSIL assembly with clr:pure
and running from a .NET core app.
可能会发生什么?
更新1:以下是Visual Studio文件: https://app.box.com/s/xejfm4s46r9hs0inted2kzhkh9qzmjpb 这是两个项目。 MSIL程序集称为 library
,而 CoreApp
是C#控制台应用程序,将调用该库。警告,运行Visual Studio时很可能使它崩溃。
Update 1: Here are the Visual Studio files: https://app.box.com/s/xejfm4s46r9hs0inted2kzhkh9qzmjpb It's two projects. The MSIL assembly is called library
and the CoreApp
is a C# console app that'll call the library. Warning, it's likely to crash Visual Studio when you run it.
更新2:我也注意到了这一点:
Update 2: I noticed this too:
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.
推荐答案
这看起来像是生成的IL中的问题 test
方法。在崩溃时,我们正在读取 ** c
,而 c
是本地数字5。
This looks like an issue in the generated IL for the test
method. At the point of the crash we're reading **c
, and c
is local number 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字节实型(双精度)。
So here we see the IL says to load the value of c
, then load a 4 byte signed integer, then treat that integer as a pointer and load an 8 byte real type (double).
在64位平台上,指针应为大小无关或64位。因此 ldind.i4
有问题,因为基础地址为8个字节。并且由于IL指定仅读取4个字节,因此jit必须扩展结果以获取8个字节的值。
On a 64 bit platform pointers should be either size-neutral or 64 bits. So the ldind.i4
is problematic as the underlying address is 8 bytes. And since the IL specifies reading only 4 bytes, the jit must extend the result to get an 8 byte value. Here it chooses to sign extend.
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上却没有,所以这就是应用程序在那里崩溃的原因。
You apparently get lucky when running on full framework as the array address is small and fits in 31 bits or less, so reading 4 bytes and then sign-exending to 8 bytes still gives the right address. But on Core it doesn't and so that's why the app crashes there.
似乎您使用Win32目标生成了库。如果使用x64目标进行重建,则IL将对 * c
使用64位加载:
It appears you generated your library using the Win32 target. If you rebuild it with an x64 target the IL will use a 64 bit load for *c
:
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
可以生成与体系结构无关的程序集,并且您不能在此代码中使用它,因为它包含指针等无法验证的构造。
It appears this is a feature in C++/CLI -- the binaries it produces are implicitly architecture dependent even in pure mode. Only /clr:safe
can produce architecture-independent assemblies, and you can't use that with this code since it contains unverifiable constructs like pointers.
还请注意,.Net Core 2.x不支持C ++ / CLI的所有功能。这个特定的示例避免了不支持的位,但是更复杂的位可能不会。
Also please note not all features of C++/CLI are supported in .Net Core 2.x. This particular example avoids unsupported bits, but more complex ones may not.
这篇关于C ++ / CLI MSIL汇编中的指针数组的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!