VS(visual studio) C++ 封裝dll,以及其隱式呼叫與顯式呼叫(靜態動態)

风陵南發表於2024-09-20

DLL介紹

DLL(動態連結庫,Dynamic Link Library)是一種可執行檔案,它包含可以在其他程式中呼叫的函式和資料。他是Windows作業系統中的一個重要概念,用於程式碼共享和模組化。

特點

  • 程式碼共享:多個程式可以同時使用同一個DLL檔案,而不需要將其程式碼編譯到每個程式中。這樣可以節省磁碟空間和記憶體,並且可以簡化程式的更新和維護。
  • 執行時連結:與靜態連結庫(.lib檔案)不同,DLL不是在編譯時連結到程式中的,而是在程式執行時連結。這意味著,如果更新了DLL,使用該DLL的程式可以在不重新編譯的情況下直接使用新版本。
  • 多語言支援:DLL可以由不同的語言編寫,例如C,C++,Delphi等,只要它們遵循一定的呼叫約定。
  • 可擴充性:應用程式可以透過載入和解除安裝DLL來動態地增加或減少功能。
  • 資源共享:DLL在記憶體中只有一個例項,所有使用它的應用程式都共享這個例項,從而節約了資源。

生成DLL

新建dll專案

新建專案-選擇“win32控制檯應用程式”

接著在彈出框中選擇dll和空專案

完成後得到空專案的目錄結構,建立DLL1.hDLL1.cpp兩個檔案

書寫程式碼

  • DLL1.cpp中實現兩個數求和與求差的介面:
int sum(int a, int b){
	return a + b;
}
int sub(int a, int b){
	return a - b;
}
  • DLL1.h中宣告函式:
int sum(int a, int b);
int sub(int a, int b);
  • 為了能在dll中使用,還需要給函式加上字首:extern "C" __declspec(dllexport)

    • 其中_declspec(dllexport)的意思是指定需要匯出到dll的目標(用於生成dll)

    • extern "C"表示將C語言程式匯出為DLL

extern "C" __declspec(dllexport) int sum(int a, int b);
extern "C" __declspec(dllexport) int sub(int a, int b);

也可以使用宏定義,使程式碼更具可讀性

#define SumAndSub_API __declspec(dllexport)

extern "C" SumAndSub_API int sum(int a, int b);
extern "C" SumAndSub_API int sub(int a, int b);

生成DLL

.h.cpp中新增程式碼之後,右擊專案選擇“生成”


生成成功後,在專案Debug資料夾下即可找到生成的dll檔案

.h所在目錄也需要記錄一下

到這DLL的封裝算是完成了

呼叫DLL

隱式呼叫

首先需要重新建立一個空專案來呼叫測試:

建立完成之後,還需引入三個檔案即前面生成的DLL1.dllDLL1.lib以及專案DLL1.h檔案

其中DLL1.dll需要放在當前專案的Debug目錄下,其他兩個在屬性中配置路徑即可(DLL1.lib還需新增依賴項)

右擊專案-選擇屬性

選擇VC++目錄

其中“包含目錄” 新增.h檔案所在資料夾
“庫目錄”新增.lib所在目錄

新增完成後如圖:

然後在連結器-輸入選項中新增依賴項

完成後應用屬性儲存,然後將DLL檔案複製到當前專案的Debug目錄下

下面新建一個test.cpp來測試下是否能夠成功呼叫到DLL

#include "iostream"
#include "DLL1.h"
using namespace std;

int main(){
	cout << sum(2, 5) << endl;
	cout << sub(5, 2) << endl;
	system("pause");
	return 0;
}

成功呼叫到DLL

顯式呼叫

顯式呼叫中又包含靜態呼叫動態呼叫

靜態顯式呼叫

  • 靜態呼叫:.lib檔案包含了函式程式碼本身,在編譯時直接將程式碼加入程式當中,稱為靜態連結庫(static link library)。靜態呼叫使用靜態連結庫,連結器從靜態連結庫LIB獲取所有被引用函式,並將庫同程式碼一起放到可執行檔案中。

先建立一個新的空專案,建立完成後新增一個.cpp原始檔並寫入簡單的主函式

int main(){

}

右擊專案選擇-生成(生成Debug目錄)

DLL1.dll放到當前專案的Debug目錄下

建立.cpp原始檔透過以下程式碼進行靜態呼叫

#include "iostream"
using namespace std;
#pragma comment(lib, "D:\\Code\\C++\\dll\\DLL1\\Debug\\DLL1.lib")
// dll中封裝的函式
extern "C" __declspec(dllimport) int sum(int, int);
extern "C" __declspec(dllimport) int sub(int, int);

int main(){
	cout << sum(2, 5) << endl;
	cout << sub(5, 2) << endl;
	system("pause");
	return 0;
}

其中#pragma comment(lib, "D:\\Code\\C++\\dll\\DLL1\\Debug\\DLL1.lib")
表示連結DLL1.lib這個庫,與在專案屬性中的“VC++目錄”中的“庫目錄”新增目錄以及連結器-輸入新增依賴性操作是等價的(一個顯式一個隱式的區別)。

動態顯式呼叫

  • 動態呼叫:.lib檔案包含了函式所在的DLL檔案和檔案中函式位置的資訊(入口),程式碼由執行時載入在程序空間中的DLL提供,稱為動態連結庫(dynamic link library)。動態呼叫使用動態連結庫,可執行模組(.dll檔案或.exe檔案)本身不包含它呼叫的DLL函式的程式碼,僅包含在執行時定位DLL函式的程式碼所需的資訊,在程式執行時能夠找到並連結到正確的DLL檔案和函式。(這些資訊通常包括函式名、引數型別等,足以讓作業系統在執行時解析並呼叫正確的函式。)

同樣建立專案後先生成Debug目錄

生成之後將DLL1.dll放入Debug目錄中

透過如下方式進行動態呼叫

  • 首先透過LoadLibrary()函式來載入指定的dll檔案,載入到程式的記憶體中(DLL沒有自己的記憶體)
  • GetProcAddress()函式檢索指定dll檔案輸出庫函式地址,透過函式指標typedef int(*func)(int a, int b);來裝載函式並使用。
  • FreeLibrary()釋放dll所佔的空間。
#include "iostream"
#include "windows.h"
using namespace std;

typedef int(*func)(int a, int b);

int main(){
	// 動態載入dll
	HMODULE hModule = LoadLibrary("DLL1.dll");
	if (!hModule){
		cout << "Error!" << endl;
	}
	// 裝載函式
	func sum = func(GetProcAddress(hModule, "sum"));
	func sub = func(GetProcAddress(hModule, "sub"));

	if (sum != NULL){
		cout << sum(5, 2) << endl;
	}
	if (sub != NULL){
		cout << sub(5, 2) << endl;
	}

	// 釋放
	FreeLibrary(hModule);
	system("pause");
	return 0;
}

載入dll的LoadLibrary()函式同樣可以使用絕對路徑,就無需將dll檔案放到Debug目錄下了

	// 動態載入dll
	HMODULE hModule = LoadLibrary("D:\\Code\\C++\\dll\\DLL1\\Debug\\DLL1.dll");

注意:動態呼叫方式通常是隻有.dll檔案,而缺少.h.lib檔案時使用,當三個檔案都齊全時,應採用更加簡單方便的隱式呼叫

相關文章