OLE2 打破软体国界

>>>  名人論史——近當代作家的史學觀點  >>> 簡體     傳統

发表日期 : 1995.02 

 

从应用层面来看,
OLE2 打破软体国界;
从基础架构来看,OLE2 是物件导向 Windows 作业系统的骨干。

横跨低阶与高阶,
这次我为你介绍三本半 OLE2 书籍。

 

你已经在去年 11 月份的无责任书评中看到,侯捷每一篇稿子的进行时程。文章的主体都是在上上个月的 25 日左右就完成,所以具时效性的话题在这专栏里看起来,像吃冰箱里那盘两个月前的冷冻蹄膀,你的大脑拼老命回想它甘美的滋味,你的味蕾却被冻得有些迟缓。其实我也很想在收到当月杂志之後,看看有什麽可以立刻回应或补充的,马上利用下个月文章的三校稿机会在其中加点料。以正常出刊时间 (每月十号) 而言 (那其实并不怎麽正常),中间还有一丝缝隙,仅容旋马;如果是真正的「正常」出刊,再加上邮递时间,那麽中间这个缝隙可就间不容发了。可怜贺元说他在 26 号收到当月杂志,但侯捷有更伟大的记录。如果是邮政出问题,怎麽补寄的杂志却又效率出奇的好呢 ? 噢,主编一定有足够的幽默感,让我在这里替万千读者请命。

所以呢,可能是因为「邮政效率日益低落」,我在这本杂志破天荒 1 号出刊的伟大月份 (去年十二月),於 22 号才看到这个专栏有了新朋友。GREAT !! 对於新文章和新朋友,衷心感到高兴与敬佩。希望我们能够继续在这个园地中看到更广泛的议题,更热烈的讨论。

■ 读者来函与你共享

o. 身为一个只是偶尔用用 PE2 的终端用户,我倒希望侯捷的「废话」愈多愈好。 技术性的东西看不懂,文采与眼界却颇令人折服。

o. 没有技术背景很难轻轻松松快快乐乐地看懂无责任书评。

o. 希望能够有另一种形式的书籍介绍 : 选定某个技术领域 (例如 Windows 程式设计), 从入门到深入到专家等级,一步步引导阅读重要着作。(答: 这个主意很棒, 我亦认为有此必要)

o. 如果中文书评有困难,或许可以在杂志上开个 Debug 专栏,由读者投稿指出 哪些烂书。(答: Debug 专栏是 Windows/DOS Developer's Journal 上的一个 专栏名称,主持人每月遴选数则软体臭虫公布之。也许我们可以弄个烂苹果奖, 问题是你愿意投稿吗 ? 你是否愿意让自己的名字上刊以昭公信 ?)

游侠列传访谈中提到的事情,读者也有意见 :

o. 您说要研究完 OLE2 之後才会考虑进行较浅显的技术, 但是我认为向下应该比向上更来得重要。如果多一点人广一点人叫好,不叫座 也不行了不是吗 ?

o. 风闻您想在探讨 OLE2 後就不再写技术性书籍而改写入门书籍,如果这是 真的,我将惋惜中文电脑书籍从此难见高品质作品,更惋惜您心中的理想国度 随之幻灭。

真是顺了嫂意逆了姑意,再怎麽做也不能让大家都满意。你其实并不明白侯捷真正要做的是什麽,但是这引发我三点感想 :

1) 每个人只关心有没有切合自己需求的书 (这很正常)。

2) 很清楚朋友们对高阶书籍期盼之殷寄望之浓。我们之所以缺乏「较大婴儿奶粉」, 赖明宗一语中的 :「使写书的人不会饿死,恐怕是先决条件」(v13n04 p.217)。 我早就说了,如果你不能接受 VxD Programming 书籍比 DOS User Guide 书籍 贵上三两倍,就要有接受前者灭绝的心理准备。很棒的作者需要很棒的读者。

3) 从任何角度任何层面阐述技术,都可以有理想有抱负。以使用介面而言,Windows 95 和 OS/2 3.0 都很有学问,有许多物件导向的精神在其中,User's Guide 的作者在 介绍操作步骤之前,应该先说明整个 UI 架构与精神。只是,许多作者本身功力薄弱, 写不出架构与精神,於是入门书籍千篇一律就是教大家「按下表单,选择命令, 出现对话盒,拉卷动杆选一个项目,按下 OK ...」,非常有碍观瞻又妨害消化。 大家都说技术文字应该深入浅出,问题是作者本身的技术没有深入,又如何将 文字浅出 ? 浅薄当浅出,低俗当通俗,国内电脑书籍写作的设限门槛太低 (普遍看来好像是...呃...没有门槛可言), 这才造成有胆量者皆可出书的现象。而你知道,无知是这麽一种东西 : 谁拥有它 谁就会产生巨大的胆量。

回到主题来,这个月的主题正是 OLE2。说不能够轻松快乐看懂无责任书评的读者,想必本篇会使你的这种感觉更强烈。我的手上有三本半的书要报告,第一本讲的是基础理论,第二本是 Microsoft Visual C++ 套件中给程式员看的技术手册;第三本书主讲如何以 Visual C++/MFC 撰写 OLE2 的各类应用程式。至於半本,事实上是某本书的一章,但足足 86 页,谓之半本书可也。

此外,另有三本书不特别花篇幅介绍内容。两本是 OLE 2 Programmer's Reference 上下册,上册主讲 OLE2 Interface API (960 页,US$ 29.95),下册主讲 Automation (368 页,US$ 24.95)。这两本书也内含在VC++ 1.5 的光碟之中。第三本书是 OLE 2.0 and DDE Distilled,作者是颇有名气的 Al Williams (着有 DOS and Windows Protected Mode)。书很薄,才 300 页,附磁片一。虽然书名伟大令人心动,内容却相当地...呃...不怎麽样。我不鼓励你吃这块鸡肋 (鸡肋应该勇於弃之,不必可惜)。

■ 我的理想

到目前为止,还没有一本 OLE 书籍符合我的理想。倒不是指深度,指的是表达方式和取材范围。洋人一向惜图如金 (国人也差不多),然而在 OLE 这麽庞大复杂的架构中,光是文字说明会令人如坠五里雾;解释两个程式间的通讯,一定要有涵盖收发两造往返动作的示意图,才能收事半功倍之效。没有一本书做到了这一点!至於取材范围,每个人都希望挑到一双合脚的鞋,无奈市面上尽是零码。我理想中的 OLE2 书籍应该有 :

1) OLE2 使用者介面之解释。OLE2 有许多专属的 UI 介面,在许多地方改变了 使用者的习惯。程式设计之前,有必要对 OLE 之操作方式做个详细的说明, 让大家了解如何以 [Insert New Object] 制作一个内嵌物件,如何以 [Paste Link] 制作一个联结物件,或是如何操作「即地编辑」(In Place Editing)。

2) OLE2 基本架构。OLE2 已不再只是单纯的行程通讯 (InterProcess Communication) 的一个规格而已,它还涵括基础的 Component Object、复合档案、记忆体管理...。 书中最好能对这些模组有些说明,为读者在狂饮之前垫点肚底。

3) OLE2 应用程式设计。基本上这有两个切入点,低阶是运用 OLE Interface 提供的函式,高阶则是利用 MFC 的 OLE Classes,或 OWL 的 OCF (ObjecComponents Framework)。这些 Classes 把低阶的 OLE Interface 包装起来,更方便使用。我不赞成从低阶切入应用程式的撰写 (何苦自 虐若此),但若在书中举一两个低阶介面完成的简单范例,将有助於了解 OLE2 基础架构。至於高阶的 OLE2 Classes,运用之前你必须先有 MFC 或 OWL 的基础。要不要在书中先介绍 MFC 或 OWL 呢 ? 我认为没有必要, 原因是如果只能以「小量篇幅」介绍这两种程式方法,可想而知对於「已经会 的人」只能温故而未能知新,对於「还不会的人」则不懂依旧不懂。

■ 勤前教育

介绍书籍之前,先做点 OLE2 技术上的说明,我想对於大多数读者的後续阅读动作会有些帮助。请先叁考图一,那是整个 OLE2 架构的描述。注意,下面我对 Windows Object 的解释非常重要,对你观念上的帮助可能不下於一篇技术性专文。

图一  略
OLE2 架构 (摘自 Inside OLE2)。图中的 Ixxx 代表各个模组必须支援的 Interface,本图只是举例,并未列出所有的 Interfaces。

 

任何深入讨论 OLE2 的书籍或文章中,你都会看到 Windows Object。到底这是什麽东西 ? 为什麽说未来的 Windows 是以 Windows Object 为基础的物件导向作业系统呢 ?

相信你一定已经清楚,DOS 是以中断向量 INT 21h 向外界开放其功能,Windows 则是以 API 形式把作业系统的功能与性质开放出来给应用程式使用。所以你在程式中呼叫各种 Windows API,取得 Windows 三大模组 (USER、KERNEL、GDI) 的能力。这些 APIs 以各自独立的 C 函式存在着,每一个 API 函式的地位是平等的,也就是说任何一段码都可以呼叫任何一个 API 函式。你的 C 程式(SDK 程式) 呼叫这些 C 函式,再自然不过了。但是这些命名规则不怎麽令人欣赏的 APIs 带来混乱而非秩序 (你能够把 DeleteObject() 和 CreatePen() 自然联想在一块儿吗 ?)

现在,进入物件导向的世界,应用程式以 C++ 语言完成,以类别和物件的形式呈现。如果 Windows 还是以无秩序的 API 函式展示它的内涵,那就不太搭轧。是的,Windows 也打算以组织紧密的物件形式展现它的内在美,这种物件就称为 Windows Object。譬如说你可以取得一个所谓的「Allocator Object」,其成员函式 Alloc() 和Realloc() 可以取代 API 函式 GlobalAlloc() 和 GlobalReAlloc()。现在你可以想像如何利用 Windows Object 在应用程式中配置记忆体了: 取得一个 Allocator Object,并呼叫其成员函式 Alloc()。

好,object 就 object,为什麽称为 Windows Object ? 原因是这种物件比一般的 C++ 物件更严格,你只能透过某种方式获得此物件之「函式表格」指标,却绝对得不到此物件本身的指标 --- 目的当然是为了隔绝外界对其资料的可能侵犯行为。我知道你会问,把物件的资料宣告为 private 不就有封装效果了吗 ? 你是对的,但或许 Microsoft 实作这些 OLE 模组时,为了内部设计方便,放了一些 public 资料,那麽就只好以上述方式加以绝缘了 (这是我的猜测,未经证实)。

上面提到 Windows Object 的「函式表格」,是一组语意相关、功能相关的函式,这个函式表格中的每一个元素,是类别中的成员函式的函式指标。函式表格被称为 Windows Object 的 Interface (图二)。

图二
Object 记忆体中的第一个位置,放的总是一个指标,指向函式表格。 函式表格内的每一个元素都是函式指标。函式表格就是 Interface。

 

现在让我们整理一下思绪。你在物件导向 Windows 作业系统中,藉着一个一个的Windows Object,利用其 Interface 中的函式,取得作业系统的功能。例如 :

#include // 此档定义 LPMALLOC 并宣告 CoGetMalloc()
LPMALLOC pIMalloc; // LPMALLOC IMalloc FAR*
LPVOID ptr;

// 取得 Interface 指标并呼叫其中函式。
CoGetMalloc(MEMCTX_TASK, &pIMalloc);
ptr = pIMalloc->Alloc(1024);

是的,你将遭遇效率降低的痛苦,因为相同一件事情的完成,Windows Object 比Windows API 多了一层动作。但是以 Windows Object 表现出来的系统特质势将成为最自然的一种表达方式 --- 以物件导向的精神而言。

OLE2 是一个集众人之力规划出来的规格,其中不同的模组规定必须支援不同的Interfaces (也就是说支援不同的函式组)。如果你能够设计一个类别,符合图一某个模组所要求的 Interfaces,那麽就可以说你实作出了 OLE2 的某个模组。例如,如果你能够设计出一个含有 IUnknown、IMalloc、IClassFactory 的类别,你就实现了OLE2 的 Conponent Object Model (COM)。目前,提供 OLE2 模组的工作由 Microsoft 负担,而实际产品 (规格之实现) 就是 OLE2 的各个 DLLs。

我知道你又要举手了 :「Windows API 用的好好的,干啥用什麽 Windows Object 让大家不得安宁」? 我还没有能力很具体告诉你使用 Windows Object 的好处,但我可以告诉你不使用 Windows Object 的坏处 : 未来 Windows 作业系统的新功能都将以 Windows Object 的形式呈现,APIs 只保留旧观。坚持使用 API 的程式当然还是可以跑,但是将没有能力使用作业系统的新特质。Inside OLE2 作者甚至把 OLE2 视为「一种新的复杂的不可思议的物种起源」。

了解基础的 Windows Object 和 Interface 之後,下面是 OLE2 的应用主题。所谓 O-L-E 就是物件联结 (Linked) 与内嵌 (Embedded) 的缩写,我们可以利用 OLE 规格 (这时候你可想像它是一份通讯协定),将甲程式 (Server) 的资料包装起来放在乙程式 (Container) 中。乙程式可以接纳来自不同 Servers 的不同型态的资料(所以乙程式的英文名字是「容器」),组成一份丰富的「文件」。这种文件称为复合文件。复合文件最适合以「复合档案」(Compound File) 的型态储存在磁碟中,原因是复合档案有 Storage 和 Stream 双层结构,意义类似磁碟子目录和档案的关系,很适合放置具有二维树状结构特性的资料。

为了让终端客户面对文件而不是程式 (就像美工面对的是其完稿而不是稿旁的剪刀和浆糊) ,OLE2 发展出所谓的即地编辑,也就是使用者在 Container 的复合文件中面对各式各样的生鲜材料 (OLE Item) 做动作时 (开启或编辑或播放...),Server 会执行起来,将 Container 改头换面。使用者面对的还是原来的视窗,但是表单和工具栏都变了。这些变化是 Server 和 Container 协商的结果,可以经由程式控制。

即地编辑是 OLE1 进化到 OLE2 过程中,在使用者介面上最大的一项改善。OLE2 的另一项新单元是 Automation,基本上这与物件联结与内嵌毫无关系 (图一)。为了达到软体分工合作,有的程式释出资料,有的程式释出功能 (函式),这就是 Automation 的作用。释出 (开放,expose) 资料或函式者,称为 Automation Server,取用资料或函式者,称为 Automation Controller。在这里资料称为 Property,函式称为 Method,与Visual Basic 的称谓一致。开放出来的资料或函式可以被 VC++ 程式或 VB 程式取用。Microsoft 准备令其 Office 软体全部支援一种名为 VBA (Visual Basic for Application) 的语言,此语言非常类似 Visual Basic,可以存取 Office 提供的软体机能,适合用於Automation Controller 这一端。

暖身结束,我们出发吧。

 

背景资料 :
书名 Inside OLE2
作者 Kraig Brockschmidt
出版 Microsoft Press
页数 16 章,977 页
售价 US$ 49.95 (含磁片两片)

Section I Windows Objects
1. An Overview of OLE2
2. Conventions, C++, and Sample Code
3. Objects and Interfaces
4. Component Objects

Section II Object Oriented System Features : Files and Data Transfer
5. Structured Storage and Compound Files
6. Uniform Data Transfer Using Data Objects
7. Clipboard Transfers Using Data Objects
8. Drag-and-Drop Operations Using Data Objects

Section III Compound Documents : OLE
9. Compound Documents and Embedded Containers
10. Compound Documents and Embedded Object Servers (EXEs)
11. In-Process Object Handlers and Servers
12. Monikers and Linking Containers
13. Moniker Binding and Link Sources
14. Conversion, Emulation, and Compatibility with OLE 1

Section IV Compound Documents : In-Place Activation
15. Visual Editing : In-Place Activation and In-Place Containers
16. In-Place Activation for Compound Document Objects

inside-ole2.jpg (18339 bytes)

虽然去年四月已经介绍过这本书的内容、文字、磁片,我还是认为有重新介绍的必要。原因是再把此书细看之後,我发现我对它说的好话太少了,不够公平。再者这一期专门介绍 OLE2,也不能不提此书。已经说过的不会再说,请你叁考以前的书评。这里我只想加一点补充。

如果要找一本深入介绍 OLE2 架构的书籍,这本书是你唯一的选择。本书目的就是要把图一的每一个模组介绍给你。实作范例方面,几乎每一章都有程式,都是以 C++ 完成,但并不架构在 MFC 之上,而是利用 OLE Interfaces。本书以两个一般应用程式成长为 OLE2 Container 和 Server 的过程,说明各项 OLE2 性质以及实作方式。

既然我不鼓励各位从低阶切入写 OLE 应用程式,我也就不鼓励各位去 trace 本书的程式 (那种痛苦不足为外人道)。各章的文字说明值得再三品味,尤其是观念上的解释。这些文字对於初入门者带来多少意义我很怀疑,但的确值得你在多阅读其他书籍多有体会之後,回头来再思索。如果打字够快,建议你边看书边把心得记录下来,甚至整段看过之後有所体会就整段翻译下来。也许一开始你觉得进度迟缓,不耐其烦,但是回头看第二次第三次时的速度,绝对比别人快 5 倍不止 --- 而我可以向你保证,OLE2 的基础观念绝对需要一看再看才能有所体会,尤其你面对的是一本原文书。

本书第一章对 OLE 整体架构做 23 页的介绍,图一的每一个模组都照顾到了。第二章是作者对自己发展的小型类别库做使用上的说明,全书的范例程式都架构在这个不太复杂的类别库中。第三章很有价值,以一个 C++ 范例和一个 C 范例说明 Windows Object 如何实作。由於 C++ 编译器对於虚拟函式提供了虚拟函式表格 (vtbl),以之做为 Interface 最为适当(也许这其实是因不是果),所以 C++ 是制作 Windows Object 最方便的语言。其他语言不是不行,但你必须自行处理函式表格以及建构元的问题 (C 语言的 malloc 只能配置记忆体,不像 C++ 的 new 运算子有呼叫建构元的能力)。

第四章一开始有个小程式,获取 Allocator Object 并呼叫其成员函式,配置记忆体来使用。关於 Windows Object 的使用,这个小程式带给我们很好的观念。本章後面还有范例,那是大灾难的开始。

第五章介绍复合档案,也就是所谓的结构化储存体 (Structured Storage)。在这里我们遭遇两个 Interfaces,一是 IStorage,一是 IStream,前者类似磁碟系统中的子目录,後者类似磁碟系统中的档案。这种二维视野除了有助於复合文件的储存之外,另一个好处是,如果有任何一个档案能够符合此一模型,任何其他也知晓这个模型的程式就能够打开这个档案并审查其内容。举个例,如果复合档案包含了一个stream,内含标准资料结构如 "Summary Information",内有标题、对象、作者、关键字,那麽任何熟悉此结构之应用程式就可以打开此档并决定此文件的标题和对象、决定作者是谁、并可能搜寻关键字。这远优於我们目前所有的档案模型 --- 只有最初写此档案的应用程式,或是私底下拥有该档案格式之知识者,才能够开档并浏览档案内容。有了复合档案,档案浏览器很容易制作出来,於是使用者就可以输入这样的查询 :「找出所有我写的文件中,标题上有 MFC 这个字的文件」。

第六章介绍 Data Object,我们将遭遇 IDataObject。可以说,只要支援 IDataObject,这个 Windows Object 就是一个 Data Object。目前由各个 Windows API 完成的所有行程通讯 (IPC) 机能,包括 clipoboard,DDE 和 OLE1,都被收集在 IDataObject 中;无论你如何获得 Data Object 的指标,你都可以随後以标准的方法对待这些资料。OLE2 把 Data Object 的使用标准化了,把它从传输协定中分离出来。所谓协定,就是通讯双方的一个标准化沟通方式,如此一来双方都同意在某种监视之下做资料传递。截至目前,资料实体的传输都与传输协定紧密地包装 (结合) 在一起,然而在 OLE2,资料的索求或设定脱离了所有的协定,可以大量简化并均匀化你的应用程式的资料交易行为。OLE2 引入两个新的资料结构,提供比较好的剪贴簿格式和比较好的全域记忆体,使我们有更好更丰富的资料描述(而不止是一个 UINT) 和更多的资料传输媒介 (而不止是一个 HGLOBAL)。

我对本书的补充到此为止。面对这本巨着的雀跃 (以为自己有救了) 以及翻开後看不懂的沮丧 (觉得自己很笨),我完全经历过并且深感同情。同情你之馀我还得可怜我自己,因为至今我还努力在这块砖头上缓慢爬行。

此书已有新版 :

inside-ole.jpg (16970 bytes)

 

背景资料 :
书名 Inside Visual C++ (2nd Edition)
作者 David J.Kruglinski
出版 Microsoft Press
页数 第 25 章 86 页 (全书共 26 章 768 页)
售价 US$ 39.95 (含光碟一片)

25. OLE and OLE Automation
Learning OLE
The Common Object Model (COM)
The Problem That COM Solves
The Essence of COM
What Is a COM Interface
The IUnknown Interface and the QueryInterface Member Function
Reference Counting: The AddRef and Release Function
Class Factories
COM and MFC - The CCmdTarget Class
A Working COM Example
OLE and the Windows Registration Database
Run-Time Object Registration
How a COM Client Calls a DLL Server
How a COM Client Calls an EXE Server
MFC and OLE
Containment vs. Inheritance
OLE Automation
Connecting C++ with Visual Basic for Application
Automation Controllers and Automation Servers
Microsoft Excel - A Better Visual Basic Than Visual Basic
Properties, Methods, and Collections
The Problem That OLE Automation Solves
The IDispatch Interface
OLE Automation Programming
The MFC IDispatch Implementation
An OLE Automation Server
An OLE Automation Controller
The VARIANT Type
Parameter and Return Type Conversions for Invoke
OLE Automation Examples
OLE and the Future

insidevcv2.jpg (17153 bytes)

可巧,去年四月我也在介绍 Inside OLE2 的同时介绍了 Inside Visual C++。一年之後本书有了第二版,这第二版也在去年十一月介绍过了,但是并没有特别介绍第 25 章。这一章达 86 页之多,内容很具唯一性,值得挑出来放在本月的 OLE2 专栏。

这一章讲两大主题,一是 OLE2 最基层的 COM (请看图一),一是 Automation。关於 COM,作者实际设计一个名为「太空船」的物件,支援 IUnknown (因此也就符合加入 COM 俱乐部的条件) 以及另两个 Interfaces,然後再设计使用这个太空船物件的 Client 端。这是一个很好的教育案例,比 Inside OLE2 第三章的例子还完备。这只是个模拟程式,原本 Server 应该成为 OLE2 系统的一个DLL,Client 则是一般 Windows 程式,但作者把 Server 和 Client 两部份该做的事放到同一个程式中,并把它做成 QuickWin 程式。除了示范 Component Object 之实作技术,本例的执行结果 (一些 printf 讯息) 也可以帮助你了解 Component Object 的产生程序和 COM 的内部动作,这会对你的 C++ 能力有些长进,因为太空船物件使用 nested classes,颇为复杂。

我不解的是,这个 QuickWin 程式有一个全域物件,全域物件不是更在 main() 或 WinMain() 之前进行建构吗 ? 但我却在 printf 输出讯息上看不到这一部份。於是我把这个程式改制作为 MS-DOS 程式,获得了理想中的输出结果。有没有人能帮我解答这个疑惑 ?

本章剩馀部份全用来介绍 Automation,这是 Inside OLE2 一书缺乏的主题。作者在这部份采用 MFC 设计程式,你的学习重点摆在 Dispatch Map 这个表格上,其形式与 Message Map 极为类似 :

BEGIN_DISPATCH_MAP(CClikDoc, CDocument)
//{{AFX_DISPATCH_MAP(CClikDoc)
DISP_PROPERTY(CClikDoc, "text", m_str, VT_BSTR)
DISP_PROPERTY_EX(CClikDoc, "x", GetX, SetX, VT_I2)
DISP_PROPERTY_EX(CClikDoc, "y", GetY, SetY, VT_I2)
DISP_FUNCTION(CClikDoc, "RefreshWindow", Refresh,VT_EMPTY, VTS_NONE)
DISP_FUNCTION(CClikDoc, "ShowWindow", ShowWindow,VT_EMPTY, VTS_NONE)
//}}AFX_DISPATCH_MAP
END_DISPATCH_MAP()

这个表格内的记录有三种类型,一种用来直接开放 property (例如上面的 text),一种用来间接开放 property (例如上面的 x, y),另一种用来开放 method。对於使用者 (也就是 Controller) 而言,无所谓直接或间接,它都是很直觉地使用。以 VB 程式为例 :

Set clik = CreateObject("Auto.Doc")
clik.Text = Text.Text (
设定 text)
clik.X = X.Text (
设定 x)
Y.Text = clik.Y (
取得 y)

但是当存取动作透过 Automation 机制,传到 Server 端,就不同了。 动作会直接设定 m_str, 动作会引发呼叫 SetX(), 动作会引发呼叫 GetY()。什麽时机使用直接开放 ? 什麽时机使用间接开放 ? 这和 class 设计过程中「究竟使用private 或 public 比较好」,有类似的考量。稍後介绍另一本书时,我会告诉你更具体的衡量标准。

你已经知道了学习重点 : Dispatch Map 表格中的每一笔记录的意义都必须了解。本章在这方面表现如何 ? 还不错。ClassWizard 帮助我们制作 Dispatch Map 的过程,也都有图为例。你在 Dispatch Map 中看到的那些奇奇怪怪的 VT_xxx,也有详细的介绍 --- 那其实是一种应用於 Automation 的资料型态,例如VT_I2 代表 short,VT_I4 代表 long。

本章给了四个 Controller 范例,以 Excel 为操作对象。Excel 是目前唯一支援 VBA 语言的软体 (我不清楚最新的 Word 6.0 有没有)。你可以获得一些真正具有实用价值的乐趣。

示意图还是太少,本章难得的文字内容如果再搭配我为了演讲 OLE 而设计的图,就可以得 100 分。长久以来书籍的制作太过僵化,图就是图,文就是文,好像硬要把它们处理成一个牛头,一个马嘴。这些作者吃北京烤鸭时大概都是先吃面皮,再吃鸭,再吃酱料。我心目中的理想书籍是以图为主,「主」并不一定代表篇幅份量,而是指讲述的中心。我也希望看到打破传统方式的书籍,大胆从图中拉线条到文字说明上,让图文并茂一体成型。看过 How xxx Work 系列书籍吗 ? 那种处理方式就是我认为最理想的。应用软体使用手册以及 How xxx Work 这类比较通俗软调的主题,已经有不少以图为主的书籍,高阶技术书籍则还停留在石器时代 --- 是不是写程式会使人脑袋硬化 ?

 

背景资料 :
书名 OLE2 Classes for The MFC Library
作者 Microsoft Co.
出版 Microsoft Co.
页数 408 页
售价 不详

Part 1 OLE2 Tutorial
1. The Microsoft Foundation OLE2 Classes
2. Contain: Creating an OLE Container
3. Contain Step 1: Creating an OLE Container
4. Contain Step 2: Creating an OLE Container
5. Scribble Step 7: Creating an OLE Server
6. Autoclik: Creating an OLE Automation Server
7. Autoclik Step 1: Creating an OLE Automation Server
8. Autoclik Step 2: Creating an OLE Automation Server
9. Autoclik Step 3: Creating an OLE Automation Server
Part 2 OLE2 Encyclopedia
Part 3 OLE2 Reference

VC++ v1.5 在其 MFC v2.5 中增加了 OLE2 相关类别,并提供这一本「小」册子 (「才」400 页出头)。如果你想学习以 MFC 2.5 写 OLE2 各式应用程式,就是这本。可惜 VC++ 1.5 采 CD-ROM 包装,所有书籍都光碟化了,你若需要印刷形式的书籍,必须另外购买;糟的是如果不想花三千多元买与 VC++ 1.0 大部份雷同的手册,而只是想要其中的 OLE2 和 ODBC 两本新书,办不到。

一般人对於软体随附的「手册」印象不佳,兴趣不大,觉得店头的「市场书」比较有启发性。这要看手册的定义怎麽下。API 函式或 C 函式库规格列表,当然不具启发性,只是供你查阅用;Windows API Bible 这样的书虽然也是一一介绍函式,却有范例以及作者心得,就深具教育功能。不过,当心,你也可能会买到名为Bible 却是函式规格列表的书籍,真是旧词新解,让我对所谓 Bible 有了新的体会。朋友说要送我当枕头,我说我不要,太高了。

这本「小」书有三部份,第三部份是标准的手册形式,一一介绍 OLE Classes 的成员函式,共 237 页。第二部份是 OLE 百科全书 (名词解释),共 73 页,很不错的一份总体介绍。已有点概念,想学习 OLE 程式设计的人,第一部份 (98 页) 最具价值。这一部份以 Visual C++ 中三个 OLE2 范例程式为对象,解释 MFC OLE2 Classes 的运用。这三个范例分别是一个 Server,一个 Container,和一个 Automation Server,每个范例都分为数个步骤,一层一层增加功能,是很理想的学习方式。有光碟而没书籍的人,我建议你把这 98 页印出来,喔,这三个程式的完整原始码最好一并印出。说到列印,我还建议你买一台雷射印表机,没哪个电脑设备的功能价格比高於这玩意儿的了。上次我从纽约带回一台 600 DPI,比国内便宜一万元,抵机票差不多够,划算。目前许多资讯来自网路或光碟片,有一台安静快速的雷射印表机,是学习的利器。

回到书上。Server 范例是一个名为「涂鸦」(scribble) 的程式,这个程式有七个步骤,本书只介绍步骤七 (增加 OLE 功能),如果你要了解其前身,套件中的 Class Library User's Guide 有完整说明。Container 的设计比 Server 单纯许多,所以你应该从这里开始。Automation Server 则示范各式各样的「开放」: 资料直接开放、资料间接开放、函式开放,以及高阶技术: 把 A 物件视为 B 物件的一个 property 开放出去(这种情况下,透过 Automation 机制传递的,是 IDispatch 指标)。

本书举的这三个 OLE 程式实例的完整度无庸置疑,只是由於篇幅嫌少,说明就显得不够详尽。最理想的情况是,首先应该告诉读者,AppWizard 做出来的码长什麽样子,具备什麽功能;这些骨干程式码中每一个为了 OLE 功能而使用的类别以及呼叫的函式,都应该详细说明;然後,在一步一步加上某些功能的过程中,我应该一步一步加上什麽码。如果能够说明程式进行时使用者的某些关键动作会执行程式中的哪一些码,就能够澄清读者的所有疑虑。噢,OLE 的书已经够少了,做到这一部份的更是一本也没有。这一部份不容易写,我自己也是以除错器设定中断点,才能概略了解其内部过程,甚至许多行为还得半推半敲,因为 Visual C++ 的除错器只能让你观察一个程式,而 OLE 却是两个程式的交通行为。如果想观察即地编辑时 Server 的行为,虽然以除错器设定了 Server 的中断点,你却必须在 Visual Workbench 中载入 Container,於是观察不到 Server 的原始码。怎麽办呢 ? 我不知道 (大概只能在 Server 原始码中加一些 TRACE 巨集然後观察DebugWin 视窗了)。

背景资料 :
书名 Heavy Metal OLE 2.0 Programming
作者 Steve Holzner
出版 IDG Books
页数 10 章,574 页
售价 US$ 39.95 (含磁片一片)

1. A review of Visual C++
2. Creating Containers
3. Creating Servers
4. Managing Multiple OLE Items
5. Power Container Programming
6. Power Server Programming
7. Inside Drag and Drop
8. Inside OLE Automation
9. Beyond MFC: Reaching the OLE Interfaces Directly
10. Visual C++'s OLE Tools

「有一种.... 叫作酷...」,李亚明的这首歌,做为这本书的销售主题曲,一定正点。封面上斗大的 Heavy Metal 泛着冷冷的银辉色;两个齿轮大概是象徵软体合作,经过处理後的诡谲色彩使整张图倒像是密宗的灵图。

有了上一本书,原本我以为这本书只是聊备一格。细看过後才发现,它还是相当有价值的。本书与上一本书形式十分接近,也都是以 MFC 撰写 OLE2 应用程式,也都是以一个个步骤,分章累加不同程度的功能,但它的安排方式比较理想一些 : 先讲一个简单的 Client,再讲一个简单的 Server,然後在 Client 上改善一些,再在 Server 上也改善一些。作者解释函式码时,稍嫌浪费篇幅 : 如果函式有三大机能,每个机能各 10 行码,他会先列出前 10 行讲第一个机能,再列出前 20 行讲第二个机能,再列出完整的 30 行讲第三个机能。这样一段一段累加程式码,页数的代价高昂,而每章最後又是一个程式码总列表。当然啦,看书时你的成就感会比较高,唰唰唰一下子又过去了好几页。

设计程式时有人喜欢把功能切分得很细,每一小段功能独立为一个函式,於是可被重复使用的机率也就比较高。但我不喜欢在书上看到这种情况,函式 call 来 call 去令人头昏眼花。本书程式在功能切割方面比上一本书单纯一些,因此比较容易消化。

第一章介绍 MFC 程式设计的大观念。有没有必要在 OLE 书中来上这麽一章,我个人的看法前面已经表示过了。第二章介绍一个基本的 Container,示范如何在骨干程式码中加入下列功能 :

1. 获得 Server 端的 Item 尺寸。这牵扯到 OnUpdate、OnGetItemPosition、 OnChangeItemPosition 等虚拟成员函式,以及一个呼叫 GetExtent 的辅助函式。 我敢说初学者一定让这四个函式搞得焦头烂额。你可以利用 Debug 模式观察 程式的运作过程,进而了解到 UI 上的什麽动作会引发这些函式被呼叫。

2. 以滑鼠操作 Item,包括搬移、缩放、选择、开启。最上层动作 就是改写 OnLButtonDown 以及 OnLButtonDblClk。你大概会以为 Container 文件中 各个 Item 的外围四方框,以及滑鼠经过 Item 时的各种游标变化,都是自然天成, 错!其实那都是程式员的责任。前者利用 CRectTracker 画上去,後者是程式员改写 OnSetCursor 函式而完成的。

3. 支援物件联结。

第三章介绍一个基本的 Server,示范如何在骨干程式码加上以下功能 :

1. 设定 OLE item 的最初大小 (利用 OnGetExtent)。这个尺寸就是你在 Container 安插一个内嵌物件时 (即地编辑),视窗之最初大小。

2. 设计 OLE Item 资料型态 (本例只是简单的一个 CString 和一个 CPoint 阵列)。

第四章介绍一个稍稍加强的 Container :

1. 支援许多个 OLE Items。这本是天经地义的事,但骨干程式码中并未做这种 安排。为了完成此任务,你必须考虑到绘图以及滑鼠操作两件事情,前者 修改 OnDraw(),令它有 while 回路巡访各个 Items,後者修改 LButtonDown(), 增加滑鼠落点测试;当然你也必须修改 LButtonDblClk(),使它能够 对滑鼠选中的那个 Item 做动作。

2. 增加数个表单项目和工具栏按钮,使能够选择前一个或後一个 Item,或对 目前选择到的 Item 做动作。

3. 加上剪贴簿的拷贝功能。

第五章介绍一个更为强化的 Container,加强的部份在於 :

1. 改变 OLE Item 在 Container 端的大小,但不影响它在 Server 端的大小。 从这个例子你可以明白一个 OLE Item 其实在 Server 和 Container 两端都有 一份尺寸。我们只要在 Container 维护两个资料成员,分别贮存两份数值即可 解决这个问题。

2. 以图示 (icon) 显示 OLE Item。这很简单,在 OnDraw() 中利用 Item 的 SetDrawAspect() 设定 DVASPECT_ICON 属性即可 (平常是 DVASPECT_CONTEXT)。

3. 复制一份 OLE Item。这也非常简单,呼叫 Item 的 CreateCloneFrom() 即可。

4. Undo 功能 - 如果我们能够维护一份复制的 Item,我们就随时可以「回复上一动」。 不过你要注意,为了 Undo 而复制的这个 Item,你应该让它成为隐形人 (利用 DoVerb(OLEIVERB_HIDE))。

5. 改善绘图效率,这主要是利用 document 的 UpdateAllViews 函式中的 pHint 叁数, 因为它可以传递给 view 的 OnUpdate 函式,让它在采取绘图措施时有所依据。

第六章介绍一个加强的 Server :

1. 解释即地编辑时 Container 和 Server 两端的表单与工具栏的合并原则。

2. 改变 Server 的文件名称。

3. 设计准备出现於即地编辑时的 Container 表单上的两个项目 (一个用来改变 资料内容,一个用来改变资料位置)。

4. 强迫 Server 为独立开启式 (OLE1 风格) 而不支援即地编辑。这很简单,即地 编辑时 OLE 会通知 Server 的 OnShow() 函式,我们只要修改它,令它呼叫 OnOpen() 即可偷天换日。Server 独立开启时 OLE Item 方框中密布的斜线是谁 为我们加上去的 ? 还会有谁呢,当然是你自己。

5. 支援以同一份 Server Item 产生数份联结物件。

6. 让 CEditView 成为 Server view 的基础类别。这引发的问题是,资料内容储存在 CEditView 物件的内部缓冲区中,我们如何让 Server Item 的 OnDraw 函式也能够 正常绘图 ? 显然你必须从缓冲区中取出资料放入 document,再让 Server Item 的 OnDraw 函式从 document 中取资料出来画。

第七章把拖拉 (Drag & Drop) 功能加到程式中。你将接触到发送端的 DoDragDrop,以及收到端的 OnDrop、OnDragEnter、OnDragOver、OnDragLeave 等函式。记得 SDK 范例中有一个程式示范如何设计橡皮盒 (以滑鼠拖拉一个可变大小的四方形),上述最後三个函式和你处理橡皮盒的原理是一样的 : 滑鼠第一次进入领空时,把四方形画一次;滑鼠飞越视窗上空时,把原四方形拭去并画上新四方形;滑鼠飞出领空时,做善後处理并结束整个程序。不过在这里我们并不绘制四方形,而是呼叫 dc.DrawFocusRect(),那就是你在拖拉过程中看到的物件虚线外框。

第八章介绍 Automation。共有五个例子,第一个例子直接开放资料,由 Controller 取得後显示出来。第二个例子直接开放 A,B 两笔资料,间接开放 C 一笔资料。C 资料将是A,B 的总和,由 Controller 的某个按键事件发生後取出显示。这种情况下不做间接开放是绝对不行的,因为 C 的取得一定得透过 Server 的运作,才会有正确的结果(让 C = A + B)。对於直接开放或间接开放之使用,此例带给你具体建议: 如果Controller 对 Server 资料之取得或设定,必须在Server 的监督之下进行,那麽就必须使用间接开放。第三个例子是开放一个函式,让 Controller 能够开启 Server。第四个例子示范让开放函式带有叁数。第五个例子也开放一个函式,允许 VB 程式取得 VC 程式的视窗 handle,随後 VB 程式呼叫三个 Windows API,在 VC 程式的视窗中画一条直线。

第九章介绍如何使用 OLE Interfaces。一共有三个例子,前两个只是把 MFC OLE Classes 中的某几个成员函式以 Interface 函式替换之,第三个例子比较有点意思,利用 Interface 函式获得一些 MFC Classes 得不到的资讯。

安装 Visual C++ 时你会得到一个 [OLE2 Toolkit] group,内有十来个工具,可以辅助我们开发 OLE 应用程式。很遗憾的是 Microsoft 没有提供这些工具的使用说明。本书第十章介绍的正是这些 OLE 工具,可惜不够深入 (甚至可以用肤浅来形容),它的作用大概只能提醒你这些工具的存在而已。LRPC Spy 和 IdataObject Viewer 所观察到的资讯,对 OLE 两端通讯动作带来一些蛛丝马迹,但是本章根本没有介绍所获得的资讯的意义。为德不卒 !!

■三两心得

很乐意藉这个机会提出学习 OLE 程式设计的三两心得。

首先你必须弄清楚,OLE 应用程式的哪些功能是 AppWizard 为我们产生的,哪些功能得劳动你自己加上去。一开始我以为滑鼠对 Item 的单击 (意味"select")、双击 (意味 "activate")、拖拉,以及每个 Item 周围的边框、框的形式变化、滑鼠在框上的游标变化,都是「自然」造就的,也就是说我以为那都是 OLE Classes 的内建能力,但其实都不是 (可怜咱们这些程式员)。这些标准行为与性质并不牵扯应用程式的资料结构,我不知道为什麽 Microsoft 的 OLE2 Classes 不把这些性质收容进去 (也许我该试试 Borland 的 OCF)。有「事件驱动」系统设计经验的朋友就会知道,这些性质放在 Classes 之中并非办不到。

再来要弄清楚的,是通讯两端所使用的 OLE2 类别 : 谁对谁沟通,谁在什麽时机上场演出。例如 Server View 有一个 OnDraw 函式,Server Item 也有一个同名函式,谁在什麽时候被呼叫 ? 又例如 Server Document 和 Server Item 各有一个 Serialize 函式,谁又在什麽时候被呼叫 ? 另外,Container 一开始欲决定 Item 尺寸时,呼叫的 GetExtent 函式系由 Server 的 OnGetExtent 函式回应之,Server 资料改变时呼叫的 NotifyChanged 函式系由Container Item 的 OnChange 函式承接,Container View 的 OnDraw 函式呼叫每一个 Item 的 Draw 函式时系由 Server Item 的 OnDraw 函式承接,凡此种种都必须搞清楚。我举这些拉拉杂杂的例子,总括一句话就是,你必须知道一来一往的函式关系。这些知识非常难得,没有一本书提到它(Heavy Metal 那本提到一点点)。在这样的书出来之前,自己用除错器设中断点观察吧。

有这些扎实的基础後,再来就是多看例子,多读书和期刊。一本书绝对不够,知识无价,书钱别省。还有,MFC 原始码在你手上,必要时进去巡幽访胜一下,对你的基础观念有绝对的帮助。别忘了做笔记,没有经过自己整理消化的知识,短暂如过眼云烟。

■ 天才老师

美静 (我的妻子) 学的是音乐。基本上音乐系学生有着比一般人更早遭遇更多压力的机会,因为他们要面对成百上千人演奏 (想像台下黑压压一片人头看着你一举一动达两个小时的情景),也因为他们的学习过程中很早就接触到一对一的专任教师 (所以音乐界极重师承)。我们常常交换彼此的学习过程与教学经验,一致的看法是: 最怕天才型指导教授。

这话需要前提: 我与美静都是中等资质。一个人在学习过程中有名师指导,照说最是幸运不过。强将手下无弱兵的压力虽然时时刻刻督促你挺起胸膛迎接挑战,但做为一个学生,这是自我应该克服的;我之所以觉得给天才型老师教导的大不幸是,他从来不能了解你的疑惑在哪里。「为什麽你这里不懂」?「为什麽你那里不懂」? 你的天才老师没有在那麽「理所当然」的地方触礁过,如何能知道你的迷惑 ? 如果一个人拥有绝对音感,怎麽能够想像另一个人竟然把 Do 听成 Sol 而不自觉 ?

学习 OLE 的过程中,我对於天才老师的恐惧日复一日。所有的 OLE 书籍好像都是天才工程师写出来的。你不能够说它深度不够,也不能够说它广度不足,你甚至不能够说它有什麽窒碍难行之处,可是它只告诉你 How,就是不告诉你 Why。作者都以非常「理所当然」的态度告诉你采用哪一个 Class,设计哪一个函式,却从不告诉你到底什麽时机这个函式会被呼叫,不告诉你这个函式和哪个函式彼此有什麽关联。这些显而易见正常该有的问题,为什麽作者们都看不见呢 ? 我自认为比一般人有更多的技术优势: 我懂得 C++、会运用 MFC、清楚 Windows 程式的运作、也对 Windows 作业系统有相当程度的了解、甚至对於 Clipboard 和 DDE 和 DDEML 等行程通讯方法的涉猎也还算深入;显然,发生在我身上的 OLE 疑惑绝不是因为该有的基础不够。

天才老师不只存在於 OLE 书籍中。目前许多程式工具 (尤其是 Application Framework 这种东西),把写程式这件事情包装得像玩游戏一样,并且想尽办法让你相信写程式从此轻松快乐。许许多多相关书籍也就蜻蜓点水般地尽在表面做功夫,从来不曾稍为深入一点点探索其中道理。我曾经给过「MFC 程式设计」的演讲题目,会後问卷调查,学员最喜欢的内容是 MFC 程式从生到死的来龙去脉,包括 WinMain 在哪里,包括视窗类别的注册与视窗的开启,也包括讯息回路和视窗函式,以及讯息的绕行与映射。原来,和侯捷一样认为这些东西很重要的人,所在多有。这些隐藏在黑盒子中的知识,才真正能够带给学习者对於黑盒子的全然掌握。你若不了解 (或至少适度了解) 你的爱车内部运作原理,绝对不可能驾御它发挥极致。这麽说,这些用来为程式员节省精力的黑盒子岂不是没有达到它的黑盒效应 ? 噢,我想黑盒子的价值在於「有人帮你把事情做好了」,而不是连「花心思深入了解」的努力都可以省略。研究别人写的码 (那些 Classes),毕竟比自己写码轻松太多--- 况且专家做出来的黑盒子故障率也低些。

也许你反问我看电视之前难道必须先知道阴极射线映像管的原理吗 ? 非也非也,电视观众像是软体终端客户,你别把自己放错位置,你是研发人员。

不只是写程式,软体的使用一样得有系统有组织地进行,而一般应用软体学习手册却只教我们选这个按那个,把系统化和组织化的工作留给读者。天才老师也存在於许多 User's Guide 书籍中,可怜!可怜!

将学问刨根究底到什麽程度,身为工程师自己当有所拿捏。一个人的成就与他做学问的态度有密切关系,你若要得心应手,千万别忽略了基础知识。而在没有知识来源的情况下,善用你的 Debugger 和 TRACE,似乎是唯一可行之道。

另一种天才型式不表现在书籍内容,而在书名。也许你有这样的经验: 爬山爬得气喘嘘嘘,迎面走来吹着口哨的快乐下山人,於是你问他还有多远到达山顶。你可能得到两种答案,一种人基於鼓励 (或者因为他是健脚),告诉你再过二十分钟光明顶可及;而你在过了第三个二十分钟後,依然气喘嘘嘘地询问吹着口哨的快乐下山人...。另一种人据实回答,告诉你前面还有三百六十五里路,二十里处有一座危险断崖,四十里处溪流湍急,六十里处遇食人族一村,有身首异处之虞...,於是你有了心里准备 (当然也可能打起退堂鼓)。我渴望知道的是实情。我不是健脚,而我相信大多数人也都不是健脚,需要过来人将路途之艰辛明白以告。给别人快乐和希望,却使他误判军机,不能不说是一种遗憾。谁要是告诉我八天学「会」MFC,二十一天学「会」OLE,只有把「会」这个字拿掉,我才能点头。拿到驾照不敢上路并不能够叫做「会」开车,只会划水不会换气也不能够叫做「会」游泳。

■ 两周年

虽然你在 95 年二月份看到这篇稿子,此刻却是 1994 年的最後一天,只剩两个小时。这个园地已经历两个整年,朋友们的关怀和建议,都使我感动并倍觉荣幸。700 个日子的耕耘,侯捷努力以报各位的信任。容我在这里许个小小心愿,愿大家更热烈叁与谈书,更主动表达意见,更积极监督国内出版品质,爱护好作品扬弃坏作品,让良币驱逐劣币 (从历史来看,这个心愿大概不会实现)。 


侯捷 2010-07-28 06:59:18

[新一篇] 你正站在十字路口 (2)—論 Application Framework 軟體開發工具

[舊一篇] 我的電腦探索
回頂部
寫評論


評論集


暫無評論。

稱謂:

内容:

驗證:


返回列表