DLL的前世今因

網上的有關DLL的文章有很多,也很雜。這裏我花了一段時間,查了資料,把各種版本自己動手試了試。以下是我的學習筆記,希望對大家有所幫助。

DLL:動態鏈接庫。

動態鏈接庫就是一種別人已經寫好的代碼(一般是函數或類),並且已經編譯和鏈接好了,我們只是需要按照一定的規則就可以使用這些代碼。

並且動態鏈接庫是可以進行跨語言的。就是說C的DLL可以在Java中使用。


DLL是與exe分開的,當exe執行的時候纔去找DLL中函數,而普通的lib文件是鏈接的時候就已經加載到exe中的。

動態鏈接庫 (DLL) 是作爲共享函數庫的可執行文件。動態鏈接提供了一種方法,使進程可以調用不屬於其可執行代碼的函數。函數的可執行代碼位於一個DLL 中,該 DLL 包含一個或多個已被編譯、鏈接並與使用它們的進程分開存儲的函數。DLL還有助於共享數據和資源。多個應用程序可同時訪問內存中單個 DLL 副本的內容。

動態鏈接與靜態鏈接的不同之處在於:動態鏈接允許可執行模塊(.dll 文件或 .exe 文件)僅包含在運行時定位 DLL 函數的可執行代碼所需的信息。在靜態鏈接中,鏈接器從靜態鏈接庫獲取所有被引用的函數,並將庫同代碼一起放到可執行文件中。

 

Lib文件是已經編譯好的,但沒有鏈接,所以程序鏈接的時候就會去找lib

Dll文件是已經編譯好,且鏈接好的,所以程序編譯和鏈接的時候都和它沒關係,只有執行的時候纔去找他。

 

 

應用程序和 DLL 之間的區別

應用程序可有多個同時在系統上運行的實例,而 DLL 只能有一個實例。

應用程序可以擁有堆棧、共用內存、文件句柄、消息隊列這樣的事物,而 DLL 不能。

 

 

 

使用 DLL 的優點

1節省內存和減少交換操作。很多進程可以同時使用一個 DLL,在內存中共享該 DLL 的一個副本。相反,對於每個用靜態鏈接庫生成的應用程序,Windows 必須在內存中加載庫代碼的一個副本。

2節省磁盤空間。許多應用程序可在磁盤上共享 DLL 的一個副本。相反,每個用靜態鏈接庫生成的應用程序均具有作爲單獨的副本鏈接到其可執行圖像中的庫代碼。

3升級到 DLL 更爲容易。當 DLL 中的函數發生更改時,只要函數的參數和返回值沒有更改,就不需重新編譯或重新鏈接使用它們的應用程序。相反,靜態鏈接的對象代碼要求在函數更改時重新鏈接應用程序。

4提供售後支持。例如,可修改顯示器驅動程序 DLL 以支持當初交付應用程序時不可用的顯示器。

5DLL可以被其他DLL調用,而lib庫不可以被其他lib調用。

 

 

 

生成DLL文件

這裏由於我們是在VS中生成的,所以可以選擇生成C語言的DLL、或生成C++DLL這裏我們是生成C語言的DLL,然後在C++中使用。

我們首先new project->C++->Win 32 Console->DLL-文件名:MYdll

這裏我們可以得到MYdll.cpp文件了。(Dllmain.cpp是入口函數不用管)

這裏由於後面使用時可能會用到.h文件,所以我們new一個頭文件,文件名:MYhead.h

3種方法:

1. 使用__declspec(dllexport) 關鍵字生成DLL

1. MYhead.h文件中聲明要導出函數

若要導出函數,__declspec(dllexport) 關鍵字必須出現在調用約定關鍵字的左邊

若要導出類中的所有公共數據成員和成員函數,關鍵字必須出現在類名的左邊。

例如:

//MYhead.h

extern "C" _declspec(dllexport) int Max(int a, intb);

//extern “C”出現的目的是由於我們此時在VS中,而此時默認的是用C++來編譯的,而我們想要的是C語言的DLL,所以要加。如果你想得到C++DLL,這裏就不需要加extern”C”了。

2. MYdll.cpp文件中把函數實現

//MYdll.cpp

#include”MYhead.h”

int Max(int a, int b)

{

return (a>b)? a:b;

}

3. 然後buildsolution就會在debug中出現libdll文件了。

2. 使用def文件生成DLL

1. MYhead.h文件中聲明要導出函數

例如:

//MYhead.h

extern "C" int Max(int a, int b);

//extern “C”的原因上面已經說過。

2. MYdll.cpp文件中把函數實現

//MYdll.cpp

#include”MYhead.h”

int Max(int a, int b)

{

return (a>b)? a:b;

}

3. new一個def文件,名字隨便。在此文件中寫入下列語句:

LIBRARY MYdll(注意,這裏是實現函數的文件名,不能是聲明文件的文件名。)

EXPORTS

Max(這裏是要導出的函數名,如果有多個,換行接着就可以了)

4. 然後buildsolution就會在debug中出現libdll文件了。

3. 使用#pragma comment(linker,”/export:_functionname”)

1. MYhead.h文件中聲明要導出函數

例如:

//MYhead.h

#pragma comment(linker,”/export:_Max”)

//注意:這裏是_Max,因爲/export:後面的必須是函數編譯之後的函數名,而C語言編譯後的函數名就是在原函數名前加一個下劃線。

注意:這種方法有個弊端,我們必須要預先知道函數編譯後的函數名,C語言還可以表示,但是C++就很難表示了。所以這種方法限於生成C語言的DLL

extern "C" int Max(int a, int b);

//extern “C”的原因上面已經說過。

2. MYdll.cpp文件中把函數實現

//MYdll.cpp

#include”MYhead.h”

int Max(int a, int b)

{

return (a>b)? a:b;

}

3. 然後buildsolution就會在debug中出現libdll文件了。

 

注:這裏爲何生成了lib文件?

這裏的lib文件是DLL文件中函數名稱和地址,如果是隱式鏈接的話,鏈接的時候程序會鏈接DLL的導入庫文件(就是這個lib文件)。在程序執行時,程序纔會根據導入庫文件中的函數地址去到DLL中尋找函數體。

DLL<-----àLIB<-----àEXE

注:DLL內部函數的調用是不用加什麼_declspec(dllexport)的,想一想DLL的原理就知道原因了。

 

鏈接DLL的方法(如何使用DLL)

注:編譯時是和庫文件沒有關係的

 

有兩種類型的鏈接:隱式鏈接和顯式鏈接。(隱式鏈接要把生成DLL的程序會產生DLL文件和導入文件lib(有時還需要.h文件)放到當前工程目錄下,顯示鏈接只需要DDL文件就可以了)

隱式鏈接:

隱式鏈接就是在程序開始執行時就將DLL文件加載到應用程序當中。爲隱式鏈接到 DLL,可執行文件必須從 DLL 的提供程序獲取下列各項:

1包含導出函數和/ C++ 類的聲明的頭文件(.h 文件)使用__declspec(dllimport)

將函數的定義導入進來 

2要鏈接的導入庫(.LIB files)。(生成 DLL 時鏈接器創建導入庫。)

3實際的 DLL.dll 文件)。

 例如:

// TestDll.h

#pragma comment(lib,"MyDll.lib")

(!!這一步開發人員也可以不使用#pragma comment(lib"MyDll.lib")語句,而直接在工程的Setting->屬性->Linker->Input->Additional Dependencies最後加MYDll.lib;注意要加”;”)

extern "C" _declspec(dllimport)int Min(int a,int b); !!如果要導出的是變量:則

extern“C” extern _declspec(dllimport) int a;因爲只有加上extern,這纔是聲明。

這行語句也可以用

extern“ C” {

#include”Mydll.h”

}

來代替,效果是一樣的。(注意要把MyDll.h拷到此工程下).推薦這樣使用。

這裏我們是使用C語言生成的DLL,這裏我們要用C++來使用,所以要加extern”C”.如果你使用的是用C++生成的DLL,這裏就不需要extern “C”了。

<span style="font-size:18px;">//TestDll.cpp
#include<iostream>
#include<Windows.h>
#include"Dlltest.h"
void main()
{int a;
a=Min(8,10)
cout<<a<<endl;
}</span>


 

/////////////////////////////////////////////////////////////

隱式鏈接體會:

1. Dll和lib不是都已經有了嗎,爲何還需要#include<.h>或dllimport。

因爲dll需要靜態導入,所以需要lib導入庫,由於lib導入庫中的函數名都是c編譯好的,函數名都是_funcname形式的,所以需要#include<..h>或dllimport函數名,這樣編譯時纔會生成函數名類似_funcname形式的。然後鏈接時纔會正確鏈接lib導入庫。

 

2. 爲何#include頭文件中要加extern “C”或者_declspec(dllimport)前要加extern “C”。

因爲lib導入庫是用C語言編譯好的,函數名形如_funcname.如果用C++來編譯頭文件或_declspec(dllimport)後面的函數名,由於C++支持多態,所以它編譯函數名時要把函數的參數個數、參數類型一起柔和在一起,則函數名會是_func_int_float類似的東西。而這樣的函數名顯示與lib導入庫的函數名匹配不了的。

 

//////////////////////////////////////////////////////////////

 

 

顯式鏈接:

顯式鏈接是應用程序在執行過程中隨時可以加載DLL文件,也可以隨時卸載DLL文件,這是隱式鏈接所無法作到的,所以顯式鏈接具有更好的靈活性,對於解釋性語言更爲合適。

在顯式鏈接下,應用程序必須進行函數調用以在運行時顯式加載 DLL。爲顯式鏈接到 DLL,應用程序必須:

1調用 LoadLibrary(或相似的函數)以加載 DLL 和獲取模塊句柄。

2調用 GetProcAddress,以獲取指向應用程序要調用的每個導出函數的函數指針。由於應用程序是通過指針調用 DLL 的函數,編譯器不生成外部引用,故無需與導入庫鏈接。

3使用完 DLL 後調用 FreeLibrary

<span style="font-size:18px;">#include<iostream>
#include<Windows.h>
using namespace std;
void main(void)
{
typedef int(*pMax)(int a,int b);
HINSTANCE hDLL;
pMax Max;
hDLL=LoadLibrary("MyDll.dll");//加載動態鏈接庫MyDll.dll文件;
Max=(pMax)GetProcAddress(hDLL,"Max");
int a=Max(5,8);
cout<<a<<endl;
FreeLibrary(hDLL);//卸載MyDll.dll文件;
System(“pause”);
}</span>


 

!!!!最後這裏給出一個非常有用的命令

查看lib文件、dll文件,使用命令行:

>dumpbin /exports xxx.lib/xxx.dll

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