C ++ / CLI MSIL汇编中的指针数组

时间:2018-07-04 21:57:56

标签: c# .net c++-cli clr cil

我正在尝试包装一些旧的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.

1 个答案:

答案 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的所有功能。这个特定的示例避免了不支持的位,但是更复杂的位可能不会。