我和我的兒子在上周末幹了一件非常有意思的事情,我們開發了一個用 Go 編寫的命令列遊戲,最近我正在重寫一款曾經在年輕時開發的遊戲,當時用的還是 Kaypro II。![](https://raw.githubusercontent.com/studygolang/gctt-images/master/Using-C-Dynamic-Libraries-In-Go-Programs/kayproii.jpg)我鐘愛這台電腦,回想起曾經使用 BASIC 在上面日日夜夜開發遊戲,它非常便攜,把鍵盤摺疊起來就可以提著走,哈哈。額,我好像偏題了,還是回到Go上面來。我發現一種使用 VT100 控制符來顯示簡單螢幕的方法,並且在上面開始寫一些商務邏輯。但隨後就遇到了一些艱難的問題,我要用倒敘的方式來描述一下,比如當不按斷行符號鍵時,我就沒辦法從標準輸入中擷取資料,啊啊啊啊啊,為了尋找解決方案,我整個周末都在閱讀資料,甚至找到兩個相關的 Go 語言庫,但是並沒有起到什麼作用。後來我意識到,如果要實現這個效果,那麼要使用 C 語言來編寫功能函數,連結成動態庫後再由 Go 調用。在一家愛爾蘭小酒吧中我開發了四個小時終於解決了這個問題,在這裡我要好好感謝一下吉尼斯黑啤酒給我帶來的啟發和激勵。要知道我過去十年一直是在 windows 下使用 C#,十年之前的話我還是在大微軟環境中用 C/C++ 開發,所以我對 Linux 和 Mac 系統下的 gcc、gco,靜態和動態庫都不熟悉,我到目前都是一直在學習這些東西,畢竟要學的東西很多。經過一番探索之後,問題開始變得明朗起來,我需要使用 ncurse 動態庫(註:ncurse 庫提供了 API,可以允許程式員編寫獨立於終端的基於文本的使用者介面),於是我決定先寫一個簡單一實例程式,如果能在 C 編譯器下能用,那麼我覺得應該在 Go 語言下也是可以的。![](https://raw.githubusercontent.com/studygolang/gctt-images/master/Using-C-Dynamic-Libraries-In-Go-Programs/Screen%2BShot%2B2013-08-20%2Bat%2B1.57.49%2BPM.png)在 Mac 系統下 ncurse 庫的路徑是 /usr/lib,這有個關於庫的文檔:[https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/ncurses.3x.html](https://www.ardanlabs.com/blog/broken-link.html)下面是 C 語言的測試程式標頭檔:test.h```cint GetCharacter();void InitKeyboard();void CloseKeyboard();```然後是源檔案:test.c```c#include <curses.h>#include <stdio.h>#include "test.h"int main() { InitKeyboard(); printf("\nEnter: "); refresh(); for (;;) { int r = GetCharacter(); printf("%c", r); refresh(); if (r == ‘q’) { break; } } CloseKeyboard(); return 0;}void InitKeyboard() { initscr(); noecho(); cbreak(); keypad(stdscr, TRUE); refresh();}int GetCharacter() { return getch();}void CloseKeyboard() { endwin();}```接下來就是困難的部分了,該如何使用 gcc 編譯器來編譯這個測試程式呢? 我想確保是用同一個編譯器來編譯 Go 和 C 語言,而且只用到最少的編譯參數和標誌。經過一個小時的探索,我寫了下面這個 makefile,說實話,這是我第一次寫 makefile 檔案。makefile```makefilebuild: rm -f test gcc -c test.c gcc -lncurses -r/usr/lib -o test test.o rm -f *.o```當運行 make 命令時,會在當前路徑下搜尋 makefile 檔案並執行,需要注意的是每個命令右邊的縮排必須使用一個 Tab 鍵,如果你使用空格鍵,那麼可能會遇到問題,當然你手動運行那些命令也能工作,不過為了方便我還是用 makefile。讓我們來分析一下當 makefile 被執行時 gcc 編譯器是怎麼處理的:下面的調用會使 gcc 根據源碼建立一個 test.o 的檔案。`-c` 參數是告訴 gcc 只需要編譯源碼檔案並且建立 test.o 的檔案。(註:如果不加 -c,那麼會自動執行後面的連結流程)```makefilegcc -c test.c```接下來,gcc 會把 test.o 和 libncurses.dylib 進行連結處理,連結後會產生 test 可執行檔。命令中的 l(小寫 L)參數是讓 gcc 去連結 libncurses.dylib 檔案,-r(小寫 R)參數指定了 gcc 去哪個路徑下擷取這個庫檔案,-o(小寫 O)參數是指定 gcc 匯出可執行檔的名字,最後讓gcc在連結操作中包含 test.o。```makefilegcc -lncurses -r/usr/lib -o test test.o```以上兩條命令就能編譯出一個能正常工作的 test 程式,你可以在命令列下輸入 `./test` 來執行它:![](https://raw.githubusercontent.com/studygolang/gctt-images/master/Using-C-Dynamic-Libraries-In-Go-Programs/Screen%2BShot%2B2013-08-20%2Bat%2B3.34.39%2BPM.png)程式運行後有個迴圈監聽字元輸入,能把我輸入的字元進行顯示,當我按下 `q` 鍵時,程式會關閉。現在我已經有一個使用 ncurses 動態庫的並且能啟動並執行程式,但是我想在 Go 中使用它,現在我需要找到一種方法能把之前寫的程式封裝成動態庫,然後被 Go 使用。非常幸運的是我找到了一些非常棒的文章,裡麵包含了動態庫給 Go 使用的方法:http://www.adp-gmbh.ch/cpp/gcc/create_lib.htmlhttp://stackoverflow.com/questions/3532589/how-to-build-a-dylib-from-several-o-in-mac-os-x-using-gcc讓我們在Go中實現這一切吧,先來建立一個新的工程:![](https://raw.githubusercontent.com/studygolang/gctt-images/master/Using-C-Dynamic-Libraries-In-Go-Programs/Screen%2BShot%2B2013-08-20%2Bat%2B6.48.52%2BPM.png)我建立了一個名叫 Keyboard 的檔案夾,裡面有兩個子檔案夾,分別叫 DyLib 和 TestApp。在 DyLib 檔案夾中我們放入C的動態庫源碼和 makefile 檔案,在 TestApp 中只有一個 main.go 檔案,到時就使用這個檔案來測試 Go 和 C 語言的動態庫互動。這是為動態庫準備的 C 標頭檔,和之前 test 標頭檔中的內容一樣:keyboard.h```cint GetCharacter();void InitKeyboard();void CloseKeyboard();```然後是實現了我們所需功能的 C 源碼檔案,除了沒有 main 函數,其他內容也和之前的 test 程式中的代碼相同,因為我們要建立一個庫檔案,所以不需要 main 函數。keyboard.c```c#include <curses.h>#include "keyboard.h"void InitKeyboard() { initscr(); noecho(); cbreak(); keypad(stdscr, TRUE); refresh();}int GetCharacter() { return getch();}void CloseKeyboard() { endwin();}```接下來是為建立動態庫準備的makefile檔案:makefile```makefiledynamic: rm -f libkeyboard.dylib rm -f ../TestApp/libkeyboard.dylib gcc -c -fPIC keyboard.c gcc -dynamiclib -lncurses -r/usr/lib -o libkeyboard.dylib keyboard.o rm -f keyboard.o cp libkeyboard.dylib ../TestApp/libkeyboard.dylibshared: rm -f libkeyboard.so rm -f ../TestApp/libkeyboard.so gcc -c -fPIC keyboard.c gcc -shared -W1 -lncurses -r/usr/lib -soname,libkeyboard.so -o libkeyboard.so keyboard.o rm -f keyboard.o cp libkeyboard.so ../TestApp/libkeyboard.so```使用這個 makefile 可以建立一個動態庫或者共用庫。使用 make 是如果不加任何參數,那麼會執行 dynamic 標記下的那些命令列,如果加上 `shared` 參數,就會建立一個共用庫檔案。要注意一個重要的 **-fPIC** 標記,有這個標記的時候 gcc 會產生共用庫所需要的地址無關代碼,當沒有這個標記時會產生可執行程式。讓我們用 makefile 檔案來建立動態庫:![](https://raw.githubusercontent.com/studygolang/gctt-images/master/Using-C-Dynamic-Libraries-In-Go-Programs/Screen%2BShot%2B2013-08-20%2Bat%2B6.53.51%2BPM.png)執行 make 命令,它會運行 makefile 檔案中 dynamic 部分的命令,等運行完畢後我們就有了新的動態庫:![](https://raw.githubusercontent.com/nicedevcn/gctt-images/master/Using-C-Dynamic-Libraries-In-Go-Programs/Screen%2BShot%2B2013-08-20%2Bat%2B6.55.09%2BPM.png)到了這一步我們可以在 DyLib 和 TestApp 檔案夾下看到產生的 libkeyboard.dylib 檔案。有個事情忘記說了,所產生的動態庫或共用庫名字必須以 lib 開頭,如果不這麼做的話就無法正常使用,同時庫檔案必須放置在程式運行時能夠載入的工作目錄下。接下來我們來看一下 Go 下的測試程式碼:```gopackage main/*#cgo CFLAGS: -I../DyLib#cgo LDFLAGS: -L. -lkeyboard#include <keyboard.h>*/import "C"import ( "fmt")func main() { C.InitKeyboard() fmt.Printf("\nEnter: ") for { r := C.GetCharacter() fmt.Printf("%c", r) if r == ‘q’ { break } } C.CloseKeyboard()}```Go 開發小組提供了兩篇文章來解釋 Go 是如何和 C 語言的動態庫進行互動的,對於理解上面的代碼擁有非常重要的作用:- http://golang.org/cmd/cgo/- http://golang.org/doc/articles/c_go_cgo.html如果你如何關聯 C++ 的庫感興趣的話,那麼 SWIG(簡單封裝和介面產生器)值得你去瞭解:- http://www.swig.org/- http://www.swig.org/Doc2.0/Go.htmlSWIG 還是留到下次再討論,先來分解一下上面的 Go 代碼:```gopackage main/*#cgo CFLAGS: -I../DyLib#cgo LDFLAGS: -L. -lkeyboard#include <keyboard.h>*/import "C"```為了提供給編譯器和連結器所需要的參數,我們需要使用特殊的 cgo 命令,這是一組內部提供的命令,必須在 `import "C"` 語句上方聲明,並且和 import 命令之間不能有空行或者其他語句,否則會造成編譯錯誤。我們在編譯和連結程式的過程中需要向 Go 提供上面的參數來產生進程標識 ,CFLAGS 標記向編譯器提供參數,我們讓編譯器能在共用庫所在檔案夾中找到標頭檔,LDFLAGS 向連結器提供一些參數,可以看到我們使用了兩個,`-L` 向連結器提供了動態庫的路徑,`-l` 則提供了動態庫的名字。有一點需要注意,當我們指定庫名的時候不需要包含首碼(lib)和尾碼名(.dylib),程式會自動在名字前面加上 lib,在後面加上 .dylib 或者 .so 尾碼名.最後我們讓 Go 匯入一個特殊的包 `"C"`,它提供了 Go 語言層面訪問我們庫的方式,沒有這個包,那我們的這一切都沒法完成。通過以下方式,我們可以調用庫中的函數:```goC.InitKeyboard()r := C.GetCharacter()C.CloseKeyboard()```有了這個 `"C"` 包,能把標頭檔中的每個函數進行封裝,這些封裝後的函數能將輸入和輸出進行相應解析處理,請留意我們是如何使用原生 Go 類型和文法從鍵盤輸入中擷取字元的。現在我們可以在命令列中構建和運行一個測試程式了:![](https://raw.githubusercontent.com/studygolang/gctt-images/master/Using-C-Dynamic-Libraries-In-Go-Programs/Screen%2BShot%2B2013-08-20%2Bat%2B7.30.11%2BPM.png太棒了,程式正常工作了!有了這些能讓遊戲真正好玩起來的鍵盤事件,現在我和兒子可以繼續開發我們的遊戲了。我花費了數小時處理這一切,如果想要做得更好,那麼還需要學習更多的知識點,過段時間我會研究一下 SWIG 和 C++ 物件導向庫的結合,不過現在能引入和使用 C 語言庫已經非常好了。如果你想瀏覽和擷取這些代碼,我已經把項目放到了 Github 倉庫的 Keyboard 下,好好享用!!閱讀第二章:[Using CGO with Pkg-Config And Custom Dynamic Library Locations](https://www.ardanlabs.com/blog/2013/08/using-cgo-with-pkg-config-and-custom.html)
via: https://www.ardanlabs.com/blog/2013/08/using-c-dynamic-libraries-in-go-programs.html
作者:William Kennedy 譯者:nicedevcn 校對:polaris1119
本文由 GCTT 原創編譯,Go語言中文網 榮譽推出
本文由 GCTT 原創翻譯,Go語言中文網 首發。也想加入譯者行列,為開源做一些自己的貢獻嗎?歡迎加入 GCTT!
翻譯工作和譯文發表僅用於學習和交流目的,翻譯工作遵照 CC-BY-NC-SA 協議規定,如果我們的工作有侵犯到您的權益,請及時聯絡我們。
歡迎遵照 CC-BY-NC-SA 協議規定 轉載,敬請在本文中標註並保留原文/譯文連結和作者/譯者等資訊。
文章僅代表作者的知識和看法,如有不同觀點,請樓下排隊吐槽
482 次點擊