今天开始重新拜读Don Box 的经典著作<<Essential COM>>.

之所以是重看, 是因为去年看的时候完全都不得要领, 光前两章就看了一个星期, 还没完全懂. 一年的修炼之后, 两个小时看完了第一章, 理解度应该在90%以上了.

言归正传, 记录下第一章的内容.

COM, Component Object Model, 看名字, 应该知道这是一个基于组件的对象模型. COM 其实只是一套规范, 定义了用C++来实现组件的重用必须要怎么做的规范. COM 是OOP的, 因为要作为组件对象嘛, 呵呵. 我们知道, 要做到OOP, 那么封装(encapsulation), 继承(inheritance), 多态(polymorphism) 一个都不能少. COM 在这三个要素中, 封装的作用尤其的重要. COM 是通过纯虚函数来把接口(interface) 和实现(implementation) 相分离的, 这让我们在写COM 类的时候, 要做到KISS(Keep It Simple& Stupid), 即接口要尽可能简单.

以上对于COM 说了个大概的思路, 下面说一下为什么要用COM 这个东西.

要实现组件的重用, 最容易想到的方法自然是dll. 没错, 把所有可以重用的组件统统包到一个dll 里. 但这样是有问题的: 由于各个编译器(compiler) 对于C++ 输出符号(export symbol)的命名(name mangling) 并没有统一, 所以用Visual C++编译的dll 在gcc 或者C++ Builder 里很可能是不能用的, 会找不到符号. 也就是说, 我们要确保的是二进制(binary)上的一致性. 有一种方法可以保证输出符号一致, 那就是加一个*.def文件. 好了, 假设这个问题我们也解决了, 问题又来了, 有一些C++的语言特性依然是编译器相关的(C++标准并没有规定语言的特征在运行时是怎么样的): 典型的就是exception 的处理. 好的, 你又可以说不用exception 就行了, 但是还有其它不能跨越dll 边界的语言特性呢? 比如多继承的时候, 纯虚基类的实现就是编译器相关的.

假设已经解决了上面的所有问题, 接下来的问题跟封装有关. 我就沿用书上的例子了: 假设有一个FastString 类已经实现了功能, 且已经发布. 现在我们要发布v2.0 的FastString 类, 为此我们加了很多的新功能, 公共(public) 接口保持不变的基础上, 添加新的函数, 为此我们也添加了成员变量, 当然它们都是私有(private) 的. 但是依然有这样一个问题: C++虽然在代码层保持了封装, 但是二进制层却不行. 客户端代码必须明确了解类库的二进制结构. 所以当我没用sizeof 之类的, 跟布局(alignment) 相关的操作时, 就会有问题. 当然这种二进制的耦合性是为了提高C++ 的performance. 一种解决方法是如mfc 的做法, 把版本号加在dll 名之中, 比如mfc80.dll, mfc90.dll 等.

总之, 我们现在要做的是设计这样一种方法: 可以屏蔽C++在二进制层面的不兼容性, 也就是说, 把接口(interface)和实现(implementation)分离. 能想到方法有两种, 包含(composition)和继承(inheritance). 这也是一般OOA/D 中能想到的方法, 我们分别来看一下这两种方法. 依然书上的例子了: 假设已有一个FastString 类已经实现了功能, 现在的目的是屏蔽二进制层面的不兼容性.

先来看包含. 我们在定义一个FastStringItf 类, 里面包含一个FastString 类的指针作为成员变量, 这其实用的是proxy 模式. 于是对于一个操作, 我们调用的是FastStringItf 接口, 实际操作的则是FastString 的实例. 但是这样有会有什么问题呢? 就是当类的操作很多的时候, 我们每次都要新加2 个函数的调用开销, 书上说对于performance 而言会不太好, 可是我并没有觉得想对于继承来说, 到底会不好到哪里.

然后就是继承了, 这也是COM 所用的方法. 首先声明一个IFastString 的接口, 然后是具体的 FastString 类. 并export 出一个全局函数来创建和返回FastString 的实例. 这个函数给dll 外的客户端代码调用, 是对外的唯一需要link 的接口. 由于我们得到了IFastString 的一个实例, 且有它的头文件(即所有操作声明), 所以我们就可以调用所有IFastString 的操作而不用link 到dll 中具体的操作实现. 好像很难懂, 看代码吧:

解决了以上问题之后, 书中又提出了另外一个问题: 当我们要为FastString 类加入新功能, 比如持久化(persistence) 的时候, 应该怎么做, 到底是修改IFastString 接口, 还是让FastString 类实现一个新的IPersistentObject 接口. 答案是后者, 因为对于接口来讲, 尽量不要修改它, 这样会破环封装性.

于是我们继续coding, 首先创建了一个IFastSting 的实例, 然后转成IPersistentObject 的实例使用持久化的功能. 但是用完之后, 我们要销毁实例的时候, 到底应该如何delete? 因为多重继承的问题, 我们到底销毁 IFastString 的实例, 还是IPersistentObject 的实例, 还是都要销毁? 这个问题对于简单的代码来说, 很容易解决, 只需要销毁一个就可以了, 但是当FastString 继承了很多的接口的时候, 可能就会搞不清. 针对这个问题, COM 引入了引用计数(reference count) 这个东西. 每当用接口来返回创建的FastString 对象的时候, 引用计数加一, 包括转换接口对象的情况. 而销毁接口对象的时候, 简单的把引用计数减一, 计数为零的时候实际销毁对象. 这两个操作对应的是COM 中AddRef() 和Release() 操作. 而对于转换对象接口, 不要依赖于C++ 的RTTI 实现, 因为这也是编译器相关的. 在最后附的例子代码中, 我们自己写两个一个DynamicCast() 函数来负责这项工作, 这个函数在COM 里的对象函数是 QueryInterface().

好了, 第一章就此结束, 初看会比较复杂, 但是当你把design pattern啊, windows 系统的其它部件的设计都有所了解的时候, 就会很容易理解. 侯捷童鞋有这样一篇文章: From Cpp to COM, 建议看一下, 里面还推荐了两本书<<Inside C++ Object Model>> 和<<Inside COM>>.

由于skydrive 更改外链链接, 导致媒体播放器长久显示不出, 现已修正, 特此公告= =v


4.19 更新
疑似skydrive 每天都会更改链接地址, 暂且没有可以上传mp3 的永久地址, 所以就先这样吧…

Applying UML and Patterns

我连这种圣经级的书都没看完, 请先bs 我.

大三的时候就买了书, 当时看得时候累的要死, 而且看了完全都忘记了. 现在再看, 好了不少.

本书以UP(Unified Process)为蓝本, 介绍了OOA/D(Object-Oriented Analysis/Design) 和UML 的相关概念. 按照UP 定义, 一个UP 项目可以划分为4个阶段(phase): 初始(inception), 细化(elaboration), 构建(construction), 移交(transition). 书里主要关注初始和细化阶段, 并把细化阶段分成了3个迭代(iteration).

最受益的是, 书中介绍的OOA 的部分, 以及OOA, OOD 如何过渡的部分. OOA 其实上课的时候说过很多, 但是不系统. 外国人写书就是这样, 很相似的概念可以花一大章节来辨明. 比如多次强调领域模型(domain model, 属于OOA中的概念) 和领域层(domain layer, 属于OOD中的概念) 中的类(class) 是不一样的. 中间还有一个低表示差异(low represental gap) 的概念, 以便前者触发(inspire)后者的设计.

关于设计模式, 之前就复习过那本经典的<<design pattern>>. 而读了这本书之后, 似乎找到了所有23 种GoF 模式的指导思想: 低耦合(low coupling), 高内聚(high cohesion).

书里关于细化的iteration-1, iteration-2 的内容花了很多时间来看, 而之后iteration-3 中所谓的高级内容却感觉很容易, 真搞笑. 虽然不敢说对于23 种GoF 模式都烂熟于胸, 但15, 16种总是有的吧. 以前在写jsp 网页的时候, 调用struts, hibernate 库(library)简直是顺理成章啊. 高级内容中就涉及到了这2 个框架(framework) 的设计. 还有就是用了不要用的3 层(layer)架构, 呵呵, 书里并没有说具体分多少层, 只说了分层的目的还是在于: 低耦合, 高内聚.

好了好了, 好像一点也不像笔记啊 = =b.

Lacrimosa
Kalafina

暗闇の中で睦み合う
絶望と未来を
悲しみを暴く月灯り
冷たく照らしてた

君のくれた秘密を標に
蒼い夜の静けさを行く

Lacrimosa
遠く砕けて消えた
眩しい世界をもう一度愛したい
瞳の中に夢を隠して
汚れた心に
涙が堕ちて来るまで

幻の馬車は闇を分け
光のある方へ
夢という罠が僕たちを
焔へ誘う

空の上の無慈悲な神々には
どんな叫びも届きはしない
Lacrimosa

僕らは燃えさかる薪となり
いつかその空を焼き尽くそう

Lacrimosa
ここに生まれて落ちた
血濡れた世界を恐れずに愛したい
許されるより許し信じて
汚れた地上で
涙の日々を数えて

Lacrimosa……

Lacrimosa
Kalafina

绝望与未来
在黑暗中悄然融合
静静映照这一切的冰冷月光之中
悲伤无处藏身

你告诉我的秘密便是我的道标
我将以此为指引穿过苍白夜晚的静寂

Lacrimosa
已然破碎消逝一去不回的灿烂世界
我仍想再一次将它深深爱恋
将梦想藏于眼眸中
直到泪水滴落
失去纯洁的心中之时

幻影的马车冲破黑暗
循着光芒驶去
梦想的陷阱将我们
引向无边烈焰

天空之上冷酷无情的诸神
怎样深切的呼唤都置若罔闻
Lacrimosa

让我们化身为熊熊燃烧的薪柴
终有一天会将这片天空烧尽

Lacrimosa
我所在的这个血染的世界
我仍想不顾一切地将它深深眷恋
与其等待被宽恕我愿意选择宽容与信任
默数在这失去纯洁的大地上
度过的落泪之日

Lacrimosa……

<<黑执事>> ED2.
Lacrimosa: 拉丁语, 流泪, 痛苦之意.