1. 程式人生 > >DLL的相關理解

DLL的相關理解

多個 控制臺程序 入口 window 完全 spec int height 點擊

一種優雅的動態鏈接庫DLL的使用

1. 什麽是DLL(動態鏈接庫)?

  動態鏈接庫(DLL)是從C語言函數庫和Pascal庫單元的概念發展而來的。所有的C語言標準庫函數都存放在某一函數庫中。在鏈接應用程序的過程中,鏈接器從庫文件中拷貝程序調用的函數代碼,並把這些函數代碼添加到可執行文件中。這種方法同只把函數儲存在已編譯的OBJ文件中相比更有利於代碼的重用。但隨著Windows這樣的多任務環境的出現,函數庫的方法顯得過於累贅。如果為了完成屏幕輸出、消息處理、內存管理、對話框等操作,每個程序都不得不擁有自己的函數,那麽Windows程序將變得非常龐大。Windows的發展要求允許同時運行的幾個程序共享一組函數的單一拷貝

。動態鏈接庫就是在這種情況下出現的。動態鏈接庫不用重復編譯或鏈接,一旦裝入內存DLL函數可以被系統中的任何正在運行的應用程序軟件所使用,而不必再將DLL函數的另一拷貝裝入內存。

  DLL是一個包含可由多個程序同時使用的代碼和數據的庫。例如:在Windows操作系統中,Comdlg32 DLL執行與對話框有關的常見函數。

  因此,每個程序都可以使用該DLL中包含的功能來實現“打開”對話框。這有助於促進代碼重用和內存的有效使用。這篇文章的目的就是讓你一次性就能了解和掌握DLL。

2. 為什麽要使用DLL(動態鏈接庫)?

  代碼復用是提高軟件開發效率的重要途徑

  一般而言,只要某部分代碼具有通用性,就可以將它構造成相對獨立的功能模塊並在之後的項目中重復使用。

  比較常見的例子是各種應用程序框架,它們都以源代碼的形式發布。由於這種復用是源代碼級別的,源代碼完全暴露給了程序員,因而稱之為“白盒復用”。白盒復用有以下三個缺點:

1.暴露源代碼,多份拷貝,造成存儲浪費;
2.容易與程序員的本地代碼發生命名沖突;
3.更新模塊功能比較困難,不利於問題的模塊化實現;

  為了彌補這些不足,就提出了“二進制級別”的代碼復用了。使用二進制級別的代碼復用一定程度上隱藏了源代碼,對於“黑盒復用”的途徑不只DLL一種,靜態鏈接庫,甚至更高級的COM組件都是。

  使用DLL主要有以下優點:

1.使用較少的資源;當多個程序使用同一函數庫時,DLL可以減少在磁盤和物理內存中加載的代碼的重復量。

  這不僅可以大大影響在前臺運行的程序,而且可以大大影響其它在Windows操作系統上運行的程序;
2.推廣模塊式體系結構;
3.簡化部署與安裝。

  優雅創建DLL,與優雅使用DLL庫 步驟

3.1 創建DLL

  用VS2013新建工程,

、  技術分享

  技術分享

  點擊確定,新建工程完畢

  這個時候VS2013自動幫我們添加了dllmain文件,

  技術分享

  Windows在加載DLL時,需要一個入口函數,就像控制臺程序需要main函數一樣。有的時候,DLL並沒有提供DllMain函數,應用程序也能成功引用DLL,這是因為Windows在找不到DllMain的時候,系統會從其它運行庫中引入一個不做任何操作的默認DllMain函數版本,並不意味著DLL可以拋棄DllMain函數。(因此我們可以不使用該dllmain文件,或者刪除即可根據編寫規範,Windows必須查找並執行DLL裏的DllMain函數作為加載DLL的依據,它使得DLL得以保留在內存裏。這個函數並不屬於導出函數,而是DLL的內部函數這就說明不能在客戶端直接調用DllMain函數,DllMain函數是自動被調用的

  DllMain函數在DLL被加載和卸載時被調用,在單個線程啟動和終止時,DllMain函數也被調用。參數ul_reason_for_call指明了調用DllMain的原因,有以下四種情況:

DLL_PROCESS_ATTACH:當一個DLL被首次載入進程地址空間時,系統會調用該DLL的DllMain函數,傳遞的ul_reason_for_call參數值為DLL_PROCESS_ATTACH。這種情況只有首次映射DLL時才發生;

DLL_THREAD_ATTACH:該通知告訴所有的DLL執行線程的初始化。當進程創建一個新的線程時,系統會查看進程地址空間中所有的DLL文件映射,之後用DLL_THREAD_ATTACH來調用DLL中的DllMain函數。要註意的是,系統不會為進程的主線程使用值DLL_THREAD_ATTACH來調用DLL中的DllMain函數;

DLL_PROCESS_DETACH:當DLL從進程的地址空間解除映射時,參數ul_reason_for_call參數值為DLL_PROCESS_DETACH。當DLL處理DLL_PROCESS_DETACH時,DLL應該處理與進程相關的清理操作。如果進程的終結是因為系統中有某個線程調用了TerminateProcess來終結的,那麽系統就不會用DLL_PROCESS_DETACH來調用DLL中的DllMain函數來執行進程的清理工作。這樣就會造成數據丟失;

DLL_THREAD_DETACH:該通知告訴所有的DLL執行線程的清理工作。註意的是如果線程的終結是使用TerminateThread來完成的,那麽系統將不會使用值DLL_THREAD_DETACH來執行線程的清理工作,這也就是說可能會造成數據丟失,所以不要使用TerminateThread來終結線程

3.2 編寫DLL的函數

  編寫DLL時的函數與一般的函數方法基本一樣。但要對庫中的函數進行必要的聲明,以說明哪些函數是可以導出的,哪些函數是不可以導出的。

  把DLL中的函數聲明為導出函數的方法有兩種:

一是使用關鍵字_declspec(dllexport)來聲明。

二是在.def文件中聲明。

一:使用關鍵字來聲明創建

  (1)普通函數

 1 #ifndef _MYCODE_H_
 2 #define _MYCODE_H_
 3 #ifdef DLLDEMO1_EXPORTS
 4 #define EXPORTS_DEMO _declspec( dllexport )
 5 #else
 6 #define EXPORTS_DEMO _declspec(dllimport)
 7 #endif
 8      EXPORTS_DEMO int Add (int a , int b);
 9 #endif
10 
11 或者:
12 
13 #ifndef _MYCODE_H_
14 #define _MYCODE_H_
15 #ifdef DLLDEMO1_EXPORTS
16 #define EXPORTS_DEMO _declspec( dllexport )
17 #else
18 #define EXPORTS_DEMO _declspec(dllimport)
19 #endif
20 extern "C" EXPORTS_DEMO int Add (int a , int b);
21 #endif

  【1】這裏采用宏定義的方法進行聲明,這樣就可以把頭文件作為與 生成的DLL、lib 三者一起進行打包發送給使用者。使用者根據宏定義的使用,即可直接包含  

  【2】extern C

  為了使一個用C++語言編寫的DLL函數可以在C語言編寫的應用程序中使用,在關鍵字_declspec(dllexport) 之前要附加另一個關鍵字:extern “C”,以通知編譯器采用C鏈接方式。

為什麽要使用extern “C”呢?C++之父在設計C++時,考慮到當時已經存在了大量的C代碼,為了支持原來的C代碼和已經寫好的C庫,需要在C++中盡可能的支持C,而extern “C”就是其中的一個策略。在聲明函數時,註意到我也使用了extern “C”,這裏要詳細的說說extern “C”。

extern “C”包含兩層含義,首先是它修飾的目標是”extern”的;其次,被它修飾的目標才是”C”的。先來說說extern;在C/C++中,extern用來表明函數和變量作用範圍(可見性)的關鍵字,這個關鍵字告訴編譯器,它申明的函數和變量可以在本模塊或其它模塊中使用。extern的作用總結起來就是以下幾點:

1.在一個文件內:

  如果外部變量不在文件的開頭定義,其有效範圍只限定在從定義開始到文件的結束處。如果在定義前需要引用該變量,則要在引用之前用關鍵字”extern”對該變量做”外部變量聲明”,表示該變量是一個已經定義的外部變量。有了這個聲明,就可以從聲明處起合理地使用該變量了。

#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
      extern int a;
      cout<<a<<endl;
}
int a = 100;

2.在多文件的程序中:

  如果多個文件都要使用同一個外部變量,不能在各個文件中各定義一個外部變量,否則會出現“重復定義”的錯誤

  正確的做法是在任意一個文件中定義外部變量,其它文件用extern對變量做“外部變量聲明”

  在編譯和鏈接時,系統會知道該變量是一個已經在別處定義的外部變量,並把另一文件中外部變量的作用域擴展到本文件,這樣在本文件就可以合法地使用該外部變量了。寫過MFC程序的人都知道,在在CXXXApp類的頭文件中,就使用extern聲明了一個該類的變量,而該變量的實際定義是在CXXXApp類的實現文件中完成的;

3. 外部函數:

  在定義函數時,如果在最左端加關鍵字extern,表示此函數是外部函數。C語言規定,如果在定義時省略extern,則隱含為外部函數。而內部函數必須在前面加static關鍵字。在需要調用此函數的文件中,用extern對函數作聲明,表明該函數是在其它文件中定義的外部函數。

  綜上,因為是對C++ 的類的dll導出,則可以采用下述形式,且可以用添加extern c 的形式:

 1 #ifndef AMF_H
 2 #define AMF_H
 3 
 4 #if defined(_WIN32) || defined(_WIN64) /*Windows*/
 5     #ifdef DLL_EXPORT
 6         #define LIBSPEC __declspec(dllexport)
 7     #elif defined(DLL_IMPORT)
 8         #define LIBSPEC __declspec(dllimport)
 9     #else
10         #define LIBSPEC //什麽都沒有定義,直接使用源碼
11     #endif
12 #else /*non-windows*/
13     #define LIBSPEC /*TODO: Allow this header file to generate (and be distributed) with non-windows shared objects*/
14 #endif
15 
16 
17 class LIBSPEC Amf /*LIBSPEC macro allows windows users the option of using this class in DLL form */
18 {
19 public:
20     Amf();
21     ~Amf();
22     Amf(const Amf& In);
23     Amf& operator=(const Amf& In);
24 
25 
26     //Amf I/O
27     bool Save(std::string AmfFilePath, bool Compressed = true); 
28     bool Load(std::string AmfFilePath, bool StrictLoad = true); 
29 }
30 
31 #undef LIBSPEC
32 #endif //AMF_WIN_H

  把上述作為 CXXXXX.h 的頭文件內容,因為該文件最終要副帶給使用者因此,不合適在臨面進行函數實現。

  而函數或者類的實現作為CXXXXX.cpp 源文件中。

1 extern "C" __declspec(dllexport) void SayHello()
2 
3 {
4 
5     ::MessageBoxW(NULL, L"Hello", L"fangyukuan", MB_OK);
6 
7 }

或者

  普通的類的實現源文件相同。

#include “CXXXX.h”

//類的實現,且不需要額外修飾
CXXX::CXXX()
{
    
}

CXXX::XXXX()
{

}

編譯即可生成相相應的DLL:

  一般生成四個文件,其中最重要的額DLL是動態鏈接庫, lib是函數接口的映射,以及 包含導出生命的頭文件.h

  技術分享

4 使用DLL

  基本上要把三個文件進行使用:

  (1)XXX.dll   文件,放在程序生成exe的目錄下面;

  (2)XXX.lib  文件,可以像在項目中進行加載,也可以放在程序使用的代碼中加載。

  放在程序中加載:告訴連接器,lib文件的位置,以及需要連接加載的lib名稱

  技術分享

  技術分享

  放在代碼中加載:

 1 #include <windows.h>
 2 #include <iostream>
 3 //(1)添加dll的頭文件
 4 #include "..\\DLLDemo1\\CXXX.h"
 5 using namespace std;
 6 //(2)加載lib文件
 7 #pragma comment(lib, "..\\debug\\DLLDemo1.lib")
 8 // (3)dll放在 exe目錄即可
 9 int main(int argc, char *argv[])
10 {
11       cout<<Add(2, 3)<<endl;
12       return 0;
13 }

  在使用上,一般沒什麽差異,直接利用include的頭文件中的類型進行使用即可。

5. 使用VS6中的工具進行檢測

  depend.exe 可以用來打開dll文件,並查看dll的依賴文件,以及dll中的導出的函數接口。

  技術分享

  註意:如果沒有使用extern c聲明的導出接口,其depends開發的效果如下:

  即名字為 ?Add@@[email protected]

  技術分享

  如果用extern C聲明, 可函數名稱就正常了。

  技術分享

  不過,一般如果不采用直接對單個導出函數進行單獨使用的方式,上述兩種差異不大。

  為了簡潔,且一般開發的C++ 的dll ,如果包含類,則沒必要使用 extern c 因為,C是沒辦法調用C++ 的dll 的。

 

endl;

DLL的相關理解