回到简体

This commit is contained in:
chai2010
2016-02-15 11:06:34 +08:00
parent 9e878f9944
commit 2b37b23285
177 changed files with 2354 additions and 2354 deletions

View File

@@ -1,10 +1,10 @@
## 13.4. 通cgo調用C代
## 13.4. 通cgo用C代
Go程序可能遇到要訪問C語言的某些硬件驅動函數的場景或者是從一個C++語言實現的嵌入式數據庫査詢記録的場或者是使用Fortran語言實現的一些性代數庫的場景。C言作爲一個通用言,很多庫會選擇提供一C兼容的API用其他不同的編程語言實現(譯Go言需要也應該擁抱這些鉅大的代碼遺産)。
Go程序可能遇到要访问C语言的某些硬件驱动函数的场景或者是从一个C++语言实现的嵌入式数据库查询记录的场或者是使用Fortran语言实现的一些线性代数库的场景。C言作为一个通用言,很多库会选择提供一C兼容的API用其他不同的编程语言实现(译Go言需要也应该拥抱这些巨大的代码遗产)。
在本中,我們將構建一個簡易的數據壓縮程序,使用了一Go言自的叫cgo的用支援C言函數調用的工具。這類工具一般被稱爲 *foreign-function interfaces* 簡稱ffi, 且在似工具中cgo也不是唯一的。SWIG http://swig.org )是另一個類似的且被泛使用的工具SWIG提供了很多複雜特性以支援C++的特性但SWIG不是我們要討論的主
在本中,我们将构建一个简易的数据压缩程序,使用了一Go言自的叫cgo的用支援C言函数调用的工具。这类工具一般被称为 *foreign-function interfaces* 简称ffi, 且在似工具中cgo也不是唯一的。SWIG http://swig.org )是另一个类似的且被广泛使用的工具SWIG提供了很多复杂特性以支援C++的特性但SWIG不是我们要讨论的主
標準庫`compress/...`子包有很多流行的壓縮算法的編碼和解碼實現包括流行的LZW壓縮算法Unix的compress命令用的算法和DEFLATE壓縮算法GNU gzip命令用的算法些包的API的細節雖然有些差,但是它都提供了針對 io.Writer類型輸出的壓縮接口和提供了針對io.Reader類型輸入的解壓縮接口。例如:
标准库`compress/...`子包有很多流行的压缩算法的编码和解码实现包括流行的LZW压缩算法Unix的compress命令用的算法和DEFLATE压缩算法GNU gzip命令用的算法些包的API的细节虽然有些差,但是它都提供了针对 io.Writer类型输出的压缩接口和提供了针对io.Reader类型输入的解压缩接口。例如:
```Go
package gzip // compress/gzip
@@ -12,11 +12,11 @@ func NewWriter(w io.Writer) io.WriteCloser
func NewReader(r io.Reader) (io.ReadCloser, error)
```
bzip2壓縮算法,是基於優雅的Burrows-Wheeler變換算法,行速度比gzip要慢但是可以提供更高的壓縮比。標準庫的compress/bzip2包目前還沒有提供bzip2壓縮算法的實現。完全從頭開始實現是一個壓縮算法是一件繁的工作,而且 http://bzip.org 已經有現成的libbzip2的開源實現,不僅文檔齊全而且性能又好。
bzip2压缩算法,是基于优雅的Burrows-Wheeler变换算法,行速度比gzip要慢但是可以提供更高的压缩比。标准库的compress/bzip2包目前还没有提供bzip2压缩算法的实现。完全从头开始实现是一个压缩算法是一件繁的工作,而且 http://bzip.org 已经有现成的libbzip2的开源实现,不仅文档齐全而且性能又好。
如果是比小的C語言庫,我完全可以用Go言重新實現一遍。如果我們對性能也有特殊要求的,我們還可以用os/exec包的方法將C編寫的應用程序作爲一個子進程運行。隻有當你需要使用複雜而且性能更高的底C接口就是使用cgo的景了(譯註用os/exec包調用子程的方法會導致程序運行時依賴那個應用程序)。下面我們將通過一個例子述cgo的具用法。
如果是比小的C语言库,我完全可以用Go言重新实现一遍。如果我们对性能也有特殊要求的,我们还可以用os/exec包的方法将C编写的应用程序作为一个子进程运行。只有当你需要使用复杂而且性能更高的底C接口就是使用cgo的景了(译注用os/exec包用子程的方法会导致程序运行时依赖那个应用程序)。下面我们将通过一个例子述cgo的具用法。
譯註:本章采用的代都是最新的。因之前已出版的中包含的代碼隻能在Go1.5之前使用。Go1.6Go言已經明確規定了哪些Go言指可以之間傳入C言函。新代碼重點是增加了bz2alloc和bz2free的兩個函數,用bz_stream象空的申請和釋放操作。下面是新代中增加的註釋,説明這個問題
译注:本章采用的代都是最新的。因之前已出版的中包含的代码只能在Go1.5之前使用。Go1.6Go言已经明确规定了哪些Go言指可以之间传入C言函。新代码重点是增加了bz2alloc和bz2free的两个函数,用bz_stream象空的申请和释放操作。下面是新代中增加的注释,说明这个问题
```Go
// The version of this program that appeared in the first and second
@@ -37,9 +37,9 @@ bzip2壓縮算法是基於優雅的Burrows-Wheeler變換算法運行速度
// pointers to Go variables.
```
要使用libbzip2需要先建一bz_stream結構體,用保持入和輸出緩存。然有三個函數BZ2_bzCompressInit用初始化BZ2_bzCompress用於將輸入緩存的數據壓縮到輸出緩BZ2_bzCompressEnd用於釋放不需要的存。(目前不要心包的具體結構, 這個例子的目的就是演示各部分如何合在一起的。)
要使用libbzip2需要先建一bz_stream结构体,用保持入和输出缓存。然有三个函数BZ2_bzCompressInit用初始化BZ2_bzCompress用于将输入缓存的数据压缩到输出缓BZ2_bzCompressEnd用于释放不需要的存。(目前不要心包的具体结构, 这个例子的目的就是演示各部分如何合在一起的。)
可以在Go代中直接調用BZ2_bzCompressInit和BZ2_bzCompressEnd但是對於BZ2_bzCompress們將定義一個C語言的包裝函數,用它完成正的工作。下面是C代碼,對應一個獨立的文件。
可以在Go代中直接用BZ2_bzCompressInit和BZ2_bzCompressEnd但是对于BZ2_bzCompress们将定义一个C语言的包装函数,用它完成正的工作。下面是C代码,对应一个独立的文件。
<u><i>gopl.io/ch13/bzip</i></u>
```C
@@ -61,7 +61,7 @@ int bz2compress(bz_stream *s, int action,
}
```
現在讓我們轉到Go言部分,第一部分如下所示。其中`import "C"`句是比特别的。其實併沒有一叫C的包但是這行語句會讓Go編譯程序在編譯之前先行cgo工具。
现在让我们转到Go言部分,第一部分如下所示。其中`import "C"`句是比特别的。其实并没有一叫C的包但是这行语句会让Go编译程序在编译之前先行cgo工具。
```Go
// Package bzip provides a writer that uses bzip2 compression (bzip.org).
@@ -101,13 +101,13 @@ func NewWriter(out io.Writer) io.WriteCloser {
}
```
預處理過程中cgo工具生成一個臨時包用包含所有在Go言中訪問的C言的函數或類型。例如C.bz_stream和C.BZ2_bzCompressInit。cgo工具通以某特殊的方式調用本地的C編譯器來發現在Go源文件導入聲明前的註釋中包含的C文件中的容(譯註`import "C"`句前僅捱着的註釋是對應cgo的特殊法,對應必要的構建參數選項和C言代)。
预处理过程中cgo工具生成一个临时包用包含所有在Go言中访问的C言的函数或类型。例如C.bz_stream和C.BZ2_bzCompressInit。cgo工具通以某特殊的方式用本地的C编译器来发现在Go源文件导入声明前的注释中包含的C文件中的容(译注`import "C"`句前仅挨着的注释是对应cgo的特殊法,对应必要的构建参数选项和C言代)。
在cgo註釋中還可以包含#cgo指令,用於給C語言工具指定特殊的參數。例如CFLAGS和LDFLAGS分别對應傳給C語言編譯器的編譯參數和鏈接器參數,使它可以特定目找到bzlib.h文件和libbz2.a文件。這個例子假你已在/usr目成功安了bzip2。如果bzip2是安在不同的位置,你需要更新這些參數(譯註:這里有一個從純C代生成的cgo定,不依bzip2靜態庫和操作繫統的具體環境,具體請訪問 https://github.com/chai2010/bzip2 )。
在cgo注释中还可以包含#cgo指令,用于给C语言工具指定特殊的参数。例如CFLAGS和LDFLAGS分别对应传给C语言编译器的编译参数和链接器参数,使它可以特定目找到bzlib.h文件和libbz2.a文件。这个例子假你已在/usr目成功安了bzip2。如果bzip2是安在不同的位置,你需要更新这些参数(译注:这里有一个从纯C代生成的cgo定,不依bzip2静态库和操作系统的具体环境,具体请访问 https://github.com/chai2010/bzip2 )。
NewWriter函數通過調用C言的BZ2_bzCompressInit函數來初始化stream中的存。在writer結構中還包括了另一buffer於輸出緩存。
NewWriter函数通过调用C言的BZ2_bzCompressInit函数来初始化stream中的存。在writer结构中还包括了另一buffer于输出缓存。
下面是Write方法的實現,返成功壓縮數據的大小,主是一個循環中調用C言的bz2compress函數實現的。從代碼可以看到Go程序可以訪問C語言的bz_stream、char和uint型,可以訪問bz2compress等函,甚至可以訪問C語言中像BZ_RUN那的宏定全部都是以C.x語法訪問。其中C.uint型和Go言的uint類型併不相同,使它具有相同的大小也是不同的型。
下面是Write方法的实现,返成功压缩数据的大小,主是一个循环中调用C言的bz2compress函数实现的。从代码可以看到Go程序可以访问C语言的bz_stream、char和uint型,可以访问bz2compress等函,甚至可以访问C语言中像BZ_RUN那的宏定全部都是以C.x语法访问。其中C.uint型和Go言的uint类型并不相同,使它具有相同的大小也是不同的型。
```Go
func (w *writer) Write(data []byte) (int, error) {
@@ -131,9 +131,9 @@ func (w *writer) Write(data []byte) (int, error) {
}
```
在循的每次迭代中向bz2compress傳入數據的地址和剩部分的度,還有輸出緩存w.outbuf的地址和容量。這兩個長度信息通過它們的地址入而不是值入,因bz2compress函可能會根據已經壓縮的數據和壓縮後數據的大小更新這兩個值。每個塊壓縮後的數據被寫入到底的io.Writer。
在循的每次迭代中向bz2compress传入数据的地址和剩部分的度,还有输出缓存w.outbuf的地址和容量。这两个长度信息通过它们的地址入而不是值入,因bz2compress函可能会根据已经压缩的数据和压缩后数据的大小更新这两个值。每个块压缩后的数据被写入到底的io.Writer。
Close方法和Write方法有着似的結構,通過一個循環將剩餘的壓縮數據刷新到輸出緩存。
Close方法和Write方法有着似的结构,通过一个循环将剩余的压缩数据刷新到输出缓存。
```Go
// Close flushes the compressed data and closes the stream.
@@ -161,11 +161,11 @@ func (w *writer) Close() error {
}
```
壓縮完成Close方法用了defer函數確保函退出前調用C.BZ2_bzCompressEnd和C.bz2free放相的C語言運行時資源。此刻w.stream指針將不再有效,我們將它設置爲nil以保安全,然在每方法中增加了nil檢測,以防止用戶在關閉後依然錯誤使用相方法。
压缩完成Close方法用了defer函数确保函退出前用C.BZ2_bzCompressEnd和C.bz2free放相的C语言运行时资源。此刻w.stream指针将不再有效,我们将它设置为nil以保安全,然在每方法中增加了nil检测,以防止用户在关闭后依然错误使用相方法。
上面的實現中,不僅僅寫是非併發安全的,甚至併發調用Close和Write方法也可能致程序的的崩潰。脩複這個問題是練習13.3的容。
上面的实现中,不仅仅写是非并发安全的,甚至并发调用Close和Write方法也可能致程序的的崩溃。修复这个问题是练习13.3的容。
下面的bzipper程序使用我自己包實現的bzip2壓縮命令。它的行爲和許多Unix繫統的bzip2命令似。
下面的bzipper程序使用我自己包实现的bzip2压缩命令。它的行为和许多Unix系统的bzip2命令似。
<u><i>gopl.io/ch13/bzipper</i></u>
```Go
@@ -190,7 +190,7 @@ func main() {
}
```
在上面的景中,我使用bzipper壓縮了/usr/share/dict/words繫統自帶的詞典,938,848字節壓縮到335,405字。大是原始數據大小的三分之一。然使用繫統自帶的bunzip2命令行解壓。壓縮前後文件的SHA256哈希是相同了,這也説明了我們的壓縮工具是正的。(如果你的繫統沒有sha256sum命令麽請先按照練習4.2實現一個類似的工具)
在上面的景中,我使用bzipper压缩了/usr/share/dict/words系统自带的词典,938,848字节压缩到335,405字。大是原始数据大小的三分之一。然使用系统自带的bunzip2命令行解压。压缩前后文件的SHA256哈希是相同了,这也说明了我们的压缩工具是正的。(如果你的系统没有sha256sum命令么请先按照练习4.2实现一个类似的工具)
```
$ go build gopl.io/ch13/bzipper
@@ -204,8 +204,8 @@ $ ./bzipper < /usr/share/dict/words | bunzip2 | sha256sum
126a4ef38493313edc50b86f90dfdaf7c59ec6c948451eac228f2f3a8ab1a6ed -
```
演示了如何將一個C語言庫鏈接到Go言程序。相反, Go編譯爲靜態庫然後鏈接到C程序或者Go程序編譯爲動態庫然後在C程序中動態加載也都是可行的(譯註在Go1.5中Windows繫統的Go語言實現併不支持生成C語言動態庫或靜態庫的特性。不好消息是,目前已有人在嚐試解決這個問題,具體請訪問 [Issue11058](https://github.com/golang/go/issues/11058) )。里我們隻展示的cgo很小的一些方面更多的關於內存管理、指針、迴調函數、中斷信號處理、字符串、errno理、終結以及goroutines和繫統線程的關繫等,有很多細節可以討論。特别是如何Go言的指針傳入C函數的規則也是異常複雜的(譯註:簡單來説,要入C函的Go指指向的數據本身不能包含指或其他引用型;且C函在返迴後不能繼續持有Go指針;併且在C函數返迴之前Go指是被定的,不能導致對應指針數據被移動或棧的調部分的原因在13.2節有討論但是在Go1.5中還沒有被明確(譯註Go1.6將會明確cgo中的指使用規則)。如果要一步閲讀,可以 https://golang.org/cmd/cgo 始。
演示了如何将一个C语言库链接到Go言程序。相反, Go编译为静态库然后链接到C程序或者Go程序编译为动态库然后在C程序中动态加载也都是可行的(译注在Go1.5中Windows系统的Go语言实现并不支持生成C语言动态库或静态库的特性。不好消息是,目前已有人在尝试解决这个问题,具体请访问 [Issue11058](https://github.com/golang/go/issues/11058) )。里我们只展示的cgo很小的一些方面更多的关于内存管理、指针、回调函数、中断信号处理、字符串、errno理、终结以及goroutines和系统线程的关系等,有很多细节可以讨论。特别是如何Go言的指针传入C函数的规则也是异常复杂的(译注:简单来说,要入C函的Go指指向的数据本身不能包含指或其他引用型;且C函在返回后不能继续持有Go指针;并且在C函数返回之前Go指是被定的,不能导致对应指针数据被移动或栈的调部分的原因在13.2节有讨论但是在Go1.5中还没有被明确(译注Go1.6将会明确cgo中的指使用规则)。如果要一步阅读,可以 https://golang.org/cmd/cgo 始。
**練習 13.3** 使用sync.Mutex以保bzip2.writer在多goroutines中被併發調用是安全的。
**练习 13.3** 使用sync.Mutex以保bzip2.writer在多goroutines中被并发调用是安全的。
**練習 13.4**爲C庫依賴的限。 使用os/exec包啟動/bin/bzip2命令作爲一個子進程,提供一個純Go的bzip.NewWriter的替代實現(譯註:雖然是Go實現,但是運行時將依賴/bin/bzip2命令其他操作繫統可能無法運行)。
**练习 13.4**为C库依赖的限。 使用os/exec包启动/bin/bzip2命令作为一个子进程,提供一个纯Go的bzip.NewWriter的替代实现(译注:虽然是Go实现,但是运行时将依赖/bin/bzip2命令其他操作系统可能无法运行)。