Windows平台Go调用DLL的坑
最近的项目中,使用了GO来开发一些服务中转程序。业务比较简单,但是有一些业务需要复用原有C++开发的代码。而在WINDOWS,用CGO方式来集成C/C++代码并不是太方便。所以用DLL把C++的代码封装起来,然后提供基本的API来完成复用。在这个过程中遇到了一些问题及解决方法,记录下来,也给遇到类似或者同样问题的人一个借鉴。
如果你还不清楚怎么在GO中调用DLL,可以参考这篇文章《WindowDLLs》。
Callback的限制
在WINDOWS下调用一些API时会要求传入回调函数,在C/C++下使用非常简单,直接传入函数指针就可以了。但是在GO这种有GC特性,又有运行时库的语言要稍微麻烦一点。
GO为了解决这种回调要求,在syscall包里提供了NewCallback和NewCallbackCDecl两个函数来帮助用户解决回调的问题。具体的Callback机制这里先不说了,只是说一下在GO里,Callback的使用是有限制的。而且对传入的GO函数也是有对应的要求。在我看的go1.4正式版本中,src/runtime/syscall_windows.go line 71:会检查callback的数量是否超过了最大的限制值,这个限制值目前是2000。如果超过就会抛出一个异常。这个是非常蛋疼的事情。而且从GO的ISSUE库里,这个问题的解决是一推再推。
GO的ISSUE里关于CALLBACK的事情已经很明确了,就是要让大家复用CALLBACK,也就是用全局函数来搞。在golang-china讨论组里,minux给予了这样的回答:
我说一下callback上限的原因。由于系统调用callback函数的时候不提供任何其他的参数,导致区分不同的callback只能通过被调用函数的地址,也就是说,一个Go的callback函数必须对应一个单独的C函数地址
旧的机制是对每个Go函数,在堆上动态构造一个对应的C函数。这样在 Go 1.1 之前的时候没问题,因为当时函数闭包也需要可执行的堆,但是 1.1 修改了函数的表示方式,闭包不再需要动态代码生成了,为了把 Windows 上也不再需要可执行的堆,必须想另外一个办法来实现 callback,新的机制参加 issue 5494,是我提议的。既然不动态生成代码,很明显的一个问题就是 callback 的总数会有一个上限。这是这有任何办法的了。
其实一般的 Windows 程序也不会有那么多 callback,2000个绝对是绰绰有余的;之所以 Go 这里很容易用光,是因为可能大家愿意使用 closure,而不是一个全局函数做 callback,使用全局函数做 callback (C/C++程序就是这么做的),2000个绝对是绰绰有余;但是由于闭包每次建立都是不同的,就算你其实就一个地方需要创建 callback,用闭包的话2000次就用光了所有的 callback。使用 callback 的时候建议用全局函数,也不要用 method,因为那样也是闭包,2000 个绝对足够足够。
所以如果你要使用CALLBACK的时候,尽量的想办法复用,否则会很囧。
栈溢出(0xC00000FD, _chkstk)
这个问题比较囧。我封装的DLL里用到了另外同事写的代码,他的风格是把函数写的巨大,在C++下运行一切正常,但是当在GO写的程序里调用的时候,内部抛出了0xC00000FD的错误,也就是栈溢出。
发生的地方是_chkstk。这个函数是VS下的C++编译器在代码生成的时候加进去的,所以别想着通过参数啊什么的干掉了。这个函数的作用是说当你的函数内有超过了一页大小的变量时,编译器会把在函数头插入对_chkstk的调用代码,_chkstk会检查栈的大小是否足够该函数的局部变量使用,如果不够,它会访问栈的GUARD PAGE,然后会触发系统内核检查到该错误,这个时候操作系统会扩展栈的大小。首先这里就有矛盾了,GO语言本身会扩展栈,而为了做到这点就要把堆拿来当栈使用,但是这个时候的问题是,它和OS默认的栈空间不同,导致内核检查到的错误是不可扩展的,这个时候OS也搞不定了,只能让程序挂掉了。而且OS本身支持的栈扩展是有限制的,不像GO实现的栈扩展,WINDOWS下这个扩展最终会触发到一个无法申请的栈地址,如果无法扩展栈,还是要让程序挂掉。可以点这里《What is the purpose of the _chkstk() function》查看更详细的解释。所以问题的本质就是局部变量太大(自己反汇编DLL发现确实有个对象变量体积巨大...),解决方法就是该把变量该放到堆里的放到堆里的就放到堆里。最后的效果是,再也不抛出这个0xC00000FD的异常了。
感想
其实上面用到的C++代码,可以用GO直接重写的,但是考虑到时间成本,最后还是放弃了。CALLBACK的问题我是暂时重写了对应的API代码来搞定。
GO在写网络通信,并发场景时比较嗨皮,但是这种跨语言的交互操作实在是太蛋疼。只能且行且蛋疼了
Update:
这个问题后来被我提交给GO的开发团队了。具体详情可以见这里:https://github.com/golang/go/issues/9457
按照minux的说法就是需要在使用DLL时的,导入runtime/cgo即可。是说编译器编译的时候,发现没有使用CGO的话,就会把系统线程的栈大小设置为64KB,而且是不扩展的;但是如果使用 了CGO,就会变大默认栈,同时变为可扩展的。
最后还是得吐槽下,What the fuck!对于GO的这种使用方法真心不喜欢,实在是太蛋疼。CGO方式还要依赖GCC之类的玩意。WINDOWS下想愉快的玩耍根本不是太可能。改天再说下用GO来的感受。
Windows平台Go调用DLL的坑的更多相关文章
- Windows平台Go调用DLL的坑(居然有这么多没听过的名词)
最近的项目中,使用了GO来开发一些服务中转程序.业务比较简单,但是有一些业务需要复用原有C++开发的代码.而在WINDOWS,用CGO方式来集成C/C++代码并不是太方便.所以用DLL把C++的代码封 ...
- C++利用模板在Windows上快速调用DLL函数
更新日志 --------- 2021/08/01 更新V2.2 增加 GetHmodule 函数 - 允许用户获取HMODULE以验证加载DLL是否成功. 2021/08/03 更新V2.3 增加 ...
- windows应用中调用DLL一步步试验
试验环境: PC:win10 build 10143 IDE: vs2015 RC WinPhone: win10 build 10136 简单界面,点按钮,算加法 一.主程用C++ 1.新建visu ...
- c#调用c++ dll 入坑记录
1.DLL引用坑 [DllImport("NetDLL.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConve ...
- 在Windows中实现Java调用DLL(转载)
本文提供调用本地 C 代码的 Java 代码示例,包括传递和返回某些常用的数据类型.本地方法包含在特定于平台的可执行文件中.就本文中的示例而言,本地方法包含在 Windows 32 位动态链接库 (D ...
- Windows下C语言调用dll动态链接库
dll是windows下的动态链接库文件,下面记录一下在windows下如何调用C语言开发的dll动态链接库. 1.dll动态链接库的源代码 hello_dll.c #include "st ...
- war包部署在tomcat下,使用windows service服务方式启动tomcat服务器,在包含调用dll的模块,报dll找不到问题的解决办法
问题描述: 开发了一个需要调用dll的java web程序,在idea开发环境下运行调试没问题,可以正常运行,在tomcat/bin下,运行批处理startup.bat,启动tomcat服务器,也可以 ...
- C/C++:Windows编程—调用DLL程序的2种方法(转载)
文章为转载,原文出处https://blog.csdn.net/qq_29542611/article/details/86618902 前言先简单介绍下DLL.DLL:Dynamic Link Li ...
- Windows平台开发Mapreduce程序远程调用运行在Hadoop集群—Yarn调度引擎异常
共享原因:虽然用一篇博文写问题感觉有点奢侈,但是搜索百度,相关文章太少了,苦苦探寻日志才找到解决方案. 遇到问题:在windows平台上开发的mapreduce程序,运行迟迟没有结果. Mapredu ...
随机推荐
- Travis CI用来持续集成你的项目
这里持续集成基于GitHub搭建的博客为项目 工具: zqz@ubuntu:~$ node --version v4.2.6 zqz@ubuntu:~$ git --version git versi ...
- jQuery的61种选择器
The Write Less , Do More ! jQuery选择器 1. #id : 根据给定的ID匹配一个元素 <p id="myId">这是第一个p标签< ...
- .NetCore中的日志(2)集成第三方日志工具
.NetCore中的日志(2)集成第三方日志工具 0x00 在.NetCore的Logging组件中集成NLog 上一篇讨论了.NetCore中日志框架的结构,这一篇讨论一下.NetCore的Logg ...
- linux基础学习笔记
我用的是centOS7.0版本的系统.linux的shell终端窗口类似于wind的command窗口 shell命令提示符格式:用户名@主机名:目录名 提示符 @前面的是已登录的用户名,@之后的为计 ...
- Java8实战分享
虽然很多人已经使用了JDK8,看到不少代码,貌似大家对于Java语言or SDK的使用看起来还是停留在7甚至6. Java8在流式 or 链式处理,并发 or 并行方面增强了很多,函数式的风格使代码可 ...
- VisualStudio2013 如何打开之前版本开发的(.vdproj )安装项目
当你的项目使用早于 visualstudio2013 的版本开发并且使用 Visual Studio Installer 制作安装项目时,在升级至 VS2013 后会发现新安装项目无法打开, VS20 ...
- 微软Azure 经典模式下创建内部负载均衡(ILB)
微软Azure 经典模式下创建内部负载均衡(ILB) 使用之前一定要注意自己的Azure的模式,老版的为cloud service模式,新版为ARM模式(资源组模式) 本文适用于cloud servi ...
- Impress.js上手 - 抛开PPT、制作Web 3D幻灯片放映
前言: 如果你已经厌倦了使用PPT设置路径.设置时间.设置动画方式来制作动画特效.那么Impress.js将是你一个非常好的选择. 用它制作的PPT将更加直观.效果也是嗷嗷美观的. 当然,如果用它来装 ...
- Linux系统中的Device Mapper学习
在linux系统中你使用一些命令时(例如nmon.iostat 如下截图所示),有可能会看到一些名字为dm-xx的设备,那么这些设备到底是什么设备呢,跟磁盘有什么关系呢?以前不了解的时候,我也很纳闷. ...
- JS案例之3——倒计时
利用简单的数字累加循环模拟倒计时的效果,逻辑比较简单.如果大牛们有更好的办法欢迎补充. 这种效果经常用于在规定的时间做某件事.比如在1分钟之后重新发送验证码等. 案例演示: 源代码如下: <!D ...