.lib和.dll.exe文件的區別和聯繫,以及導出dll的兩種方法

注:本文內容均收集於網絡後總結。

1  .lib和.dll.exe的區別

(1).動態鏈接庫(Dynamic Link Library,縮寫爲DLL)是一個可以被其它應用程序共享的程序模塊,其中封裝了一些可以被共享的例程和資源。動態鏈接庫文件的擴展名一般是dll,也有可能是drv、sys和fon,它和可執行文件(exe)非常類似..dll是在你的程序運行的時候才連接的文件,因此它是一種比較小的可執行文件格式,.dll還有其他的文件格式如.ocx等,所有的.dll文件都是可執行。DLL中雖然包含了可執行代碼卻不能單獨執行,而應由Windows應用程序直接或間接調用。 動態鏈接是相對於靜態鏈接而言的。所謂靜態鏈接是指把要調用的函數或者過程鏈接到可執行文件中,成爲可執行文件的一部分。換句話說,函數和過程的代碼就在程序的exe文件中,該文件包含了運行時所需的全部代碼。當多個程序都調用相同函數時,內存中就會存在這個函數的多個拷貝,這樣就浪費了寶貴的內存資源。
而動態鏈接所調用的函數代碼並沒有被拷貝到應用程序的可執行文件中去,而是僅僅在其中加入了所調用函數的描述信息(往往是一些重定位信息)。僅當應用程序被裝入內存開始運行時,在Windows的管理下,纔在應用程序與相應的DLL之間建立鏈接關係。當要執行所調用DLL中的函數時,根據鏈接產生的重定位信息,Windows才轉去執行DLL中相應的函數代碼.

(2).lib是在你的程序編譯連接的時候就連接的文件,因此你必須告知編譯器連接的lib文件在那裏。一般來說,與動態連接文件相對比,lib文件也被稱爲是靜態連接庫。當你把代碼編譯成這幾種格式的文件時,在以後他們就不可能再被更改。如果你想使用lib文件,就必須:

①.包含一個對應的頭文件告知編譯器lib文件裏面的具體內容

②.設置lib文件允許編譯器去查找已經編譯好的二進制代碼

如果你想從你的代碼分離一個dll文件出來代替靜態連接庫,仍然需要一個lib文件。這個lib文件將被連接到程序告訴操作系統在運行的時候你想用到什麼dll文件,一般情況下,lib文件裏有相應的dll文件的名字和一個指明dll輸出函數入口的順序表。如果不想用lib文件或者是沒有lib文件,可以用WIN32 API函數LoadLibrary、GetProcAddress。事實上,我們可以在Visual C++ IDE中以二進制形式打開lib文件,大多情況下會看到ASCII碼格式的C++函數或一些重載操作的函數名字。
一般我們最主要的關於lib文件的麻煩就是出現unresolved symble 這類錯誤,這就是lib文件連接錯誤或者沒有包含.c、.cpp文件到工程裏,關鍵是如果在C++工程裏用了C語言寫的lib文件,就必需要這樣包含:
extern "C"
{
#include "myheader.h"
}
這是因爲C語言寫的lib文件沒有C++所必須的名字破壞,C函數不能被重載,因此連接器會出錯。
(3).看看別人是怎麼說明三者的區別的

(1)lib是編譯時需要的,dll是運行時需要的。
如果要完成源代碼的編譯,有lib就夠了。
如果也使動態連接的程序運行起來,有dll就夠了。
在開發和調試階段,當然最好都有。
(2)一般的動態庫程序有lib文件和dll文件。lib文件是必須在編譯期就連接到應用程序中的,而dll文件是運行期纔會被調用的。如果有dll文 件,那麼對應的lib文件一般是一些索引信息,具體的實現在dll文件中。如果只有lib文件,那麼這個lib文件是靜態編譯出來的,索引和實現都在其 中。靜態編譯的lib文件有好處:給用戶安裝時就不需要再掛動態庫了。但也有缺點,就是導致應用程序比較大,而且失去了動態庫的靈活性,在版本升級時,同 時要發佈新的應用程序才行。
(3)在動態庫的情況下,有兩個文件,一個是引入庫(.LIB)文件,一個是DLL文件,引入庫文件包含被DLL導出的函數的名稱和位置,DLL包含實際 的函數和數據,應用程序使用LIB文件鏈接到所需要使用的DLL文件,庫中的函數和數據並不複製到可執行文件中,因此在應用程序的可執行文件中,存放的不 是被調用的函數代碼,而是DLL中所要調用的函數的內存地址,這樣當一個或多個應用程序運行是再把程序代碼和被調用的函數代碼鏈接起來,從而節省了內存資 源。從上面的說明可以看出,DLL和.LIB文件必須隨應用程序一起發行,否則應用程序將會產生錯誤。
一、開發和使用dll需注意三種文件   
  1、   dll頭文件   
  它是指dll中說明輸出的類或符號原型或數據結構的.h文件。當其它應用程序調用dll時,需要將該文件包含入應用程序的源文件中。   
  2、   dll的引入庫文件   
  它是dll在編譯、鏈接成功後生成的文件。主要作用是當其它應用程序調用dll時,需要將該文件引入應用程序。否則,dll無法引入。   
  3、   dll文件(.dll)   
  它是應用程序調用dll運行時,真正的可執行文件。dll應用在編譯、鏈接成功後,.dll文件即存在。開發成功後的應用程序在發佈時,只需要有.exe文件和.dll文件,不必有.lib文件和dll頭文件。  
動態鏈接庫 (DLL) 是作爲共享函數庫的可執行文件。動態鏈接提供了一種方法,使進程可以調用不屬於其可執行代碼的函數。函數的可執行代碼位於一個 DLL 中,該 DLL 包含一個或多個已被編譯、鏈接並與使用它們的進程分開存儲的函數。DLL 還有助於共享數據和資源。多個應用程序可同時訪問內存中單個 DLL 副本的內容。
動態鏈接與靜態鏈接的不同之處在於:動態鏈接允許可執行模塊(.dll 文件或 .exe 文件)僅包含在運行時定位 DLL 函數的可執行代碼所需的信息。在靜態鏈接中,鏈接器從靜態鏈接庫獲取所有被引用的函數,並將庫同代碼一起放到可執行文件中。
使用動態鏈接代替靜態鏈接有若干優點。DLL 節省內存,減少交換操作,節省磁盤空間,更易於升級,提供售後支持,提供擴展 MFC 庫類的機制,支持多語言程序,並使國際版本的創建輕鬆完成。
lib與dll文件最大區別在調用方面
dll可以靜態陷入
lib與DLL
從這一章起,我講述的內容將特定於windows平臺。其實這篇文章也可看作是我在windows下的開發經驗總結,因爲以後我決定轉unix了。
前面有一章說編譯與鏈接的,說得很簡略,其實應該放到這一章一塊兒來說的。許多單講C++的書其實都過於學院派,對於真實的工作環境,上百個源文件怎麼結合起來,幾乎沒有提及。我引導讀者一步步看看lib與DLL是怎麼回事。
一個最簡單的C++程序,只需要一個源文件,這個源文件包含了如下語句
int main(){return 0;}
自然,這個程序什麼也不做。
當需程序需要做事情時,我們會把越來越多的語句添加到源文件中,例如,我們會開始在main函數中添加代碼:
#include <stdio.h>
int main()
{
printf("Hello World!\n");
return 0;
}
由於人的智力水平的限制,當一個函數中包含了太多的語句時,便不太容易被理解,這時候開始需要子函數:
#include <stdio.h>
void ShowHello()
{
printf("Hello World!\n");
}
int main()
{
ShowHello();
return 0;
}
同樣的道理,一個源文件中包含了太多的函數,同樣不好理解,人們開始分多個源文件了
// main.cpp
void ShowHello();//[1]
int main()
{
ShowHello();
return 0;
}
// hello.cpp
#include <stdio.h>
void ShowHello()
{
printf("Hello World!\n");
}
將這兩個文件加入到一個VC工程中,它們會被分別編譯,最後鏈接在一起。在VC編譯器的輸出窗口,你可以看到如下信息
--------------------Configuration: hello - Win32 Debug--------------------
Compiling...
main.cpp
hello.cpp
Linking...
hello.exe - 0 error(s), 0 warning(s)
這展示了它們的編譯鏈接過程。
接下來,大家就算不知道也該猜到,當一個工程中有太多的源文件時,它也不好理解,於是,人們想到了一種手段:將一部分源文件預先編譯成庫文件,也即lib文件,當要使用其中的函數時,只需要鏈接lib文件就可以了,而不用再理會最初的源文件。
在VC中新建一個static library類型的工程,加入hello.cpp文件,然後編譯,就生成了lib文件,假設文件名爲hello.lib。
別的工程要使用這個lib有兩種方式:
1 在工程選項-〉link-〉Object/Library Module中加入hello.lib
2 可以在源代碼中加入一行指令
#pragma comment(lib, "hello.lib")
注意這個不是C++語言的一部分,而是編譯器的預處理指令,用於通知編譯器需要鏈接hello.lib
根據個人愛好任意使用一種方式既可。
這種lib文件的格式可以簡單的介紹一下,它實際上是任意個obj文件的集合。obj文件則是cpp文件編譯生成的,在本例中,lib文件只包含了一個 obj文件,如果有多個cpp文件則會編譯生成多個obj文件,從而生成的lib文件中也包含了多個obj,注意,這裏僅僅是集合而已,不涉及到 link,所以,在編譯這種靜態庫工程時,你根本不會遇到鏈接錯誤。即使有錯,錯誤也只會在使用這個lib的EXE或者DLL工程中暴露出來。
關於靜態lib,就只有這麼多內容了,真的很簡單,現在我們介紹另外一種類型的lib,它不是obj文件的集合,即裏面不含有實際的實現,它只是提供動態 鏈接到DLL所需要的信息。這種lib可以在編譯一個DLL工程時由編譯器生成。涉及到DLL,問題開始複雜起來,我不指望在本文中能把DLL的原理說清 楚,這不是本文的目標,我介紹操作層面的東西。
簡單的說,一個DLL工程和一個EXE工程的差別有兩點:
1 EXE的入口函數是main或者WinMain,而DLL的入口函數是DllMain
2 EXE的入口函數標誌着一段處理流程的開始,函數退出後,流程處理就結束了,而DLL的入口函數對系統來說,只是路過,加載DLL的時候路過一次,卸載 DLL的時候又路過一次[2],你可以在DLL入口函數中做流程處理,但這通常不是DLL的目的,DLL的目的是要導出函數供其它DLL或EXE使用。你可以把DLL和EXE的關係理解成前面的main.cpp和hello.cpp的關係,有類似,實現手段不同罷了。
先看如何寫一個DLL以及如何導出函數,讀者應該先嚐試用VC創建一個新的動態鏈接庫工程,創建時選項不選空工程就可以了,這樣你能得到一個示例,以便開始在這個例子基礎上工作。
看看你創建的例子中的頭文件有類似這樣的語句:
#ifdef DLL_EXPORTS
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif
這就是函數的導出與使用導出函數的全部奧妙了。你的DLL工程已經在工程設置中定義了一個宏DLL_EXPORTS,因此你的函數聲明只要前面加 DLL_API就表示把它導出,而DLL的使用者由於沒有定義這個宏,所以它包含這個頭文件時把你的函數看作導入的。通過模仿這個例子,你就可以寫一系列的標記爲導出的函數了。
導出函數還有另一種方法,是使用DEF文件,DEF文件的作用,在現在來說只是起到限定導出函數名字的作用,這裏,我們要引出第二種[4]使用DLL的方法:稱爲顯示加載,通過Windows API的LoadLibrary和GetProcAddress這兩個函數來實現[5],這裏GetProcAddress的參數需要一個字符串形式的函數名稱,如果DLL工程中沒有使用DEF文件,那麼很可能你要使用非常奇怪的函數名稱(形如:)才能正確調用,這是因爲C++中的函數重載機制把函數名字重新編碼了,如果使用DEF文件,你可以顯式指定沒編碼前的函數名。
 

有了這些知識,你可以開始寫一些簡單的DLL的應用,但是我可以百分之百的肯定,你會遇到崩潰,而之前的非DLL的版本則沒有問題。假如你通過顯式加載來使用DLL,有可能會是調用約定不一致而引起崩潰,所謂調用約定就是函數聲明前面加上__stdcall __cdecl等等限定詞,注意一些宏如WINAPI會定義成這些限定詞之一,不理解他們沒關係,但是記住一定要保持一致,即聲明和定義時一致,這在用隱式加載時不成問題,但是顯示加載由於沒有利用頭文件,就有可能產生不一致。
調用約定並不是我真正要說的,雖然它是一種可能。我要說的是內存分配與釋放的問題。請看下面代碼:
void foo(string& str)
{
str = "hello";
}
int main()
{
string str;
foo(str);
printf("%s\n", str.c_str());
return 0;
}
當函數foo和main在同一個工程中,或者foo在靜態庫中時,不會有問題,但是如果foo是一個DLL的導出函數時,請不要這麼寫,它有可能會導致崩 潰[6]。崩潰的原因在於“一個模塊中分配的內存在另一個模塊中釋放”,DLL與EXE分屬兩個模塊,例子中foo裏面賦值操作導致了內存分配,而 main中return語句之後,string對象析構引起內存釋放。
我不想窮舉全部的這類情況,只請大家在設計DLL接口時考慮清楚內存的分配釋放問題,請遵循誰分配,誰釋放的原則來進行。
如果不知道該怎麼設計,請抄襲我們常見的DLL接口--微軟的API的做法,如:
CreateDC
ReleaseDC
的成對調用,一個函數分配了內存,另外一個函數用來釋放內存。
回到我們有可能崩潰的例子中來,怎麼修改才能避免呢?
這可以做爲一個練習讓讀者來做,這個練習用的時間也許會比較長,如果你做好了,那麼你差不多就出師了。一時想不到也不用急,我至少見過兩個有五年以上經驗的程序員依然犯這樣的錯誤。

注[1]:爲了說明的需要,我這裏使用直接聲明的方式,實際工程中是應該使用頭文件的。
注[2]: 還有線程創建與銷燬也會路過DLL的入口,但是這對新手來說意義不大。
注[3]:DEF文件格式很簡單,關於DEF文件的例子,可以通過新建一個ATL COM工程看到。
注[4]:第一種方法和使用靜態庫差不多,包含頭文件,鏈接庫文件,然後就像是使用普通函數一樣,稱爲隱式加載。
注[5]:具體調用方法請參閱MSDN。
注[6]:之所以說有可能是因爲,如果兩個工程的設置都是採用動態連接到運行庫,那麼分配釋放其實都在運行庫的DLL中進行,那麼這種情況便不會發生崩潰

dll和.lib都是程序集合,便於代碼重用。都是二進制的文件。

.dll也叫動態鏈接庫,與程序鏈接的方式爲運行時鏈接(run-time linked),爲PE(portable executable)格式,也就是程完整的程序。.exe、.dll、.fon、.mod、.drv、.ocx等等都是動態鏈接庫。如.exe爲系統調用的函數集合。.dll不存在同名引用,且有導出表,與導入表。

.lib也叫靜態鏈接庫,在編譯時與程序鏈接(link-time linked),將“嵌入”到程序中。會有冗餘(程序文件代碼的冗餘和運行時內存存儲的冗餘),當兩個lib相鏈接時地址會重新建立同。在使用.lib之前,要在程序源代碼中引用lib對應的頭文件.h,這些頭文件告訴編譯器.lib中有什麼。

在生成.dll時,通常會生成一個.lib。這個.lib將被編譯到程序文件中,在程序運行的時候,告訴操作系統將要加載的.dll。這個.lib包括對應.dll的文件名、順序表(ordinal table包含.dll暴露出的函數的進入點),在程序運行的時候,通過順序表實現函數的跳轉。

如果不想使用或者找不到該.lib,可以用LoadLibrary () Win32 API和GetLibrary () Win32 API。(暫時不知道怎樣處理:))

VC IDE爲了實現程序調試,會生成.PDB(程序數據庫,二進制),裏面包含源文件調用的文件信息和行信息。這樣就可以逐行調試了。(不是很明白:))

打開.lib,查看其ascii碼,可以看到如@@My_Function1123的函數名,這些名稱在編譯時被編譯器運用mangling mechanism(混淆機制?不知道怎麼翻譯)進行了名稱的mangling。

在程序的編譯過程中,如果出現如下錯誤“unresolved symbol ”,通常是因爲找不到引用過的外部函數對應的.lib文件,或者是.c、.cpp源文件。

如果在c++工程中使用用c編寫的.lib文件,需要做如下引用:

extern “C”{        #include “headfile.h”} dll---com組件dll|  ||  |__常規dll___win32 dll|            |___mfc dll|            |___extended dll  (所有dll不參與編譯)lib|_____與obj文件類似的未編譯過符號文件,與obj文件區別是聲明瞭轉入轉出函數


引用一篇很好的文章

  對比Windows和Linux兩系統的動態庫
http://www.linux-cn.com/html/test/20070411/2287.html


摘要:動態鏈接庫技術實現和設計程序常用的技術,在Windows和Linux系統中都有動態庫的概念,採用動態庫可以有效的減少程序大小,節省空間,提高效率,增加程序的可擴展性,便於模塊化管理。但不同操作系統的動態庫由於格式 不同,在需要不同操作系統調用時需要進行動態庫程序移植。本文分析和比較了兩種操作系統動態庫技術,並給出了把Visual C++編制的動態庫移植到Linux上的方法和經驗。

1、引言

動態庫(Dynamic Link Library abbr,DLL)技術是程序設計中經常採用的技術。其目的減少程序的大小,節省空間,提高效率,具有很高的靈活性。採用動態庫技術對於升級軟件版本更加容易。與靜態庫(Static Link Library)不同,動態庫裏面的函數不是執行程序本身的一部分,而是根據執行需要按需載入,其執行代碼可以同時在多個程序中共享。

在Windows和Linux操作系統中,都可採用這種方式進行軟件設計,但他們的調用方式以及程序編制方式不盡相同。本文首先分析了在這兩種操作系統中 通常採用的動態庫調用方法以及程序編制方式,然後分析比較了這兩種方式的不同之處,最後根據實際移植程序經驗,介紹了把VC++編制的Windows動態 庫移植到Linux下的方法。

2、動態庫技術

2.1 Windows動態庫技術

動態鏈接庫是實現Windows應用程序共享資源、節省內存空間、提高使用效率的一個重要技術手段。常見的動態庫包含外部函數和資源,也有一些動態庫只包 含資源,如Windows字體資源文件,稱之爲資源動態鏈接庫。通常動態庫以.dll,.drv、.fon等作爲後綴。相應的windows靜態庫通常 以.lib結尾,Windows自己就把一些主要的系統功能以動態庫模塊的形式實現。

Windows動態庫在運行時被系統加載到進程的虛擬空間中,使用從調用進程的虛擬地址空間分配的內存,成爲調用進程的一部分。DLL也只能被該進程的線 程所訪問。DLL的句柄可以被調用進程使用;調用進程的句柄可以被DLL使用。DLL模塊中包含各種導出函數,用於向外界提供服務。DLL可以有自己的數 據段,但沒有自己的堆棧,使用與調用它的應用程序相同的堆棧模式;一個DLL在內存中只有一個實例;DLL實現了代碼封裝性;DLL的編制與具體的編程語 言及編譯器無關,可以通過DLL來實現混合語言編程。DLL函數中的代碼所創建的任何對象(包括變量)都歸調用它的線程或進程所有。

根據調用方式的不同,對動態庫的調用可分爲靜態調用方式和動態調用方式。

(1)靜態調用,也稱爲隱式調用,由編譯系統完成對DLL的加載和應用程序結束時DLL卸載的編碼(Windows系統負責對DLL調用次數的計數),調 用方式簡單,能夠滿足通常的要求。通常採用的調用方式是把產生動態連接庫時產生的.LIB文件加入到應用程序的工程中,想使用DLL中的函數時,只須在源 文件中聲明一下。 LIB文件包含了每一個DLL導出函數的符號名和可選擇的標識號以及DLL文件名,不含有實際的代碼。Lib文件包含的信息進入到生成的應用程序中,被調 用的DLL文件會在應用程序加載時同時加載在到內存中。

(2)動態調用,即顯式調用方式,是由編程者用API函數加載和卸載DLL來達到調用DLL的目的,比較複雜,但能更加有效地使用內存,是編制大型應用程序時的重要方式。在Windows系統中,與動態庫調用有關的函數包括:

①LoadLibrary(或MFC 的AfxLoadLibrary),裝載動態庫。

②GetProcAddress,獲取要引入的函數,把符號名或標識號轉換爲DLL內部地址。

③FreeLibrary(或MFC的AfxFreeLibrary),釋放動態鏈接庫。

在windows中創建動態庫也非常方便和簡單。在Visual C++中,可以創建不用MFC而直接用C語言寫的DLL程序,也可以創建基於MFC類庫的DLL程序。每一個DLL必須有一個入口點,在VC++ 中,DllMain是一個缺省的入口函數。DllMain負責初始化(Initialization)和結束(Termination)工作。動態庫輸出 函數也有兩種約定,分別是基於調用約定和名字修飾約定。DLL程序定義的函數分爲內部函數和導出函數,動態庫導出的函數供其它程序模塊調用。通常可以有下 面幾種方法導出函數:

①採用模塊定義文件的EXPORT部分指定要輸入的函數或者變量。

②使用MFC提供的修飾符號_declspec(dllexport)。

③以命令行方式,採用/EXPORT命令行輸出有關函數。

在windows動態庫中,有時需要編寫模塊定義文件(.DEF),它是用於描述DLL屬性的模塊語句組成的文本文件。

2.2 Linux共享對象技術

在Linux操作系統中,採用了很多共享對象技術(Shared Object),雖然它和Windows裏的動態庫相對應,但它並不稱爲動態庫。相應的共享對象文件以.so作爲後綴,爲了方便,在本文中,對該概念不進 行專門區分。Linux系統的/lib以及標準圖形界面的/usr/X11R6/lib等目錄裏面,就有許多以so結尾的共享對象。同樣,在Linux 下,也有靜態函數庫這種調用方式,相應的後綴以.a結束。Linux採用該共享對象技術以方便程序間共享,節省程序佔有空間,增加程序的可擴展性和靈活 性。Linux還可以通過LD-PRELOAD變量讓開發人員可以使用自己的程序庫中的模塊來替換系統模塊。

同Windows系統一樣,在Linux中創建和使用動態庫是比較容易的事情,在編譯函數庫源程序時加上-shared選項即可,這樣所生成的執行程序就 是動態鏈接庫。通常這樣的程序以so爲後綴,在Linux動態庫程序設計過程中,通常流程是編寫用戶的接口文件,通常是.h文件,編寫實際的函數文件, 以.c或.cpp爲後綴,再編寫makefile文件。對於較小的動態庫程序可以不用如此,但這樣設計使程序更加合理。

編譯生成動態連接庫後,進而可以在程序中進行調用。在Linux中,可以採用多種調用方式,同Windows的系統目錄(..\system32等)一 樣,可以把動態庫文件拷貝到/lib目錄或者在/lib目錄裏面建立符號連接,以便所有用戶使用。下面介紹Linux調用動態庫經常使用的函數,但在使用 動態庫時,源程序必須包含dlfcn.h頭文件,該文件定義調用動態鏈接庫的函數的原型。

(1)_打開動態鏈接庫:dlopen,函數原型void *dlopen (const char *filename, int flag);

dlopen用於打開指定名字(filename)的動態鏈接庫,並返回操作句柄。

(2)取函數執行地址:dlsym,函數原型爲: void *dlsym(void *handle, char *symbol);

dlsym根據動態鏈接庫操作句柄(handle)與符號(symbol),返回符號對應的函數的執行代碼地址。

(3)關閉動態鏈接庫:dlclose,函數原型爲: int dlclose (void *handle);

dlclose用於關閉指定句柄的動態鏈接庫,只有當此動態鏈接庫的使用計數爲0時,纔會真正被系統卸載。

(4)動態庫錯誤函數:dlerror,函數原型爲: const char *dlerror(void); 當動態鏈接庫操作函數執行失敗時,dlerror可以返回出錯信息,返回值爲NULL時表示操作函數執行成功。

在取到函數執行地址後,就可以在動態庫的使用程序裏面根據動態庫提供的函數接口聲明調用動態庫裏面的函數。在編寫調用動態庫的程序的makefile文件時,需要加入編譯選項-rdynamic和-ldl。

除了採用這種方式編寫和調用動態庫之外,Linux操作系統也提供了一種更爲方便的動態庫調用方式,也方便了其它程序調用,這種方式與Windows系統 的隱式鏈接類似。其動態庫命名方式爲“lib*.so.*”。在這個命名方式中,第一個*表示動態鏈接庫的庫名,第二個*通常表示該動態庫的版本號,也可 以沒有版本號。在這種調用方式中,需要維護動態鏈接庫的配置文件/etc/ld.so.conf來讓動態鏈接庫爲系統所使用,通常把動態鏈接庫所在目錄名 追加到動態鏈接庫配置文件中。如具有X window窗口系統發行版該文件中都具有/usr/X11R6/lib,它指向X window窗口系統的動態鏈接庫所在目錄。爲了使動態鏈接庫能爲系統所共享,還需運行動態鏈接庫的管理命令./sbin/ldconfig。在編譯所引 用的動態庫時,可以在gcc採用 –l或-L選項或直接引用所需的動態鏈接庫方式進行編譯。在Linux裏面,可以採用ldd命令來檢查程序依賴共享庫。

3、兩種系統動態庫比較分析

Windows和Linux採用動態鏈接庫技術目的是基本一致的,但由於操作系統的不同,他們在許多方面還是不盡相同,下面從以下幾個方面進行闡述。

(1)動態庫程序編寫,在Windows系統下的執行文件格式是PE格式,動態庫需要一個DllMain函數作爲初始化的人口,通常在導出函數的聲明時需 要有_declspec(dllexport)關鍵字。Linux下的gcc編譯的執行文件默認是ELF格式,不需要初始化入口,亦不需要到函數做特別聲 明,編寫比較方便。

(2)動態庫編譯,在windows系統下面,有方便的調試編譯環境,通常不用自己去編寫makefile文件,但在linux下面,需要自己動手去編寫makefile文件,因此,必須掌握一定的makefile編寫技巧,另外,通常Linux編譯規則相對嚴格。

(3)動態庫調用方面,Windows和Linux對其下編制的動態庫都可以採用顯式調用或隱式調用,但具體的調用方式也不盡相同。

(4)動態庫輸出函數查看,在Windows中,有許多工具和軟件可以進行查看DLL中所輸出的函數,例如命令行方式的dumpbin以及VC++工具中 的DEPENDS程序。在Linux系統中通常採用nm來查看輸出函數,也可以使用ldd查看程序隱式鏈接的共享對象文件。

(5)對操作系統的依賴,這兩種動態庫運行依賴於各自的操作系統,不能跨平臺使用。因此,對於實現相同功能的動態庫,必須爲兩種不同的操作系統提供不同的動態庫版本。

4、動態庫移植方法

如果要編制在兩個系統中都能使用的動態鏈接庫,通常會先選擇在Windows的VC++提供的調試環境中完成初始的開發,畢竟VC++提供的圖形化編輯和 調試界面比vi和gcc方便許多。完成測試之後,再進行動態庫的程序移植。通常gcc默認的編譯規則比VC++默認的編譯規則嚴格,即使在VC++下面沒 有任何警告錯誤的程序在gcc調試中也會出現許多警告錯誤,可以在gcc中採用-w選項關閉警告錯誤。

下面給出程序移植需要遵循的規則以及經驗。

(1)儘量不要改變原有動態庫頭文件的順序。通常在C/C++語言中,頭文件的順序有相當的關係。另外雖然C/C++語言區分大小寫,但在包含頭文件 時,Linux必須與頭文件的大小寫相同,因爲ext2文件系統對文件名是大小寫敏感,否則不能正確編譯,而在Windows下面,頭文件大小寫可以正確 編譯。

(2)不同系統獨有的頭文件。在Windows系統中,通常會包括windows.h頭文件,如果調用底層的通信函數,則會包含winsock..h頭文 件。因此在移植到Linux系統時,要註釋掉這些Windows系統獨有的頭文件以及一些windows系統的常量定義說明,增加Linux都底層通信的 支持的頭文件等。

(3)數據類型。VC++具有許多獨有的數據類型,如__int16,__int32,TRUE,SOCKET等,gcc編譯器不支持它們。通常做法是需 要把windows.h和basetypes.h中對這些數據進行定義的語句複製到一個頭文件中,再在Linux中包含這個頭文件。例如把套接字的類型爲 SOCKET改爲int。

(4)關鍵字。VC++中具有許多標準C中所沒有采用的關鍵字,如BOOL,BYTE,DWORD,__asm等,通常在爲了移植方便,儘量不使用它們,如果實在無法避免可以採用#ifdef 和#endif爲LINUX和WINDOWS編寫兩個版本。

(5)函數原型的修改。通常如果採用標準的C/C++語言編寫的動態庫,基本上不用再重新編寫函數,但對於系統調用函數,由於兩種系統的區別,需要改變函 數的調用方式等,如在Linux編制的網絡通信動態庫中,用close()函數代替windows操作系統下的closesocket()函數來關閉套接 字。另外在Linux下沒有文件句柄,要打開文件可用open和fopen函數,具體這兩個函數的用法可參考文獻[2]。

(6)makefile的編寫。在windows下面通常由VC++編譯器來負責調試,但gcc需要自己動手編寫makefile文件,也可以參照 VC++生成的makefile文件。對於動態庫移植,編譯動態庫時需要加入-shared選項。對於採用數學函數,如冪級數的程序,在調用動態庫是,需 要加入-lm。

(7)其它一些需要注意的地方

①程序設計結構分析,對於移植它人編寫的動態庫程序,程序結構分析是必不可少的步驟,通常在動態庫程序中,不會包含界面等操作,所以相對容易一些。

②在Linux中,對文件或目錄的權限分爲擁有者、羣組、其它。所以在存取文件時,要注意對文件是讀還是寫操作,如果是對文件進行寫操作,要注意修改文件或目錄的權限,否則無法對文件進行寫。

③指針的使用,定義一個指針只給它分配四個字節的內存,如果要對指針所指向的變量賦值,必須用malloc函數爲它分配內存或不把它定義爲指針而定義爲變 量即可,這點在linux下面比windows編譯嚴格。同樣結構不能在函數中傳值,如果要在函數中進行結構傳值,必須把函數中的結構定義爲結構指針。

④路徑標識符,在Linux下是“/”,在Windows下是“\”,注意windows和Linux的對動態庫搜索路徑的不同。

⑤編程和調試技巧方面。對不同的調試環境有不同的調試技巧,在這裏不多敘述。

5、結束語

本文系統分析了windows和Linux動態庫實現和使用方式,從程序編寫、編譯、調用以及對操作系統依賴等方面綜合分析比較了這兩種調用方式的不同之 處,根據實際程序移植經驗,給出了把VC++編制的Windows動態庫移植到Linux下的方法以及需要注意的問題,同時並給出了程序示例片斷,實際在 程序移植過程中,由於系統的設計等方面,可能移植起來需要注意的方面遠比上面複雜,本文通過總結歸納進而爲不同操作系統程序移植提供了有意的經驗和技巧。

 
2. 導出(輸出)dll的兩種方法
(1).DLL的Export和Import介紹

DLL的export是指將DLL中的函數和數據輸出到其它程式中,以供其使用。DLL的import是指使用DLL的程式引入DLL中的函數和數據。

DLL的export

DLL中包含有一個表,稱爲export table(以下簡稱ET),其中包含了DLL中可以被外部程式使用的所有函數和數據的名字。只有記錄在ET中的函數和數據纔可以被外部程式所使用(如果沒有.DEF文件的話),其它所有沒有記錄在ET中的函數和數據都被視爲是DLL私有的。因此,要將DLL中的函數和數據export只有兩個方法:

l 爲DLL創建一個.DEF文件(模塊定義文件),並在build該DLL時使用這個.DEF文件。使用這種方法使你可以將函數按序號export。

l 在DLL中想要export的函數和數據定義前添加_declspec(dllexport)關鍵字(對於函數和變量定義,加在最前面;對於class定義,加在class關鍵字後),這樣該函數和數據就會被添加到ET中。使用這種方法函數將按名字export。

Windows下,無論使用上述的哪一種方法,都必須要將export函數聲明爲_stdcall。

關於C和C++的兼容問題

如果要寫C和C++兼容的DLL,因爲在C和C++下使用了不同的名字修飾規則以及不同的調用約定,所以,如果DLL是用C編寫和編譯的,則在用於C++模塊時,函數的聲明前應加上extern “C”關鍵字,以告訴LINKER使用C外部連接(即按照C名字修飾規則在外部模塊中尋找函數);反之,如果DLL是用C++編寫和編譯的,則在用於C模塊時,函數的聲明前要加上extern “C++”關鍵字。VC++通過_cplusplus宏來標識C++程式。如果是C++程式,VC編譯器就會爲你定義_cplusplus宏。所以在DLL中可以使用如下的技術來解決兼容問題:

#ifdef _cplusplus

extern “C” {

#endif

// 將所有的函數聲明放在這裏

#ifdef _cplusplus

}

#endif

.DEF文件

.DEF文件是包含了DLL模塊信息的文本文件。其語法結構如下:

LIBRARY DLL file name

DESCRIPTION “descriptions”

EXPORTS

Function names @nums

LIBRARY爲關鍵字,後面緊跟關聯的DLL文件名;DESCRIPTION後爲可選的描述字符串,除了增加可讀性外沒什麼用處;EXPORTS後是export函數的列表,首先是函數名,然後是@符號,後緊跟一十進制數,爲該函數的標號,範圍從1到DLL可export函數的總數。注意,這裏的名字是經過名字修飾後的函數名字,如果是DLL是用C++寫的話,那麼就很鬱悶了。

如果是擴展DLL(extension DLL),並且通過.DEF文件export,那麼必須在頭文件中添加如下的語句:

        #undef AFX_DATA
         #define AFX_DATA AFX_EXT_DATA
         // 頭文件中的其它內容
         #undef AFX_DATA
         #define AFX_DATA

這些語句確保一些MFC中內部使用的變量被export到外部程式中。例如:在class中通過DECLARE_DYNAMIC獲得的CRuntimeClass變量。否則DLL將會無法正確地編譯和連接,或外部程式無法正確連接到該DLL。

DLL的import

外部程式的一個源文件要使用DLL中的函數和數據,就像要使用外部模塊中的函數和數據一樣,必須首先給出函數和數據的聲明;對於class則要給出類的定義。這就稱爲import。對於VC編譯器,Import DLL的函數和數據的語法與一般的聲明類似,但要在前面加上_declspec(dllimport)關鍵字(對於函數和變量聲明,加在最前面;對於class定義,加在class關鍵字後)。如果是函數,則該關鍵字是可選的,但使用該關鍵字有可能會導致編譯器產生較高效的代碼。但對於變量和class,則必須使用該關鍵字。

通過使用以下的技術,可以編寫在.LIB文件和外部程序源文件通用的頭文件:

         #ifdef _EXPORTING
         #define CLASS_DECLSPEC    __declspec(dllexport)
      #else
         #define CLASS_DECLSPEC    __declspec(dllimport)
         #endif

編譯器提供的_EXPORTING宏可以用於標式該源文件來自DLL文件還是外部程式。

(2)._declspec(dllexport)與_declspec(dllimport)

寫WIN32程序的人,做過DLL,都會很清楚__declspec(dllexport)的作用,它就是爲了省掉在DEF文件中手工定義導出哪些函數的一個方法。當然,如果你的DLL裏全是C++的類的話,你無法在DEF裏指定導出的函數,只能用__declspec(dllexport)導出類。但是,MSDN文檔裏面,對於__declspec(dllimport)的說明讓人感覺有點奇怪,先來看看MSDN裏面是怎麼說的:

不使用 __declspec(dllimport) 也能正確編譯代碼,但使用 __declspec(dllimport) 使編譯器可以生成更好的代碼。編譯器之所以能夠生成更好的代碼,是因爲它可以確定函數是否存在於 DLL 中,這使得編譯器可以生成跳過間接尋址級別的代碼,而這些代碼通常會出現在跨 DLL 邊界的函數調用中。但是,必須使用 __declspec(dllimport) 才能導入 DLL 中使用的變量。

初看起來,這段話前面的意思是,不用它也可以正常使用DLL的導出庫,但最後一句話又說,必須使用 __declspec(dllimport) 才能導入 DLL 中使用的變量這個是什麼意思??

那我就來試驗一下,假定,你在DLL裏只導出一個簡單的類,注意,我假定你已經在項目屬性中定義了 SIMPLEDLL_EXPORT
SimpleDLLClass.h

#ifdef SIMPLEDLL_EXPORT
#define DLL_EXPORT __declspec(dllexport)
#else
#define DLL_EXPORT
#endif

class DLL_EXPORT SimpleDLLClass
{
public:
SimpleDLLClass();
virtual ~SimpleDLLClass();

virtual getValue() { return m_nValue;};
private:
int m_nValue;
};
SimpleDLLClass.cpp

#include "SimpleDLLClass.h"

SimpleDLLClass::SimpleDLLClass()
{
m_nValue=0;
}

SimpleDLLClass::~SimpleDLLClass()
{
}
然後你再使用這個DLL類,在你的APP中include SimpleDLLClass.h時,你的APP的項目不用定義 SIMPLEDLL_EXPORT 所以,DLL_EXPORT 就不會存在了,這個時候,你在APP中,不會遇到問題。這正好對應MSDN上說的__declspec(dllimport)定義與否都可以正常使用。但我們也沒有遇到變量不能正常使用呀。 那好,我們改一下SimpleDLLClass,把它的m_nValue改成static,然後在cpp文件中加一行

int SimpleDLLClass::m_nValue=0;
如果你不知道爲什麼要加這一行,那就回去看看C++的基礎。 改完之後,再去LINK一下,你的APP,看結果如何, 結果是LINK告訴你找不到這個m_nValue。明明已經定義了,爲什麼又沒有了?? 肯定是因爲我把m_nValue定義爲static的原因。但如果我一定要使用Singleton的Design Pattern的話,那這個類肯定是要有一個靜態成員,每次LINK都沒有,那不是完了? 如果你有Platform SDK,用裏面的Depend程序看一下,DLL中又的確是有這個m_nValue導出的呀。
再回去看看我引用MSDN的那段話的最後一句。 那我們再改一下SimpleDLLClass.h,把那段改成下面的樣子:

#ifdef SIMPLEDLL_EXPORT
#define DLL_EXPORT __declspec(dllexport)
#else
#define DLL_EXPORT __declspec(dllimport)
#endif
再LINK,一切正常。原來dllimport是爲了更好的處理類中的靜態成員變量的,如果沒有靜態成員變量,那麼這個__declspec(dllimport)無所謂。

_declspec(dllexport)與_declspec(dllimport)都是DLL內的關鍵字,即導出與導入。他們是將DLL內部的類與函數以及數據導出與導入時使用的。主要區別在於,dllexport是在這些類、函數以及數據的申明的時候使用。用過表明這些東西可以被外部函數使用,即(dllexport)是把DLL中的相關代碼(類,函數,數據)暴露出來爲其他應用程序使用。使用了(dllexport)關鍵字,相當於聲明瞭緊接在(dllexport)關鍵字後面的相關內容是可以爲其他程序使用的。而dllimport關鍵字是在外部程序需要使用DLL內相關內容時使用的關鍵字。當一個外部程序要使用DLL內部代碼(類,函數,全局變量)時,只需要在程序內部使用(dllimport)關鍵字聲明需要使用的代碼就可以了,即(dllimport)關鍵字是在外部程序需要使用DLL內部相關內容的時候才使用。(dllimport)作用是把DLL中的相關代碼插入到應用程序中。

  _declspec(dllexport)與_declspec(dllimport)是相互呼應,只有在DLL內部用dllexport作了聲明,才能在外部函數中用dllimport導入相關代碼。實際上,在應用程序訪問DLL時,實際上就是應用程序中的導入函數與DLL文件中的導出函數進行鏈接。而且鏈接的方式有兩種:隱式迎接和顯式鏈接。

  隱式鏈接是指通過編譯器提供給應用程序關於DLL的名稱和DLL函數的鏈接地址,面在應用程序中不需要顯式地將DLL加載到內存,即在應用程序中使用dllimport即表明使用隱式鏈接。不過不是所有的隱式鏈接都使用dllimport。

  顯式鏈接剛同應用程序用語句顯式地加載DLL,編譯器不需要知道任何關DLL的信息。

(3).在VC中不用MFC如何製作dll,導出dll以供其他模塊(dll.或exe)使用

方法一:使用export 和 import
在VC中建立一個Console Application,建立2個文件:Dll.h 和 Dll.cpp
Dll.h
#ifdef MYLIBAPI
#else
#define MYLIBAPI extern "C" _declspec (dllimport)
#end if
MYLIBAPI int Add (int iLeft, int iRight)
MYLIBAPI int Sub (int iLeft, int iRight)
Dll.cpp
#define MYLIBAPI extern "C" _declspec (dllexport)
#include "Dll.h"
int Add (int iLeft, int iRight)
{
return iLeft + iRight ;
}
int Sub (int iLeft, int iRight)
{
return iLeft - iRight ;
}
保存文件。
在Project->setting->link 最下面加上 “/dll”, "/"之前一定要與前一項
有空格。
然後編譯,就可以在debug 或 release下面找到dll 和 lib 文件了
使用的時候包含dll.h文件
 
方法二:使用def文件
建立一個console application, 建立2個文件dll.h 和 dll.cpp
Dll.h
int Add (int iLeft, int iRight) ;
int Sub (int iLeft, int iRight) ;
Dll.cpp
#include "Dll.h"
int Add (int iLeft, int iRight)
{
return iLeft + iRight ;
}
int Sub (int iLeft, int iRight)
{
return iLeft - iRight ;
}
然後再當前目錄下面建立一個.def文件,文件名最好和要輸出的dll名字一樣,擴展名
爲.def, 裏面寫上:
LIBRARY dllname.dll
EXPORTS
Add @1
Add @2
然後將這個文件添加到工程中,
在link中設置 /dll, 然後編譯
在debug或release中就可以找到dll和lib了
使用的時候加上dll.h文件
 
參考文獻:
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章