產生這個問題的根源在於應用程序與MFC規則DLL共享MFC DLL(或MFC擴展DLL)的程序總是默認使用EXE的資源,我們必須進行資源模塊句柄的切換,其實現方法有三:
方法一 在DLL接口函數中使用:
AFX_MANAGE_STATE(AfxGetStaticModuleState());
我們將DLL中的接口函數ShowDlg改爲:
void ShowDlg(void)
{
//方法1:在函數開始處變更,在函數結束時恢復
//將AFX_MANAGE_STATE(AfxGetStaticModuleState());作爲接口函數的第一//條語句進行模塊狀態切換
AFX_MANAGE_STATE(AfxGetStaticModuleState());
CDialog dlg(IDD_DLL_DIALOG);//打開ID爲2000的對話框
dlg.DoModal();
}
這次我們再點擊EXE程序中的“調用DLL”按鈕,彈出的是DLL中的如圖13的對話框!嘿嘿,彈出了正確的對話框資源。
AfxGetStaticModuleState是一個函數,其原型爲:
AFX_MODULE_STATE* AFXAPI AfxGetStaticModuleState( );
該函數的功能是在棧上(這意味着其作用域是局部的)創建一個AFX_MODULE_STATE類(模塊全局數據也就是模塊狀態)的實例,對其進行設置,並將其指針pModuleState返回。
AFX_MODULE_STATE類的原型如下:
// AFX_MODULE_STATE (global data for a module)
class AFX_MODULE_STATE : public CNoTrackObject
{
public:
#ifdef _AFXDLL
AFX_MODULE_STATE(BOOL bDLL, WNDPROC pfnAfxWndProc, DWORD dwVersion);
AFX_MODULE_STATE(BOOL bDLL, WNDPROC pfnAfxWndProc, DWORD dwVersion,BOOL bSystem);
#else
AFX_MODULE_STATE(BOOL bDLL);
#endif
~AFX_MODULE_STATE();
CWinApp* m_pCurrentWinApp;
HINSTANCE m_hCurrentInstanceHandle;
HINSTANCE m_hCurrentResourceHandle;
LPCTSTR m_lpszCurrentAppName;
… //省略後面的部分
}
AFX_MODULE_STATE類利用其構造函數和析構函數進行存儲模塊狀態現場及恢復現場的工作,類似彙編中call指令對pc指針和sp寄存器的保存與恢復、中斷服務程序的中斷現場壓棧與恢復以及操作系統線程調度的任務控制塊保存與恢復。
許多看似不着邊際的知識點居然有驚人的相似!
AFX_MANAGE_STATE是一個宏,其原型爲:
AFX_MANAGE_STATE( AFX_MODULE_STATE* pModuleState )
該宏用於將pModuleState設置爲當前的有效模塊狀態。當離開該宏的作用域時(也就離開了pModuleState所指向棧上對象的作用域),先前的模塊狀態將由AFX_MODULE_STATE的析構函數恢復。
方法二 在DLL接口函數中使用:
AfxGetResourceHandle();
AfxSetResourceHandle(HINSTANCE xxx);
AfxGetResourceHandle用於獲取當前資源模塊句柄,而AfxSetResourceHandle則用於設置程序目前要使用的資源模塊句柄。
我們將DLL中的接口函數ShowDlg改爲:
void ShowDlg(void)
{
//方法2的狀態變更
HINSTANCE save_hInstance = AfxGetResourceHandle();
AfxSetResourceHandle(theApp.m_hInstance);
CDialog dlg(IDD_DLL_DIALOG);//打開ID爲2000的對話框
dlg.DoModal();
//方法2的狀態還原
AfxSetResourceHandle(save_hInstance);
}
通過AfxGetResourceHandle和AfxSetResourceHandle的合理變更,我們能夠靈活地設置程序的資源模塊句柄,而方法一則只能在DLL接口函數退出的時候纔會恢復模塊句柄。方法二則不同,如果將ShowDlg改爲:
extern CSharedDllApp theApp; //需要聲明theApp外部全局變量
void ShowDlg(void)
{
//方法2的狀態變更
HINSTANCE save_hInstance = AfxGetResourceHandle();
AfxSetResourceHandle(theApp.m_hInstance);
CDialog dlg(IDD_DLL_DIALOG);//打開ID爲2000的對話框
dlg.DoModal();
//方法2的狀態還原
AfxSetResourceHandle(save_hInstance);
//使用方法2後在此處再進行操作針對的將是應用程序的資源
CDialog dlg1(IDD_DLL_DIALOG); //打開ID爲2000的對話框
dlg1.DoModal();
}
在應用程序主對話框的“調用DLL”按鈕上點擊,將看到兩個對話框,相繼爲DLL中的對話框(圖13)和EXE中的對話框(圖14)。
方法三 由應用程序自身切換
資源模塊的切換除了可以由DLL接口函數完成以外,由應用程序自身也能完成。
現在我們把DLL中的接口函數改爲最簡單的:
void ShowDlg(void)
{
CDialog dlg(IDD_DLL_DIALOG); //打開ID爲2000的對話框
dlg.DoModal();
}
而將應用程序的OnCalldllButton函數改爲:
void CSharedDllCallDlg::OnCalldllButton()
{
//方法3:由應用程序本身進行狀態切換
//獲取EXE模塊句柄
HINSTANCE exe_hInstance = GetModuleHandle(NULL);
//或者HINSTANCE exe_hInstance = AfxGetResourceHandle();
//獲取DLL模塊句柄
HINSTANCE dll_hInstance = GetModuleHandle("SharedDll.dll");
AfxSetResourceHandle(dll_hInstance); //切換狀態
ShowDlg(); //此時顯示的是DLL的對話框
AfxSetResourceHandle(exe_hInstance); //恢復狀態
//資源模塊恢復後再調用ShowDlg
ShowDlg(); //此時顯示的是EXE的對話框
}
方法三中的Win32函數GetModuleHandle可以根據DLL的文件名獲取DLL的模塊句柄。如果需要得到EXE模塊的句柄,則應調用帶有Null參數的GetModuleHandle。
方法三與方法二的不同在於方法三是在應用程序中利用AfxGetResourceHandle和AfxSetResourceHandle進行資源模塊句柄切換的。同樣地,在應用程序主對話框的“調用DLL”按鈕上點擊,也將看到兩個對話框,相繼爲DLL中的對話框(圖13)和EXE中的對話框(圖14)。
在下一節我們將對MFC擴展DLL進行詳細分析和實例講解,歡迎您繼續關注本系列連載。