Windows Dll動態庫隱式鏈接

Dll動態庫隱式鏈接

本文主要介紹以下幾個方面:

  • 構建DLL的步驟
  • 構建可執行的模塊的步驟
  • 隱式鏈接步驟
  • 讓程序運行起來

構建DLL的步驟

1)我們必須創建一個頭文件,在其中包含我們想要的DLL中導出的函數原型、結構以及符號。爲了構建該DLL,DLL的所有源文件需要包含這個頭文件。

2)創建C/C++源文件來實現想要在DLL模塊中導出的函數和變量。

3)在構建DLL模塊的時候,編譯器會對每個源文件進行處理併產生一個.obj模塊(每個源文件對應一個.obj模塊)。

4)當所有.obj模塊都創建完畢之後,連接器會將所有.obj模塊的內容合併起來,產生一個單獨的DLL映像文件,這個映像文件包含DLL中所有二進制代碼以及全局/靜態變量。

5)如果鏈接器檢測到DLL的源文件輸出了至少一個函數或變量,那麼鏈接器還會生成一個.lib文件。這個lib文件非常小,這是因爲它不包含任何函數或者變量。它只是列出了所有被導出的函數和變量的符號名。

構建可執行的模塊的步驟

1)在所有引用了導出的函數、變量、數據結構或符號的源文件中,必須包含由DLL的開發人員所創建的頭文件。

2)創建C/C++源文件來實現想要包含在可執行模塊中的函數和變量。
當然,代碼可以引用在DLL的頭文件中定義的函數和變量。

3)在構建可執行模塊的時候,編譯器會對每個源文件進行處理併產生一個.obj模塊(每個源文件對應一個.obj模塊)。

4)當所有.obj模塊都創建完畢之後,連接器會將所有.obj模塊的內容合併起來,產生一個單獨的可執行映像文件。這個映像文件包含可執行文件中所有二進制代碼以及全局/靜態變量。該可執行模塊還包含一個導入段(import section),其中列出了所有它需要的DLL模塊的名稱。此外,對列出的每個DLL,該段還記錄了可執行文件的二進制代碼從中引用的函數和變量的符號名。

5)加載程序先爲新的進程創建一個虛擬地址空間,並將可執行模塊映射到新進程的地址空間中。加載程序接着解析可執行模塊的導入段。對導入段中列出的每個DLL,加載程序會在用戶的系統中對該DLL模塊進行定位,並將該DLL映射到進程的地址空間。注意,由於DLL模塊可以從其他DLL模塊中導入函數和變量,因此DLL模塊可能有自己的導入段並需要將它所需的DLL模塊映射到進程的地址空間中。

隱式鏈接步驟

創建DLL頭文件,在源文件中對頭文件中聲明的函數或者變量進行定義。
userdefine.h頭文件如下

#ifdef USERDEFINE_EXPORTS
#define USERDEFINE_API extern "C" __declspec(dllexport)
#else
#define USERDEFINE_API extern "C" __declspec(dllimport)
#endif


USERDEFINE_API int fnuserdefine(void);

USERDEFINE_API int g_nResult;//儘量避免導出變量

userdefine.cpp文件如下:

#define USERDEFINE_API extern "C" __declspec(dllexport)

#include "userdefine.h"
#include <tchar.h>

int g_nResult;

int fnuserdefine(void)
{
    g_nResult = 256;
    _tprintf(_T("DLL test example\n"));
    return g_nResult;
}

在編譯前面的DLL源文件的時候,USERDEFINE_API在包含userdefine.h頭文件之前定義爲__declspec(dllexport)。如果編譯器看到一個變量、函數或C++類是用__declspec(dllexport)修飾的,那麼它就知道應該在生成的DLL模塊中導出該變量、函數或者C++類。注意,對那些要被導出的函數和變量,我們必須在頭文件中的變量和函數定義的前面加上USERDEFINE_API的定義。另外注意的是,在源文件userdefine.cpp文件中不必在要被導出的變量和函數前面加USERDEFINE_API標識符。因爲編譯器在解析頭文件的時候會記住應該導出哪些變量或函數。
USERDEFINE_API符號包含extern “C”修飾符。只有在編寫C++代碼的時候,才應該使用這個修飾符,在編寫C代碼的時候不應該使用該修飾符。C++編譯器通常會對函數名和變量名進行改編(name-mangled),這在鏈接的時候會導致嚴重的問題。
可執行文件不應該在包含這個頭文件之前定義USERDEFINE_API。由於USERDEFINE_API未定義,因此頭文件會將USERDEFINE_API定義爲__declspec(dllimport),這樣編譯器就知道該可執行文件的源文件要從DLL模塊中導入一些變量和函數。

讓程序運行起來

可執行文件的源代碼如下:

#include "userdefine.h"
#pragma comment(lib,"userdefine.lib")
int _tmain( int argc, TCHAR* argv[] )
{
    int nRet = fnuserdefine();
    _tprintf(_T("nRet=%d\n"), nRet);
}

編譯源文件的時候,需要把DLL的頭文件和lib文件放在該工程目錄下。程序運行的時候需要向程序告知DLL的路徑。
啓動一個可執行模塊的時候,操作系統會加載程序會先爲進程創建虛擬地址空間,接着把可執行模塊映射到進程的地址空間。之後加載程序會檢查可執行模塊的導入段,試圖對所需的DLL進行定位並將它們映射到進程的地址空間中。
由於導入段只包含DLL的名稱,不包含DLL的路徑,因此加載程序必須在用戶的磁盤上搜索DLL。下面是加載程序的搜索順序。
1)包含可執行文件的目錄;
2)Windows的系統目錄;
3 )16位的系統目錄,即Windows目錄中System的子目錄;
4)Windows目錄;
5)進程的當前目錄;
6)PATH環境變量中所列出的目錄;

發佈了65 篇原創文章 · 獲贊 9 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章