快速上手 Go CGO,掌握在 Go 里寫 C!
大家好,我是煎魚。
最近因為各種奇怪的原因,接觸到了 Go 特色之一 CGO。這方面的相關(guān)內(nèi)容也相對少一些,給大家拋磚引玉。

圖片來源于 marlin
畢竟很多跨語言調(diào)用,還是會依賴 CGO 這個特性。希望大家在真正要用時有個前置知識墊肚子。
CGO 是什么
CGO 就是 C 和 Go,兩個編程語言。指的是能夠創(chuàng)建調(diào)用 C 代碼的 Go 包。對照著 Go 代碼中的 “C”:
package main
import "C"
func main() {}一旦程序中出現(xiàn) import "C",則意味著開啟 CGO 特性。在進行 go build 等階段時,將會調(diào)用 C 編譯器(通常是 gcc 或 clang)。
CGO 對應(yīng)的環(huán)境變量是 CGO_ENABLED,設(shè)置為 1 則開啟 CGO,為 0 則關(guān)閉 CGO。
編譯命令如下:
CGO_ENABLED=0 go build -o hellojy main.go當然,對于默認值。該環(huán)境變量值為 1,C 編譯器也是使用 gcc。我們可以通過 go env 看到:

一旦關(guān)閉就會影響 CGO 編譯。需要特別留意,交叉編譯時會默認關(guān)閉 CGO。
CGO 快速上手
最小 Demo
先來一個 CGO 的 Go 例子:
package main
//#include <stdio.h>
import "C"
func main() {
 s := C.CString("hello world.")
 C.puts(s)
}運行 go run main.go,輸出結(jié)果:
hello world.聲明 C 注解
如果你沒有了解過 CGO,看到上面的例子,可能會有好幾個疑問。
首先是 include:
//#include <stdio.h>
import "C"import "C" 我們懂,是導(dǎo)入 C 的偽包。前面的注解是什么?
無論是:
//#include <stdio.h>又或是:
/*
#include <stdio.h>
#include <stdlib.h>
*/實際上這是導(dǎo)入 C 前的注解,注解內(nèi)容可以包含任何 C 代碼,例如:函數(shù)、變量的聲明定義、庫引用等。(該注解要緊挨導(dǎo)入語句)
回到 Demo 本身,如果我們?nèi)サ?nbsp;//#include <stdio.h>,再運行會出現(xiàn)如下報錯:
# command-line-arguments
./main.go:7:2: could not determine kind of name for C.puts去掉后,語句 C.puts(s) 將無法運行。
實際上 stdio.h 的全稱是:standard input output.header(標準輸入輸出頭文件)。該文件大都是些輸入輸出函數(shù)的聲明,引用了這庫,就能使用 C 的 puts 方法。
其他同理,你在注解中聲明、定義的東西,均可以在 Go 代碼中通過 C 這個偽包來引用和調(diào)用。
其次像是 CString 方法,屬于在 Go 和 C 類型之間需要復(fù)制數(shù)據(jù)的特殊函數(shù),偽包 C 有進行預(yù)定義。
例如:
func C.CString(string) *C.char
func C.CBytes([]byte) unsafe.Pointer
func C.GoString(*C.char) string
func C.GoStringN(*C.char, C.int) string
func C.GoBytes(unsafe.Pointer, C.int) []byteGo 和 C 類型對照
Go 官方有提供一份基礎(chǔ)類型的對照表,大家可以參照來使用和理解。
如下:
C 語言類型  | CGO 類型  | Go語言類型  | 
char  | C.char  | byte  | 
singed char  | C.schar  | int8  | 
unsigned char  | C.uchar  | uint8  | 
short  | C.short  | int16  | 
unsigned short  | C.ushort  | uint16  | 
int  | C.int  | int32  | 
unsigned int  | C.uint  | uint32  | 
long  | C.long  | int32  | 
unsigned long  | C.ulong  | uint32  | 
long long int  | C.longlong  | int64  | 
unsigned long long int  | C.ulonglong  | uint64  | 
float  | C.float  | float32  | 
double  | C.double  | float64  | 
size_t  | C.size_t  | uint  | 
注意事項
使用 CGO,除了會帶來一定的性能損耗外。需要特別注意的是:內(nèi)存泄露。因為 Go 是帶垃圾回收機制的編程語言,而使用了 C 后,需要手動的管理內(nèi)存。
還是這個 Demo:
package main
//#include <stdio.h>
import "C"
func main() {
 s := C.CString("hello world.")
 C.puts(s)
}如果這是一個常駐進程,也沒有任何釋放動作。用 C.CString 方法所申請的變量 s 就會泄露。
因此與 “C” 相關(guān)的變量創(chuàng)建,需要進行手動的內(nèi)存管理。正確的代碼如下:
/*
#include <stdio.h>
#include <stdlib.h>
*/
import "C"
import (
 "unsafe"
)
func main() {
 b := C.CString("hello world.")
 C.puts(b)
 C.free(unsafe.Pointer(b))
}需要調(diào)用 C.free 方法進行主動的內(nèi)存釋放。如果該程序自然結(jié)束,也會自動回收。
總結(jié)
在今天這篇文章中,我們介紹了 Go 語言中 CGO 的基礎(chǔ)知識和快速入門。整體上,只要適應(yīng)了寫法,CGO 的用法就不算太麻煩。
需要特別注意手動內(nèi)存管理、性能損耗等多方面的制約。后續(xù)我們也會繼續(xù)深入 CGO 方面的內(nèi)容。















 
 
 











 
 
 
 