概述
Cgo,详细介绍Go与C交互详细过程
文章目录
- Cgo,详细介绍Go与C交互详细过程
- 概念解释
- 使用Cgo在Go中直接编写C代码
- Cgo工作的基本过程
- 基础类型转换
- 关于字符串的两个特别的方法
- 结构体
- 类型转换方法
- 函数指针和回调
- go调用C的函数指针
- C回调go的函数
- 关于C数据作为参数
- 可变参数
- C引用Go
- 动态库和静态库
概念解释
- Cgo是Go语言提供的一个工具,它本身是一个可执行文件,当我们调用go build指令编译项目的时候,Cgo会在需要处理C代码的时候被自动使用
- Cgo依赖Gcc工作
- Cgo本身可以被直接执行,并提供了一系列可选指令选项帮助程序员查找问题
使用Cgo在Go中直接编写C代码
package main
/*
#include <stdio.h>
void PrintHello()
{
printf("hello world")
}
*/
import "C"
func main() {
C.PrintHello()
}
上面这段代码通过调用C标准库中的printf函数向标准输出输出hello world字符串
/*
#include <stdio.h>
void PrintHello()
{
printf("hello world")
}
*/
- 这段被注释的内容被称之为 “序言”,或是"序文"(preamble),可以在序言中直接编写任意的C代码,或引入标准库的头文件,或是要使用的库文件的头文件
- import "C"其中的C并不是一个真正的go包,称为伪包,用来帮助Cgo识别C代码,需要注意的是在序文结束的后的 import “C” 必须紧跟在序言后面,不能有空行,否则会编译出错
- 序言中声明的C函数在Go中进行调用的时候要用C.xxx的形式,所有引入的C函数,变量,以及类型,在使用的时候都要以大写的C.作为前缀
- 所有的C类型都应该局限在使用了 import "C"的包中,避免暴露在包外
Cgo工作的基本过程
对上面的helloworld示例程序执行 go tool cgo main.go
会看到Cgo生成的一系列中间文件
打开main.cgo2.c可以找到上面的代码片段,首先cgo把我们定义在序言中的c代码完整的搬运到了这里,包括引入的头文件和我们定义的PrintHello函数
在文件的末尾,cgo为我们生成了新的函数 _cgo_bd85ba2d6721_Cfunc_PrintHello 并在这个函数中调用我们之前在序言中定义的PrintHello函数
打开_cgo_gotypes.go文件,这个文件属于main包,所以其中定义函数可以被 go 的 main 函数调用,可以看到在第32行,编译制导语句go:cgo_import_static 将上文中提到的 cgo 生成的c函数 _cgo_bd85ba2d6721_Cfunc_PrintHello 引入并和字节型变量__cgofn__cgo_bd85ba2d6721_Cfunc_PrintHello 对齐,并用一个新的变量 _cgo_bd85ba2d6721_Cfunc_PrintHello 保存这个字节型变量的地址,从而实现在Go中拿到C函数的地址,在37行定义了一个新的go函数 func _Cfunc_PrintHello,并在其中调用了之前通过字节变量拿到的C函数
再对 main.cgo1.go 文件进行考察,这是被cgo改写后的go文件,这个被改写的go文件是最终交给go编译器的文件,可以看出 import “C” 语句已经被移除,并引入了 unsafe 包, 在main函数中调用了在上文中提到的 _Cfunc_PrintHello 函数
总结一下:
针对我们这个例子,cgo首先扫描 import “C” 的文件,并将序言部分的C代码拷贝到一个C文件中,这个C文件应该会被gcc编译成 .o 等待后续链接操作,使用 nm _cgo.o 指令,可以看到其中有 PrintHello 函数的定义可以证实我们的猜测,cgo同时会生成一个中间的Go文件,使用编译制导指令在链接期获取C函数的地址,并使用一个新的go函数包装对C函数的调用,cgo会生成一个新的main文件,这个文件是真正被Go编译器处理的main文件,并将其中 import “C” 语句移除,并将以 C.xxx 形式调用的C函数替换为cgo自己生成的Go函数
基础类型转换
这个表展示了常见的数据类型在C和Go中名称
Go name | C name |
---|---|
go name | c name |
C.char, C.schar | signed char |
C.uchar | unsigned char |
C.short, C.ushort | unsigned short |
C.int, C.uint | unsigned int |
C.long,C.ulong | unsigned long |
C.longlong | long long |
C.ulonglong | unsigned long long |
C.float, C.double, C.complexfloat | complex float |
C.complexdouble | omplex double |
unsafe.Pointer | void* |
__int128_t and __uint128_t | [16]byte |
C.struct_xxx | struct |
C.union_xxx | union |
关于字符串的两个特别的方法
可以在序言中声明以下两个特别的C方法
size_t _GoStringLen(_GoString_ s);
const char *_GoStringPtr(_GoString_ s);
他们的参数类型是_GoString_ s,第一个方法返回Go字符串的长度,第二个方法返回指向这个字符串的char*指针,下面为示例代码
package main
/*
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// 这两个函数要声明在序言中
size_t _GoStringLen(_GoString_ s);
const char *_GoStringPtr(_GoString_ s);
void PrintGoStringInfo(char* s, size_t count)
{
// 注意,s尾部没有"