依旧的博客

技术学习

C++博客 首页 新随笔 联系 聚合 管理
  17 Posts :: 1 Stories :: 2 Comments :: 0 Trackbacks

#

关于Windows程序模型的最重要之处在于,程序是在Windows面向对象的体系结构中运行的。

在WinMain()函数中,程序所进行的最重要工作是注册窗口类,从而把自定义的窗口过程提供给Windows。然后程序调用Windows创建和显示窗口,由此启动同用户的交互过程。在消息循环中,程序不断取得消息,但并不进行处理,而是将其发回Windows,由Windows将消息发给相应的窗口过程。消息循环的作用在于控制生命期,如果没有消息循环,进程将立即结束。

在较高层次上来看,一个可扩展的系统会给模块提供资源和自由,而模块应当配合系统的整体结构。程序执行时,Windows会为其创建进程,分配资源,并调用WinMain()。WinMain()是进程入口,也是进程出口,在此期间进程可以做任何事情,但是为了使用Windows提供的各种便利,它必须符合Windows程序模型,将自己的运行结合到Windows环境中。作为进程出口,WinMain()决定着程序生命期。一个提供窗口过程而等待Windows调用的程序如何维持和结束自己的生命期呢,应该由消息来决定。当进程没有要处理的消息时,它应该等待,所以WinMain()必须知道有没有消息,Windows发给窗口过程的消息不能绕过WinMain();当进程收到特定的消息时,它结束生命期,所以WinMain()还应该了解消息的内容。这正是GetMessage()所做的,如果取不到消息就阻塞,如果取到WM_QUIT消息就返回0,结束消息循环。那么如果取到普通的消息呢,由WinMain()直接调用窗口过程不可以吗?这种做法有悖于程序由Windows调用的基本思想,而实际上也会出现问题。一个窗口程序可能有很多窗口类,一些窗口类及其窗口过程是程序自定义的,另一些则是在Windows内部定义的,程序看不到其窗口过程,比如各种控件窗口。窗口程序运行起来以后,这些窗口类互相配合,它们通信的方式就是消息。由于消息指向的窗口过程可能是自定义的,也可能是Windows内部的,只有Windows才能把它们都送到目的地,并保持发送方式的一致性。所以WinMain()取到消息后,通过DispatchMessage()将其发回Windows,由Windows为其调用适当的窗口过程,直到窗口过程调用后返回Windows,DispatchMessage()才返回。(Windows调用窗口过程之后控制首先返回Windows,由WinMain()调用窗口过程之后控制保持在程序中,这种区别是否也有作用?不过经我试验,在一个Win32 SDK的Hello程序中改由WinMain()调用窗口过程,没有发现什么问题)

 
参考资料:

1.《Windows程序设计》/Charles Petzold 著 北京博彦科技发展有限公司 译 北大出版社
posted @ 2007-04-15 13:08 依旧的博客 阅读(333) | 评论 (0)编辑 收藏

如果在VC中创建一个控制台的EXE和一个Win32的DLL,从DLL中导出一个函数,该函数用new分配一块内存,返回其指针,然后在EXE中调用该函数,获得返回的指针,用delete释放这块内存,就会引发断言错误。

产生这个问题的原因是:EXE和DLL中分别静态链接了C运行时库,从而new和delete运算符来自C运行时库的不同版本。C运行时库在管理堆内存时,会使用一些全局变量来跟踪内存分配情况,因此程序中链接的C运行时库必须唯一,否则就会引起不一致。

解决的办法很简单:在EXE和DLL中都动态链接C运行时库,也就是在工程设置的Link面板选择"忽略所有默认的库",再加入msvcrt.lib。

对这个问题有两种错误的观点需要澄清:一种以为EXE和DLL有不同的堆,实际上DLL总是被映射到加载它的进程的地址空间,它没有自己的堆;一种以为DLL和EXE相对于不同的起始地址,动态链接的地址映射机制引起了前面的问题,实际上DLL是和OBJ一样的目标模块,每个目标模块都有自己的起始地址,但是链接成加载模块以后就会统一到一个起始地址,一个目标模块对其它模块的引用在链接前是以符号方式表示的,链接后会被修改成地址方式。静态链接和动态链接都会保证:加载模块是统一编址的。

参考资料:
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. 《操作系统-内核与设计原理(第四版)》/William Stallings 著,魏迎梅等译 电子工业出版社

posted @ 2007-01-28 14:02 依旧的博客 阅读(302) | 评论 (0)编辑 收藏

有两个比较基本的问题:

1. 为什么要用异常代替错误码?

错误码的缺点:

1) 默认是可以忽略的,因为调用函数时可以不处理其返回值,从而错误处理要依赖于程序员的主动性,而不是程序机制的要求;

2) 不能跨作用域传送,必须逐层向上转发,即使中间没有对错误码进行重新定义;
 

使用异常可以解决解决这两个问题:

1) 异常默认是不可忽略的,抛出的异常必须捕获,否则就会报错;

2) 异常可以跨作用域传送,从而错误的发现和处理被很好地分离开来;
 

 

2. 异常和断言的区别:
 

异常被捕获后可以不作处理,程序从捕获位置继续执行。而断言是完全无法忽略的,程序在断言失败处立即终止。因此断言通常用于调试版本,用来发现程序中的逻辑错误。虽然异常也能起到这样的作用,但是不应该用异常代替断言:
1) 如果发现了逻辑错误,必须修改程序,而不可能在程序中进行处理和恢复,所以不需要向外传送,没有必要使用异常。
2) 使用断言的开销比异常小得多,而且断言可以从发布版中完全去除。
 

异常用于处理正确程序中的运行期问题(比如内存分配失败,窗口创建失败,线程创建失败,打开文件失败),以尽可能恢复,而不是终止程序。对于运行异常,使用断言是非常不合适的,理由很显然:
1) 断言在发布版不起作用;
2) 断言的处理方式不够友好;
3) 运行异常不是程序错误,没有必要报告源代码出错位置;



参考资料:

1.《C++编程规范-101条规则、准则与最佳实践》/Herb Sutter,Andrei Alexandrescu 著 刘基诚 译 人民邮电出版社
2.《C++程序设计语言》/Bjarne Stroustrup 著 裘宗燕 译 机械工业出版社
3.《C与C++中的异常处理》/Robert Schmidt 著 无情 译 http://download.pchome.net/development/reference/11135.html

posted @ 2006-10-19 21:28 依旧的博客 阅读(1431) | 评论 (0)编辑 收藏

软件设计中会碰到这样的关系:一个对象依赖于另一个对象,必须根据后者的状态更新自己的状态,可以把后者称作目标对象,前者称作观察者对象。不但观察者依赖于目标,当目标的状态改变时也要通知观察者,这就出现了双向的依赖。两个对象互相依赖的后果是它们必须一起复用。如果一个目标有多个观察者,那么目标也依赖所有观察者,从而目标对象无法独立复用。如何消除目标和观察者之间的互相依赖呢?观察者模式帮助我们解决这个问题。

观察者模式把目标对观察者的依赖进行抽象:使目标只知道自己有若干观察者,但不知道这些观察者具体是谁,可能有多少个;当目标状态改变时只要给这些观察者一个通知,不必作更多的事情。这样目标对观察者的依赖就达到了抽象和最小,而目标对具体观察者的依赖被解除了。

类图如下:

Observer.JPG

Subject对象保存一个Observer引用的列表,当我们让一个ConcreteObserver对象观察Subject对象时,调用后者的Attach()方法,将前者的引用加入该列表中。当Subject对象状态改变时,它调用自身的Notify方法,该方法调用列表中每一个Observer的Update()方法。一个ConcreteObserver只要重定义Update()就能收到通知,作为对通知的响应,Update()调用Subject对象的getStatus()获取数据,然后更新自身。当不需要继续观察时,ConcreteObserver对象调用Subject对象的Detach()方法,其引用被从列表中移除。

解除目标对具体观察者的依赖以后,很容易增加新的具体观察者,因为不受依赖的方面就可以自由变化;而目标也可以独立地复用,因为无所依赖的方面就可以不受影响。

以上主要考虑了一个目标有多个观察者的情况,我们设法解除了目标对具体观察者的依赖,使具体观察者的种类和数目容易改变。有时候一个观察者观察多个目标也是有意义的,在前面的类图中,观察者对具体目标的依赖仍然存在,因此无法适应目标方面的变化。怎样抽象这种依赖呢?使观察者只知道若干个目标会向自己发出通知,而不知道这些目标具体是谁,可能有多少个;在目标向观察者发送通知时,将一个自身的引用作为参数,然后观察者调用其抽象方法就可以获得目标状态。这就使得观察者对目标的依赖是抽象的,观察者对具体目标的依赖被解除了。

类图如下:

Observer2.JPG

参考资料:

1.《设计模式-可复用面向对象软件的基础》/Erich Gamma等著,李英军等译 机械工业出版社

posted @ 2006-09-10 12:53 依旧的博客 阅读(2275) | 评论 (0)编辑 收藏

Factory Method-创建多种同类产品的工厂:

为一类产品 ( 一个抽象产品及其所有具体产品 ) 提供一个工厂,该类的每一种具体产品由工厂中的一个方法创建。种种做法的缺点是不易增加新的具体产品,每增加一个具体产品,工厂中就要增加一个方法,这意味着工厂的所有使用者都要重新编译。可以用参数化的方法来改进,工厂只提供一个接受参数的创建函数,参数的取值标志了某种具体产品,在创建函数中对参数进行判断,根据不同的参数值创建不同的具体产品并返回。这就减小了增加具体产品的代价,每增加一种具体产品时只要修改工厂的创建函数的实现即可。

原来是用工厂的不同方法创建不同的具体对象,现在是用同一个方法的不同参数创建不同的具体对象。还可以用抽象工厂的不同派生类来创建不同的具体对象,这种做法比较笨重.

Abstract Factory-可替换的工厂:

见参考资料1


参考资料:

1.《设计模式-可复用面向对象软件的基础》/Erich Gamma等著,李英军等译 机械工业出版社
2.《敏捷软件开发-原则,模式与实践》/Robert C.Martin著 邓辉译  清华大学出版社

posted @ 2006-08-08 13:01 依旧的博客 阅读(292) | 评论 (0)编辑 收藏

MVC(Model-View-Controller)模式的基本思想是数据,显示和处理相分离。模型(Model)负责数据管理,视图(View)负责数据显示,控制器(Controller)负责业务逻辑和响应策略。

从MVC的形成过程来看,最初只有模型和视图两个元素。模型封装了数据并提供操作接口,视图用来表现数据和接收用户请求。模型是独立的,而视图依赖于模型:从模型获取数据进行显示;向模型发送用户请求,并根据返回结果刷新自己。

需要用多个视图表现同一模型时,情况发生了变化:一个视图修改数据以后,不但本身要刷新,其他所有视图也要刷新。如果由该视图通知其他视图,它就需要知道其他所有视图,由于每个视图都可能发出修改,每个视图都要知道其他所有视图,这种关联过于复杂,不但难以维护,而且不便于增加新的视图。如果让模型通知所有视图更新,可能会影响模型的独立性。用观察者(Observer)模式可以解决上述矛盾,从而实现:由模型通知视图,而模型不依赖于具体的视图,具体视图之间相互独立。

视图是用户请求的接收者,但不宜作为请求的处理者。因为界面是易变的,如果业务代码和界面代码放在一起,频繁的界面修改可能会破坏比较稳定的业务代码。将业务逻辑分离出来,由一个控制器负责,就是为了避免这种干扰。

模型,视图和控制器的基本协作关系如下图

MVC模式协作图.gif

模型在状态变化的时候,直接通知所有视图,视图向模型查询状态数据,然后刷新自身。当用户发出操作时,视图把消息发给控制器,控制器按照业务逻辑进行处理,需要查询或更新数据时,控制器会调用模型。下面是一个更详细的示意图

MVC模式协作图2.gif


同样的数据,可以有不同的显示和进行各种处理。显示仅仅是表现数据,而处理是根据用户请求改变数据的过程,不但包含业务逻辑,也要提供响应策略。响应策略由控制器负责,视图可以使用不同的控制器提供不同的响应方式,这是策略(Strategy)模式的应用。

此外,MVC还允许视图嵌套,通过使用组合(Composite)模式,一致地处理组合视图和普通视图。

用多个视图表现一个模型,在视图不变的情况下改变响应策略,允许视图嵌套,这是MVC的三个主要特性。在内部结构上,MVC的主要关系是由观察者模式,策略模式和组合模式给出的。由观察者模式确定的模型视图关系是其中最为重要的。

MVC模式有许多变体。前述结构中,由模型通知视图刷新,称为主动MVC;如果由控制器更新模型以后通知视图,称为被动MVC结构。在许多应用中,没有明显的控制器角色,也没有视图嵌套。可见根据实际需要,构成MVC的三个模式上都可能出现变化。Web浏览器就是被动MVC结构的一个实例。
“浏览器是一个交互程序,从概念上讲,它是由一组客户、一组解释器与一个管理它们的控制器所组成。控制器形成了浏览器的中心部件,它解释鼠标点击与键盘输入,并且调用其他组件来执行用户指定的操作。例如,当用户键入一个URL或者点击一个超文本引用时,控制器调用一个客户从所需文档所在的远程服务器上取回该文档,并且调用解释器向用户显示该文档。每个浏览器必须包含一个HTML解释器来显示文档,其他解释器是可选的。HTML解释器的输入由符合HTML语法的文档所组成,输出由位于用户显示器上的格式版本文档所组成。解释器通过将HTML规则转换成适合用户显示硬件的命令来处理版面细节。HTML解释器一个最重要的功能是包含可选项。解释器必须存储关于显示器上位置之间关系的信息和HTML文档中被瞄定的项。当用户用鼠标选定了一个项,浏览器通过当前的光标位置和存储的位置信息来决定哪个项被用户选定。”(参考资料5)


MFC的文档/视图结构(Document/View architecture)是MVC模式的一种变体,下面讨论它是怎样实现的。

文档/视图结构没有体现业务逻辑和视图的分离,但是将响应策略和视图区分开来。它主要包含四种对象:

  1. 文档
  2. 视图
  3. 视图框架窗口
  4. 文档模板

这里的视图框架窗口定义了视图对用户输入的响应方式,而文档模板用来管理前三种对象的组合。文档,视图,视图框架窗口三者是对应的,从而构成一个三元组。一个应用程序可能需要多个这样的三元组,以实现文档的多视图,所以引入文档模板来表示该三元组。因为程序中可能使用多个文档模板,MFC用一个文档管理者对象来管理它们。

在MFC中,应用程序和主框架窗口是用来封装底层机制的对象,文档,视图,视图框架窗口和文档模板是用来构架文档/视图结构的对象。应用程序通过文档管理者来使用文档/视图结构。

如果要给文档增加一种视图,只需要增加一个文档模板;如果要改变一种视图的响应策略,只要改变对应文档模板中的视图框架窗口。

<未完待续>


参考资料:

1.《设计模式-可复用面向对象软件的基础》/Erich Gamma等著,李英军等译 机械工业出版社
2.《Java与模式》/阎宏 电子工业出版社
3.  模型-视图-控制器 ( MSDN > 技术资源库 > 体系结构 > 使用 Microsoft .NET 的企业解决方案模式 >第3章 Web 表示模式)
4. 《Java设计:对象,UML和过程》/Kirk Knoernschild 著,罗英伟等译 人民邮电出版社
5. 《计算机网络与因特网》/D.E.Comer 著 徐良贤等译 机械工业出版社
6.《深入解析MFC》/中国电力出版社
7.《VC技术内幕》第5版 / 希望电子出版社

posted @ 2006-08-06 10:05 依旧的博客 阅读(3029) | 评论 (0)编辑 收藏

1. 一个用例可以有多个参与者,并且可以同时有多个参与者。
用例和参与者的关联可以是双向的,参与者和用例都可以发起通信。

2. 用例之间的基本关系有:泛化,包含和扩展。
用例A到B的泛化关系表示A和B是具体与抽象的关系。
用例A到B的包含关系表示A使用了B提供的功能。
用例A到B的扩展关系表示A向B提供的可用的功能。
但从A到B的包含关系和从B到A的扩展关系是不同的:
A包含B说明B是从A中分解出来的公共行为;B自身是独立的,但对于A来说是不可缺少的一部分。
B扩展A说明B是从A中分解出来的变体行为,必须指定扩展点,也就是在基本用例中执行变体行为的具体条件。B仅仅是A的补充,而不是不可缺少的部分,B自身也不是独立的。A可以单独执行,表示通常的情况,在特定的情况下,用B来补充它。
抽象用例不能被实例化,不能被实际执行,它的作用在于更好地组织用例关系。




参考书:
《UML用户指南》/Grady Booch,James Rumbaugh,Ivar Jacobson著 邵维忠等译 机械工业出版社
《统一软件开发过程》/Ivar Jacobson,Grady Booch,James Rumbaugh著 周伯生等译 机械工业出版社

posted @ 2006-06-01 17:18 依旧的博客 阅读(447) | 评论 (0)编辑 收藏

我曾经在有一个Windows XP的机器上另装一个Windows 2000,结果2000装完后XP不能启动了。后来上网看了一些资料,又几经试验,解决了这个问题。这里总结一下其中的原理,这个现象很容易在重装低版本系统后出现,而且弄清原理以后,我们可以更自由地处理多系统安装的问题。

单个操作系统的引导过程是这样的:首先,主引导记录(MBR)被加载到内存运行,它读取磁盘分区表(DPT),查找第一个活动分区(可引导分区),该分区的引导扇区存放着操作系统的引导记录。然后,系统引导记录被加载到内存运行,它从系统安装目录读取系统的启动文件,将其加载执行,控制随后的启动过程。

这里面涉及到一些程序和数据,它们存放在不同的地方,在不同阶段运行。第一段程序MBR,它的数据是DPT,它们存放在磁盘的主引导扇区。第二段程序是系统引导记录,存放在系统所在分区的引导扇区。第三段程序是系统启动文件,存放在系统所在分区系统安装目录中。这三段程序像接力跑一样,前一段程序的工作就是加载后一段程序,并把控制交给它。 引导记录和启动文件随操作系统而不同, 是在安装时形成的,每个系统的安装程序都把其引导记录写入安装分区的引导扇区,而启动文件是系统的一部分。

上面的引导过程有一个基本缺陷,就是只能引导一个系统,并且只能引导装在第一活动分区的系统。

如果一个操作系统不在活动分区,那么该系统要被引导有三种办法,改写MBR,改写第一活动分区引导记录,或把所在分区设为第一活动分区。最后一种做法是不方便的,系统通常会改写前两段引导程序,那么它在解决自身引导问题的同时,也不能破坏其他系统的引导,这就引出了多系统地引导问题。常见的做法是系统提供一个启动管理器接管引导过程。启动管理器能够获得机器上多个系统的引导记录,从而可以根据用户选择启动不同的系统。系统在安装时改写磁盘第一活动分区的引导记录,使启动管理器被作为第三段程序加载。

如果启动管理器能够知道机器上每个系统所在的分区,就能获得该系统的引导记录,从而可以引导该系统。但实际上,启动管理器所属系统的引导记录是不能再次被加载的,必须特殊对待。同一系列的系统,也可能有类似的问题。所以启动管理器可能要了解机器上每个系统具体如何启动,相应进行引导。这样只有让高版本的系统提供启动管理器,因为低版本的启动管理器无法启动高版本系统。
2000/XP的启动管理器是OS Loader。它对98和2000/XP的引导就是不同的,对98是加载98引导记录的镜像文件,对2000/XP是加载HAL.DLL等文件。OS Loader在引导多系统时,对于windows系列的引导有特殊性,必须向下兼容。

OS Loader的载体是ntldr文件,它运行时还会读取一个配置文件boot.ini,两个文件都存放在磁盘第一活动分区根目录。boot.ini记录了每个系统所在的分区,每个版本的windows在安装时都会在boot.ini中填写有关自身的一项。2000/XP在安装时都会更新OS Loader和重写第一活动分区的引导记录,后安装者的两个程序才会被保留。如果后装2000,由于前述的OS Loader版本问题,就可能无法引导XP。

posted @ 2006-05-18 16:27 依旧的博客 阅读(888) | 评论 (0)编辑 收藏

我们知道MFC的作用在于封装Windows的编程接口,并提供应用程序框架的开发模式。为了完成从前者到后者的过渡,MFC实现了几种基本机制,它们是消息映射,命令传递,运行时类信息(RTCI),动态创建和序列化。

消息映射和命令传递是对SDK程序交互机制的封装。序列化是应用程序需要的一种基本特性,即把数据保存到磁盘和从磁盘打开数据。通过RTCI和动态创建,可以把软件的对象数据保存到磁盘,反过来从这些数据识别和恢复对象,从而实现对象的序列化。基于数据库的序列化机制和这种方式不同,应用程序和数据库之间有一个约定,以什么样的格式保存什么样的数据,再以同样的方式打开,并且如何重建对象数据也定下来了,在打开数据时,应用程序不需要有适应性,不需要识别数据类型,也不需要根据在运行期才确定的类型名称创建其对象。

动态创建就是创建某种类型的对象,具体类型在运行时确定,编译时可能不知道。比如运行时用户输入一个类型名称,如果该类型是程序类型体系中的一员,则程序中将能够创建该类型的对象。下面的代码是使用MFC动态创建机制的一个简化的例子:

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();
     }
}

实现动态创建的思路是把动态的类型名称与程序类型体系中的每一个进行比较,与某个类型吻合时让该类型创建自身的对象。这样,支持动态创建的类库中的每一个类都要额外实现一些功能,即判别一个名称是否与自身相符,以及创建自身的对象。

判别一个名称是否与自身相符,这是运行时类识别的内容,所以MFC动态创建是在RTCI基础上实现的。

RTCI是一个对象能够判定自己是否属于某种类型,该类型的名称在运行时确定,编译时可能不知道。从下面的例子很容易理解RTCI,

void Func()
{
     char szClassName[64];
     CDocument* pDoc = new CDocument;
    
     cout << "enter a class name...  ";
     cin >> szClassName;
    
     cout << pDoc->IsKindOf(szClassName); //是返回1,否返回0
}

有一点需要说明的是,因为CDocument派生于CObject,所以IsKindOf对于CObject也要返回1。因为我们是从动态创建出发的,所以如果是这样可能会有一点背离初衷。但是RTCI明显和动态创建有密切联系,RTCI也可能有单独的价值,所以先把RTCI实现起来。

实现RTCI的思路是让每一个类记录自身的类型信息,并提供IsKindOf(char*)函数进行所给类型与自身类型的比较,而且还要能访问基类的类型信息,进行比较,一直到根类。所以记录的类型信息要按继承关系连起来,每个类的IsKindOf()还要调用基类的IsKindOf()。MFC把要记录的类型信息抽取到一个CRuntimeClass结构体中,每个类中加入一个CRuntimeClass成员即可。

现在回到动态创建,在RTCI建立的数据结构基础上将可实现它。动态创建从不同于IsKindOf()的角度使用这一数据结构,它要遍历所有类型的CRuntimeClass。那么仅仅有继承关系的类的CRuntimeClass相连还不够,要把所有类的CRuntimeClass连成一个链表。其实动态创建并不关心类间的继承关系,它平等看待每个类。现在以CRuntimeClass为结点构成一个纵横两个方向的链表,IsKindOf()和动态创建分别使用它不同的侧面。

序列化的概念是在文件中存储对象信息,并能根据它恢复对象。对于文档视图结构的软件,用户需要保存所编辑的文档和打开已编辑的文档,这正是序列化的应用,所以序列化是非常重要的一种特性。在序列化恢复对象时,就可以用到动态创建。

使用MFC序列化的例子如下,

void CMyDocument::Serialize(CArichive &ar)
{
    if (ar.IsStoring())
    {
        ar << m_pMyClass; //CMyClass m_pMyClass;
    }
    else
    {
        ar >> m_pMyClass;
    }
}

一个支持序列化的类提供Serialize(CArchive &)函数,重载<<和>>操作。注意两者是不同的,在上例中,CMyDocument类的信息并不被序列化,而CMyClass类的信息被序列化。实际上一个序列化类的<<和>>操作,其不涉及类信息的部分是调用Serialize()完成的,它必须同时实现这两者。

按照MFC的要求,需要在支持序列化的类定义中使用DECLARE_SERIAL宏,在类实现中使用IMPLEMENT_SERIAL宏。我们看一下这两个宏实现了什么,

#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; } \

主要是加入了对>>的重载,但是没有重载<<,MFC仅提供了CObject对<<的重载,如下,

_AFX_INLINE CArchive& AFXAPI operator<<(CArchive& ar, const CObject* pOb)
 { ar.WriteObject(pOb); return ar; }

这是因为在序列化读和写的时候,都需要具体类的CRuntimeClass信息。相应的GetRuntimeClass()是一个虚函数,CObject重载<<,在写类信息时调用到该函数,由于虚函数机制,写入的是具体类的信息。但是这里隐含着一个条件,就是调用<<和GetRuntimeClass()时,具体类对象已经存在了,而调用>>和读入类信息时,该类对象还未被创建,所以无法利用这种机制,只能在每个具体类中都重载一次>>。我觉得《深入解析MFC》对这个问题的解释不正确。

这里有一个问题需要明确一下,序列化为什么要写入类信息?一是它应该保存完整的能够独立恢复对象的信息,二是在程序读入对象时,要把它的类信息和程序中期望的(所能处理的)类信息相比较,进行检验。

看IMPLEMENT_SERIAL宏对重载>>的实现,是提供一个期望的CRuntimeClass结构(用于检验),委托CArchive进行对象读取。因为读对象时首先要跟文件打交道,所以交给CArchive处理,随后把读出的数据写入对象时,CArchive再调用具体类的Serialize(),如此合作是十分恰当的。在这里,CArchive还负责了读出和检验类信息,然后创建对象的过程。因为一方面具体类对象还不存在,另一方面这些操作对所有具体类都没有分别,应该提出来,在类级别实现或者让合作者实现。实际上,MFC先把这个过程交给CArchive::ReadClass(),后者又调用CRuntimeClass::Load()。 

对于序列化来说,搞清它的概念以后,就是实现Serialzie(),重载<<和>>。对<<和>>的重载涉及很多工作,MFC已经帮我们实现了,我们也看见了大概的设计,主要是与CArchive分工合作,其次是CRuntimeClass。

现在看到CRuntimeClass结构体在MFC对RTCI,动态创建和序列化的实现中都起着重要的作用,重新认识一下这个数据结构很有必要。

CRuntimeClass包含了关于类的各种信息和有关操作。把类及其基类的CRuntimeClass连成一个链表,就可以很方便地实现RTCI的IsKindOf();把所有类的CRuntimeClass连成一个链表,再加上一个简单的CreateObject函数,就可以对以任意类名进行动态创建的企图做出反应;CRuntimeClass还实现了向文件读写类信息的Load(),Store(),配合序列化的实现。

在分析消息映射和命令传递机制之前,需要对Windows程序模型有很好的理解。

未完待续...


参考:

《深入解析MFC》/中国电力出版社
《深入浅出MFC》/华中科大出版社
《Windows程序设计》/北大出版社

posted @ 2006-05-15 19:33 依旧的博客 阅读(1121) | 评论 (0)编辑 收藏

欣赏好的思路是一件愉快的事,特别是对我不会做的题目。

1. 问题:对32位的二进制整数,不用循环,求出其中1的个数。

#define POW(c) (1<<(c))
#define MASK(c) (((unsigned long)-1) / (POW(POW(c)) + 1))
#define ROUND(n, c) (((n) & MASK(c)) + ((n) >> POW(c) & MASK(c)))

int bit_count(unsigned int n)
{
    n 
= ROUND(n, 0);
    n 
= ROUND(n, 1);
    n 
= ROUND(n, 2);
    n 
= ROUND(n, 3);
    n 
= ROUND(n, 4);
    
return n;
}

基本的想法是把所有的1加起来,得到的就是1的个数。我们需要把这些1分离出来,每个1都是平等的,与其位置无关。难题在于不能一个一个去取,那就用到了循环,当然递归也是不允许的。需要有一种统一的办法,可是很难想象具体该怎样。我们逐步地做这件事,假设前16位和后16位分别求得了1的个数,那么加起来就行了。16位二进制中的1仍然是未知的,随机出现的,问题的性质没有变,但我们可以继续分解,这种逐步的做法不一定就意味着递归。每个16位分解为两个8位,...,每个2位分解为两个1位,把两个1位上的数相加就是这两位上1的个数。现在需要取出每一位上的数吗?如果想到了这个问题,就离最终的思路不远了。现在32位已经分成了16个两位,很容易将其看作两个16位,一个是所有奇数位,一个是所有偶数位。我们不难把这两个16位分开,然后移位相加,就求出了每两位中1的个数。到了这一步,以后的思路就很自然了。


参考:

《计算二进制位'1'的个数》来自 http://kaikai.cnblogs.com
posted @ 2006-05-12 23:14 依旧的博客 阅读(636) | 评论 (2)编辑 收藏

仅列出标题
共2页: 1 2