這是一個特別在ARM上發生的問題,而不是在x86或x64上。我遇到了用戶報告的這個問題,並且能夠通過Windows IoT在Raspberry Pi 2上使用UWP進行重現。我之前在調用約定時遇到過這種問題,但我在P/Invoke聲明中指定了Cdecl,並且試圖在本機端顯式添加__cdecl,並得到相同的結果。下面是一些信息:什麼可能導致P/Invoke參數傳遞失序?
的P/Invoke聲明(reference):
[DllImport(Constants.DllName, CallingConvention = CallingConvention.Cdecl)]
public static extern FLSliceResult FLEncoder_Finish(FLEncoder* encoder, FLError* outError);
C#的結構(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
{
}
在C標題中的功能(reference)
FLSliceResult FLEncoder_Finish(FLEncoder, FLError*);
FLSliceResult可能會導致一些問題,因爲我t是由值返回的,並且在本機端有一些C++的東西?
本機端的結構具有實際的信息,但對於C API,FLEncoder定義爲as an opaque pointer。當在x86和x64上調用上面的方法時,情況很順利,但在ARM上,我觀察到以下情況。第一個參數的地址是SECOND參數的地址,第二個參數是空的(例如,當我登錄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);
這種邏輯可以觸發最新的developer build的崩潰,但不幸的是,我還沒有想出如何可靠地通過Nuget提供本地調試符號,以便它可以跨越(僅從源代碼構建所有東西似乎是這樣做的...... )所以調試有點awkwar因爲需要構建本地和託管組件。如果有人想嘗試,我很樂意提供如何使這更容易的建議。但是,如果有人以前經歷過這種情況,或者對此有何看法,請添加答案,謝謝!當然,如果任何人想要再生產的情況下(不是簡單地建立一個不提供源代碼或難以建立源代碼的代碼),那麼請留下評論,但我不想通過製作一個如果沒有人會使用它(我不確定在實際的ARM上運行Windows內容有多受歡迎)
編輯有趣的更新:如果我在C#中「僞造」簽名並刪除第二個參數,那麼第一個通過確定。
EDIT 2第二個有趣的更新:如果我改變大小的C#FLSliceResult定義從UIntPtr
到ulong
那麼參數進來正確......這是沒有意義的,因爲size_t
ARM的應該是unsigned int類型。
編輯3添加[StructLayout(LayoutKind.Sequential, Size = 12)]
到C#中的定義也使這項工作,但爲什麼?C/C++中針對此體系結構的sizeof(FLSliceResult)會返回8,因爲它應該如此。在C#中設置相同的大小會導致崩潰,但將其設置爲12會使其工作。
編輯4我簡化了測試用例,以便我也可以編寫C++測試用例。在C#UWP中失敗,但在C++ UWP中成功。
EDIT 5Here是C++和C#的彙編指令作比較(雖然C#我不知道有多少拿,所以我錯在服用側太多)
編輯6進一步的分析表明,在我說謊並且說C#中的結構是12個字節的「良好」運行期間,返回值被傳遞到寄存器r0,其他兩個參數通過r1,r2進入。然而,在惡劣的運行,這種轉移在這樣兩個ARG遊戲通過R0,R1和返回值進來的是別的地方(堆棧指針?)
編輯7我諮詢了Procedure Call Standard for the ARM Architecture。我發現這個引用:「大於4字節的複合類型,或者其大小不能由調用者和被調用者靜態確定的大小,在調用該函數時作爲額外參數傳遞的地址中存儲在內存中(§5.5, 規則A.4),在函數調用期間的任何點都可以修改用於結果的內存。「這意味着傳入r0是正確的行爲,因爲額外的參數意味着第一個參數(因爲C調用約定沒有指定參數個數的方法)。我想知道CLR是否將這與另一個關於基本原理的規則混淆在一起:64位數據類型:「雙字大小的基本數據類型(例如,long long,double和64位容器化矢量)是 在r0中返回並且R1「。
編輯8好吧,有很多證據指出CLR在這裏做錯了事,所以我提交了一份bug report。我希望有人注意到所有自動化機器人在該回購站發佈問題:-S。
評論不適用於擴展討論;這個對話已經[轉移到聊天](http://chat.stackoverflow.com/rooms/157727/discussion-on-question-by-borrrden-what-could-cause-p-invoke-arguments-to-be-出)。 – Andy
60 upvotes並沒有提供賞金......這很奇怪 –
@MauricioGraciaGutierrez我想我可以用「這是一個JIT引擎中的錯誤」來回答這個問題(我假設大多數人來這裏是爲了upvote,因爲他們對該bug的解決方案 – borrrden