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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM