我們知道庫一般有靜态庫和動态庫2種:
- 靜态庫是編譯時就連結到可執行檔案中的;
- 動态庫是在程式運作時再進行加載的。
故本文讨論的連結與加載方式是指對動态庫而言的。
一、動态庫的加載方式
1、隐式加載
就是我們需要準備好.h、.lib或者.so,對頭檔案進行包含,并添加對lib的連結指令,來完成對庫函數的調用。這種連結方式,稱之為隐式連結。
在程式從開始運作時,就會按照系統中一定的搜尋路徑,尋找動态庫,找到就自動加載它,才能成功運作程式,這些步驟,是系統自動完成的。
一般來說,隐式連結的動态庫,在程式加載時,無需使用者幹涉,便稱之為隐式加載。
一般通常使用隐式加載的方式,比顯示加載,代碼編寫更簡單。
2、顯式加載
我們對動态庫的調用,是在代碼中直接使用LoadLibrary,或其他加載函數,直接對dll或so進行加載,然後解析檔案中的函數符号,并調用該函數。這種連結方式,稱之為顯式連結。
這種加載方式依賴于使用者的加載代碼,是以是程式運作起來後,屬于延遲加載。需要使用者明确知道庫檔案名稱,故稱之為顯式加載。
這種方式,解析函數符号的代碼較多,編寫不友善。
二、Windows下程式如何查找dll
1、搜尋順序
在win系統下,應用程式搜尋其依賴的dll時,按如下順序進行查找:
- 應用程式所在的路徑;
- Windows的SYSTEM目錄,如C:\Windows\System,通過調用GetSystemDirectory函數可以擷取這個目錄的路徑;
- Windows目錄,如C:\Windows,通過調用GetWindowsDirectory函數可以擷取這個目錄的路徑;
- PATH環境變量指定的路徑。
上述查找順序,對隐式加載、顯式加載方式的dll均有效。
隐式加載dll時,沒什麼好說的,必定通過上述順序查找dll。
顯式加載dll時,由于使用了LoadLibrary函數,如下:
HMODULE WINAPI LoadLibrary(
_In_ LPCTSTR lpFileName
);
- 如果lpFileName字元串指定完整路徑,則該函數僅搜尋該dll的路徑。
- 如果lpFileName字元串指定一個沒有路徑的dll名稱或者相對路徑(如lpFileName=“Hello.dll”),則該函數使用标準搜尋政策來查找子產品,也就是使用上述順序進行查找"Hello.dll"。
2、指定隐式加載dll的路徑
對于需要隐式加載的dll,由于其加載過程由系統接管。即隐式加載的dll是在exe主體子產品之前加載進來的,執行順序上是先通過PE檔案的導入表加載隐式的dll,然後再加載EXE本體。
是以在程式exe中無法通過代碼,來指定隐式加載dll的路徑。
隻能通過修改PATH環境變量來指定加載dll的路徑,或者直接将dll移入可以被搜尋到的路徑中來實作。
3、指定顯式加載dll的路徑
對于需要顯式加載的dll,由于其LoadLibrary函數參數,即可指定dll的路徑;若隻提供了檔案名,則也可以将dll的路徑設定到PATH環境變量中,或者直接将dll移入可以被搜尋到的路徑中。
4、小結
dll的搜尋順序,對于無論是顯式、隐式加載dll,都是有效的,因為都需要查找dll。隻不過顯式加載時,它通過傳遞參數直接定位到具體dll時,不需要額外搜尋。
牢記:使用隐式加載時,程式中無法通過代碼,來指定該dll搜尋路徑。
無論顯式、隐式加載,通常最簡單的辦法,就是将dll與exe放于同一目錄下。
三、Linux下程式如何查找so
1、搜尋順序
在linux系統下,應用程式搜尋其依賴的so時,按如下順序進行查找:
- gcc編譯時指定的運作時庫路徑 -Wl,-rpath
- 環境變量LD_LIBRARY_PATH指定的路徑
- 從cache檔案/etc/ld.so.cache中查找,該檔案包含了先前在庫路徑中找到的編譯好的候選庫清單。
- 系統預設庫位置/lib和/usr/lib路徑
上述查找順序,對隐式加載、顯式加載方式的so均有效。
隐式加載so時,沒什麼好說的,必定通過上述順序查找so。
顯式加載so時,由于使用了dlopen函數,如下:
- 如果filename字元串指定完整路徑,則該函數僅搜尋該dll的路徑。
- 如果filename字元串指定一個沒有路徑的dll名稱或者相對路徑(如lpFileName=“Hello.so”),則該函數使用标準搜尋政策來查找子產品,也就是使用上述順序進行查找"Hello.so"。
關于linux下顯式加載so的例子,可參考《Linux 動态加載并調用動态庫(.so)方法介紹》。
2、指定隐式加載so的路徑
對于需要隐式加載的so,由于其加載過程由系統接管。即隐式加載的so是在程式主體子產品之前加載進來的,執行順序上是先加載隐式的so,然後再加載程式本體。
是以在程式中無法通過代碼,來指定隐式加載so的路徑。
但是可以通過修改上述順序中任一環節,來指定隐式加載的so路徑。
linux與win有差別,win下直接将dll和exe放于同一目錄就可以找到。但是在linux下不行,這顯得有些智障。。。主要是linux的理念不一樣,系統設計者希望大家把so庫都放到系統指定的目錄下,但是個人并不喜歡這樣做,很多的應用so糅合在一起很亂。
推薦使用第一種,即指定gcc編譯選項來實作。
假設,我們使用Qt開發的程式,希望在程式所在目錄下lib/中去尋找so,那麼可以在程式工程的.pro檔案中,添加如下編譯選項:
QMAKE_LFLAGS += -Wl,-rpath=./lib
則該程式啟動時,會在./lib/去找so。當然也可以改成./目前目錄下,這就和win下時很像了。
另外,如果程式顯示加載1.so,1.so隐式加載調用2.so,出現找不到2.so的問題。那麼,可以在1.so代碼所屬工程.pro中,添加上述的編譯選項,就可以解決這個問題。
3、指定顯式加載so的路徑
對于需要顯式加載的so,由于其dlopen函數參數,即可指定so的路徑;若隻提供了檔案名,則可以通過修改上述順序中任一環節,來指定so路徑。
4、小結
so的搜尋順序,對于無論是顯式、隐式加載,都是有效的,因為都需要查找so。隻不過顯式加載時,它通過傳遞參數直接定位到具體so時,不需要額外搜尋。
牢記:使用隐式加載時,程式中無法通過代碼,來指定該so搜尋路徑。但是可以通過編譯選項來指定。
四、總結
win、linux下動态庫的加載和搜尋機制很相似,但是有一些不同,主要不同之處展現在,隐式加載動态庫時:
- win下,隻能通過修改PATH指定庫路徑,但是預設支援搜尋程式目前目錄。
- linux下,支援gcc編譯選項指定庫路徑,但是預設不支援搜尋程式目前目錄。
另外,在win下,使用隐式加載時,程式中無法通過代碼,來指定該dll搜尋路徑。
在linux下,雖然依然不能通過程式中代碼來,指定該so搜尋路徑;但是可以使用gcc編譯選項的方式,提前指定該程式搜尋so的路徑,也算是一種很好的補充方式。
若對你有幫助,歡迎點贊、收藏、評論,你的支援就是我的最大動力!!!
同時,阿超為大家準備了豐富的學習資料,歡迎關注公衆号“超哥學程式設計”,即可領取。