??xml version="1.0" encoding="utf-8" standalone="yes"?>
在WinMain()函数中,E序所q行的最重要工作是注册窗口类Q从而把自定义的H口q程提供lWindows。然后程序调用Windows创徏和显C窗口,由此启动同用L(fng)交互q程。在消息循环中,E序不断取得消息Q但q不q行处理Q而是其发回WindowsQ由Windows消息发l相应的H口q程。消息@环的作用在于控制生命期,如果没有消息循环Q进E将立即l束?
在较高层ơ上来看Q一个可扩展的系l会(x)l模块提供资源和自由Q而模块应当配合系l的整体l构。程序执行时QW(xu)indows?x)?f)其创E,分配资源Qƈ调用WinMain()。WinMain()是进E入口,也是q程出口Q在此期间进E可以做M事情Q但是ؓ(f)了用Windows提供的各U便利,它必ȝ合WindowsE序模型Q将自己的运行结合到Windows环境中。作E出口,W(xu)inMain()军_着E序生命期。一个提供窗口过E而等待Windows调用的程序如何维持和l束自己的生命期呢,应该由消息来军_。当q程没有要处理的消息Ӟ它应该等待,所以WinMain()必须知道有没有消息,W(xu)indows发给H口q程的消息不能绕qWinMain()Q当q程收到特定的消息时Q它l束生命期,所以WinMain()q应该了解消息的内容。这正是GetMessage()所做的Q如果取不到消息阻塞,如果取到WM_QUIT消息p?Q结束消息@环。那么如果取到普通的消息呢,由WinMain()直接调用H口q程不可以吗Q这U做法有(zhn)于E序由Windows调用的基本思想Q而实际上也会(x)出现问题。一个窗口程序可能有很多H口c,一些窗口类?qing)其H口q程是程序自定义的,另一些则是在Windows内部定义的,E序看不到其H口q程Q比如各U控件窗口。窗口程序运行v来以后,q些H口cM盔R合,它们通信的方式就是消息。由于消息指向的H口q程可能是自定义的,也可能是Windows内部的,只有Windows才能把它们都送到目的圎ͼq保持发送方式的一致性。所以WinMain()取到消息后,通过DispatchMessage()其发回WindowsQ由Windows为其调用适当的窗口过E,直到H口q程调用后返回WindowsQDispatchMessage()才返回?Windows调用H口q程之后控制首先q回WindowsQ由WinMain()调用H口q程之后控制保持在程序中Q这U区别是否也有作用?不过l我试验Q在一个Win32 SDK的HelloE序中改由WinMain()调用H口q程Q没有发C么问?
参考资料:(x)
1.《WindowsE序设计?Charles Petzold ?北京博RU技发展有限公司 ?北大出版C?
]]>
产生q个问题的原因是QEXE和DLL中分别静态链接了Cq行时库Q从而new和deleteq算W来自Cq行时库的不同版本。Cq行时库在管理堆内存Ӟ?x)用一些全局变量来跟t内存分配情况,因此E序中链接的Cq行时库必须唯一Q否则就?x)引起不一致?br />
解决的办法很单:(x)在EXE和DLL中都动态链接Cq行时库Q也是在工E设|的Link面板选择"忽略所有默认的?Q再加入msvcrt.lib?br />
对这个问题有两种错误的观炚w要澄清:(x)一U以为EXE和DLL有不同的堆,实际上DLLL被映到加蝲它的q程的地址I间Q它没有自己的堆Q一U以为DLL和EXE相对于不同的起始地址Q动态链接的地址映射机制引v了前面的问题Q实际上DLL是和OBJ一L(fng)目标模块Q每个目标模块都有自q起始地址Q但是链接成加蝲模块以后׃(x)l一C个v始地址Q一个目标模块对其它模块的引用在链接前是以符h式表C的Q链接后?x)被修改成地址方式。静态链接和动态链接都?x)保证?x)加蝲模块是统一~址的?br />
参考资料:(x)
1. http://topic.csdn.net/t/20020714/19/873683.html
2. MSDN July 2000/Knowledge Base/Windows Development/Win32 Software Development Kit/HOWTO: Use the C Run-Time
3. 《操作系l-内核与设计原?W四??William Stallings 著,迎梅等??sh)子工业出版C?br />
1) 默认是可以忽略的Q因用函数时可以不处理其q回|从而错误处理要依赖于程序员的主动性,而不是程序机制的要求Q?/span>
2) 不能跨作用域传送,必须逐层向上转发Q即使中间没有对错误码进行重新定义;
使用异常可以解决解决q两个问题:(x)
1) 异常默认是不可忽略的Q抛出的异常必须捕获Q否则就?x)报错?/span>
2) 异常可以跨作用域传送,从而错误的发现和处理被很好地分d来;
2. 异常和断a的区别:(x)
异常被捕获后可以不作处理Q程序从捕获位置l箋执行。而断a是完全无法忽略的Q程序在断言p|处立即终止。因此断a通常用于调试版本Q用来发现程序中的逻辑错误。虽然异怹能v到这L(fng)作用Q但是不应该用异总替断aQ?br />1) 如果发现了逻辑错误Q必M改程序,而不可能在程序中q行处理和恢复,所以不需要向外传送,没有必要使用异常?br />2) 使用断言的开销比异常小得多Q而且断言可以从发布版中完全去除?br />
异常用于处理正确E序中的q行期问?span lang="EN-US">(比如内存分配p|Q窗口创建失败,U程创徏p|Q打开文gp|)Q以可能恢复,而不是终止程序。对于运行异常,使用断言是非怸合适的Q理由很昄Q?br />1) 断言在发布版不v作用Q?br />2) 断言的处理方式不够友好;
3) q行异常不是E序错误Q没有必要报告源代码出错位置Q?br />
参考资料:(x)
1.《C++~程规范-101条规则、准则与最?jng)_c(din)?Herb SutterQAndrei Alexandrescu ?刘基??人民邮电(sh)出版C?br />2.《C++E序设计语言?Bjarne Stroustrup 著 裘宗燕 ?机械工业出版C?br />3.《C与C++中的异常处理?Robert Schmidt ?无情 ?http://download.pchome.net/development/reference/11135.html
ZcM?/span> ( 一个抽象品及(qing)其所有具体?/span> ) 提供一个工厂,该类的每一U具体品由工厂中的一个方法创建。种U做法的~点是不易增加新的具体品,每增加一个具体品,工厂中就要增加一个方法,q意味着工厂的所有用者都要重新编?/font>。可以用参数化的Ҏ(gu)来改q,工厂只提供一个接受参数的创徏函数Q参数的取值标志了某种具体产品Q在创徏函数中对参数q行判断Q根据不同的参数值创Z同的具体产品q返回。这减了增加具体产品的代P每增加一U具体品时只要修改工厂的创建函数的实现卛_?/span>
原来是用工厂的不同方法创Z同的具体对象Q现在是用同一个方法的不同参数创徏不同的具体对象。还可以用抽象工厂的不同zcL创徏不同的具体对象,q种做法比较W重.
Abstract Factory-可替换的工厂Q?/span>
见参考资?
参考资料:(x)
1.《设计模?可复用面向对象Y件的基础?Erich Gamma{著Q李英军{译 机械工业出版C?br />2.《敏捯Y件开?原则Q模式与实践?Robert C.Martin?邓辉译 清华大学出版社
MFC的文?视图l构(Document/View architecture)是MVC模式的一U变体,下面讨论它是怎样实现的?br>
文档/视图l构没有体现业务逻辑和视囄分离Q但是将响应{略和视囑分开来。它主要包含四种对象Q?/p>
q里的视图框架窗口定义了视图对用戯入的响应方式Q而文档模板用来管理前三种对象的组合。文档,视图Q视图框架窗口三者是对应的,从而构成一个三元组。一个应用程序可能需要多个这L(fng)三元l,以实现文档的多视图,所以引入文档模板来表示该三元组。因为程序中可能使用多个文档模板QMFC用一个文档管理者对象来理它们?br>
在MFC中,应用E序和主框架H口是用来封装底层机制的对象Q文档,视图Q视图框架窗口和文档模板是用来构架文?视图l构的对象。应用程序通过文档理者来使用文档/视图l构?br>
如果要给文档增加一U视图,只需要增加一个文档模板;如果要改变一U视囄响应{略Q只要改变对应文档模板中的视图框架窗口?br>
<未完待箋>
参考资料:(x)
1.《设计模?可复用面向对象Y件的基础?Erich Gamma{著Q李英军{译 机械工业出版C?br>2.《Java与模式?阎宏 ?sh)子工业出版C?br>3. 模型-视图-控制?/a> ( MSDN > 技术资源库 > 体系l构 > 使用 Microsoft .NET 的企业解x案模?nbsp;>
4. 《Java设计Q对象,UML和过E?Kirk Knoernschild 著,|英伟等?人民邮电(sh)出版C?br>5. 《计机|络与因特网?D.E.Comer ?徐良贤等?机械工业出版C?br>6.《深入解析MFC?中国?sh)力出版C?br>7.《VC技术内q》第5?/ 希望?sh)子出版C?/nobr>
q里面涉?qing)到一些程序和数据Q它们存攑֜不同的地方,在不同阶D运行。第一D늨序MBRQ它的数据是DPTQ它们存攑֜盘的主引导扇区。第二段E序是系l引D录,存放在系l所在分区的引导扇区。第三段E序是系l启动文Ӟ存放在系l所在分区系l安装目录中。这三段E序像接力跑一P前一D늨序的工作是加蝲后一D늨序,q把控制交给它?/span>
引导记录和启动文仉操作pȝ而不同,
是在安装时Ş成的Q每个系l的安装E序都把其引D录写入安装分区的引导扇区Q而启动文件是pȝ的一部分?/span>
上面的引DE有一个基本缺P是只能引导一个系l,q且只能引导装在W一zd分区的系l?br />
如果一个操作系l不在活动分区,那么该系l要被引导有三种办法Q改写MBRQ改写第一zd分区引导记录Q或把所在分为第一zd分区。最后一U做法是不方便的Q系l通常?x)改写前两段引导E序Q那么它在解册w引导问题的同时Q也不能破坏其他pȝ的引|q就引出了多pȝ地引导问题。常见的做法?/span>pȝ提供一个启动管理器接管引导q程。启动管理器能够获得机器上多个系l的引导记录Q从而可以根据用户选择启动不同的系l。系l在安装时改写磁盘第一zd分区的引D录,使启动管理器被作为第三段E序加蝲?/span>
如果启动理器能够知道机器上每个pȝ所在的分区Q就能获得该pȝ的引D录,从而可以引Dpȝ。但实际上,启动理器所属系l的引导记录是不能再ơ被加蝲的,必须Ҏ(gu)对待。同一pd的系l,也可能有cM的问题。所以启动管理器可能要了解机器上每个pȝ具体如何启动Q相应进行引对{这样只有让高版本的pȝ提供启动理器,因ؓ(f)低版本的启动理器无法启动高版本pȝ?/span>2000/XP的启动管理器是OS Loader。它?8?000/XP的引导就是不同的Q对98是加?8引导记录的镜像文Ӟ?000/XP是加载HAL.DLL{文件。OS Loader在引导多pȝӞ对于windowspd的引导有Ҏ(gu)性,必须向下兼容?/span>
OS Loader的蝲体是ntldr文gQ它q行时还?x)读取一个配|文件boot.iniQ两个文仉存放在磁盘第一zd分区根目录。boot.ini记录了每个系l所在的分区Q每个版本的windows在安装时都会(x)在boot.ini中填写有兌w的一V?000/XP在安装时都会(x)更新OS Loader和重写第一zd分区的引D录,后安装者的两个E序才会(x)被保留。如果后?000Q由于前q的OS Loader版本问题Q就可能无法引导XP?/span>
动态创建就是创建某U类型的对象Q具体类型在q行时确定,~译时可能不知道。比如运行时用户输入一个类型名Uͼ如果该类型是E序cd体系中的一员,则程序中能够创cd的对象。下面的代码是用MFC动态创建机制的一个简化的例子Q?/p>
CRuntimeClass* g_pFirstClass;
void func()
{
char szClassName[64];
CRuntimeClass* pClass;
CObject* pObject;
cout << "enter a class name... ";
cin >> szClassName;
for (pClass = g_pFirstClass; pClass != NULL; pClass = pClass->m_pNextClass)
{
if (strcmp(szClassName, pClass->m_lpszClassName) == 0)
pObject = pClass->CreateObject();
}
}
实现动态创建的思\是把动态的cd名称与程序类型体pM的每一个进行比较,与某个类型吻合时让该cd创徏自n的对象。这P支持动态创建的cd中的每一个类都要额外实现一些功能,卛_别一个名U是否与自n相符Q以?qing)创w的对象?/p>
判别一个名U是否与自n相符Q这是运行时c识别的内容Q所以MFC动态创建是在RTCI基础上实现的?/p>
RTCI是一个对象能够判定自己是否属于某U类型,该类型的名称在运行时定Q编译时可能不知道。从下面的例子很Ҏ(gu)理解RTCIQ?/p>
void Func()
{
char szClassName[64];
CDocument* pDoc = new CDocument;
cout << "enter a class name... ";
cin >> szClassName;
cout << pDoc->IsKindOf(szClassName); //是返?Q否q回0
}
有一炚w要说明的是,因ؓ(f)CDocumentz于CObjectQ所以IsKindOf对于CObject也要q回1。因为我们是从动态创建出发的Q所以如果是q样可能?x)有一点背d街但是RTCI明显和动态创建有密切联系QRTCI也可能有单独的h(hun)|所以先把RTCI实现h?/p>
实现RTCI的思\是让每一个类记录自n的类型信息,q提供IsKindOf(char*)函数q行所l类型与自ncd的比较,而且q要能访问基cȝcd信息Q进行比较,一直到根类。所以记录的cd信息要按l承关系qv来,每个cȝIsKindOf()q要调用基类的IsKindOf()。MFC把要记录的类型信息抽取到一个CRuntimeClassl构体中Q每个类中加入一个CRuntimeClass成员卛_?/p>
现在回到动态创建,在RTCI建立的数据结构基上将可实现它。动态创Z不同于IsKindOf()的角度用这一数据l构Q它要遍历所有类型的CRuntimeClass。那么仅仅有l承关系的类的CRuntimeClass相连q不够,要把所有类的CRuntimeClassq成一个链表。其实动态创建ƈ不关心类间的l承关系Q它q等看待每个cR现在以CRuntimeClass为结Ҏ(gu)成一个纵横两个方向的链表QIsKindOf()和动态创建分别用它不同的侧面?/p>
序列化的概念是在文g中存储对象信息,q能Ҏ(gu)它恢复对象。对于文档视囄构的软gQ用户需要保存所~辑的文档和打开已编辑的文档Q这正是序列化的应用Q所以序列化是非帔R要的一U特性。在序列化恢复对象时Q就可以用到动态创建?/p>
使用MFC序列化的例子如下Q?/p>
void CMyDocument::Serialize(CArichive &ar)
{
if (ar.IsStoring())
{
ar << m_pMyClass; //CMyClass m_pMyClass;
}
else
{
ar >> m_pMyClass;
}
}
一个支持序列化的类提供Serialize(CArchive &)函数Q重?lt;<?gt;>操作。注意两者是不同的,在上例中QCMyDocumentcȝ信息q不被序列化Q而CMyClasscȝ信息被序列化。实际上一个序列化cȝ<<?gt;>操作Q其不涉?qing)类信息的部分是调用Serialize()完成的,它必d时实现这两者?/p>
按照MFC的要求,需要在支持序列化的cd义中使用DECLARE_SERIAL宏,在类实现中用IMPLEMENT_SERIAL宏。我们看一下这两个宏实C什么,
#define DECLARE_SERIAL(class_name) \
_DECLARE_DYNCREATE(class_name) \
AFX_API friend CArchive& AFXAPI operator>>(CArchive& ar, class_name* &pOb);
#define IMPLEMENT_SERIAL(class_name, base_class_name, wSchema) \
CObject* PASCAL class_name::CreateObject() \
{ return new class_name; } \
_IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, \
class_name::CreateObject) \
AFX_CLASSINIT _init_##class_name(RUNTIME_CLASS(class_name)); \
CArchive& AFXAPI operator>>(CArchive& ar, class_name* &pOb) \
{ pOb = (class_name*) ar.ReadObject(RUNTIME_CLASS(class_name)); \
return ar; } \
主要是加入了?gt;>的重载,但是没有重蝲<<QMFC仅提供了CObject?lt;<的重载,如下Q?/p>
_AFX_INLINE CArchive& AFXAPI operator<<(CArchive& ar, const CObject* pOb)
{ ar.WriteObject(pOb); return ar; }
q是因ؓ(f)在序列化d写的时候,都需要具体类的CRuntimeClass信息。相应的GetRuntimeClass()是一个虚函数QCObject重蝲<<Q在写类信息时调用到该函敎ͼ׃虚函数机Ӟ写入的是具体cȝ信息。但是这里隐含着一个条Ӟ是调用<<和GetRuntimeClass()Ӟ具体cd象已l存在了Q而调?gt;>和读入类信息Ӟ该类对象q未被创建,所以无法利用这U机Ӟ只能在每个具体类中都重蝲一?gt;>。我觉得《深入解析MFC》对q个问题的解释不正确?/p>
q里有一个问题需要明一下,序列化ؓ(f)什么要写入cM息?一是它应该保存完整的能够独立恢复对象的信息Q二是在E序d对象Ӟ要把它的cM息和E序中期望的(所能处理的)cM息相比较Q进行检验?/p>
看IMPLEMENT_SERIAL宏对重蝲>>的实玎ͼ是提供一个期望的CRuntimeClassl构(用于?Q委托CArchiveq行对象d。因对象旉先要跟文件打交道Q所以交lC(j)Archive处理Q随后把d的数据写入对象时QCArchive再调用具体类的Serialize()Q如此合作是十分恰当的。在q里QCArchiveq负责了d和检验类信息Q然后创建对象的q程。因Z斚w具体cd象还不存在,另一斚wq些操作Ҏ(gu)有具体类都没有分别,应该提出来,在类U别实现或者让合作者实现。实际上QMFC先把q个q程交给C(j)Archive::ReadClass()Q后者又调用CRuntimeClass::Load()?nbsp;
对于序列化来_(d)搞清它的概念以后Q就是实现Serialzie()Q重?lt;<?gt;>。对<<?gt;>的重载涉?qing)很多工作,MFC已经帮我们实CQ我们也看见了大概的设计Q主要是与CArchive分工合作Q其ơ是CRuntimeClass?/p>
现在看到CRuntimeClassl构体在MFC对RTCIQ动态创建和序列化的实现中都L(fng)重要的作用,重新认识一下这个数据结构很有必要?/p>
CRuntimeClass包含了关于类的各U信息和有关操作。把cd(qing)其基cȝCRuntimeClassq成一个链表,可以很方便地实现RTCI的IsKindOf()Q把所有类的CRuntimeClassq成一个链表,再加上一个简单的CreateObject函数Q就可以对以Lcdq行动态创建的企图做出反应QCRuntimeClassq实C向文件读写类信息的Load()QStore()Q配合序列化的实现?/p>
在分析消息映和命o(h)传递机制之前,需要对WindowsE序模型有很好的理解?/p>
未完待箋...
参考:(x)
《深入解析MFC?中国?sh)力出版C?br>《深入浅出MFC?华中U大出版C?br>《WindowsE序设计?北大出版C?/p>