什么是dll:
dll只是一組源代碼的模塊,每個模塊包含一些可供應用程序或者其他dll調用的函數,在應用程序調用一個dll里面的函數的時候,操作系統會將dll的文件映像映射到進程的地址空間中,這樣進程中所有的線程就可以調用dll中的函數了
dll加載完成后,這個時候dll對於進程中的線程來說只是一些被放在地址進程空間附加的代碼和數據,操作系統為了節省內存空間,同一個dll在內存中只有一個,也就是說如果你的的兩個應用程序都需要加載user32.dll,那么操作系統也只會加載一次user32.dll到內存中
因為代碼段在內存中的權限都是為只讀的,所以當多個應用程序加載同一個dll的時候,不用擔心應用程序會修改dll的代碼段。當線程調用dll的一個函數,函數會在線程棧中取得傳遞給他的參數,並使用線程棧來存放他需要的變量,dll函數創建的任何對象都為調用線程或者調用進程擁有,dll不會擁有任何對象,也就是說如果dll中的一個函數調用了VirtualAlloc,系統會從調用進程的地址空間預定地址,即使撤銷了對dll的映射,調用進程的預定地址依然會存在,直到 用戶取消預定或者進程結束
示例代碼:
mylib.h
1 #ifdef MYLIBAPI 2 #else 3 #define MYLIBAPI extern "C" __desclspec(dllimport) 4 #endif 5 6 MYLIBAPI int g_nResult; 7 8 MYLIBAPI int Add(int nLeft,int nRight)
mylib.cpp
1 #include <windows.h> 2 3 #define MYLIBAPI extern "C" __declspec(dllexport) 4 #include "mylib.h" 5 int g_nResult; 6 7 int Add(int nLeft,int nRight) 8 { 9 g_nResult = nLeft + nRight; 10 return g_nResult; 11 }
輸入命令:
cl /LDd mylib.cpp
可以生成可供調試的dll
這個時候會多出四個文件,分別是mylib.exp,mylib.lib,mylib.dll,mylib.obj
mylib.obj保存的是在鏈接器生成dll的需要的信息
當鏈接器檢測到應用程序導出了一個函數或者變量,鏈接器就會生成mylib.lib文件,這個只是列出了導出的函數和變量的符號名
輸入命令查看lib里面的導出段
dumpbin -exports mylib.lib

我們可以看到這個lib里面export了_Add和_g_nResult
如果我們使用dumpbin -imports mylib.lib
imports里面沒有變量或者函數,這是因為lib里面記錄的只是導出的函數和變量,只有在聲明有導出函數或者變量的時候,才會生成這個文件
mylib.dll則是我們最終生成的模塊
如果使用dumpbin查看mylib.dll的導出
導入則因為太多,所以不貼出來
到我們需要將一個函數導出的時候,可以使用__desclspec(dllexport)來聲明為導出函數,需要從dll使用一個函數的時候,可以使用__desclspecc(dllimport)來前置聲明一個函數,當然,也可以不使用import前置聲明,但是使用improt可以明確告訴編譯器這些函數是從dll導入的,提高效率
什么是導出?
當將函數或者變量聲明為導出后,編譯器在生成obj的時候會嵌入一些額外的信息,以便於讓鏈接器在生成dll的時候使用,並且會生成一個記錄導出函數和變量的lib文件,在生成可執行文件的時候,我們需要通過鏈接這個lib來取得dll的一些信息,鏈接器在生成dll的時候,會在dll文件中嵌入一個導出符號表,這個符號表記錄了導出的函數和變量的符號名,並且保存對應的文件偏移量地址,這樣當可執行文件需要調用dll里面的函數的時候,可以通過這個符號表來找到對應函數的地址
最后我們開始構建可執行文件,代碼如下
myexe.cpp
#include <cstdio>
#include "mylib.h"
int main(void)
{
int nLeft = 10,nRight = 20;
printf("%d\n",Add(nLeft,nRight));
}
#include "mylib.h"
int main(void)
{
int nLeft = 10,nRight = 20;
printf("%d\n",Add(nLeft,nRight));
}
cl myexe.cpp mylib.lib
我們在編譯的時候一定要鏈接mylib.lib,這樣編譯器才知道要到哪里去找mylib的變量和函數相關信息,並且可執行文件也才知道程序需要mylib.dll這個dll,這樣程序在加載的時候會搜索用戶磁盤上的dll,如果沒找到則會報錯,找到則將dll映射到進程的內存空間里面
當dll映射到進程的內存空間里面后,加載程序會查看在對應的dll的導出段符號是否存在,如果不存在,則報錯,如果存在,那么加載程序會將該符號加載到該符號的所在的文件偏移量(RVA,虛擬地址,但在dll里面實際上是該符號所在文件的位置),加上該dll加載的虛擬地址,保存到可執行程序的導入段中,當代碼引用到導入符號的時候,可執行文件會去查看導入段並且得到導入符號的地址,這樣就能訪問導入的變量或者函數
例如我們生成的mylib.dlll文件,利用dumpbin可以得到輸出:
我們可以看到Add的RVA是1000,假設我們的dll被應用程序映射到1000的地址空間中,那么在應用程序執行的時候,Add函數最終會被加載到1000+1000即2000處,這個就是我們前面所說的dll在映射到地址空間后,對於應用程序來說不過是一堆附加的代碼和數據