轉:DLL動態連結程式庫和LIB靜態連結庫之程式員分析

來源:互聯網
上載者:User
http://www.cnblogs.com/strinkbug/archive/2007/04/24/725050.html什麼是lib檔案,lib和dll的關係如何

(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動態連結程式庫和LIB靜態連結庫之程式員經驗分析

http://www.cnblogs.com/lancidie/archive/2011/03/12/1982253.html

Author: FreeKnight

前言:依舊,吐槽,哈哈哈~~~~其實依舊是為公司程式員掃盲,呼,挺想睡覺的其實。言歸正傳吧。

1:神馬是Dll和Lib,神馬是靜態連結和動態連結

大家都懂的,DLL就是動態連結程式庫,LIB是靜態連結庫。DLL其實就是EXE,只不過沒main。

動態連結是相對於靜態連結而言的。所謂靜態連結就是把函數或過程直接連結到可執行檔中,成為可執行程式中的一部分,當多個程式調用同樣的函數時,記憶體裡就會有這個函數的多個拷貝,浪費記憶體資源。而動態連結則是提供了一個函數的描述資訊給可執行檔(並沒有記憶體拷貝),當程式被夾在到記憶體裡開始啟動並執行時候,系統會在底層建立DLL和應用程式之間的串連關係,當執行期間需要調用DLL函數時,系統才會真正根據連結的定位資訊去執行DLL中的函數代碼。

在WINDOWS32系統底下,每個進程有自己的32位的線性地址空間,若一個DLL被進程使用,則該DLL首先會被調入WIN32系統的全域堆棧,然後通過記憶體對應檔方式映射到這個DLL的進程地址空間。若一個DLL被多個進程調用,則每個進程都會接收到該DLL的一個映像,而非多份的拷貝。但,在WIN16系統下,每個進程需要擁有自己的一份DLL空間,可以理解為何靜態連結沒啥區別。

2:DLL和LIB區別和聯絡。

DLL是程式在運行階段才需要的檔案。

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      // 應用程式內代碼,無需做任何處理。

{

       ....

}

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.