昨天看了《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. Delphi初学者,向万一老师致敬

    今天首开博客园... 刚开始学习Delphi难免诸多不懂... 感谢万能的万一老师...

  2. java 类的关系

    在面向对象中,类与类之间的关系有泛化,依赖,关联,聚合,组合几种.其中,聚合和组合都属于关联.在具体编程中: 依赖表现为如果A类依赖于B,则B体现为A的局部变量,方法参数或静态方法的调用.eg:cla ...

  3. 2011 wireshark 实用过滤表达式(针对ip、协议、端口、长度和内容) 实例介绍

    首先说几个最常用的关键字,“eq” 和 “==”等同,可以使用 “and” 表示并且,“or”表示或者.“!" 和 "not” 都表示取反. 一.针对wireshark最常用的自然 ...

  4. 让谷歌浏览器 chrome 支持小于12px的字体

    webkit的私有属性:{-webkit-text-size-adjust:none;} 但是,在最新版的谷歌里.已经不在支持这个属性啦. 用css3的transform:scale()缩放大小,但是 ...

  5. C语言extern作用(全局变量)

    用C语言编写程序的时候,我们经常会遇到这样一种情况:希望在头文件中定义一个全局变量,然后包含到两个不同的c文件中,希望这个全局变量能在两个文件中共用. 举例说明:项目文件夹project下有main. ...

  6. Linq To Object

    //SelectMany List<List<int>> Numbers = new List<List<int>>() { new List<i ...

  7. python 处理中文文件时的编码问题,尤其是utf-8和gbk

    python代码文件的编码 py文件默认是ASCII编码,中文在显示时会做一个ASCII到系统默认编码的转换,这时就会出错:SyntaxError: Non-ASCII character.需要在代码 ...

  8. 意外作出了一个javascript的服务器,可以通过js调用并执行任何java(包括 所有java 内核基本库)及C#类库,并最终由 C# 执行你提交的javascript代码! 不敢藏私,特与大家分

    最近研发BDC 云开发部署平台的数据路由及服务管理器意外作出了一个javascript的服务器,可以通过js调用并执行任何java(包括 所有java 内核基本库)及C#类库,并最终由 C# 执行你提 ...

  9. 工资低的.Net程序员,活该你工资低

    这两天博客园上关于“.Net工资低”的讨论挺多的,让我不禁想起一句话“拉不出屎来怪地球没引力”. 那些抱怨“做.Net工作三年了月薪才6千,我的同学做Java现在都一万二”的哥们,你问问自己“我会什么 ...

  10. 如何使用Coded UI Test对Webpage进行自动化测试

    在Visual Studio中,Coded UI Test已经不是什么新特性了,较早版本的Visual Studio中就已经有这个东东了.它主要用来帮助自动化测试工程师和开发人员确保程序在UI方面没有 ...