`

软件发展

 
阅读更多
 软件工程发展到今天,从一开始的结构化编程,到面向对象编程,再到现在的COM,编程,目
标只有一个,就是希望软件能象积方块一样是累起来的,是组装起来的,而不是一点点编出来的。
结构化编程是函数块的形式,通过把一个软件划分成许多模块,每个模块完成各自不同的功
能,尽量做到高内聚低藕合,这已经是一个很好的开始,我们可以把不同的模块分给不同的人去
做,然后合到一块,这已经有了组装的概念了。软件工程的核心就是要模块化,最理想的情况就
是100%内聚0%藕合。整个软件的发展也都是朝着这个方向走的。结构化编程方式只是一个开始。
下一步就出现了面向对象编程,它相对于面向功能的结构化方式是一个巨大的进步。我们知道整
个自然界都是由各种各样不同的事物组成的,事物之间存在着复杂的千丝万缕的关系,而正是靠
着事物之间的联系、交互作用,我们的世界才是有生命力的才是活动的。我们可以认为在自然界
中事物做为一个概念,它是稳定的不变的,而事物之间的联系是多变的、运动的。事物应该是这
个世界的本质所在。面向对象的着眼点就是事物,就是这种稳定的概念。每个事物都有其固有的
属性,都有其固有的行为,这些都是事物本身所固有的东西,而面向对象的方法就是描述出这种
稳定的东西。而面向功能的模块化方法它的着眼点是事物之间的联系,它眼中看不到事物的概念
它只注重功能,我们平常在划分模块的时侯有没有想过这个函数与哪些对象有关呢?很少有人这
么想,一个函数它实现一种功能,这个功能必定与某些事物想联系,我们没有去掌握事物本身而
只考虑事物之间是怎么相互作用而完成一个功能的。说白了,这叫本末倒置,也叫急功近利,因
为不是我们智慧不够,只是因为我们没有多想一步。面向功能的结构化方法因为它注意的只是事
物之间的联系,而联系是多变的,事物本身可能不会发生大的变化,而联系则是很有可能发生改
变的,联系一变,那就是另一个世界了,那就是另一种功能了。如果我们用面向对象的方法,我
们就可以以不变应万变,只要事先把事物用类描述好,我们要改变的只是把这些类联系起来方法
只是重新使用我们的类库,而面向过程的方法因为它构造的是一个不稳定的世界,所以一点小小
的变化也可能导致整个系统都要改变。然而面向对象方法仍然有问题,问题在于重用的方法。搭
积木式的软件构造方法的基础是有许许多多各种各样的可重用的部件、模块。我们首先想到的是
类库,因为我们用面向对象的方法产生的直接结果就是许多的类。但类库的重用是基于源码的方
式,这是它的重大缺陷。首先它限制了编程语言,你的类库总是用一种语言写的吧,那你就不能
拿到别的语言里用了。其次你每次都必须重新编译,只有编译了才能与你自己的代码结合在一起
生成可执行文件。在开发时这倒没什么,关键在于开发完成后,你的EXE都已经生成好了,如果
这时侯你的类库提供厂商告诉你他们又做好了一个新的类库,功能更强大速度更快,而你为之心
动又想把这新版的类库用到你自己的程序中,那你就必须重新编译、重新调试!这离我们理想的
积木式软件构造方法还有一定差距,在我们的设想里希望把一个模块拿出来再换一个新的模块是
非常方便的事,可是现在不但要重新编译,还要冒着很大的风险,因为你可能要重新改变你自己
的代码。另一种重用方式很自然地就想到了是DLL的方式。Windows里到处是DLL,它是Windows
的基础,但DLL也有它自己的缺点。总结一下它至少有四点不足。(1)函数重名问题。DLL里是一
个一个的函数,我们通过函数名来调用函数,那如果两个DLL里有重名的函数怎么办?(2)各编译
器对C++函数的名称修饰不兼容问题。对于C++函数,编译器要根据函数的参数信息为它生成
修饰名,DLL库里存的就是这个修饰名,但是不同的编译器产生修饰的方法不一样,所以你在VC
里编写的DLL在BC里就可以用不了。不过也可以用extern "C";来强调使用标准的C函数特性,关闭
修饰功能,但这样也丧失了C++的重载多态性功能。(3)路径问题。放在自己的目录下面,别人
的程序就找不到,放在系统目录下,就可能有重名的问题。而真正的组件应该可以放在任何地方
甚至可以不在本机,用户根本不需考虑这个问题。(4)DLL与EXE的依赖问题。我们一般都是用隐
式连接的方式,就是编程的时侯指明用什么DLL,这种方式很简单,它在编译时就把EXE与DLL绑
在一起了。如果DLL发行了一个新版本,我们很有必要重新链接一次,因为DLL里面函数的地址可
能已经发生了改变。DLL的缺点就是COM的优点。首先我们要先把握住一点,COM和DLL一样都是
基于二进制的代码重用,所以它不存在类库重用时的问题。另一个关键点是,COM本身也是DLL,
既使是ActiveX控件.ocx它实际上也是DLL,所以说DLL在还是有重用上有很大的优势,只不过我们
通过制订复杂的COM协议,通COM本身的机制改变了重用的方法,以一种新的方法来利用DLL,来
克服DLL本身所固有的缺陷,从而实现更高一级的重用方法。COM没有重名问题,因为根本不是通
过函数名来调用函数,而是通过虚函数表,自然也不会有函数名修饰的问题。路径问题也不复存
在,因为是通过查注册表来找组件的,放在什么地方都可以,即使在别的机器上也可以。也不用
考虑和EXE的依赖关系了,它们二者之间是松散的结合在一起,可以轻松的换上组件的一个新版
本,而应用程序混然不觉。


二、用VC进行COM编程,必须要掌握哪些COM理论知识
我见过很多人学COM,看完一本书后觉得对COM的原理比较了解了,COM也不过如此,可是就
是不知道该怎么编程序,我自己也有这种情况,我经历了这样的阶段走过来的。要学COM的基本
原理,我推荐的书是《COM技术内幕》。但仅看这样的书是远远不够的,我们最终的目的是要学
会怎么用COM去编程序,而不是拼命的研究COM本身的机制。所以我个人觉得对COM的基本原理不
需要花大量的时间去追根问底,没有必要,是吃力不讨好的事。其实我们只需要掌握几个关键概
念就够了。这里我列出了一些我自己认为是用VC编程所必需掌握的几个关键概念。(这里所说的
均是用C语言条件下的COM编程方式)
(1) COM组件实际上是一个C++类,而接口都是纯虚类。组件从接口派生而来
    我们可以简单的用纯粹的C++的语法形式来描述COM是个什么东西:
class IObject
{
public:
virtual Function1(...) = 0;
virtual Function2(...) = 0;
....
};
class MyObject : public IObject
{
public:
virtual Function1(...){...}
virtual Function2(...){...}
....
};
看清楚了吗?IObject就是我们常说的接口,MyObject就是所谓的COM组件。切记切记接口都
纯虚类,它所包含的函数都是纯虚函数,而且它没有成员变量。而COM组件就是从这些纯虚
类继承下来的派生类,它实现了这些虚函数,仅此而已。从上面也可以看出,COM组件是以
C++为基础的,特别重要的是虚函数和多态性的概念,COM中所有函数都是虚函数,都必须通
过虚函数表VTable来调用,这一点是无比重要的,必需时刻牢记在心.
COM规范规定任何组件、任何接口都必须从IUnknown继承,IUnknown包含三个函数,分别是
QueryInterface、AddRef、Release。这三个函数是无比重要的,而且它们的排列顺序也是不
可改变的。QueryInterface用于查询组件实现的其它接口,说白了也就是看看这个组件的父
类中还有哪些接口类,AddRef用于增加引用计数,Release用于减少引用计数。引用计数也
是COM中的一个非常重要的概念。大体上简单的说来可以这么理解,COM组件是个DLL,当客
户程序要用它时就要把它装到内存里。另一方面,一个组件也不是只给你一个人用的,可能
会有很多个程序同时都要用到它。但实际上DLL只装载了一次,即内存中只有一个COM组件,
那COM组件由谁来释放?由客户程序吗?不可能,因为如果你释放了组件,那别人怎么用,
所以只能由COM组件自己来负责。所以出现了引用计数的概念,COM维持一个计数,记录当前
有多少人在用它,每多一次调用计数就加一,少一个客户用它就减一,当最后一个客户释放
它的时侯,COM知道已经没有人用它了,它的使用已经结束了,那它就把它自己给释放了。
引用计数是COM编程里非常容易出错的一个地方,但所幸VC的各种各种的类库里已经基本上
把AddRef的调用给隐含了,在我的印象里,我编程的时侯还从来没有调用过AddRef,我们
只需在适当的时侯调用Release。至少有两个时侯要记住调用Release,第一个是调用了
QueryInterface以后,第二个是调用了任何得到一个接口的指针的函数以后,记住多查MSDN
以确定某个函数内部是否调用了AddRef,如果是的话那调用Release的责任就要归你了。
IUnknown的这三个函数的实现非常规范但也非常烦琐,容易出错,所幸的事我们可能永远也
不需要自己来实现它们。
IClassFactory的作用是创建COM组件。我们已经知道COM组件实际上就是一个类,那我们平
常是怎么实例化一个类对象的?是用‘new’命令!很简单吧,COM组件也一样如此。但是谁
来new它呢?不可能是客户程序,因为客户程序不可能知道组件的类名字,如果客户知道组
件的类名字那组件的可重用性就要打个大大的折扣了,事实上客户程序只不过知道一个代表
着组件的128位的数字串而已,这个等会再介绍。所以客户无法自己创建组件,而且考虑一
下,如果组件是在远程的机器上,你还能new出一个对象吗?所以创建组件的责任交给了一
个单独的对象,这个对象就是类厂。每个组件都必须有一个与之相关的类厂,这个类厂知道
怎么样创建组件,当客户请求一个组件对象的实例时,实际上这个请求交给了类厂,由类厂
创建组件实例,然后把实例指针交给客户程序。这个过程在跨进程及远程创建组件时特别有
用,因为这时就不是一个简单的new操作就可以的了,它必须要经过调度,而这些复杂的操
作都交给类厂对象去做了。IClassFactory最重要的一个函数就是CreateInstance,顾名思议
就是创建组件实例,一般情况下我们不会直接调用它,API函数都为我们封装好它了,只有
某些特殊情况下才会由我们自己来调用它,这也是VC编写COM组件的好处,使我们有了更多
的控制机会,而VB给我们这样的机会则是太少太少了。
IDispatch叫做调度接口。它的作用何在呢?这个世上除了C++还有很多别的语言,比如VB、
VJ、VBScript、JavaScript等等。可以这么说,如果这世上没有这么多乱七八糟的语言,那
就不会有IDispatch。:-) 我们知道COM组件是C++类,是靠虚函数表来调用函数的,对于VC来
说毫无问题,这本来就是针对C++而设计的,以前VB不行,现在VB也可以用指针了,也可以
通过VTable来调用函数了,VJ也可以,但还是有些语言不行,那就是脚本语言,典型的如
VBScript、JavaScript。不行的原因在于它们并不支持指针,连指针都不能用还怎么用多态
性啊,还怎么调这些虚函数啊。唉,没办法,也不能置这些脚本语言于不顾吧,现在网页上
用的都是这些脚本语言,而分布式应用也是COM组件的一个主要市场,它不得不被这些脚本
语言所调用,既然虚函数表的方式行不通,我们只能另寻他法了。时势造英雄,IDispatch应
运而生。:-)  调度接口把每一个函数每一个属性都编上号,客户程序要调用这些函数属性的
时侯就把这些编号传给IDispatch接口就行了,IDispatch再根据这些编号调用相应的函数,仅
此而已。当然实际的过程远比这复杂,当给一个编号就能让别人知道怎么调用一个函数那不
是天方夜潭吗,你总得让别人知道你要调用的函数要带什么参数,参数类型什么以及返回什
东西吧,而要以一种统一的方式来处理这些问题是件很头疼的事。IDispatch接口的主要函数
是Invoke,客户程序都调用它,然后Invoke再调用相应的函数,如果看一看MS的类库里实现
Invoke的代码就会惊叹它实现的复杂了,因为你必须考虑各种参数类型的情况,所幸我们不
需要自己来做这件事,而且可能永远也没这样的机会。:-) 
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics