Що може призвести до того, що аргументи P / Invoke виходять з ладу при передачі?


79

Це проблема, яка трапляється саме на ARM, а не на x86 або x64. Мені повідомив користувач про цю проблему, і я зміг відтворити її за допомогою UWP на Raspberry Pi 2 через Windows IoT. Я вже бачив подібну проблему з невідповідними умовами викликів, але я вказую Cdecl у декларації P / Invoke, і я намагався явно додати __cdecl на рідній стороні з тими ж результатами. Ось трохи інформації:

Декларація P / Invoke ( посилання ):

[DllImport(Constants.DllName, CallingConvention = CallingConvention.Cdecl)]
public static extern FLSliceResult FLEncoder_Finish(FLEncoder* encoder, FLError* outError);

Структури C # ( посилання ):

internal unsafe partial struct FLSliceResult
{
    public void* buf;
    private UIntPtr _size;

    public ulong size
    {
        get {
            return _size.ToUInt64();
        }
        set {
            _size = (UIntPtr)value;
        }
    }
}

internal enum FLError
{
    NoError = 0,
    MemoryError,
    OutOfRange,
    InvalidData,
    EncodeError,
    JSONError,
    UnknownValue,
    InternalError,
    NotFound,
    SharedKeysStateError,
}

internal unsafe struct FLEncoder
{
}

Функція в заголовку C ( посилання )

FLSliceResult FLEncoder_Finish(FLEncoder, FLError*);

FLSliceResult може спричиняти деякі проблеми, оскільки він повертається за значенням і містить на ньому речі C ++ на рідній стороні?

Структури на рідній стороні мають фактичну інформацію, але для API C FLEncoder визначається як непрозорий вказівник . При виклику методу вище на x86 і x64 все працює безперебійно, але на ARM я спостерігаю наступне. Адреса першого аргументу - це адреса ДРУГОГО аргументу, а другий аргумент - нульовий (наприклад, коли я реєструю адреси на стороні C #, я отримую, наприклад, 0x054f59b8 та 0x0583f3bc, але потім на рідній стороні аргументи 0x0583f3bc та 0x00000000). Що може спричинити подібну проблему? Хто-небудь має якісь ідеї, бо я тупиця ...

Ось код, який я запускаю для відтворення:

unsafe {
    var enc = Native.FLEncoder_New();
    Native.FLEncoder_BeginDict(enc, 1);
    Native.FLEncoder_WriteKey(enc, "answer");
    Native.FLEncoder_WriteInt(enc, 42);
    Native.FLEncoder_EndDict(enc);
    FLError err;
    NativeRaw.FLEncoder_Finish(enc, &err);
    Native.FLEncoder_Free(enc);
}

Запуск програми на C ++ із наступним працює відмінно:

auto enc = FLEncoder_New();
FLEncoder_BeginDict(enc, 1);
FLEncoder_WriteKey(enc, FLSTR("answer"));
FLEncoder_WriteInt(enc, 42);
FLEncoder_EndDict(enc);
FLError err;
auto result = FLEncoder_Finish(enc, &err);
FLEncoder_Free(enc);

Ця логіка може спричинити збій з останньою версією розробникаале, на жаль, я ще не з'ясував, як надійно забезпечити вбудовані символи налагодження через Nuget, щоб їх можна було пройти (це, здається, робить лише все з джерела ...), тому налагодження трохи незручне, оскільки обидва рідні і керовані компоненти повинні бути побудовані. Я відкритий для пропозицій, як це зробити простіше, якщо хтось хоче спробувати. Але якщо хтось із цим стикався раніше або має якісь ідеї щодо того, чому це трапляється, додайте відповідь, дякую! Звичайно, якщо хтось бажає випадку відтворення (або простого для побудови, який не передбачає переходу до джерела, або важкого для створення такого, що робить), тоді залиште коментар, але я не хочу проходити процес його створення якщо ніхто не збирається ним користуватися (я не впевнений, наскільки популярний запуск Windows-матеріалів на реальній ARM)

РЕДАКТУВАТИ Цікаве оновлення: якщо я "підроблю" підпис у C # і видаляю 2-й параметр, то перший надходить через OK.

EDIT 2 Друге цікаве оновлення: Якщо я зміню визначення розміру C # FLSliceResult з UIntPtrна, ulongтоді аргументи надходять правильно ... що не має сенсу, оскільки size_tна ARM має бути беззнаковий int.

РЕДАКТУВАТИ 3 Додавання [StructLayout(LayoutKind.Sequential, Size = 12)]до визначення в C # також робить цю роботу, але ЧОМУ? sizeof (FLSliceResult) в C / C ++ для цієї архітектури повертає 8 як слід. Встановлення однакового розміру в C # спричиняє збій, але встановлення значення 12 змушує це працювати.

EDIT 4 Я мінімізував тестовий приклад, щоб також міг написати тестовий кейс на C ++. У C # UWP це не вдається, але в C ++ UWP це вдається.

EDIT 5 Ось розібрані інструкції як для C ++, так і для C # для порівняння (хоча C # я не впевнений, скільки брати, тому помилився на тому, що взяв занадто багато)

EDIT 6 Подальший аналіз показує, що під час "хорошого" запуску, коли я брешу і кажу, що структура складає 12 байт на C #, повертається значення передається в регістр r0, а інші два аргументи надходять через r1, r2. Однак у поганому запуску це зміщується так, що два аргументи надходять через r0, r1, а повертане значення знаходиться десь в іншому місці (вказівник стека?)

EDIT 7 Я проконсультувався зі стандартом виклику процедур для архітектури ARM . Я знайшов цю цитату: "Складений тип, що перевищує 4 байти, або розмір якого не може статично визначений як абонентом, так і абонентом, зберігається в пам'яті за адресою, переданою як додатковий аргумент під час виклику функції (§5.5, правило A .4). Пам'ять, яка буде використана для результату, може бути змінена в будь-який момент під час виклику функції. " Це означає, що перехід у r0 є правильною поведінкою, оскільки додатковий аргумент передбачає перший (оскільки умова виклику C не має способу вказати кількість аргументів). Цікаво, чи не плутає CLR це з іншим правилом про фундаментальне 64-розрядні типи даних: "Подвійний розмір основного типу даних (наприклад, довгі довгі, подвійні та 64-розрядні контейнерні вектори) повертається в r0 та r1."

EDIT 8 Добре, є безліч доказів того, що CLR робить тут не те, тому я подав повідомлення про помилку . Сподіваюся, хтось помічає це між усіма автоматизованими ботами, що публікують проблеми в цьому репо: -S.


1
Коментарі не призначені для розширеного обговорення; цю розмову переміщено до чату .
Енді

60 голосів "за" і жодної нагороди не було запропоновано ... це дивно
Mauricio Gracia Gutierrez

6
@MauricioGraciaGutierrez Я думаю, я міг би відповісти на це запитання "це помилка в двигуні JIT" (я припускаю, що більшість людей приходять сюди, щоб проголосувати за те, що вони зацікавлені у вирішенні помилки)
borrrden

звук , як великий і маленької індійської проблеми ... stackoverflow.com/questions/217980 / ...
Proxytype

Чи можна це питання закрити, оскільки воно здається помилкою?
huysentruitw

Відповіді:


1

Питання, яке я подав на GH, сидить там уже досить давно. Я вважаю, що така поведінка є просто помилкою і не потрібно більше часу витрачати на її вивчення.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.