我们照样可以在EXE程序中隐式调用MFC规则DLL,只需要将DLL工程生成的.lib文件和.dll文件拷入当前工程所在的目录,并在RegularDllCallDlg.cpp文件(上一节中图5所示对话框类的实现文件)的顶部添加:
#pragma comment(lib,"RegularDll.lib") void ShowDlg(void);
并将void CRegularDllCallDlg::OnCalldllButton()
改为:void CRegularDllCallDlg::OnCalldllButton() { ShowDlg(); }
共享MFC DLL的规则DLL的模块切换
应用程序进程本身及其调用的每个DLL模块都具有一个全局唯一的HINSTANCE句柄,它们代表了DLL或EXE模块在进程虚拟空间中的起始地址。进程本身的模块句柄一般为0x400000,而DLL模块的缺省句柄为0x10000000。如果程序同时加载了多个DLL,则每个DLL模块都会有不同的HINSTANCE。应用程序在加载DLL时对其进行了重定位。
共享MFC DLL(或MFC扩展DLL)的规则DLL涉及到HINSTANCE句柄问题,HINSTANCE句柄对于加载资源特别重要。EXE和DLL都有其自己的资源,而且这些资源的ID可能重复,应用程序需要通过资源模块的切换来找到正确的资源。如果应用程序需要来自于DLL的资源,就应将资源模块句柄指定为DLL的模块句柄;如果需要EXE文件中包含的资源,就应将资源模块句柄指定为EXE的模块句柄。
这次我们创建一个动态链接到MFC DLL的规则DLL,在其中包含如图6的对话框。
图6 DLL中的对话框
另外,在与这个DLL相同的工作区中生成一个基于对话框的MFC程序,其对话框与上节中图5完全一样。但是在此工程中我们另外添加了一个如图7的对话框。
图7 EXE中的对话框
图6和图7中的对话框除了caption不同(以示区别)以外,其它的都相同。
尤其值得特别注意,在DLL和EXE中我们对图6和图7的对话框使用了相同的资源ID=2000,在DLL和EXE工程的resource.h中分别有如下的宏:
- //DLL中对话框的ID
- #define IDD_DLL_DIALOG 2000
- //EXE中对话框的ID
- #define IDD_EXE_DIALOG 2000
与上一节中静态链接MFC DLL的规则DLL相同,我们还是在规则DLL中定义接口函数ShowDlg,原型如下:
- #include "StdAfx.h"
- #include "SharedDll.h"
- void ShowDlg(void)
- {
- CDialog dlg(IDD_DLL_DIALOG); //打开ID为2000的对话框
- dlg.DoModal();
- }
而为应用工程主对话框的“调用DLL”的单击事件添加如下消息处理函数:
- void CSharedDllCallDlg::OnCalldllButton()
- {
- ShowDlg();
- }
我们以为单击“调用DLL”会弹出如图6所示DLL中的对话框,可是可怕的事情发生了,我们看到是图7所示EXE中的对话框!
产生这个问题的根源在于应用程序与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中的如图6的对话框!嘿嘿,弹出了正确的对话框资源。
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中的对话框(图6)和EXE中的对话框(图7)。
方法三 由应用程序自身切换
资源模块的切换除了可以由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中的对话框(图6)和EXE中的对话框(图7)。
后面一节中将为大家详细讲解和实例剖析MFC扩展DLL,欢迎继续关注。