W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
原文鏈接:https://chai2010.cn/advanced-go-programming-book/ch2-cgo/ch2-04-func.html
函數(shù)是 C 語言編程的核心,通過 CGO 技術(shù)我們不僅僅可以在 Go 語言中調(diào)用 C 語言函數(shù),也可以將 Go 語言函數(shù)導(dǎo)出為 C 語言函數(shù)。
對于一個(gè)啟用 CGO 特性的程序,CGO 會(huì)構(gòu)造一個(gè)虛擬的 C 包。通過這個(gè)虛擬的 C 包可以調(diào)用 C 語言函數(shù)。
/*
static int add(int a, int b) {
return a+b;
}
*/
import "C"
func main() {
C.add(1, 1)
}
以上的 CGO 代碼首先定義了一個(gè)當(dāng)前文件內(nèi)可見的 add 函數(shù),然后通過 C.add
。
對于有返回值的 C 函數(shù),我們可以正常獲取返回值。
/*
static int div(int a, int b) {
return a/b;
}
*/
import "C"
import "fmt"
func main() {
v := C.div(6, 3)
fmt.Println(v)
}
上面的 div 函數(shù)實(shí)現(xiàn)了一個(gè)整數(shù)除法的運(yùn)算,然后通過返回值返回除法的結(jié)果。
不過對于除數(shù)為 0 的情形并沒有做特殊處理。如果希望在除數(shù)為 0 的時(shí)候返回一個(gè)錯(cuò)誤,其他時(shí)候返回正常的結(jié)果。因?yàn)?C 語言不支持返回多個(gè)結(jié)果,因此 <errno.h>
標(biāo)準(zhǔn)庫提供了一個(gè) errno
宏用于返回錯(cuò)誤狀態(tài)。我們可以近似地將 errno
看成一個(gè)線程安全的全局變量,可以用于記錄最近一次錯(cuò)誤的狀態(tài)碼。
改進(jìn)后的 div 函數(shù)實(shí)現(xiàn)如下:
#include <errno.h>
int div(int a, int b) {
if(b == 0) {
errno = EINVAL;
return 0;
}
return a/b;
}
CGO 也針對 <errno.h>
標(biāo)準(zhǔn)庫的 errno
宏做的特殊支持:在 CGO 調(diào)用 C 函數(shù)時(shí)如果有兩個(gè)返回值,那么第二個(gè)返回值將對應(yīng) errno
錯(cuò)誤狀態(tài)。
/*
#include <errno.h>
static int div(int a, int b) {
if(b == 0) {
errno = EINVAL;
return 0;
}
return a/b;
}
*/
import "C"
import "fmt"
func main() {
v0, err0 := C.div(2, 1)
fmt.Println(v0, err0)
v1, err1 := C.div(1, 0)
fmt.Println(v1, err1)
}
運(yùn)行這個(gè)代碼將會(huì)產(chǎn)生以下輸出:
2 <nil>
0 invalid argument
我們可以近似地將 div 函數(shù)看作為以下類型的函數(shù):
func C.div(a, b C.int) (C.int, [error])
第二個(gè)返回值是可忽略的 error 接口類型,底層對應(yīng) syscall.Errno
錯(cuò)誤類型。
C 語言函數(shù)還有一種沒有返回值類型的函數(shù),用 void 表示返回值類型。一般情況下,我們無法獲取 void 類型函數(shù)的返回值,因?yàn)闆]有返回值可以獲取。前面的例子中提到,cgo 對 errno 做了特殊處理,可以通過第二個(gè)返回值來獲取 C 語言的錯(cuò)誤狀態(tài)。對于 void 類型函數(shù),這個(gè)特性依然有效。
以下的代碼是獲取沒有返回值函數(shù)的錯(cuò)誤狀態(tài)碼:
//static void noreturn() {}
import "C"
import "fmt"
func main() {
_, err := C.noreturn()
fmt.Println(err)
}
此時(shí),我們忽略了第一個(gè)返回值,只獲取第二個(gè)返回值對應(yīng)的錯(cuò)誤碼。
我們也可以嘗試獲取第一個(gè)返回值,它對應(yīng)的是 C 語言的 void 對應(yīng)的 Go 語言類型:
//static void noreturn() {}
import "C"
import "fmt"
func main() {
v, _ := C.noreturn()
fmt.Printf("%#v", v)
}
運(yùn)行這個(gè)代碼將會(huì)產(chǎn)生以下輸出:
main._Ctype_void{}
我們可以看出 C 語言的 void 類型對應(yīng)的是當(dāng)前的 main 包中的 _Ctype_void
類型。其實(shí)也將 C 語言的 noreturn 函數(shù)看作是返回 _Ctype_void
類型的函數(shù),這樣就可以直接獲取 void 類型函數(shù)的返回值:
//static void noreturn() {}
import "C"
import "fmt"
func main() {
fmt.Println(C.noreturn())
}
運(yùn)行這個(gè)代碼將會(huì)產(chǎn)生以下輸出:
[]
其實(shí)在 CGO 生成的代碼中,_Ctype_void
類型對應(yīng)一個(gè) 0 長的數(shù)組類型 [0]byte
,因此 fmt.Println
輸出的是一個(gè)表示空數(shù)值的方括弧。
以上有效特性雖然看似有些無聊,但是通過這些例子我們可以精確掌握 CGO 代碼的邊界,可以從更深層次的設(shè)計(jì)的角度來思考產(chǎn)生這些奇怪特性的原因。
CGO 還有一個(gè)強(qiáng)大的特性:將 Go 函數(shù)導(dǎo)出為 C 語言函數(shù)。這樣的話我們可以定義好 C 語言接口,然后通過 Go 語言實(shí)現(xiàn)。在本章的第一節(jié)快速入門部分我們已經(jīng)展示過 Go 語言導(dǎo)出 C 語言函數(shù)的例子。
下面是用 Go 語言重新實(shí)現(xiàn)本節(jié)開始的 add 函數(shù):
import "C"
//export add
func add(a, b C.int) C.int {
return a+b
}
add 函數(shù)名以小寫字母開頭,對于 Go 語言來說是包內(nèi)的私有函數(shù)。但是從 C 語言角度來看,導(dǎo)出的 add 函數(shù)是一個(gè)可全局訪問的 C 語言函數(shù)。如果在兩個(gè)不同的 Go 語言包內(nèi),都存在一個(gè)同名的要導(dǎo)出為 C 語言函數(shù)的 add 函數(shù),那么在最終的鏈接階段將會(huì)出現(xiàn)符號重名的問題。
CGO 生成的 _cgo_export.h
文件會(huì)包含導(dǎo)出后的 C 語言函數(shù)的聲明。我們可以在純 C 源文件中包含 _cgo_export.h
文件來引用導(dǎo)出的 add 函數(shù)。如果希望在當(dāng)前的 CGO 文件中馬上使用導(dǎo)出的 C 語言 add 函數(shù),則無法引用 _cgo_export.h
文件。因?yàn)?nbsp;_cgo_export.h
文件的生成需要依賴當(dāng)前文件可以正常構(gòu)建,而如果當(dāng)前文件內(nèi)部循環(huán)依賴還未生成的 _cgo_export.h
文件將會(huì)導(dǎo)致
cgo 命令錯(cuò)誤。
#include "_cgo_export.h"
void foo() {
add(1, 1);
}
當(dāng)導(dǎo)出 C 語言接口時(shí),需要保證函數(shù)的參數(shù)和返回值類型都是 C 語言友好的類型,同時(shí)返回值不得直接或間接包含 Go 語言內(nèi)存空間的指針。
![]() | ![]() |
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: