真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

使用cgo調(diào)用C代碼

使用 cgo 調(diào)用 C 代碼

cgo 是用來(lái)為 C 函數(shù)創(chuàng)建 Go 綁定的工具。諸如此類的工具都叫作外部函數(shù)接口(FFI)。
其他的工具還有,比如SWIG(sig.org)是另一個(gè)工具,它提供了更加復(fù)雜的特性用來(lái)集成C++的類,這個(gè)不講。

創(chuàng)新互聯(lián)公司專注于橫山企業(yè)網(wǎng)站建設(shè),響應(yīng)式網(wǎng)站,商城系統(tǒng)網(wǎng)站開(kāi)發(fā)。橫山網(wǎng)站建設(shè)公司,為橫山等地區(qū)提供建站服務(wù)。全流程定制網(wǎng)站設(shè)計(jì),專業(yè)設(shè)計(jì),全程項(xiàng)目跟蹤,創(chuàng)新互聯(lián)公司專業(yè)和態(tài)度為您提供的服務(wù)

使用cgo的場(chǎng)景

如果一個(gè)程序已經(jīng)有現(xiàn)成的C語(yǔ)言的實(shí)現(xiàn),但是還沒(méi)有Go語(yǔ)言的實(shí)現(xiàn)的時(shí)候,那沒(méi)有一下3種選擇:

  1. 如果是一個(gè)比較小的C語(yǔ)言庫(kù),可以使用純 Go 語(yǔ)言來(lái)移植它(重新實(shí)現(xiàn)一遍)。
  2. 如果性能不是很關(guān)鍵,可以用 os/exec 包以輔助子進(jìn)程的方式來(lái)調(diào)用C程序。
  3. 當(dāng)需要使用復(fù)雜而且性能要求高的底層C接口時(shí),就是使用cgo的場(chǎng)景了

簡(jiǎn)單說(shuō)就是,如果是簡(jiǎn)單的實(shí)現(xiàn),那么就再造一個(gè)Go語(yǔ)言的輪子。如果性能要求不高,可以直接通過(guò)系統(tǒng)來(lái)調(diào)用這個(gè)程序。只有不想重新造輪子又不想間接的通過(guò)系統(tǒng)來(lái)調(diào)用的時(shí)候,就需要用到 cgo 了。

bzip2 壓縮程序正是這樣的一個(gè)情況。接下來(lái)就要使用 cgo 來(lái)構(gòu)建一個(gè)簡(jiǎn)單的數(shù)據(jù)壓縮程序。
標(biāo)準(zhǔn)庫(kù)的 compress/... 子包中提供了流行壓縮算法的壓縮器和解壓縮器,包括流行的LZW壓縮算法(Unix的compress命令用的算法)和DEFLATE壓縮算法(GNU gzip命令用的算法)。這些包中的 API 有些許的不同,但都提供一個(gè)對(duì) io.Writer 的封裝用來(lái)對(duì)寫入的數(shù)據(jù)進(jìn)行壓縮,并且還有一個(gè)對(duì) io.Reader 的封裝,在讀取數(shù)據(jù)的同時(shí)進(jìn)行壓縮。例如:

package gzip // compress/gzip
func NewWriter(w io.Writer) io.WriteCloser
func NewReader(r io.Reader) (io.ReadCloser, error)

bzip2 算法基于優(yōu)雅的 Burrows-Wheeler 變換,它和 gzip 相比速度要慢但是壓縮比更高。標(biāo)準(zhǔn)庫(kù)的 compress/bzip2 提供了 bzip2 的解壓縮器,但是目前還沒(méi)有提供壓縮功能。從頭開(kāi)始實(shí)現(xiàn)這個(gè)壓縮算法比較麻煩,而且 http://bzip.org 已經(jīng)有現(xiàn)成的libbzip2的開(kāi)源實(shí)現(xiàn)了,這是一個(gè)文檔完善且高性能的開(kāi)源 C 語(yǔ)言實(shí)現(xiàn)。

要使用C語(yǔ)言的libbzip2包,需要先構(gòu)建一個(gè)bz_stream結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體包含輸入和輸出緩沖區(qū),以及三個(gè)C函數(shù):

  • BZ2_bzCompressInit: 初始化緩存,分配流的緩沖區(qū)
  • BZ2_bzCompress: 將輸入緩存的數(shù)據(jù)壓縮到輸出緩存
  • BZ2_bzCompressEnd: 釋放不需要的緩存

C代碼

可以在Go代碼中直接調(diào)用BZ2_bzCompressInit和BZ2_bzCompressEnd。
但是對(duì)于BZ2_bzCompress,我們將定義一個(gè)C語(yǔ)言的包裝函數(shù),用它完成真正的工作。下面是C代碼,和其他Go文件放在同一個(gè)包下:

// bzip 包中的文件 bzip2.c
// 對(duì) libbzip2 的簡(jiǎn)單包裝,適合 cgo 使用
#include 

int bz2compress(bz_stream *s, int action,
                char *in, unsigned *inlen, char *out, unsigned *outlen) {
  s->next_in = in;
  s->avail_in = *inlen;
  s->next_out = out;
  s->avail_out = *outlen;
  int r = BZ2_bzCompress(s, action);
  *inlen -= s->avail_in;
  *outlen -= s->avail_out;
  s->next_in = s->next_out = NULL;
  return r;
}

安裝gcc
可能會(huì)出現(xiàn)如下的錯(cuò)誤提示:

exec: "gcc": executable file not found in %PATH%

這個(gè)應(yīng)該是缺少gcc編譯器,所以需要安裝配置好。在Windows系統(tǒng)上可能要麻煩一點(diǎn),這個(gè)問(wèn)題可以去看下一章節(jié)。不過(guò)即使Windows上解決了gcc的依賴,但還是沒(méi)解決在Windows上安裝bzip2。這個(gè)不折騰了,所以這個(gè)示例還是需要Linux系統(tǒng)。

cgo注釋

然后是Go代碼,這里只是源碼文件開(kāi)頭的部分,第一部分如下所示。
聲明 import "C" 很特別, 并沒(méi)有這樣的一個(gè)包,但是這會(huì)讓編譯程序在編譯之前先運(yùn)行cgo工具:

// bzip 包中的文件 bzip2.go 的第一部分

// 包 bzip 封裝了一個(gè)使用 bzip2 壓縮算法的 writer (bzip.org).
package bzip

/*
#cgo CFLAGS: -I/usr/include
#cgo LDFLAGS: -L/usr/lib -lbz2
#include 
#include 
bz_stream* bz2alloc() { return calloc(1, sizeof(bz_stream)); }
int bz2compress(bz_stream *s, int action,
                char *in, unsigned *inlen, char *out, unsigned *outlen);
void bz2free(bz_stream* s) { free(s); }
*/
import "C"

import (
    "io"
    "unsafe"
)

type writer struct {
    w      io.Writer // 基本輸出流
    stream *C.bz_stream
    outbuf [64 * 1024]byte
}

// NewWriter 對(duì)于 bzip2 壓縮的流返回一個(gè) writer
func NewWriter(out io.Writer) io.WriteCloser {
    const blockSize = 9
    const verbosity = 0
    const workFactor = 30
    w := &writer{w: out, stream: C.bz2alloc()}
    C.BZ2_bzCompressInit(w.stream, blockSize, verbosity, workFactor)
    return w
}

在預(yù)處理過(guò)程中,cgo 產(chǎn)生一個(gè)臨時(shí)包,這個(gè)包里包含了所有C語(yǔ)言的函數(shù)和類型對(duì)應(yīng)的Go語(yǔ)言聲明。例如 C.bz_stream 和 C.BZ2_bzCompressInit。cgo 工具通過(guò)以一種特殊的方式調(diào)用C編譯器來(lái)發(fā)現(xiàn)在Go源文件中 import "C" 聲明之前的注釋中包含的C頭文件中的內(nèi)容。

在cgo注釋中還可以包含 #cgo 指令,用來(lái)指定C工具鏈中其他的選項(xiàng)。CFLAGS 和 LDFLAGS 分別對(duì)應(yīng)傳給C語(yǔ)言編譯器的編譯參數(shù)和鏈接器參數(shù),使它們可以從特定目錄找到bzlib.h頭文件和libbz2.a庫(kù)文件。這個(gè)例子假定已經(jīng)在 /usr 目錄成功安裝了bzip2庫(kù)。根據(jù)個(gè)人的安裝情況,可以修改或者刪除這些標(biāo)記。(這里還有一個(gè)純C生成的cgo綁定,不依賴bzip2靜態(tài)庫(kù)和操作系統(tǒng)的具體環(huán)境,具體訪問(wèn)github: https://github.com/chai2010/bzip2 ,這里就順帶提一下現(xiàn)在有更方便的實(shí)現(xiàn)方式了)

NewWriter 調(diào)用C函數(shù) BZ2_bzCompressInit 來(lái)初始化流的緩沖區(qū)。在 writer 結(jié)構(gòu)體中還包含一個(gè)額外的緩沖區(qū)用來(lái)耗盡解壓縮器的輸出緩沖區(qū)。

Write方法

下面所示的 Write 方法將未解壓的數(shù)據(jù)寫入壓縮器中,然后在一個(gè)循環(huán)中調(diào)用 bz2compress 函數(shù),直到所有的數(shù)據(jù)壓縮完畢。Go程序可以訪問(wèn)C的類型(比如 bz_stream、char 和 uint),C的函數(shù)(比如 bz2compress),甚至是類似C的預(yù)處理宏的對(duì)象(比如 BZ_RUN),這些都通過(guò) C.x 的方式來(lái)訪問(wèn)。即使類型 C.unit 和 Go 的 uint 長(zhǎng)度相同,它們的類型也是不同的:

// bzip 包中的文件 bzip2.go 的第二部分

func (w *writer) Write(data []byte) (int, error) {
    if w.stream == nil {
        panic("closed")
    }
    var total int // 寫入的未壓縮字節(jié)數(shù)

    for len(data) > 0 {
        inlen, outlen := C.uint(len(data)), C.uint(cap(w.outbuf))
        C.bz2compress(w.stream, C.BZ_RUN,
            (*C.char)(unsafe.Pointer(&data[0])), &inlen,
            (*C.char)(unsafe.Pointer(&w.outbuf)), &outlen)
        total += int(inlen)
        data = data[inlen:]
        if _, err := w.w.Write(w.outbuf[:outlen]); err != nil {
            return total, err
        }
    }
    return total, nil
}

每一次的迭代首先計(jì)算傳說(shuō)數(shù)據(jù) data 剩余的長(zhǎng)度以及輸出緩沖 w.outbuf 的容量。然后把這兩個(gè)值的地址以及 data 和 w.outbuf 的地址都傳遞給 bz2compress 函數(shù)。兩個(gè)長(zhǎng)度信息傳地址而不傳值,這樣C函數(shù)就可以更新這兩個(gè)值。這兩個(gè)值記錄的分別是已壓縮的數(shù)據(jù)和壓縮后數(shù)據(jù)的大小。然后把每塊壓縮后的數(shù)據(jù)寫入底層的 io.Writer(w.w.Write方法)。

Close方法

Close方法和Write方法結(jié)構(gòu)類似,通過(guò)一個(gè)循環(huán)將剩余的壓縮后的數(shù)據(jù)從輸出緩沖區(qū)寫入底層:

// bzip 包中的文件 bzip2.go 的第三部分

// Close 方法清空壓縮的數(shù)據(jù)并關(guān)閉流
// 它不會(huì)關(guān)閉底層的 io.Writer
func (w *writer) Close() error {
    if w.stream == nil {
        panic("closed")
    }
    defer func() {
        C.BZ2_bzCompressEnd(w.stream)
        C.bz2free(w.stream)
        w.stream = nil
    }()
    for {
        inlen, outlen := C.uint(0), C.uint(cap(w.outbuf))
        r := C.bz2compress(w.stream, C.BZ_FINISH, nil, &inlen,
            (*C.char)(unsafe.Pointer(&w.outbuf)), &outlen)
        if _, err := w.w.Write(w.outbuf[:outlen]); err != nil {
            return err
        }
        if r == C.BZ_STREAM_END {
            return nil
        }
    }
}

壓縮完成后,Close 方法最后會(huì)調(diào)用 C.BZ2_bzCompressEnd 來(lái)釋放流緩沖區(qū),這寫語(yǔ)句寫在 defer 中來(lái)確保所有路徑返回后都會(huì)釋放資源。這個(gè)時(shí)候,w.stream 指針就不能安全地解引用了,要把它設(shè)置為 nil,并且在方法調(diào)用的開(kāi)頭添加顯式的 nil 檢查。這樣如果用戶在 Close 之后錯(cuò)誤地調(diào)用方法,程序就會(huì)panic。

使用bzip包

下面的程序,使用上面的程序包實(shí)現(xiàn)bzip2壓縮命令。用起來(lái)和很多UNIX系統(tǒng)上面的 bzip2 命令相似:

// bzipper 讀取輸入、使用 bzip2 壓縮然后輸出數(shù)據(jù)
package main

import (
    "io"
    "log"
    "os"

    "gopl/bzip"
)

func main() {
    w := bzip.NewWriter(os.Stdout)
    if _, err := io.Copy(w, os.Stdin); err != nil {
        log.Fatalf("bzipper: %v\n", err)
    }
    if err := w.Close(); err != nil {
        log.Fatalf("bzipper: close: %v\n", err)
    }
}

總結(jié)

這里演示了如何將C庫(kù)鏈接進(jìn)Go程序中。(反過(guò)來(lái),可以將Go程序編譯為靜態(tài)庫(kù)然后鏈接進(jìn)C程序中,也可以編譯為動(dòng)態(tài)庫(kù)通過(guò)C程序來(lái)加載和共享
另外還有一些別的問(wèn)題。

沒(méi)有bzip2庫(kù)
這里的例子是假設(shè)已經(jīng)安裝了 bzip2 庫(kù)。如果是安裝位置不對(duì),可以修改 #cgo 來(lái)解決。另外,也有人提供了不用依賴本機(jī)上的 bzip2 庫(kù)的實(shí)現(xiàn)。
這里有一個(gè)從純C代碼生成的cgo綁定,不依賴bzip2靜態(tài)庫(kù)和操作系統(tǒng)的具體環(huán)境,具體請(qǐng)?jiān)L問(wèn) https://github.com/chai2010/bzip2

并發(fā)安全問(wèn)題
上面的實(shí)現(xiàn)中,結(jié)構(gòu)體 writer 不是并發(fā)安全的。并且并發(fā)調(diào)用 Close 和 Write 也會(huì)導(dǎo)致C代碼崩潰。這個(gè)問(wèn)題可以用加鎖的方式來(lái)避免,使用 sync.Mutex 可以保證 bzip2.writer 在多個(gè)goroutines中被并發(fā)調(diào)用是安全的。

os/exec 包的實(shí)現(xiàn)
開(kāi)篇提到了還有一種實(shí)現(xiàn)方式:用 os/exec 包以輔助子進(jìn)程的方式來(lái)調(diào)用C程序。
可以使用 os/exec 包將 /bin/bzip2 命令作為一個(gè)子進(jìn)程執(zhí)行。實(shí)現(xiàn)一個(gè)純Go的 bzip.NewWriter 來(lái)替代原來(lái)的實(shí)現(xiàn)。這樣就是一個(gè)純Go的實(shí)現(xiàn),不需要C言語(yǔ)的基礎(chǔ)。不過(guò)雖然是純Go的實(shí)現(xiàn),但還是要依賴本機(jī)能夠運(yùn)行 /bin/bzaip2 命令的。

安裝cgo環(huán)境

MinGW(Minimalist GNU For Windows),是個(gè)精簡(jiǎn)的Windows平臺(tái)C/C++、ADA及Fortran編譯器,相比Cygwin而言,體積要小很多,使用較為方便。
實(shí)際上也沒(méi)那么方便...

錯(cuò)誤信息

首先,會(huì)遇到下面的報(bào)錯(cuò):

exec: "gcc": executable file not found in %PATH%

這是因?yàn)槿鄙賕cc編譯器。

另外如果裝好了,可能還會(huì)遇到這個(gè)問(wèn)題:

cc1.exe: sorry, unimplemented: 64-bit mode not compiled in

這是因?yàn)樾枰惭b一個(gè)64位的版本。

最后還會(huì)有一個(gè)小問(wèn)題的報(bào)錯(cuò),類似下面這樣:

.\main.go:9:45: could not determine kind of name for C.FLT_MAX

看這個(gè)報(bào)錯(cuò)的內(nèi)容是我們的代碼的問(wèn)題。其實(shí)就是 cgo 注釋必須緊挨著 import "C",中間不能有空行。

下載安裝

可以去這里下載:
https://sourceforge.net/projects/mingw-w64/
不過(guò)這只是一個(gè)在線安裝程序。應(yīng)該是國(guó)外的資源,在線安裝無(wú)法成功。
還可以直接去下載編譯好的版本,資源都在頁(yè)面的Files分頁(yè)里查找。因?yàn)橛幸恍┚幾g的選項(xiàng)來(lái)適配各種系統(tǒng)和版本,下載了下面路徑下的文件:

Home/Toolchains targetting Win64/Personal Builds/mingw-builds/8.1.0/threads-win32/seh/

文件名是這個(gè):x86_64-8.1.0-release-win32-seh-rt_v6-rev0.7z
具體的下載地址是:
https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/7.3.0/threads-win32/seh/x86_64-7.3.0-release-win32-seh-rt_v5-rev0.7z
因?yàn)槭蔷幾g好的版本,無(wú)需安裝直接解壓就可以了。

設(shè)置環(huán)境變量

解壓后放在系統(tǒng)的某個(gè)目錄下,比如 gcc.exe 這個(gè)文件的路徑是這個(gè):D:\MinGW\bin\gcc.exe。下面就按這個(gè)路徑來(lái)設(shè)置環(huán)境變量。
我的電腦->屬性->高級(jí)系統(tǒng)設(shè)置,在“高級(jí)”分頁(yè)下的“環(huán)境變量...”里就可以設(shè)置環(huán)境變量。
添加一條環(huán)境變量:

變量(KET)值(VALUE)
MinGW D:\MinGW

然后在已有的環(huán)境變量 Path 里添加一項(xiàng) %MinGW%\bin,到此設(shè)置完成。

變量(KET)值(VALUE)
Path 省略其他已有的值...;%MinGW%\bin;

運(yùn)行一個(gè)簡(jiǎn)單cgo程序

下面是一段簡(jiǎn)單的cgo代碼:

package main

// #include 
import "C"
import "fmt"

func main() {
    fmt.Println("Max float value of float is", C.FLT_MAX)
}

就像普通的Go程序那樣編譯運(yùn)行就好了。


當(dāng)前名稱:使用cgo調(diào)用C代碼
轉(zhuǎn)載來(lái)于:http://weahome.cn/article/pdepsh.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部