
CGO混合编程
1.CGO环境准备
Go 有强烈的 C 背景,除了语法具有继承性外,其设计者以及其设计目标都与C语言有着千丝万缕的联系
在Go与C语言互操作方面,Go更是提供了强大的支持。尤其是在Go中使用C,甚至可以直接在Go源文件中编写C代码.
在如下一些场景中,可能会涉及到Go与C的互操作:
1.提升局部代码性能时,用C替换一些Go代码。C之于Go,好比汇编之于C
2.嫌Go内存GC性能不足,自己手动管理应用内存
3.实现一些库的Go Wrapper。比如Oracle提供的C版本OCI,但Oracle并未提供Go版本的以及连接DB的协议细节,因此只能通过包装C版本OCI版本的方式以提供Go开发者使用
- 前置条件
使用CGO特性,需要安装C/C构建工具链;
在macOS和Linux下安装GCC,使用命令安装:
yum install gcc;在windows下是需要安装MinGW工具 http://www.mingw-w64.org/doku.php;
同时需要保证环境变量CGO_ENABLED被设置为1,这表示CGO是被启用的状态。在本地构建时CGO_ENABLED默认是启用的,当交叉构建时CGO默认是禁止的;
通过 import "C" 语句启用CGO特性;
2.编译流程
源代码 -->预处理器 -->编译器 -->目标代码 -->链接器 -->可执行程序
- 通过gcc查看编译过程
//分步编译 1.预处理: 宏定义展开 头文件展开 条件编译 去掉注释 gcc -E hello.c -o hello.i 2.编译: 检查语法 将源码编成汇编语言 gcc -S hello.i -o hello.s 3.汇编: 将汇编语言变成机器语言(二进制语言) gcc -c hello.s -o hello.o 4.链接: 链接外部库 gcc hello.o -o hello
- windows上编译Linux可执行二进制文件
SET CGO_ENABLED=0 SET GOOS=linux SET GOARCH=amd64 go build main.go //CGO_ENABLED是否使用cgo编译,0为不使用,1为使用,使用cgo进行交叉编译时需要编译机器安装对应的cgo程序. //GOOS目标操作系统标识,windows对应Windows操作系统exe可执行文件,darwin对应Mac可执行文件,linux对应Linux可执行文件,freebsd对应UNIX系统. //GOARCH目标可执行程序系统架构,包括386,amd64,arm. //go build后接所编译程序的入口文件.
- Linux上编译Windows可执行二进制文件
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go
- 在Go语言中查看汇编语言代码
#将Go语言程序显示为汇编语言 go build -gcflags "-N -l" go tool objdump -s 'main.Demo' -s ./Go程序.exe
3.import "C"
如果在Go代码中出现了 import "C"语句则表示使用了CGO特性,紧跟在这行语句前面的注释是一种特殊语法,里面包含的是正常的C语言代码。
当确保CGO启用的情况下,还可以在当前目录中包含C/C++对应的源文件。
package main //以块注释的形式写C语言代码 /* #include <stdio.h> //C语言函数格式 void Print() { printf("x86cloud~"); } */ import "C" func main() { C.Print() }C语言和Go语言混合编程需要安装GCC,同时编译速度相对会变慢
4.CGO语句
可以通过#cgo语句 设置编译阶段和链接阶段的相关参数
编译阶段的参数主要用于定义相关宏和指定头文件检索路径。
链接阶段的参数主要是指定库文件检索路径和要链接的库文件。
CFLAGS部分: -D 部分定义了宏 PORT,值为8080; -I 定义了头文件包含的检索目录。 LDFLAGS部分: -L 指定了链接时库文件检索目录; -l 指定了链接时需要链接libevent库。
- #cgo
// #cgo CFLAGS: -D PORT = 8080 -I./include // #cgo LDFLAGS: -L/usr/local/lib -llibevent // #include <libevent.h> import "C"
5.go和c类型转换
在Go语言中访问C语言的符号时,一般是通过虚拟的“C”包访问,比如C.int对应C语言的int类型。
Go语言中数值类型和C语言数据类型基本上是相似的,以下是对应关系表。
| C语言类型 | CGO类型 | Go语言类型 |
|---|---|---|
| char | C.char | byte |
| singed char | C.schar | int8 |
| unsigned char | C.uchar | uint8 |
| short | C.short | int16 |
| unsigned short | C.short | uint16 |
| int | C.int | int32 |
| unsigned int | C.uint | uint32 |
| long | C.long | int32 |
| unsigned long | C.ulong | uint32 |
| long long int | C.longlong | int64 |
| unsigned long long int | C.ulonglong | uint64 |
| float | C.float | float32 |
| double | C.double | float64 |
| size_t | C.size_t | uint |
如果Go语言在调用C语言函数时需要传递参数或者接受返回值,需要进行类型转换
- CGO类型转换示例代码
package main //以块注释的形式写C语言代码 /* #include <stdio.h> //C语言函数格式 int add(int a, int b) { return a + b; } */ import "C" import "fmt" func main() { a := 10 b := 20 c := C.add(C.int(a), C.int(b)) fmt.Println(c) fmt.Printf("%T\n", c) //转成Go语言整型 fmt.Println(int(c)) }