c++ shared - 共享對象(.so),靜態庫(.a)和DLL(.so)之間的區別?




library linux (5)

我參與了一些有關Linux的圖書館的辯論,並想確認一些事情。

這是我的理解(請糾正我,如果我錯了,我會稍後編輯我的帖子),建立一個應用程序時有兩種使用庫的方式:

  1. 靜態庫(.a文件):在鏈接時,將整個庫的副本放入最終應用程序中,以便庫中的函數始終可供調用應用程序使用
  2. 共享對象(.so文件):在鏈接時,該對象僅通過相應的頭文件(.h)文件針對其API進行驗證。 直到運行時才需要它,庫實際上並不使用。

靜態庫的明顯優勢是它們允許整個應用程序是獨立的,而動態庫的好處是可以替換“.so”文件(即:由於安全性需要更新錯誤)而不需要重新編譯基礎應用程序。

我聽說有些人在共享對象和動態鏈接庫(DLL)之間做了區分,即使它們都是“.so”文件。 在Linux或任何其他符合POSIX標準的操作系統(例如:MINIX,UNIX,QNX等)上進行C / C ++開發時,共享對象和DLL之間是否有區別? 我被告知一個關鍵的區別(到目前為止)是共享對象僅在運行時使用,而DLL必須首先在應用程序中使用dlopen()調用打開。

最後,我也聽到一些開發人員提到“共享檔案”,據我了解,這些檔案本身也是靜態庫,但不會直接由應用程序使用。 相反,其他靜態庫將鏈接到“共享歸檔”,以將共享歸檔中的一些(但不是全部)功能/資源引入正在構建的靜態庫中。

預先感謝您的幫助。

更新

在向我提供這些條款的背景下,我發現了這些術語中的細微差別,甚至可能只是我業界的俗語:

  1. 共享對象:程序啟動時自動鏈接到程序的庫,並作為獨立文件存在。 該庫在編譯時包含在鏈接列表中(即:對於名為mylib.so的庫文件,LDOPTS + = - lmylib)。 該庫必須在編譯時和應用程序啟動時出現。
  2. 靜態庫(Static Library):在構建時將包含應用程序代碼和庫代碼的單個(較大)應用程序合併到實際程序本身中的庫,該程序在構建程序時自動鏈接到程序中,而最終的二進製文件包含兩者主程序和庫本身作為一個獨立的二進製文件存在。 該庫在編譯時包含在鏈接列表中(即:對於名為mylib.a的庫文件,LDOPTS + = - lmylib)。 庫必須在編譯時出現。
  3. DLL:基本上與共享對象相同,但不是在編譯時包含在鏈接列表中,而是通過dlopen() / dlsym()命令加載庫,以便在構建時不需要存在庫該程序進行編譯。 此外,庫不需要在應用程序啟動或編譯時出現(必然) ,因為它只在dlopen / dlsym調用完成時才需要。
  4. 共享存檔:基本上與靜態庫相同,但是使用“導出共享”和“-fPIC”標誌進行編譯。 該庫在編譯時包含在鏈接列表中(即:對於名為mylib S .a的庫文件,LDOPTS + = - lmylib S )。 兩者之間的區別在於,如果共享對像或DLL想要將共享歸檔文件靜態鏈接到其自己的代碼中,並且能夠使共享對像中的函數可用於其他程序,而不是僅僅使用它們內部的DLL。 這對於有人為您提供靜態庫並且希望將其重新打包為SO的情況很有用。 庫必須在編譯時出現。

其他更新

DLL ”和“ shared library ”之間的區別只是當時我工作的公司(Windows開發人員被迫轉向Linux開發,術語卡住)中的一種(懶惰的,不准確的)口語化,堅持描述如上所述。

另外,在“共享檔案”的情況下,庫名稱後面的尾隨“ S ”字面僅僅是該公司使用的慣例,而不是一般的行業。


Answers

靜態庫(.a)是一個庫,它可以直接鏈接到由鏈接器生成的最終可執行文件中,它包含在其中,並且不需要將庫導入將部署可執行文件的系統中。

共享庫(.so)是一個鏈接庫,但未嵌入最終可執行文件中,因此將在可執行文件啟動時加載,並且需要存在於部署可執行文件的系統中。

Windows(.dll)上動態鏈接庫就像Linux上的共享庫(.so),但與操作系統(Windows和Linux)相關的兩個實現之間存在一些差異:

DLL可以定義兩種功能:導出和內部。 導出的函數打算由其他模塊調用,以及從它們定義的DLL中調用。 內部函數通常只能在定義它們的DLL中調用。

Linux上的SO庫不需要特殊的導出語句來指示可導出符號,因為所有符號都可用於詢問過程。


我一直認為DLL和共享對像對於同一事物而言只是不同的術語--Windows將它們稱為DLL,而在UNIX系統中它們是共享對象,通用術語 - 動態鏈接庫 - 涵蓋了兩者(甚至包括函數在UNIX上打開一個.so,在'動態庫'之後調用dlopen() )。

它們確實只在應用程序啟動時連接,但是您對頭文件的驗證概念不正確。 頭文件定義了為了編譯使用該庫的代碼所需的原型,但是在鏈接時,鏈接器在庫本身內部查找以確保它需要的功能實際上存在。 鏈接器必須在鏈接時找到函數體,否則會引發錯誤。 它也在運行時這樣做,因為正確地指出自編譯程序後庫本身可能已經發生了變化。 這就是為什麼ABI穩定性在平台庫中如此重要,因為ABI的變化是破壞了針對舊版本編譯的現有程序的原因。

靜態庫僅僅是直接從編譯器中取出的對象文件的捆綁包,就像您在項目編譯過程中自己構建的那些對象文件一樣,因此它們以完全相同的方式被拉入並鏈接到鏈接器,並且未使用的位是以完全相同的方式下降。


我可以詳細說明Windows中DLL的細節,以幫助我的朋友在* NIX-land中澄清這些謎團...

DLL就像一個共享對象文件。 兩者都是圖像,可以通過相應操作系統的程序加載器加載到內存中。 這些圖像伴隨著各種元數據,以幫助鏈接器和加載器進行必要的關聯並使用代碼庫。

Windows DLL有一個導出表。 導出可以是名稱,也可以是表格位置(數字)。 後一種方法被認為是“老派”,並且更加脆弱 - 重建DLL並且改變表中函數的位置將在災難中結束,而如果入口點的鏈接是按名稱鏈接的,則不存在真正的問題。 所以,把它當作一個問題來解決,但要知道,如果你使用“恐龍”代碼(如第三方供應商庫)來處理這個問題。

Windows DLL是通過編譯和鏈接來構建的,就像您對EXE(可執行應用程序)所做的一樣,但是DLL並不是孤立的,就像SO應該由應用程序使用一樣,要么通過動態加載,要么通過鏈接時綁定(對SO的引用嵌入到應用程序二進製文件的元數據中,操作系統程序加載器將自動加載引用的SO)。 DLL可以引用其他DLL,就像SO可以引用其他SO一樣。

在Windows中,DLL將只提供特定的入口點。 這些被稱為“出口”。 開發人員可以使用特殊的編譯器關鍵字將符號設置為外部可見的(對於其他鏈接器和動態加載器),或者導出可以在鏈接時使用的模塊定義文件中列出,當DLL本身是被創建。 現代的做法是使用關鍵字來裝飾函數定義以導出符號名稱。 也可以使用關鍵字創建頭文件,這些關鍵字會將該符號聲明為從當前編譯單元之外的DLL導入的符號。 查找關鍵字__declspec(dllexport)和__declspec(dllimport)以獲取更多信息。

DLL的一個有趣特性是它們可以聲明一個標準的“加載/卸載”處理函數。 每當DLL加載或卸載時,DLL都可以執行一些初始化或清理,視情況而定。 這很好地映射為將DLL作為面向對象的資源管理器,例如設備驅動程序或共享對象接口。

當開發人員想要使用已經構建的DLL時,她必須引用由DLL開發人員在創建DLL時創建的“導出庫”(* .LIB),或者她必須在運行時顯式加載DLL並請求通過LoadLibrary()和GetProcAddress()機制按名稱輸入地址。 大多數情況下,鏈接到一個LIB文件(它只包含DLL導出入口點的鏈接程序元數據)就是DLL的使用方式。 動態加載通常用於在程序行為中實現“多態”或“運行時可配置性”(訪問加載項或後來定義的功能,也稱為“插件”)。

Windows的做事方式有時會造成一些混亂; 系統使用.LIB擴展來引用常規靜態庫(存檔,如POSIX * .a文件)以及在鏈接時將應用程序綁定到DLL所需的“導出存根”庫。 因此,應該總是查看* .LIB文件是否具有相同名稱的* .DLL文件; 如果不是,那麼* .LIB文件就是一個靜態庫存檔,並且不會導出DLL的綁定元數據。


你是正確的,因為靜態文件在鏈接時被複製到應用程序,並且這些共享文件僅在鏈接時被驗證並在運行時加載。

dlopen調用不僅適用於共享對象,如果應用程序希望在運行時代表它,否則共享對象會在應用程序啟動時自動加載。 DLLS和.so是一回事。 dlopen的存在為流程添加更細粒度的動態加載能力。 您不必使用dlopen來打開/使用DLL,這在應用程序啟動時也會發生。


這個問題恰恰說明了為什麼我喜歡按照我的問題提到的方式來做事情, 在類型ID可接受之後是const?

總之,我發現記住規則的最簡單的方法是“const”遵循它所適用的東西。 所以在你的問題中,“int const *”意味著int是常量,而“int * const”意味著指針是常量。

如果有人決定把它放在最前面(例如:“const int *”),作為一種特殊的例外情況,它適用於後面的東西。

許多人喜歡使用這種特殊的例外,因為他們認為它看起來更好。 我不喜歡它,因為它是一個例外,因此混淆了事物。





c++ c linux dll linker