Массив указателей в сборке C ++ / CLI MSIL - PullRequest
0 голосов
/ 05 июля 2018

Я пытаюсь обернуть некоторый устаревший код C для использования с C #, работающим на .NET Core Я использую подход, приведенный здесь , чтобы создать оболочку 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 ++, он будет работать точно так, как ожидается, и не вылетает. Сбой происходит только при компиляции кода в сборку MSIL с clr:pure и запуске из основного приложения .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 голосов
/ 08 июля 2018

Это похоже на проблему в сгенерированном IL для метода test. В точке сбоя мы читаем **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-байтовый вещественный тип (double).

На 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 будет использовать 64-битную загрузку для *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 может создавать независимые от архитектуры сборки, и вы не можете использовать это с этим кодом, поскольку он содержит непроверяемые конструкции, такие как указатели.

Также обратите внимание, что не все функции C ++ / CLI поддерживаются в .Net Core 2.x. В этом конкретном примере избегаются неподдерживаемые биты, но более сложные могут этого не делать.

...