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
posted @ 2020-05-06 09:45  Me1onRind  阅读(1279)  评论(0编辑  收藏  举报