在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就稳了。