在VC++6.0中用MFC进行COM编程
首先应当明确,MFC中是通过嵌套类而不是多重继承来实现COM接口的,通过接口映射机制将接口和实现该接口的嵌套类关联起来;MFC中提供一套简明的宏来实现嵌套类的定义.其次,MFC通过CCmdTarget类实现了IUnknown接口。
本文首先描述创建一个COM服务器的步骤和核心代码.然后说明客户程序关键代码。
此COM服务器实现一个TimeLogServer组件,为简明起见,此组件只有一个接口ITimeLog,通过ITimeLog的方法OutputLog可以将日志文本输出至日志文件。
创建一个MFC DLL工程,选择支持Automation(当然本程序不一定是自动化服务器,在这里这样做好处在于自动实现了几个必要的输出函数如DllGetClassObject,DllRegisterServer等,否则要自己写)
第一节 COM服务器
一. 声明组件和接口
1.写一个GUIDs.h,在GUIDs.h中声明组件和接口的GUID
//声明组件GUID {A433E701-E45E-11d3-97B5-52544CBA7F28} //DEFINE_GUID(CLSID_TimeLogServer, //0xa433e701, 0xe45e, 0x11d3, 0x97, 0xb5, 0x52, 0x54, 0x4c, 0xba, 0x7f, 0x28); static const IID CLSID_TimeLogServer = {0xa433e701, 0xe45e, 0x11d3, {0x97, 0xb5, 0x52, 0x54, 0x4c, 0xba, 0x7f, 0x28}}; // 声明接口GUID{A433E702-E45E-11d3-97B5-52544CBA7F28} //DEFINE_GUID(IID_ITimeLog, //0xa433e702, 0xe45e, 0x11d3, 0x97, 0xb5, 0x52, 0x54, 0x4c, 0xba, 0x7f, 0x28); static const IID IID_ITimeLog = {0xa433e702, 0xe45e, 0x11d3, {0x97, 0xb5, 0x52, 0x54, 0x4c, 0xba, 0x7f, 0x28}}; |
2.写一个ITimeLogServer.h,在ITimeLogServer.h文件中声明组件和接口
//ITimeLogServer.h #include ";GUIDs.h"; //接口ITimeLog的声明 DECLARE_INTERFACE_(ITimeLog,IUnknown) { STDMETHOD(OutputLog)(BSTR* varLogText)PURE; }; |
说明:
1.宏DEFINE_GUID将组件和接口的progid与GUID相关联.可以用guidgen.exe工具产生。
2.宏DECLARE_INTERFACE_声明接口;该宏第一个参数为接口名,第二个参数为该接口的基类.声明没有基类的接口用DECLARE_INTERFACE宏。
3.宏STDMETHOD声明接口中的方法.此方法的返回值为HRESULT.PURE被解释为";=0";,即此方法为纯虚函数.当方法的返回值不是HRESULT时,用宏STDMETHOD_(返回类型,函数名)(参数)PURE;
二.声明组件类CTimeLogServer和实现接口的嵌套类
在ClassWizard中添加新类CTimeLogServer,其基类选择为CCmdTarget.修改其头文件TimeLogServer1.h,加上#include ";ITimeLogServer.h";;同时在类声明体中加上
//声明实现ITimelog接口的嵌套类 BEGIN_INTERFACE_PART(TimeLog,ITimeLog)//自动声明IUnknown接口的三个方法 STDMETHOD(OutputLog)(BSTR* varLogText); END_INTERFACE_PART(TimeLog) //声明接口映射 DECLARE_INTERFACE_MAP() //声明类厂 DECLARE_OLECREATE(CTimeLogServer) |
三.实现类厂和接口映射
在CTimeLogServer的实现文件中写入:
//实现类厂 IMPLEMENT_OLECREATE(CTimeLogServer,";TimeLogServer";, 0xa433e701, 0xe45e, 0x11d3, 0x97, 0xb5, 0x52, 0x54, 0x4c, 0xba, 0x7f, 0x28); //映射接口到相应的嵌套类 BEGIN_INTERFACE_MAP(CTimeLogServer,CCmdTarget) INTERFACE_PART(CTimeLogServer,IID_ITimeLog,TimeLog) END_INTERFACE_MAP() |
四.在组件的构造和析构函数中对全局对象计数
CTimeLogServer::CTimeLogServer() { ::AfxOleLockApp(); } CTimeLogServer::~CTimeLogServer() { ::AfxOleUnlockApp(); } |
五.为嵌套类实现IUnknown接口
//为嵌套类而实现IUnknown接口 STDMETHODIMP_(ULONG) CTimeLogServer::XTimeLog::AddRef() { METHOD_PROLOGUE(CTimeLogServer,TimeLog) return pThis->;ExternalAddRef(); } STDMETHODIMP_(ULONG) CTimeLogServer::XTimeLog::Release() { METHOD_PROLOGUE(CTimeLogServer,TimeLog) return pThis->;ExternalRelease(); } STDMETHODIMP CTimeLogServer::XTimeLog::QueryInterface(REFIID riid,void**ppvObj) { METHOD_PROLOGUE(CTimeLogServer,TimeLog) return pThis->;ExternalQueryInterface(&;riid,ppvObj); } |
说明:虽然CCmdTarget类已经实现了IUnknown接口,但是还必须通过上述代码来将嵌套类的IUnknown映射到CCmdTarget支持的IUnknown接口.METHOD_PROLOGUEH宏的两个参数分别是实现组件对象的类和实现接口的嵌套类。
六.实现ItimeLog接口的方法OutputLog
注意本组件的功能是往日志文件中输入日志.
1. 在组件类中添加一个文件指针:
// Attributes public: protected: FILE* m_logfile; |
2. 初始化和退出
首先在CTimeLogServer的构造函数中进行一些初始化:
CTimeLogServer::CTimeLogServer() { ::AfxOleLockApp(); CTime TimeStamp = CTime::GetCurrentTime(); CString FileName; FileName.Format(_T(";%s.log";),TimeStamp.Format(";%Y%m%d";)); m_logfile = fopen(FileName,_T(";a";)); if(m_logfile) { fprintf(m_logfile,_T(";# # # # # # # # # # # # # # # # # \n";)); fprintf(m_logfile,_T(";开始于:%s";),(LPCTSTR)TimeStamp.Format(";%Y年%m月%d日%H:%M %S";)); fprintf(m_logfile,_T(";\n";)); } } //然后在析构函数中关闭文件 CTimeLogServer::~CTimeLogServer() { ::AfxOleUnlockApp(); if(m_logfile) { CTime TimeStamp = CTime::GetCurrentTime(); fprintf(m_logfile,_T(";\n";)); fprintf(m_logfile,_T(";结束于:%s";),(LPCTSTR)TimeStamp.Format(";%Y年%m月%d日%H:%M %S";)); fprintf(m_logfile,_T(";\n";)); fprintf(m_logfile,_T(";# # # # # # # # # # # # # # # # #\n";)); fclose(m_logfile); } } |
3. 实现接口ITimeLog方法
//实现接口ITimeLog方法 STDMETHODIMP CTimeLogServer::XTimeLog::OutputLog(BSTR* varLogText) { METHOD_PROLOGUE(CTimeLogServer,TimeLog) if(pThis->;m_logfile) { CTime TimeStamp = CTime::GetCurrentTime(); CString NowTime = TimeStamp.Format(";%Y年%m月%d日%H:%M:%S";); CString LogText((LPCWSTR)*varLogText); fprintf(pThis->;m_logfile,";\n%s\n%s\n%";,NowTime,LogText); return NOERROR; } else { AfxMessageBox(";没有日志文件!";); return S_FALSE; } } |
七.完善组件服务器
在应用对象CTimeLogServerApp的 实现文件中,处理Instance()和ExitInstance()
BOOL CTimeLogServerApp::InitInstance() { ::AfxOleLockApp(); // Register all OLE server (factories) as running. This enables the // OLE libraries to create objects from other applications. COleObjectFactory::RegisterAll(); return TRUE; } int CTimeLogServerApp::ExitInstance() { // TODO: Add your specialized code here and/or call the base class ::AfxOleUnlockApp(); return CWinApp::ExitInstance(); } |
第二节 客户程序
使用COM组件服务器的客户程序关键步骤是:初始化COM库,创建组件对象并获取IUnknown接口指针,查询接口并使用,释放组件。
#include ";ITimeLogServer.h"; //初始化COM库,对组件实例化 HRESULT hResult; IUnknown* pIUnknown; hResult = ::CoInitialize(NULL); if(FAILED(hResult)) { ::AfxMessageBox(";不能初始化COM库!";); return FALSE; }
//创建组件实例 pIUnknown = NULL; hResult = ::CoCreateInstance(CLSID_TimeLogServer,NULL, CLSCTX_INPROC_SERVER,IID_IUnknown,(void**)&;pIUnknown); if(FAILED(hResult)) { pIUnknown = NULL; ::AfxMessageBox(";不能创建TimeLog对象!";); return FALSE; } //查询接口并使用 if(pIUnknown!=NULL) { ITimeLog* pITimeLog; HResult=pIUnknown->;QueryInterface(IID_ITimeLog,(void**)&;pITimeLog); if(FAILED(hResult)) { ::AfxMessageBox(";不能获取接口ITimeLog!";); pIUnknown->;Release(); return; } BSTR bstrLogText; bstrLogText = m_logtext.AllocSysString(); CString text((LPCWSTR)bstrLogText); ::AfxMessageBox(text);
if(FAILED(pITimeLog->;OutputLog(&;bstrLogText))) { ::AfxMessageBox(";日志输出出错!";); pITimeLog->;Release(); return; } pITimeLog->;Release(); ::AfxMessageBox(";日志已经写入!";); } //释放组件 pIUnknown->;Release(); pIUnknown = NULL; ::CoUninitialize(); |
应用MFC开发高级应用程序
[摘要]:目前在Windows下开发应用程序的工具虽然很多,但是C/C++作为一种非常成熟和高效的开发语言在大型复杂项目的开发中仍然得到了广泛应用。为了减轻程序开发负担,提高开发效率,各种流行的C++都提供了类库,本文就是针对如何在Visual C++环境中使用MFC类库来开发高级程序所需要解决的一些问题进行了的探讨,重点讨论了利用MFC开发单文档多视应用程序和DDE应用程序的方法。
一、使用C/C++
随着Windows系列操作系统的日益普遍,传统的基于DOS编程逐渐转向Windows下编程已经成为必然趋势。目前在Windows下开发应用程序的工具很多,典型的如Borland C++、Visual C++、Visual Baisic以及Delphi等等。每种开发工具都各有其特点,一般来讲用户可以根据自己的使用习惯和开发项目的性质来选择具体的开发语言。
Visual Basic是一个被软件界称之为划时代的革新产品,该软件改变了人们开发Windows程序的方式,它采用交互式的可视化操作,使得人们开发Windows程序的每一过程都有直观形象的反馈,从而加速整个开发进程。Visual Basic使得Windows程序设计人员不再只依赖于复杂的SDK编程,使得开发Windows程序非常容易,可以说,用户学习并使用VB来开发Windows应用的时间是最短的。Visual Basic版本几经演变,目前已经发展到5.0。在4.0版本中,由于完全使用了面向对象的编程概念,同时具有Windows 3.1和Windows 95下的版本,因而使得其开发复杂程序的功能逐渐增强。VB5.0则抛弃了Windows 3.x的用户,只能在32位Windows中使用,据悉,该版本吸收了Delphi的成功策略,引入了本地代码(Native Code)编译器,从而使得程序执行速度大大加快,克服了以往版本由于执行文件采用P-Code代码而导致运行速度慢的特点,根据微软的声明,该版本的采用本地代码编译后得到的应用程序在某些情况下执行速度较以往提高了10~20倍,执行速度可以直逼与采用Visual C++编写的应用,而应用开发速度则是VB的强项,因此Visual Basic 5.0非常具有竞争性。目前Visual Basic非常广泛地用于开发各种Windows程序,如数据库前端应用程序和多媒体应用等。但是,在作者看来,采用VB也有一定的缺点,原因有以下几点:
1. Visual Basic来源于Basic语言,虽然经过微软的不断增强,但是仍然缺乏非常灵活的数据类型和编程策略,因而在开发一些项目必须的复杂数据结构遇到麻烦,如链表、图和二叉树等等。由于在中大型项目开发后期,开发工作不再以界面为主,而是在算法设计和底层软硬件工作,这就使VB开发项目的后期工作量大幅度增加,为了达到项目要求,经常需要再转向C/C++开发一些专用的动态连接库来解决问题。
2. Visual Basic运行速度慢,前文讲过,采用P-Code代码虽然执行文件很小,但是在运行时需要解释执行,并且,它的运行必须有对应的VBRUN.DLL和所使用的VBX或者OCX支持。对于浮点操作密集或者循环嵌套很多的应用来说,VB没有采取特别的优化,因而执行速度远不如用C/C++和Fortran开发的应用速度快。VB 5.0虽然通过引入本地代码编译器大大弥补了这个缺陷,但是由于其只能运行于32位Windows环境因而在16位Windows上速度问题仍然得不到解决。虽然目前转向32位Windows的趋势非常强劲,但是不容忽视由于硬件的限制或者使用习惯等诸多原因,还有许多用户仍然在16位Windows上工作。在计算机十分普及的美国,96年使用16位Windows的用户仍然超过了使用32位Windows的用户,任何进行系统软件设计的人员都应该照顾到这些仍然使用16位Windows的用户。
3. VB不能灵活地使用系统资源。熟悉Windows编程的人都知道,如果要直接访问硬件或者要编写对系统进行有效访问的应用程序,没有Windows API函数的帮助则非常困难,但是令VB程序员失望的是,API函数是用C语言和汇编语言实现的,是为C编程准备的,如果要在VB里面使用这些上千个API函数则比较麻烦,特别是,如果设计人员不懂C语言则尤其困难。由于API函数的复杂性,而其本身不是为了方便VB编程而提供的,因此在VB里面调用API函数需要一定的技巧,这些技巧足够用一本很厚的书来表述。VB程序员可以从书店里找到好多本类似的书籍。可以说,任何一个VB程序员发展到一定阶段都需要与众多的API函数打交道。另外,由于VB不支持端口操作,因此,如果要编写类似数据采集等需要与硬件交互的程序则需要求救于C/C++语言。
4. Visual Basic项目分发和管理困难,其原因同上讲的,VB应用的运行不能脱离VB的运行库和所使用的控件,因此,如果开发人员要将VB应用分发给用户那么一定要带上VB的运行库和所使用的控件,并且要保证正确安装,这就导致即使一个非常简单的应用也需要附带大量其它相关支撑库程序,对于VB 4.0及更高版本,由于大量的使用了OLE控件(在VB中称为OCX),其安装更为复杂。
Delphi软件是国际宝兰公司(Borland)的得意之作,也是备受软件界推崇,与VB一样,它完全是一个交互式的可视化开发平台,支持Client/Server应用程序的开发,其最新版本2.0可以开发Windows 3.x、Windows 95和Windows NT的应用程序。Delphi开发速度也非常快,与VB相比,由于具有本地代码编译器因此它产生的可执行文件执行速度大大加快。Delphi软件是一个非常有竞争力的软件,采用的是面向对象的Object pascal语言,支持硬件操作和API调用。但是由于采用的编程语言为Pascal,这种语言并不非常流行,许多程序设计人员完全不熟悉这种语言,因此极大地限制了该软件的使用,如果宝兰公司能够将Delphi软件提供的RAD开发机制引入到其Borland C++中,则可能会形成一个非常成功的产品(目前该版本已经推出,即C++ Builder,笔者注)。
VB和Delphi引入的可视化开发方法还有一个共同的缺点就是各个版本之间的兼容问题。一般来讲,采用这些可视化开发工具开发的应用程序在移植到高版本时不会遇到太大困难,但是一旦往相反方向移植则困难重重,有时甚至不可能。C/C++语言则不具有这种局限性,各个版本之间的相互移植并不困难,高版本往低版本移植一般只需重建工程文件即可大功告成。
综上所述,根据作者的观点,如果要开发一个大型复杂的应用程序首选的还是C/C++,特别是在16位Windows下。虽然这会使前期工作增加,但是在项目的中后期将逐渐会领略到其优越性和开发效率,其灵活高效的执行代码适合于对速度和应用程序之间的协同性要求很高的场合。纯粹基于Windows SDK来开发Windows程序是一项艰巨的工程,值得庆幸的是目前各种流行的C/C++开发工具都提供了类库开发框架来简化整个开发过程而又不失其固有的灵活高效性,不同的开发语言所提供的类库开发框架不同,如Borland C++提供的OWL(Object Windows Library)和 Visual C++提供的MFC(Microsoft Fundmental Class),这两种类库都封装了大量的Windows API和Windows的开发元素而使得开发任务简化,两种类库各有其优点,据作者掌握的资料,采用MFC编写的应用程序执行代码更小,执行速度也更快,这大概是因为该软件的开发者是开发Windows操作系统的Microsoft公司的缘故吧,现在MFC正逐渐成为Windows下的类库开发标准,正被越来越多的其它C/C++编译工具所支持,如Watcom C++。使用MC类库同时配合Visual C++提供的AppWizard、ClassWizard和AppStudio可以大幅度提高开发效率。笔者在工作中积累了一些MFC的使用经验现在提出来供大家参考,希望对广大同行有所帮助,尤其是那些仍然致力于16位Windows编程的程序员。本文使用的Visual C++ 1.51编译器,但是其方法同样适用于其它VC++版本,包括Visual C++ 4.x。
二、MFC编程综述
采用MFC开发Windows程序之所以能够大幅度提高开发速度和效率主要是因为MFC在类层次封装了大量Windows SDK函数和典型Windows应用的缺省处理,这样,用户只需要较少的编程就可以实现自己的开发任务。如果在MFC基础上再配合Visual C++提供的AppWizard、ClassWizard和AppStudio工具那么更可以大幅度加快开发进程。MFC提供大量的基类供程序员使用,常见的如CWinApp类、CFrameWnd类、CMDIFrameWnd类、CMDIChildWnd类、CView类、CDC类和CDocument类等等。通过从这些基类中派生出用户自己的类,然后重载特殊的几个函数就可以生成一个独立的应用程序。可以说,采用MFC编写Windows应用程序是非常方便的,虽然其学习过程并不简单,但是其提供的灵活高效性足以使任何Windows程序开发人员为之付出努力。如果用户不曾使用过MFC,那么用户可以通过附录中所列的参考书去学习MFC的强大功能。
采用MFC应用框架产生的应用程序使用了标准化的结构,因而使得采用MFC编写的程序的在不同平台上的移植变得非常容易,事实上,MFC的16位和32位版本之间差别很小。MFC提供的标准化结构是经过众多专家分析调研后总结编写出来的,一般情况下可以满足绝大多数用户的要求,但有时用户也可以通过重载一些函数来修改其缺省的风格从而实现自己特有的风格,如自定义应用图表和灰色背景等。在MFC提供的文档视结构中,文档、视和资源之间的联系是通过定义文档模板来实现的,如:
m_pSimuTemplate = new CMultiDocTemplate( IDR_SIMUTYPE, RUNTIME_CLASS(CSimuDoc), RUNTIME_CLASS(CMyChild), // Derived MDI child frame RUNTIME_CLASS(CSimuView)); |
上中第一项IDR_SIMUTYPE就包括了视口的菜单,加速键和图表等资源,如果用户使用AppWizard来产生的应用基本框架,那么其也同时产生了缺省的图标,如果用户不满意缺省图标(实际上用户很少满足于缺省图标),只需要将缺省图标删除,然后编辑或者直接引入一个新的图标,在存储这一图标时只需要使用与被删除图标同样的ID即可实现替代。
熟悉Windows程序开发的人都知道,在Windows上通过使用灰色背景可以增强应用程序的视觉效果,曾有人戏称,灰色是图形界面永恒的颜色。使用MFC产生的应用程序的背景缺省为白色,如果用户希望改变成灰色或者其它颜色,那就需要使用单独处理,解决的办法很多,如在每次视口的OnPaint()事件中采用灰色刷子人为填充背景,但是这不是最好的办法。笔者发现最好的办法就是采用AfxRegisterWndClass()函数注册一个使用灰色背景刷的新的窗口类,这需要重载PreCreateWindow()函数来实现这一点,如下程序代码片段所示:
BOOL CSimuView::PreCreateWindow(CREATESTRUCT& cs) { HBRUSH hbkbrush=CreateSolidBrush(RGB(192,192,192));//创建灰色背景刷 LPCSTR lpMyOwnClass=AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW|CS_OWNDC,0,hbkbrush);//注册新类 cs.lpszClass=lpMyOwnClass;//修改缺省的类风格
return TRUE; } |
采用这种方法速度最快,也最省力。同时,还可以在PreCreateWindow()函数定义所希望的任何窗口风格,如窗口大小,光标式样等。
三、使用单文档-多视结构
如果用户使用过MFC进行编程,那么就会发现借助于AppWizard基于MFC无论编写SDI(单文档界面)还是编写MDI(多文档界面)都是十分方便的。MDI应用程序目前使用越来越普遍,人们熟悉的Microsoft公司的Office系列产品以及Visual系列产品都是典型的多文档应用程序。这种多文档界面具有多窗口的特点,因而人们可以在一个程序中使用多个子窗口来实现不同数据的浏览查看。如果用户要实现在MDI各个窗口之间针对同一数据进行不同的可视化就是一件比较麻烦的事情。值得庆幸的是,MFC提供的文档-视结构大大简化了这一工作。文档-视结构通过将数据从用户对数据的观察中分离出来,从而方便实现多视,亦即多个视口针对同一数据,如果一个视口中数据发生改变,那么其它相关视口中的内容也会随之发生改变以反映数据的变化。
SDI和MDI这两种Windows标准应用程序框架并不是总能满足用户的需要,就作者的工作而言,就特别需要一种被称为单文档多视的应用程序,英文可以缩写为SDMV。通过SDMV应用我们可以利用文档类来统一管理应用程序的所有数据,同时需要采用多窗口以多种方式来可视化这些的数据,如棒图,趋势图和参数列表,从而方便用户从不同角度来观察数据。MDI虽然具有多窗口的特点,但是其为多文档,即通常情况下,一个视口对应一个文档,视口+文档便构成一个子窗口。在各个子窗口之间数据相互独立,如果要保持数据同步更新就需要采用特殊的技术了,采用这种方式既费时又费力。通过笔者的实践发现,利用MFC本身提供的多视概念通过适当改造MDI窗口应用程序就可以实现上述SDMV结构。
所谓SDMV应用程序本质上仍然是一个MDI应用程序,只是在程序中我们人为控制使其只能生成一个文档类,这个文档在第一个视口创建时创建,注意,这里并不需要限制各个视口的创建先后顺序。此后与MDI窗口固有特性不同的是,所有新创建的子窗口都不再创建独立文档,而是把该新视口直接连接到已有的文档对象上,这样就使其成为单文档多视的结构,所有相关数据都存储在文档对象中,一旦文挡中数据发生改变,通过UpdateAllViews()函数通知所有相关视口,各个视口就可以在OnUpdate()中相应数据的变化。这种响应机制如下图所示:
由于MDI本质上并不是为这种单文档多视机制服务的,因而在实际应用时需要解决一些问题。
1、窗口标题问题
窗口标题本来不应该成为问题,缺省情况下MDI窗口通过在文档模板中提供的资源ID所提供的对应字符串来确定窗口标题。但是对于SDMV应用,由于各个视口实质上是对应于同一个文挡,因此每个视口都具有相同标题,只不过增加了一个数据用于指示这是第几个视口。如果在各个视口中指明具体的窗口名字,那么由不同的视口启动创建文档产生的窗口标题就不同,这个名字会影响到后继视口。为了作到不同类型的视口如棒图视口和曲线视口具有不同的标题,这就需要一定的技术处理。根据笔者的摸索发现可以采用如下步骤实现:
首先在从标准的MDI子窗口基类CMDIChildWnd派生一个自己的子窗口类,姑且命名为CMyChild,然后在其成员变量中增加一个CString型变量用以存储当前窗口标题:
CString winTitle;
然后在不同的视口创建过程中通过获取父窗口指针按自己的意愿对上述变量进行赋值,程序片段如下:
pChild=(CMyChild*)GetParent(); pChild->winTitle="棒图显示窗口"; |
最后在CMyChild派生类中重载CMDIChildWnd基类中的OnUpdateFrameTitle()函数来强制实现窗口标题的个性化,这一函数在各种类库手册上和联机帮助中都没有,但的确有这样一个具有保护属性的函数用来实现窗口标题的更新操作,这可以从MFC类库的源代码中找到该函数的实现。重载后的源代码如下:
void CMyChild::OnUpdateFrameTitle(BOOL bAddToTitle) { // update our parent window first GetMDIFrame()->OnUpdateFrameTitle(bAddToTitle);
if ((GetStyle() & FWS_ADDTOTITLE) == 0) return; // leave child window alone!
CDocument* pDocument = GetActiveDocument(); if (bAddToTitle && pDocument != NULL) { char szOld[256]; GetWindowText(szOld, sizeof(szOld)); char szText[256];
lstrcpy(szText,winTitle); //Modified by author! if (m_nWindow > 0) wsprintf(szText + lstrlen(szText), ":%d", m_nWindow);
// set title if changed, but don't remove completely if (lstrcmp(szText, szOld) != 0) SetWindowText(szText); } } |
2、如何创建SDMV应用
如何创建SDMV应用比较麻烦,下面通过举例来具体说明。该例子假设用户需要建棒图类型和曲线形式的两种视口,假设用户已经利用CView基类派生并且实现了这两个类,分别对应于CMyChart和CMyTraceView两个类。
1) 在应用类(从CWinApp派生出来的类)的头文件中加入下列变量和函数原型说
明:
CMultiDocTemplate* m_pMyTraceTemplate; CMultiDocTemplate* m_pMyChartTemplate; int ExitInstance(); |
2) 在应用类的InitInstance成员函数中删除对AddDocTemplate函数的调用和OpenFileNew()语句,并且加入如下代码:
m_pMyTraceTemplate = new CMultiDocTemplate( IDR_MYTRACEVIEW, RUNTIME_CLASS(CSimuDoc), RUNTIME_CLASS(CMyChild), // Derived MDI child frame RUNTIME_CLASS(CMyTraceView));
m_pMyChartTemplate = new CMultiDocTemplate( IDR_MYCHART, RUNTIME_CLASS(CSimuDoc), RUNTIME_CLASS(CMyChild), // Derived MDI child frame RUNTIME_CLASS(CMyChart)); |
3) 实现ExitInstance()函数,在其中删除所用的两个辅助模板:
int CTestApp::ExitInstance() { if(m_pMyChartTemplate) delete m_pMyChartTemplate; if(m_pMyTraceTemplate) delete m_pMyTraceTemplate; return TRUE; } |
4) 在菜单资源中去掉File菜单中的New和Open项,加入New Chart View和New Trace View两项,在对应的菜单命令中实现如下:
void CMainFrame::OnNewMychart() { // TODO: Add your command handler code here OnNewView(((CSimuApp*)AfxGetApp())->m_pMyChartTemplate); }
void CMainFrame::OnNewMyTrace() { // TODO: Add your command handler code here OnNewView(((CSimuApp*)AfxGetApp())->m_pMyTraceTemplate); } |
上中OnNewView的实现如下:
BOOL CMainFrame::OnNewView(CMultiDocTemplate* pDocTemplate) { CMDIChildWnd* pActiveChild = MDIGetActive(); CDocument* pDocument; if (pActiveChild == NULL || (pDocument = pActiveChild->GetActiveDocument()) == NULL) { TRACE0("Now New the specify view\n"); ASSERT(pDocTemplate != NULL); ASSERT(pDocTemplate->IsKindOf(RUNTIME_CLASS(CDocTemplate))); pDocTemplate->OpenDocumentFile(NULL); return TRUE; }
// otherwise we have a new frame to the same document! CMultiDocTemplate* pTemplate = pDocTemplate; ASSERT_VALID(pTemplate); CFrameWnd* pFrame = pTemplate->CreateNewFrame(pDocument, pActiveChild); if (pFrame == NULL) { TRACE0("Warning: failed to create new frame\n"); return FALSE; // command failed } pTemplate->InitialUpdateFrame(pFrame, pDocument); return TRUE; } |
OnNewView是整个SDMV应用的核心组成,它的任务是创建一个新的指定类型的视口,它首先判断是否有活动视口存在,文档是否已经创建,正常情况下活动视口存在则表明文档存在,如果不存在则利用所指定的文档模板创建一个新的活动视口,否则则只创建视口,同时将其连接到已存在的文档对象上。
通过以上步骤就可以实现SDMV应用,在其后的具体应用中利用文档对象的UpdateAllViews()函数和视口的OnUpdate()函数就可以很好的工作了。
四、使用DDE服务
Windows 3.x是一个分时多任务操作环境,在此环境下,多个应用程序可以并发地执行。为了在并发执行的多个任务之间共享数据和资源,Windows 提供了几种机制,主要是通过剪贴板(Clipboard)和动态数据交换(Dynamic Data Exchange)。前者对于用户需要直接参与的数据交换来说,是一个非常方便的工具,但是如果希望数据交换自动进行时就必须依靠DDE技术了。编写DDE应用的技术也发展了好几代,从最初的基于消息的DDE到基于DDEML(动态数据交换管理库),再到现在流行的OLE技术。DDE技术的发展使得程序开发人员编写DDE应用更为简洁。从发展趋势来看,基于OLE的数据交换是最好的,它特别符合当今软件领域的客户-服务器机制(Client-Server)。为适应多平台和Internet的需要,在OLE基础上微软又开发了ActiveX技术。但是不容忽视的是,基于传统的DDE数据交换也自有它的应用空间,使用仍然广泛。目前在Windows 3.x下,基于OLE的远程数据交换还很不成熟,但是在WFW(Windows for Workgroup)下基于网络动态数据交换的技术却很成熟,目前也应用非常普遍。关于DDE应用的开发和NetDDE的应用可以参看附录7。
1、回调函数的处理
由于DDEML机制需要使用回调函数,因此使用DDEML的关键是解决在MFC编程体系中回调函数的使用。回调函数(Callback function)大量用于Windows的系统服务,通过它,程序员可以安装设备驱动程序和消息过滤系统,以控制Windows的有效使用。许多程序员都发现,利用MFC或者其它的C++应用编写回调函数是非常麻烦的,其根本原因是回调函数是基于C编程的Windows SDK的技术,不是针对C++的,程序员可以将一个C函数直接作为回调函数,但是如果试图直接使用C++的成员函数作为回调函数将发生错误,甚至编译就不能通过。通过查询资料发现,其错误是普通的C++成员函数都隐含了一个传递函数作为参数,亦即“this”指针,C++通过传递一个指向自身的指针给其成员函数从而实现程序函数可以访问C++的数据成员。这也可以理解为什么C++类的多个实例可以共享成员函数但是确有不同的数据成员。由于this指针的作用,使得将一个CALLBACK型的成员函数作为回调函数安装时就会因为隐含的this指针使得函数参数个数不匹配,从而导致回调函数安装失败。要解决这一问题的关键就是不让this指针起作用,通过采用以下两种典型技术可以解决在C++中使用回调函数所遇到的问题。这种方法具有通用性,适合于任何C++。
1). 不使用成员函数,直接使用普通C函数,为了实现在C函数中可以访问类的成员变量,可以使用友元操作符(friend),在C++中将该C函数说明为类的友元即可。这种处理机制与普通的C编程中使用回调函数一样。
2). 使用静态成员函数,静态成员函数不使用this指针作为隐含参数,这样就可以作为回调函数了。静态成员函数具有两大特点:其一,可以在没有类实例的情况下使用;其二,只能访问静态成员变量和静态成员函数,不能访问非静态成员变量和非静态成员函数。由于在C++中使用类成员函数作为回调函数的目的就是为了访问所有的成员变量和成员函数,如果作不到这一点将不具有实际意义。解决的办法也很简单,就是使用一个静态类指针作为类成员,通过在类创建时初始化该静态指针,如pThis=this,然后在回调函数中通过该静态指针就可以访问所有成员变量和成员函数了。这种处理办法适用于只有一个类实例的情况,因为多个类实例将共享静态类成员和静态成员函数,这就导致静态指针指向最后创建的类实例。为了避免这种情况,可以使用回调函数的一个参数来传递this指针,从而实现数据成员共享。这种方法稍稍麻烦,这里就不再赘述。
2、在MFC中使用DDEML
对于典型的MFC应用程序,主框架窗口类(CMainFrame)只有一个实例,因此可以使用静态成员函数作为回调函数,从而实现DDE机制。具体的代码片段如下:
(1) 在CMainFrame类中声明如下静态成员:
static CMainFrame* pThis; static DWORD idInst; static HDDEDATA CALLBACK EXPORT DdeCallback(UINT,UINT,HCONV,HSZ,HSZ, HDDEDATA,DWORD,DWORD); |
(2) 在类的创建代码(OnCreate())中作如下说明:
pThis=this; lpDdeCallback=MakeProcInstance((FARPROC)DdeCallback,hInstance); if(DdeInitialize(&idInst,(PFNCALLBACK)lpDdeCallback,CBF_FAIL_EXECUTES | CBF_SKIP_REGISTRATIONS|CBF_SKIP_UNREGISTRATIONS,0L)) { AfxMessageBox("不能初始化DDE服务","错误"); DestroyWindow(); } |
(3) 回调函数实现如下:
HDDEDATA FAR PASCAL _export CMainFrame::DdeCallback(UINT iType,UINT iFmt, HCONV hConv,HSZ hsz1,HSZ hsz2,HDDEDATA hData,DWORD dwData1,DWORD dwData2) { char szBuffer[16]; int i;
switch(iType) { case XTYP_CONNECT: //hsz1=topiv, hsz2=service return (HDDEDATA)TRUE;//TRUE; case XTYP_ADVSTART: //hsz1=topic, hsz2=item case XTYP_REQUEST: case XTYP_ADVREQ: case XTYP_POKE: //hsz1=Topic, hsz2=item, hData=data case XTYP_ADVSTOP: return NULL; } } |
3、避免变量类型冲突
如果在MFC应用直接使用DDEML服务,那么该MFC应用在编译时将会遇到变量类型HSZ重复定义错误。经过追踪发现,错误在于在DDEML.H对HSZ作了如下定义:
DECLARE_HANDLE32(HSZ);
而在AFXEXT.H(通过stdafx.h引入)中对HSZ又作了如下说明:
typedef BPSTR FAR* HSZ; // Long handle to a string
两个定义一个为32位整数,一个为BASIC字符串指针,当然会发生编译器不能作变量类型转换的错误。实际上,将HSZ声明为BASIC字符串指针主要用于在MFC应用中使用VBX控制。要改正这一错误,就必须保证不要在同一个代码模块中使用DDEML和VBX支持,通过将使用DDEML和VBX的代码分开,并在使用DDEML代码的模块中最开头定义如下编译器宏就可以解决上述问题:
#define NO_VBX_SUPPORT
五、使用3D控制
毫无疑问,3D控制的使用可以显著提高Windows应用程序的界面友好性,目前,许多流行的Windows应用程序都使用了3D控制,典型的如Microsoft公司的Office系列软件,而且,在Windows 95和Windows NT 4.0中,3D控制更是作为操作系统的一部分直接提供,这意味着在其上运行的软件不需要作任何特殊处理,就具有3D界面效果,但是,很遗憾的是,在Windows 3.x中,除了命令按钮控制使用3D控制以外,其余所有的控制,如编辑框,列表框,检查框等都只使用2D控制,要想使用3D控制,程序设计人员就必须在自己的程序中作一定的修改,考虑到目前3D效果的流行,这点努力是值得的。为了支持3D效果,Microsoft公司提供了一个专门用于3D控制的动态连接库,即CTL3D.DLL,但是在其Visual C++中却没有如何使用3D控制的讨论,并且,Visual C++也不直接支持3D编码,因为它不包括使用3D控制所必须的头文件。但是,这并不意味着在Visual C++中不能使用3D控制,只不过用户需要从其它地方获取技术支持罢了。由于使用的是动态连接库机制,因此,任何其它语言提供的3D头文件和CTL3D.DLL的输入库都是可用的。作者使用的就是Borland公司的Borland C++中提供的CTL3D.H和CTL3D.LIB。在C/C++中使用3D控制的方法也有很多种,在这里,为节约篇幅,只讨论与本文相关的主题,即使用MFC编程时如何使用3D控制。
在MFC的所有对话框中使用3D控制可以遵循如下步骤:
1. 在CWinApp::InitInstance函数中调用Ctl3dRegister和Ctl3dAutosubclass函数:
Ctl3dRegister(AfxGetInstanceHandle()); Ctl3dAutoSubclass(AfxGetInstanceHandle()); |
值得一提的是,在AppWizard产生的应用框架的CWinApp::InitInstance中有一个函数调用为SetDialogBkColor,此函数的作用是将所有对话框的背景颜色设置为灰色,这个功能与3D界面实现相同的功能,可以移去此语句。
由于CTL3D在初始化时读入所有的系统颜色并自己维持,为了使应用程序能够正确反映系统颜色的变化,MFC应用程序可以在WM_SYSCOLORCHANGE消息中调用Ctl3dColorChange函数。
2. 在MFC应用程序的CWinApp类中的ExitInstance函数中调用Ctl3dUnregister函数,以方便Windows对CTL3D库的正确管理。
3. 在MFC应用程序的项目文件中加入CTL3D.LIB(可以用IMPORT.EXE产生)。使用上述CTL3D的自动子类化的机制可以大大简化使用3D控制,如果这不满足你的要求,那么你就必须单独在需要使用3D控制的对话框的OnInitDialog()中自行子类化相关的控制类了,典型的如下代码片断所示:
BOOL CMyDialog::OnInitDialog() { Ctl3dSubclassDlgEx(m_hWnd,CTL3D_ALL); return TRUE; } |
上面讲了在对话框中使用3D效果的办法,如果用户想在非对话框中使用3D控制,典型的在FormView导出类中使用,可以在导出类的OnInitialUpdate函数中进行适当修改,修改的大小取决于你是否使用了3D控制的自动子类化机制。如果使用前面提到的自动子类化方法,那么仅需要在相应的OnInitialUpdate函数中调用Ctl3dSubclassDlg函数了,如下代码片断所示:
void CMyView::OnInitialUpdate() { Ctl3dSubclassDlg(m_hWnd,CTL3D_ALL); }
否则,则需要修改如下:
void CMyView::OnInitialUpdate() { Ctl3dSubclassDlgEx(m_hWnd,CTL3D_ALL); } |
六、使用自定义消息
1、MFC的消息映射机制
Windows是一个典型的消息驱动的操作系统,程序的运行是靠对各种消息的响应来实现的,这些消息的来源非常广泛,既包括Windows系统本身,如WM_CLOSE、WM_PAINT、WM_CREATE和WM_TIMER等常用消息,又包括用户菜单选择、键盘加速键以及工具条和对话框按钮等等,如果应用程序要与其它程序协同工作,那么消息的来源还包括其它应用程序发送的消息,串行口和并行口等硬件发送的消息等等。总之,Windows程序的开发是围绕着对众多消息的合理响应和实现来实现程序的各种功能的。使用过C语言来开发Windows程序的人都知道,在Windows程序的窗口回调函数中需要安排Switch语句来响应大量的消息,同时由于消息的间断性使得不同的消息响应之间信息的传递是通过大量的全局变量或者静态数据来实现的。
人们常用的两种类库OWL和MFC都提供了消息映射机制用以加速开发速度,使用者只需要按规定定义好对应消息的处理函数自身即可,至于实际调用由类库本身所提供的机制进行,或采用虚函数,或采用消息映射宏。为了有效节约内存,MFC并不大量采用虚函数机制,而是采用宏来将特定的消息映射到派生类中的响应成员函数。这种机制不但适用于Windows自身的140条消息,而且适用于菜单命令消息和按钮控制消息。MFC提供的消息映射机制是非常强大的,它允许在类的各个层次上对消息进行控制,而不简单的局限于消息产生者本身。在应用程序接收到窗口命令时,MFC将按如下次序寻找相应的消息控制函数:
SDI应用
MDI应用
视口
视口
文档
文档
SDI主框架
MDI子框架
应用
MDI主框架
应用
大多数应用对每一个命令通常都只有一个特定的命令控制函数,而这个命令控制函数也只属于某一特定的类,但是如果在应用中对同一消息有多个命令控制函数,那么只有优先级较高的命令控制函数才会被调用。为了简化对常用命令的处理,MFC在基类中提供并实现了许多消息映射的入口,如打印命令,打印预览命令,退出命令以及联机帮助命令等,这样在派生类中就继承了所有的基类中的消息映射函数,从而可以大大简化编程。如果我们要在自己派生类中实现对消息的控制,那么必须在派生类中加上相应的控制函数和映射入口。
2、使用自己的消息
在程序设计的更深层次,人们常常会发现只依赖于菜单和命令按钮产生的消息是不够的,常常因为程序运行的逻辑结构和不同视口之间数据的同步而需要使用一些自定义的消息,这样通过在相应层次上安排消息响应函数就可以实现自己的特殊需要。比如如果我们要在特定的时间间隔内通知所有数据输出视口重新取得新数据,要依靠菜单命令和按钮命令实现不够理想,比较理想的解决办法是采用定时器事件进行特定的计算操作,操作完成后再采用SendMessage发送自己的特定消息,只有当这一消息得到处理后才会返回主控程序进行下一时间计算。通过在文档层次上安排对消息的响应取得最新计算数据,而后通过UpdateAllViews()成员函数来通知所有相关视口更新数据的显示。视口通过重载OnUpdate()成员函数就可以实现特定数据的更新显示。
如果用户能够熟练使用SendMessage()函数和PostMessage()函数,那么要发送自定义消息并不难,通常有两种选择,其一是发送WM_COMMAND消息,通过消息的WORD wParam参数传递用户的命令ID,举例如下:
SendMessage(WM_COMMAND,IDC_GETDATA,0); //MFC主框架发送
然后在文档层次上安排消息映射入口:
ON_COMMAND(IDC_GETDATA, OnGetData)
同时在文档类中实现OnGetData()函数:
void CSimuDoc::OnGetData() { TRACE("Now in SimuDoc,From OnGetData\n"); UpdateAllViews(NULL); } |
注意在上中的消息映射入口需要用户手工加入,Visual C++提供的ClassWizard并不能替用户完成这一工作。上中例子没有使用PostMessage函数而使用SendMessage函数的原因是利用了SendMessage函数的特点,即它只有发送消息得到适当处理后方才
返回,这样有助于程序控制。另一种发送自定义消息的办法是直接发送命令ID,在控制层次上采用ON_MESSAGE来实现消息映射入口,注意这时的命令控制函数的原型根据Windows本身消息处理的规定必须如下:
afx_msg LONG OnCaculationOnce(WPARAM wParam,LPARAM lParam); |
相对来讲,这种机制不如上述机制简单,也就不再赘述。
七、使用不带文挡-视结构的MFC应用
文档-视结构的功能是非常强大的,可以适合于大多数应用程序,但是有时我们只需要非常简单的程序,为了减少最终可执行文件尺寸和提高运行速度,我们没有必要使用文挡-视结构,典型的有简单SDI应用和基于对话框的应用。
1、简单SDI应用
此时只需要使用CWinApp和CFrameWnd两个类就完全可以了。由于CWinApp类封装了WinMain函数和消息处理循环,因此任何使用MFC进行编程的程序都不能脱离开该类。实际上使用CWinApp类非常简单,主要是派生一个用户自己的应用类,如CMyApp,然后只需重载CWinApp类的InitInstance()函数:
BOOL CMyApp::InitInstance() { m_pMainWnd=new CMainFrame(); ASSERT(m_pMainWnd!=NULL); //error checking only m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return TRUE; } |
至于所需要的主框架类,则可以直接使用ClassWizard实用程序生成,该类的头文件与实现代码可以与CMyApp类的头文件和实现代码放在一起。注意,这里由一个技巧,由于ClassWizard的使用需要有相应的CLW文件存在,而收工建代码时没有对应的CLW文件,因此不能直接使用,解决办法是进入App Studio实用工具后使用ClassWizard,此时系统会发觉不存在相应的CLW文件,系统将提示你重建CLW文件并弹出相应对话框,这时候你不需要选择任何文件就直接选择OK按钮,这样系统将为你产生一个空的CLW文件,这样就可以使用ClassWizard实用工具了。为了将CWinApp和CFrameWnd的派生类有机地结合在一起,只需在CFrameWnd派生类的构造函数中进行窗口创建即可。典型代码如下:
CMainFrame::CMainFrame() { Create(NULL,"DDE Client Application",WS_OVERLAPPEDWINDOW,rectDefault, NULL,MAKEINTRESOURCE(IDR_MAINFRAME)); } |
采用ClassWizard实用程序生成相关类代码后,所有的类的其它实现和维护就同普通由AppWizard实用程序产生的代码一样了。
2、基于对话框的程序
有些主要用于数据的输入和输出等的应用在使用时没有必要改变窗口大小,典型的如各种联机注册程序,这些使用对话框作为应用的主界面就足够了,而且开发此类应用具有方便快捷的特点,代码也比较短小,如果直接采用各种控制类生成所需要的控制就特别麻烦。在Visual C++ 4.x版本中使用AppWizard就可以直接生成基于对话框的应用。在Visual 1.x中没有此功能,因此这类应用需要程序员自己实现。实际上使用MFC实现基于对话框的应用非常简单,同样只使用两个MFC类作为基类,这两个类为CWinApp类和CDialog类。所使用的对话框主界面同样可以先用App Studio编辑对话框界面,再使用ClassWizard产生相应代码框架,然后修改CMyApp类的声明,增加一个该对话框类的成员变量m_Mydlg,最后修改CMyApp类的InitInstance()函数如下:
BOOL CMyApp::InitInstance() { m_Mydlg.DoModal(); return TRUE; } |
八、MFC应用的人工优化
使用C/C++编写Windows程序的优点就是灵活高效,运行速度快琕isual C++编译器本身的优化工作相当出色,但这并不等于不需要进行适当的人工优化,为了提高程序的运行速度,程序员可以从以下几方面努力:
1) 减少不必要的重复显示
相对来讲,Windows的GDI操作是比较慢的,因此在程序中我们应该尽可能地控制整个视口的显示和更新,如果前后两此数据不发生变化,那么就不要重新进行视口的GDI图形操作,尤其对于背景图显示时非万不得已时不要重绘,同时不要经常五必要的刷新整个窗口。
2) 在视口极小化时不要进行更新屏幕操作
在窗口处于极小化时没有必要继续进行视口更新工作,这样可以显著提高速度。为此需要在子窗口一级捕获上述信息(视口不能捕获该类信息),再在视口中进行相应操作。如下代码片段所示:
首先在子窗口类中添加如下程序段:
void CMyChild::OnSysCommand(UINT nID,LPARAM lparam) { CMDIChildWnd::OnSysCommand(nID,lparam); if(nID==SC_MINIMIZE) { RedrawFlag=0; } else RedrawFlag=1; } |
再在视口更新时中修改如下:
void CMyChart::OnUpdate( CView* pSender, LPARAM lHint, CObject* pHint ) { if(pChild->RedrawFlag) { InvalidateRect(&r,FALSE); TRACE("Now In CMyChart::OnUpdate\n"); } } |
至于上中pChild指针可以在视口创建的例程中获取:
pChild=(CMyChild*)GetParent();
3) 使用永久性的资源
在频繁进行GDI输出的视口中,如在监控软件中常常使用的趋势图显示和棒图显示等等,应该考虑在类层次上建立频繁使用的每种画笔和刷子,这可以避免频繁的在堆中创建和删除GDI对象,从而提高速度。
4) 使用自有设备描述句柄
亦即在创建视口时通过指定WM_OWNDC风格来拥有自己的显示设备句柄,这虽然会多消耗一些内存,一个DC大约占800字节的内存,但是这避免了每次进行GDI操作前创建并合理初始化显示设备句柄这些重复操作。特别是要自定义坐标系统和使用特殊字体的视口这一点尤其重要。在16M机器日益普遍的今天为了节约一点点内存而降低速度的做法并不可取。
5) 优化编译时指定/G3选项和/FPix87选项
/G3选项将强迫编译器使用386处理器的处理代码,使用嵌入式协处理器指令对那些频繁进行浮点运算的程序很有帮助。采用这两种编译开关虽然提高了对用户机型的要求,但在386逐渐被淘汰,486市场大幅度萎缩,586市场日益普及的今天上述问题已经不再成为问题了。
九、结束语
总体上讲,使用Visual C++和MFC类库进行Windows编程是非常方便的,本文中所提到的一些看法只代表本人的观点,经验也只是笔者根据近年使用MFC进行Windows编程的总结,在此写出来是希望对那些使用VC和MFC进行Windows编程的同行有所帮助,如有不同看法欢迎与笔者联系讨论。
MFC中自由使用自定义消息
消息映射、循环机制是Windows程序运行的基本方式。VC++ MFC 中有许多现成的消息句柄,可当我们需要完成其它的任务,需要自定义消息,就遇到了一些困难。在MFC ClassWizard中不允许添加用户自定义消息,所以我们必须在程序中添加相应代码,以便可以象处理其它消息一样处理自定义消息。通常的做法是采取以下步骤:
第一步:定义消息。
推荐用户自定义消息至少是WM_USER+100,因为很多新控件也要使用WM_USER消息。
#define WM_MY_MESSAGE (WM_USER+100) |
第二步:实现消息处理函数。该函数使用WPRAM和LPARAM参数并返回LPESULT。
LPESULT CMainFrame::OnMyMessage(WPARAM wParam, LPARAM lParam) { // TODO: 处理用户自定义消息 ... return 0; } |
第三步:在类头文件的AFX_MSG块中说明消息处理函数:
class CMainFrame:public CMDIFrameWnd { ... // 一般消息映射函数 protected: // {{AFX_MSG(CMainFrame) afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); afx_msg void OnTimer(UINT nIDEvent); afx_msg LRESULT OnMyMessage(WPARAM wParam, LPARAM lParam); //}}AFX_MSG DECLARE_MESSAGE_MAP() } |
第四步:在用户类的消息块中,使用ON_MESSAGE宏指令将消息映射到消息处理函数中。
BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWnd) //{{AFX_MSG_MAP(CMainFrame) ON_WM_CREATE() ON_WM_TIMER() ON_MESSAGE(WM_MY_MESSAGE, OnMyMessage) //}}AFX_MSG_MAP END_MESSAGE_MAP() |
如果用户需要一个定义整个系统唯一的消息,可以调用SDK函数RegisterWindowMessage定义消息:
static UINT WM_MY_MESSAGE=RegisterWindowMessage("User"); |
并使用ON_REGISTERED_MESSAGE宏指令取代ON_MESSAGE宏指令,其余步骤同上。
当需要使用自定义消息时,可以在相应类中的函数中调用函数PostMessage或SendMessage发送消息PoseMessage(WM_MY_MESSAGE,O,O); 如果向其他进程发送消息可通过如下方法发送消息:
DWORD result; SendMessageTimeout(wnd->m_hWnd, // 目标窗口 WM_MY_MESSAGE, // 消息 0, // WPARAM 0, // LPARAM SMTO_ABORTIFHUNG | SMTO_NORMAL, TIMEOUT_INTERVAL, &result); |
以避免其它进程如果被阻塞而造成系统死等状态。
可是如果需要向其它类(如主框架、子窗口、视类、对话框、状态条、工具条或其他控件等)发送消息时,上述方法显得无能为力,而在编程过程中往往需要获取其它类中的某个识别信号,MFC框架给我们造成了种种限制,但是可以通过获取某个类的指针而向这个类发送消息,而自定义消息的各种动作则在这个类中定义,这样就可以自由自在的向其它类发送消息了。
下面举的例子叙述了向视类和框架类发送消息的方法:
在主框架类中向视类发送消息:
视类中定义消息:
ON_REGISTERED_MESSAGE(WM_MY_MESSAGE,OnMyMessage) //定义消息映射 视类定义消息处理函数:
// 消息处理函数 LRESULT CMessageView::OnMyMessage(WPARAM wParam, LPARAM lParam) { // TODO: 处理用户自定义消息 ... return 0; }
//发送消息的测试函数 void CMainFrame::OnTest() { CView * active = GetActiveView();//获取当前视类指针 if(active != NULL) active->PostMessage(WM_MY_MESSAGE,0,0); } |
在其它类中向视类发送消息:
//发送消息的测试函数 void CMainFrame::OnTest() { CMDIFrameWnd *pFrame; CMDIChildWnd *pChild; CView *pView; //获取主窗口指针 pFrame =(CMDIFrameWnd*)AfxGetApp()->m_pMainWnd; // 获取子窗口指针 pChild = (CMDIChildWnd *) pFrame->GetActiveFrame(); //获取视类指针 pView = pChild->GetActiveView(); if(pView != NULL) pView->PostMessage(WM_MY_MESSAGE,0,0);//发送消息 } |
其余步骤同上。
在视类中向主框架发送消息:
首先在主框架中定义相关的消息,方法同上,然后在发送消息的函数中添加代码如下
//发送消息的测试函数 void CMessageView::OnTest() { CFrameWnd * active = GetActiveFrame();//获取当前主窗口框架指针 if(active != this) active->PostMessage(WM_MY_MESSAGE,0,0); return 0; } |
在其它类中向不同的类发送消息可依次方法类推,这样我们的程序就可以的不受限制向其它类和进程发送消息,而避免了种种意想不到的风险。
下面一个例子程序为多文档程序里在一对话框中向视类发送消息,详述了发送自定义消息的具体过程。
实现步骤:
第一步:在VC++中新建工程Message,所有ClassWizard步骤选项均为缺省,完成。
第二步:在主菜单中添加测试菜单为调出对话框,在框架类中建立相应函数OnTest()
第三步:在资源中建立对话框,通过ClassWizard添加新类TestDialog,添加测试按钮,
在对话框类中建立相应函数OnDialogTest()
//通过对话框按钮发送消息的函数 void TestDialog::OnDialogTest() { CMDIFrameWnd *pFrame; CMDIChildWnd *pChild; CView *pView; //获取主窗口指针 pFrame =(CMDIFrameWnd*)AfxGetApp()->m_pMainWnd; // 获取子窗口指针 pChild = (CMDIChildWnd *) pFrame->GetActiveFrame(); //获取视类指针 pView = pChild->GetActiveView(); if(active != NULL) active->PostMessage(WM_MY_MESSAGE,0,0);//发送消息 } |
在Message.h头文件中添加如下语句:
static UINT WM_MY_MESSAGE=RegisterWindowMessage("Message"); |
第四步:在视类中添加自定义消息:
在头文件MessageView.h中添加消息映射
protected: //{{AFX_MSG(CMessageView) //}}AFX_MSG afx_msg LRESULT OnMyMessage(WPARAM wParam, LPARAM lParam); //此行为添加代码 DECLARE_MESSAGE_MAP() 在视类文件MessageView.cpp中的消息映射中添加自定义消息映射 BEGIN_MESSAGE_MAP(CMessageView, CView) //{{AFX_MSG_MAP(CMessageView) //}}AFX_MSG_MAP // Standard printing commands ON_REGISTERED_MESSAGE(WM_MY_MESSAGE,OnMyMessage) //此行添加代码定义唯一消息 END_MESSAGE_MAP() |
添加相应的0消息处理函数
LRESULT CMessageView::OnMyMessage(WPARAM wParam, LPARAM lParam) { CRect rect; GetClientRect(&rect); InvalidateRect(&rect); test=!test; return 0; } |
在MessageView.h中添加布尔变量 public:BOOL test;
在视类构造函数中初始化 test变量:test=FALSE;
修改CMessageView::OnDraw()函数
void CMessageView::OnDraw(CDC* pDC) { CMessageDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // 以下程序显示消息响应效果 if(test) pDC->TextOut(0,0,"消息响应!"); } |
第五步:显示测试对话框
在MainFrame类中包含对话框头文件:
#include "TestDialog.h"; OnTest()函数中添加代码 void CMainFrame::OnTest() { TestDialog dialog; dialog.DoModal(); } |
运行程序,在测试菜单打开对话框,点击测试按钮即可看到结果。
为MFC和ATL控件创建签署的CAB文件
如果您计划通过Internet销售 MFC控件和ATL 控件,您应该将其打包成签署的小巧(Cabinet -CAB) 文件。签署文件确保用户下载您的控件时源代码是安全的。一个CAB文件包含一个您的控件及其安装信息(比如,都需要哪些动态链接库DLL和OCX一起安装)的压缩版本。
创建和签署CAB文件的工具包含于Visual C++ 5.0 光盘中的 CAB&SIGN 目录下面。 该目录下的公用程序在您安装Visual C++时并不会自动地一起安装,因此您必须将CAB&SIGN 目录下面的内容拷贝到您的硬盘驱动器中。
在签署文件之前,您需要有一个软件出版商证明书(Software Publisher Certificate)。您必须向证书发放机构(Certification Authority)申请自己的证书。利用CAB&SIGN目录下面的工具,您能够创建一个用于测试的测试证明书,但是该证明书不能用于签署发售的代码。有关申请软件出版商证明书的信息,请参考步骤 1 。
下面是创建签署CAB文件的步骤 :
1 获得一个软件出版商证明书 (您只需要这一次申请就可以永久使用)
2 创建 CAB 文件
3 签署您的文件
4 将签署过的 CAB文件嵌入一个Web页(可选项)获得一个软件出版商证明书
在签署文件之前,您需要有一个软件出版商证明书(SPC)。为此,您必须向证书发放机构(Certification Authority)提出申请。在申请过程中,您必须生成一个密匙对,并向证书发放机构提供证明信息,比如说您的名字、地址以及公共密匙。而且,您必须作出具有法律约束力的誓言:您不能也将不会发布您知道或者应该知道其中包含了病毒的软件,或者是恶意破坏用户的机器或代码。
有关如何得到软件出版商证明书更详尽的信息,请参考Signing Code with Microsoft's Authenticode。若要申请该证书,请参考 Digital Certificates for Authenticode ;若要创建一个测试证书来测试签署文件,请参考Making A Test Software Publisher Certificate.
证书发放机构生成一个符合工业标准X.509证书格式(包含版本3扩展)的软件出版商证明书 。该证书确定并且包含您的公共密匙,并以证书发放机构存档作为参考,把一个拷贝以电子邮件的方式返回给您。收到该证书之后,您应该在所有要发布的、用私人密匙签署的软件当中,包含一份该证书的拷贝。
获得一个软件出版商证明书
您可以使用Visual C++ 5.0 光盘中CAB&SIGN目录下的 MAKECERT和 CERT2SPC 公用程序,做一个测试软件出版商证明书。注意,该测试软件出版商证明书对真正的软件发布无效,但是能够被用来测试您代码的签署。
比如,要做一个私人密匙文件MYKEY.PVK 和一个公司证书CERT.CER,则运行公用程序MAKECERT,其命令如下:
C:\CAB&SIGN\MAKECERT -u: MyKey -n: CN = MySoftwareCompany -k: MYKEY.PVK CERT.CER |
MyKey 是您的密匙名,MySoftwareCompany 是您的公司名。注意公用程序 MAKECERT 在命令行选项中区分大小写,因此您必须使用小写的-u、-n以及-k;-n选项的值必须是大写的CN=。
做一个名为CERT.SPC测试软件出版商证明书,则运行公用程序CERT2SPC,命令如下:
C:\CAB&SIGN\CERT2SPC C:\CAB&SIGN\ROOT.CER CERT.CER CERT.SPC |
注意CERT.SPC文件是利用您刚使用MAKECERT创建的CERT.CER文件以及CAB&SIGN目录下面的ROOT.CER文件创建的 。
创建一个 CAB文件
这一部分描述如何创建能在互连网上分派ATL和MFC组件的CAB文件。如果您要了解有关CAB文件的更多信息,请参考Cabinet文件参考书目(File Reference),该文件位于平台软件开发工具包(Platform SDK,包含在Visual C++ 5.0联机文档中)的设置和系统管理服务(Setup and System Management Services)部分的\Setup API\Overview\Cabinet Files目录下面。
创建一个 CAB文件:
创建一个INF文件。
运行公共例程CABARC(在光盘上的CAB&SIGN目录中)。例如:
C:\CAB&SIGN\CABARC -s 6144 n MYCTL.CAB NEEDED1.DLL NEEDED2.DLL MYCTL.OCX MYCTL.INF |
CABARC 创建了一个名为MYCTL.CAB的CAB文件。
您必须在您的源文件(INF、OCX以及DLL文件)目录下面运行CABARC。存档在CAB 文件中的文件需要在命令行列出,次序同它们在INF文件中的完全一致。在上例中,INF文件的列出次序是NEEDED1.DLL,然后 是NEEDED2.DLL,最后是MYCTL.OCX。
-s选项为代码签署保留空间。n 命令指定您想创建的是CAB文件。CABARC 命令和选项的说明可以查看,其方式是在命令行键入CABARC:
C:\CAB&SIGN\CABARC
创建一个INF文件
INF文件是一个文本文件,指定运行控件所需要下载或者呈交的文件(比如DLL或者其它OCX)。一个INF文件就捆绑了CAB压缩文件所有的必须文件。 缺省情况下,与现有硬盘中文件版本号相同的文件不被下载。要了解有关INF文件及其选项(包括如何创建独立于平台的INF文件)的详细情况,请参考万维网站Packaging Component Code for Automatic Download,或者参考平台软件开发工具包(Platform SDK,包含在Visual C++ 5.0联机文档中)的设置和系统管理服务(Setup and System Management Services)部分的\Setup API\Overview\INF Files目录下的内容。
作为示例,下面的INF是用来为ATL多边形控件创建一个CAB文件的。 您可以通过从Visual C++ 5.0光盘下载ATL POLYGON示例程序来创建POLYGON.DLL,并创建一个最小版本。创建该最小版本另外需要一个DLL即 ATL.DLL。ATL.DLL要先于POLYGON.DLL注册,因此首先把ATL.DLL 放置到INF当中。
; Sample INF file for POLYGON.DLL [version] ; version signature (same for both NT and Win95) do not remove signature="$CHICAGO$" AdvancedINF=2.0
[Add.Code] polygon.dll=polygon.dll atl.dll=atl.dll
; needed DLL [atl.dll] file-win32-x86=thiscab FileVersion=2,00,0,7024 DestDir=11 RegisterServer=yes
[polygon.dll] file-win32-x86=thiscab clsid={4CBBC676-507F-11D0-B98B-000000000000} FileVersion=1,0,0,1 RegisterServer=yes ; end of INF file |
该INF指定了系统需要安装特定版本的ATL.DLL。如果系统中还没有该文件,则需要从和该INF一起创建的CAB文件下载。"thiscab" 是一个关键字,意指包含该INF的CAB文件。您也可以从网上下载所需要的DLL文件,只要指定一个HTTP 网址即可,绝对路径或者相对路径都可以,比如:
file-win32-x86=http://www.mysite.com/mydir/NEEDED.DLL
关键字"file-win32-x86" 指定平台是 x86。
得到一个文件的版本号的过程是:在Windows NT或者Windows 95 Explorer中右键点击该文件;从弹出列表中选择Properties,然后在接着弹出的对话框中选择版本标签。有时,您可能需要在文件版本中插入一个额外的0 。比如,对话框中显示ATL.DLL的版本号是2.00.7024,在INF文件中则变为2,00,0,7024 。
"DestDir"指的是装载目录或者文件的地址: 11 指定为系统目录 WINDOWS/SYSTEM 或者 WINNT/SYSTEM32; 10 规定为窗口目录、WINDOWS或者WINNT。如果没有指定DestDir(典型情况),则代码从固定的OCCACHE目录装载。
"clsid" 指的是要安装控件的CLSID。
创建 INF文件后,再运行CABARC公用程序(在Visual C++ 5.0光盘上的CAB&SIGN目录中)创建CAB文件。您必须在您的源文件目录下面运行CABARC。存档在CAB 文件中的文件需要在命令行列出,次序同它们在INF文件中完全一致。比如,从上面的INF文件为多边形控件做一个CAB文件,需要使用下面的命令 :
C:\CAB&SIGN\CABARC -s 6144 POLYGON.CAB ATL.DLL POLYGON.DLL POLYGON.INF
该CAB文件包含ATL.DLL和POLYGON.DLL的压缩版本,以及将它们展开到POLYGON.INF 文件所需要的信息。
有关创建一个下载MFC控件的CAB文件的示例,请参考MFC 4.2b Component Download Information.您需要包含在MFC控件中的DLL文件有MSVCRT.DLL,MFC42.DLL以及 OLEPRO32.DLL。
签署一个CAB文件
使用Code Signing Wizard签署一个CAB文件:
1. 运行公用程序 SIGNCODE (在Visual C++ 5.0光盘的CAB&SIGN 目录下),启动Code Signing Wizard。
C:\CAB&SIGN\SIGNCODE
2. 在Code Signing Wizard对话框当中,点击Next前进到下一页。
3. 在您想签署那一个程序? 编辑框中,键入您想签署的CAB文件。
4.在您想在证书中使用什么名字? 编辑框中,键入您想在证书中使用的名字。
5. 点击Next,前进到下一页。
6. 在 您想将该程序签署在哪一个软件开发商证书下面? 编辑框中,键入软件开发商证书(SPC)文件名。
7. 在您要在该证书的哪一个文件中查找密码、密匙? 编辑框中,键入私人密匙(PVK)文件名。
8. 连续两次点击 Next前进到最后一页。
9.点击Sign,签署CAB 文件。您的文件将被数字化签署。
您可以不使用CAB文件直接签署您的DLL和OCX 。CAB文件的好处在于它是压缩的,而且,如果和INF文件一起使用,可以将所有必要代码捆绑在一起。
将签署过的 CAB文件嵌入一个Web页
ATL和MFC控件使用标签嵌入网页。在标签,您需要为该控件指定三个属性 :
ID - 控件名称
CLASSID - 控件的CLSID
CODEBASE - 下载控件的位置。CODEBASE 可以指向许多不同的文件类型。
CODEBASE可以直接指向一个OCX 文件或者DLL文件:
CODEBASE="http://www.mysite.com/mydir/polygon.dll#version=1,0,0,1" |
因为这仅仅完成了DLL或OCX文件的下载和安装,任何必要的DLL支持必须已经装载到了客户机上。
如果您在CAB文件中包含了选项版本号,它应该指向要下载的控件。例如,如果POLYGON.DLL有一个版本号1,0,0,1,则CAB文件的版本号也必须是1,0,0,1:
CODEBASE="http://www.mysite.com/mydir/polygon.cab#version=1,0,0,1" |
如果您没有包含选项版本号,则不能替换同一个组件(如果它们出现在客户机上的话)的老版本号。
MAP原理及其在MFC中的实现
一、 Map的基本知识
映射(Map),又称为字典(Dictionary),是由关键字(Key)及其对应的元素值(Value)所组成的元素单元(Element)的表单式集合。
通常,对于Map而言,使用给定的Key,可以迅速地从单元集合中检索到相应的元素。因此,在需要对大量数据进行查找操作而查找的性能又占据重要地位的场合,Map无疑是一种较理想的容器。譬如,在MFC中,使用Map来实现HandleMaps(句柄映射),以及其他的一些内部数据结构。同时,MFC也提供了公共Map类。使用公共Map类,MFC程序员可以轻易地高效地根据自身的需求实现程序中自定义的映射。
通常,当一个Map对象被删除时,或者,当其中的元素被移除时,关键字和元素值也将被完全删除。
从数据结构的角度分析,有关Map的典型操作有:
1、向Map中插入具有给定关键字的元素单元。
2、在Map中查找具有给定关键字的元素单元。
3、在Map中删除具有给定关键字的元素单元。
4、枚举(遍历)Map中的所有元素单元。
MFC中的各种Map实现,都提供了实现上述操作的成员函数。为了方便讨论,我们以CMap为代表,进行讲解。
一旦你已经向Map中插入了一个关键字-元素值组合对(Key-Value pair)单元,就可以利用关键字访问Map,从而有效地检索、添加或者删除元素单元,也可以遍历Map中的所有单元。
对MFC中的CMap等,除了关键字访问方法之外,还有另一种不同的类型--POSITION,也可以作为访问元素单元的辅助方式,可以使用一个POSITION来"记住"一个元素单元或者对Map进行枚举操作。你可能认为这种使用POSITION实现的遍历等同于使用关键字来进行的Map遍历,事实上并非如此,确切的说,两种检索的等价性是不确定的。
MFC中的提供了基于模板的CMap类。利用CMap模板类,可以处理特定的数据类型,例如用户自定义的类或结构体等。同时,MFC也提供了基于指定数据类型的非模板类,其中包括:
类名 |
关键字类型 |
元素值类型 |
CMapWordToPtr |
WORDS |
Void pointers |
CMapPtrToWord |
Void |
pointers WORDS |
CMapPtrToPtr |
Void pointers |
Void pointers |
CMapWordToOb |
WORDS |
Objects |
CMapStringToOb |
Strings |
Objects |
CMapStringToPtr |
Strings |
Void pointers |
CMapStringToString |
Strings |
String |
二、
Map的工作原理
使用Map的最大优势是它的快速查找的优秀性能,而取得最优性能的关键在于尽量使得在检索周期内所需进行的元素检查(比对)次数达到最少。顺序查找的性能是最差的,因为如果使用顺序查找算法在包含n个元素单元的Map中查找某个元素,可能(最坏情况下)需要进行n次独立的比较运算。
二元查找(折中查找)的性能表现要稍好一些,可是,一个不容忽视的问题是--二元查找要求待查序列为有序排列,这无疑会降低Map自身的操作灵活性。在我们的理解中,所谓的最佳算法应当是不论元素单元数目的多少,也不论元素是以什么顺序进行排列,查找过程都无需任何额外的比对操作,而能够仅仅通过简单的计算方法,就可以直接指向最终的相应元素的快速、高效算法。这听起来有些玄乎,但事实上,这种算法的确是有可能实现的(而且,我相信,Map可以做得到)。
在MFC的CMap及其相关的Map类中,只要对Map进行正确设置,Lookup函数通常能够一次到位的查找到任意元素,而很少需要进行两次或者三次以上的查找比对。
那么,这种高效的查找是如何实现的呢?
不失一般性,以MFC中的CMap模板类为例。在Map被创建之后(通常是恰恰在第一个元素被插入之前的瞬间),系统会为一个指向CAssoc结构体的指针数组的哈希表分配内存。MFC使用CAssoc结构体描述元素值和关键字的组合对。
CAssoc结构体描述如下:
struct CAssoc { CAssoc* pNext; UINT nHashValue; CString key; CString value; }; |
无论何时,只要有一个元素值-关键字单元被加入到Map中,就会随之创建一个新的CAssoc结构体,并根据单元中的关键字的实际值来计算出相应的哈希值。同时,拷贝一个指向CAssoc结构体的指针并将其插入到哈希表中索引值为i的位置中。其中,i的计算公式如下:
i=nHashValue%nHushTableSize
式中,nHashValue是由关键字Key的实际值计算出来的哈希值;nHashTableSize是哈希表中元素的数目(默认情况下,哈希表的大小为17)。
如果在哈希表中的索引值为i的位置已经容纳了一个CAssoc指针,那么MFC将建立一个单独的CAssoc结构体的链表(List),链表中的第一个CAssoc结构体的地址被存储到哈希表中,而将第二个CAssoc结构体的地址存储到前一个CAssoc结构体的pNext域,以此类推。下图展示了哈希表的一种可能实现情况,在该哈希表中,共有10个元素,其中5个元素地址分别唯一的存储,另外5个分别存储在长度为2和3的两个链表中。
调用一个Map的Lookup()函数时,MFC根据输入的关键字的实际值计算相应的哈希值,然后使用前面提到的公式将哈希值转换为索引值,并从哈希表中的相应位置检索CAssoc指针。
理想情况下,该位置只包含一个CAssoc指针,而非CAssoc指针链表。如果事实情况真如同我们所期望的那样,单一地址对应单一CAssoc指针,那么,元素单元将能够被一次查找到位,并直接读出;如果从哈希表中检索到的是CAssoc链表的指针头地址,则MFC顺序比对链表元素CAssoc结构所包含的关键字,直至查找到正确结果。但是,正如我们先前所讨论的那样,只要正确设置Map,链表中的元素一般就不会超过三个,这就意味着,查找通常可以在三次元素比对操作之内完成。
三、 优化查找效率
在MFC的Map中,查找性能主要依赖于两个因素:
1、哈希表的大小
2、尽可能产生唯一哈希值的优异算法
哈希表的大小对于Map的查找性能而言,是非常重要的。举个简单的例子,如果有一个Map要容纳1000个元素单元,但是哈希表却只能提供17个存放CAssoc指针的空间,那么,即使是最佳情况,哈希表中的每个CAssoc链表中也将包含58或59个CAssoc结构体,自然,在这种情况下,查找性能将受到严重阻碍。
哈希算法亦是影响查找效率的重要因素之一。如果所使用的哈希算法只能产生少量的不同哈希值(从而也只能产生少量的不同的哈希表索引值),查找性能也同样将被降低。
优化Map查找性能的最有效途径是尽可能的增大哈希表以降低因索引值相同而产生冲突的可能。微软推荐将哈希表的大小设置为Map中所存储元素数目的110% ~120%,以使得Map的应用性能在内存消耗和查找效率之间取得相对平衡。
在MFC中,指定哈希表大小,可调用InitHashTable()函数:
map.InitHashTable(1200);
式中,假设Map中要存储1000个元素,按照微软公司的推荐,将哈希表的大小扩展到实际存储元素数目的120%,即设置Map大小为1200。
从统计学上考虑,实用奇数作为哈希表的大小也将有助于减少冲突的发生。因此,初始化一个存储1000个元素的哈希表的InitHashTable()函数可以如下形式使用:
map.InitHashTable(1201);
同时,在InitHashTable()函数的调用时机上,应该注意的是,该函数应当在map包含有任何元素之前使。如果map中已经包含了一个或者更多的元素,那么,重新改变map的大小,将会引发断言(Assertion)错误。
尽管MFC中所使用的哈希算法能够适应于大多数场合,但如果您真的有所需要,或者,只要你愿意,用户也可以使用自己的算法来取代原有的算法。对于一个输入的关键字的值,要计算出它的哈希值,MFC通常调用一个全局模板函数HashKey(),对于大多数数据类型而言,HashKey()函数是以下面的方式实现的:
AFX_INLINE UINT AFXAPI HashKey(ARG_KEY key) { file://一般情况的默认算法。 return ((UINT)(void*)(DWORD)key) >> 4; }
但对于字符串而言,其具体的实现方式如下:
UINT AFXAPI HashKey(LPCWSTR key) // Unicode编码字符串 { UINT nHash = 0; while (*key) nHash = (nHash<<5) + nHash + *key++; return nHash; }
UINT AFXAPI HashKey(LPCSTR key) file:// ANSI编码字符串 { UINT nHash = 0; while (*key) nHash = (nHash<<5) + nHash + *key++; return nHash; } |
要实现对应于特定数据类型的用户自定义哈希算法,您可以使用上述的字符串版本的HashKey()函数作为参考,写一个类似的特定类型的HashKey()函数。
四、 使用MFC中的CMap类
有关MFC中的CMap类的概况,上面的文字段落中已经陆续提及,在此不再赘言。下面,列出CMap类的基本成员函数,并通过一个简短的程序片段来粗略地演示CMap类的使用方法。
构造函数:
操作:
Lookup |
通过给定的关键字查找相应的元素值。 |
SetAt |
向Map中插入一个元素单元;若存在匹配键字,则替代之。 |
operator [] |
向Map中插入一个元素 -SetAt的子操作 |
RemoveKey |
移除由关键字标示的元素单元 |
RemoveAll |
移除Map中的所有元素单元 |
GetStartPosition |
返回第一个元素单元的位置 |
GetNextAssoc |
读取下一个元素单元 |
GetHashTableSize |
返回哈希表的大小(元素单元的数目) |
InitHashTable |
初始化哈希表,并指定它的大小 |
状态:
GetCount |
返回Map中元素的数目 |
IsEmpty |
检查Map是否为空(无元素单元) |
应用实例如下:
CMap
myMap;
file://初始化哈希表,并指定其大小(取奇数)。MyMap.InitHashTable(257);
file://向myMap中添加元素单元。 for (int i=0;i < 200;i++) myMap.SetAt( i, CPoint(i, i) );
file:// 删除实际值为偶数的关键字所对应的的元素单元。 POSITION pos = myMap.GetStartPosition(); int nKey; CPoint pt; while (pos != NULL) { myMap.GetNextAssoc( pos, nKey, pt );
if ((nKey%2) == 0) myMap.RemoveKey( nKey ); }
#ifdef _DEBUG afxDump.SetDepth( 1 ); afxDump << "myMap: " << &myMap << "\n"; #endif |
在上面的应用程序片段中,我们可以了解有关CMap类的在通常情况下的使用方法。
1、首先我们使用CMap模板类来定义一个实例--myMap对象。
2、紧接着要做的是对myMap对象的哈希表的大小进行初始化设置。此时,应该先对myMap可能的容量需求进行估计,然后选择适当大小的奇数--或者,有可能的话,使用素数的效果会更好一些--来作为哈希表的初始值。
3、然后,向myMap中添加元素单元。
4、使用myMap进行数据映射、查找、遍历等操作。
5、调用myMap.RemoveAll()函数移除所有元素,释放myMap占用的内存空间。
CMap对应IMPLEMENT_SERIAL,从而支持用户对其元素进行串行化(Serialization)以及倾注(Dumping)操作。在对CMap的独立元素进行倾注操作时,应该注意的是,你必须将倾注环境(Dump Context)的深度设置为1或者更大的数字。
MFC中多线程的应用
我试着用自已的话来表述线程的概念,还有很短时间里编的一个小示例程序(不知恰当不?,也不知能说得清不..),见笑了.
线程其实和标准的windows主程序(WinMain)没啥两样...主程序其实是一个特殊的线程,称为主线程而已,其实你完全可以把线程想象成和winmain一起**同时运行**,但是** 可以相互访问(即在一个地址空间) **的一些小的WinMain程序.它和主线程一样,里面可以创建窗口,获取消息,等等..
由于线程们在一个地址空间且同时运行,所以会造成一些麻烦。因为我们编程都要用别人的函数库,而他们的函数库里面往往会有很多静态或全局的状态或中间变量,有着很复杂的相互依赖关系,如果执行某个功能不串行化(所谓串行化,也就是只能等一个功能调用返回后,另一个线程才能调用,不可以同时调用),就会造成大乱.这对线程来说,有术语称同步,windows为我们提供了很多同步的方法,MFC也提供了一些同步核心对象的类封装.对于某个功能调用库来说,叫线程安全.比如MFC的类库并不是线程安全的.
现在我举个刚刚自编的例子来简单说明这些概念。下面的一个对话框应用是多线程的.演示两个小动画:
(1)第一个动画由主线程的Timer来驱动,第二个动画由主线所创建的工作线程来驱动.分别显示在不同的位置.之所以我要加入Timer,也是为了形成线程驱动和timer驱动的对照,这是动画的两种驱动方式(还有在idle中驱动的)。
(2)这两个动画永远是不同的.也就是比如:一个是变哭,一个就会变笑,等那个变笑了,这个就变哭.动画图片来自于OICQ中的Face目录下,一般同样的头像会oicq会带三个图片(*-1.bmp,*-2.bmp,*-3.bmp),*-2.bmp是变灰的图片,我就取了1和3的图片来作动画.
这个程序的几个关键要注意的:
(1)主线程用PostThreadMessage和工作线程通信.工作线程用PeekMessage来取回消息。为了简单起见,我只用了一个WM_QUIT的消息来指示工作线程退出.
(2)主线程和工作线程同时调用了一个DisplayFace函数来进行动画显示.为了让两个动画一哭一笑做到不同,采用了CCriticalSection来进行同步.
示例如下:
(1)先用appwizards生成一个MFC的Dialog应用模板,假定对话框类为CTest01Dlg。
(2)再添入两个oicq的bmp文件到资源中去
(3)添加一个按钮(button)到对话框上.用作启动、停止动画的button
(4)用ClassWizard为button/onclick及dlg/ontimer生成事件响应函数,
(5)用Resource Symbol加入一个标识定义IDC_TIMER1
(6)在ClassView中为CTest01Dlg加入以下成员变量和成员函数
CriticalSection ccs;
CBitmap bm[2];
CWinThread* pMyThread;
static UINT MyThreadProc( LPVOID pParam);
void DisplayFace(CPoint r);
实现文件中加入相应代码(见下面)
(7)stdafx.h中加入#include
源代码如下,凡是我新加的代码周围都有注释包围,其它是ClassWizards自动写的:
// stdafx.h : include file for standard system include files,
// or project specific include files that are used frequently, but
// are changed infrequently
file://
#if !defined(AFX_STDAFX_H__5B92DAA8_FE27_4702_8037_A2538343E69D__INCLUDED_)
#define AFX_STDAFX_H__5B92DAA8_FE27_4702_8037_A2538343E69D__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers
#include
// MFC core and standard components
#include
// MFC extensions
#include
// MFC support for Internet Explorer 4 Common Controls
file://加入头引用主要是CCriticalSection对象的定义.
#include
file://加入结束
#ifndef _AFX_NO_AFXCMN_SUPPORT
#include
// MFC support for Windows Common Controls
#endif // _AFX_NO_AFXCMN_SUPPORT
file://{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_STDAFX_H__5B92DAA8_FE27_4702_8037_A2538343E69D__INCLUDED_)
// test01Dlg.h : header file
file://
#if !defined(AFX_TEST01DLG_H__F3780E23_CCFC_468C_A262_50FFF1D991BC__INCLUDED_)
#define AFX_TEST01DLG_H__F3780E23_CCFC_468C_A262_50FFF1D991BC__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
/////////////////////////////////////////////////////////////////////////////
// CTest01Dlg dialog
class CTest01Dlg : public CDialog
{
// Construction
public:
file://加入
CBitmap bm[2];
CCriticalSection ccs;
CWinThread* pMyThread;
static UINT MyThreadProc( LPVOID pParam);
void DisplayFace(CPoint r);
CTest01Dlg(CWnd* pParent = NULL); // standard constructor
file://加入结束
// Dialog Data
file://{{AFX_DATA(CTest01Dlg)
enum { IDD = IDD_TEST01_DIALOG };
// NOTE: the ClassWizard will add data members here
file://}}AFX_DATA
// ClassWizard generated virtual function overrides
file://{{AFX_VIRTUAL(CTest01Dlg)
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
file://}}AFX_VIRTUAL
// Implementation
protected:
HICON m_hIcon;
// Generated message map functions
file://{{AFX_MSG(CTest01Dlg)
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
afx_msg void OnButton1();
afx_msg void OnTimer(UINT nIDEvent);
file://}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
file://{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_TEST01DLG_H__F3780E23_CCFC_468C_A262_50FFF1D991BC__INCLUDED_)
// test01Dlg.cpp : implementation file
file://
#include "stdafx.h"
#include "test01.h"
#include "test01Dlg.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About
class CAboutDlg : public CDialog
{
public:
CAboutDlg();
// Dialog Data
file://{{AFX_DATA(CAboutDlg)
enum { IDD = IDD_ABOUTBOX };
file://}}AFX_DATA
// ClassWizard generated virtual function overrides
file://{{AFX_VIRTUAL(CAboutDlg)
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
file://}}AFX_VIRTUAL
// Implementation
protected:
file://{{AFX_MSG(CAboutDlg)
file://}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
file://{{AFX_DATA_INIT(CAboutDlg)
file://}}AFX_DATA_INIT
}
void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
file://{{AFX_DATA_MAP(CAboutDlg)
file://}}AFX_DATA_MAP
}
BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
file://{{AFX_MSG_MAP(CAboutDlg)
// No message handlers
file://}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CTest01Dlg dialog
CTest01Dlg::CTest01Dlg(CWnd* pParent /*=NULL*/)
: CDialog(CTest01Dlg::IDD, pParent)
{
file://{{AFX_DATA_INIT(CTest01Dlg)
// NOTE: the ClassWizard will add member initialization here
file://}}AFX_DATA_INIT
// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
file://加入
pMyThread =NULL;
file://加入结束
}
void CTest01Dlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
file://{{AFX_DATA_MAP(CTest01Dlg)
// NOTE: the ClassWizard will add DDX and DDV calls here
file://}}AFX_DATA_MAP
}
BEGIN_MESSAGE_MAP(CTest01Dlg, CDialog)
file://{{AFX_MSG_MAP(CTest01Dlg)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_BUTTON1, OnButton1)
ON_WM_TIMER()
file://}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CTest01Dlg message handlers
BOOL CTest01Dlg::OnInitDialog()
{
CDialog::OnInitDialog();
// Add "About..." menu item to system menu.
// IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// Set the icon for this dialog. The framework does this automatically
// when the application''s main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
// TODO: Add extra initialization here
file://加入
bm[0].LoadBitmap (IDB_BITMAP1);
bm[1].LoadBitmap (IDB_BITMAP3);
file://加入结束
return TRUE; // return TRUE unless you set the focus to a control
}
void CTest01Dlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
CDialog::OnSysCommand(nID, lParam);
}
}
void CTest01Dlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);
// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialog::OnPaint();
}
}
HCURSOR CTest01Dlg::OnQueryDragIcon()
{
return (HCURSOR) m_hIcon;
}
file://加入
void CTest01Dlg::OnButton1()
{
static BOOL bStarted=FALSE;
if (!bStarted){
SetTimer(IDC_TIMER1,500,NULL);
pMyThread=AfxBeginThread(MyThreadProc,this);
}else{
if (pMyThread){
pMyThread->PostThreadMessage (WM_QUIT,0,0);
::WaitForSingleObject(pMyThread->m_hThread ,INFINITE);
pMyThread=NULL;
}
KillTimer(IDC_TIMER1);
}
bStarted=!bStarted;
((CButton*)GetDlgItem(IDC_BUTTON1))->SetWindowText((bStarted?_T("停止"):_T("启动")));
}
void CTest01Dlg::OnTimer(UINT nIDEvent)
{
if (nIDEvent==IDC_TIMER1)
DisplayFace(CPoint(10,10));
CDialog::OnTimer(nIDEvent);
}
void CTest01Dlg::DisplayFace(CPoint p)
{
static int i=0;
ccs.Lock ();
BITMAP bmo;
bm[i].GetObject (sizeof(bmo),&bmo);
CClientDC dc(this);
CDC bmpDC;
bmpDC.CreateCompatibleDC (&dc);
bmpDC.SelectObject (&bm[i]);
dc.BitBlt (p.x ,p.y ,bmo.bmWidth,bmo.bmHeight,&bmpDC,0,0,SRCCOPY);
i++;
if (i==sizeof(bm)/sizeof(bm[0])) i=0;
ccs.Unlock ();
}
UINT CTest01Dlg::MyThreadProc(LPVOID pParam)
{
CTest01Dlg *me=(CTest01Dlg *)pParam;
MSG msg;
while(!PeekMessage(&msg,NULL,0,0,PM_NOREMOVE)){
me->DisplayFace (CPoint(100,10));
::Sleep (200);
}
return 0;
}
file://加入结束
用MFC构造DirectX应用框架
p > Microsoft DirectX SDK是开发基于 Windows 平台游戏的一个软件开发工具,其功能主要包括在五个组件中 :DirectDraw DirectSound DirectPlay Direct3D和DirectInput,每个组件都具不中的功能: DirectDraw使用直接写存技术加快游戏的动画速度;
DirectSound控制游戏声音的合成和播放;
DirectPlay使游戏具有网络多人游戏功能;
Direct3D让程序员更方便地开发三维游戏;
DirectInput使游戏支持更多的办入设备(现在只支持游戏杆,鼠标和键盘)。
可以说DirectX SDK提供了编写一个游戏 所必须的功能及基层函数,所以大多Windows游戏都使用了DirectX SDK.
MFC(Microsoft Foundation Class)类库是Microsoft Visual C++中提供的一个功能强大的 Windows 应用程序开发类, 使用这些类我们可以避免和繁琐的Windows API打交道,而且在 Visual C++中我们还可以利用ClassWizard 对MFC 类进行Windows 消息映射,所以如果能用MFC 类库来开发DirectX SDK的应用程序,至少有以下几个好处:
可以用VC++的ClassWizard方便地对Windows消息进行映射;
增加了程序的可读性,并且可以用VC++的 ClassView方便的管理所用的类;
增加程序代码的可重用性, 可以在原有的基础上开发出功能更强大的应用程序;
更进一步,如果我们能开发出一个能生成DirectX SDK应用程序基本框架的VC++的工程向导,则为以后开发DirectX SDK应用于程序提供及大的方便。下面,我们将用Visual C++先编写一个DirectX SDK应用程序的基本框架。
二 编写 DirectX SDK 应用程序基本框架
我们按下列步骤建立一个DirectX SDK 程序的基本框架:
1 用Visual C ++的MFC App Wizard (EXE) 生成一个基本对话框的工程文件,取名为DirectX,在向导第二步时取消About Box 的复选框,然后按Finish按钮。
2 删除在DirectX 工程目录中生成的DirectXDlg.H两个文件,并在Visual C++的File View中删除以上两个文件,按CTRL+W启动ClassWizard删除CdirectXDlg类,然后在ResourseView中删除IDD_DIRECTX_DIALOG.
3 建立两个文件DirectXWnd.H(这两个文件在本文的附录中,请注意不要删除有“//{”和“//}”之间的内容,否则将不能使用ClassWizard对窗口信息进行映射),并把它们加入到工程中? 时工程中将加入一个基于CWnd的CdirectXWnd类,这是我们的DirectX应用程序的基类。CdirectXWnd类创建一个窗口并生成一个与该窗口相关联的DirectDraw对象lpDD,同时还生成一个显示平面(lpFrontBuffer)和一个显示缓冲平面(lpBackBuffer),该类使用了几个虚函数,必要时其派生类可以覆盖这些函数。
4 打开DirectX.CPP,把 # include”DirectXDLG.h”改为 #include “DirectXWnd.H”然后把CdirectXApp::InitInstance()函数修改如下,其中黑体字为要增加的内容:
BOOL CdirectXApp::initlnstance()
{
#ifdef_AFXDLL
Enable3dControls();//Call this when using MFC in a shared DLL
#else
Enable3dConteolsStatic();//Call this when linking to MFC
Statically
#endif
CdirectXWnd *pWnd =new CdirectXWnd();
PWnd-$#@62;Create(“DirectXWnd Test”);
m-pMainWnd =pWnd;
pWnd-$#@62;UpdateWindow();
pWnd-$#@62;SetFocus();
if (pWnd-$#@62;initializeGame(640,480,8)= =FALSE){
pWnd-$#@62;DestroyWindow();
return FALSE;
}
MSG msg;
White(1)
{
if(PeekMessage(&msg,NULL,0,0,PM-NOREMOVE)) {
if( !GetMessage(&msg,NULL,0,0))
return msg.wParam;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else{
if (pWnd-$#@62;blsActive) {
pWnd-$#@62; UpdateFrame();
}
}
}
return TURE;
}
编译该程序并运行,可以看到出现一个黑色的屏幕窗口,按ESC或F12则可退出程序。至此我们的基本框架已经建立好了,虽然这个框架还比较简单,但我们可以在此基础上开发出更强大的应用框架。为了方便使用该框架,我们可以为该框架写一个Custom App Wizard,当然也可以不写,只要把该工程目录下的文件拷贝到另一个工程目录中即可。
三 测试框架
现在,我们按下列步骤写一个程序来测试这个框架:
1 把刚才创建的工程框架拷贝到一个新目录下,并打开。用Class View 创建一个基于CWnd的类CtestWnd,然后把CtestWnd.h和CtestWnd.CPP文件中的所有“CWnd”字符串替换为“CdirectXWnd”,并在CtestWnd.h文件头加入下列字符串:#include “DirectXWnd.h”.
2 打开DirectX.CPP文件,在文件头加入# include “TestWnd.h”,并把该文件中的所有”CdirectXWnd”字符串替换成”CtestWnd”并保存。
3 为CtestWnd类增加一个虚函数UpdateFrame(),这个函数覆盖了其基类CdirectXWnd的UpdateFrame():
void CtestWnd :: UpdateFrame()
{
staic int x= 0, dx =5;
staic int y =0, dy =5;
HDC hdc;
DDBL TFX ddbltfx;
HRESULT ddrval;
UpdateWindow();
Ddbltfx.swSize = sizedof(ddbltfx);
Ddbltfx.dwFillColor = 0;
Ddrval = lpBackBuffer -$#@62;Blt(
NULL ,//dest rect
NULL,//src surface
NULL,// src rect
DDBLT_COLORFILL |DDBLT_WAIT,
&ddbltfx);
if (ddrval ! = DD_OK)
{
msg(“ Fill failed ddrval =0x%081x”,ddrval);
return;
}
if (lpBackBuffer -$#@62;GetDC(&hdc) = =DD- OK)
{
if(x$#@60;0) dx=5;
if(x$#@62;590) dx= -5;
if (y$#@60;0) dy =5;
if ( y$#@62;430) dy = -5;
x += dx ; y + =dy;
Ellipse( hdc ,x,y,x+50,y+50);
LpBackBuffer -$#@62;ReleaseDC(hdc);
}
while(1)
{
HRESULT ddrval;
ddrva =lp FrontBuffer-$#@62;Flip(NULL,0);
if( ddrval = =DD_ OK)
{break;}
if (ddravl= =DDERR_SURFACELOST)
{
if(!CdirectXWnd::RestoreSurfaces())
{
break;
}
}
if (ddravl !=DDERR_WASSTILLDRAWING)
{
break;
}
}
}
4:编译并运行程序,屏幕上会出现一个白色球在移动
附录:
文件DirectXWnd.h
#if !defined(DIRECTXWND-H)
#define DIRECTXWND_H
//DirectXWnd.h:header file
#include$#@60;ddraw.h$#@62;
#pragma comment (lib,“ddraw。Lib”)//链接时加入ddraw.lib库
class CdirectXWnd: public CWnd
{
//Construction
public:
CDirectXWnd();
//Attributes
public:
BOOL blsActive; //应用程序是否激活
protected:
LPDERECTDRAW lpDD; //DirectDraw对象指针
LPDERECTDRAWSURFACE lpFrontBuffer;//DirectDraw主缓冲区
LPDERECTDRAWSURFACE lpBacdBuffer;//DirectDraw后备缓冲区
int nBufferCount; //后备缓冲区个数
//Operations
protected:
void Msg(LPSTR fmt,...);
public:
BOOL Create(LPCSTR lpszAppName); //创建窗体
//Overrides
virtual BOOL InitializeGame(UINT Gmodex,UINT GmodeY,UINT GBPP);
virtual BOOL CleanSurface();
virtual void UpdateFrame();
virtual BOOL RestoreSurfaces(void);
//{{AFX-VIRTUAL(CdirectXWnd)
//}}AFX_VIRTUAL
//Implementation
public:
virtual~CdirectXWnd();
//Generated message map functions
protected:
//{{AFX-MSG(CderectXWnd)
afx-msg void OnActivateApp(BOOL bActive, HTASK hTask);
//}}AFX-MSG
DECLARE-MESSAGE-MAP()
};
////////////////////////
//{{AFX-INSERT-LOCATION}}
#endif//!defined(DERECTXWND-H)
文件DirectXWnd.CPP
//DirectXWnd.cpp:implementation file
#include”stdafx.h”
#include”DirectX.h”
#include”DirectXWnd.h”
#ifdef-DEUG
#define new DEBUG-NEW
#undef THIS ?FILE
static char THIS ?FILE[]=--FILE--;
#endif
CDirectXWnd::CdirectWXnd()
{
lpDD=NULL;
lpFrontBuffer=NULL;
lpBackBuffer=NULL;
nBufferCount=0;
blsActive=TRUE;
}
CDirectXWnd::~CdirectXWnd()
{
if(lpDD){
CleanSurface();
lpDD-$#@62;Release();
lpDD=NULL;
}
}
BOOL CdirectXWnd::Create(LPCSTR IpszAppName)
{
CString className=AfxRegisterWndClass(CS-DBLCLKS,::LoadCursor(NULL,IDC-ARRWINDOW, className,IpszAppName,
WS-VISIBLE | WS-SYSMENU | WS-POPUP, 0, 0, GetSystemMetrics(SM-CXSCREEN), GetSystemMetrics(SM-CYSCREEN),NULL,NULL));
}
BOOL CdirectXWnd::InitializeGame(UINT GmodeX,UINT GModeY, UINT GBPP)
{
HRESULT ddrval;
ddrval=DirectDrawCreate(NULL,&lpDD,NULL);
if(ddrval!=DD-OK){
Msg(“DirectDrawCreate failed err=%d”, ddrval);
return FALSE;
}
ddral=lpDD-$#@62;SetCooperativeLevel(m-hWnd,DDSCL-EXCLUSIVE | DDSCL-FULLSCREEN);
if(ddrval!=DD-OK){
Msg(“SetCooperativeLevel failed err=%d”,ddrval);
return FALSE;
}
ddrval=lpDD_$#@62;SetDisplayMode(GmodeX,GmodeY,GBPP);
if(ddrval!-DD-OK)
{
Msg(“SetDisplayMode failed err=%d”,ddrval0;
return FALSE;
}
//check capabilites
DDCAPS ddcaps;
ddcaps.dwSize=sizeof(ddcaps);
ddrval=lpDD-$#@62;GetCaps(&ddcaps,NULL);
if(ddrval!=DD-OK){
Msg(“SetDisplayMode failed err=%d”,ddrval);
return FALSE;
}
if(ddcaps.dwCaps&DDCAPS_NOHARDWARE){
Msg(“No hardware support at all”);
}
//default to double buffered on 1mb, triple buffered
if(nBufferCount = =0){
if(ddcaps.dwVidMemTotal$#@60;=1024L*1024L*(GBPP/8)| |
GModeX$#@62;640){
NBufferCount =2;
}
else{
nBufferCount =3
}
}
DDSURFACEDESC ddsd;
: :ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize=sizeof(ddsd);
ddsd.dwFlags=DDSD_CAPS| DDSD_BACKBUFFERCOUNT;
ddsd.ddsCaps.dwCaps=DDSCAPS_PRIMARYSURFACE|
DDSCAPS_FLIP |DDSCAPS_COMPLEX;
ddsd.dwBackBufferCount=nBufferCount_1;
ddrval=lpDD_$#@62;CreateSurface(&ddsd,&lpFrontBuffer,NULL);
if(ddrval !=DD_OK){
Msg(“CreateSurface failed err=%d”,ddrval);
return FALSE;
}
DDSCAPS ddscaps;
ddscaps.dwCaps=DDSCAPS_BACKBUFFER;
ddrval=lpFrontBuffer_$#@62;GetAttachedSurface(&ddscaps,&lpBackBuffer);
if(ddrval !=DD_OK){
Msg(“GetAttachedsurface failed err=%d”,ddrval);
return FALSE;
}
return TRUE;
}
void CdirectXWnd: :Msg(LPSTR FMT, ...)
{
char buff[256];
va_list va;
lstrcpy(buff,”DirectxWnd:”);
va_start(va,fmt);
wvsprintf(&buff[lstrlen(buff)],fmt,va);
va_end(va);
lstrcat(buff,”/r/n”);
AfxMessageBox(buff);
}
//////////////////////////////
//Virtual Function
BOOL CdirectXWnd: :RestoreSurfaces()
{
HRESULT ddrval;
ddrval = lpFrontBuffer_$#@62;Restore();
if(ddrval !=DD_OK)
return FALSE;
return TRUE;
}
BOOL CDirectXWnd: :CleanSurface()
}
if(lpBackBuffer){
lpBackBuffer_$#@62;Release();
lpBackBuffer=NULL;
}
if(lpFrontBuffer){
lpFrontBuffer_$#@62;Release();
lpFrontBuffer=NULL;
}
return TRUE;
}
void CDirectSWnd: :UpdateFrame()
{
}
BEGIN_MESSAGE_MAP(CdirectXWnd,CWnd)
//{{AFX_MSG_MAP(CdirectXWnd)
ON_WM_KEYDOWN()
ON_WM_ACTIVATEAPP()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////
//CDirectXWnd message gandlers
void CDirectXWnd: :OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
switch(nChar)
{
case VK_ESCAPE:
case VK_F12:
PostMessage(WM_CLOSE);
break;
}
CWnd: :OnKeyDown(nChar, nRepCnt, nFlags);
}
void CdirectXWnd: :OnActivateApp(BOOL bActive,HTASK hTask){
CWnd: :OnActivateApp(bActive,hTask);
BlsActive=bActive;
}
用MFC的消息映像实现动态菜单
--- 当我们提到动态菜单的实现时,我们通常的做法是使用GetMenu() 函数获取一个Cmenu 类指针,然后调用CMenu 类方法AppendMenu, InsertMenu, ModifyMenu, RemoveMenu 等。本文介绍一种更加简洁的方法,它利用MFC 的消息映像机制及CCmdUI 类方法来实现。
---- 首先,我们简要说说VC 中MFC 的消息映像。每个Windows 程序员大概都对以前使用的窗口函数WindowProc 记忆犹新,当我们面对各种消息时,我们别无他方,只能使用庞大而机械的switch-case 语句来实现不同的分支选择。在VC5.0 中使用V4.2 版的MFC 基本类库,你将告别switch-case 语句,代之以透明的消息映像。要在一个类中使用消息映像,在类声明中,必须显式的加入宏DECLARE_MESSAGE_MAP:
class CMyClass: public CBaseClass
{
DECLARE_MESSAGE_MAP()
}
---- 在类实现中,必须使用两个宏BEGIN_MESSAGE_MAP 和END_MESSAGE_MAP,BEGIN_MESSAGE_MAP 带两个参数:当前类和直接父类:
---- BEGIN_MESSAGE_MAP(CMyClass, CBaseClass)
---- // 消息映像项
---- ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
---- // 消息映像项
---- END_MESSAGE_MAP()
---- 消息映像项使用下列基本语法:
---- ON_MessageName(ID, ClassMethod)
---- MessageName 是需要处理的消息,ID 是发送消息的标识符,而ClassMethod 为处理此消息的类方法名。MessageName 是MFC 预定义的,可分为以下三种:
---- 命令消息
---- 子窗口通知消息
---- Windows 消息
---- 共一百多个,用户不必记住它们,因为消息映像可以很简单的利用ClassWizard 加入。处理一个消息的类方法ClassMethod 必须在类定义中声明,且有实现代码。其原型为:
---- Afx_msg return_type ClassMethod(paras table)
---- 类CCmdUI 专门(且仅仅)与ON_UPDATE_COMMAND_UI 消息映像宏配套使用,用于管理菜单(还有工具栏按扭等)的实时状态,如是否变灰,是否加选中标记等。
---- ON_UPDATE_COMMAND_UI 消息映像宏原型为:
---- ON_UPDATE_COMMAND_UI(Menu_Item_ID, Menu_Proc)
---- ON_UPDATE_COMMAND_UI 消息映像宏将一个菜单项(命令项)和一个更新处理过程联结,从而在适当的时机自动调用此更新处理过程来完成对菜单项状态的更新。
---- Menu_Item_ID 为菜单项的ID 号,Menu_Proc 为此菜单项的更新处理函数,? 为:
---- afx_msg void Menu_Proc (CCmdUI* pCmdUI)
---- 它带有一个CCmdUI 类指针,使用它可调用CCmdUI 的类方法。与菜单有关的类方法有:
Enable(BOOL) 使菜单项有效或无效
SetText(LPCTSTR) 设置菜单项的文本
SetCheck(int) 加上或去掉选中标记“X”
SetRadio(BOOL) 加上或去掉选中标记“.”
---- MenuProc 被调用的时机有以下几种情况:
---- 用鼠标选中包含该菜单项的菜单条
---- 用热键选中包含该菜单项的菜单条
---- 用快捷键选中与该菜单项在同一菜单条下的任一菜单项
---- 我们以下面菜单结构为例:
Test menu
Item One ID_ITEM_ONE Ctrl+1
Item Two ID_ITEM_TWO Ctrl+2
Popup Popup One ID_POPUP_ONE Ctrl+3
Popup Two ID_POPUP_TWO Ctrl+4
---- 当用鼠标左键点按Test menu 菜单条或按Alt+t 或按Ctrl+1/2/3/4 时,四个菜单项的更新处理过程MenuProc 都将被调用。
---- 当我们考察上面这个具有嵌套结构的菜单时,我们面临这样一个问题:菜单项Item One/Item Two 的更新函数和Popup One/Popup Two 的更新函数形式上是否一致?当Popup One 和Popup Two 都变灰时Popup 是否自动变灰?
---- 根据MFC 的内部机制,仅仅弹出菜单的第一项应附加一些代码,其余项的形式基本是一致的。也就是说在上例中,除菜单项Popup One 外,其他菜单项更新函数的代码基本一致,即根据条件,简单调用CCmdUI 类方法即可。菜单项Popup One 由于是弹出式菜单Popup 的第一项,它的更新函数在以下两种情况下都会被调用:
---- 当弹出式菜单(Popup)的菜单项(Popup One 和Popup Two)要被绘出时
---- 当此弹出式菜单即Popup 本身要被绘出时
---- 第一种情况很好理解,正如我们选中Test menu 而Item One 和Item Two 的更新函数会自动执行一样。第二种情况其实也很自然,因为Popup 和Item One/Item Two 不一样,它没有ID 号,不能添加消息映像项,那么它的状态如何更新呢?于是它的第一项的更新函数被调用,为了区分是不同的调用,它将CCmdUI 的类成员变量m_pSubMenu 设置为不同的值。在第一种情况下,m_pSubMenu 等于NULL, 第二种情况下,m_pSubMenu 不等于NULL。
---- 以下我们给出一个实际的编程范例。由于篇幅关系,我们仅仅给出一些关键的语句,其余的则一并略去。
---- 在头文件的类声明中:
BOOL m_bItemOne, m_bItemTwo, m_bPopupOne, m_bPopupTwo;
//用于决定各个菜单项的状态
protected:
afx_msg void OnUpdateMenuitemOne(CCmdUI* pCmdUI);
afx_msg void OnUpdateMenuitemTwo(CCmdUI* pCmdUI);
afx_msg void OnUpdatePopupOne(CCmdUI* pCmdUI);
afx_msg void OnUpdatePopupTwo(CCmdUI* pCmdUI);
//各菜单项的更新函数
DECLARE_MESSAGE_MAP()
在源文件中:
BEGIN_MESSAGE_MAP(CMyDoc, CDocument)
ON_UPDATE_COMMAND_UI (ID_ITEM_ONE,
OnUpdateMenuitemOne)
ON_UPDATE_COMMAND_UI (ID_ITEM_TWO,
OnUpdateMenuitemTwo)
ON_UPDATE_COMMAND_UI (ID_POPUP_ONE,
OnUpdatePopupOne)
ON_UPDATE_COMMAND_UI (ID_ POPUP_TWO,
OnUpdatePopupTwo)
END_MESSAGE_MAP()
void CMyApp::OnUpdatetMenuitemOne (CCmdUI* pCmdUI)
{
pCmdUI->Enable(m_bItemOne);
if(m_bItemOne) pCmdUI->SetText("Item One");
else pCmdUI->SetText("Item One is now disabled");
}
void CMyApp::OnUpdatetMenuitemTwo (CCmdUI* pCmdUI)
{
pCmdUI->Enable(m_bItemTwo);
if(m_bItemTwo) pCmdUI->SetText("Item Two");
else pCmdUI->SetText("Item Two is now disabled");
}
void CMyApp::OnUpdatePopupOne(CCmdUI* pCmdUI)
{
if (pCmdUI->m_pSubMenu != NULL)
{
BOOL b_Popup = m_bPopupOne || m_bPopupTwo;
pCmdUI->m_pMenu->EnableMenuItem(pCmdUI->m_nIndex,
MF_BYPOSITION |
(bEnable ? MF_ENABLED :
(MF_DISABLED | MF_GRAYED)));
return;
}
pCmdUI->Enable(m_bPopupOne);
}
void CMyApp::OnUpdatePopupTwo(CCmdUI* pCmdUI)
{
pCmdUI->Enable(m_bPopupTwo);
}