Go與C互動的詳細介紹
- 1、概念解釋
- 2、使用Cgo在Go中直接編寫C程式碼
- 3、基礎型別轉換
- 4、關於字串的兩個特別的方法
- 5、結構體
- 6、型別轉換方法
- 6、函式指標和回撥
- 7、關於C資料作為引數
- 8、可變引數
- 9、C引用Go
- 10、動態庫和靜態庫
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
轉換為C的char*
,如果使用_GoStringPtr
傳入一個臨時的string
到C中,在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