【編程學習】windows C++ 動態鏈接庫(DLL)的編寫和使用

動態鏈接庫,Dynamic Link Library,簡稱“DLL”,是一個預先經過編譯的二進制程序,能夠被其它程序所調用,但與可執行文件(.exe)不同,它不能獨立運行,必須由其它的程序進行調用。


靜態鏈接庫與動態鏈接庫

  • 靜態鏈接庫
    利用函數和數據編譯成的一個二進制文件,擴展名爲.Lib。當應用程序調用靜態庫進行編譯鏈接時,鏈接器會從庫中複製這些函數和數據並把它們和應用程序的其他模塊組合起來創建最終的可執行文件(.exe)。發佈產品時,只需要發佈這個可執行文件,並不需要發佈被使用的靜態庫。
    ——特點:靜態庫對函數庫的鏈接是放在編譯時期完成的;程序在運行時與函數庫再無瓜葛,移植方便;浪費空間和資源,因爲所有相關的目標文件與牽涉到的函數庫被鏈接合成一個可執行文件。

  • 動態鏈接庫
    在使用動態庫的時候,往往提供兩個文件:一個引入庫文件(.lib)和一個DLL文件(.dll)。雖然引入庫的後綴名也是“lib”,但是,動態庫的引入庫文件和靜態庫文件有着本質上的區別: 對一個DLL來說,其引入庫文件包含該DLL導出的函數和變量的符號名,而DLL文件包含該DLL實際的函數和數據。在使用動態庫的情況下,在編譯鏈接可執行文件時,只需要鏈接該DLL的引入庫文件,該DLL中的函數代碼和數據並不複製到可執行文件中,直到可執行程序運行時,纔去加載所需的DLL,將該DLL映射到進程的地址空間中,然後訪問DLL中導出的函數。這時,在發佈產品時,除了發佈可執行文件以外,同時還要發佈該程序將要調用的動態鏈接庫。
    ——特點:動態庫把對一些庫函數的鏈接載入推遲到程序運行的時期;可以實現進程之間的資源共享(因此動態庫也稱爲共享庫);將一些程序升級變得簡單。

Window與Linux執行文件格式不同,在創建動態鏈接庫的時候有一些差異,本文編寫時,主要利用VS2012在windows10系統上進行編譯。


動態鏈接庫的編寫

  1. 創建Win32控制檯程序, 選擇“DLL”和“導出符號”選項。
    在這裏插入圖片描述
    得到:
    在這裏插入圖片描述
  2. 編寫一個簡單的相加函數
// MyDLL.h
#ifdef MYDLL_EXPORTS
#define MYDLL_API __declspec(dllexport)
#else
#define MYDLL_API __declspec(dllimport)
#endif

MYDLL_API float MyAdd(float a, float b);
// MyDLL.cpp 
#include "stdafx.h"
#include "MyDLL.h"

// Add函數
MYDLL_API float MyAdd(float a, float b)
{
	float c;
	c = a + b;
	return c;
}
  1. 編譯
    Debug模式,win32平臺,生成解決方案,在Debug文件夾中有“.lib”和“.dll”文件。

動態鏈接庫的調用

動態鏈接庫的調用共有兩種方式:1)隱式調用;2) 顯示調用。

1. 隱式調用

新建win32控制檯程序”DLLtest”,將上一步中得到的MyDLL.h, MyDLL.lib, MyDLL.dll複製到新建項目源程序所在的文件夾中。此處可類比OpenCV的配置過程。

// DLLtest.cpp
#include "stdafx.h"
#include "MyDLL.h"
#pragma comment(lib, "MyDLL.lib")  //或者在依賴項中添加

#include <iostream>
using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
	float a = 8.5;
	float b = 7.5;
	float sum;
	sum = MyAdd(a, b);
	cout<<"Test out:"<<sum<<endl;
getchar();
	return 0;
}

2. 顯式調用

顯示調用是指應用程序在運行過程中可以隨時加載或卸載DLL,用法比隱式調用更加靈活,但調用方式相對較複雜:

#include "stdafx.h"
#include <Windows.h>
#include <iostream>
using namespace std;

typedef float(*Add)(float, float);

int _tmain(int argc, _TCHAR* argv[])
{
	float a = 4.5;
	float b = 5.5;
	float sum;
	Add add1;
	HINSTANCE hDLL;	
	hDLL = LoadLibrary("MyDLL.dll");   //加載動態鏈接庫
	if(hDLL==NULL){
		FreeLibrary(hDLL);
		cout<<"Library is NULL!"<<endl;
	}
	add1 = (Add)GetProcAddress(hDLL, "MyAdd"); //獲取函數"MyAdd"的地址
	if(add1==NULL){
		FreeLibrary(hDLL);
		cout<<"Pointer is NULL!"<<endl;
	}
	sum = add1(a, b);      
	cout<<"Test out:"<<sum<<endl;
	FreeLibrary(hDLL);  //卸載動態鏈接庫

	getchar();
	return 0;
}

從上面的編程就可以看出來顯示調用要複雜一些。用LoadLibrary()加載動態鏈接庫, GetProcAddress() 獲取導出函數的指針,用FreeLibrary() 卸載動態鏈接庫,並且中間要用typedef定義跟要調用的函數同類型的指針函數[2][3]。

hdll=LoadLibrary(“DLL地址”);這裏字符串類型是LPSTR,當是unicode字符集的時候會不行,因此要在項目屬性-常規裏面把默認字符集“使用unicode字符集”改成“使用多字節字符集”即可。


注意事項

1. 關鍵字 extern “C”
MYDLL_API float MyAdd(float a, float b); 換成 extern “C” MYDLL_API float MyAdd(float a, float b);, 這樣可以使C語言等其它語言進行調用, 並選擇“屬性—常規—在靜態庫中使用MFC”去掉了對MSVCR110D.DLL的依賴[4]。

2.注意DLL編譯的平臺
如果是Win32,那麼調用這個DLL應用程序也應該是Win32的平臺,如果是x64,調用程序則就是在x64的平臺下,不然會出現錯誤:fatal error LNK1120: 1 個無法解析的外部命令
有時也要注意DLL是在哪種模式下生成的。Debug模式生成,最好也在Debug下調用,對於Release也一樣,不然會出現莫名的錯誤。

3. DLL可調用的函數返回值
不要傳遞非基本類型,例如C++中的string, 會報錯[5]; 也不要返回局部變量值,例如char*, 在調用函數結束後,其指針將會被釋放,從而無法獲得返回值,最好可以當做函數的輸入參數。


參考資料

[1] GCC編譯過程與動態鏈接庫和靜態鏈接庫
[2] dll文件生成使用: 隱式和顯示的介紹和調用
[3] C++ dll調用-動態(顯式): 有顯示調用的操作過程和注意事項
[4] VS2010編寫動態鏈接庫DLL及單元測試用例,調用DLL測試正確性: Depends的使用,extern “C”
[5] c++ dll接口返回字符串

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章