1. 程式人生 > 實用技巧 >在C++/CLI環境下,千萬不要把普通全域性函式當標準C/C++的函式指標傳遞給native的庫使用

在C++/CLI環境下,千萬不要把普通全域性函式當標準C/C++的函式指標傳遞給native的庫使用

先上一個簡單程式碼:

#include <cstdlib>
#include <cstdio>

// native apis
extern "C"
{
    typedef void (*CallbackFunction)(int value);

    CallbackFunction GlobalFunction;
    __declspec(dllexport) void NativeSetCallback(CallbackFunction func)
    {
        GlobalFunction = func;
    }
}

// test cli
namespace 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就穩了。