1. 程式人生 > 其它 >Go與C互動的詳細介紹

Go與C互動的詳細介紹

目錄

教程:http://www.topgoer.cn/docs/cgo/cgo-1d2r00032319l

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轉換為Cchar*,如果使用_GoStringPtr傳入一個臨時的stringC中,在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