最近的项目中,使用了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. 【疯狂造轮子-iOS】JSON转Model系列之二

    [疯狂造轮子-iOS]JSON转Model系列之二 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇<[疯狂造轮子-iOS]JSON转Model系列之一> ...

  2. 探索ASP.NET MVC5系列之~~~3.视图篇(下)---包含常用表单和暴力解猜防御

    其实任何资料里面的任何知识点都无所谓,都是不重要的,重要的是学习方法,自行摸索的过程(不妥之处欢迎指正) 汇总:http://www.cnblogs.com/dunitian/p/4822808.ht ...

  3. HTML 事件(二) 事件的注册与注销

    本篇主要介绍HTML元素事件的注册.注销的方式. 其他事件文章 1. HTML 事件(一) 事件的介绍 2. HTML 事件(二) 事件的注册与注销 3. HTML 事件(三) 事件流.事件委托 4. ...

  4. HTML5 sessionStorage会话存储

    sessionStorage 是HTML5新增的一个会话存储对象,用于临时保存同一窗口(或标签页)的数据,在关闭窗口或标签页之后将会删除这些数据.本篇主要介绍 sessionStorage(会话存储) ...

  5. Android 6.0 权限知识学习笔记

    最近在项目上因为6.0运行时权限吃了亏,发现之前对运行时权限的理解不足,决定回炉重造,重新学习一下Android Permission. 进入正题: Android权限 在Android系统中,权限分 ...

  6. [C#] C# 知识回顾 - 委托 delegate

    C# 知识回顾 - 委托 delegate [博主]反骨仔 [原文]http://www.cnblogs.com/liqingwen/p/6031892.html 目录 What's 委托 委托的属性 ...

  7. PHP设计模式(三)抽象工厂模式(Abstract Factory For PHP)

    一.什么是抽象工厂模式 抽象工厂模式的用意为:给客户端提供一个接口,可以创建多个产品族中的产品对象 ,而且使用抽象工厂模式还要满足以下条件: 系统中有多个产品族,而系统一次只可能消费其中一族产品. 同 ...

  8. 通过squid 禁止访问/只允许访问指定 网址

    安装 squid yum install squid -y 备份squid.conf cp  squid.conf  squid.conf-list vi  squid.conf 输入: acl de ...

  9. Linux LVM逻辑卷配置过程详解

    许多Linux使用者安装操作系统时都会遇到这样的困境:如何精确评估和分配各个硬盘分区的容量,如果当初评估不准确,一旦系统分区不够用时可能不得不备份.删除相关数据,甚至被迫重新规划分区并重装操作系统,以 ...

  10. BZOJ 3626: [LNOI2014]LCA [树链剖分 离线|主席树]

    3626: [LNOI2014]LCA Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 2050  Solved: 817[Submit][Status ...