最近的项目中,使用了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的坑的更多相关文章

  1. Windows平台Go调用DLL的坑(居然有这么多没听过的名词)

    最近的项目中,使用了GO来开发一些服务中转程序.业务比较简单,但是有一些业务需要复用原有C++开发的代码.而在WINDOWS,用CGO方式来集成C/C++代码并不是太方便.所以用DLL把C++的代码封 ...

  2. C++利用模板在Windows上快速调用DLL函数

    更新日志 --------- 2021/08/01 更新V2.2 增加 GetHmodule 函数 - 允许用户获取HMODULE以验证加载DLL是否成功. 2021/08/03 更新V2.3 增加 ...

  3. windows应用中调用DLL一步步试验

    试验环境: PC:win10 build 10143 IDE: vs2015 RC WinPhone: win10 build 10136 简单界面,点按钮,算加法 一.主程用C++ 1.新建visu ...

  4. c#调用c++ dll 入坑记录

    1.DLL引用坑 [DllImport("NetDLL.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConve ...

  5. 在Windows中实现Java调用DLL(转载)

    本文提供调用本地 C 代码的 Java 代码示例,包括传递和返回某些常用的数据类型.本地方法包含在特定于平台的可执行文件中.就本文中的示例而言,本地方法包含在 Windows 32 位动态链接库 (D ...

  6. Windows下C语言调用dll动态链接库

    dll是windows下的动态链接库文件,下面记录一下在windows下如何调用C语言开发的dll动态链接库. 1.dll动态链接库的源代码 hello_dll.c #include "st ...

  7. war包部署在tomcat下,使用windows service服务方式启动tomcat服务器,在包含调用dll的模块,报dll找不到问题的解决办法

    问题描述: 开发了一个需要调用dll的java web程序,在idea开发环境下运行调试没问题,可以正常运行,在tomcat/bin下,运行批处理startup.bat,启动tomcat服务器,也可以 ...

  8. C/C++:Windows编程—调用DLL程序的2种方法(转载)

    文章为转载,原文出处https://blog.csdn.net/qq_29542611/article/details/86618902 前言先简单介绍下DLL.DLL:Dynamic Link Li ...

  9. Windows平台开发Mapreduce程序远程调用运行在Hadoop集群—Yarn调度引擎异常

    共享原因:虽然用一篇博文写问题感觉有点奢侈,但是搜索百度,相关文章太少了,苦苦探寻日志才找到解决方案. 遇到问题:在windows平台上开发的mapreduce程序,运行迟迟没有结果. Mapredu ...

随机推荐

  1. ExtJS 4.2 评分组件

    上一文章是扩展ExtJS自带的Date组件.在这里将创建一个评分组件. 目录 1. 介绍 2. 示例 3. 资源下载 1. 介绍 代码参考的是 Sencha Touch 2上的一个RatingStar ...

  2. JS调用Android、Ios原生控件

    在上一篇博客中已经和大家聊了,关于JS与Android.Ios原生控件之间相互通信的详细代码实现,今天我们一起聊一下JS调用Android.Ios通信的相同点和不同点,以便帮助我们在进行混合式开发时, ...

  3. C#给PDF文档添加文本和图片页眉

    页眉常用于显示文档的附加信息,我们可以在页眉中插入文本或者图形,例如,页码.日期.公司徽标.文档标题.文件名或作者名等等.那么我们如何以编程的方式添加页眉呢?今天,这篇文章向大家分享如何使用了免费组件 ...

  4. html5 canvas常用api总结(三)--图像变换API

    canvas的图像变换api,可以帮助我们更加方便的绘画出一些酷炫的效果,也可以用来制作动画.接下来将总结一下canvas的变换方法,文末有一个例子来更加深刻的了解和利用这几个api. 1.画布旋转a ...

  5. .NET Core的日志[5]:利用TraceSource写日志

    从微软推出第一个版本的.NET Framework的时候,就在“System.Diagnostics”命名空间中提供了Debug和Trace两个类帮助我们完成针对调试和跟踪信息的日志记录.在.NET ...

  6. JavaScript实现常用的排序算法

    ▓▓▓▓▓▓ 大致介绍 由于最近要考试复习,所以学习js的时间少了 -_-||,考试完还会继续的努力学习,这次用原生的JavaScript实现以前学习的常用的排序算法,有冒泡排序.快速排序.直接插入排 ...

  7. Linux 常用命令(持续补充)

    常用命令: command &:将进程放在后台执行 ctrl + z:暂停当前进程 并放入后台 jobs:查看当前后台任务 bg( %id):将任务转为后台执行 fg( %id):将任务调回前 ...

  8. CSS知识总结(七)

    CSS常用样式 5.背景样式 1)背景颜色 background-color : transparent | color 常用值:①英文单词,②十六进制,③RGB或RGBA 另外,还有一种是 渐变色彩 ...

  9. ThinkPHP 模板substr的截取字符串函数

    ThinkPHP 模板substr的截取字符串函数在Common/function.php加上以下代码 /** ** 截取中文字符串 **/ function msubstr($str, $start ...

  10. BPM任务管理解决方案分享

    一.方案概述任务是企业管理者很多意志的直接体现,对于非常规性事务较多的企业,经常存在各类公司下达的各种任务跟进难.监控难等问题,任务不是完成效果不理解,就是时间超期,甚至很多公司管理层下达的任务都不了 ...