使⽤cgo的程序如何交叉编译
背景
最近在研究读取配置相关的开源项⽬–libconfig。libconfig项⽬⽬前仅⽀持c版本和c++版本。我们当前项⽬是⽤golang编写的,于是就想通过cgo的⽅式调⽤libconfig⾥⾯的功能。刚开始⼀切顺利,分别编译libconfig的windows版本库和linux版本库;分别在windows环境和linux环境下写DEMO程序,这些都没有任何问题。
问题出现
由于,我们开发环境在windows系统下,⽽线上环境是linux。为了发布⽅便,我们采⽤了交叉编译。现在通过cgo⽅式引⼊libconfig后,问题来了!通过以下脚本编译失败了!
SET GO111MODULE=off
SET CGO_ENABLED=1
SET GOOS=linux
SET GOARCH=amd64
go build -a -v -
报错截图如下:
红⾊框内,就是报错具体类容了。最开始,⼀直怀疑是minGW版本问题,换了⼏个版本,还是同样的报错。后来⼜想可能是环境变量问题,于是修改脚本如下:
SET GO111MODULE=off
SET CGO_ENABLED=1
set GOHOSTOS=linux
SET CGO_CFLAGS=-Wall
set CGO_LDFLAGS=-static
set CGO_CXXFLAGS=-Wall
set CGO_FFLAGS=-Wall
SET GOOS=linux
SET GOARCH=amd64
set GOGCCFLAGS=-m64 -mthreads -fmessage-length=0
go build -a -v -
报错情况依然。。。 在⽹上搜索了很多相关帖⼦,要么没有结贴,要么没有说清楚⼀个所以然。值到我看到了⼀篇⽂章《cgo is not go》,这篇⽂章从多个⽅⾯分析了cgo的优缺点。其中⼀点,就明确指出Cross compilation goes out the window(交叉编译不⽀持)。看到这⾥后,我终于放弃了挣扎!
cgo is not go
借⽤ JWZ 的⼀句话
有些⼈,当他们⾯临⼀个问题时,认为“我知道,我会使⽤ cgo ”。那么现在,他们有了两个问题。
最近有⼈在 Gopher 的 Slack Channel 上使⽤ cgo,对此我感到⼗分担⼼,尤其是竟然有个组织内部打算⽤⼀个项⽬来展⽰ Go,那真是⼀个坏主意。对此,我曾说过很多次了,因此也许你们讨厌了我的游说,所以我想到了把它写下来并且去做。
cgo 是⼀个令⼈惊异的技术,它允许 Go 程序与 C 的类库交互操作。那是⼀个极其有⽤的特征,今天它达到了⼀个 Go 所⽆法企及的地位。cgo 是让 Go 程序在 Android 和 iOS 上运⾏的关键。
然⽽,这只是我的个⼈意见,我不为任何⼈说话,我认为 cgo 在 Go 项⽬中被过度使⽤了。我相信当⾯临需要重载⼀⼤段⽤ Go 编写的 C 代码时,程序员会更愿意选择⽤ cgo 去打包库⽽⾮ Go,因为他们认为那样更容易解决问题。我认为那是虚假经济。
显⽽易见的,cgo 也存在⼀些不可避免的问题,最明显的⼀个问题是作为⼀个⼆进制 blog,你不得不与显卡驱动或者窗⼝系统进⾏交互。但是使⽤ cgo 所存在的问题,经过权衡后,⼤部分⼈认为还是⽐较少的。
当你在 Go 项⽬上建⽴⼀个 cgo 库的时候,你可能没有意识到你的这种权衡其实是不完整的。
终⽇乾乾
终⽇乾乾
翻译于 2016/01/21 17:19
go语言开发环境搭建
2
构建时间变长
当你在包中引⽤ import “C”,go build 就会做很多额外的⼯作来构建你的代码,构建就不会仅仅是向 go tool compile 传递⼀堆 .go ⽂件了,取⽽代之的是:
cgo ⼯具就会被调⽤,在 C 转换 Go、Go 转换C的之间⽣成各种⽂件。
你系统的 C 编译器会被调⽤来处理你包中所有的C⽂件。
所有独⽴的编译单元会被组合到⼀个 .o ⽂件。
⽣成的 .o ⽂件会在系统的连接器中对它的引⽤进⾏⼀次检查修复。
当你针对这个包编程的时候,所有上⾯的⼯作在你编译或者测试的过程中都会进⾏。Go ⼯具在可能的情况下,会并⾏的处理这些⼯作,但是这个包的编译时间会随着所有 C 代码的构建⽽增加。
你可以将这部分 C 代码重构出这个包来解决,但是如果你不⽤ cgo,⾃然也就不会遇到这个问题。
对了,你还必须调试 C 代码在不同平台的兼容性问题。
drkaka
drkaka
翻译于 2016/01/21 09:08
复杂的构造
Go的⽬标之⼀是⽣产⼀种语⾔,它的构造过程就是它⾃我描述的过程;你程序的资源包含了⾜够的信息来使⽤⼀个⼯具去创建这个项⽬。这不是说⽤ Makefile 来⾃动化构建⼯程是不好的,但是在cgo被引进项⽬之前,你创建并且测试不需要任何东西,除了go⼯具。后来cgo 被引⼊,设置所有的环境变量、跟踪那些可能被安装在奇怪地⽅的共享对象和头⽂件,现在这些你都需要做。
记住,Go⽀持那些不装载开箱即⽤的平台,所以你不得不花⼀些时间去提出⼀个Windows⽤户的解决⽅案。
还有,现在你的⽤户还要安装C编译器,⽽不是只安装Go编译器。他们同时还需要安装你项⽬所依赖的C库,所以你同时还要承担这些⽀持的成本。
终⽇乾乾
终⽇乾乾
翻译于 2016/01/26 16:47
1
交叉编译不⽀持
Go对于交叉编译的⽀持是最好的。根据Go1.5交叉编译,你可以从任何⽀持的平台到其他任何平台,通过Go项⽬⽹站上的官⽅许可安装程序。
在默认情况下cgo是不允许交叉编译的。 通常这不构成问题,如果你的项⽬是纯Go的。 当你混合了C库的依赖, 你或者不得不放弃交叉编译你的产品,或者不得不花时间在寻并且维护C的交叉编译⼯具链来达成你的⽬标。
或许你所做的产品仅通过TCP与客户端通信,并且你计划让它在SaaS(软件服务化 Software as a Service)的模型上运⾏,那么你根本不关⼼交叉编译就是合理的。然⽽,如果你在做⼀个其他⼈会⽤的产品,可能会整合到他们的产品中去,或许那是⼀个监控解决⽅案,或许那是⼀个你SaaS服务的客户端,那么你就把他们很容易交叉编译的特性给锁死了。
Go所⽀持的平台在持续地增长。Go 1.5增加了64位ARM和PowerPC的⽀持。Go 1.6增加了64位MIPS的⽀持, IBM的s390体系结构则在Go 1.7. RISC-V中提供。如果你的产品依赖C库,不仅你要⾯临以上描述的所有交叉编译问题,你还要确保你所依赖的C代码在Go所⽀持的新平台上能可靠运⾏——你必须要做这些运⽤C/Go混合提供的有限的调试。这⼜引出了下⾯的问题。
终⽇乾乾
终⽇乾乾
翻译于 2016/01/27 14:42
1
你失去了通向你所有⼯具的⼊⼝
Go有⼀些伟⼤的⼯具;有竞态分析、性能分析、覆盖率、模糊测试和其他源代码分析⼯具。这些中没有任何⼀个能够跨越cgo⾎液或是cgo ⼤脑的屏障。
相反地,像Valgrind这样的优秀⼯具不理解Go的调⽤约定和堆栈布局。在这⼀点上,Ian Lance Taylor的在Go 1.6中将C的内存清理和空指针调试相结合的⼯作对于cgo的⽤户来说将会⾮常有益。
梳理Go的代码和C的代码结果到了两个世界的交汇处,不是结盟;C的内存安全,Go程序的可调试性。
性能永远是⼀个问题
C代码和Go代码⽣存在两个不同的宇宙中,cgo横贯了他们两个之间的分界线。这个过渡并不是⾃由的,它取决于它在你代码中的位置,花费可能是⽆⾜轻重的,也可能是巨额的。
终⽇乾乾
终⽇乾乾
翻译于 2016/01/29 10:19
1
其它翻译版本 (1)
C 并不了解 Go 的调⽤协定或堆栈,所以 Go 语⾔调⽤到 C 代码时必须⾸先记录 Go 函数⼊⼝堆栈的所有细节,然后切换到 C 的堆栈,运⾏ C 代码,这部分 C 代码并不知道它如何被调⽤,更不知道外部的 Go 语⾔运⾏时环境。
公平地说,Go 也不知道任何关于 C 的情况。这就是为什么随着编译器和垃圾收集器在定位⽆⽤栈帧和堆⽅⾯的发展,Go 和 C 两者之间传递数据的规则变得越来越复杂。
如果在 C 的运⾏过程中出现⼀个错误,Go ⾄少得能做到打印错误堆栈信息,然后退出程序,⽽不是把核⼼⽂件都暴露出来。
管理这种双⽅互相调⽤的堆栈,再加上在信号、线程和回调,真的很是不容易的。Ian Lance Taylor 在1.6版本的 Go 语⾔中做了⼤量的⼯作来改进与 C 语⾔信号处理⽅⾯的互操作性。
这⾥要说的是 C 与 Go 语⾔之间的的互相调⽤是⽐较繁琐的,⽽且永远是会有性能开销的。
雪落⽆痕xdj
翻译于 2016/01/28 21:43
其它翻译版本 (1)
C 是主导,⽽不是你的代码
你⽤那种语⾔捆绑或包装C代码都没关系;Python、有JNI的Java、⼀些使⽤了libFFI的语⾔,或者是有cgo的Go;这是C的世界,你只是在其上⽣存。
Go代码和C代码必须在资源共享⽅式上取得⼀致,如地址空间、信号处理和线程调度——⽽我所说的⼀致,是Go要围绕着C的假设。 C代码可以假设它⼀直在⼀个线程上运⾏,或者是⽆顾虑地在没有任何准备⼯作的情况下直接运⾏在有许多线程的环境中。
你不是在写⼀个Go程序使⽤C库中的⼀些逻辑,取⽽代之的是你在写⼀个Go程序,它必须与⼀段容易冲突的、很难被取代的、很难协商的、不顾虑你的问题的C代码共存。
终⽇乾乾
终⽇乾乾
翻译于 2016/01/28 13:57
1
部署变得更复杂
对于普通读者来说,任何关于Go的描述都要包含⾄少以下词汇中的⼀点:
简单,静态⼆进制
这是Go的法宝,它引领Go成为了远离虚拟机和运⾏管理的典型代表。使⽤cgo,你就要放弃这些。
根据你的环境,把你的Go项⽬打成⼀个deb或rpm包,并且假设你其他的依赖也包装好了,把它们加进安装依赖,然后把问题从操作系统的包管理中抛出,这也许是可⾏的。但是这⼏个构建和部署程序的重要改变就像 go build && scp⼀样仓促直⽩。
编译⼀个完全静态的Go程序是可⾏的,但是如果你的项⽬中包含了cgo那将是绝不简单的,其后果将影响⼀整个构建和部署的⽣命周期。
请明智地选择
要明确,我不是说你不应该使⽤cgo。但是在你做这笔交易之前,请仔细考虑你同时要放弃的Go的优点。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。