快速上手 Go CGO,掌握在 Go 里寫 C!
大家好,我是煎魚。
最近因?yàn)楦鞣N奇怪的原因,接觸到了 Go 特色之一 CGO。這方面的相關(guān)內(nèi)容也相對(duì)少一些,給大家拋磚引玉。
圖片來源于 marlin
畢竟很多跨語言調(diào)用,還是會(huì)依賴 CGO 這個(gè)特性。希望大家在真正要用時(shí)有個(gè)前置知識(shí)墊肚子。
CGO 是什么
CGO 就是 C 和 Go,兩個(gè)編程語言。指的是能夠創(chuàng)建調(diào)用 C 代碼的 Go 包。對(duì)照著 Go 代碼中的 “C”:
package main
import "C"
func main() {}
一旦程序中出現(xiàn) import "C",則意味著開啟 CGO 特性。在進(jìn)行 go build 等階段時(shí),將會(huì)調(diào)用 C 編譯器(通常是 gcc 或 clang)。
CGO 對(duì)應(yīng)的環(huán)境變量是 CGO_ENABLED,設(shè)置為 1 則開啟 CGO,為 0 則關(guān)閉 CGO。
編譯命令如下:
CGO_ENABLED=0 go build -o hellojy main.go
當(dāng)然,對(duì)于默認(rèn)值。該環(huán)境變量值為 1,C 編譯器也是使用 gcc。我們可以通過 go env 看到:
一旦關(guān)閉就會(huì)影響 CGO 編譯。需要特別留意,交叉編譯時(shí)會(huì)默認(rèn)關(guān)閉 CGO。
CGO 快速上手
最小 Demo
先來一個(gè) CGO 的 Go 例子:
package main
//#include <stdio.h>
import "C"
func main() {
s := C.CString("hello world.")
C.puts(s)
}
運(yùn)行 go run main.go,輸出結(jié)果:
hello world.
聲明 C 注解
如果你沒有了解過 CGO,看到上面的例子,可能會(huì)有好幾個(gè)疑問。
首先是 include:
//#include <stdio.h>
import "C"
import "C" 我們懂,是導(dǎo)入 C 的偽包。前面的注解是什么?
無論是:
//#include <stdio.h>
又或是:
/*
#include <stdio.h>
#include <stdlib.h>
*/
實(shí)際上這是導(dǎo)入 C 前的注解,注解內(nèi)容可以包含任何 C 代碼,例如:函數(shù)、變量的聲明定義、庫引用等。(該注解要緊挨導(dǎo)入語句)
回到 Demo 本身,如果我們?nèi)サ?nbsp;//#include <stdio.h>,再運(yùn)行會(huì)出現(xiàn)如下報(bào)錯(cuò):
# command-line-arguments
./main.go:7:2: could not determine kind of name for C.puts
去掉后,語句 C.puts(s) 將無法運(yùn)行。
實(shí)際上 stdio.h 的全稱是:standard input output.header(標(biāo)準(zhǔn)輸入輸出頭文件)。該文件大都是些輸入輸出函數(shù)的聲明,引用了這庫,就能使用 C 的 puts 方法。
其他同理,你在注解中聲明、定義的東西,均可以在 Go 代碼中通過 C 這個(gè)偽包來引用和調(diào)用。
其次像是 CString 方法,屬于在 Go 和 C 類型之間需要復(fù)制數(shù)據(jù)的特殊函數(shù),偽包 C 有進(jìn)行預(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 類型對(duì)照
Go 官方有提供一份基礎(chǔ)類型的對(duì)照表,大家可以參照來使用和理解。
如下:
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 |
注意事項(xiàng)
使用 CGO,除了會(huì)帶來一定的性能損耗外。需要特別注意的是:內(nèi)存泄露。因?yàn)?Go 是帶垃圾回收機(jī)制的編程語言,而使用了 C 后,需要手動(dòng)的管理內(nèi)存。
還是這個(gè) Demo:
package main
//#include <stdio.h>
import "C"
func main() {
s := C.CString("hello world.")
C.puts(s)
}
如果這是一個(gè)常駐進(jìn)程,也沒有任何釋放動(dòng)作。用 C.CString 方法所申請(qǐng)的變量 s 就會(huì)泄露。
因此與 “C” 相關(guān)的變量創(chuàng)建,需要進(jìn)行手動(dò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 方法進(jìn)行主動(dòng)的內(nèi)存釋放。如果該程序自然結(jié)束,也會(huì)自動(dòng)回收。
總結(jié)
在今天這篇文章中,我們介紹了 Go 語言中 CGO 的基礎(chǔ)知識(shí)和快速入門。整體上,只要適應(yīng)了寫法,CGO 的用法就不算太麻煩。
需要特別注意手動(dòng)內(nèi)存管理、性能損耗等多方面的制約。后續(xù)我們也會(huì)繼續(xù)深入 CGO 方面的內(nèi)容。