最近需要做一些在go中使用動態C++庫的工作,經常碰到找不到動態庫路徑這種情況,所以就花點時間,專門做一下實驗來瞭解Go。
一、範例程式碼目錄結構(假設代碼根目錄為/home/gdc/cgotest): ----|bin:
----|pkg
----|src
--------|main
------------|main.go
--------|oidb
------------|hello
----------------|hello.go:
----------------|api.h
------------|lib
----------------|libapi.so
二、代碼
api.h檔案內容:
#ifndef __API_H__
#define __API_H__
void hello();
#endif
hello.go檔案內容:
package hello
/*
#cgo CFLAGS: -I./include
#cgo LDFLAGS: -L../lib -lapi
#include "api.h"
*/
import "C"
func Hello(){
C.hello();
}
main.go檔案內容:
package main
import "oidb/hello"
func main(){
hello.Hello();
}
三、前提
A. 將該代碼目錄添加到GOPATH環境變數中。——export GOPATH=$GOPATH:/home/gdc/cgotest
B. 環境變數LD_LIBRARY_PATH值為空白
四、關於cgo中使用動態C/C++庫的一些小實驗:
A. 按照如上的目錄組織及檔案內容,可以在任意地方執行go build oidb/hello或者 go build oidb/hello。
執行go build oidb/hello這個命令,可能會沒啥反應,但是可以告訴你編譯通過了。
執行go build oidb/hello這個命令,會在代碼根目錄下的pkg/linux_amd64目錄(如果不存在會自動建立)建立oidb/hello.a。
OK,說明該包已經可以給別人使用了。但是別人能否正常使用呢。接下來做一些main.go相關實驗。
B. 很遺憾,此時我在很多位置嘗試執行go build main 或者 go install main,都會提示我找不到libapi.so動態庫。
但有趣的是,當我在lib或hello目錄下執行go build main 或者 go install main時,都會成功。其中執行go install main會在代碼根目錄的bin子目錄下產生
一個main可執行檔,go build main會在目前的目錄下產生一個main可執行程式。然後當我嘗試執行這個位於bin目錄下的main可執行檔後,會提示找
不到libapi.so這個動態庫。
然後我又嘗試將lib目錄複寫到bin目錄下和代碼根目錄下,然後在bin子目錄下執行main可執行檔,還是提示找不到libapi.so動態庫。
最後,我拿出終極大招,執行"export LD_LIBRARY_PATH=/home/gdc/cgotest/lib"(此時我將lib目錄移動到代碼根目錄下了),然後就可以正常
運行main可執行程式了。
然後我又將該lib目錄移動到bin目錄和src目錄下,然後分別用export命令將這個lib目錄的新位置添加到LD_LIBRARY_PATH環境變數中,然後執行main,都可以正常執行。
小結: main包的編譯理解:
當編譯或安裝main包時,go會以此時所處的位置為目前的目錄。然後如果其引用的某個包中使用相對目錄依賴某個動態庫C/C++庫。那麼會從目前的目錄出發,根據那個相對位置去找動態庫。所以上面在編譯或安裝main包時,唯獨在lib或hello目錄下成功通過編譯了,這就是因為go以從目前的目錄出發,到其父目錄的lib子目錄下尋找libapi.so動態庫,然後成功找到,從而通過編譯。其實不一定非要在lib或者hello目錄下編譯main包。只要確保編譯main包位置的父目錄下有一個子目錄lib,同時該lib目錄下有libapi.so這個檔案,即可通過編譯。比如,我將lib目錄在代碼根目錄下,然後我轉到bin目錄下,然後就可以成功編譯或安裝main包了。
當然了,如果你在使用動態庫時使用"-L"選項設定的是絕對目錄而不是相對目錄,那麼編譯或安裝main包,就沒有這麼多限制了。你可以在任意位置編譯或安裝。
main可執行程式執行的理解:
不管你在代碼中使用"-L"選項指定的動態庫位置是相對目錄或絕對目錄,要想執行這個main可執行程式,都要將所依賴的動態庫所在的目錄添加到環境變數LD_LIBRARY_PATH中(或者將動態庫拷貝到系統預設的庫搜尋路徑下,但是老大不允許啊,鬱悶)。
C. 下面,再做一些關於直接編譯main.go檔案的實驗吧。
這個的編譯與編譯main包一樣的——"-L"使用相對路徑,那麼必須確保編譯的位置在加上相對路徑能最終找到動態庫;"-L"使用絕對路徑,則可以在任意位置編譯。
D. 最後,再做一些關於go中設定環境變數的實驗。因為main程式的執行需要依賴LD_LIBRARY_PATH這個環境變數的值。
D1. 第一種方法:魏老大說的,就是寫一個shell指令碼,在這個指令碼中先執行export語句,將動態庫的路徑加入到LD_LIBRARY_PATH中。然後再運行程式。
OK。這個方法通過。
D2. 第二種方法:在go中設定環境變數的值。
經過自己實驗,然後問魏老大,感覺這是不可能的。因為當程式啟動時,系統就會自動載入該程式所依賴的那些庫,而此時你在程式中設定環境變數的 代碼還沒運行呢。當然還是找不到動態庫。
一個解決辦法:自己手動載入動態庫。
參考了http://blog.csdn.net/joker0910/article/details/6103793這篇文章的手動載入庫,可以正常使用。
F. 補充(2014.07.21)。
忘記做go test hello這個實驗了。經實驗,發現假如在該包中的-L使用相對目錄來定位動態庫,那麼要想成功執行這個命令,需要以下兩點:
第一,要確保執行該命令的位置再加上相對目錄所得到的目錄需要包含所依賴的動態庫。這個與編譯或安裝main包很像。
第二,需要將所依賴的動態庫所在的目錄添加到環境變數LD_LIBRARY_PATH中。這個與執行main很像。
修改後的hello.go檔案內容如下:
package hello
/*
#cgo LDFLAGS: -ldl
#include <stdlib.h>
#include <dlfcn.h>
#include "api.h"
void hello_c(char* lib_path){
char* func_name="hello";
void* libc;
void (*hello_call)();
if(libc = dlopen(lib_path,RTLD_LAZY))
{
hello_call = dlsym(libc, func_name);
(*hello_call)();
dlclose(libc);
}
}
*/
import "C"
import "unsafe"
var EXTENSION_DIR string = "/home/guess/.davengu_workdir/go_learning/cgo/use_shared_library/src/oidb/lib/"
var OIDB_API string = "libapi.so"
//#cgo LDFLAGS: -L/home/guess/.davengu_workdir/go_learning/cgo/use_shared_library/src/oidb/lib -lapi
//#cgo LDFLAGS: -L../lib -lapi
func Hello() {
libPathC := C.CString(EXTENSION_DIR+OIDB_API);
defer C.free(unsafe.Pointer(libPathC));
C.hello_c(libPathC);
}
參考文章:
1. 《Linux下,手動載入動態庫》:http://blog.csdn.net/joker0910/article/details/6103793