MFC下DLL编程(图解)
DLL(Dynamic?Link?Library,動(dòng)態(tài)鏈接庫)是微軟公司為Windows和OS/2操作系統(tǒng)設(shè)計(jì)一種供應(yīng)用程序在運(yùn)行時(shí)調(diào)用的共享函數(shù)庫。DLL是應(yīng)用程序的一種擴(kuò)展,也是軟件共享和重用的傳統(tǒng)方法。
DLL除了可同時(shí)被多個(gè)應(yīng)用程序共享外,還可以在不改變調(diào)用接口(從而不需修改使用它的應(yīng)用程序)的情況下,改進(jìn)和升級(jí)里面的庫函數(shù)。而且DLL與編寫它的語言無關(guān),例如,用VC生成的規(guī)則DLL,可以被VB、Delphi等生成的應(yīng)用程序使用。
DLL可以用多種語言和工具編寫,我們這里只介紹如何使用MFC來編寫和使用DLL。相關(guān)說明文檔位于MSDN幫助的“目錄\開發(fā)工具和語言\Visual?Studio\Visual?C++\常見編程方法\DLL\”中。
8.1??基礎(chǔ)
本節(jié)先討論DLL與靜態(tài)庫的區(qū)別,然后列出幾種適合放置DLL的目錄,最后介紹MFC?DLL的三種類型。
8.1.1??DLL與靜態(tài)鏈接庫
靜態(tài)鏈接庫Lib(Static?Link?Library),是在編譯的鏈接階段將庫函數(shù)嵌入到應(yīng)用程序的內(nèi)部。如果系統(tǒng)中運(yùn)行的多個(gè)應(yīng)用程序都包含所用到的公共庫函數(shù),則必然造成很大的浪費(fèi)。這樣即增加了鏈接器的負(fù)擔(dān),也增大了可執(zhí)行程序的大小,還加大了內(nèi)存的消耗。Lib的好處是應(yīng)用程序可以獨(dú)立運(yùn)行,而不需要在操作系統(tǒng)中另外安裝對(duì)應(yīng)的DLL。
而DLL采用動(dòng)態(tài)鏈接,對(duì)公用的庫函數(shù),系統(tǒng)只有一個(gè)拷貝(一般是位于系統(tǒng)目錄的*.DLL文件),而且只有在應(yīng)用程序真正調(diào)用時(shí),才加載到內(nèi)存。在內(nèi)存中的庫函數(shù),也只有一個(gè)拷貝,可供所有運(yùn)行的程序調(diào)用。當(dāng)再也沒有程序需要調(diào)用它時(shí),系統(tǒng)會(huì)自動(dòng)將其卸載,并釋放其所占用的內(nèi)存空間。參見圖8-1。
DLL的缺點(diǎn)是應(yīng)用程序不能獨(dú)立運(yùn)行,需要在操作系統(tǒng)中另外安裝對(duì)應(yīng)的DLL。例如,如果你的MFC項(xiàng)目被設(shè)置成“在共享DLL中使用MFC”的,則雖然生成的可執(zhí)行程序很小,但是在其他沒有安裝Visual?C++(運(yùn)行環(huán)境)的機(jī)器上是不能直接運(yùn)行的,需要另外安裝MFC的動(dòng)態(tài)鏈接庫(如mfc90.dll)。
8.1.2??放置DLL的目錄
為了使需要?jiǎng)討B(tài)鏈接庫的應(yīng)用程序可以運(yùn)行,需要將DLL文件放在操作系統(tǒng)能夠找到的地方。Windows操作系統(tǒng)查找DLL的目錄順序?yàn)?#xff1a;
8.1.3??MFC?DLL的類型
使用MFC編寫的DLL,可以分成兩大類:
l規(guī)則DLL——規(guī)則(regular)DLL中所包含的函數(shù),可以被所有Windows應(yīng)用程序使用;
共享MFC——DLL中不包含MFC庫函數(shù),需要另外安裝MFC動(dòng)態(tài)鏈接庫后才能使用;
靜態(tài)MFC——DLL中包含MFC庫函數(shù),可以脫離MFC動(dòng)態(tài)鏈接庫獨(dú)立使用。
擴(kuò)展DLL——擴(kuò)展(extension)DLL中所定義的類和函數(shù),只能被所MFC應(yīng)用程序使用。而且擴(kuò)展DLL中不能包含MFC庫函數(shù),也需要另外安裝MFC動(dòng)態(tài)鏈接庫后才能使用。
8.1.4??導(dǎo)出函數(shù)的方法
使用MFC創(chuàng)建DLL時(shí),從項(xiàng)目中導(dǎo)出(export)函數(shù)到DLL文件的方法有:
?使用模塊定義文件(.def)。
?使用__declspec(dllexport)關(guān)鍵字或其替代宏AFX_EXT_CLASS。
這兩種方法是互斥的,對(duì)每個(gè)函數(shù)只需用一種方法即可。另外,DEF文件只能用來導(dǎo)出函數(shù),不能用于導(dǎo)出整個(gè)類。導(dǎo)出C++類,必須用__declspec(dllexport)關(guān)鍵字或其替代宏AFX_EXT_CLASS。
?
1.DEF文件
模塊定義(module?definition)文件(.def)是包含一個(gè)或多個(gè)描述DLL各種屬性的模塊語句的文本文件。DEF文件必須至少包含下列模塊定義語句:
l?文件中的第一個(gè)語句必須是LIBRARY語句。此語句將.def文件標(biāo)識(shí)為屬于DLL。LIBRARY語句的后面是DLL的名稱(缺省為DLL項(xiàng)目名)。鏈接器將此名稱放到DLL的導(dǎo)入庫中。
l?EXPORTS語句列出名稱,可能的話還會(huì)列出DLL導(dǎo)出函數(shù)的序號(hào)值。通過在函數(shù)名的后面加上@符和一個(gè)數(shù)字,給函數(shù)分配序號(hào)值。當(dāng)指定序號(hào)值時(shí),序號(hào)值的范圍必須是從1到N,其中N是DLL導(dǎo)出函數(shù)的個(gè)數(shù)。
即,DEF文件的格式為:(在這兩個(gè)語句之間,還可以加上可選的描述語句:DESCRIPTION?"庫描述串"。分號(hào);后的文本內(nèi)容行為注釋)
;?庫名.def
LIBRARY?庫名
EXPORTS
函數(shù)名1?@1
函數(shù)名2?@2
……
函數(shù)名n?@n
在使用MFC?DLL向?qū)?chuàng)建MFC?DLL項(xiàng)目時(shí),VC會(huì)自動(dòng)創(chuàng)建一個(gè)與項(xiàng)目同名但沒有任何函數(shù)導(dǎo)出項(xiàng)的DEF文件(項(xiàng)目名.def),格式為:
;?項(xiàng)目名.def?:?聲明?DLL?的模塊參數(shù)。
?
LIBRARY??????"項(xiàng)目名"
?
EXPORTS
????;?此處可以是顯式導(dǎo)出
例如,項(xiàng)目名為RegDll的DEF文件(RegDll.def)的內(nèi)容為:
;?RegDll.def?:?聲明?DLL?的模塊參數(shù)。
?
LIBRARY??????"RegDll"
?
EXPORTS
;?此處可以是顯式導(dǎo)出
如果生成擴(kuò)展DLL并使用.def文件導(dǎo)出,則將下列代碼放在包含導(dǎo)出類的頭文件的開頭和結(jié)尾:
#undef?AFX_DATA
#define?AFX_DATA?AFX_EXT_DATA
//?<你的頭文件體>
#undef?AFX_DATA
#define?AFX_DATA
這些代碼行確保內(nèi)部使用的MFC變量或添加到類的變量是從擴(kuò)展DLL導(dǎo)出(或?qū)?#xff09;的。例如,當(dāng)使用DLAECRE_DYNAMIC派生類時(shí),該宏擴(kuò)展以將CRuntimeClass成員變量添加到類。省去這四行代碼可能會(huì)導(dǎo)致不能正確編譯或鏈接DLL,或在客戶端應(yīng)用程序鏈接到DLL時(shí)導(dǎo)致錯(cuò)誤。
當(dāng)生成DLL時(shí),鏈接器使用.def文件創(chuàng)建導(dǎo)出(.exp)文件和導(dǎo)入庫(.lib)文件。然后,鏈接器使用導(dǎo)出文件生成DLL文件。隱式鏈接到DLL的可執(zhí)行文件在生成時(shí)鏈接到導(dǎo)入庫。請(qǐng)注意,MFC本身就是使用.def文件從MFCx0.dll導(dǎo)出函數(shù)和類的。
?
2.關(guān)鍵字或宏
除了使用DEF文件來導(dǎo)出函數(shù)外,還可以在源程序中使用__declspec(dllexport)關(guān)鍵字或其替代宏AFX_EXT_CLASS:
#define?AFX_EXT_CLASS??AFX_CLASS_EXPORT?(定義在頭文件afxv_dll.h中)
#define?AFX_CLASS_EXPORT??__declspec(dllexport)?(定義在頭文件afxver_.h中)
來導(dǎo)出函數(shù)和整個(gè)C++類。
具體的格式為:
l導(dǎo)出整個(gè)類:
class?AFX_EXT_CLASS?類名[?:?public基類]
{
……
}
l?導(dǎo)出類的成員函數(shù):
class?類名[?:?public基類]
{
AFX_EXT_CLASS?返回類型?函數(shù)名1(……)?;
AFX_EXT_CLASS?返回類型?函數(shù)名2(……)?;
……
}
l?導(dǎo)出外部C格式的(全局)函數(shù):
extern?"C"?__declspec(dllexport)?返回類型?函數(shù)名(……)?
{
……
}
如果希望用MFC(C++)編寫的規(guī)則DLL中的函數(shù),也能夠被非MFC程序來調(diào)用,需要為函數(shù)聲明指定extern?"C"。不然,C++編譯器會(huì)使用C++類型安全命名約定(也稱作名稱修飾)和C++調(diào)用約定(使用此調(diào)用約定從C調(diào)用會(huì)很困難)。
為了使用方便,可以定義宏:
#define?DllExport?extern?"C"?__declspec(dllexport)
然后再使用它,例如:
DllExport?int?Add(int?d1,?int?d2)?{……}
8.2??擴(kuò)展DLL
使用MFC編寫的擴(kuò)展DLL,可以導(dǎo)出整個(gè)類(從而能使用類中的所有成員,包括數(shù)據(jù)成員和成員函數(shù)),也可以導(dǎo)出指定的若干(成員或全局)函數(shù)。
下面我們通過一個(gè)四則運(yùn)算的例子,看看如何用宏AFX_EXT_CLASS來編寫和使用導(dǎo)出整個(gè)C++類的擴(kuò)展MFC?DLL。
8.2.1??創(chuàng)建DLL項(xiàng)目
我們創(chuàng)建一個(gè)名為ExtDll的擴(kuò)展DLL的“Visual?C++”之“MFC”的“MFC?DLL”項(xiàng)目,注意需選中“創(chuàng)建解決方案的目錄”復(fù)選框,參見圖8-2。
?
圖8-2??新建MFC?DLL項(xiàng)目ExtDll的對(duì)話框
?
按“確定”鈕,彈出“MFC?DLL向?qū)А睂?duì)話框。在“DLL類型”欄中,選中“擴(kuò)展DLL”單選鈕,參見圖8-3。按“完成”鈕,創(chuàng)建ExtDll解決方案和項(xiàng)目。
?
圖8-3??選擇“擴(kuò)展DLL”的MFC?DLL向?qū)?duì)話框
8.2.2??添加導(dǎo)出類
為新項(xiàng)目添加用于四則計(jì)算的導(dǎo)出類CCompute。方法有多種,可以在項(xiàng)目管理區(qū)的“類視圖”頁中,選中項(xiàng)目名“ExtDll”,按鼠標(biāo)右鍵,在彈出菜單中選“添加\類”。在彈出的“添加類”對(duì)話框中,選擇“Visual?C++”之“MFC”的“MFC類”項(xiàng),參見圖8-4。
?
圖8-4??添加類對(duì)話框
?
按“添加”鈕,彈出“MFC類向?qū)А睂?duì)話框。在“類名”欄中鍵入“CCompute”,在“基類”下拉式列表,選“CObject”,參見圖8-5。按“完成”鈕,添加該類到ExtDll項(xiàng)目。
?
圖8-5??MFC類向?qū)?duì)話框
8.2.3??編寫導(dǎo)出類代碼
我們將整個(gè)CCompute類設(shè)為導(dǎo)出類,并在里面添加2個(gè)成員變量、1個(gè)構(gòu)造函數(shù)和4個(gè)用于四則運(yùn)算的成員函數(shù),外加1個(gè)演示導(dǎo)出函數(shù)的取模全局函數(shù)Mod。
下面是CCompute類的頭文件(Compute.h),其中紅色的部分是自己添加:(注意導(dǎo)出宏AFX_EXT_CLASS的使用)
#pragma once// CCompute 命令目標(biāo)class AFX_EXT_CLASS CCompute : public CObject{public:int m_data1, m_data2;public:CCompute();CCompute(int d1, int d2);virtual ~CCompute();public:int Add();int Sub();int Mul();double Div();};AFX_EXT_CLASS int Mod(int d1, int d2);?
下面是CCompute類的代碼源文件(Compute.cpp),其中紅色為自己添加的部分:
// Compute.cpp : 實(shí)現(xiàn)文件// #include "stdafx.h"#include "Compute.h"// CCompute CCompute::CCompute(){}CCompute::CCompute(int d1, int d2){m_data1 = d1;m_data2 = d2;}CCompute::~CCompute(){}// CCompute 成員函數(shù)int CCompute::Add(){return m_data1 + m_data2;}int CCompute::Sub(){return m_data1 - m_data2;}int CCompute::Mul(){return m_data1 * m_data2;}double CCompute::Div(){if (m_data2 == 0 ) {AfxMessageBox(L"Divided by zero!");return 0;}return (double)m_data1 / m_data2;}int Mod(int d1, int d2){if (d2 == 0 ) {AfxMessageBox(L"Modulo by zero!");return 0;}return d1 % d2;}?
編譯運(yùn)行時(shí),后彈出圖8-6所示的對(duì)話框:
?
圖8-6??調(diào)試會(huì)話的可執(zhí)行文件對(duì)話框
要求你選擇或輸入使用此DLL的應(yīng)用程序之可執(zhí)行文件的名稱或路徑。這是因?yàn)?/span>DLL雖然包含了可運(yùn)行函數(shù)的二進(jìn)制代碼,但是它并不是獨(dú)立的應(yīng)用程序,不能單獨(dú)運(yùn)行。因此,我們必須編寫使用DLL的客戶程序。
8.2.4??添加客戶程序項(xiàng)目
為了演示擴(kuò)展DLL的應(yīng)用,我們?cè)谠鉀Q方案ExtDll中,添加一個(gè)客戶程序項(xiàng)目ExtClient。具體做法是,打開新建項(xiàng)目對(duì)話框,選中“Visual?C++”之“MFC”的“MFC應(yīng)用程序”模板,鍵入項(xiàng)目名ExtClient。注意,需選在對(duì)話框底部的“解決方案”下拉式列表中選中“添入解決方案”表項(xiàng),參見圖8-7。
?
圖8-6??新建客戶程序項(xiàng)目的對(duì)話框
?
按“確定”鈕進(jìn)入“MFC應(yīng)用程序向?qū)А睂?duì)話框,在“應(yīng)用程序類型”頁,選中“基于對(duì)話框”單選鈕,按“完成”添加項(xiàng)目。
此時(shí),ExtDll解決方案包含兩個(gè)項(xiàng)目:DLL項(xiàng)目ExtDll和客戶程序項(xiàng)目ExtClient,生成的文件目錄結(jié)構(gòu)為:
ExtDll?←解決方案目錄
Debug?←解決方案的調(diào)試目錄
Release?←解決方案的發(fā)行目錄
ExtDll?←DLL項(xiàng)目目錄
Debug?←DLL的調(diào)試目錄
Release?←DLL的發(fā)行目錄
res?←DLL的資源目錄
ExtClient?←客戶程序項(xiàng)目目錄
Debug?←客戶程序的調(diào)試目錄
Release?←客戶程序的發(fā)行目錄
res?←客戶程序的資源目錄
8.2.5??設(shè)置依賴項(xiàng)(個(gè)人看還要添加引用ExtDll)
為了使客戶程序可以調(diào)用DLL,需要將它們關(guān)聯(lián)起來。最簡單的辦法是設(shè)置DLL項(xiàng)目為客戶項(xiàng)目的依賴項(xiàng)。具體做法是,在項(xiàng)目管理區(qū)中選中客戶項(xiàng)目名“ExtClient”,選中菜單項(xiàng)“項(xiàng)目\項(xiàng)目依賴項(xiàng)”,在彈出的“項(xiàng)目依賴項(xiàng)”對(duì)話框中,選中“依賴欄”中的“ExtDll”復(fù)選框,參見圖8-7。
?
圖8-7??設(shè)置ExtClient項(xiàng)目依賴項(xiàng)的對(duì)話框
8.2.6??編寫客戶程序代碼
1.編輯對(duì)話框資源
添加表示操作數(shù)的2個(gè)靜態(tài)文本框和2個(gè)文本編輯框(ID值分別為IDC_DATA1和IDC_DATA2)、5個(gè)表示四則運(yùn)算和取模運(yùn)算的按鈕(ID值分別為IDC_ADD、IDC_SUB、IDC_MUL、IDC_DIV和IDC_MOD)、表示計(jì)算結(jié)果的1個(gè)靜態(tài)文本框和1個(gè)文本編輯框(ID值為IDC_RESULT),刪除原來“確定”按鈕,將原來的“取消”按鈕的“Caption”屬性值改為“退出”,參見圖8-8。
?
圖8-8??客戶程序的對(duì)話框界面
?
2.添加控件變量
為了動(dòng)態(tài)獲取用戶輸入的數(shù)據(jù),我們需要為2個(gè)表示操作數(shù)據(jù)的文本編輯框,添加控件的Value值類別int型變量m_iData1和m_iData2。
3.添加事件處理
分別對(duì)5個(gè)計(jì)算按鈕,為對(duì)話框類CExtClientDlg添加按鈕通知消息BN_CLICKED(鼠標(biāo)單擊)事件的處理程序OnBnClickedAdd等。
4.編寫代碼
為了讓客戶程序可以使用DLL項(xiàng)目中的計(jì)算類CCompute,需要在客戶程序?qū)υ捒蝾怌ExtClientDlg的頭文件
?
在ExtClientDlg.h?中
添加??#include?"..\ExtDll\Compute.h"//相對(duì)路徑(這個(gè)比絕對(duì)路徑要好),看情況..\表示相對(duì)自己上一級(jí)目錄。用來引入CCompute類。
檢查class?CAboutDlg?:?public?CDialogEx?中的,對(duì)話框數(shù)據(jù)enum?{?IDD?=?操作對(duì)話框數(shù)據(jù)ID?};
在對(duì)話框類的定義中,手工添加公共型類變量和成員函數(shù):
public:???CCompute?*m_pComp; ???void?Comp(UINT?nID);
?
?
?ExtClientDlg.c?//文件
// CExtClientDlg 對(duì)話框class CExtClientDlg : public CDialog{……public:int m_iData1;int m_iData2;CCompute *m_pComp;void Comp(UINT nID);afx_msg void OnBnClickedAdd();afx_msg void OnBnClickedSub();afx_msg void OnBnClickedMul();afx_msg void OnBnClickedDiv();afx_msg void OnBnClickedMod(); };在客戶對(duì)話框類ExtClientDlg.cpp的初始化對(duì)話框成員函數(shù)OnInitDialog中,手工添加設(shè)置數(shù)據(jù)編輯框初值的代碼(紅色部分): BOOL CExtClientDlg::OnInitDialog(){CDialog::OnInitDialog();……// TODO: 在此添加額外的初始化代碼 SetDlgItemInt(IDC_DATA1, 5);SetDlgItemInt(IDC_DATA2, 3);return TRUE; // 除非將焦點(diǎn)設(shè)置到控件,否則返回 TRUE }
?
?代碼文件ExtClientDlg.cpp中其他新加內(nèi)容有:(其中紅色部分為手工添加的)
void CExtClientDlg::OnBnClickedAdd(){// TODO: 在此添加控件通知處理程序代碼 Comp(IDC_ADD);}void CExtClientDlg::OnBnClickedSub(){// TODO: 在此添加控件通知處理程序代碼 Comp(IDC_SUB); }void CExtClientDlg::OnBnClickedMul(){// TODO: 在此添加控件通知處理程序代碼 Comp(IDC_MUL);}void CExtClientDlg::OnBnClickedDiv(){// TODO: 在此添加控件通知處理程序代碼 Comp(IDC_DIV);} void CExtClientDlg::OnBnClickedMod(){// TODO: 在此添加控件通知處理程序代碼 Comp(IDC_MOD);}void CExtClientDlg::Comp(UINT nID){UpdateData(); // 動(dòng)態(tài)獲取用戶輸入的數(shù)據(jù)并賦值給對(duì)應(yīng)的控件變量m_pComp = new CCompute(m_iData1, m_iData2); // 創(chuàng)建計(jì)算對(duì)象int r;double dr;switch(nID) { // 進(jìn)行四則和取模運(yùn)算case IDC_ADD: r = m_pComp->Add(); break;case IDC_SUB: r = m_pComp->Sub(); break;case IDC_MUL: r = m_pComp->Mul(); break;case IDC_DIV: dr = m_pComp->Div(); break;case IDC_MOD: r = Mod(m_iData1, m_iData2); break;}delete m_pComp;if (nID != IDC_DIV) SetDlgItemInt(IDC_RESULT, r); // 顯示整數(shù)結(jié)果else { // 顯示除法所得的實(shí)數(shù)結(jié)果wchar_t buf[20];swprintf_s(buf, 20, L"%g", dr);SetDlgItemText(IDC_RESULT, buf);}}//修改點(diǎn)擊退出按鈕動(dòng)作----退出void CExtClientDlg::OnBnClickedCancel(){// TODO:在此添加控件通知處理程序代碼 exit(0); }?
?
8.2.7??編譯運(yùn)行
為了運(yùn)行客戶程序,需要將客戶程序項(xiàng)目設(shè)置成啟動(dòng)項(xiàng)目。具體做法是,先在項(xiàng)目管理區(qū)中選中ExtClient項(xiàng)目,然后選擇菜單項(xiàng)“項(xiàng)目\設(shè)為啟動(dòng)項(xiàng)目”。
編譯后,會(huì)在解決方案的Debug或Release目錄中生成動(dòng)態(tài)鏈接庫文件ExtDll.dll和客戶程序的可執(zhí)行文件ExtClient.exe,以及DLL的導(dǎo)出文件ExtDll.exp和(靜態(tài)連接)庫文件ExtDll.lib。如果出現(xiàn):
>ExtClientDlg.obj?:?error?LNK2019:?無法解析的外部符號(hào)?"__declspec(dllimport)?public:?__thiscall?CCompute::CCompute(int,int)"?(__imp_??0CCompute@@QAE@HH@Z),該符號(hào)在函數(shù)?"public:?void?__thiscall?CExtClientDlg::Comp(unsigned?int)"?(?Comp@CExtClientDlg@@QAEXI@Z)?中被引用
1>ExtClientDlg.obj?:?error?LNK2019:?無法解析的外部符號(hào)?"__declspec(dllimport)?public:?virtual?__thiscall?CCompute::~CCompute(void)"?(__imp_??1CCompute@@UAE@XZ),該符號(hào)在函數(shù)?"public:?virtual?void?*?__thiscall?CCompute::`scalar?deleting?destructor'(unsigned?int)"?(??_GCCompute@@UAEPAXI@Z)?中被引用
1>D:\ExtDll\Debug\ExtClient.exe?:?fatal?error?LNK1120:?7?個(gè)無法解析的外部命令
這樣的錯(cuò)誤是因?yàn)槿鄙賚ib庫導(dǎo)致添加操作如下,這里是缺少
右鍵“ExtClient”項(xiàng)目名稱出現(xiàn)
下一步添加lib庫
添加路徑:
?
問題解決。
運(yùn)行結(jié)果如圖8-9所示:
?
圖8-9??客戶程序ExtClient的運(yùn)行結(jié)果
8.3??規(guī)則DLL
使用MFC編寫的規(guī)則DLL,雖然只能導(dǎo)出函數(shù)而不能導(dǎo)出整個(gè)類,但是其導(dǎo)出的函數(shù)卻可以其他被非MFC應(yīng)用程序所調(diào)用。下面我們?nèi)酝ㄟ^上面的四則運(yùn)算的例子,看看如何用關(guān)鍵字__declspec(dllexport)和extern?"C"來編寫和使用導(dǎo)出若干(全局)C函數(shù)的規(guī)則MFC?DLL。
8.3.1??創(chuàng)建DLL項(xiàng)目
我們創(chuàng)建一個(gè)名為RegDll的規(guī)則DLL的“Visual?C++”之“MFC”的“MFC?DLL”項(xiàng)目,注意仍需選中“創(chuàng)建解決方案的目錄”復(fù)選框,參見圖8-10。
?
圖8-10??新建MFC?DLL項(xiàng)目RegDll的對(duì)話框
?
按“確定”鈕,彈出“MFC?DLL向?qū)А睂?duì)話框。在“DLL類型”欄中,選中“使用共享MFC?DLL的規(guī)則DLL”單選鈕,參見圖8-11。按“完成”鈕,創(chuàng)建RegDll解決方案和項(xiàng)目。
?
圖8-11??選擇規(guī)則DLL的MFC?DLL向?qū)?duì)話框
?
也可以選擇“帶靜態(tài)鏈接MFC的規(guī)則DLL”,差別是所生成的DLL中會(huì)包含MFC庫,當(dāng)然所生成的庫文件也會(huì)大一些(但因此可不用另外安裝MFC動(dòng)態(tài)鏈接庫)。例如,在此例中,選共享MFC所生成的RegDll.dll文件只有13KB大,而選擇靜態(tài)MFC的則有199KB。
規(guī)則DLL項(xiàng)目是使用共享MFC還是使用靜態(tài)MFC,也可以在生成DLL項(xiàng)目之后,通過項(xiàng)目屬性對(duì)話框的“配置屬性\常規(guī)”頁中的“MFC的使用”欄中的下拉式列表選項(xiàng)來切換,這一點(diǎn)與普通MFC應(yīng)用程序項(xiàng)目的類似。
8.3.2??使用DEF文件導(dǎo)出函數(shù)
1.編輯DEF文件
在項(xiàng)目管理區(qū)中,選擇“解決方案資源管理器”頁,展開“RegDll”項(xiàng)目項(xiàng),雙擊其“RegDll.def”子項(xiàng),打開DLL項(xiàng)目中自動(dòng)生成的DEF文件。在該DEF文件中加入需要導(dǎo)出的5個(gè)函數(shù)項(xiàng):(紅色部分為手工添加的)
;?RegDll.def?:?聲明?DLL?的模塊參數(shù)。
?
LIBRARY??????"RegDll"
?
EXPORTS
????;?此處可以是顯式導(dǎo)出
????Add @1
????Sub @2
????Mul @3
????Div @4
? ? Mod @5
2.編寫導(dǎo)出函數(shù)代碼
可以在RegDll項(xiàng)目的應(yīng)用程序類的代碼文件RegDll.cpp的尾部手工添加如下代碼:
extern "C" int Add(int d1, int d2) { return d1 + d2;}extern "C" int Sub(int d1, int d2) { return d1 - d2;}extern "C" int Mul(int d1, int d2) { return d1 * d2;}extern "C" double Div(int d1, int d2) {if (d2 == 0) {AfxMessageBox(L"Divided by zero!");return 0;}return (double)d1 / d2;}extern "C" int Mod(int d1, int d2) {return d1 % d2;}?
注意,函數(shù)前的extern?"C"是不可少的,它指定按C語言約定來生成導(dǎo)出函數(shù)。不然,缺省情況下,C++編譯器會(huì)生成冗長的函數(shù)修飾符,不能簡單地用函數(shù)名來調(diào)用。
8.3.3??使用關(guān)鍵字__declspec(dllexport)導(dǎo)出函數(shù)
也可以不修改DEF文件,而在代碼文件中直接用關(guān)鍵字__declspec(dllexport)和extern?"C"來指定導(dǎo)出函數(shù)。對(duì)應(yīng)的代碼為:(也加在RegDll.cpp的尾部)
#define DllExport extern "C" __declspec(dllexport)DllExport int Add(int d1, int d2) { return d1 + d2;}DllExport int Sub(int d1, int d2) { return d1 - d2;}DllExport int Mul(int d1, int d2) { return d1 * d2;}DllExport double Div(int d1, int d2) {if (d2 == 0) {AfxMessageBox(L"Divided by zero!");return 0;}return (double)d1 / d2;}DllExport int Mod(int d1, int d2) {if (d2 == 0) {AfxMessageBox(L"Modulo by zero!");return 0;}return d1 % d2;}?
8.3.4??編寫客戶程序?
1.添加客戶程序項(xiàng)目
與上節(jié)類似,為例演示DLL的調(diào)用,我們也為RegDLL解決方案添加一個(gè)客戶程序——基于對(duì)話框的MFC應(yīng)用程序項(xiàng)目RegClient,參見圖8-12。
?
圖8-12??添加客戶程序項(xiàng)目RegClient的對(duì)話框
?
2.設(shè)置依賴項(xiàng)
我們也通過設(shè)置DLL項(xiàng)目為客戶項(xiàng)目的依賴項(xiàng)將RegClient與RegDll.dll關(guān)聯(lián)起來,參見圖8-13。
?
圖8-13??設(shè)置RegClient項(xiàng)目依賴項(xiàng)的對(duì)話框
?
3.編輯對(duì)話框資源
為了節(jié)省時(shí)間,避免重復(fù)勞動(dòng),可以復(fù)制ExtClient項(xiàng)目中的對(duì)話框。具體做法是:在RegDll解決方案環(huán)境中打開ExtDll解決方案中ExtClient項(xiàng)目的資源文件ExtClient.rc文件,(用鼠標(biāo)或按Ctrl?+?A組合鍵)選中其主對(duì)話框中的所有控件,(按Ctrl?+?C或Ctrl?+?Insert組合鍵)復(fù)制它們到剪接板。然后打開RegClient項(xiàng)目的主對(duì)話框編輯器,先刪除其中的所有控件,然后再粘貼剪接板中的控件到對(duì)話框,參見圖8-8。具體操作如下:
?
打開ExtClient.rc文件
?
如下圖
?
選擇主對(duì)話框:
?
復(fù)制粘貼
?
4.編寫代碼
類似ExtClient程序,我們也需要為2個(gè)數(shù)據(jù)編輯框添加類變量,并逐個(gè)為運(yùn)算符按鈕添加單擊事件處理函數(shù)。在頭文件RegClientDlg.h的尾部會(huì)出現(xiàn)如下代碼:(其中紅色的Comp函數(shù)原型是手工添加的)
public:int m_iData1;int m_iData2;void Comp(UINT nID);afx_msg void OnBnClickedAdd();afx_msg void OnBnClickedSub();afx_msg void OnBnClickedMul();afx_msg void OnBnClickedDiv();afx_msg void OnBnClickedMod();?
在客戶對(duì)話框類RegClientDlg.cpp的初始化對(duì)話框成員函數(shù)OnInitDialog中,手工添加設(shè)置數(shù)據(jù)編輯框初值的代碼(紅色部分):
BOOL CRegClientDlg::OnInitDialog(){CDialog::OnInitDialog();……// TODO: 在此添加額外的初始化代碼 SetDlgItemInt(IDC_DATA1, 5);SetDlgItemInt(IDC_DATA2, 3);return TRUE; // 除非將焦點(diǎn)設(shè)置到控件,否則返回 TRUE }對(duì)應(yīng)的代碼文件RegClientDlg.cpp尾部新增的代碼為:(其中紅色部分是手工添加的)void CRegClientDlg::OnBnClickedAdd(){// TODO: 在此添加控件通知處理程序代碼 Comp(IDC_ADD);}void CRegClientDlg::OnBnClickedSub(){// TODO: 在此添加控件通知處理程序代碼 Comp(IDC_SUB);}void CRegClientDlg::OnBnClickedMul(){// TODO: 在此添加控件通知處理程序代碼 Comp(IDC_MUL);}void CRegClientDlg::OnBnClickedDiv(){// TODO: 在此添加控件通知處理程序代碼 Comp(IDC_DIV);}void CRegClientDlg::OnBnClickedMod(){// TODO: 在此添加控件通知處理程序代碼 Comp(IDC_MOD);}#define DllImport extern "C" _declspec(dllimport)DllImport int Add(int d1, int d2);DllImport int Sub(int d1, int d2);DllImport int Mul(int d1, int d2);DllImport double Div(int d1, int d2);DllImport int Mod(int d1, int d2);void CRegClientDlg::Comp(UINT nID){UpdateData();int r;double dr;switch(nID) {case IDC_ADD: r = Add(m_iData1, m_iData2); break;case IDC_SUB: r = Sub(m_iData1, m_iData2); break;case IDC_MUL: r = Mul(m_iData1, m_iData2); break;case IDC_MOD: r = Mod(m_iData1, m_iData2); break;case IDC_DIV: dr = Div(m_iData1, m_iData2); break;}if (nID != IDC_DIV) SetDlgItemInt(IDC_RESULT, r);else {wchar_t buf[20];swprintf_s(buf, 20, L"%g", dr);SetDlgItemText(IDC_RESULT, buf);}}?
5.編譯運(yùn)行
似上節(jié)的ExtClient項(xiàng)目,先設(shè)置RegClient項(xiàng)目為啟動(dòng)項(xiàng)目,再編譯運(yùn)行,結(jié)果如圖8-14所示:
?
圖8-14??客戶程序RegClient的運(yùn)行結(jié)果
?
總結(jié)
以上是生活随笔為你收集整理的MFC下DLL编程(图解)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: VC++2010中的GetWindowT
- 下一篇: VC++动态链接库编程之MFC DLL