• COM初体验


      以前在我学校里培训过一段时间C++,我敬爱的吴老师略有提及。那个时候觉得COM遥不可及,觉得,哇塞好神圣。我觉得自己啥都没学好,我不应该这么早去涉及这片过于光荣的领地。既没有觉悟也没有动力去迎接这样一场学习。让对于COM的学习一拖再拖,就像拖延症。然而现实总是残酷的这项技术早已经不再神秘不再光荣依旧,技术的发展甩给我狠狠地一记巴掌,如果连这种技术都不了解确实很难混下去了。

    •COM是微软组件对象模型的简称。由于COM具有二进制代码共享的特性,所以它具备了高可开发性、高度可维护性和高度的可移植性(跨开发语言),以至于在Windows上面的诸多应用软件采用了COM来做整体的架构。比如微软的DirectX等。COM虽然流行于2000-2004年之间,由于它的普及面之广,应用软件种类之繁多再加上Windows对其默认支持很好,开发出来的软件无需依赖其他的开发包,所以被很多软件公司采用至今。作为一个VC++程序员,是否系统掌握COM的用法成为是否合格的重要的衡量指标之一。
    •下面我简单地讲解COM组件的三个优点。
    •采用COM组件架构我们的软件,会使我们更方便地进行模块划分,而且各模块独立性高,耦合度低,从而更方便地进行开发任务的分工。(开发性)
    •采用COM组件架构我们的软件,会使我们更方便地维护、升级软件,因为我们可以很方便地直接用新模块替换旧模块,而不影响软件的其它功能。(维护性)
    •采用COM组件架构我们的软件,可以使我们已编写好的功能模块可以很方便地移植到其它平台,如从C++的MFC平台移植到C#的WinForm平台。因为COM组件是跨应用的,可以被C++调用也可以被C#调用。(移植性)
    C++程序中的组件与接口:
    •接口,是一种约定,一种协议。它是抽象的,指明了具体含义,但却没有实现这个定义。我们看一下C++的纯虚函数:求最大公约数,virtual int GreatestCommonDivisor(int a, int b) = 0;  //求a与b的最大公约数。这个函数的定义很明确,但没有实现这个含义的具体方法,所以,是抽象的。
    •我们一般采用interface这个英文单词表示C++中的接口,它在Microsoft Visual Studio 安装目录VCPlatformSDKincludeobjbase.h中被预定义。

    #define   interface   struct

    在其它开发平台下,也可以自己编写预定义代码

    COM组件与COM接口:

    •COM的定义:是Component Object Model (组件对象模型)的缩写
    •COM组件是可以以二进制的形式发布,具有指定规则的二进制结构;
    •COM组件是可以被其它应用程序来调用,以实现二进制代码的共享(跨应用);
    •COM组件是完全与编程语言无关的。(跨语言);
    •COM组件只能被运行在Windows操作系统平台上面,Linux,Mac不能适用。
    •COM组件的内存结构和C++编译器为抽象基类所生成的内存结构是相同的。因此可以用C++的抽象基类来定义COM接口。
    •COM组件必须继承于最基本的COM接口: IUnknow。
    •IUnknow有三个函数,为别是QueryInterface, AddRef, Release。
    1 interface IUnknown
    2 {
    3         virtual HRESULT QueryInterface(const IID &iid, void **ppv) = 0;
    4         virtual ULONG AddRef() = 0;
    5         virtual ULONG Release() = 0;
    6 };

    QueryInterface:

    可以通过QueryInterface函数来查询某个组件是否支持某个特定的接口。若支持,QueryInterface返回一个指向此接口的指针。这里我们看到函数返回类型为HRESULT,参数其中一个的类型是const IID&。HRESULT跟IID是什么呢?

    IID:
    •IID,接口标识符,每个接口都可以设置一个IID,用于标志该接口,若标志了某个接口后,IID的值不能再修改。
    •IID其实是: typedef GUID IID;
    typedef struct _GUID
    {
            DWORD  Data1;    //随机数
            WORD  Data2;        //和时间相关
            WORD  Data3;        //和时间相关
            BYTE  Data4[8];     //和网卡MAC相关
    } GUID;

    GUID:

    •GUID有16个字节,共128位二进制数。
    •GUID的生成方法,可以采用Windows SDK v6.0A的Tools文件夹下的GUID生成器生成。
    •从理论上讲,它是不能保证唯一,但由于重复的可能性非常非常非常。。。非常小。有句夸张的说法是:“在每秒钟产生一万亿个GUID的情况下,即使太阳变成白矮星的时候,它仍是唯一的”
    •GUID的表示方法:

    // {0E04C466-6CE9-4513-B306-43E8F7025EB9}

     static const GUID guid =

    { 0xe04c466, 0x6ce9, 0x4513, { 0xb3, 0x6, 0x43, 0xe8, 0xf7, 0x2, 0x5e, 0xb9 } };

    QueryInterface的实现:

    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void **ppv)
        {
            
            if (iid == IID_IUnknown)
            {
                //即使CA继承了两个IUnknown接口,其中一个来自于IX,另一个来自于IY。我们一般返回第一个被继承的IX接口。
                *ppv = static_cast<IX*>(this);        
            }
            else if (iid == IID_IX)
            {
                //返回IX接口
                *ppv = static_cast<IX*>(this);        
            }
            else if (iid == IID_IY)
            {
                //返回IY接口
                *ppv = static_cast<IY*>(this);
            }
            else
            {
                //查询不到IID,*ppv返回NULL。
                *ppv = NULL;
                return E_NOINTERFACE;    //函数返回值返回E_NOINTERFACE,表示组件不支持iid的接口。
            }
    
            //查询成功时,需要自增引用计数
            AddRef();        
    
            return S_OK;    //返回S_OK
        }

     引用计数的原理:

    •引用计数技术就是用来管理对象生命期的一种技术。
    •对象O可能同时被外界A,外界B,外界C引用。也就是说外界A,外界B,外界C可能都在使用对象O。
    •每次当对象被外界引用时,计数器就自增1。
    •每次当外界不用对象时,计数器就自减1。
    •在计数值为零时,对象本身执行delete this,销毁自己的资源。
    •引用计数使得对象通过计数能够知道何时对象不再被使用,然后及时地删除自身所占的内存资源。
    •IUnknown接口的AddRef与Release就是引用计数的实现方法。
    AddRef和Release的实现:
     1 virtual ULONG STDMETHODCALLTYPE AddRef()
     2     {
     3         //简单实现方法
     4         return ++m_lCount;
     5 
     6         //多线程编程采用如下方法,这种方法确保同一个时刻只会有一个线程来访问成员变量
     7         //return InterlockedIncrement(&m_lCount);
     8     }
     9 
    10     virtual ULONG STDMETHODCALLTYPE Release()
    11     {
    12         //简单实现方法
    13         if (--m_lCount == 0)
    14         {
    15             delete this;    //销毁自己
    16             return 0;
    17         }
    18         return m_lCount;
    19 
    20         ////多线程编程采用如下方法,这种方法确保同一个时刻只会有一个线程来访问成员变量
    21         //if (InterlockedDecrement(&m_lCount) == 0)
    22         //{
    23         //    delete this;        //销毁自己
    24         //    return 0;
    25         //}
    26         //return m_lCount;
    27     }

    引用计数的优化:

    •这种优化可行吗?答案是可行的!因为这种优化符合了引用计数优化的“局部变量原则”
    •引用计数的优化原则:

    一、输入参数原则:输入参数指的是给函数传递某个值的参数。在函数体中将会使用这个值但却不会修改它或将其返回给调用者。在C++中,输入参数实际上就是那些按值传递的参数。对传入函数的接口指针,无需调用AddRef与Release

    二、局部变量原则对于局部复制的接口指针,由于它们只是在函数的生命期内才存在,因此无需调用AddRef与Release

    •输入参数原则:
    1  void Fun(IX *pIXParam)     //参数传递存在赋值过程
    2 {
    3         //pIXParam->AddRef();   //可优化,注释掉
    4         pIXParam->Fx1();
    5         pIXParam->Fx2();
    6         //pIXParam->Release();    //可优化,注释掉
    7 }
    •局部变量原则:
    1 void Fun(IX *pIX)
    2 {
    3         IX *pIX2 = pIX;
    4         //pIX2->AddRef();    //可优化,注释掉
    5         pIX2->Fx1();
    6         pIX2->Fx2();
    7         //pIX2->Release();    //可优化,注释掉
    8 }
    •以下代码可以优化吗?:
    void  Fun(IX **ppIX)
    {
            (*ppIX)->Fx1();
            (*ppIX)->Fx2();
            (*ppIX)->Release();    //可以优化吗?
            *ppIX = m_pIXOther;
            (*ppIX)->AddRef();    //可以优化吗?
            (*ppIX)->Fx1();
            (*ppIX)->Fx2();
    }
    •答案是否定的!因为它不是输入参数原则,而是输入-输出参数原则。此原则下,引用计数不能优化!

    //以上两句务必要运行,因为*ppIX 与m_pIXOther不一个属性同一个组件。

    //比如假设*ppIX是指向第一次的new CA(),而m_pIXOther却是指向第二次的new CA()。

    //或者*ppIX是指向new CA(),而m_pIXOther是指向new CZ(),CA与CZ的共同点,只是都继承了IX接口而已。

    •引用计数,带来了高效的内存资源管理方法,能及时地释放不再使用的资源。但却带来了编码的麻烦。在后续的讲解中,会讲到对引用计数的封装,也就是智能指针,到时组件的客户不再编写AddRef与Release代码,也不需要编写delete代码,便可以方便,舒心地进行内存资源的管理。
     
     
     
     
     
     
     
     
     
  • 相关阅读:
    dedecms list 判断 每隔3次输出内容
    dede 后台登录以后一片空白
    SSO单点登录在web上的关键点 cookie跨域
    简单批量复制百度分享链接
    PHP强大的内置filter (一)
    MySql数据备份与恢复小结
    linux命令 screen的简单使用
    xdebug初步
    本地虚拟机挂载windows共享目录搭建开发环境
    MySQL 5.6 警告信息 command line interface can be insecure 修复
  • 原文地址:https://www.cnblogs.com/XCoderLiu/p/3535944.html
Copyright © 2020-2023  润新知