一、動態鏈接庫(DLL)
動態鏈接庫提供了一種方法,使進程可以調用不屬於其執行代碼的函數。函數的可執行代碼位於一個.dll文件中,該文件包含一個或多個已被編譯、鏈接並使用它們的進程分開存儲的函數。
優點:
1.擴展了 應用程序的特性
2.可以使用多種編程語言來編寫
3.簡化項目的管理
二、依賴項
當某個程序或DLL使用其他DLL或DLL函數時,就會創建依賴項,因此程序就不會再獨立了,依賴項如果被破壞,該程序可能出現問題。
三、入口點
在創建DLL時,可以有選擇地指定入口點的函數。當進程或線程將它們自身附加到DLL或者將它們自身以DLL分離時,都會調用入口函數。入口函數應該只進行簡單的初始化工作,而不應該調用其他DLL函數或者終止函數。
關於創建DLL,我的環境是VS2017,步驟為:文件->新建項目->選擇動態鏈接庫(DLL)->完成。在創建的項目中的源文件可以看到有一個已經創建好的dllmain.cpp,這個就DLL入口點的代碼了。
1 // dllmain.cpp : 定義 DLL 應用程序的入口點。 2 #include "stdafx.h" 3 BOOL APIENTRY DllMain( HMODULE hModule, //模塊句柄 4 DWORD ul_reason_for_call,//調用原因 5 LPVOID lpReserved //參數保留,好像沒什么用 6 ) 7 { 8 switch (ul_reason_for_call) 9 { 10 case DLL_PROCESS_ATTACH: //DLL被某個程序加載 11 case DLL_THREAD_ATTACH: //DLL被某個線程加載 12 case DLL_THREAD_DETACH: //DLL被某個線程卸載 13 case DLL_PROCESS_DETACH: //DLL被某個程序加載 14 break; 15 } 16 return TRUE; 17 }
四、如何導出(在DLL中實現的)
第一種:
步驟1:向所有需要導出的DLL函數中添加關鍵字__declspec(dllexport)。
步驟2:要在應用程序中使用導出的DLL函數,必須使用__declspec(dllexport)來聲明要導入的各個函數。
本章使用的就是這種方法
通常最好使用一個包含define語句和ifdef語句的頭文件,以便分隔導出語句和導入語句,代碼如下。
說明:該DLL庫實現的是簡單的加減乘除運算,注意實現減法的這個函數,我這里特別使用了一個抽象類來實現,我發現有些企業代碼都會有這種類似的抽象類,然后用純虛函數做接口。
1 //__declspec(dllexport)修飾符指示編譯器和鏈接器從 2 //DLL 導出函數或變量,以便其他應用程序可以使用它,在ExprotDll.h中 3 #pragma once 4 #ifdef EXPORTDLL_EXPORTS 5 #define EXPORTDLL_API __declspec(dllexport) 6 #else 7 #define EXPORTDLL_API __declspec(dllimport) 8 #endif 9 10 //導出的類 11 class EXPORTDLL_API CExportDll { 12 public: 13 // TODO: 在此添加方法 14 //用純虛函數做為接口 15 virtual int SUBTARCT(int a, int b) = 0; 16 virtual ~CExportDll() {}; 17 18 }; 19 20 //導出函數,如果是在C++環境下,則執行"{" "}"中的內容 21 #ifdef __cplusplus 22 extern "C" { 23 #endif 24 EXPORTDLL_API CExportDll* Create(); 25 EXPORTDLL_API void Destroy(CExportDll* ex); 26 #ifdef __cplusplus 27 } 28 #endif 29 30 //也可用下列方法定義導出函數 31 extern "C" EXPORTDLL_API int nExportDll; 32 extern "C" EXPORTDLL_API int ADD(int a, int b); 33 extern "C" EXPORTDLL_API int MULTIPLY(int a, int b); 34 extern "C" EXPORTDLL_API float DIVIDE(float a, float b);
第二種:
創建模塊定義文件.def以列出導出的DLL函數:
五、特別調用
需要注意以下一些情況:
1.如果使用了Win32 API,則應該使用關鍵字__stdcall修飾函數
2.如果將C++生成的DLL供標准C語言使用,輸出文件用extern "C"來修飾,設置導出函數時采用.def文件形式,而不是__declspec(dllexport)
六、編寫DLL
創建DLL工程並且定義了相應的導出函數、變量或類之后,接下來就是實現功能了。本章中因為CExportDll是一個抽象類,所以需要再創建一個它的子類(CExportDllChild.h中),實現相應的接口功能(EXportDll.cpp中),同時其他函數也在(EXportDll.cpp)中實現功能。
1 //ExportDllChild.h 2 #pragma once 3 #include "ExportDll.h" 4 5 class CExportDllChild :public CExportDll 6 { 7 virtual int SUBTARCT(int a, int b); 8 virtual ~CExportDllChild() {}; 9 };
1 // ExportDll.cpp : 定義 DLL 應用程序的導出函數。 2 3 #include "stdafx.h" 4 #include "ExportDllChild.h" 5 6 //這是一個導出變量的一個示例 7 EXPORTDLL_API int nExportDll = 0; 8 9 //這是導出函數的幾個示例 10 //當前使用的是C++編譯出來的 11 12 //構造對象 13 EXPORTDLL_API CExportDll* Create() 14 { 15 return new CExportDllChild; 16 } 17 //析構對象 18 EXPORTDLL_API void Destroy(CExportDll* ex) 19 { 20 delete ex; 21 } 22 //加 23 EXPORTDLL_API int ADD(int a, int b) 24 { 25 return a + b; 26 } 27 //減 28 int CExportDllChild::SUBTARCT(int a, int b) 29 { 30 return a - b; 31 } 32 //乘 33 EXPORTDLL_API int MULTIPLY(int a, int b) 34 { 35 return a * b; 36 } 37 //除 38 EXPORTDLL_API float DIVIDE(float a, float b) 39 { 40 if (b != 0) { 41 return a / b; 42 } 43 else 44 return -1; 45 }
創建編寫好后,進行生成,可以在項目Debugp目錄下看到生成的動態庫.dll和靜態庫.lib
七、調用DLL
調用DLL有兩種方法,一種是顯式鏈接方式,另一種是隱式鏈接方式。
在調用前,先創建一個Win32控制台應用程序或者MFC,這里創建的是一個簡單的Win32程序(LoadDll)
1.顯式鏈接:
在此項目中,需要將CExportDllChild.h放在LoadDll目錄下。然后在cpp中添加以下代碼
1 // LoadDll.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。 2 // 3 4 #include "pch.h" 5 #include <Windows.h> 6 #include <iostream> 7 #include "ExportDll.h" 8 9 /*-------------動態鏈接-------------*/ 10 int main() 11 { 12 HMODULE hModule; //模塊句柄 13 //定義函數指針 14 typedef int(*FUNC1)(int a, int b); 15 typedef CExportDll* (*CreateFun2)(); 16 typedef void(*DestroyFun2)(CExportDll* ex); 17 typedef int(*FUNC3)(int a, int b); 18 typedef float(*FUNC4)(float a, float b); 19 //調用DLL,先找當前文件夾,如果沒有,就會去system32下查找 20 hModule = ::LoadLibrary(L"ExportDll.dll"); 21 22 if (hModule == NULL){ 23 MessageBox(NULL, L"DLL加載失敗", L"Mark", MB_OK); 24 } 25 //獲取相應DLL函數的入口地址 26 FUNC1 add = (FUNC1)::GetProcAddress(hModule, "ADD"); 27 CreateFun2 create = (CreateFun2)::GetProcAddress(hModule, "Create"); 28 DestroyFun2 destroy = (DestroyFun2)::GetProcAddress(hModule, "Destroy"); 29 FUNC3 multiply = (FUNC3)::GetProcAddress(hModule, "MULTIPLY"); 30 FUNC4 divide = (FUNC4)::GetProcAddress(hModule, "DIVIDE"); 31 //加 32 if (add != NULL){ 33 std::cout << "a+b="<< add(10, 5) << std::endl; 34 } 35 //減 36 if (create&&destroy) 37 { 38 CExportDll* p = create(); 39 std::cout << "a-b=" << p->SUBTARCT(10, 5) << std::endl; 40 destroy(p); 41 p = NULL; 42 } 43 //乘 44 if (multiply != NULL){ 45 std::cout << "a*b=" << multiply(10, 5) << std::endl; 46 } 47 //除 48 if (divide != NULL) 49 { 50 std::cout << "a/b=" << divide(10, 5) << std::endl; 51 } 52 FreeLibrary(hModule); //釋放句柄 53 return 0; 54 }
調用結果:
2.隱式調用
在程序開始執行時就將DLL文件加載到應用程序中。隱式調用沒用過,不做表述了。