動態鏈接庫和靜態鏈接庫:
動態鏈接庫一般不能直接執行,而且它們一般也不接收消息。
它們是包含許多函數的獨立文件,這些函數可以被應用程序和其他 DLL 調用以完成某些特定的工作。
一個動態鏈接庫只有在另外一個模塊調用其所包含的函數時才被啟動。
“靜態鏈接” 一般是在程序開發過程中發生的,用於把一些文件鏈接在一起創建一個 Windows 可執行文件。
這些文件包括各種各樣的對象模塊(.OBJ),運行時庫文件(.LIB),通常還有已編譯的資源文件(.RES)。
與其相反,動態鏈接則發生在程序運行時。
靜態庫:函數和數據被編譯進一個二進制文件,擴展名為(.lib)。
在使用靜態庫的情況下,在編譯鏈接可執行文件時:
鏈接器從靜態庫中復制這些函數和數據,並把它們和應用程序的其他模塊組合起來創建最終的可執行文件(.exe)。
當發布產品時,只需要發布這個可執行文件,並不需要發布被使用的靜態庫。
“動態鏈接” 是指 Windows 的鏈接過程,在這個過程中它把模塊中的函數調用與在庫模塊中的實際函數鏈接在一起。
動態庫:在使用動態庫時,往往提供兩個文件:一個導入庫(.lib,非必須) 和一個(.dll)文件。
導入庫和靜態庫本質上的區別:
靜態庫本身就包含了實際執行代碼和地址符號表等數據。
而對於導入庫而言,其實際的執行代碼位於動態庫中,導入庫只包含了地址符號表等,確保程序找到對應函數的一些基本地址信息。
動態鏈接庫的標准擴展名是(.dll)。只有擴展名為(.dll)的動態鏈接庫才能被 Windows 操作系統自動加載。
如果該文件有另外的擴展名,則程序必須明確地用 LoadLibrary() 或 LoadLibraryEx() 加載相應模塊。
編寫動態鏈接庫
我們編寫的程序都可以根據 UNICODE 標識符的定義編譯成能夠處理 UNICODE 或者 非 UNICODE 字符串的程序。
在創建一個 DLL 時,對於任何有字符或者字符串參數的函數,它都應該包括 UNICODE 和非 UNICODE 兩個版本。
VC++6.0 編譯器下:
File->New->Win32 Dynamic-Link Library->An empty DLL project || An Simple DLL project
An empty DLL project 和 An Simple DLL project 的區別是:后者有個簡單的示例代碼。
我以前者為例:
新建兩個文件:MyDLL.h,MyDLL.cpp。
// MyDLL.h
#define Import extern "C" _declspec(dllexport) Import int sum(int a, int b); Import int sub(int a, int b);
...................................................................................................................................................................................................................................................................
// MyDLL.cpp
#include"MyDLL.h" Import int sum(int a, int b) { return a+b; } Import int sub(int a, int b) { return a-b; }
最后編譯 MyDLL.cpp,如果成功則在 Debug 里可以看到 MyDLL.dll。
提示:
函數聲明前加上 "_declspec(dllexport)" 表明函數將輸出為動態鏈接庫,是必不可少的。
在相同的調用約定下,采用不同的編譯器,對函數名的修飾是不一樣的。
例如:C語言和C++語言導出的dll文件中,函數的修飾名是不一樣的。
如果要C語言風格的(.dll)文件,就要再加上 "extern C" 進行修飾,或者把源文件名的后綴改為(.c)。
如果是要C++風格的(.dll)文件,則源文件名后綴必須為(.cpp)。
調用方式:
隱式調用:
將 MyDLL.lib 和 MyDLL.h 拷貝到需要應用該 DLL 的工程的目錄下,將 MyDLL.dll 拷貝到產生的應用程序的目錄下,
並在需要應用該 DLL 中的函數的 CPP 文件開頭添加如下幾行:
#include"MyDLL.h" #pragma comment(lib,"MyDLL")
例如:
// MyDLL.cpp
#include<stdio.h> #include"MyDLL.h" #pragma comment(lib,"MyDLL") int main(void) { printf("3+6=%d\n",sum(3,6)); printf("8-6=%d\n",sub(8,6)); return 0; }
顯式調用:
1、將 MyDLL.lib 和 MyDLL.h 拷貝到需要應用該 DLL 的工程的目錄下,將 MyDLL.dll 拷貝到產生的應用程序的目錄下,
在添加 CPP 文件之前一步,需要在 Project->Setting->Link->Object/library modules 的框中增加 MyDll.lib 這個庫。
最后,在創建的 CPP 文件的開頭添加這一行:
#include"MyDLL.h"
現在就可以使用這個 DLL 文件了。
2、將 MyDLL.lib 和 MyDLL.h 拷貝到需要應用該 DLL 的工程的目錄下,將 MyDLL.dll 拷貝到產生的應用程序的目錄下,
簡單的調用 DLL 文件的 CPP 文件如下:
// MyDLL.cpp
#include<stdio.h> #include<windows.h> int main(void) { HMODULE hModule; typedef int (*pSum)(int a, int b); typedef int (*pSub)(int a, int b); pSum Sum = NULL; pSub Sub = NULL; hModule = LoadLibrary("MyDLL.dll"); Sum = (pSum)GetProcAddress(hModule,"sum"); Sub = (pSum)GetProcAddress(hModule,"sub"); printf("3+6=%d\n",Sum(3,6)); printf("8-6=%d\n",Sub(8,6)); return 0; }
介紹一下兩個函數:
LoadLibrary() 介紹:
功能:將指定模塊加載到調用進程的地址空間中。指定的模塊可能會導致加載其他模塊。
函數原型:HMODULE WINAPI LoadLibrary(
LPCTSTR lpFileName // 動態鏈接庫的名字。
);
返回值:如果函數成功, 則返回值是模塊的句柄。如果函數失敗, 返回值為 NULL。
GetProcAddress() 介紹:
功能:從指定的動態鏈接庫 (DLL) 中檢索導出函數或變量的地址。
函數原型:FARPROC WINAPI GetProcAddress(
HMODULE hModule, // 模塊的句柄。
LPCSTR lpProcName // 函數或變量的名字, 或函數的序號值。
);
返回值:如果函數成功, 則返回值是導出函數或變量的地址。如果函數失敗, 返回值為 NULL。
再來看一下這段代碼: typedef int (*pSum)(int a, int b);
我們通常見到的都是: typedef unsigned Long uLong;
其實 typedef int (*pSum)(int a, int b); 的意思也挺好理解的:
就是定義一個別名為 pSum 函數指針,指向返回值為 int 型並且含有兩個 int 型參數的函數指針。
VS2017下:
其實 VS2017 下的步驟和上面也一樣,這里介紹一下模塊定義文件創建 DLL 文件:
文件->新建->項目->DLL
// MyDLL.cpp
#include "stdafx.h" int sum(int a, int b) { return a + b; } int sub(int a, int b) { return a - b; }
解決方案->資源文件->添加->新建項->代碼->模塊定義文件
// Source.def
LIBRARY
EXPORTS
sum
sub
項目->MyDLL屬性->鏈接器->輸入->模塊定義文件:Source.def
最后:生成->MyDLL
隱式調用和顯式調用的對比:
1、隱式鏈接方式實現簡單,一開始就把dll加載進來,在需要調用的時候直接調用即可。
但是如果程序要訪問十多個 DLL 文件,如果都采用隱式鏈接方式加載他們的話,在該程序啟動時:
這些dll都需要被加載到內存中,並映射到調用進程的地址空間,這樣將加大程序的啟動時間。
而且一般來說,在程序運行過程中只是在某個條件滿足的情況下才需要訪問某個dll中的函數。
這樣如果所有dll都被加載到內存中,資源浪費是比較嚴重的。
2、顯示加載的方法則可以解決上述問題,DLL 只有在需要用到的時候才會被加載到內存中。
另外,其實采用隱式鏈接方式訪問 DLL 時,在程序啟動時也是通過調用 LoadLibrary() 加載該進程需要的動態鏈接庫的。
帶有 API 函數的 動態鏈接庫:
創建方式相同,只是得有個 DllMain() 入口函數。
// Dll.h /* #define Import extern "C" _declspec(dllexport) Import void Text(void); */ #include"Dll.h" #include<windows.h> BOOL APIENTRY DllMain(HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpRserved) { switch(ul_reason_for_call) { case DLL_PROCESS_ATTACH: Text(); break; case DLL_PROCESS_DETACH: break; case DLL_THREAD_ATTACH: break; case DLL_THREAD_DETACH: break; } return 0; } Import void Text(void) { MessageBox (NULL, TEXT ("Hello, World!"), TEXT ("HelloMsg"), MB_OKCANCEL); }
DllMain() 簡介:
功能:動態鏈接庫 (DLL) 中的可選入口點。
函數原型:BOOL APIENTRY DllMain(
HMODULE hModule, // DLL 模塊的句柄。
DWORD ul_reason_for_call, // 指示為什么調用 DLL 入口點函數的原因代碼。
LPVOID lpvReserved // 保留值,通常為 NULL。
);
參數:ul_reason_for_call
值 | 含義 |
DLL_PROCESS_ATTACH | 當 dll 文件第一次被進程加載時,調用該值下的 DLL 函數。 |
DLL_PROCESS_DETACH | 當 dll 文件從進程中被解除時,調用該值下的 DLL 函數。TerminateProcess() 除外。 |
DLL_THREAD_ATTACH | 當進程創建一個線程時,新建的線程將調用該值下的 DLL 函數。 |
DLL_THREAD_DETACH | 當線程調用 ExitThread() 結束時,該進程將調用該值下的 DLL 函數。TerminateThread() 除外。 |
返回值:當系統使用 DLL_PROCESS_ATTACH 值調用 DllMain 函數時,
如果調用成功則返回 TRUE,否則返回 FALSE。
關於 MFC 的動態鏈接庫的創建與引用,以后再談吧!