1. 程式人生 > >Visual Studio 2013中.dll檔案的顯式呼叫方法

Visual Studio 2013中.dll檔案的顯式呼叫方法

為什麼需要dll

     程式碼複用是提高軟體開發效率的重要途徑。一般而言,只要某部分程式碼具有通用性,就可將它構造成相對獨立的功能模組並在之後的專案中重複使用。比較常見的例子是各種應用程式框架, 如ATL、MFC等,它們都以原始碼的形式釋出。由於這種複用是“原始碼級別”的,原始碼完全暴露給了程式設計師,因而稱之為“白盒複用”。“白盒複用”的缺點 比較多,總結起來有4點。
1.暴露了原始碼;
2.容易與程式設計師的“普通”程式碼發生命名衝突;
3.多份拷貝,造成儲存浪費;
4.更新功能模組比較困難。
    以上4點概括起來就是“暴露的原始碼”,造成“程式碼嚴重耦合”。為了彌補這些不足,就提出了“二進位制級別”的程式碼複用。使用二進位制級別的程式碼複用一定程度上隱藏了原始碼,對於緩解程式碼耦合現象起到了一定的作用。這樣的複用被稱為“黑盒複用”。
在Windows作業系統中有兩種可執行檔案,其後綴名分別為.exe和.dll。它們的區別在於,.exe檔案可被獨立的裝載於記憶體中執行;.dll檔案卻不能,它只能被其它程序呼叫。然而無論什麼格式,它們都是二進位制檔案。上面說到的“二進位制級別”的程式碼複用,可以使用.dll來實現。
與白盒複用相 比,.dll很大程度上彌補了上述4大缺陷。.dll是二進位制檔案,因此隱藏了原始碼;如果採用“顯式呼叫”(後邊將會提到),一般不會發生命名衝突;由 於.dll是動態連結到應用程式中去的,它並不會在連結生成程式時被原原本本拷貝進去;.dll檔案相對獨立的存在,因此更新功能模組是可行的。

C/C++有兩種庫:分別為ddl和lib,其中:
1、包含了函式所在的DLL檔案和檔案中函式位置的資訊(入口),程式碼由執行時載入在程序空間中的DLL提供,稱為動態連結庫dynamic link library。
2、包含函式程式碼本身,在編譯時直接將程式碼加入程式當中,稱為靜態連結庫static link library。
動態連結庫 (DLL) 是作為函式和資源的共享庫的可執行檔案。 動態連結可使執行檔案呼叫函式或使用儲存在單獨檔案中的資源。 可從使用這些函式和資源的可執行檔案中對其分別進行編譯和部署。 作業系統可在已載入可執行檔案時或在執行時按需將 DLL 載入到可執行的記憶體空間中。 DLL 還可以在可執行檔案之間輕鬆共享函式和資源。 多個應用程式可同時訪問記憶體中單個 DLL 副本的內容。
靜態連結會將 .lib

檔案中所有物件程式碼複製到可執行檔案中。 動態連結僅包括在執行時用於查詢和載入含有資料項或函式的 DLL 所需的資訊。 在建立 DLL 時,還會建立一個包含此資訊的 .lib 檔案。 生成呼叫 DLL 的可執行檔案時,連結器會使用 .lib 檔案中的匯出符號來為載入程式儲存此資訊。 當載入程式載入 DLL 時,該 DLL 會對映到你的可執行檔案的記憶體空間中。 將呼叫 DLL 中的特殊函式 DllMain 來執行 DLL 要求的任何初始化。
使用動態連結代替靜態連結有若干優點。 當使用 DLL 時,可以節省記憶體空間,並減少交換操作。 當多個應用程式可以使用 DLL 的單個副本時,可以節省磁碟空間和下載頻寬。 DLL 可單獨部署和更新,這可以使你在無需重新生成和釋出所有程式碼的情況下,提供售後支援和軟體更新。 DLL 是一種提供特定區域資源的簡便方法,可以支援多語言程式,並簡化建立國際版本應用程式的過程。

相應的,c/c++共有兩種連結方式:隱式呼叫和顯示呼叫。

隱式呼叫:即通過LIB和標頭檔案。該方法需要DLL工程經編譯後產生的LIB檔案,此檔案包含DLL允許應用程式呼叫的所有函式列表。LINKER檢測應用程式呼叫了LIB檔案中的某個函式時,就會在應用程式EXE檔案中加入相關資訊。該應用程式執行時,系統會檢視這個檔案的DLL資訊,後將DLL檔案對映到地址空間。因為LIB檔案並沒有包含函式的具體實現,因此LIB檔案相較於DLL檔案和STATIC-LIB檔案(靜態連結庫)會比較小。
系統尋找DLL檔案的路徑先後順序如下:1. EXE檔案所在目錄 2. 當前程式工作目錄 3. 系統目錄 4. Windows目錄 4. 環境變數中所有目錄
VC中載入DLL的LIB檔案的方法如下:1. LIB檔案直接加入到工程檔案列表中 2. 設定Project Settings載入LIB檔案 3. 預編譯指令 #pragma comment (lib, "*.lib")
顯示呼叫:當只提供DLL檔案而沒有其相關的LIB檔案和標頭檔案的情況下,只能使用顯示呼叫。顯示呼叫能夠更加有效的使用記憶體,編寫大型程式時往往使用顯示呼叫。該方法使用Load Library或者AfxLoadLibrary對DLL進行動態載入;使用GetProcessAdress獲得所呼叫函式的指標;使用完畢後以Free Library或者AfxFreeLibrary將DLL從地址空間中解除安裝。

使用lib需注意兩個檔案
1.h標頭檔案,包含lib中說明輸出的類或符號原型或資料結構。應用程式呼叫lib時,需要將該檔案包含入應用程式的原始檔中。
2.LIB檔案。
使用dll需注意三個檔案:
1.h標頭檔案,包含dll中說明輸出的類或符號原型或資料結構的.h檔案。應用程式呼叫dll時,需要將該檔案包含入應用程式的原始檔中。
2.LIB檔案,是dll在編譯、連結成功之後生成的檔案,作用是當其他應用程式呼叫dll時,需要將該檔案引入應用程式,否則產生錯誤(如果不想用lib檔案或者沒有lib檔案,可以用WIN32 API函式LoadLibrary、GetProcAddress裝載)。
3.dll檔案,真正的可執行檔案,開發成功後的應用程式在釋出時,只需要有.exe檔案和.dll檔案,並不需要.lib檔案和.h標頭檔案。

在使用時,靜態連結庫只要把.h和.lib檔案加入到工程資料夾中即可。而動態連結庫要把.h、.lib和.dll檔案加入到工程中。
本文主要總結呼叫動態連結庫的方法:C++呼叫DLL的方式有兩種,一種是靜態,另外一種是動態。

僅有.dll檔案時候的使用方法
  在沒有.h和.lib檔案時,需要函式指標和WIN32 API函式LoadLibrary、GetProcAddress裝載,只需要.dll檔案即可(將.dll檔案置入工程目錄中)。具體思路:
1.先編寫一個DLL,我這裡是直接在CPP裡編寫了函式宣告和定義,沒有單獨的標頭檔案,因為很多情況下的DLL都是沒有和lib和標頭檔案一起的。
2.然後另外新建一個專案,來呼叫DLL,方法是:
2.1.宣告標頭檔案<windows.h>,說明我想用windows32方法來載入和解除安裝DLL
2.2然後用typedef定義一個指標函式型別.typedef void(*fun) //這個指標型別,要和你呼叫的函式型別和引數保持一致,記住,是指標引數就是(int *,int)
2.3.定一個控制代碼例項,用來取DLL的例項地址。HINSTANCE hdll;格式為hdll=LoadLibrary(“DLL地址”);這裡字串型別是LPSTR,當是unicode字符集的時候會不行,因此要在配置-屬性-常規裡面把預設字符集“unicode”改成支援多字元擴充套件即可。
2.4.取的地址要判斷,返回的控制代碼是否為空,如果為無效控制代碼,那麼要釋放載入DLL所佔用的記憶體:FreeLibrary(hdll);
2.5.然後定義一個函式指標,用來獲取你要用的函式地址。先是定一個函式指標 fun FUN;然後通過GetProcAdress來獲取函式的地址,這個函式引數是DLL的控制代碼和你要呼叫的函式名:比如:FUN=(fun)GetProcAdress(hdll,"sum");這裡也要判斷要函式指標是否為空,如果沒取到要求的函式,那麼要釋放控制代碼:FreeLibrary(hdll);
2.6.然後通過函式指標來呼叫函式。FUN(int *p,int count);這裡不能用函式名來使用函式,因為這個DLL本身不是當前CPP的一部分,而是通過windows去呼叫.沒有在這個工程裡宣告或者定義,而是暴露出一個頭,要指標獲取他的地址,通過指標來呼叫.
2.7.最後呼叫結束後,就釋放控制代碼FreeLibrary(hdll);

(1)LoadLibrary 函式

function LoadLibrary(LibFileName : PChar): Thandle;
[功能]:載入由引數 LibFileName 指定的 DLL 檔案。
[說明]:引數 LibFileName 指定了要裝載的 DLL 檔名,如果 LibFileName 沒有包含一個路徑,系統將按照:當前目錄、Windows 目錄、Windows 系統目錄、包含當前任務可執行檔案的目錄、列在 PATH 環境變數中的目錄等順序查詢檔案。
如果函式操作成功,將返回裝載 DLL 庫模組的例項控制代碼,否則,將返回一個錯誤程式碼,錯誤程式碼的定義如下表所示。

錯誤程式碼
  含義
  0
  系統記憶體不夠,可執行檔案被破壞或呼叫非法
  2
  檔案沒有被發現
  3
  路徑沒有被發現
  5
  企圖動態連結一個任務錯誤或者有一個共享或網路保護錯誤
  6
  庫需要為每個任務建立分離的資料段  
  8
  沒有足夠的記憶體啟動應用程式  
  10
  Windows  版本不正確  
  11
  可執行檔案非法或不是Windows  應用程式,或在.  EXE映像中有錯誤  
  12
  應用程式為一個不同的作業系統設計(如  OS/2  
  13
  應用程式為  MS  DOS   4. 0  設計  
  14
  可執行檔案的型別不知道  
  15
  試圖裝載一個真實模式應用程式(為早期Windows  版本設計)
  16
  試圖裝載包含可寫的多個數據段的可執行檔案的第二個例項  
  19
  試圖裝載一個壓縮的可執行檔案(檔案必須被解壓後才能被裝載)  
  20
  DLL  檔案非法
  21
  應用程式需要  32  位擴充套件
假如在應用程式中用 LoadLibrary 函式裝入某一個 DLL 前, 其他應用程式已把該 DLL 裝入記憶體中了,則系統將不再裝入該 DLL 的另一個例項,而是使該 DLL 的“引用計數”加 1 。

(2)GetProcAddress 函式

function GetProcAddress(Module:Thandle; ProcName:PChar): TfarProc;
[功能]:返回引數 Module 指定的模組中,由引數 ProcName 指定的過程或函式的入口地址。
[說明]:引數 Module 包含被呼叫函式的 DLL 控制代碼,這個值由 LoadLibrary 返回, ProcName
是指向含有函式名的以 nil 結尾的字串指標,或者可以是函式的次序值,但大多數情況下,用函式名是一種更穩妥的選擇。如果該函式執行成功,則返回 DLL 中由引數 ProcName 指定的過程或函式的入口地址,否則返回 nil 。

(3)FreeLibrary 函式

procedure  FreeLibrary(Module: Thandle);
[說明]:將由引數 Module 指定的 DLL 檔案從記憶體中解除安裝 1 次。
[說明]:Module 為 DLL 庫的控制代碼。這個值由 LoadLibrary 返回。由於 DLL 在記憶體中只裝載一次,因此呼叫 FreeLibrary 首先使 DLL 的引用計數減 1,如果計數減為 0 則解除安裝該 DLL。
[注意]:每呼叫一次 LoadLibrary 函式就應呼叫一次 FreeLibrary 函式,以保證不會有多餘的庫模組在應用程式結束後仍留在記憶體中,否則導致記憶體洩漏。

例項:

#include <iostream>
#include <windows.h>
using namespace std;

typedef int(*FUNA)(int&, int&);//定義指向和DLL中相同的函式原型指標

int main() {
	const char* dllName = "add.dll";
	const char* funName = "add";
	int x(10), y(10);
	HMODULE hDLL = LoadLibrary(dllName);//載入dll
	if (hDLL != NULL)
	{
		FUNA fp = FUNA(GetProcAddress(hDLL, "add"));//獲取匯入到應用程式中的函式指標,根據方法名取得

		if (fp != NULL)
		{
			cout << "Input 2 Numbers:";
			cin >> x >> y;
			fp(x, y);
		}
		else
		{
			cout << "Cannot Find Function : " << funName << endl;
		}
		FreeLibrary(hDLL);
	}
	else
	{
		cout << "Cannot Find dll : " << dllName << endl;
	}
	return 1;
}

    在除錯過程中可能出現問題:const char *與LPCWSTR 不相容:在VC 6.0中編譯成功的專案在VS2005 vs2005、vs2008、vs2010中常會出現型別錯誤。如使用MessageBox(hwnd,"TEST",NULL,0)就會報錯,如果使用強制轉換(LPCWSTR)"TEST",雖然能夠通過,但是會出現亂碼。

    出錯原因:程式在UNICODE(寬位元組)字符集下執行,如果呼叫了 MessageBox ,實際上呼叫的是 MessageBoxW 函式;如果程式在 ANSI 字符集執行,呼叫 MessageBox ,就相當於呼叫 MessageBoxA;其中 MessageBoxW 支援 UNICODE;MessageBoxA 支援ANSI;UNICODE與ANSI 有什麼區別:UNICODE版的字元比ANSI 的記憶體佔用大,比如:Win32程式中出現的標準定義 char 佔一個位元組,而 char 的UNICODE版被定義成這樣:typedef unsigned short wchar_t ;佔2個位元組。所以有字元做引數的函式相應也用兩個版本了。

    解決上面問題可用的方法是:專案選單——專案屬性(最後一個)——配置屬性——常規——專案預設值——字符集,將使用Unicode字符集改為“未設定”即可。

另外發現,該種方法只適應於C方式生成的dll檔案,並不適應於C++方式生成的dll檔案。

參考:

http://blog.csdn.net/waterbinbin/article/details/52625508

https://msdn.microsoft.com/zh-cn/library/ms235636

http://blog.csdn.net/cd520yy/article/details/49455127

http://www.jb51.net/article/36447.htm

http://www.cnblogs.com/lhbssc/archive/2012/02/08/2342853.html

http://blog.csdn.net/xl_lbj/article/details/12434175 (5)

http://blog.csdn.net/u010111422/article/details/38681289