快速上手 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" 我們懂,是導入 C 的偽包。前面的注解是什么?
無論是:
//#include <stdio.h>
又或是:
/*
#include <stdio.h>
#include <stdlib.h>
*/
實際上這是導入 C 前的注解,注解內(nèi)容可以包含任何 C 代碼,例如:函數(shù)、變量的聲明定義、庫引用等。(該注解要緊挨導入語句)
回到 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 類型之間需要復制數(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) []byte
Go 和 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)容。