在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就稳了。
分类:
C++
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)