VC++动态链接库(DLL)编程深入浅出之三(2)

来源:互联网 发布:mac上卸载搜狗输入法 编辑:程序博客网 时间:2024/05/16 14:48

5.4 MFC规则DLL的调用

  笔者编写了如图12的对话框MFC程序(下载本工程附件)来调用5.3节的MFC规则DLL,在这个程序的对话框上点击“调用DLL”按钮时弹出5.3节MFC规则DLL中的对话框。


图12 MFC规则DLL的调用例子


  下面是“调用DLL”按钮单击事件的消息处理函数:

void CRegularDllCallDlg::OnCalldllButton()
{
 typedef void (*lpFun)(void);
 HINSTANCE hDll; //DLL句柄
 hDll = LoadLibrary("RegularDll.dll");
 if (NULL==hDll)
 {
  MessageBox("DLL加载失败");
 }

 lpFun addFun; //函数指针
 lpFun pShowDlg = (lpFun)GetProcAddress(hDll,"ShowDlg");
 if (NULL==pShowDlg)
 {
  MessageBox("DLL中函数寻找失败");
 }
 pShowDlg();
}


  上述例子中给出的是显示调用的方式,可以看出,其调用方式与第4节中非MFC DLL的调用方式没有什么不同。

  我们照样可以在EXE程序中隐式调用MFC规则DLL,只需要将DLL工程生成的.lib文件和.dll文件拷入当前工程所在的目录,并在RegularDllCallDlg.cpp文件(图12所示对话框类的实现文件)的顶部添加:

#pragma comment(lib,"RegularDll.lib")
void ShowDlg(void);


  并将void CRegularDllCallDlg::OnCalldllButton() 改为:

void CRegularDllCallDlg::OnCalldllButton()
{
 ShowDlg();
}


  5.5 共享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(下载本工程附件),在其中包含如图13的对话框。


图13 DLL中的对话框


  另外,在与这个DLL相同的工作区中生成一个基于对话框的MFC程序,其对话框与图12完全一样。但是在此工程中我们另外添加了一个如图14的对话框。


图14 EXE中的对话框


  图13和图14中的对话框除了caption不同(以示区别)以外,其它的都相同。

  尤其值得特别注意,在DLL和EXE中我们对图13和图14的对话框使用了相同的资源ID=2000,在DLL和EXE工程的resource.h中分别有如下的宏:

//DLL中对话框的ID

#define IDD_DLL_DIALOG 2000

//EXE中对话框的ID

#define IDD_EXE_DIALOG 2000


  与5.3节静态链接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”会弹出如图13所示DLL中的对话框,可是可怕的事情发生了,我们看到是图14所示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中的如图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)。

原创粉丝点击