先上一个简单代码:

#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: ; =============== S U B R O U T I N E =======================================
.nep:
.nep:
.nep: ; void __fastcall Project2::`anonymous namespace'::CliGlobalCallback(Project2::_anonymous_namespace_ *__hidden this, int)
.nep: ?CliGlobalCallback@?A0x1736b8ca@Project2@@YAXH@Z proc near
.nep: ; DATA XREF: .rdata:__unep@?CliGlobalCallback@?A0x1736b8ca@Project2@@$$FYAXH@Z↓o
.nep: jmp short loc_18000502A
.nep: ; ---------------------------------------------------------------------------
.nep: ud2
.nep: ; ---------------------------------------------------------------------------
.nep: 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:
.nep: ; =============== S U B R O U T I N E =======================================
.nep:
.nep:
.nep: StandardGlobalCallback@0x1736b8ca proc near
.nep: ; DATA XREF: .rdata:__unep@?StandardGlobalCallback@?A0x1736b8ca@@$$J0YAXH@Z↓o
.nep: jmp short loc_18000503A
.nep: ; ---------------------------------------------------------------------------
.nep: ud2
.nep: ; ---------------------------------------------------------------------------
.nep: 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: ; =============== S U B R O U T I N E =======================================
.nep:
.nep:
.nep: CliGlobalCallback@0x1736b8ca proc near ; DATA XREF: .rdata:__unep@?CliGlobalCallback@?A0x1736b8ca@@$$J0YAXH@Z↓o
.nep: jmp short loc_18000502A
.nep: ; ---------------------------------------------------------------------------
.nep: ud2
.nep: ; ---------------------------------------------------------------------------
.nep: 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:
.nep: ; =============== S U B R O U T I N E =======================================
.nep:
.nep:
.nep: StandardGlobalCallback@0x1736b8ca proc near
.nep: ; DATA XREF: .rdata:__unep@?StandardGlobalCallback@?A0x1736b8ca@@$$J0YAXH@Z↓o
.nep: jmp short loc_18000503A
.nep: ; ---------------------------------------------------------------------------
.nep: ud2
.nep: ; ---------------------------------------------------------------------------
.nep: 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:
.nep: ; =============== S U B R O U T I N E =======================================
.nep:
.nep:

这样再传递给Native就稳了。

在C++/CLI环境下,千万不要把普通全局函数当标准C/C++的函数指针传递给native的库使用的更多相关文章

  1. [PHP] PHP在CLI环境下的错误日志

    1.display_errors = Off;//控制php是否输出错误;在生产环境中输出会泄露敏感信息;建议记录错误而不是将它们发送到STDOUToff :不显示任何错误;stderr :向STDE ...

  2. [PHP] cli环境下php设置进程名字

    if (function_exists('cli_set_process_title')) { cli_set_process_title("superman php master proc ...

  3. 多线程编程之Linux环境下的多线程(一)

    一.Linux环境下的线程 相对于其他操作系统,Linux系统内核只提供了轻量级进程的支持,并未实现线程模型.Linux是一种“多进程单线程”的操作系统,Linux本身只有进程的概念,而其所谓的“线程 ...

  4. 腾讯云CMQ消息队列在Windows环境下的使用

    版权声明:本文由李少华原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/100 来源:腾云阁 https://www.qclo ...

  5. 在SAP云平台的CloudFoundry环境下消费ABAP On-Premise OData服务

    我的前一篇文章 使用Java+SAP云平台+SAP Cloud Connector调用ABAP On-Premise系统里的函数介绍了在SAP云平台的Neo环境下如何通过SAP Cloud Conne ...

  6. PHP 命令行模式实战之cli+mysql 模拟队列批量发送邮件(在Linux环境下PHP 异步执行脚本发送事件通知消息实际案例)

    源码地址:https://github.com/Tinywan/PHP_Experience 测试环境配置: 环境:Windows 7系统 .PHP7.0.Apache服务器 PHP框架:ThinkP ...

  7. Atititcmd cli环境变量的调用设置与使用

    Atititcmd cli环境变量的调用设置与使用 1.1. Cgi 环境变量的调用设置与使用1 1.2. 环境变量vs  系统变量1 1.3. 环境变量的分类 A.与服务器相关的环境变量B ,与客户 ...

  8. php cli方式下获取服务器ip

    (未整理....) (1)php cli方式下获取服务器ip [php]  function getServerIp(){          $ss = exec('/sbin/ifconfig et ...

  9. linux环境下给文件加密/解密的方法

      原文地址:linix环境下给文件加密/解密的方法 作者:oracunix 一. 利用 vim/vi 加密:优点:加密后,如果不知道密码,就看不到明文,包括root用户也看不了:缺点:很明显让别人知 ...

随机推荐

  1. Android ViewPager进行碎片(Fragment)的分页操作

    今天讲的是ViewPager 这个东西就是 假设你写了Fragment和Fragment1两个界面,用这个控件就可以实现两个界面之间的滑动,而不用Intent去跳转: 注意: 在布局中定义控件的时候, ...

  2. C、C++、boost、Qt在嵌入式系统开发中的使用

    概述 嵌入式系统开发相对来说属于偏底层的开发,也就是与硬件结合比较紧密,只能使用C/C++语言.对于做平台开发的人来说,C语言真的是很"古老"的语言,属于操作系统语言!好多人会觉得 ...

  3. VMware虚拟机磁盘收缩的几种方法

    原文地址:http://www.cnblogs.com/5201351/p/4290401.html 根据下面转载的内容,我在VMware 12.0.0 build-2985596的ubuntu上做试 ...

  4. 再见HTML ! 用纯Python就能写一个漂亮的网页

    我们在写一个网站或者一个网页界面的时候,需要学习很多东西,对小白来说很困难!比如我要做一个简单的网页交互: 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后,不知道在 ...

  5. Python高性能HTTP客户端库requests的使用

    Python中有许多HTTP客户端.使用最广泛且最容易的是requests. 持续连接 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手.很 ...

  6. spring data jpa 之 通用接口

    园主这一阵子接到一个需求,就是将spring data jpa再进行封装,实现通过调用一个baseRepository,来实现每个类的增删改查操作,结合spring data jpa 原有的便捷操作, ...

  7. Druid数据源的使用

    1 Druid数据源简介 Druid是Java语言中最好的数据库连接池.Druid能够提供强大的监控和扩展功能.通过访问http://localhost:8080(自己的端口)/druid/ 可以查看 ...

  8. Java连接Redis,存储对象获取对象()byte和json),连接池

    Java连接Redis Jedis连接Redis,Lettuce连接Redis Jedis连接Redis 1. 创建maven项目 2. 引入依赖 <dependencies> <d ...

  9. Java 循环语句及流程控制语句

    java循环语句while与do-while 一 while循环 while循环语句和选择结构if语句有些相似,都是根据条件判断来决定是否执行大括号内的执行语句. 区别在于,while语句会反复地进行 ...

  10. C#LeetCode刷题之#263-丑数(Ugly Number)

    问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/3862 访问. 编写一个程序判断给定的数是否为丑数.丑数就是只包含 ...