技术。但幸运的事,这一切终归要结束了。
让我们从这个目的出发看看COM为什么会成为它现在的样子。其实COM不是什么新玩意,最初的DLL就是重用二进制代码的技术。DLL在C的年代可能还不错,但到了C++的年代就不行了。原因在于如果你在.h文件中改变了类定义(增加或者减少了成员变量),代码库用户的代码必须重新编译才可以,否则用户的代码会按你的旧类的结构为你的新类分配内存,这将产生什么后果可想而知。这就是为什么通过接口继承和通过接口操作对象成为COM的强制规范的原因,能够通过对象的方式组织代码是COM的重要努力。那么著名的IUnknown接口又是怎么回事呢?这是为了让使用不同编译器的C++开发人员能够共享劳动成果。
首先看QueryInterface,因为COM是基于接口的,那么一个类可能实现了几个接口,而对于用户来说,你又只能通过操作接口来操作类,这样你必须把类造型成某个特定的接口,使用Dynamic_cast吗?不行,因为这是编译器相关的,那么,就只好把它放在类的实现代码中了,这就是QueryInterface的由来。至于AddRef和Release,他们产生的第一个原因是delete这个操作要求一个类具有虚析构函数(否则的话,他的父类的析构函数将不会被调用),但不幸的是不同的编译器中析构函数在vtbl中的位置并不相同,这就是说你不能让用户直接调用delete,那么你的COM对象必须提供自己删除自己的方法;另外的原因在于一个COM对象可能作为几个接口在被用户同时使用,如果用户粗暴的删掉了某个接口,那么其他的接口也就不能用了,这样,只好要求用户在想用一个接口的时候通过AddRef通知COM对象“我要使用你了,你多了一个用户”,而在不用之后要调用Release告诉COM对象“我不用你了,你少了一个用户”,如果COM对象发现自己没有用户了,它就把自己删掉。
再看看诡异的HRESULT,这是跨语言和跨编译器的代价。其实,异常处理
是物竞天择的结果——连一直用效率作标榜的C++都肯牺牲效率来实现这个try-catch,可见它的意义,但COM为了照顾那些低级的语言居然抛弃了这个特征——产生的结果就是HRESULT。我们可以看看他是多么愚蠢的东西。首先,我们要通过一个整数来传递错误信息,通过IErrorInfo来查找真正的错误描述,本来在现代语言中一个try-catch可以解决的问题,现在我们却需要让用户和开发者都走很多路才能解决,你怎么评价这个结果?其次,由于这个返回计算结果的位置被占用了,我们不得不用怪异的out参数来返回结果。想想一个简单的int add(intop1,intop2)在COM中竟然要变成HRESULT add(intop1,intop2,int* result),我不知道你对此作何感想。而且由于COM的方法无法返回一个对象而只能返回一个指针,为了完成一个简单的std::string GetName()这一类的操作,你要费多少周折——你需要先分配一块内存空间,然后在COM实现中把一个字符串拷贝到这个空间,用完了你要删掉这个空间,不知道你是否觉得这种
工作很幸福,反正我觉得很痛苦。还有COM为了照顾那些解释性的语言,又加入了Automation技术,你有没有为此觉得痛苦?本来一个简单的方法调用,现在却需
要传给你一个标志变量,然后让你根据这个标志变量去执行相应的操作。(这一点我现在仍然不明白,为什么解释性的语言需要通过这个方式来执行一个方法)。“我受够了,我觉得头痛”,你会说,是啊,我想所有的人都受够了,所有这些因素实际上是把COM技术变成了一头让人无法驾驭的怪兽。
人对复杂事物的掌控能力终究是有限的,C++本身已经够复杂了,COM