dll和lib(包括靜態連接庫和與dll同時生成的lib)

轉:http://blog.csdn.net/galaxy_li/article/details/7411956git

1:神馬是Dll和Lib,神馬是靜態連接和動態連接安全

你們都懂的,DLL就是動態連接庫,LIB是靜態連接庫。DLL其實就是EXE,只不過沒main。函數

動態連接是相對於靜態連接而言的。所謂靜態連接就是把函數或過程直接連接到可執行文件中,成爲可執行程序中的一部分,當多個程序調用一樣的函數時,內存裏就會有這個函數的多個拷貝,浪費內存資源。而動態連接則是提供了一個函數的描述信息給可執行文件(並無內存拷貝),當程序被夾在到內存裏開始運行的時候,系統會在底層建立DLL和應用程序之間的鏈接關係,當執行期間須要調用DLL函數時,系統纔會真正根據連接的定位信息去執行DLL中的函數代碼。ui

在WINDOWS32系統底下,每一個進程有本身的32位的線性地址空間,若一個DLL被進程使用,則該DLL首先會被調入WIN32系統的全局堆棧,而後經過內存映射文件方式映射到這個DLL的進程地址空間。若一個DLL被多個進程調用,則每一個進程都會接收到該DLL的一個映像,而非多份的拷貝。但,在WIN16系統下,每一個進程須要擁有本身的一份DLL空間,能夠理解爲什麼靜態連接沒啥區別。.net

 

2:DLL和LIB區別和聯繫。線程

DLL是程序在運行階段才須要的文件。3d

LIB是程序編譯時須要連接的文件。版本控制

DLL只有一種,其中必定是函數和過程的實現。指針

LIB是有兩種。若只生成LIB的話,則這個LIB是靜態編譯出來的,它內部包含了函數索引以及實現,這個LIB會比較大。若生成DLL的話,則也會生成一個LIB,這個LIB和剛纔那個LIB不一樣,它是隻有函數索引,沒有實現的,它很小。可是這倆LIB依然遵循上個原則,是在編譯時候是須要被連接的。若不連接第一個LIB的話,在程序運行時會沒法找到函數實現,當掉。若不連接第二個LIB的話,在程序運行時依然會沒法找到函數實現。但第二種LIB有一種替代方式,就是在程序裏,使用LoadLibrary,GetProcAddress替代第二個LIB的功能。第一種LIB生成的EXE文件會很大,由於LIB全部信息被靜態連接進EXE裏了。第二種LIB生成的EXE文件會比較小,由於函數過程實現依舊在DLL內。調試

(囉嗦了一堆,某志但願你們可以明白兩個LIB的區別。要再不行的話,咱們能夠將靜態編譯的LIB稱爲 靜態連接庫。但動態編譯的LIB稱爲 引入庫。可能會比較好一些。)

靜態連接LIB的優勢是免除掛接動態連接庫,缺點是EXE大,版本控制麻煩些。

動態連接DLL的優勢是文件小,版本更換時換DLL就行了,缺點是多了點文件。動態連接如果被多個進程使用,會更加方便和節省內存。

 

3:爲何編譯DLL時總會同時生成一個LIB?這個LIB有用嗎?

若咱們不是用靜態連接,而使用DLL,那麼咱們也須要一個LIB,這個LIB的做用是被連接到程序裏,在程序運行時告訴系統你須要什麼DLL文件。這個LIB裏保存的是DLL的名字和輸出函數入口的順序表。它是有意義的。

固然,若咱們的應用程序裏不連接這個LIB,則可使用LoadLibrary,GetProcAddress來告訴系統咱們在運行時須要怎麼着DLL以及其內的函數。

 

4:DLL意義。

1:DLL真正實現了跨語言。各類語言均可以生成DLL,而對系統以及應用程序來講,哪一種語言生成的DLL是沒有區別的。

2:DLL有足夠的封裝性,對於版本更新有很大好處。由於DLL是運行期間纔會使用,因此,即便DLL內函數實現有變化(只要參數和返回值不發生變化),程序是不須要進行編譯的。大大提升了軟件開發和維護的效率。

3:DLL被多個進程使用,由於有內存映射機制,無需佔用更多內存。

 

5:建立DLL。(注意:某志就再也不講解使用MFC AppWizard[dll] 方式建立DLL了。有興趣的本身去百度。這裏建立DLL只指使用Win32 Dynamic-link Library建立Non-MFC DLL。呃,DLL的三種類型就不解釋了,依舊那句話:百度一下你就知道。)

每一個應用程序必須有一個main或者winmain函數做爲入口,DLL同樣,有本身的缺省的入口函數,就是DllMain。函數以下

BOOL APIENTRY DllMain( HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
 switch (ul_reason_for_call)
 {
 case DLL_PROCESS_ATTACH:   // 進程被調用
 case DLL_THREAD_ATTACH:     // 線程被調用
 case DLL_THREAD_DETACH:   // 線程被中止
 case DLL_PROCESS_DETACH:  // 進程被中止
  break;
 }
 return TRUE;
}

通常狀況下,咱們不須要對這個缺省的入口函數進行什麼修改,它就會使動態連接庫獲得正確的初始化。可是,當咱們的DLL須要額外分配內存或者資源的時候,或者,DLL但願對調用本身的進程或線程進行初始化或清除的額外操做時,能夠在上述代碼case中加一些本身感冒的東東。(懶……不想細寫了- -Orz,如今是晚上2點了,明天還一堆的事情)

DLL對於導出類和導出函數沒啥不一樣。只要加上 __declspec( dllexport ) 修飾函數或者類就行了。

可是有查看過DLL代碼的人員都會常常見到這麼一段代碼

#ifdef FK_DLL_EXPORTS

#define FK_DLL __declspec( dllexport )

#else

#define FK_DLL __declspec( dllimport )

#endif

意義很明顯,可是,問題是  FK_DLL_EXPORTS 這個宏是應該在哪兒定義呢?在DLL項目內,仍是在使用DLL的應用程序內?

這點某志曾迷糊好久,呵呵~其實後來想一想,仍是蠻明確的。export是導出。import是導入。對於DLL來講,是要導出這些函數給其餘應用程序使用的,因此應當定義 FK_DLL_EXPORTS 宏。對於使用DLL的應用程序來講,是導入,是無需定義的。

使用時候也很簡單。

class FK_DLL CMyDllClass{} ;

則整個類被導出。

FK_DLL void MyTestFun( int a );

則該函數被導出。

可是有時咱們能夠見到這樣的代碼

extern "C" FK_DLL void MyTestFun2( float b );

其中extern "C"的原理就是標示該函數要求以C形式去進行編譯,不要以C++形式去編譯。具體的編譯原理就不羅嗦了,簡而言之,被extern "C"定義函數,能夠被C以及其餘語言進行DLL調用,而未被extern "C"定義的函數,C是沒法訪問DLL中這個函數的。

 

在VS中開發DLL還有一種方式,使用.def文件。

新建個文本文檔,改後綴爲FKDll.def,加入到工程裏。

FKDll.def里加入如下代碼

LIBRARY FKDll

EXPORTS

MyTestFun@1

MyTestFun2@2

就能夠了。其中,LIBRARY語句是說明.def文件是屬於FKDll這個Dll的。EXPORTS下面是咱們須要導出的函數名。後面加的@+數字,是表示導出函數的順序編號。這樣就足夠了。(詳細的本身百度,好睏,zzzZZZ)

 

6:使用DLL

使用DLL有兩種方式。顯式連接和隱式連接。

隱式連接很容易。直接#progam comment(lib, "FKDll.lib") 就能夠。固然,也能夠在項目工程->屬性->連接庫里加上庫和路徑(相對路徑和絕對路徑均可以)。

顯式連接則麻煩些。在程序中使用LoadLibrary加載DLL,再GetProcAddress獲取函數實現,在程序退出以前,調用FreeLibrary來動態釋放掉連接庫。

‍例如:

void Main()

{

     typedef void (*FKDllFun1)(int a);

    FKDllFun1 pFun1;

    HINSTANCE hDLL  = LoadLibrary("FKDll.dll");   // 若hDll爲空則讀取Dll失敗。

    pFun1 = (pFun1)GetProcAddress(hDll, "MyTestFun1" );   // 從應用程序中的DLL鏡像中獲取名爲 MyTestFun1 的函數指針

    pFun1( 100 );

    FreeLibrary(hDll);

}

固然,咱們剛纔.def裏面還指定了導出函數的導出順序,那麼咱們能夠修改裏面獲取函數指針那一段爲

‍pFun1 = (pFun1)GetProcAddress(hDll, MAKEINTERSOURCE(1) );  // 1 是剛纔指定的MyTestFun1函數導出順序編號。

這樣能夠更快,可是別將編號記混了,會致使詭異的錯誤。

 

7:比較顯式連接和隱式連接。

可能的話,儘可能使用顯式連接。

顯式連接能夠在程序執行時動態的加載DLL和卸載DLL文件,隱式連接是作不到的。

顯式連接LoadLibrary,GetProcAddress時能獲知是否加載失敗,咱們能夠對其進行檢查錯誤處理。而顯式連接多是一個很惡劣的提示或是程序崩潰的結果。

對於有些Ex類型的增強函數,顯式連接能夠容許咱們找到替代方案。也包括選擇D3d9.dll和OpenGL.dll時也可採用一樣處理。

例如:

if( GetProcAddress( hDll, "FKDllFunEx") == NULL )

{

‍    pFun = GetProcAddress( hDll, "FKDllFun");    // 而後使用pFun進行處理

}

 

8:導出類和導出函數

類和函數的導出方式上面給出了說明,本來極其相似的。

咱們說下使用導出類。

若咱們隱式的使用了一個導出類,則咱們在應用程序裏繼承它的時候,就如同該類就在應用程序代碼裏同樣,無需任何處理。

例如:

class FK_DLL CMyDllClass{} ;    // Dll文件內的代碼

-----------------------

class CAppClass : public CMyDllClass      // 應用程序內代碼,無需作任何處理。

{

       ....

}

也能夠直接使用DLL導出類

void main

{

     CMyDllClass* pClass = new CMyDllClass ();

}

可是,若應用程序聲明或者分類一個DLL中導出類的對象時會存在一個很討厭的問題:這個操做會使內存跟蹤系統失效,使其錯誤的報告內存分配和釋放狀況。

爲解決這個問題,咱們能夠給出兩個接口函數對DLL導出類進行建立銷燬支持,就可使內存跟蹤系統正常了。例如

class FK_DLL CMyDllClass{} ; 

額外增長倆函數

FK_DLL CMyDllClass* CreateMyDllClass(){ return new CMyDllClass(); }

FK_DLL void DestoryMyDllClass( CMyDllClass* p_pClass ){ delete p_pClass; }

-----------------------------------------------

上面的方法能夠正確進行內存跟蹤了,可是,由於DLL導出類CMyDllClass依舊是導出的狀態,用戶一樣能夠跳過咱們提供的接口直接使用。那麼怎麼辦呢。方法是再也不對類進行DLL導出,而對類內的函數所有進行DLL導出便可,

-----------------------------------------------

可是若僅僅提供上面兩個接口函數以及類內所有函數,的確功能能夠實現,卻沒法進行類繼承了。若這個類繼承很重要,必須開放,那麼就須要使用新的內存跟蹤程序替換應用程序內的原有內存跟蹤程序。或者使用下面的一個方法。(見模塊9:複雜問題)

-----------------------------------------------

一樣,咱們也能夠發現,在不導出DLL類自己,而只導出DLL類內函數也有一些好處,一些咱們不但願外界知道的函數能夠不設置導出標記,這進一步保護了DLL內函數的安全性。

 

9:複雜問題。

若咱們使用LoadLibrary顯式加載一個DLL,並嘗試在應用程序中調用一個類內成員函數的話,不管該函數是否在頭文件中有聲明,VS會給出一個"unresolved external symbol(未解析的外部符號)"的錯誤。咱們此時能夠將項目屬性中的內聯函數擴展選項修改成"Only __inline"或"Any Suitable"便可。但,咱們可能在調試連編的時候指望關閉內聯函數擴展,那麼另外一種解決方案是,將但願導出的函數聲明爲虛函數,例如

class CMyDllClass

{

   FK_DLL virtual void MyTestFun( int a ){  dosth(); }  

   // 用上面代碼替換 FK_DLL void MyTestFun( int a ){  dosth(); }  

}

這樣作還有一個額外的好處。將導出的類成員函數設置爲虛函數以後,該虛函數所在的類在應用程序中也如同被聲明同樣,能夠接受繼承。

例如如果上面的作法,應用程序就能夠進行順利繼承,而沒必要要求CMyDllClass 被標示爲導出。(原理不知,但願精通底層的高手協助解釋。)

class CAppClass : public CMyDllClass      // 應用程序內代碼,無需作任何處理。

{

       ....

}

相關文章
相關標籤/搜索