昨天看了《COM本质论》的第一章”COM是一个更好的C++”,觉得很有必要做一些笔记,于是整理成这篇文章,我相信你值得拥有。

这篇文章主要讲的内容是:一个实现了快速查找功能的类FastString,在一个小小的需求之后,慢慢的演变成一个COM组件的过程。

类FastString实现了一个快速查找字符串的功能,快到时间复杂度是O(1),我们先不管作者是怎么实现的,估计是通过空间换时间。由于这个类查找字符串很快,于是作者就把这个类当做一个产品,以源码的方式卖给需要的厂商,厂商用后感觉很好,但有的厂商想要获得字符串长度的功能,他们觉得strlen(str)速度太慢,毕竟这个函数获取字符串的长度是线性的,时间复杂度是O(N),于是作者决定修改他的FastString,其内心一直在告诉自己:我的FastString必须是Fast。

我们先来看看作者FastString的样子:

class FastString
{
public:
FastString(const char* str);
FastString(void );
int Find (const char* str );
private:
char* m_str ;
};

可别小看这个类,它查找字符串可快了(我也不知道为什么它就他妈的这么快)。聪明的作者听了厂商的需求之后,很快的就想到了很好的解决方案,通过一个变量len来存字符串的长度,通过一个函数Length返回变量len,时间复杂度可是O(0)哦,于是作者很快的实现了厂商的需求,大概如下:

class FastString
{
public:
FastString(const char* str);
FastString(void );
int Length ();//新增的
int Find (const char* str );
private:
char* m_str ;
int len ;//新增的
};

在经过天衣无缝的测试之后,作者骄傲的将他的作品分发给了愿意再次掏钱的厂商,厂商用了很是火大,出现了各种莫名其妙的问题,在被各个厂商咆哮之后,作者发现了他的作品的缺陷,于是决定走上COM之路

我们先来看看厂商用了作者的FastString之后为什么就挂了呢?

厂商们拿了作者的源码之后,就以源码的方式和自己的其他代码一起编译成一个DLL文件,然后让自己的产品升级,升级就是简单的覆盖这个DLL文件,于是厂商的产品升级之后就挂了。因为FastString可能在多个DLL中多个文件都实例化了,在这些DLL中FastString占用4个字节的内存,而新版本的FastString占用的是8个字节的内存,厂商只覆盖了FastString所在的DLL,而没有覆盖所有使用了FastString的DLL,由于FastString所在的DLL创建FastString是8个字节,而其他DLL中是4个字节,如果跨库传递FastString,将一个4字节的对象当做一个8字节的对象来用,这还不挂。

聪明的作者很快就实现了他的COM组件,源码大概是下面这个样子,不要奇怪为什么作者的COM之路这么顺风顺水,这么快就出了作品。

#pragma once
class IExtensibleObject
{
public:
virtual void* Dynamic_Cast(const char* str)=;
virtual void AddRef()=;
virtual void Release()=;
}; class IFastString:public IExtensibleObject
{
public:
virtual int Length(void)=;
virtual int Find(const char* str)=;
}; class FastString:public IFastString
{
public:
FastString(const char* str=NULL);
virtual void* Dynamic_Cast(const char* str);
virtual void AddRef() ;
virtual void Release();
virtual int Length();
virtual int Find(const char* str);
~FastString();
private:
char* m_str;
int len;
int m_cPtrs;//引用计数
}; //导出函数
extern "C" __declspec(dllimport) IFastString* CreateFastString(const char* psz);

作者的COM组件做到了一下几点,终于实现了增量更新。

1:作者不在以源码的方式卖给厂商,而是以头文件和库的方式卖个厂商,厂商可以通过静态/动态的方式链接作者的库。

2:作者不在让厂商到处实例化他的FastString,我可爱的FastString。而是通过一个导出函数实例化FastString,并返回IFastString,这样就不会出现不同DLL中FastString实例大小不一样的问题。现在所有的实例都在作者的DLL中创建了。

3:关于回收FastString的问题?作者刚开始是想直接delete掉CreateFastString返回的指针,但为了实现COM组件,此时的FastString已经不是彼时的自己了,他继承并实现了多个接口,由于接口之间转换来转换去,都不知道删除哪个指针了,于是作者决定通过使用引用计数的方式销毁FastString。

4:为什么要自己实现Dynamic_Cast?

RTTI是一个与编译器极为相关的特征,每个编译器厂商对RTTI的实现是独有的,这大大破坏了“以抽象基类作为接口而获得的编译器独立性”,既然每个编译器可能有不同的实现,即析构函数不能定义成虚函数,因为不同的编译器,虚函数在虚方法表中的位置是不一样的,有的编译器放在最前面有的放在最后面,这会导致不同的编译器编译后虚方法在虚方法表中的位置是不一样的。所以析构函数不能定义成virtual,其他public接口都必须定义成virtual。其他虚方法在虚方法表中的位置和虚方法的声明保持一致,即按照声明的顺序存放在虚方法中。

由于类型转换和引用计数是每个接口都需要的,于是把他们提出来放到最顶层,让所有的接口继承它。

5:新增的接口只能加在最后面,废弃的接口不能删除。

如果新增的接口插在中间,那么部分接口在虚方法表中的地址就会发生变化,新版本的DLL就不能与已经发布的程序兼容,就不能实现增量升级,即只用覆盖某个DLL,而不需要全部都要更新,废弃的接口删除会导致同样的问题。

综述:为什么作者的这个DLL能实现增量更新?

COM对象通过特定的导出方法在DLL中以new的方式创建,通过引用计数自动析构,客户端不能自己创建COM对象,COM对象的内部结构发生变化,对外部也没有影响,如果新增了接口,就在最后加,之前的接口在虚方法表中的位置不会受到印象,即对别的接口没有影响,废弃的接口不能删除,

改变对象的内存结构和新增virtual方法都没关系,那不就成了。实现增量不在是问题,我们在回到FastString这个问题上,如果FastString一开始是以上诉方式实现的,现在要新增一个len字段和一个Length接口,我就这样增了,新出个版本,直接覆盖以前的那个DLL,我直接可以用,一切都是OK的,外部的调用不会受到任何影响。为了证明这个FastString能实现增量升级,我做了一个DEMO,大家可以试一下,我就是下载地址

你或许会说我这说的都不是COM,但这的确是更好的C++。

COM是一个更好的C++的更多相关文章

  1. [Swift]LeetCode496. 下一个更大元素 I | Next Greater Element I

    You are given two arrays (without duplicates) nums1 and nums2 where nums1’s elements are subset of n ...

  2. [Swift]LeetCode503. 下一个更大元素 II | Next Greater Element II

    Given a circular array (the next element of the last element is the first element of the array), pri ...

  3. [Swift]LeetCode1019. 链表中的下一个更大节点 | Next Greater Node In Linked List

    We are given a linked list with head as the first node.  Let's number the nodes in the list: node_1, ...

  4. Leetcode 496. 下一个更大元素 I

    1.题目描述 给定两个没有重复元素的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集.找到 nums1 中每个元素在 nums2 中的下一个比其大的值. nums1 中数字  ...

  5. 下一个更大的数 Next Greater Element

    2018-09-24 21:52:38 一.Next Greater Element I 问题描述: 问题求解: 本题只需要将nums2中元素的下一个更大的数通过map保存下来,然后再遍历一遍nums ...

  6. LeetCode--496--下一个更大元素I(java)

    给定两个没有重复元素的数组 nums1和 nums2 ,其中nums1 是 nums2 的子集.找到 nums1 中每个元素在 nums2 中的下一个比其大的值. nums1 中数字 x 的下一个更大 ...

  7. [Leetcode]下一个更大元素II

    题目 给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素.数字 x 的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地 ...

  8. Leetcode 503. 下一个更大元素 II

    1.题目描述 给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素.数字 x 的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应 ...

  9. Leetcode---栈系列刷题(python3实现)----#496 下一个更大元素I

    给定两个没有重复元素的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集.找到 nums1 中每个元素在 nums2 中的下一个比其大的值. nums1 中数字 x 的下一个更 ...

随机推荐

  1. Appium客户端

    Appium版本:1.5.3 Xcode有两个版本:Xcode8.1   Xcode7.2.1 iOS10以下只能用Xcode7.2.1 iOS10及以上可以用Xcode8.1   1.Appium客 ...

  2. HTTP请求响应对照表

    1)请求(客户端->服务端[request]) GET(请求的方式) /books/java.html(请求的目标资源) HTTP/1.1(请求采用的协议和版本号) Accept: */*(客户 ...

  3. bcb安装控件方法汇总

    1.BCB 菜单: Project->Options->Packages->Add[喝小酒的网摘]http://blog.const.net.cn/a/8496.htm如果是BCB6 ...

  4. linux 下部署nodejs(两种方式)

    本次博客的编写时用的系统环境,刚装好的Centos 6.4  64位虚拟机. 另外关于linux 其他系统的安装 可以参考https://github.com/joyent/node/wiki/Ins ...

  5. 数据库知识整理<八>

    联接: 8.1理解简单的单联接: 基本上联接的结果是每个集合的笛卡尔积.例如:两个集合{a,b,c}和{a,b}的笛卡尔积是如下的成对集合:{(a,a),(a,b),(b,a),(b,b),(c,a) ...

  6. IIS问题解决:URL中制表符引起的Bad Request - Invalid URL

    昨天处理好了Google网站管理员中的500错误,今天处理了一些400处理,比如下面的以制表符(tab)结尾的URL: http://www.cnblogs.com/me-sa/archive/200 ...

  7. VS下的Resharper插件报错“Can not resolve symbol”的解决办法

    今天准备写代码的时候,发现代码中大片的红色,就像下面的图片一样.但是编译一下,也可以重新生成,运行也没有问题.于是就看了下svn上是不是有人改了哪里,发现也没有问题.于是又清理了下解决方案,再次生成, ...

  8. DFD数据流程图

    顶层图DFD 0层图 1层图

  9. [ACM_模拟] The Willy Memorial Program (poj 1073 ,联通水管注水模拟)

    Description Willy the spider used to live in the chemistry laboratory of Dr. Petro. He used to wande ...

  10. Windows 8.1 开发过程中遇到的小问题(2)

    又是在Windows 8.1 的分享功能,再次出现错误: A COM call (IID: ***, method index: *) to an ASTA (thread *) was blocke ...