前面咱们对非MFC DLL进行了介绍,这一节将详细地讲述MFC规则DLL的建立与使用技巧。html
另外,自从本文开始连载后,收到了一些读者的e-mail。有的读者提出了一些问题,笔者将在本文的最后一次连载中选取其中的典型问题进行解答。因为时间的关系,对于读者朋友的来信,笔者暂时不能一一回复,还望海涵!因为笔者的水平有限,文中不免有错误和纰漏,也热诚欢迎读者朋友不吝指正!编程
5. MFC规则DLL网络
5.1 概述框架
MFC规则DLL的概念体如今两方面:socket
(1) 它是MFC的函数
“是MFC的”意味着能够在这种DLL的内部使用MFC;工具
(2) 它是规则的this
“是规则的”意味着它不一样于MFC扩展DLL,在MFC规则DLL的内部虽然可使用MFC,可是其与应用程序的接口不能是MFC。而MFC扩展DLL与应用程序的接口能够是MFC,能够从MFC扩展DLL中导出一个MFC类的派生类。spa
Regular DLL可以被全部支持DLL技术的语言所编写的应用程序调用,固然也包括使用MFC的应用程序。在这种动态链接库中,包含一个从CWinApp继承下来的类,DllMain函数则由MFC自动提供。操作系统
Regular DLL分为两类:
(1)静态连接到MFC 的规则DLL
静态连接到MFC的规则DLL与MFC库(包括MFC扩展 DLL)静态连接,将MFC库的代码直接生成在.dll文件中。在调用这种DLL的接口时,MFC使用DLL的资源。所以,在静态连接到MFC 的规则DLL中不须要进行模块状态的切换。
使用这种方法生成的规则DLL其程序较大,也可能包含重复的代码。
(2)动态连接到MFC 的规则DLL
动态连接到MFC 的规则DLL 能够和使用它的可执行文件同时动态连接到 MFC DLL 和任何MFC扩展 DLL。在使用了MFC共享库的时候,默认状况下,MFC使用主应用程序的资源句柄来加载资源模板。这样,当DLL和应用程序中存在相同ID的资源时(即所谓的资源重复问题),系统可能不能得到正确的资源。所以,对于共享MFC DLL的规则DLL,咱们必须进行模块切换以使得MFC可以找到正确的资源模板。
咱们能够在Visual C++中设置MFC规则DLL是静态连接到MFC DLL仍是动态连接到MFC DLL。如图8,依次选择Visual C++的project -> Settings -> General菜单或选项,在Microsoft Foundation Classes中进行设置。
图8 设置动态/静态连接MFC DLL
5.2 MFC规则DLL的建立
咱们来一步步讲述使用MFC向导建立MFC规则DLL的过程,首先新建一个project,如图9,选择project的类型为MFC AppWizard(dll)。点击OK进入如图10所示的对话框。
图9 MFC DLL工程的建立
图10所示对话框中的1区选择MFC DLL的类别。
2区选择是否支持automation(自动化)技术, automation 容许用户在一个应用程序中操纵另一个应用程序或组件。例如,咱们能够在应用程序中利用 Microsoft Word 或Microsoft Excel的工具,而这种使用对用户而言是透明的。自动化技术能够大大简化和加快应用程序的开发。
3区选择是否支持Windows Sockets,当选择此项目时,应用程序能在 TCP/IP 网络上进行通讯。 CWinApp派生类的InitInstance成员函数会初始化通信端的支持,同时工程中的StdAfx.h文件会自动include <AfxSock.h>头文件。
添加socket通信支持后的InitInstance成员函数以下:
BOOL CRegularDllSocketApp::InitInstance() { if (!AfxSocketInit()) { AfxMessageBox(IDP_SOCKETS_INIT_FAILED); return FALSE; } return TRUE; }
图10 MFC DLL的建立选项
5.3 一个简单的MFC规则DLL
这个DLL的例子(属于静态连接到MFC 的规则DLL)中提供了一个如图11所示的对话框。
图11 MFC规则DLL例子
在DLL中添加对话框的方式与在MFC应用程序中是同样的。
在图11所示DLL中的对话框的Hello按钮上点击时将MessageBox一个“Hello,pconline的网友”对话框,下面是相关的文件及源代码,其中删除了MFC向导自动生成的绝大多数注释(下载本工程附件):
第一组文件:CWinApp继承类的声明与实现
// RegularDll.h : main header file for the REGULARDLL DLL #if !defined(AFX_REGULARDLL_H__3E9CB22B_588B_4388_B778_B3416ADB79B3__INCLUDED_) #define AFX_REGULARDLL_H__3E9CB22B_588B_4388_B778_B3416ADB79B3__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #ifndef __AFXWIN_H__ #error include 'stdafx.h' before including this file for PCH #endif #include "resource.h" // main symbols class CRegularDllApp : public CWinApp { public: CRegularDllApp(); DECLARE_MESSAGE_MAP() }; #endif // RegularDll.cpp : Defines the initialization routines for the DLL. #include "stdafx.h" #include "RegularDll.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif BEGIN_MESSAGE_MAP(CRegularDllApp, CWinApp) END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CRegularDllApp construction CRegularDllApp::CRegularDllApp() { } ///////////////////////////////////////////////////////////////////////////// // The one and only CRegularDllApp object CRegularDllApp theApp;
在这一组文件中定义了一个继承自CWinApp的类CRegularDllApp,并同时定义了其的一个实例theApp。乍一看,您会觉得它是一个MFC应用程序,由于MFC应用程序也包含这样的在工程名后添加“App”组成类名的类(并继承自CWinApp类),也定义了这个类的一个全局实例theApp。
咱们知道,在MFC应用程序中CWinApp取代了SDK程序中WinMain的地位,SDK程序WinMain所完成的工做由CWinApp的三个函数完成:
virtual BOOL InitApplication( );
virtual BOOL InitInstance( );
virtual BOOL Run( ); //传说中MFC程序的“活水源头”
可是MFC规则DLL并非MFC应用程序,它所继承自CWinApp的类不包含消息循环。这是由于,MFC规则DLL不包含CWinApp::Run机制,主消息泵仍然由应用程序拥有。若是DLL 生成无模式对话框或有本身的主框架窗口,则应用程序的主消息泵必须调用从DLL 导出的函数来调用PreTranslateMessage成员函数。
另外,MFC规则DLL与MFC 应用程序中同样,须要将全部 DLL中元素的初始化放到InitInstance 成员函数中。
第二组文件 自定义对话框类声明及实现(点击查看附件)
分析:
这一部分的编程与通常的应用程序根本没有什么不一样,咱们照样能够利用MFC类向导来自动为对话框上的控件添加事件。MFC类向导照样会生成相似ON_BN_CLICKED(IDC_HELLO_BUTTON, OnHelloButton)的消息映射宏。
第三组文件 DLL中的资源文件
//{{NO_DEPENDENCIES}} // Microsoft Developer Studio generated include file. // Used by RegularDll.rc // #define IDD_DLL_DIALOG 1000 #define IDC_HELLO_BUTTON 1000
分析:
在MFC规则DLL中使用资源也与在MFC应用程序中使用资源没有什么不一样,咱们照样能够用Visual C++的资源编辑工具进行资源的添加、删除和属性的更改。
第四组文件 MFC规则DLL接口函数
#include "StdAfx.h" #include "DllDialog.h" extern "C" __declspec(dllexport) void ShowDlg(void) { CDllDialog dllDialog; dllDialog.DoModal(); }
分析:
这个接口并不使用MFC,可是在其中却能够调用MFC扩展类CdllDialog的函数,这体现了“规则”的概类。
与非MFC DLL彻底相同,咱们可使用__declspec(dllexport)声明或在.def中引出的方式导出MFC规则DLL中的接口。
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(); }
咱们照样能够在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中的对话框
图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中的接口函数改成最简单的:
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进行详细分析和实例讲解,欢迎您继续关注本系列连载。
引自:pconline