cgo使用示例总结
1. go直接调用写在本文件的c函数
- 需要import "C", 目的是让go的编译器识别并提取出C代码, 做处理后才能真正让go代码直接调用c的函数
- import "C" 和 上面的C代码之间不能有空行 这是语法规则
- c 的 plus 函数返回的类型在go里并不是 int 而是 _Ctype_int
// main.go
package main
// int plus(int a, int b) {
// return a + b;
// }
import "C"
import (
"fmt"
)
func main() {
a := 1
b := 2
sum := C.plus(C.int(a), C.int(b))
fmt.Printf("%d + %d = %d\n", a, b, sum)
}
2. 将c的代码抽到纯c文件(包括文件头)
- 如果.h和.c文件和main.go文件在同一个目录下, 只能go build而go run会报错, 具体原因应该和编译器有关, 所以这里用了go mod
go mod init demo
- 将c代码移至test.h和test.c 编译的时候无需指定其他参数 go编译器会自动寻找同目录下.h的同名.c文件
- 一般来说包对外仅提供go函数, 对c函数的调用封装在test.go里面
目录结构
+ demo
- main.go
- go.mod
-+ c_code
-- test.h
-- test.c
-- test.go
代码
// c_code/test.h
#ifndef TEST_H
#define TEST_H
int plus(int a, int b);
#endif
// c_code/test.c
#include "test.h"
int plus(int a, int b) {
return a + b;
}
// c_code/test.go
package c_code
/*
#include "test.h"
*/
import "C"
func Plus(v1 int, v2 int) int {
return int(C.plus(C.int(v1), C.int(v2)))
}
// main.go
package main
import (
"demo/c_code"
"fmt"
)
func main() {
a := 1
b := 2
sum := c_code.Plus(a, b)
fmt.Printf("%d + %d = %d\n", a, b, sum)
}
3. 把基本类型改为struct
- malloc函数既可以在c里面调用, 也可以在go里调用 根据场景使用
- c里的sizeof()用法在 go 里 不是像函数那样调用而是下划线(毕竟在c里这不是一个真正的函数而是关键字)
- c的struct指针在go里都是unsafe.Pointer类型
// test.h
#ifndef TEST_H
#define TEST_H
struct number {
int value;
};
void init_number(struct number *n, int value);
int plus(struct number *a, struct number *b);
#endif
// test.c
#include "test.h"
void init_number(struct number *n, int value) {
n->value = value;
}
int plus(struct number *a, struct number *b) {
return a->value + b->value;
}
package c_code
/*
#include "test.h"
#include <stdlib.h>
*/
import "C"
import (
"unsafe"
)
func Plus(v1 int, v2 int) int {
var a, b unsafe.Pointer
a = C.malloc(C.sizeof_struct_number)
b = C.malloc(C.sizeof_struct_number)
C.init_number((*C.struct_number)(a), C.int(v1))
C.init_number((*C.struct_number)(b), C.int(v2))
C.free(a)
C.free(b)
return int(C.plus((*C.struct_number)(a), (*C.struct_number)(b)))
}
main函数无需修改
4. go调用c, c再调用go(回调函数场景)
- 这里的示例注册直接=调用
- 注意 //export 中 // 和 export中间不能有空格
// c_code/test.h
#ifndef TEST_H
#define TEST_H
typedef void (*callback) (void *);
void register_callback(callback cb, void *go_func);
#endif
// c_code/test.c
#include "test.h"
void register_callback(callback cb, void *go_func) {
cb(go_func);
}
// c_code/test.go
package c_code
/*
#include "test.h"
void go_callback_proxy();
*/
import "C"
import (
"unsafe"
)
//export go_callback_proxy
func go_callback_proxy(p unsafe.Pointer) {
f := (*func())(p)
(*f)()
}
func RegisterCallback(f *func()) {
C.register_callback(C.callback(C.go_callback_proxy), unsafe.Pointer(f))
}
// main.go
package main
import (
"demo/c_code"
"fmt"
)
func main() {
callback := func() {
fmt.Println("Call function in go")
}
c_code.RegisterCallback(&callback)
}
5. 场景4 但是回调函数是go struct的方法
只需要将参数从函数指针修改为go struct的指针, 然后go_callback_proxy()函数 做相应修改即可
注意
传入c的 golang的struct指针 如果其指有指向其他go数据结构的指针, 运行的时候可能会报错:
panic: runtime error: cgo argument has Go pointer to Go pointer
通过设置 export GODEBUG=cgocheck=0 环境变量可以解决
但是使用的时候要注意内存安全
部分常用类型关系映射
C | Go | 转换方法 |
---|---|---|
char | byte | C.char |
signed char | int8 | C.schar |
unsigned char | uint8 | C.uchar |
short int | int16 | C.short |
short unsigned int | uint16 | C.ushort |
int | int | C.int |
unsigned int | uint | c.uint |
long long int | int64 | c.longlong |
float | float32 | c.float |
double | float64 | c.double |
void * | unsafe.Pointer | |
struct foo * | unsafe.Pointer | (*C.struct_foo)() |
char * 字符串 | string | C.Cstring C.GoString |