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

 

posted @   bodong  阅读(226)  评论(0编辑  收藏  举报
编辑推荐:
· 从 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)
点击右上角即可分享
微信分享提示