亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Go與C語言的互操作實現(xiàn)

 更新時間:2021年12月09日 11:15:58   投稿:zx  
在Go與C語言互操作方面,Go更是提供了強大的支持。尤其是在Go中使用C,你甚至可以直接在Go源文件中編寫C代碼,本文就詳細的介紹一下如何使用,感興趣的可以了解一下

Go有強烈的C背景,除了語法具有繼承性外,其設計者以及其設計目標都與C語言有著千絲萬縷的聯(lián)系。在Go與C語言互操作(Interoperability)方面,Go更是提供了強大的支持。尤其是在Go中使用C,你甚至可以直接在Go源文件中編寫C代碼,這是其他語言所無法望其項背的。

在如下一些場景中,可能會涉及到Go與C的互操作:

1、提升局部代碼性能時,用C替換一些Go代碼。C之于Go,好比匯編之于C。
2、嫌Go內(nèi)存GC性能不足,自己手動管理應用內(nèi)存。
3、實現(xiàn)一些庫的Go Wrapper。比如Oracle提供的C版本OCI,但Oracle并未提供Go版本的以及連接DB的協(xié)議細節(jié),因此只能通過包裝C? OCI版本的方式以提供Go開發(fā)者使用。
4、Go導出函數(shù)供C開發(fā)者使用(目前這種需求應該很少見)。
5、Maybe more…

一、Go調(diào)用C代碼的原理

下面是一個短小的例子:

package main
 
// #include <stdio.h>
// #include <stdlib.h>
/*
void print(char *str) {
    printf("%s\n", str);
}
*/
import "C"
 
import "unsafe"
 
func main() {
    s := "Hello Cgo"
    cs := C.CString(s)
    C.print(cs)
    C.free(unsafe.Pointer(cs))
}

與"正常"Go代碼相比,上述代碼有幾處"特殊"的地方:
1) 在開頭的注釋中出現(xiàn)了C頭文件的include字樣
2) 在注釋中定義了C函數(shù)print
3) import的一個名為C的"包"
4) 在main函數(shù)中居然調(diào)用了上述的那個C函數(shù)-print

沒錯,這就是在Go源碼中調(diào)用C代碼的步驟,可以看出我們可直接在Go源碼文件中編寫C代碼。

首先,Go源碼文件中的C代碼是需要用注釋包裹的,就像上面的include 頭文件以及print函數(shù)定義;
其次,import "C"這個語句是必須的,而且其與上面的C代碼之間不能用空行分隔,必須緊密相連。這里的"C"不是包名,而是一種類似名字空間的概念,或可以理解為偽包,C語言所有語法元素均在該偽包下面;
最后,訪問C語法元素時都要在其前面加上偽包前綴,比如C.uint和上面代碼中的C.print、C.free等。

我們?nèi)绾蝸砭幾g這個go源文件呢?其實與"正常"Go源文件沒啥區(qū)別,依舊可以直接通過go build或go run來編譯和執(zhí)行。但實際編譯過程中,go調(diào)用了名為cgo的工具,cgo會識別和讀取Go源文件中的C元素,并將其提取后交給C編譯器編譯,最后與Go源碼編譯后的目標文件鏈接成一個可執(zhí)行程序。這樣我們就不難理解為何Go源文件中的C代碼要用注釋包裹了,這些特殊的語法都是可以被Cgo識別并使用的。

二、在Go中使用C語言的類型

1、原生類型

數(shù)值類型

在Go中可以用如下方式訪問C原生的數(shù)值類型:

C.char,
C.schar (signed char),
C.uchar (unsigned char),
C.short,
C.ushort (unsigned short),
C.int, C.uint (unsigned int),
C.long,
C.ulong (unsigned long),
C.longlong (long long),
C.ulonglong (unsigned long long),
C.float,
C.double

Go的數(shù)值類型與C中的數(shù)值類型不是一一對應的。因此在使用對方類型變量時少不了顯式轉(zhuǎn)型操作,如Go doc中的這個例子:

func Random() int {
    return int(C.random())//C.long -> Go的int
}
 
func Seed(i int) {
    C.srandom(C.uint(i))//Go的uint -> C的uint
}

指針類型

原生數(shù)值類型的指針類型可按Go語法在類型前面加上*,比如var p *C.int。而void*比較特殊,用Go中的unsafe.Pointer表示。任何類型的指針值都可以轉(zhuǎn)換為unsafe.Pointer類型,而unsafe.Pointer類型值也可以轉(zhuǎn)換為任意類型的指針值。unsafe.Pointer還可以與uintptr這個類型做相互轉(zhuǎn)換。由于unsafe.Pointer的指針類型無法做算術操作,轉(zhuǎn)換為uintptr后可進行算術操作。

字符串類型

C語言中并不存在正規(guī)的字符串類型,在C中用帶結尾'\0'的字符數(shù)組來表示字符串;而在Go中,string類型是原生類型,因此在兩種語言互操作是勢必要做字符串類型的轉(zhuǎn)換。

通過C.CString函數(shù),我們可以將Go的string類型轉(zhuǎn)換為C的"字符串"類型,再傳給C函數(shù)使用。就如我們在本文開篇例子中使用的那樣:

s := "Hello Cgo\n"
cs := C.CString(s)
C.print(cs)

不過這樣轉(zhuǎn)型后所得到的C字符串cs并不能由Go的gc所管理,我們必須手動釋放cs所占用的內(nèi)存,這就是為何例子中最后調(diào)用C.free釋放掉cs的原因。在C內(nèi)部分配的內(nèi)存,Go中的GC是無法感知到的,因此要記著釋放。

通過C.GoString可將C的字符串(*C.char)轉(zhuǎn)換為Go的string類型,例如:

// #include <stdio.h>
// #include <stdlib.h>
// char *foo = "hellofoo";
import "C"
 
import "fmt"
 
func main() {
… …
    fmt.Printf("%s\n", C.GoString(C.foo))
}

數(shù)組類型

C語言中的數(shù)組與Go語言中的數(shù)組差異較大,后者是值類型,而前者與C中的指針大部分場合都可以隨意轉(zhuǎn)換。目前似乎無法直接顯式的在兩者之間進行轉(zhuǎn)型,官方文檔也沒有說明。但我們可以通過編寫轉(zhuǎn)換函數(shù),將C的數(shù)組轉(zhuǎn)換為Go的Slice(由于Go中數(shù)組是值類型,其大小是靜態(tài)的,轉(zhuǎn)換為Slice更為通用一些),下面是一個整型數(shù)組轉(zhuǎn)換的例子:

// int cArray[] = {1, 2, 3, 4, 5, 6, 7};
 
func CArrayToGoArray(cArray unsafe.Pointer, size int) (goArray []int) {
    p := uintptr(cArray)
    for i :=0; i < size; i++ {
        j := *(*int)(unsafe.Pointer(p))
        goArray = append(goArray, j)
        p += unsafe.Sizeof(j)
    }
 
    return
}
 
func main() {
    … …
    goArray := CArrayToGoArray(unsafe.Pointer(&C.cArray[0]), 7)
    fmt.Println(goArray)
}

執(zhí)行結果輸出:[1 2 3 4 5 6 7]

這里要注意的是:Go編譯器并不能將C的cArray自動轉(zhuǎn)換為數(shù)組的地址,所以不能像在C中使用數(shù)組那樣將數(shù)組變量直接傳遞給函數(shù),而是將數(shù)組第一個元素的地址傳遞給函數(shù)。

2、自定義類型

除了原生類型外,我們還可以訪問C中的自定義類型。

枚舉(enum)

// enum color {
//    RED,
//    BLUE,
//    YELLOW
// };
 
var e, f, g C.enum_color = C.RED, C.BLUE, C.YELLOW
fmt.Println(e, f, g)

輸出:0 1 2

對于具名的C枚舉類型,我們可以通過C.enum_xx來訪問該類型。如果是匿名枚舉,則似乎只能訪問其字段了。

結構體(struct)

// struct employee {
//     char *id;
//     int  age;
// };
 
id := C.CString("1247")
var employee C.struct_employee = C.struct_employee{id, 21}
fmt.Println(C.GoString(employee.id))
fmt.Println(employee.age)
C.free(unsafe.Pointer(id))

輸出:
1247
21

和enum類似,我們可以通過C.struct_xx來訪問C中定義的結構體類型。

聯(lián)合體(union)

這里我試圖用與訪問struct相同的方法來訪問一個C的union:

// #include <stdio.h>
// union bar {
//        char   c;
//        int    i;
//        double d;
// };
import "C"
 
func main() {
    var b *C.union_bar = new(C.union_bar)
    b.c = 4
    fmt.Println(b)
}

不過編譯時,go卻報錯:b.c undefined (type *[8]byte has no field or method c)。從報錯的信息來看,Go對待union與其他類型不同,似乎將union當成[N]byte來對待,其中N為union中最大字段的size(圓整后的),因此我們可以按如下方式處理C.union_bar:

func main() {
    var b *C.union_bar = new(C.union_bar)
    b[0] = 13
    b[1] = 17
    fmt.Println(b)
}

輸出:&[13 17 0 0 0 0 0 0]

typedef

在Go中訪問使用用typedef定義的別名類型時,其訪問方式與原實際類型訪問方式相同。如:

// typedef int myint;
 
var a C.myint = 5
fmt.Println(a)
 
// typedef struct employee myemployee;
 
var m C.struct_myemployee

從例子中可以看出,對原生類型的別名,直接訪問這個新類型名即可。而對于復合類型的別名,需要根據(jù)原復合類型的訪問方式對新別名進行訪問,比如myemployee實際類型為struct,那么使用myemployee時也要加上struct_前綴。

三、Go中訪問C的變量和函數(shù)

實際上上面的例子中我們已經(jīng)演示了在Go中是如何訪問C的變量和函數(shù)的,一般方法就是加上C前綴即可,對于C標準庫中的函數(shù)尤其是這樣。不過雖然我們可以在Go源碼文件中直接定義C變量和C函數(shù),但從代碼結構上來講,大量的在Go源碼中編寫C代碼似乎不是那么“專業(yè)”。那如何將C函數(shù)和變量定義從Go源碼中分離出去單獨定義呢?我們很容易想到將C的代碼以共享庫的形式提供給Go源碼。

Cgo提供了#cgo指示符可以指定Go源碼在編譯后與哪些共享庫進行鏈接。我們來看一下例子:

package main
 
// #cgo LDFLAGS: -L ./ -lfoo
// #include <stdio.h>
// #include <stdlib.h>
// #include "foo.h"
import "C"
import "fmt“
 
func main() {
    fmt.Println(C.count)
    C.foo()
}

我們看到上面例子中通過#cgo指示符告訴go編譯器鏈接當前目錄下的libfoo共享庫。C.count變量和C.foo函數(shù)的定義都在libfoo共享庫中。我們來創(chuàng)建這個共享庫:

// foo.h
 
int count;
void foo();
 
//foo.c
#include "foo.h"
 
int count = 6;
void foo() {
    printf("I am foo!\n");
}
 
$> gcc -c foo.c
$> ar rv libfoo.a foo.o

我們首先創(chuàng)建一個靜態(tài)共享庫libfoo.a,不過在編譯Go源文件時我們遇到了問題:

$> go build foo.go
# command-line-arguments
/tmp/go-build565913544/command-line-arguments.a(foo.cgo2.)(.text): foo: not defined
foo(0): not defined

提示foo函數(shù)未定義。通過-x選項打印出具體的編譯細節(jié),也未找出問題所在。不過在Go的問題列表中我發(fā)現(xiàn)了一個issue(http://code.google.com/p/go/issues/detail?id=3755),上面提到了目前Go的版本不支持鏈接靜態(tài)共享庫。

那我們來創(chuàng)建一個動態(tài)共享庫試試:

$> gcc -c foo.c
$> gcc -shared -Wl,-soname,libfoo.so -o libfoo.so  foo.o

再編譯foo.go,的確能夠成功。執(zhí)行foo。

$> go build foo.go && go
6
I am foo!

還有一點值得注意,那就是Go支持多返回值,而C中并沒不支持。因此當將C函數(shù)用在多返回值的調(diào)用中時,C的errno將作為err返回值返回,下面是個例子:

package main
 
// #include <stdlib.h>
// #include <stdio.h>
// #include <errno.h>
// int foo(int i) {
//    errno = 0;
//    if (i > 5) {
//        errno = 8;
//        return i – 5;
//    } else {
//        return i;
//    }
//}
import "C"
import "fmt"
 
func main() {
    i, err := C.foo(C.int(8))
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println(i)
    }
}
 
$> go run foo.go
exec format error

errno為8,其含義在errno.h中可以找到:

#define ENOEXEC      8  /* Exec format error */

的確是“exec format error”。

四、C中使用Go函數(shù)

與在Go中使用C源碼相比,在C中使用Go函數(shù)的場合較少。在Go中,可以使用"export + 函數(shù)名"來導出Go函數(shù)為C所使用,看一個簡單例子:

package main
 
/*
#include <stdio.h>
 
extern void GoExportedFunc();
 
void bar() {
        printf("I am bar!\n");
        GoExportedFunc();
}
*/
import "C"
 
import "fmt"
 
//export GoExportedFunc
func GoExportedFunc() {
        fmt.Println("I am a GoExportedFunc!")
}
 
func main() {
        C.bar()
}

不過當我們編譯該Go文件時,我們得到了如下錯誤信息:

# command-line-arguments
/tmp/go-build163255970/command-line-arguments/_obj/bar.cgo2.o: In function `bar':
./bar.go:7: multiple definition of `bar'
/tmp/go-build163255970/command-line-arguments/_obj/_cgo_export.o:/home/tonybai/test/go/bar.go:7: first defined here
collect2: ld returned 1 exit status

代碼似乎沒有任何問題,但就是無法通過編譯,總是提示“多重定義”。翻看Cgo的文檔,找到了些端倪。原來

There is a limitation: if your program uses any //export directives, then the C code in the comment may only include declarations (extern int f();), not definitions (int f() { return 1; }).

似乎是// extern int f()與//export f不能放在一個Go源文件中。我們把bar.go拆分成bar1.go和bar2.go兩個文件:

// bar1.go
 
package main
 
/*
#include <stdio.h>
 
extern void GoExportedFunc();
 
void bar() {
        printf("I am bar!\n");
        GoExportedFunc();
}
*/
import "C"
 
func main() {
        C.bar()
}
// bar2.go
 
package main
 
import "C"
import "fmt"
 
//export GoExportedFunc
func GoExportedFunc() {
        fmt.Println("I am a GoExportedFunc!")
}

編譯執(zhí)行:

$> go build -o bar bar1.go bar2.go
$> bar
I am bar!
I am a GoExportedFunc!

個人覺得目前Go對于導出函數(shù)供C使用的功能還十分有限,兩種語言的調(diào)用約定不同,類型無法一一對應以及Go中類似Gc這樣的高級功能讓導出Go函數(shù)這一功能難于完美實現(xiàn),導出的函數(shù)依舊無法完全脫離Go的環(huán)境,因此實用性似乎有折扣。

五、其他

雖然Go提供了強大的與C互操作的功能,但目前依舊不完善,比如不支持在Go中直接調(diào)用可變個數(shù)參數(shù)的函數(shù)(issue975),如printf(因此,文檔中多用fputs)。

這里的建議是:盡量縮小Go與C間互操作范圍。

什么意思呢?如果你在Go中使用C代碼時,那么盡量在C代碼中調(diào)用C函數(shù)。Go只使用你封裝好的一個C函數(shù)最好。不要像下面代碼這樣:

C.fputs(…)
C.atoi(..)
C.malloc(..)

而是將這些C函數(shù)調(diào)用封裝到一個C函數(shù)中,Go只知道這個C函數(shù)即可。

C.foo(..)

相反,在C中使用Go導出的函數(shù)也是一樣。

到此這篇關于Go與C語言的互操作實現(xiàn)的文章就介紹到這了,更多相關Go與C語言互操作內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Go語言反射獲取類型屬性和方法示例

    Go語言反射獲取類型屬性和方法示例

    這篇文章主要為大家介紹了本文將介紹?Go?反射?reflect.StructField?和?reflect.Method?相關的內(nèi)容,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-06-06
  • golang 中string和int類型相互轉(zhuǎn)換

    golang 中string和int類型相互轉(zhuǎn)換

    這篇文章主要介紹了golang 中string和int類型相互轉(zhuǎn)換,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-02-02
  • go日志庫logrus的安裝及快速使用

    go日志庫logrus的安裝及快速使用

    這篇文章主要為大家介紹了go日志庫logrus的安裝及快速使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-08-08
  • 如何使用Golang發(fā)送Get和Post請求

    如何使用Golang發(fā)送Get和Post請求

    這篇文章主要給大家介紹了關于如何使用Golang發(fā)送Get和Post請求的相關資料,Go語言(Golang)的標準庫提供了處理HTTP請求的功能,這使得將Go用于web應用程序變得非常容易,需要的朋友可以參考下
    2023-06-06
  • 基于Go和PHP語言實現(xiàn)爬樓梯算法的思路詳解

    基于Go和PHP語言實現(xiàn)爬樓梯算法的思路詳解

    這篇文章主要介紹了Go和PHP 實現(xiàn)爬樓梯算法,本文通過動態(tài)規(guī)劃和斐波那契數(shù)列兩種解決思路給大家講解的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-05-05
  • Go語言結構化日志slog的用法解析

    Go語言結構化日志slog的用法解析

    go?1.21.0?版本引入了一個新的包?log/slog,該包提供了結構化日志的功能,本文小編就來和大家聊聊log/slog?包的使用,感興趣的小伙伴可以跟隨小編一起學習一下
    2023-10-10
  • Go語言中的sync包同步原語最新詳解

    Go語言中的sync包同步原語最新詳解

    Go語言在sync包中提供了一套多才多藝的同步機制,以及用于管理對共享資源的并發(fā)訪問的原子操作,了解這些工具并為您的并發(fā)需求選擇合適的工具是編寫高效可靠的并發(fā)Go程序的關鍵,這篇文章主要介紹了Go語言中的`sync`包同步原語,需要的朋友可以參考下
    2023-12-12
  • Golang實現(xiàn)支持多種類型的set

    Golang實現(xiàn)支持多種類型的set

    在項目開發(fā)中,常常會用到set去重,為什么不寫一個set呢,而且go現(xiàn)在支持了泛型,所以本文就來用Golang實現(xiàn)一個支持多種類型的set呢
    2023-05-05
  • goland設置控制臺折疊效果

    goland設置控制臺折疊效果

    這篇文章主要介紹了goland設置控制臺折疊效果,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-12-12
  • Go 標準庫增加metrics指標探討分析

    Go 標準庫增加metrics指標探討分析

    go中有一個神奇的標準庫 runtime/metrics,提供了一系列預定義好的 Go 自身的相關指標,如果沒有編寫過基礎監(jiān)控庫或者關注的比較少的朋友可能會沒接觸到這類指標,本文展開現(xiàn)有metrics 指標,并結合現(xiàn)有的社區(qū)討論一起看看還有沒有必要增加更多的標準庫指標
    2023-10-10

最新評論