Что может привести к тому, что аргументы P/Invoke будут неработоспособными при их передаче?

Это проблема, которая возникает специально для 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);

Структуры С# (ссылка):

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 FLEncoder определен как как непрозрачный указатель. При вызове метода выше на x86 и x64 все работает плавно, но на ARM я наблюдаю следующее. Адрес первого аргумента является адресом аргумента SECOND, а второй аргумент равен null (например, когда я регистрирую адреса на стороне С#, я получаю, например, 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);
}

Запуск приложения на С++ со следующими работами отлично:

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)

ИЗОБРАЖЕНИЕ Интересное обновление: если я "подделаю" подпись на С# и удалю второй параметр, то первый появится через "ОК".

EDIT 2 Второе интересное обновление: если я изменил определение размера С# FLSliceResult от UIntPtr до ulong, тогда аргументы вернутся... что не имеет смысла, поскольку size_t на ARM должно быть unsigned int.

EDIT 3 Добавление [StructLayout(LayoutKind.Sequential, Size = 12)] к определению в С# также делает эту работу, но ПОЧЕМУ? sizeof (FLSliceResult) в C/С++ для этой архитектуры возвращает 8 как следует. Установка такого же размера в С# вызывает сбой, но установка его на 12 делает работу.

РЕДАКТИРОВАТЬ 4. Я минимизировал тестовый пример, чтобы написать сценарий на С++. В С# UWP он терпит неудачу, но в С++ UWP он преуспевает.

РЕДАКТИРОВАТЬ 5 Здесь представлены разобранные инструкции для С++ и С# для сравнения (хотя С# я не конечно, сколько взять, чтобы я ошибся на стороне слишком многого)

РЕДАКТИРОВАТЬ 6 Дальнейший анализ показывает, что во время "хорошего" запуска, когда я ложусь и говорю, что структура имеет значение 12 байтов на С#, возвращаемое значение передается регистру r0, а остальные два аргумента входящий через r1, r2. Однако в неудачном прогоне это смещается так, что два аргумента входят через r0, r1, а возвращаемое значение находится где-то в другом месте (указатель стека?)

EDIT 7 Я обратился к Стандарт вызова процедур для архитектуры ARM, Я нашел эту цитату: "Композитный тип размером более 4 байт или размер которого не может быть определен статически как вызывающим, так и вызываемый, хранится в памяти по адресу, переданному в качестве дополнительного аргумента при вызове функции (§5.5, правило А .4). Память, которая будет использоваться для результата, может быть изменена в любой момент во время вызова функции ". Это означает, что переход в r0 является правильным поведением, поскольку дополнительный аргумент подразумевает первый (поскольку в C-вызове нет способа указать количество аргументов). Интересно, смешивает ли CLR это с другим правилом о 64-битных типах фундаментальных:" Фундаментальный тип данных размером в два слова (например, длинный, двойной и 64 -битные контейнерные векторы) возвращается в r0 и r1. "

EDIT 8 Хорошо, есть много доказательств, указывающих на то, что CLR делает неправильную вещь здесь, поэтому я подал сообщение об ошибке . Я надеюсь, что кто-то замечает это между всеми автоматическими ботами, отправляющими сообщения об этом репо: -S.