Go 語(yǔ)言 函數(shù)調(diào)用

2023-03-22 14:59 更新

原文鏈接:https://chai2010.cn/advanced-go-programming-book/ch2-cgo/ch2-04-func.html


2.4 函數(shù)調(diào)用

函數(shù)是 C 語(yǔ)言編程的核心,通過(guò) CGO 技術(shù)我們不僅僅可以在 Go 語(yǔ)言中調(diào)用 C 語(yǔ)言函數(shù),也可以將 Go 語(yǔ)言函數(shù)導(dǎo)出為 C 語(yǔ)言函數(shù)。

2.4.1 Go 調(diào)用 C 函數(shù)

對(duì)于一個(gè)啟用 CGO 特性的程序,CGO 會(huì)構(gòu)造一個(gè)虛擬的 C 包。通過(guò)這個(gè)虛擬的 C 包可以調(diào)用 C 語(yǔ)言函數(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)可見(jiàn)的 add 函數(shù),然后通過(guò) C.add

2.4.2 C 函數(shù)的返回值

對(duì)于有返回值的 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)算,然后通過(guò)返回值返回除法的結(jié)果。

不過(guò)對(duì)于除數(shù)為 0 的情形并沒(méi)有做特殊處理。如果希望在除數(shù)為 0 的時(shí)候返回一個(gè)錯(cuò)誤,其他時(shí)候返回正常的結(jié)果。因?yàn)?C 語(yǔ)言不支持返回多個(gè)結(jié)果,因此 <errno.h> 標(biāo)準(zhǔn)庫(kù)提供了一個(gè) errno 宏用于返回錯(cuò)誤狀態(tài)。我們可以近似地將 errno 看成一個(gè)線(xiàn)程安全的全局變量,可以用于記錄最近一次錯(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 也針對(duì) <errno.h> 標(biāo)準(zhǔn)庫(kù)的 errno 宏做的特殊支持:在 CGO 調(diào)用 C 函數(shù)時(shí)如果有兩個(gè)返回值,那么第二個(gè)返回值將對(duì)應(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ù)看作為以下類(lèi)型的函數(shù):

func C.div(a, b C.int) (C.int, [error])

第二個(gè)返回值是可忽略的 error 接口類(lèi)型,底層對(duì)應(yīng) syscall.Errno 錯(cuò)誤類(lèi)型。

2.4.3 void 函數(shù)的返回值

C 語(yǔ)言函數(shù)還有一種沒(méi)有返回值類(lèi)型的函數(shù),用 void 表示返回值類(lèi)型。一般情況下,我們無(wú)法獲取 void 類(lèi)型函數(shù)的返回值,因?yàn)闆](méi)有返回值可以獲取。前面的例子中提到,cgo 對(duì) errno 做了特殊處理,可以通過(guò)第二個(gè)返回值來(lái)獲取 C 語(yǔ)言的錯(cuò)誤狀態(tài)。對(duì)于 void 類(lèi)型函數(shù),這個(gè)特性依然有效。

以下的代碼是獲取沒(méi)有返回值函數(shù)的錯(cuò)誤狀態(tài)碼:

//static void noreturn() {}
import "C"
import "fmt"

func main() {
    _, err := C.noreturn()
    fmt.Println(err)
}

此時(shí),我們忽略了第一個(gè)返回值,只獲取第二個(gè)返回值對(duì)應(yīng)的錯(cuò)誤碼。

我們也可以嘗試獲取第一個(gè)返回值,它對(duì)應(yīng)的是 C 語(yǔ)言的 void 對(duì)應(yīng)的 Go 語(yǔ)言類(lèi)型:

//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 語(yǔ)言的 void 類(lèi)型對(duì)應(yīng)的是當(dāng)前的 main 包中的 _Ctype_void 類(lèi)型。其實(shí)也將 C 語(yǔ)言的 noreturn 函數(shù)看作是返回 _Ctype_void 類(lèi)型的函數(shù),這樣就可以直接獲取 void 類(lèi)型函數(shù)的返回值:

//static void noreturn() {}
import "C"
import "fmt"

func main() {
    fmt.Println(C.noreturn())
}

運(yùn)行這個(gè)代碼將會(huì)產(chǎn)生以下輸出:

[]

其實(shí)在 CGO 生成的代碼中,_Ctype_void 類(lèi)型對(duì)應(yīng)一個(gè) 0 長(zhǎng)的數(shù)組類(lèi)型 [0]byte,因此 fmt.Println 輸出的是一個(gè)表示空數(shù)值的方括弧。

以上有效特性雖然看似有些無(wú)聊,但是通過(guò)這些例子我們可以精確掌握 CGO 代碼的邊界,可以從更深層次的設(shè)計(jì)的角度來(lái)思考產(chǎn)生這些奇怪特性的原因。

2.4.4 C 調(diào)用 Go 導(dǎo)出函數(shù)

CGO 還有一個(gè)強(qiáng)大的特性:將 Go 函數(shù)導(dǎo)出為 C 語(yǔ)言函數(shù)。這樣的話(huà)我們可以定義好 C 語(yǔ)言接口,然后通過(guò) Go 語(yǔ)言實(shí)現(xiàn)。在本章的第一節(jié)快速入門(mén)部分我們已經(jīng)展示過(guò) Go 語(yǔ)言導(dǎo)出 C 語(yǔ)言函數(shù)的例子。

下面是用 Go 語(yǔ)言重新實(shí)現(xiàn)本節(jié)開(kāi)始的 add 函數(shù):

import "C"

//export add
func add(a, b C.int) C.int {
    return a+b
}

add 函數(shù)名以小寫(xiě)字母開(kāi)頭,對(duì)于 Go 語(yǔ)言來(lái)說(shuō)是包內(nèi)的私有函數(shù)。但是從 C 語(yǔ)言角度來(lái)看,導(dǎo)出的 add 函數(shù)是一個(gè)可全局訪(fǎng)問(wèn)的 C 語(yǔ)言函數(shù)。如果在兩個(gè)不同的 Go 語(yǔ)言包內(nèi),都存在一個(gè)同名的要導(dǎo)出為 C 語(yǔ)言函數(shù)的 add 函數(shù),那么在最終的鏈接階段將會(huì)出現(xiàn)符號(hào)重名的問(wèn)題。

CGO 生成的 _cgo_export.h 文件會(huì)包含導(dǎo)出后的 C 語(yǔ)言函數(shù)的聲明。我們可以在純 C 源文件中包含 _cgo_export.h 文件來(lái)引用導(dǎo)出的 add 函數(shù)。如果希望在當(dāng)前的 CGO 文件中馬上使用導(dǎo)出的 C 語(yǔ)言 add 函數(shù),則無(wú)法引用 _cgo_export.h 文件。因?yàn)?nbsp;_cgo_export.h 文件的生成需要依賴(lài)當(dāng)前文件可以正常構(gòu)建,而如果當(dāng)前文件內(nèi)部循環(huán)依賴(lài)還未生成的 _cgo_export.h 文件將會(huì)導(dǎo)致 cgo 命令錯(cuò)誤。

#include "_cgo_export.h"

void foo() {
    add(1, 1);
}

當(dāng)導(dǎo)出 C 語(yǔ)言接口時(shí),需要保證函數(shù)的參數(shù)和返回值類(lèi)型都是 C 語(yǔ)言友好的類(lèi)型,同時(shí)返回值不得直接或間接包含 Go 語(yǔ)言?xún)?nèi)存空間的指針。



以上內(nèi)容是否對(duì)您有幫助:
在線(xiàn)筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)