鏈接庫(kù)是以二進(jìn)制方式存儲(chǔ)數(shù)據(jù)的磁盤(pán)文件,本身不能運(yùn)行,需要其他的應(yīng)用程序執(zhí)行時(shí)動(dòng)態(tài)加載或在編譯前進(jìn)行引用。它分靜態(tài)鏈接庫(kù)和動(dòng)態(tài)鏈接庫(kù)兩種。
(1) 靜態(tài)鏈接庫(kù)(Static-Link Library)
靜態(tài)鏈接庫(kù)是一些相對(duì)較小的、比較穩(wěn)定的函數(shù)庫(kù)。例如C語(yǔ)言的標(biāo)準(zhǔn)函數(shù)庫(kù)stdlib.lib。它通常是幾組與應(yīng)用程序相鏈接的可重用的函數(shù),作為應(yīng)用程序的一部分,如果其內(nèi)容有所更新,必須重新鏈接。
(2) 動(dòng)態(tài)鏈接庫(kù)(Dynamic-Link Library)
動(dòng)態(tài)鏈接庫(kù)可以在需要時(shí)動(dòng)態(tài)加載,其在內(nèi)存中只有一個(gè)實(shí)例,如果一個(gè)應(yīng)用程序調(diào)用了DLL,那么后面其他的應(yīng)用程序再調(diào)用時(shí),只是將DLL的地址映射到自己的進(jìn)程地址空間中(16位的系統(tǒng)時(shí)DLL是被加載到共享地址空間,32位系統(tǒng)則是采用地址映射的方式)。對(duì)于每一個(gè)DLL,系統(tǒng)維持一個(gè)計(jì)數(shù)器,它記錄當(dāng)前有多少個(gè)應(yīng)用程序在使用該DLL。一旦該計(jì)數(shù)器減為0,系統(tǒng)就會(huì)將該DLL從內(nèi)存中卸載掉。由于DLL是可以被動(dòng)態(tài)加載的,所以可以將應(yīng)用程序的某些模塊編譯到DLL中,這樣可以減少應(yīng)用程序的體積,而且在以后維護(hù)時(shí),只需更新相應(yīng)的DLL即可,無(wú)需重新編譯應(yīng)用程序。
DLL的結(jié)構(gòu)和應(yīng)用程序很相近,每個(gè)應(yīng)用程序都已一個(gè)入口函數(shù)WinMain,每個(gè)DLL也有一個(gè)入口函數(shù)DllMain;應(yīng)用程序中含有資源,DLL也可含有資源;DLL和應(yīng)用程序一樣有自己的數(shù)據(jù)段和代碼段,也可使用回調(diào)(callback)函數(shù),也可自定義消息。不同的主要是DLL有輸入符號(hào)表和輸出符號(hào)表,以方便應(yīng)用程序調(diào)用DLL中的函數(shù)。DLL中的入口函數(shù)DllMain的主要框架如下:
BOOL APIENTRY DllMain(HANDLE hModule, // 動(dòng)態(tài)鏈接庫(kù)被調(diào)用時(shí)一個(gè)指向自己的句柄
DWORD ul_reason_for_call, // 動(dòng)態(tài)鏈接庫(kù)被調(diào)用的原因
LPVOID lpReserved // 系統(tǒng)保留 )
{
switch(ul_reason_for_call)
{
case DLL_PROCESS_ATTACH : // 進(jìn)程被調(diào)用
case DLL_THREAD_ATTACH : // 線程被調(diào)用
case DLL_THREAD_DETACH : // 線程被停止
case DLL_PROCESS_DETACH : // 進(jìn)程被停止
return TRUE;
}
}
1、靜態(tài)鏈接庫(kù)的創(chuàng)建及使用
(1)靜態(tài)鏈接庫(kù)的創(chuàng)建
VC++提供創(chuàng)建向?qū)?,運(yùn)行菜單“File/new”,進(jìn)入工程向?qū)нx項(xiàng),對(duì)話框列表中選定“Win32 Static Libraby”。
工程向?qū)е饕O(shè)置Pre-Compiled header和MFC support兩個(gè)選項(xiàng)。
Pre-Compiled header復(fù)選框:選中的話,向?qū)г诠こ讨袝?huì)生成stdafx.h和stdafx.cpp兩個(gè)文件;
MFC support復(fù)選框:選中的話,向?qū)?huì)在stdafx.h中include afx.h和afxwin.h兩個(gè)頭文件,如果第一個(gè)不選擇的話,此復(fù)選框無(wú)意義;
如果連個(gè)都不選擇的話,向?qū)?huì)創(chuàng)建一個(gè)空的工程;
然后用戶自己動(dòng)手添加相應(yīng)的功能實(shí)現(xiàn)文件,完成后經(jīng)過(guò)編譯生成LIB文件。
(2)靜態(tài)鏈接庫(kù)的使用
有兩種方法:一種是在VC的“工程”>>“設(shè)置”里面設(shè)定;一種是編寫(xiě)代碼設(shè)定。
第一種:打開(kāi)“projects”>>"settings",選擇“Link”選項(xiàng)卡,在“Objects/library modules”輸入需要使用的靜態(tài)鏈接庫(kù)文件,例如“static.lib"。如有多個(gè)靜態(tài)鏈接庫(kù)文件時(shí),中間要用逗號(hào)隔開(kāi)。
第二種:在要使用到靜態(tài)鏈接庫(kù)的.cpp文件的起始部分輸入以下代碼:
include "static.h" // 添加對(duì)鏈接庫(kù)頭文件的包含,注意:不管是第一種還是第二種方法,該頭文件必須包含
#pragma comment(lib, "static.lib") // 添加對(duì)鏈接庫(kù)的調(diào)用,注意:該行代碼是在編譯階段完成靜態(tài)鏈接庫(kù)的鏈接
2、動(dòng)態(tài)鏈接庫(kù)的創(chuàng)建和使用
動(dòng)態(tài)鏈接庫(kù)VC工程向?qū)峁┝藘煞N創(chuàng)建向?qū)?,一個(gè)是”Win32 Dynamic-Link Library“工程向?qū)В粋€(gè)是”MFC AppWizard(dll)“工程向?qū)А?/p>
(1)Win32 Dynamic-Link Library的創(chuàng)建
Win32動(dòng)態(tài)鏈接庫(kù)的創(chuàng)建需要用戶設(shè)置工程類(lèi)型:
An empty DLL project——不帶任何文件的空工程;
A simple DLL project——?jiǎng)?chuàng)建一個(gè)動(dòng)態(tài)鏈接庫(kù)工程,該工程有三個(gè)文件:StdAfx.cpp和StdAfx.h,以及一個(gè)帶有DllMain函數(shù)實(shí)現(xiàn)文件;
A DLL that exports some symbols——?jiǎng)?chuàng)建一個(gè)可以導(dǎo)出類(lèi)的動(dòng)態(tài)鏈接庫(kù)工程;
(2)MFC AppWizard(dll)創(chuàng)建
MFC AppWizard(dll)工程向?qū)婕跋旅?種選擇方式:
Regular DLL with MFC statically linked——以靜態(tài)調(diào)用MFC庫(kù)的方式創(chuàng)建正規(guī)的DLL,這樣創(chuàng)建的DLL體積會(huì)很大,使用時(shí)可以沒(méi)有MFC庫(kù)(mfc42.dll等一些文件);
Regular DLL using shared MFC DLL——以動(dòng)態(tài)調(diào)用MFC庫(kù)的方式創(chuàng)建正規(guī)的DLL,這樣創(chuàng)建的DLL體積很小,使用時(shí)系統(tǒng)中必須有MFC庫(kù);
MFC Extension DLL(using share MFC DLL)——運(yùn)用動(dòng)態(tài)鏈接的方式創(chuàng)建擴(kuò)展的DLL,它能夠支持C++接口,可以像類(lèi)一樣使用,即可以派生和繼承。這樣生成的DLL文件比動(dòng)態(tài)方式創(chuàng)建的正規(guī)的DLL還小。
(注意:正規(guī)的DLL的兩種調(diào)用方式可以通過(guò)”project/settings“設(shè)置對(duì)話框中General選項(xiàng)卡的”Microsoft Foundation Classes“列表框中選擇如何引用MFC的動(dòng)態(tài)庫(kù)來(lái)進(jìn)行相互轉(zhuǎn)變。)
擴(kuò)展DLL和正規(guī)DLL區(qū)別:
正規(guī)DLL既可以被MFC庫(kù)的應(yīng)用程序加載,也可被非MFC庫(kù)的應(yīng)用程序加載;而擴(kuò)展只能動(dòng)態(tài)鏈接到MFC庫(kù),而且也只能在使用MFC庫(kù)的應(yīng)用程序中使用;
擴(kuò)展DLL中有明顯動(dòng)態(tài)鏈接庫(kù)的入口函數(shù)DllMain,而規(guī)則DLL則是由編譯器隱式創(chuàng)建;
擴(kuò)展DLL沒(méi)有自己的模塊狀態(tài),它總是使用客戶程序的模塊狀態(tài),而使用規(guī)則DLL時(shí)需要設(shè)置模塊狀態(tài),其設(shè)置語(yǔ)句:AFX_MANAGE_STATE(&afxModuleState)
動(dòng)態(tài)鏈接庫(kù)的函數(shù)需要導(dǎo)出供應(yīng)用程序調(diào)用。導(dǎo)出時(shí)可以使用命名改編和DEF文件兩種方式。使用命名改編也可以將鏈接庫(kù)中類(lèi)導(dǎo)出。
命名改編:
方法:在需要導(dǎo)出的函數(shù)前面增加extern "C" __declspec(dllexport)聲明。
其實(shí)只增加__declspec(dllexport)就可導(dǎo)出函數(shù),但是如果沒(méi)有加上extern "C",就只能使用隱式調(diào)用了(##pragma comment(lib, "Dynic.lib"),因?yàn)榇藭r(shí)導(dǎo)出函數(shù)名稱(chēng)發(fā)生變化。(注意:extern "C"中的'C'一定要大寫(xiě))名稱(chēng)發(fā)生改變時(shí)因?yàn)開(kāi)_declspec是一種命名和調(diào)用約定,與此類(lèi)似的還有_cdecl、_stdcall、_fastcall和thiscall。采用不同的命名和調(diào)用約定,導(dǎo)出的函數(shù)名與原函數(shù)名就會(huì)不同,extern "C"就是限制在導(dǎo)出函數(shù)時(shí)不改變?cè)瘮?shù)的函數(shù)名。
DEF文件:
使用模塊定義文件導(dǎo)出函數(shù)時(shí),導(dǎo)出的函數(shù)名稱(chēng)不會(huì)發(fā)生改變,而且也不需使用extern "C" __declspec(dllexport)聲明函數(shù)。下面是一個(gè)簡(jiǎn)單DEF文件的內(nèi)容(只涉及到DLL導(dǎo)出):
; ResourseDll.def : Declares the module parameters for the DLL.
LIBRARY "ResourseDll"
DESCRIPTION 'ResourseDll Windows Dynamic Link Library'
EXPORTS
; Explicit exports can go here
func1@1
func2@2
LIBRARY:動(dòng)態(tài)鏈接庫(kù)名稱(chēng)模塊,LIBRARY后面的字符就是動(dòng)態(tài)鏈接庫(kù)名稱(chēng);
DESCRIPTION:動(dòng)態(tài)鏈接庫(kù)描述模塊,LIBRARY后面的字符就是對(duì)動(dòng)態(tài)鏈接庫(kù)的描述;
EXPORTS:函數(shù)導(dǎo)出模塊,其后是導(dǎo)出函數(shù),定義導(dǎo)出函數(shù)的語(yǔ)法例如"func1@1“,func1是函數(shù)名,@后面的數(shù)字表示函數(shù)在動(dòng)態(tài)鏈接庫(kù)中的導(dǎo)出序號(hào);
";":注釋符號(hào),分號(hào)后面的就是注釋文本
(3)動(dòng)態(tài)鏈接庫(kù)的使用
動(dòng)態(tài)鏈接庫(kù)的使用分為隱式調(diào)用和動(dòng)態(tài)加載兩種。
隱式調(diào)用:
在需要使用鏈接庫(kù)的.cpp文件起始部分添加下列代碼:
include "static.h" // 添加對(duì)鏈接庫(kù)頭文件的包含
#pragma comment(lib, "Dynic.lib") // 添加對(duì)鏈接庫(kù)的調(diào)用,注意:該行代碼是在編譯階段完成對(duì)鏈接庫(kù)的鏈接
上面的第2行代碼也可通過(guò)"project/settings"對(duì)話框進(jìn)行設(shè)置(前面靜態(tài)庫(kù)使用中介紹過(guò))
(注意:此時(shí)必須把DLL文件和LIB文件一起拷到應(yīng)用程序的目錄下,缺一不可,這是因?yàn)閯?dòng)態(tài)鏈接庫(kù)與靜態(tài)鏈接庫(kù)不同,靜態(tài)鏈接庫(kù)把函數(shù)實(shí)現(xiàn)放在LIB文件中,所以只需要LIB文件即可,而動(dòng)態(tài)鏈接庫(kù)的函數(shù)實(shí)現(xiàn)放在DLL文件中,LIB文件只是存放函數(shù)入口地址)
動(dòng)態(tài)加載:
動(dòng)態(tài)加載主要通過(guò)LoadLibrary、GetProcAddress和FreeLibrary三個(gè)函數(shù)實(shí)現(xiàn)。
HINSTANCE LoadLibrary(
LPCTSTR lpLibFileName // 要裝載的DLL或可執(zhí)行程序的文件名,可以是相對(duì)路徑,也可是絕對(duì)路徑
);
該函數(shù)用來(lái)裝載模塊(動(dòng)態(tài)連接庫(kù)和可執(zhí)行程序都是模塊),函數(shù)執(zhí)行成功返回模塊句柄。
FARPROC GetProcAddress(
HMODULE hModule, // 加載到內(nèi)存的DLL模塊句柄,主要指LoadLibrary函數(shù)的返回值
LPCSTR lpProcName // 函數(shù)的名稱(chēng)
);
該函數(shù)如果執(zhí)行成功,返回模塊中具體函數(shù)的指針。
BOOL FreeLibrary(
HMODULE hLibModule // 裝載在內(nèi)存中的DLL模塊句柄
);
該函數(shù)用來(lái)釋放DLL模塊所占用的內(nèi)存,主要與LoadLibrary函數(shù)配套使用。
(4)動(dòng)態(tài)鏈接庫(kù)導(dǎo)出類(lèi)
要想能夠從動(dòng)態(tài)鏈接庫(kù)中導(dǎo)出C++類(lèi),關(guān)鍵在于在該類(lèi)的類(lèi)名和class關(guān)鍵字中間插入語(yǔ)句__declspec(dllexport)。如果只想導(dǎo)出類(lèi)的某個(gè)成員函數(shù),則只需要在該成員函數(shù)的返回類(lèi)型和函數(shù)名中間添加__declspec(dllexport)即可。使用__declspec(dllexport)語(yǔ)句的成員函數(shù)可以像全局函數(shù)一樣被調(diào)用。
動(dòng)態(tài)鏈接庫(kù)中類(lèi)可以和應(yīng)用程序中類(lèi)一樣使用,但是需要而且只能使用隱式調(diào)用的方式來(lái)加載動(dòng)態(tài)鏈接庫(kù)。
導(dǎo)出整個(gè)C++類(lèi):
class __declspec(dllexport)CClass
{
public:
int Func1(int a, int b);
int Func2(int a, int b);
};
導(dǎo)出類(lèi)中某個(gè)成員函數(shù):
class CClass
{
public:
int Func1(int a, int b);
int __declspec(dllexport)Func2(int a, int b);
};