Jest to problem, który występuje szczególnie w ARM, a nie na x86 lub x64. Miałem ten problem zgłoszony przez użytkownika i mogłem go odtworzyć za pomocą UWP na Raspberry Pi 2 przez Windows IoT. Widziałem już tego rodzaju problem z niedopasowanymi konwencjami wywołań, ale określam Cdecl w deklaracji P / Invoke i próbowałem jawnie dodać __cdecl po stronie natywnej z tymi samymi wynikami. Oto kilka informacji:
Deklaracja P / Invoke ( odniesienie ):
[DllImport(Constants.DllName, CallingConvention = CallingConvention.Cdecl)]
public static extern FLSliceResult FLEncoder_Finish(FLEncoder* encoder, FLError* outError);
Struktury C # ( odwołanie ): The C # structs ( reference ):
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
{
}
Funkcja w nagłówku C ( odwołanie )
FLSliceResult FLEncoder_Finish(FLEncoder, FLError*);
FLSliceResult może powodować pewne problemy, ponieważ jest zwracany przez wartość i zawiera pewne elementy C ++ po stronie natywnej?
Struktury po stronie natywnej zawierają aktualne informacje, ale w przypadku interfejsu API języka C FLEncoder jest definiowany jako nieprzezroczysty wskaźnik . Wywołując powyższą metodę na x86 i x64 rzeczy działają płynnie, ale na ARM obserwuję co następuje. Adres pierwszego argumentu to adres DRUGIEGO argumentu, a drugiego argumentu null (np. Gdy loguję adresy po stronie C # otrzymuję np. 0x054f59b8 i 0x0583f3bc, ale potem po stronie natywnej argumenty to 0x0583f3bc i 0x00000000). Co może powodować tego rodzaju problem z awariami? Czy ktoś ma jakieś pomysły, bo jestem zdumiony ...
Oto kod, który uruchomiłem do odtworzenia:
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);
}
Uruchamianie aplikacji C ++ z następującymi elementami działa dobrze:
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);
Ta logika może spowodować awarię w najnowszej kompilacji programistyale niestety nie odkryłem jeszcze, jak niezawodnie być w stanie zapewnić natywne symbole debugowania za pośrednictwem Nuget, tak aby można je było przejść (tylko budowanie wszystkiego ze źródła wydaje się to robić ...), więc debugowanie jest nieco niezręczne, ponieważ oba natywne i zarządzanych komponentów. Jestem jednak otwarty na sugestie, jak to ułatwić, jeśli ktoś chce spróbować. Ale jeśli ktoś doświadczył tego wcześniej lub ma jakieś pomysły, dlaczego tak się dzieje, dodaj odpowiedź, dziękuję! Oczywiście, jeśli ktoś chce powielanego przypadku (albo łatwego do zbudowania, który nie zapewnia przechodzenia do źródła, albo trudnego do zbudowania, który to robi), zostaw komentarz, ale nie chcę przechodzić przez proces tworzenia takiego jeśli nikt nie będzie go używał (nie jestem pewien, jak popularne jest uruchamianie rzeczy Windows na rzeczywistym ARM)
EDYTUJ Interesująca aktualizacja: Jeśli „sfałszuję” podpis w C # i usuwam drugi parametr, to pierwszy przechodzi przez OK.
EDIT 2 Druga interesująca zmiana: Gdybym zmienić definicję C # FLSliceResult wielkości od UIntPtr
aby ulong
następnie argumenty przyjść poprawnie ... co nie ma sensu, ponieważ size_t
na ARM powinny być unsigned int.
EDYCJA 3 Dodanie [StructLayout(LayoutKind.Sequential, Size = 12)]
do definicji w C # również sprawia, że to działa, ale DLACZEGO? sizeof (FLSliceResult) w C / C ++ dla tej architektury zwraca 8 tak, jak powinien. Ustawienie tego samego rozmiaru w C # powoduje awarię, ale ustawienie go na 12 sprawia, że działa.
EDYCJA 4 Zminimalizowałem przypadek testowy, aby móc napisać również przypadek testowy w C ++. W C # UWP kończy się niepowodzeniem, ale w C ++ UWP kończy się sukcesem.
EDYCJA 5 Oto zdemontowane instrukcje dla C ++ i C # dla porównania (chociaż C # nie jestem pewien, ile wziąć, więc popełniłem błąd po stronie biorąc zbyt dużo)
EDYCJA 6 Dalsza analiza pokazuje, że podczas "dobrego" przebiegu, kiedy kłamię i mówię, że struktura ma 12 bajtów w C #, wartość zwracana jest przekazywana do rejestru r0, a pozostałe dwa argumenty przychodzą przez r1, r2. Jednak w złym przebiegu jest to przesuwane tak, że dwa argumenty przychodzą przez r0, r1, a wartość zwracana jest gdzie indziej (wskaźnik stosu?)
EDYCJA 7 Zapoznałem się ze standardem wywołania procedury dla architektury ARM . Znalazłem następujący cytat: „Typ złożony większy niż 4 bajty lub którego rozmiaru nie można określić statycznie zarówno przez wywołującego, jak i odbierającego, jest przechowywany w pamięci pod adresem przekazanym jako dodatkowy argument podczas wywołania funkcji (§5.5, reguła A .4). Pamięć, która ma być używana na wynik, może być modyfikowana w dowolnym momencie podczas wywołania funkcji. " Oznacza to, że przejście do r0 jest poprawnym zachowaniem, ponieważ dodatkowy argument implikuje pierwszy (ponieważ konwencja wywoływania C nie ma sposobu na określenie liczby argumentów). Zastanawiam się, czy CLR myli to z inną zasadą dotyczącą fundamentalnych 64-bitowe typy danych: „Podstawowy typ danych o rozmiarze dwóch słów (np. Długie długie, podwójne i 64-bitowe wektory kontenerowe) jest zwracany w r0 i r1.”
EDYCJA 8 Ok, jest wiele dowodów wskazujących na to, że CLR robi tutaj niewłaściwą rzecz, więc złożyłem raport o błędzie . Mam nadzieję, że ktoś zauważy to między wszystkimi automatycznymi botami publikującymi problemy w tym repozytorium: -S.