如果go代码里使用了cgo,那么编译的时候电脑上必须按照gcc才能正确编译
这篇文章展示了如何用c写一个包让go调用并且不需要使用cgo
A 需要注意的:
1. C代码天生的不安全
2. Go1的兼容性保证不包括C代码
3. C函数不能inline
4. 逃逸分析不能跟踪传递到C函数里的value (逃逸分析:http://kenwublog.com/jvm-optimization-escape-analysis)
5. C编译器(5c,6c,8c)没有对应的Go编译器优化做的好,因此生成的代码也许没有Go代码优化的那么好
6. C代码是plan9风格的,与C89较相似
B 返回的方式
第一个例子是一个简单的返回true的函数:True
void ·True(bool res) { res = true; FLUSH(&res); }
简要的分析 函数各部分:
1. 返回值:
此函数True的签名是 void ·True(bool res),C与Go的互操作是通过堆栈上传递参数实现的,所以C的返回值要求是void
2符号 ·
·是Go的包命名系统的一部分,在C函数名前面加· 使得这个函数就属于当前这个包。可以在·前面加包的名字来指定这个函数的归属,但是当你的包的命名系统很复杂的时候这将变得很复杂,这个不在这篇文章的讨论范围内
3.参数返回
Go调用C的时候,参数和返回都是通过参数的方式传递给被调用的C函数
这个例子的函数体里,给res赋值true或者false很直接明了,但是最后一行的FLUSH(&res)需要解释下:因为在这个函数内部没有使用res,编译器可能将这个赋值给优化掉(赋值语句会被删掉),FLUSH用来确保res能够正确的写回到堆栈上去。
C. 前向声明
为了在Go代码里能够正确的识别我们的True函数,我们需要写一个前向声明。如果没有前向声明,Go编译器不知道这个函数的存在。这个与首字母大小写使得函数在包外面是否可见没有关联
前向声明方式如下:
// True always returns true. func True() bool前向声明包含如下信息:函数True没有参数并且返回一个bool类型的值。因为这是个普通的Go函数,你可以在前面添加注释,这样可以用godoc显示出来(C函数里的注释不会被godoc显示)
D. 向C代码传递参数
下面将给出一个函数Max,他将返回两个int参数中最大的那个
void ·Max(intptr a, intptr b, intptr res) { res = a > b ? a : b; FLUSH(&res); }Max和前面的True函数相似,前面两个参数是函数参数,最后一个参数是返回值。函数的返回值并不是一定要命名为res,只是标准库里一个常用的用法而已
a和b是intptr类型的,这个是c里等价于Go里的平台相关的int类型(x86:int32 x64:int64)
Max的前向声明如下,可以通过这个例子看看Go的参数和返回值与C里的参数的对应关系
// Max returns the maximum of two integers. func Max(a, b int) int
E. 传递地址
前面两个例子,我们传递两个参数给函数并且把拷贝通过堆栈返回。Go的函数调用是值传递,调用C函数也是如此
最后一个例子我们将传递某个变量的指针给C函数,并通过指针改变这个值
void ·Inc(intptr* addr) { *addr+=1; USED(addr); }Inc函数得到了a的指针,解引用并且+1,USE宏和FLUSH作用类似
前向声明如下:
// Inc increments the value of the integer add address p. func Inc(p *int)
F. 所有代码
测试代码和测试结果如下:
ccode.c
#include "runtime.h" // required for go types like bool and intptr // The interpunkt indicates that the function belongs to the current // namespace. Without the interpunkt, the function would be defined in // the global C namespace, inaccessible to your Go package and // potentially conflicting with another C function. void ·True(bool res) { res = true; FLUSH(&res); } // intptr is equivalent to Go's arch specific int type void ·Max(intptr a, intptr b, intptr res) { res = a > b ? a : b; FLUSH(&res); } void ·Inc(intptr* addr) { *addr+=1; USED(addr); }
ccode.go
package ccode // True always returns true. func True() bool // Max returns the maximum of two integers. func Max(a, b int) int // Inc increments the value of the integer add address p. func Inc(p *int)
ccode_test.go
package ccode import "testing" func TestTrue(t *testing.T) { if !True() { t.Fatalf("True(): expected %v got %v", true, True()) } } func TestMax(t *testing.T) { a, b := 42, -6 if Max(a, b) != a { t.Fatalf("Max(%v, %v): expected %v, got %v", a, b, a, Max(a, b)) } } func TestInc(t *testing.T) { v := 9000 if Inc(&v); v < 9001 { t.Fatalf("Inc(9000): expected 9001, got %v", v) } }
执行结果:
> go test -v ccode === RUN TestTrue --- PASS: TestTrue (0.00 seconds) === RUN TestMax --- PASS: TestMax (0.00 seconds) === RUN TestInc --- PASS: TestInc (0.00 seconds) PASS ok ccode 0.338s
总结
这篇文章简要的讲解了怎么用C来写Go的包,适当的使用对某些情况比较方便或者在使用汇编语言的时候非常重要
测试工程打包下载:
http://download.csdn.net/detail/varding/6394401
注:添加了package main,不仅有测试方法还有使用方法
可能有些朋友对golang了解的还不是很透彻,编译什么的还有些疑问,所以我把这个工程打包传上来了,希望大家能一次测试通过
同时也对go build , go test ,go install之类的使用有个整体的了解
下载包里的env.cmd是我日常使用的,这个能添加当前包的路径为gopath,方便go build之类的命令,可以放到其他工程根目录下直接使用,很方便,建议收藏
使用说明:
1.双击env.cmd,以下命令都是在弹出的窗口里输入的 2.测试:go test -v ccode 输出: === RUN TestTrue --- PASS: TestTrue (0.00 seconds) === RUN TestMax --- PASS: TestMax (0.00 seconds) === RUN TestInc --- PASS: TestInc (0.00 seconds) PASS ok ccode 0.255s 3.安装ccode库:go install ccode 4.编译:go build main 5.运行:main.exe
有疑问加站长微信联系(非本文作者)