1. 動態連接庫的分類html
Visual C++支持三種DLL,它們分別是Non-MFC DLL(非MFC動態庫)、 MFC Regular DLL(MFC規則DLL)、 MFC Extension DLL(MFC擴展DLL)。ios
(1) 非MFC動態庫:不採用MFC類庫結構,其導出函數爲標準的C接口,能被非MFC或MFC編寫的應用程序所調用。windows
(2) MFC規則DLL:包含一個繼承自CWinApp的類,但其無消息循環。安全
(3) MFC擴展DLL:採用MFC的動態連接版本建立,它只能被用MFC類庫所編寫的應用程序所調用。ide
2. DLL中導出函數的聲明方式函數
一種方式是:在函數聲明中加上__declspec(dllexport)。
另一種方式是:採用模塊定義(.def)文件聲明,(.def)文件爲連接器提供了有關被連接程序的導出、屬性及其餘方面的信息。測試
(1) 方式一:在函數聲明中加上__declspec(dllexport)編碼
1 #pragma once 2 3 extern "C" __declspec(dllexport) int add(int x, int y);
1 #include "stdafx.h" 2 #include "dllTest.h" 3 4 int add(int x, int y) 5 { 6 return (x + y); 7 }
(2) 方式二:採用模塊定義(.def)文件聲明spa
①首先建立 一個DLL程序(DllTestDef)3d
②在*.cpp中添加須要導出的接口函數
1 #include "stdafx.h" 2 3 int __stdcall Add(int a, int b) 4 { 5 return (a + b); 6 } 7 8 int _stdcall Sub(int a, int b) 9 { 10 return (a - b); 11 }
③而後建立一個.def的文件
1 LIBRARY dllTest 2 EXPORTS 3 Add @ 1 4 Sub @ 2
3. DLL的調用方式
(1)動態調用:
"LoadLibrary-GetProcAddress-FreeLibrary"系統API提供的三位一體"DLL加載-DLL函數地址獲取-DLL釋放"方式,這種調用方式稱爲DLL的動態調用。
1 #include "stdafx.h" 2 #include <iostream> 3 using namespace std; 4 #include <windows.h> 5 6 typedef int(*lpAdd)(int, int); 7 typedef int(*lpSub)(int, int); 8 9 int main() 10 { 11 HMODULE hDll = NULL; 12 lpAdd pAdd = NULL; 13 lpSub pSub = NULL; 14 15 hDll = LoadLibrary(TEXT("dllTest.dll")); 16 if (hDll != NULL) 17 { 18 pAdd = (lpAdd)GetProcAddress(hDll, "Add"); 19 pSub = (lpSub)GetProcAddress(hDll, "Sub"); 20 21 if (pAdd != NULL) 22 { 23 cout << "3 + 5 = " << pAdd(3, 5) << endl; 24 } 25 26 if (pSub != NULL) 27 { 28 cout << "2 + 4 = " << pSub(2, 4) << endl; 29 } 30 } 31 32 getchar(); 33 return 0; 34 }
(2)靜態調用:
靜態調用,也稱爲隱式調用,由編譯系統完成對DLL的加載和應用程序結束時DLL卸載的編碼(Windows系統負責對DLL調用次數的計數),調用方式簡單,可以知足一般的要求。一般採用的調用方式是把產生動態鏈接庫時產生的.LIB文件加入到應用程序的工程中,想使用DLL中的函數時,只須在源文件中聲明一下。
1 #include "stdafx.h" 2 #include <iostream> 3 using namespace std; 4 5 #pragma comment(lib, "..\\x64\\Release\\dllTest.lib") 6 extern "C" __declspec(dllimport) int add(int x, int y); 7 8 int main() 9 { 10 cout << "4 + 7 = " << add(4, 7) << endl; 11 12 getchar(); 13 return 0; 14 }
4. DllMain函數
Windows在加載DLL的時候,須要一個入口函數,如同控制檯或 DOS 程序須要 main 函數、WIN32程序須要 WinMain 函數同樣。
在前面的例子中,DLL並無提供 DllMain函數, 應用工程也能成功引用 DLL,這是由於Windows在找不到DllMain的時候, 系統會從其它運行庫中引入一個不作任何操做的缺省 DllMain函數版本,並不意味着 DLL能夠放棄 DllMain函數。
根據編寫規範, Windows必須查找並執行 DLL裏的 DllMain函數做爲加載 DLL的依據,它使得 DLL得以保留在內存裏。這個函數並不屬於導出函數,而是 DLL 的內部函數。這意味着不能直接在應用工程中引用 DllMain函數, DllMain是自動被調用的 。
1 BOOL APIENTRY DllMain( HMODULE hModule, 2 DWORD ul_reason_for_call, 3 LPVOID lpReserved 4 ) 5 { 6 switch (ul_reason_for_call) 7 { 8 case DLL_PROCESS_ATTACH: 9 case DLL_THREAD_ATTACH: 10 case DLL_THREAD_DETACH: 11 case DLL_PROCESS_DETACH: 12 break; 13 } 14 return TRUE; 15 }
5. 關於調用約定
C/C++缺省的調用方式是__cdecl方式,Windows API使用__stdcall調用方式,在DLL導出函數中,爲了跟Windows API保持一致,建議使用__stdcall調用方式。
__cdecl方式與__stdcall對函數名最終生成符號的方式不一樣。若採用C編譯方式(在C++中需將函數聲明爲extern "C")。
__stdcall調用約定在輸出函數名錢加下劃線,後面加「@」符號和參數的字節數,形如_functionname@number;
而__cdecl調用約定僅在輸出函數名前加下劃線,形如_functionname。
6. DLL導出變量
DLL定義的全局變量能夠被調用進程訪問,DLL也能夠訪問調用進程的全局數據。
(1) 在DLL中導出變量有兩種方法:
方法一:用模塊定義文件(.def)進行導出聲明
1 // dllmain.cpp : 定義 DLL 應用程序的入口點。 2 #include "stdafx.h" 3 4 int dllGlobalVar; 5 6 BOOL APIENTRY DllMain( HMODULE hModule, 7 DWORD ul_reason_for_call, 8 LPVOID lpReserved 9 ) 10 { 11 switch (ul_reason_for_call) 12 { 13 case DLL_PROCESS_ATTACH: 14 dllGlobalVar = 123; 15 break; 16 case DLL_THREAD_ATTACH: 17 case DLL_THREAD_DETACH: 18 case DLL_PROCESS_DETACH: 19 break; 20 } 21 return TRUE; 22 } 23 24 25 26 LIBRARY dllExportVariable_def 27 EXPORTS 28 dllGlobalVar DATA
特別要注意的是用extern int dllGlobalVar聲明所導入的並非DLL中全局變量自己,而是其地址,應用程序必須經過強制指針轉換來使用DLL中的全局變量。這一點,從*(int*)dllGlobalVar能夠看出。所以在採用這種方式引用DLL全局變量時,千萬不要進行這樣的賦值操做:
dllGlobalVar = 1;
其結果是dllGlobalVar指針的內容發生變化,程序中之後再也引用不到DLL中的全局變量了。
而經過_declspec(dllimport)方式導入的就是DLL中全局變量自己而再也不是其地址了,筆者建議在一切可能的狀況下都使用這種方式。
方法二:用__declspec進行導出聲明
1 __declspec(dllexport) extern int dllGlobalVar = 88;
(2) 調用DLL中導出的變量:
一樣,應用程序調用DLL中的變量也有兩種方法。
第一種是隱式連接:
1 #include <iostream> 2 using namespace std; 3 4 #pragma comment(lib, "..\\x64\\Debug\\dllExportVariable_declspec.lib") 5 extern _declspec(dllimport) int dllGlobalVar; 6 7 int main() 8 { 9 cout << "dllGlobalVar = " << dllGlobalVar << endl; 10 11 dllGlobalVar = 88; 12 cout << "dllGlobalVar = " << dllGlobalVar << endl; 13 14 getchar(); 15 return 0; 16 }
第二種是顯式連接:
1 #include <iostream> 2 using namespace std; 3 4 #include <windows.h> 5 6 int main() 7 { 8 int my_int; 9 HINSTANCE hInstLibrary = LoadLibrary(TEXT("dllExportVariable_def.dll")); 10 11 if (hInstLibrary != NULL) 12 { 13 my_int = *(int*)GetProcAddress(hInstLibrary, "dllGlobalVar"); 14 cout << "my_int = " << my_int << endl; 15 } 16 FreeLibrary(hInstLibrary); 17 18 getchar(); 19 return 0; 20 }
Note:通常不建議從DLL中導出全局變量,對於但願從DLL獲取資源以實現資源共享的情景,最好是經過導出一個Get函數得到,這樣操做起來更方便並且更安全。
7. DLL導出類
1、導出類的簡單方式
這種方式是比較簡單的,同時也是不建議採用的不合適方式。
只須要在導出類加上__declspec(dllexport),就能夠實現導出類。對象空間仍是在使用者的模塊裏,dll只提供類中的函數代碼。
不足的地方是:使用者須要知道整個類的實現,包括基類、類中成員對象,也就是說全部跟導出類相關的東西,使用者都要知道。經過Dependency Walker能夠看到,這時候的dll導出的是跟類相關的函數:如構造函數、賦值操做符、析構函數、其它函數,這些都是使用者可能會用到的函數。
這種導出類的方式,除了導出的東西太多、使用者對類的實現依賴太多以外,還有其它問題:必須保證使用同一種編譯器。導出類的本質是導出類裏的函數,由於語法上直接導出了類,沒有對函數的調用方式、重命名進行設置,致使了產生的dll並不通用。
簡單方式導出類的DLL示例:
1 #pragma once 2 3 //相關的類都必須導出 4 class _declspec(dllexport) CBase 5 { 6 public: 7 void Test1(); 8 private: 9 int m_var1; 10 }; 11 12 //相關的類都必須導出 13 class _declspec(dllexport) CData 14 { 15 public: 16 void Test2(); 17 private: 18 int m_var2; 19 }; 20 21 //要導出的類 22 class _declspec(dllexport) CExportClass : public CBase 23 { 24 public: 25 CExportClass(int i = 0); 26 27 void TestFun(); 28 CData GetDataObj() { return m_DataObj; } 29 30 private: 31 int m_i; 32 CData m_DataObj; 33 };
1 CExportClass::CExportClass(int i) : m_i(i) 2 { 3 } 4 5 void CExportClass::TestFun() 6 { 7 cout << "This is TestFun() from CExportClass class!" << endl; 8 } 9 10 void CBase::Test1() 11 { 12 cout << "This is Test1() from CBase class!" << endl; 13 } 14 15 void CData::Test2() 16 { 17 cout << "This is Test2() from CData class!" << endl; 18 }
調用示例:
1 #include "stdafx.h" 2 #include <Windows.h> 3 #include "..\dllTest_ExportClass(NotRecommend)\dllTest_ExportClass(NotRecommend).h" 4 5 #pragma comment(lib, "..\\x64\\Debug\\dllTest_ExportClass(NotRecommend).lib") 6 7 int main() 8 { 9 CExportClass obj(55); 10 obj.Test1(); 11 obj.TestFun(); 12 13 CData DataObj = obj.GetDataObj(); 14 DataObj.Test2(); 15 16 system("pause"); 17 return 0; 18 }
2、導出類的較好方式
這種方式和COM相似,它的結構是這樣的:導出類是一個派生類,派生自一個抽象類(都是純虛函數)。使用者只需知道這個抽象類的結構。
DLL最少須要提供一個用於獲取類對象指針的接口。使用者和DLL提供者共用一個抽象類的頭文件。使用者依賴於DLL的東西不多,只須要知道抽象類的接口,以及獲取對象指針的導出函數,對象內存空間的申請是在DLL模塊中作的,釋放也在DLL模塊中完成(須要在最後調用釋放對象的函數)。
這種方式比較好,通用,產生的DLL沒有特定的環境限制。除了對DLL導出類有好處外,它採用接口和實現分離,也可使得工程結構更清晰,使用者只須要知道接口,不須要知道實現。
測試示例代碼下載地址: https://files.cnblogs.com/files/YQ2014/dllTest_ExportClass%28NotRecommend%29.zip
參考資料:
http://www.cnblogs.com/cswuyg/archive/2011/10/06/DLL2.html
http://www.codeproject.com/KB/cpp/howto_export_cpp_classes.aspx
導出類的DLL要當心DLL Hell問題。
詳細的能夠參考:DLL導出類避免地獄問題的完美解決方案