Это проблема, которая возникает специально для 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);
ИЗОБРАЖЕНИЕ Интересное обновление: если я "подделаю" подпись на С# и удалю второй параметр, то первый появится через "ОК".
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.