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 |