Go与C交互的详细介绍
1、概念解释
- Cgo是Go语言提供的一个工具,它本身是一个可执行文件,当我们调用go build指令编译项目的时候,Cgo会在需要处理C代码的时候被自动使用
- Cgo依赖Gcc工作
- Cgo本身可以被直接执行,并提供了一系列可选指令选项帮助程序员查找问题
2、使用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"的包中,避免暴露在包外
3、基础类型转换
这个表展示了常见的数据类型在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 |
4、关于字符串的两个特别的方法
可以在序言中声明以下两个特别的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尾部没有"\0", 在C中直接当字符串处理会出错
char* buf = malloc((count + 1) * sizeof(char)); //
memset(buf, 0, count + 1);
memcpy(buf, s, count);
printf("%s\n", buf);
printf("sizeof goString: %ld\n", count);
free(buf);
}
*/
import "C"
func main() {
str := "hello world"
C.PrintGoStringInfo(C._GoStringPtr(str), C._GoStringLen(str))
}
- 需要注意的是
_GoStringPtr
返回的char*
尾部是不包含的\0的在C中直接当字符串处理会出错 - 这两个函数仅可在序言中使用,不能在其他的C文件中使用,C代码绝不能通过
_GoStringPtr
返回的指针修改其指向的内容
注意:这两个函数可以很方便的将Go string
转换为C的char*
,如果使用_GoStringPtr
传入一个临时的string
到C中,在C中应拷贝一份副本到C内存中,尤其是在一些异步调用的过程中,从官方关于cgo的文档看来,这两个函数似乎并不保证传入的临时Go string
类型不会被gc回收
5、结构体
- C中定义的结构体字段名有可能和Go中的关键字冲突,这些发生冲突的字段会被自动加上下划线作为前缀,访问这种字段的时候要用这样的形式:
x._type
- 在C中的一些字段无法在Go中表达,如位域和未对其的结构,在Go的结构体中,这些字段会被忽略,但会在下一个字段之前或者结构体的结尾之前留下相应的空白空间
需要给结构体中的数组进行赋值可以用以下方法
/*
typedef struct {
int a[32];
} STRU_A
// 须包含这两个库文件
#include <stdlib.h>
#include <stringlh>
*/
import "C"
name = "abcd"
struA := C.STRU_A{}
cName = C.CString(name)
defer C.free(unsafe.Pointer(cName))
C.memcpy(unsafe.Pointer(&struA.cName), unsafe.Pointer(cName), C.size_t(len(name)))
6、类型转换方法
// Go string to C string
// The C string is allocated in the C heap using malloc.
// It is the caller's responsibility to arrange for it to be
// freed, such as by calling C.free (be sure to include stdlib.h 确保包含这个库
// if C.free is needed).
// 这个方法会在C的堆上分配内存,需要使用C.free释放,需要包含stdlib.h
func C.CString(string) *C.char
// Go []byte slice to C array
// The C array is allocated in the C heap using malloc.
// It is the caller's responsibility to arrange for it to be
// freed, such as by calling C.free (be sure to include stdlib.h
// if C.free is needed).
// 这里同样需要使用C.free释放内存
func C.CBytes([]byte) unsafe.Pointer
// C string to Go string
func C.GoString(*C.char) string
// C data with explicit length to Go string
func C.GoStringN(*C.char, C.int) string
// C data with explicit length to Go []byte
func C.GoBytes(unsafe.Pointer, C.int) []byte
C.malloc
并不是直接调用C中的malloc
,而是调用了一个包装了一个Go
的辅助函数,其包装了C库中的malloc
,并保证永远不会返回nil
。如果C的malloc
表示用尽内存,这个辅助函数就会使程序崩溃,就像Go自身用尽内存发生崩溃,因为C的malloc
不能失败,所以他没有返回errno的两个结果的形式
c中的
sizeof
并不能以C.sizeof
的形式使用,而是应该用C.size_T
的形式使用,T是C中的类型名
6、函数指针和回调
1、go调用C的函数指针
go不能直接调用C的函数指针,但可以调用C的函数,也可以持有C的函数指针,如果go想调用一个C的函数指针,可以将C的指针传入go中,go再将这个指针通过一个C接口送到C侧,然后由C侧执行并返回结果
package main
// typedef int (*intFunc) ();
//
// int
// bridge_int_func(intFunc f)
// {
// return f();
// }
//
// int fortytwo()
// {
// return 42;
// }
import "C"
import "fmt"
func main() {
f := C.intFunc(C.fortytwo)
fmt.Println(int(C.bridge_int_func(f)))
// Output: 42
}
2、C回调go的函数
C可以调用go中被//export标记的导出的函数
需要在C序言中声明
C文件
typedef void(*cbtype)();
void registerCallback(cbtype cb);
go文件
/*
// 在序言中声明一次,这是为了让cgo能够 “看到” 这个C函数,否则无法通过编译
void callbackFunc();
*/
import "C"
func foo() {
C.registerCallback(C.cbtype(C.callbackFunc))
}
//export callbackFunc
func callbackFunc() {
fmt.Println("go callback func")
}
7、关于C数据作为参数
在C中,向函数传入一个固定大小的数组作为参数,需要一个指向数组第一个元素的指针。C编译器知到这样的调用约定,并相应的调整调用方式。但在Go中并非如此,你必须明确的传入指向数组第一个元素的指针C.f(&C.x[0])
译者注:这里指的是C中数组名传入函数后变为指向首元素的指针
8、可变参数
调用可变参C函数是不被支持的,但可以通过使用C函数包装的方法来规避这个问题,如下
package main
// #include <stdio.h>
// #include <stdlib.h>
//
// static void myprint(char* s) {
// printf("%s\n", s);
// }
import "C"
import "unsafe"
func main() {
cs := C.CString("Hello from stdio")
C.myprint(cs)
C.free(unsafe.Pointer(cs))
}
9、C引用Go
Go方法可以按照以下方法导出给C代码使用
//export MyFunction
func MyFunction(arg1, arg2 int, arg3 string) int64 {...}
//export MyFunction2
func MyFunction2(arg1, arg2 int, arg3 string) (int64, *C.char) {...}
他们带C代码中以如下形式使用
extern GoInt64 MyFunction(int arg1, int arg2, GoString arg3);
extern struct MyFunction2_return MyFunction2(int arg1, int arg2, GoString arg3);
在生成的 _cogo_export.h
头文件,在序言以及所有拷贝自cgo导入的文件内容之后。有多返回值的函数会被映射为返回一个结构体
并不是所有的go类型都可以被映射到C类型,Go的 struct
不被支持;使用C的Struct
,Go的array
类型不被支持,使用C的指针
可以使用C类型 GoString
调用一个需要传入go字符串的go函数,如上文所述。GoString
类型会在序言中被自动定义, 注意,C类型无法创建这种类型的值,这种方式只有在从Go向C传递string值,和返回Go中时有用
10、动态库和静态库
cgo只能引入纯c语法的头文件,C的头文件中只能有C语言的语法,不能出现C++特性的语法,比如重载,默认参数等,在C侧的接口实现如果使用C++写,需要使用extern "C"
将要实现的接口声明一次,这样可以告诉编译器不要将接口按照C++规则进行符号修饰。
cgo:https://blog.csdn.net/chidan4846/article/details/100641147?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162219653316780271545983%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=162219653316780271545983&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_v2~rank_v29-3-100641147.pc_search_result_control_group&utm_term=_GoStringPtr&spm=1018.2226.3001.4187