前文我們對非MFC DLL和MFC規則DLL進行了介紹,現在開始詳細分析DLL的最后一種類型――MFC擴展DLL。
6.1概論 MFC擴展DLL與MFC規則DLL的相同點在于在兩種DLL的內部都可以使用MFC類庫,其不同點在于MFC擴展DLL與應用程序的接口可以是MFC的。MFC擴展DLL的含義在于它是MFC的擴展,其主要功能是實現從現有MFC庫類中派生出可重用的類。MFC擴展DLL使用MFC 動態鏈接庫版本,因此只有用共享MFC 版本生成的MFC 可執行文件(應用程序或規則DLL)才能使用MFC擴展DLL。
從前文可知,MFC規則DLL被MFC向導自動添加了一個CWinApp的對象,而MFC擴展DLL則不包含該對象,它只是被自動添加了DllMain 函數。對于MFC擴展DLL,開發人員必須在DLL的DllMain函數中添加初始化和結束代碼。
從下表我們可以看出三種DLL對DllMain入口函數的不同處理方式:
DLL類型 |
入口函數 |
非 MFC DLL |
編程者提供DllMain函數 |
MFC規則 DLL |
CWinApp對象的InitInstance 和 ExitInstance |
MFC擴展 DLL |
MFC DLL向導生成DllMain 函數 |
對于MFC擴展DLL,系統會自動在工程中添加如下表所示的宏,這些宏為DLL和應用程序的編寫提供了方便。像AFX_EXT_CLASS、AFX_EXT_API、AFX_EXT_DATA這樣的宏,在DLL和應用程序中將具有不同的定義,這取決于_AFXEXT宏是否被定義。這使得在DLL和應用程序中,使用統一的一個宏就可以表示出輸出和輸入的不同意思。在DLL中,表示輸出(因為_AFXEXT被定義,通常是在編譯器的標識參數中指定/D_AFXEXT);在應用程序中,則表示輸入(_AFXEXT沒有定義)。
宏 |
定義 |
AFX_CLASS_IMPORT |
__declspec(dllexport) |
AFX_API_IMPORT |
__declspec(dllexport) |
AFX_DATA_IMPORT |
__declspec(dllexport) |
AFX_CLASS_EXPORT |
__declspec(dllexport) |
AFX_API_EXPORT |
__declspec(dllexport) |
AFX_DATA_EXPORT |
__declspec(dllexport) |
AFX_EXT_CLASS |
#ifdef _AFXEXT AFX_CLASS_EXPORT #else AFX_CLASS_IMPORT |
AFX_EXT_API |
#ifdef _AFXEXT AFX_API_EXPORT #else AFX_API_IMPORT |
AFX_EXT_DATA |
#ifdef _AFXEXT AFX_DATA_EXPORT #else AFX_DATA_IMPORT |
6.2 MFC擴展DLL導出MFC派生類 在這個例子中,我們將產生一個名為“ExtDll”的MFC擴展DLL工程,在這個DLL中導出一個對話框類,這個對話框類派生自MFC類CDialog。
使用MFC向導生成MFC擴展DLL時,系統會自動添加如下代碼:
static AFX_EXTENSION_MODULE ExtDllDLL = { NULL, NULL }; extern "C" int APIENTRY
DllMain( HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved ) { // Remove this if you use lpReserved
UNREFERENCED_PARAMETER( lpReserved );
//說明:lpReserved是一個被系統所保留的參數,對于隱式鏈接是一個非零值,對于顯式鏈接值是零
if (dwReason == DLL_PROCESS_ATTACH) { TRACE0( "EXTDLL.DLL Initializing!\n" ); // Extension DLL one-time initialization if ( !AfxInitExtensionModule( ExtDllDLL, hInstance )) return 0; // Insert this DLL into the resource chain new CDynLinkLibrary( ExtDllDLL ); } else if (dwReason == DLL_PROCESS_DETACH) { TRACE0( "EXTDLL.DLL Terminating!\n" ); // Terminate the library before destructors are called AfxTermExtensionModule( ExtDllDLL ); } return 1; // ok } |
這一段代碼含義晦澀,我們需要對其進行解讀:
(1)上述代碼完成MFC擴展DLL的初始化和終止處理;
(2)初始化期間所創建的 CDynLinkLibrary 對象使MFC擴展 DLL 可以將 DLL中的CRuntimeClass 對象或資源導出到應用程序;
(3)AfxInitExtensionModule函數捕獲模塊的CRuntimeClass 結構和在創建 CDynLinkLibrary 對象時使用的對象工廠(COleObjectFactory 對象);
(4)AfxTermExtensionModule函數使 MFC 得以在每個進程與擴展 DLL 分離時(進程退出或使用AfxFreeLibrary卸載DLL時)清除擴展 DLL;
(5)第一條語句static AFX_EXTENSION_MODULE ExtDllDLL = { NULL, NULL };定義了一個AFX_EXTENSION_MODULE類的靜態全局對象,AFX_EXTENSION_MODULE的定義如下:
struct AFX_EXTENSION_MODULE { BOOL bInitialized; HMODULE hModule; HMODULE hResource; CRuntimeClass* pFirstSharedClass; COleObjectFactory* pFirstSharedFactory; }; |
由AFX_EXTENSION_MODULE的定義我們可以更好的理解(2)、(3)、(4)點。
在資源編輯器中添加一個如圖15所示的對話框,并使用MFC類向導為其添加一個對應的類CExtDialog,系統自動添加了ExtDialog.h和ExtDialog.cpp兩個頭文件。
 圖15 MFC擴展DLL中的對話框
|
修改ExtDialog.h中CExtDialog類的聲明為:
class AFX_EXT_CLASS CExtDialog : public CDialog { public: CExtDialog( CWnd* pParent = NULL ); enum { IDD = IDD_DLL_DIALOG }; protected: virtual void DoDataExchange( CDataExchange* pDX ); DECLARE_MESSAGE_MAP() }; |
這其中最主要的改變是我們在class AFX_EXT_CLASS CExtDialog語句中添加了“AFX_EXT_CLASS”宏,則使得DLL中的CExtDialog類被導出。
6.3 MFC擴展DLL的加載
6.3.1 隱式加載
我們在6.2工程所在的工作區中添加一個LoadExtDllDlg工程,用于演示MFC擴展DLL的加載。在LoadExtDllDlg工程中添加一個如圖16所示的對話框,這個對話框上包括一個“調用DLL”按鈕。
 圖16 MFC擴展DLL調用工程中的對話框
|
在與圖16對應對話框類實現文件的頭部添加:
// LoadExtDllDlg.cpp : implementation file //
#include "..\ExtDialog.h" #pragma comment( lib, "ExtDll.lib" )
而“調用DLL”按鈕的單擊事件的消息處理函數為:
void CLoadExtDllDlg::OnDllcallButton() { CExtDialog extDialog; extDialog.DoModal(); } |
當我們單擊“調用DLL”的時候,彈出了如圖15的對話框。
為提供給用戶隱式加載(MFC擴展DLL一般使用隱式加載,具體原因見下節),MFC擴展DLL需要提供三個文件:
(1)描述DLL中擴展類的頭文件;
(2)與動態鏈接庫對應的.LIB文件;
(3)動態鏈接庫.DLL文件本身。
有了這三個文件,應用程序的開發者才可充分利用MFC擴展DLL。
6.3.2 顯示加載
顯示加載MFC擴展DLL應使用MFC全局函數AfxLoadLibrary而不是WIN32 API中的LoadLibrary。AfxLoadLibrary 最終也調用了 LoadLibrary這個API,但是在調用之前進行了線程同步的處理。
AfxLoadLibrary 的函數原型與 LoadLibrary完全相同,為:
HINSTANCE AFXAPI AfxLoadLibrary( LPCTSTR lpszModuleName ); |
與之相對應的是,MFC 應用程序應使用AfxFreeLibrary 而非FreeLibrary 卸載MFC擴展DLL。AfxFreeLibrary的函數原型也與 FreeLibrary完全相同,為:
BOOL AFXAPI AfxFreeLibrary( HINSTANCE hInstLib ); |
如果我們把上例中的“調用DLL”按鈕單擊事件的消息處理函數改為:
void CLoadExtDllDlg::OnDllcallButton() { HINSTANCE hDll = AfxLoadLibrary( "ExtDll.dll" ); if(NULL == hDll) { AfxMessageBox( "MFC擴展DLL動態加載失敗" ); return; }
CExtDialog extDialog; extDialog.DoModal(); AfxFreeLibrary(hDll); } |
則工程會出現link錯誤:
LoadExtDllDlg.obj : error LNK2001: unresolved external symbol "__declspec(dllimport) public: virtual __thiscall CExtDialog::~CExtDialog(void)" (__imp_??1CExtDialog@@UAE@XZ)
LoadExtDllDlg.obj : error LNK2001: unresolved external symbol "__declspec(dllimport) public: __thiscall CExtDialog::CExtDialog(class CWnd *)" (__imp_??0CExtDialog@@QAE@PAVCWnd@@@Z) |
提示CExtDialog的構造函數和析構函數均無法找到!是的,對于派生MFC類的MFC擴展DLL,當我們要在應用程序中使用DLL中定義的派生類時,我們不宜使用動態加載DLL的方法。
6.4 MFC擴展DLL加載MFC擴展DLL 我們可以在MFC擴展DLL中再次使用MFC擴展DLL,但是,由于在兩個DLL中對于AFX_EXT_CLASS、AFX_EXT_API、AFX_EXT_DATA宏的定義都是輸出,這會導致調用的時候出現問題。
我們將會在調用MFC擴展DLL的DLL中看到link錯誤:
error LNK2001: unresolved external symbol …....... |
因此,在調用MFC擴展DLL的MFC擴展DLL中,在包含被調用DLL的頭文件之前,需要臨時重新定義AFX_EXT_CLASS的值。下面的例子顯示了如何實現:
//臨時改變宏的含義“輸出”為“輸入”
#undef AFX_EXT_CLASS #undef AFX_EXT_API #undef AFX_EXT_DATA #define AFX_EXT_CLASS AFX_CLASS_IMPORT #define AFX_EXT_API AFX_API_IMPORT #define AFX_EXT_DATA AFX_DATA_IMPORT
//包含被調用MFC擴展DLL的頭文件
#include "CalledDLL.h"
//恢復宏的含義為輸出
#undef AFX_EXT_CLASS #undef AFX_EXT_API #undef AFX_EXT_DATA #define AFX_EXT_CLASS AFX_CLASS_EXPORT #define AFX_EXT_API AFX_API_EXPORT #define AFX_EXT_DATA AFX_DATA_EXPORT |
6.5 MFC擴展DLL導出函數和變量
MFC擴展DLL導出函數和變量的方法也十分簡單,下面我們給出一個簡單的例子。
我們在MFC向導生成的MFC擴展DLL工程中添加gobal.h和global.cpp兩個文件:
//global.h:MFC擴展DLL導出變量和函數的聲明
extern "C" { int AFX_EXT_DATA total; //導出變量 int AFX_EXT_API add( int x, int y ); //導出函數 }
//global.cpp:MFC擴展DLL導出變量和函數定義
#include "StdAfx.h" #include "global.h"
extern "C" int total; int add(int x,int y) { total = x + y; return total; } |
編寫一個簡單的控制臺程序來調用這個MFC擴展DLL:
#include <iostream.h> #include <afxver_.h>
//AFX_EXT_DATA、AFX_EXT_API宏的定義在afxver_.h頭文件中
#pragma comment ( lib, "ExtDll.lib" ) #include "..\global.h"
int main(int argc, char* argv[]) { cout << add(2,3) << endl; cout << total; return 0; } |
運行程序,在控制臺上看到:
5
5
另外,在Visual C++下建立MFC擴展DLL時,MFC DLL向導會自動生成.def文件。因此,對于函數和變量,我們除了可以利用AFX_EXT_DATA、AFX_EXT_API宏導出以外,在.def文件中定義導出也是一個很好的辦法。與之相比,在.def文件中導出類卻較麻煩。通常需要從工程生成的.map文件中獲得類的所有成員函數被C++編譯器更改過的標識符,并且在.def文件中導出這些“奇怪”的標識符。因此,MFC擴展DLL通常以AFX_EXT_CLASS宏直接聲明導出類。
6.6 MFC擴展DLL的應用 上述各小節所舉MFC擴展DLL的例子均只是為了說明某方面的問題,沒有真實地體現“MFC擴展” 的內涵,譬如6.2派生自CDialog的類也不具備比CDialog更強的功能。MFC擴展DLL的真實內涵體現在它提供的類雖然派生自MFC類,但是提供了比MFC類更強大的功能、更豐富的接口。下面我們來看一個具體的例子(
單擊此處下載本工程)。
我們知道static控件所對應的CStatic類不具備設置背景和文本顏色的接口,這使得我們不能在對話框或其它用戶界面上自由靈活地修改static控件的顏色風格,因此我們需要一個提供了SetBackColor和SetTextColor接口的CStatic派生類CMultiColorStatic。
這個類的聲明如下:
class AFX_EXT_CLASS CMultiColorStatic : public CStatic { // Construction
public: CMultiColorStatic(); virtual ~CMultiColorStatic(); // Attributes protected: CString m_strCaption; COLORREF m_BackColor; COLORREF m_TextColor; // Operations public: void SetTextColor( COLORREF TextColor ); void SetBackColor( COLORREF BackColor ); void SetCaption( CString strCaption );
// Generated message map functions protected: afx_msg void OnPaint(); DECLARE_MESSAGE_MAP() }; |
在這個類的實現文件中,我們需要為它提供WM_PAINT消息的處理函數(這是因為顏色的設置依賴于WM_PAINT消息):
BEGIN_MESSAGE_MAP(CMultiColorStatic, CStatic)
//{{AFX_MSG_MAP(CMultiColorStatic) ON_WM_PAINT() //為這個類定義WM_PAINT消息處理函數 //}}AFX_MSG_MAP END_MESSAGE_MAP() |
下面是這個類中的重要成員函數:
//為CMultiColorStatic類添加“設置文本顏色”接口
void CMultiColorStatic::SetTextColor( COLORREF TextColor ) { m_TextColor = TextColor; //設置文字顏色 }
//為CMultiColorStatic類添加“設置背景顏色”接口
void CMultiColorStatic::SetBackColor( COLORREF BackColor ) { m_BackColor = BackColor; //設置背景顏色 }
//為CMultiColorStatic類添加“設置標題”接口
void CMultiColorStatic::SetCaption( CString strCaption ) { m_strCaption = strCaption; }
//重畫Static,顏色和標題的設置都依賴于這個函數
void CMultiColorStatic::OnPaint() { CPaintDC dc(this); // device context for painting CRect rect; GetClientRect( &rect ); dc.SetBkColor( m_BackColor ); dc.SetBkMode( TRANSPARENT ); CFont *pFont = GetParent()->GetFont();//得到父窗體的字體 CFont *pOldFont; pOldFont = dc.SelectObject( pFont );//選用父窗體的字體 dc.SetTextColor( m_TextColor );//設置文本顏色 dc.DrawText( m_strCaption, &rect, DT_CENTER );//文本在Static中央 dc.SelectObject( pOldFont ); } |
為了驗證CMultiColorStatic類,我們制作一個基于對話框的應用程序,它包含一個如圖17所示的對話框。該對話框上包括一個static控件和三個按鈕,這三個按鈕可分別把static控件設置為“紅色”、“藍色”和“綠色”。
 圖17 擴展的CStatic類調用演示
|
下面看看應如何編寫與這個對話框對應的類。
包含這種Static的對話框類的聲明如下:
#include "..\MultiColorStatic.h" #pragma comment ( lib, "ColorStatic.lib" )
// CCallDllDlg dialog
class CCallDllDlg : public CDialog { public: CCallDllDlg(CWnd* pParent = NULL); // standard constructor enum { IDD = IDD_CALLDLL_DIALOG }; CMultiColorStatic m_colorstatic; //包含一個CMultiColorStatic的實例 protected: virtual void DoDataExchange(CDataExchange* pDX);//DDX/DDV support HICON m_hIcon;
// Generated message map functions //{{AFX_MSG(CCallDllDlg)
virtual BOOL OnInitDialog(); afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); afx_msg void OnRedButton(); afx_msg void OnBlueButton(); afx_msg void OnGreenButton(); //}}AFX_MSG DECLARE_MESSAGE_MAP() }; |
下面是這個類中與使用CMultiColorStatic相關的主要成員函數:
void CCallDllDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CCallDllDlg) DDX_Control(pDX, IDC_COLOR_STATIC, m_colorstatic); //使m_colorstatic與IDC_COLOR_STATIC控件關聯 //}}AFX_DATA_MAP }
BOOL CCallDllDlg::OnInitDialog() { … // TODO: Add extra initialization here
// 初始static控件的顯示
m_colorstatic.SetCaption("最開始為黑色"); m_colorstatic.SetTextColor(RGB(0,0,0)); return TRUE; // return TRUE unless you set the focus to a control }
//設置static控件文本顏色為紅色
void CCallDllDlg::OnRedButton() { m_colorstatic.SetCaption( "改變為紅色" ); m_colorstatic.SetTextColor( RGB( 255, 0, 0 ) ); Invalidate( TRUE ); //導致發出WM_PAINT消息 }
//設置static控件文本顏色為藍色
void CCallDllDlg::OnBlueButton() { m_colorstatic.SetCaption( "改變為藍色" ); m_colorstatic.SetTextColor( RGB( 0, 0, 255 ) ); Invalidate( TRUE ); //導致發出WM_PAINT消息 }
//設置static控件文本顏色為綠色
void CCallDllDlg::OnGreenButton() { m_colorstatic.SetCaption( "改變為綠色" ); m_colorstatic.SetTextColor( RGB(0,255,0) ); Invalidate( TRUE ); //導致發出WM_PAINT消息 } |
至此,我們已經講解完成了所有類型的動態鏈接庫,即非MFC DLL、MFC規則DLL和MFC擴展DLL。下一節將給出DLL的三個工程實例,與讀者朋友們共同體會DLL的應用范圍和使用方法。