1. 程式人生 > >windows程式設計之動態連結庫的使用

windows程式設計之動態連結庫的使用

當我們進行程式的編寫的時候,經常需要將一些資源進行重複利用等操作。於是,微軟就提出了DLL(動態連結庫),它可以很好的將資源進行重複利用。

當一個程序載入一個DLL的時候呢,一般都會執行DllMain這個動態連結庫的入口點(有時候不會,後面再講),來看下它的宣告:

BOOL WINAPI DllMain(  
  HINSTANCE hinstDLL,  // dll模組的記憶體基址
  DWORD fdwReason,     // 載入本dll的原因
  LPVOID lpvReserved   // 系統保留
);
其中,fdwReason有四個原因,第一個是DLL_PROCESS_ATTACH,表示程序載入本dll時。第二個是DLL_THREAD_ATTACH,表示執行緒載入本dll時。第三個是DLL_THREAD_DETACH,程序解除安裝本dll時,第四個是DLL_PROCESS_DETACH,執行緒解除安裝本dll時。

因為它是被剛載入或解除安裝的時候呼叫的,因此我們可以在該函式內寫一些初始化的工作和釋放資源的工作。

在DLL中,我們編寫完要共享的程式碼後,我們需要對這些程式碼進行匯出,變成匯出函式或類等才能被我們共享使用。下面來介紹兩種DLL匯出函式的兩種方式。

第一種匯出方式是使用def檔案,裡邊的內容格式如下:

LIBRARY 專案名
EXPORTS
匯出函式名1
匯出函式名2
寫完之後,將字尾名改為.def格式,將其新增到工程裡邊,然後在VS裡邊右鍵屬性----配置資訊/連結器/輸入/模組定義檔案這一欄新增該檔案即可,然後我們生成解決方案,我們的函式就被匯出了。我們可以使用Dependency Walker工具來檢視是否已經被匯出了。

第二種匯出的方式是使用__declspec(export)的方式。我們在要匯出的函式名前新增這樣一句話即可。如果你使用的是VS的C++編譯器,最好在該句話前在新增一句extern "C"來宣告是以C語言的標準來進行編譯的,為什們呢?因為VS的C++編譯器會自動的將我們的函式名以微軟的方式新增一些資訊。以下是一個宣告匯出函式的例子:

extern "C" __declspec(dllexport) DWORD TestFun(int a, int b)
{
	return a + b;
}

知道了怎樣在dll工程中匯出一個函式後,我們在來看下如何在別的工程中使用我們匯出的函式。呼叫匯出函式,也有兩種方式,一種是隱式呼叫,一種是顯示呼叫。

先來看下隱式呼叫。在我們的dll工程中,生成解決方案後,會生成一個dll檔案和一個.lib檔案。其中.dll檔案裡邊包含了程式碼的實現等資訊。而.lib檔案則包含了函式的記憶體地址資訊,用於指明我們的函式在哪個記憶體地址。因此,我們可以連結這個lib檔案來使用我們的函式。在此之前呢,我們還要宣告一下我們要使用哪個函式,然後再來連結,宣告時要在函式前加一句__declspec(dllimport)表示匯入一個函式,下面是一個示例:

extern "C" __declspec(dllimport) int TestFun(int a, int b);<span style="white-space:pre">	</span>//宣告一個匯入函式
#pragma comment(lib,"testLib.lib")<span style="white-space:pre">	</span>//連結我們的lib檔案
顯式呼叫。假設我們沒有.lib檔案,那麼我們應該使用顯示呼叫。顯示呼叫就是呼叫LoadLibrary函式來動態的載入一個dll檔案,然後用GetProcAddress來確定匯出函式的地址。它們的宣告如下:
HMODULE LoadLibrary(  
  LPCTSTR lpFileName   // 模組名
);

FARPROC GetProcAddress(  
  HMODULE hModule,    // 上一個函式的返回值
  LPCSTR lpProcName   // 函式名
);
下面是一個顯示呼叫的示例程式碼:
#include <stdio.h>
#include <windows.h>
//定義一個函式指標,返回值和引數要與匯出的函式相同
typedef int(*lpTestFun)(int,int);

int _tmain(int argc, _TCHAR* argv[])
{

	HMODULE hDll;
	lpTestFun testFun;
	//載入一個dll
	hDll = LoadLibrary("testLib.dll");
	if (hDll)
	{
		//獲取要匯出函式的地址
		testFun = (lpTestFun)GetProcAddress(hDll, "testFun");
		if (testFun)
		{
			printf("testFun result:%d\n", testFun(3, 2));
		}
	}
	return 0;
}

在文章開頭講過我們可能不需要DllMain這個函式入口點,當我們動態的載入一個函式時,除了呼叫LoadLibrary這個函式外,我們還可以呼叫它的加強版LoadLibraryEx,其宣告如下:

HMODULE LoadLibraryEx(  
  LPCTSTR lpFileName,  	  // 檔名
  HANDLE hFile,           // 系統保留,必須為NULL
  DWORD dwFlags           // 一些標誌位
);
該函式的第三個引數裡邊,有一個DONT_RESOLVE_DLL_REFERENCES這個標誌位,它表示當使用這個標誌位的時候呢,載入一個Dll時,程式不會執行我們的DllMain這個入口點函式。為什麼要使用這個函式呢,假如我們在網上下載了一個dll,它可能在DllMain裡邊寫了一些邪惡的程式碼,如果我們呼叫LoadLibrary去載入時,就可能引發一些嚴重的後果。這時加強版就起到了作用。