在C++/CLI環境下,千萬不要把普通全域性函式當標準C/C++的函式指標傳遞給native的庫使用
阿新 • • 發佈:2020-08-27
先上一個簡單程式碼:
#include <cstdlib> #include <cstdio> // native apis extern "C" { typedef void (*CallbackFunction)(int value); CallbackFunction GlobalFunction; __declspec(dllexport) void NativeSetCallback(CallbackFunction func) { GlobalFunction = func; } } // test clinamespace Project2 { static void CliGlobalCallback(int value) { System::Console::WriteLine(System::String::Format("{0}", value)); } extern "C" { static void StandardGlobalCallback(int value) { printf_s("%d", value); } } } extern "C" { // 這樣才可以防止前面的Callback被strip __declspec(dllexport) void NativeTestAPI() { NativeSetCallback(Project2::CliGlobalCallback); NativeSetCallback(Project2::StandardGlobalCallback); } }
從直觀感覺上來看,CliGlobalCallback和StandardGlobalCallback的函式簽名是一樣的,都傳遞給NativeSetCallback也不會報錯,然而實際上這兩個函式的簽名並不相同。檢視生成的二進位制程式碼可以看到如下區別:
.nep:0000000180005020 ; =============== S U B R O U T I N E ======================================= .nep:0000000180005020 .nep:0000000180005020 .nep:0000000180005020 ; void __fastcall Project2::`anonymous namespace'::CliGlobalCallback(Project2::_anonymous_namespace_ *__hidden this, int) .nep:0000000180005020 ?CliGlobalCallback@?A0x1736b8ca@Project2@@YAXH@Z proc near .nep:0000000180005020 ; DATA XREF: .rdata:__unep@?CliGlobalCallback@?A0x1736b8ca@Project2@@$$FYAXH@Z↓o .nep:0000000180005020 jmp short loc_18000502A .nep:0000000180005022 ; --------------------------------------------------------------------------- .nep:0000000180005022 ud2 .nep:0000000180005024 ; --------------------------------------------------------------------------- .nep:0000000180005024 jmp cs:__m2mep@?CliGlobalCallback@?A0x1736b8ca@Project2@@$$FYAXH@Z .nep:000000018000502A ; --------------------------------------------------------------------------- .nep:000000018000502A .nep:000000018000502A loc_18000502A: ; CODE XREF: Project2::`anonymous namespace'::CliGlobalCallback(int)↑j .nep:000000018000502A jmp cs:__mep@?CliGlobalCallback@?A0x1736b8ca@Project2@@$$FYAXH@Z .nep:000000018000502A ?CliGlobalCallback@?A0x1736b8ca@Project2@@YAXH@Z endp .nep:000000018000502A .nep:0000000180005030 .nep:0000000180005030 ; =============== S U B R O U T I N E ======================================= .nep:0000000180005030 .nep:0000000180005030 .nep:0000000180005030 StandardGlobalCallback@0x1736b8ca proc near .nep:0000000180005030 ; DATA XREF: .rdata:__unep@?StandardGlobalCallback@?A0x1736b8ca@@$$J0YAXH@Z↓o .nep:0000000180005030 jmp short loc_18000503A .nep:0000000180005032 ; --------------------------------------------------------------------------- .nep:0000000180005032 ud2 .nep:0000000180005034 ; --------------------------------------------------------------------------- .nep:0000000180005034 jmp cs:__m2mep@?StandardGlobalCallback@?A0x1736b8ca@@$$J0YAXH@Z .nep:000000018000503A ; --------------------------------------------------------------------------- .nep:000000018000503A .nep:000000018000503A loc_18000503A: ; CODE XREF: StandardGlobalCallback@0x1736b8ca↑j .nep:000000018000503A jmp cs:__mep@?StandardGlobalCallback@?A0x1736b8ca@@$$J0YAXH@Z .nep:000000018000503A StandardGlobalCallback@0x1736b8ca endp .nep:000000018000503A
CLI版本函式簽名中,有一個隱藏的__this,這在呼叫時,應該是需要有引數來提供的,雖然不是使用者手動提供。因此如果將CLI的這個函式作為普通的函式指標傳遞給Native的函式並作為普通的Native函式指標使用會導致CLI堆損壞,導致極其難查的崩潰,因為崩潰現場跟這裡八竿子打不到一塊兒去了。
正確的做法是使用extern "C"把它包起來,無論你在函式內部是否訪問CLI的程式碼,都沒關係。比如這樣:
// test cli namespace Project2 { extern "C" { static void CliGlobalCallback(int value) { System::Console::WriteLine(System::String::Format("{0}", value)); } } extern "C" { static void StandardGlobalCallback(int value) { printf_s("%d", value); } } }
然後再看一下彙編:
.nep:0000000180005020 ; =============== S U B R O U T I N E ======================================= .nep:0000000180005020 .nep:0000000180005020 .nep:0000000180005020 CliGlobalCallback@0x1736b8ca proc near ; DATA XREF: .rdata:__unep@?CliGlobalCallback@?A0x1736b8ca@@$$J0YAXH@Z↓o .nep:0000000180005020 jmp short loc_18000502A .nep:0000000180005022 ; --------------------------------------------------------------------------- .nep:0000000180005022 ud2 .nep:0000000180005024 ; --------------------------------------------------------------------------- .nep:0000000180005024 jmp cs:__m2mep@?CliGlobalCallback@?A0x1736b8ca@@$$J0YAXH@Z .nep:000000018000502A ; --------------------------------------------------------------------------- .nep:000000018000502A .nep:000000018000502A loc_18000502A: ; CODE XREF: CliGlobalCallback@0x1736b8ca↑j .nep:000000018000502A jmp cs:__mep@?CliGlobalCallback@?A0x1736b8ca@@$$J0YAXH@Z .nep:000000018000502A CliGlobalCallback@0x1736b8ca endp .nep:000000018000502A .nep:0000000180005030 .nep:0000000180005030 ; =============== S U B R O U T I N E ======================================= .nep:0000000180005030 .nep:0000000180005030 .nep:0000000180005030 StandardGlobalCallback@0x1736b8ca proc near .nep:0000000180005030 ; DATA XREF: .rdata:__unep@?StandardGlobalCallback@?A0x1736b8ca@@$$J0YAXH@Z↓o .nep:0000000180005030 jmp short loc_18000503A .nep:0000000180005032 ; --------------------------------------------------------------------------- .nep:0000000180005032 ud2 .nep:0000000180005034 ; --------------------------------------------------------------------------- .nep:0000000180005034 jmp cs:__m2mep@?StandardGlobalCallback@?A0x1736b8ca@@$$J0YAXH@Z .nep:000000018000503A ; --------------------------------------------------------------------------- .nep:000000018000503A .nep:000000018000503A loc_18000503A: ; CODE XREF: StandardGlobalCallback@0x1736b8ca↑j .nep:000000018000503A jmp cs:__mep@?StandardGlobalCallback@?A0x1736b8ca@@$$J0YAXH@Z .nep:000000018000503A StandardGlobalCallback@0x1736b8ca endp .nep:000000018000503A .nep:0000000180005040 .nep:0000000180005040 ; =============== S U B R O U T I N E ======================================= .nep:0000000180005040 .nep:0000000180005040
這樣再傳遞給Native就穩了。