DLL編程
靜態(tài)鏈接:每個(gè)應(yīng)用程序使用函數(shù)庫,必須擁有一份庫的備份。多個(gè)應(yīng)用程序運(yùn)行時(shí),內(nèi)存中就有多份函數(shù)庫代碼的備份。
動態(tài)連接庫:多個(gè)應(yīng)用程序可以共享一份函數(shù)庫的備份。
DLL的調(diào)用方式:即DLL的使用者在調(diào)用庫中輸出函數(shù)時(shí),函數(shù)參數(shù)的壓棧和出棧順序和方法。
VC++支持四種方式:
<1>_cdecl調(diào)用方式: 也叫C調(diào)用方式,函數(shù)參數(shù)的壓棧順序是從右至左,參數(shù)的出棧方式由調(diào)用者完成,在調(diào)用DLL的函數(shù)的地方都應(yīng)包含清空堆棧的代碼,它是C/C++缺省的調(diào)用方式。
<2>_stdcall標(biāo)準(zhǔn)調(diào)用方式:函數(shù)參數(shù)壓棧順序是從右至左,參數(shù)出棧工作由被調(diào)用者負(fù)責(zé)完成。系統(tǒng)將加在函數(shù)原型定義前的”WINAPI”宏翻譯為適當(dāng)?shù)恼{(diào)用方式,對于Win32是_stdcall調(diào)用方式。
<3>_fastcall:主要特點(diǎn)是調(diào)用速度快,被調(diào)用的函數(shù)參數(shù)傳遞不依靠堆棧,而是通過寄存器,但并不是對所有的參數(shù)傳遞均使用寄存器,往往只是用ECX和EDX傳送前兩個(gè)雙字或比較小的參數(shù),其余的參數(shù)傳遞仍然采用從右至左壓棧方式,出棧工作由被調(diào)用的函數(shù)完成。
<4>thiscall:前三種是關(guān)鍵字,可以加到函數(shù)前作修飾,thiscall不是關(guān)鍵字,因此程序中不能顯式寫入,這種方式僅應(yīng)用于C++成員函數(shù),this指C++中指向?qū)ο蟮闹羔槪?span lang=EN-US>this存放在ECX寄存器中,參數(shù)從右至左壓棧,出棧由被調(diào)用者完成。
DLL的入口函數(shù)
DllMain()函數(shù)負(fù)責(zé)完成DLL的初始化和解說DLL調(diào)用后的清理工作。當(dāng)加載DLL時(shí),如存在DllMain()函數(shù)則調(diào)用它。
MFC DLL
MFC DLL可以讓我們的程序使用MFC庫,它分為3類:
<1>Regular Dll with MFC Statically linked (正規(guī))
靜態(tài)鏈接MFC庫,在DLL工程中將包含工程中所需的MFC庫代碼的拷貝,因此,程序可以脫離MFC庫使用。
<2>Regular Dll using shared MFC Dll (正規(guī))
動態(tài)共享MFC庫,工程必須在裝有MFC庫的機(jī)器上才能運(yùn)行。
MFC正規(guī)DLL編寫注意問題:
應(yīng)在輸出函數(shù)的函數(shù)體內(nèi)首先加入:
AFX_MANAGE_STATE(AfxGetStaticModuleState());
<3>MFC Extension Dll(using shared MFC Dll) (擴(kuò)展)
它不但能輸出函數(shù),還能輸出類,用戶可以直接使用、繼承這個(gè)輸出類,但它不是正規(guī)DLL,如果要讓非VC++程序調(diào)用,必須使用正規(guī)DLL。
MFC擴(kuò)展DLL編寫注意問題:
<1>在要輸出的類定義中加入:AFC_EXT_CLASS
如:class AFX_EXT_CLASS cls{};
<2>在要輸出的函數(shù)定義前加入:AFC_EXT_CLASS
如:AFX_EXT_CLASS int func(){}
自行編寫DLL的方法
<1>在DLL中編寫的函數(shù)前加上”__declspec(dllexport)”即可導(dǎo)出該函數(shù)。
<2>從DLL中導(dǎo)出類,是在class和類名之間加入”__declspec(dllexport)”,如果只想導(dǎo)出類中指定的函數(shù),可只在該函數(shù)前加上”__declspec(dllexport)”。
<3>C++為支持函數(shù)重載,采用了名稱壓軋,因此,DLL文件在編譯時(shí),函數(shù)名會發(fā)生改變,為保證對DLL的正確訪問,可在”__declspec(dllexport)”聲明之前加入”extern “c””,編譯時(shí)就不會發(fā)生名稱改變,但extern “c”只能用于導(dǎo)出全局函數(shù),不能導(dǎo)出類的成員,如果在函數(shù)名前加入了調(diào)用約定(如:_stdcall),編譯時(shí)還是會發(fā)生名稱改變。
也可通過模塊定義文件的方式解決名稱改變的問題,模塊定義文件的后綴為”.def”,步驟如下:
1新建一個(gè)文本文檔,改名為”x.def”;
2將x.def加入到工程;
3編輯x.def。EXPORTS下所寫函數(shù)名如與DLL文件中函數(shù)名相同,則以所寫名稱導(dǎo)出該函數(shù)。
<4>DLL通過GetForegroundWindow()獲得正在使用它的前景窗口的句柄。
<5>GetModuleHandle()得到一個(gè)DLL的句柄。
GetSystemMetrics()獲取系統(tǒng)信息。
系統(tǒng)對DLL中可改變的數(shù)據(jù),在進(jìn)程寫訪問時(shí)會拷貝到一個(gè)新的數(shù)據(jù)頁面,如果多個(gè)進(jìn)程要共享該數(shù)據(jù),可設(shè)置節(jié),創(chuàng)建節(jié)后,將數(shù)據(jù)放到節(jié)中且必須初始化:
#pragma data_seg(“name”) //開頭
//數(shù)據(jù)
#pragma data_seg() //結(jié)尾
#pragma comment(linker,”/section:name,RWS”)
//設(shè)為讀、寫、共享,也可寫在.def文件中。
使用DLL
要使用DLL,首先要將DLL文件映像到用戶進(jìn)程的地址空間中,并聲明被調(diào)用的函數(shù),然后才能進(jìn)行函數(shù)調(diào)用,調(diào)用方法與一般函數(shù)相同。
將DLL映像到進(jìn)程地址空間的方法:
<1>隱式的加載時(shí)鏈接
DLL工程經(jīng)編譯后,產(chǎn)生一個(gè).dll文件,一個(gè).lib文件及一個(gè)包含DLL輸出函數(shù)聲明的.h頭文件,隱式調(diào)用DLL就是將這個(gè).lib文件鏈接到工程中。
lib文件中包含了DLL允許調(diào)用的所有函數(shù)列表,鏈接器發(fā)現(xiàn)程序調(diào)用了lib文件中列出的某個(gè)函數(shù)時(shí),會在程序的可執(zhí)行文件的文件映像中加入包含這個(gè)函數(shù)的DLL文件的名字信息,當(dāng)程序運(yùn)行時(shí),可執(zhí)行文件被操作系統(tǒng)產(chǎn)生映像文件,系統(tǒng)會查看這個(gè)映像文件中關(guān)于DLL的信息,然后將這個(gè)DLL文件映像到進(jìn)程的地址空間。
鏈接lib文件的方法:
1加入到文件列表
2在Link項(xiàng)下加入
3#pragma comment(lib,”mydll.lib”)
<2>顯式的運(yùn)行時(shí)鏈接步驟如下:
1用LoadLibrary()或AfxLoadLibrary()加載DLL或可運(yùn)行模塊;
2用GetProcAddress()得到要調(diào)用的DLL中函數(shù)的指針,然后使用該函數(shù);
3使用完DLL以后,用FreeLibrary()將DLL在進(jìn)程的映射解除,減少加載DLL的記數(shù)。
被調(diào)用的函數(shù)聲明的方法有三種:
<1>用”extern”聲明被調(diào)用函數(shù)。
<2>使用”__declspec(dllimport)”聲明,即告訴編譯器,所引用的函數(shù)或文件是從DLL中輸入的,編譯器能生成運(yùn)行效率更高的代碼。
<3>也可將聲明放在DLL編寫的頭文件中,在使用的文件中包含該頭文件即可。
要使用DLL中導(dǎo)出的類,必須在使用的文件中包含該類所在的頭文件!
盡量導(dǎo)出方法(做接口)少導(dǎo)出類。
標(biāo)準(zhǔn)DLL中導(dǎo)出函數(shù)的寫法:
extern "C" BOOL __declspec(dllexport) EXPORT ShowDlg() ///標(biāo)準(zhǔn)導(dǎo)出函數(shù)格式
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());//些宏不可少!
// 此處為普通函數(shù)體
}
1.AfxGetStaticModuleState()指向當(dāng)前模塊狀態(tài);
2.當(dāng)前函數(shù)調(diào)用結(jié)束后原模塊的狀態(tài)自動被恢復(fù);
3.用于DLL中所調(diào)用MFC函數(shù)、類、資源時(shí)的模塊狀態(tài)切換
并在def文件中定義導(dǎo)出函數(shù)的序號:ShowDlg @1
在調(diào)用處寫如下代碼:
typedef void (*dllfun)(); //定義函數(shù)指針類型
HINSTANCE hlib= NULL;
hlib=LoadLibrary("std_dll.dll"); //加載庫
dllfun ShowDlg = NULL;//定義函數(shù)指針
ShowDlg=(dllfun)GetProcAddress(hlib,"ShowDlg");//獲取庫中函數(shù)地址
ShowDlg(); //調(diào)用函數(shù)
擴(kuò)展DLL導(dǎo)出類的寫法:
class AFX_EXT_CLASS clsName{};
擴(kuò)展DLL中的資源使用
簡單的說:每個(gè)DLL有自己特有的資源。在使用時(shí),明確的告訴系統(tǒng)要使用哪個(gè)DLL的資源。現(xiàn)在的問題就是如何告訴系統(tǒng)使用哪個(gè)DLL的資源。函數(shù):AfxSetResourceHandle() 可以完成這個(gè)功能。參數(shù)是資源的句柄。
那怎么得到某個(gè)DLL的資源句柄呢?如下:
在擴(kuò)展DLL的入口函數(shù)
extern "C" int APIENTRY DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
…
if (!AfxInitExtensionModule(ShpSymbolDLL, hInstance))
return 0;
…
}
其中ShpSymbolDll 可能會因工程名不同而不同,這里就以這個(gè)名稱代替來說明了,DLL的資源句柄就可以在此得到。
ShpSymbolDLL 定義:
AFX_EXTENSION_MODULE ShpSymbolDLL = { NULL, NULL };
ShpSymbolDLL.hResource 這個(gè)就是我們要的了。其它參數(shù)請看說明。現(xiàn)在我們在使用某DLL的資源時(shí)只要先加入以下兩行就可以正確執(zhí)行了:
HINSTANCE hOld = AfxGetResourceHandle();
AfxSetResourceHandle( ShpSymbolDLL.hResource );
注意在用完之后再恢復(fù):
AfxSetResourceHandle( hOld );
另外一個(gè)不得不提起的東西,在入口函數(shù)中有一行
new CDynLinkLibrary(ShpSymbolDLL);
旁邊有一說明:將此 DLL 插入到資源鏈中。
的確如此。言下之意,上面所說的沒什么用了?其實(shí)不然,假如DLL中有一個(gè)Dialog。 ID為120,在你調(diào)用此DLL的應(yīng)用程序資源中,如果沒有ID的值為120。那么,上面的都是白做了,你會得到預(yù)料中的結(jié)果。但如果應(yīng)用程序中有一相同ID的對話框資源呢?請大家一試。結(jié)果就不一樣了。其中的原因與new CDynLinkLibrary(ShpSymbolDLL) 相關(guān)聯(lián)。
具體請看MFC中 的代碼 DoModal() 就會得到解答。或看MSDN中帶的例子 dllhusk,系統(tǒng)自動會查找相應(yīng)的資源,但不會判斷哪個(gè)是正確的。以找到的第一個(gè)資源為準(zhǔn)。
另:為了編寫方便,可以寫一個(gè)類 ,寫成全局的。
class CModuleInfo
{public:
HMODULE m_hModule;
HMODULE m_hResource;
public:
CModuleInfo(void){}
~CModuleInfo(void){}
};
class AFX_EXT_CLASS CModuleStateMana
{
HINSTANCE m_hInstOld;
public:
CModuleStateMana();
~CModuleStateMana();
};
實(shí)現(xiàn)如下:
CModuleInfo s_mi;
CModuleStateMana::CModuleStateMana()
{
m_hInstOld = AfxGetResourceHandle();
AfxSetResourceHandle( s_mi.m_hModule );
}
CModuleStateMana::~CModuleStateMana()
{
AfxSetResourceHandle( m_hInstOld );
}
然后在入口函數(shù)之前加入
extern CModuleInfo s_mi;
函數(shù)中加入:
s_mi.m_hModule = ShpSymbolDLL.hModule;
s_mi.m_hResource= ShpSymbolDLL.hResource;
在調(diào)用的時(shí)候只要先加入:
CModuleStateMana msm;
就可以正確調(diào)用了。