1. 程式人生 > >Windows 動態鏈接庫編程

Windows 動態鏈接庫編程

div blog sys 系統目錄 庫項目 編譯器 搜索 其他 例如

Windows 動態鏈接庫編程

1、介紹

Windows操作系統是應用最關的操作系統,因此動態鏈接庫也為程序員所熟悉,即使對於普通的使用者來說,很多時候也會碰到.dll結尾的文件,這就是動態鏈接庫文件。Windows下的動態鏈接庫可以通過參考頭文件和.lib庫文件進行編譯,從而使得動態鏈接庫隱式地被使用;也可以使用LoadLibrary、GetProcAddress等函數來顯式調用動態鏈接庫。

2、語法、導入導出

在Windows編程中,對於要使用或被使用的函數或者變量,需要使用 __declspec 關鍵字來聲明,以告訴編譯器該變量或函數不是普通的變量或函數,而是一個動態鏈接庫的接口屬性。

如果定義一個要被其他代碼使用的函數,可以寫成:
__declspec( dllexport ) int add(int a, int b);

如果在該代碼中,打算使用另外一個程序中的變量,則可以寫成:
__declspec( dllimport ) char *name;

動態鏈接庫通常包含一系列供其他程序使用函數,因此 declspec( dllexport ) 語法形式最為常用。如果動態庫需要其他程序中的定義的全局變量,則需要在其他程序中使用導出該變量,在動態鏈接庫中則需要使用 extern declspec( dllexport ) 將該變量聲明為外部變量以便使用。

3、鏈接方式

可以以下列兩種方式之一鏈接到(或加載)DLL:

隱式鏈接
顯式鏈接

隱式鏈接有時稱為靜態加載或加載時動態鏈接。顯式鏈接有時稱為動態加載或運行時動態鏈接。在隱式鏈接下,使用 DLL 的可執行文件鏈接到該 DLL 的創建者所提供的導入庫(.lib 文件)。使用 DLL 的可執行文件加載時,操作系統加載此 DLL。客戶端可執行文件調用 DLL 的導出函數,就好像這些函數包含在可執行文件內一樣。

在顯式鏈接下,使用 DLL 的可執行文件必須進行函數調用以顯式加載和卸載該 DLL,並訪問該 DLL 的導出函數。客戶端可執行文件必須通過函數指針調用導出函數。可執行文件對兩種鏈接方法可以使用同一個 DLL。另外,由於一個可執行文件可隱式鏈接到某個 DLL,而另一個可顯式附加到此 DLL,故這些機制不是互斥的。

4、隱式鏈接

隱式鏈接動態鏈接庫比較簡單,不予詳述。

5、顯式鏈接API函數

顯式鏈接主要涉及到3個API函數( LoadLibrary , GetProcAddress 和 FreeLibrary ),要使用這些函數包含windows.h頭文件即可。

(1)HINSTANCE LoadLibrary(LPCSTR lpLibFileName);

該函數用來加載指定動態庫文件,並且返回句柄。

參數lpLibFileName為動態鏈接庫的名稱。Windows 首先搜索“已知 DLL”,如 Kernel32.dll 和 User32.dll。然後按下列順序搜索 DLL:

1、當前進程的可執行模塊所在的目錄。
2、當前目錄。
3、Windows 系統目錄。GetSystemDirectory 函數檢索此目錄的路徑。
4、Windows 目錄。GetWindowsDirectory 函數檢索此目錄的路徑。
5、PATH 環境變量中列出的目錄。

(2)FARPROC GetProcAddress (HMODULE hModule, LPCSTR lpProcName);

函數GetProcAddress 用來獲取 DLL 導出函數的地址。返回由lpProcName指定的變量或函數指針。

參數hModule為已經加載的動態庫文件的句柄。

參數lpProcName為要調用的變量或函數名稱。

(3)BOOL FreeLibrary(HMODULE hModule);
從內存中釋放hModule所代表的動態鏈接庫。

(4)如果發生錯誤,可以調用GetLastError()函數或去錯誤代碼。

6、顯示鏈接舉例

(1)動態庫文件代碼:dll_demo.c

#include <stdio.h>

__declspec( dllexport ) int add(int a, int b)
{
printf("calling add\n");
return a + b;
}

該文件中的add()函數計算兩個整數之和,並且返回之前打印提示字符串。函數使用 __declspec( dllexport ) 語法來說明函數add(int a, int b)要被導出。

(2)客戶端事例代碼:main.c

#include <stdio.h>
#include <windows.h>

int main (int argc, char *argv[])
{
int a = 10, b = 20;
int c = 0;
HINSTANCE hInstLibrary = NULL;
int (*add)();

printf ("Load DLL file\n");
if ((hInstLibrary = LoadLibrary(L"dll_demo.dll")) == NULL)
{
printf ("***LoadLibrary ERROR: %d.\n", GetLastError());
return 1;
}
if((add = (int (*)())GetProcAddress(hInstLibrary, "add")) == NULL) {
printf ("***GetProcAddress ERROR: %d.\n", GetLastError());
return 1;
}

c = add(a, b);
printf("%d + %d = %d\n", a, b, c);

FreeLibrary(hInstLibrary);
return 0;
}

程序利用LoadLibrary函數加載動態鏈接dll_demo.dll,利用FreeLibrary關閉句柄,利用GetLastError()獲取錯誤代碼,利用GetProcAddress定位共享庫中的add函數,然後調用該函數執行加法計算,並打印結果。

(3)編譯與運行

編譯共享庫:

在VS.Net中創建一個動態鏈接庫項目,名稱為dll_demo,加入文件dll_demo.c,編譯後生成dll_demo.dll文件。

編譯事例程序:

在VS.Net中創建一個動態鏈接庫項目,名稱為dll_main,加入文件main.c,編譯後生成dll_main.exe可以執行文件。

運行:

將 dll_demo.dll 和 dll_main.exe 放在同一個目錄下,然後雙擊運行 dll_main.exe。

輸出:

Load DLL file
calling add
10 + 20 = 30

7、調用動態鏈接庫中的變量

也可以使用動態鏈接庫中的變量。例如,在動態鏈接庫中定義:

__declspec( dllexport ) int num = 100;

那麽可以在事例程序中這樣調用:

int *d;
d = (int *)GetProcAddress(hInstLibrary, "num");
printf("num = %d\n", *d);

Windows 動態鏈接庫編程