COM组件详解

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。            
               
                    本文链接:https://blog.csdn.net/rankun1/article/details/50850432
                
            
                    
                                                    
                                        
                
                                            
一 组件基础
  1 软件开发的阶段
    1.1 结构化编程
      采用自顶向下的编程方式,划分模块
      和功能的一种编程方式。
    1.2 面向对象编程
      采用对象的方式,将程序抽象成类,
      模拟现实世界,采用继承、多态的方式
      设计软件的一种编程方式。
    1.3 面向组件编程
      将功能和数据封装成二进制代码,采用
      搭积木的方式实现软件的一种编程方式。  2 组件和优点
    2.1 组件 - 实际是一些可以执行的二进
      制程序,它可以给其他的应用程序、操
      作系统或其他组件提供功能
    2.2 优点
      2.2.1 可以方便的提供软件定制机制
      2.2.2 可以很灵活的提供功能
      2.2.3 可以很方便的实现程序的分布式 
        开发。
    3 组件的标准 - COM(Component Object Model )
  
    3.1 COM是一种编程规范,不论任何开发语言
    要实现组件都必须按照这种规范来实现。
    组件和开发语言无关。
    这些编程规范定义了组件的操作、接口的
    访问等等。
    3.2 COM接口
    COM接口是组件的核心,从一定程度上
    讲"COM接口是组件的一切".
    COM接口给用户提供了访问组件的方式.
    通过COM接口提供的函数,可以使用组件
    的功能.    
      
  4 COM组件(组件不是动态库)
    4.1 COM组件-就是在Windows平台下,
    封装在动态库(DLL)或者可执行文件(EXE)
    中的一段代码,这些代码是按照COM的
    规范实现.
    4.2 COM组件的特点
      4.2.1 动态链接
      4.2.2 与编程语言无关
      4.2.3 以二进制方式发布
      二 COM接口
  1 接口的理解
    DLL的接口 - DLL导出的函数
    类的接口 - 类的成员函数
    COM接口 - 是一个包含了一组函数指针
     的数据结构,这些函数是由组件实现的
       2 C++的接口实现
    2.1 C++实现接口的方式,使用抽象结构体
      定义接口.(使用抽象类定义接口也可以)interface IMath {//纯虚函数};
    目前VC中,interface其实就是struct
    2.2 基于抽象结构体,派生出子类并实现
      虚函数功能.
      
    2.3 定义接口函数IMath* CreateInstance(){return new CMath;}
使用时,引入抽象结构体接口定义头文件,导入功能实现动态库,通过接口定义函数获得抽象结构体指针,调用相关功能函数
      接口思想使得功能实现与功能的使用隔离开,功能实现代码改变时,不用改变功能的使用的代码
注:结构体也具有封装,继承的特点,类可以继承结构体,c++接口正是利用了类继承结构体接口思想如图所示(途中有些内容在下面介绍)

// Interface.cpp : Defines the entry point for the console application.// #include "stdafx.h"#include "objbase.h" //接口定义interface IMath{public:    virtual int Add( int nAdd1, int nAdd2 ) = 0;    virtual int Sub( int nSub1, int nSub2 ) = 0;}; //接口的实现1class CImpMath1 : public IMath{public:    virtual int Add( int nAdd1, int nAdd2 );    virtual int Sub( int nSub1, int nSub2 );}; int CImpMath1::Add( int nAdd1, int nAdd2 ){    return ( nAdd1 + nAdd2 );} int CImpMath1::Sub( int nSub1, int nSub2 ){    return ( nSub1 - nSub2 );} //接口的实现2class CImpMath2 : public IMath{public:    virtual int Add( int nAdd1, int nAdd2 );    virtual int Sub( int nSub1, int nSub2 );}; int CImpMath2::Add( int nAdd1, int nAdd2 ){    return ( nAdd1 + nAdd2 );} int CImpMath2::Sub( int nSub1, int nSub2 ){    return ( nSub1 - nSub2 );} //创建接口IMath * CreateInstance( ){    return new CImpMath2;} int main(int argc, char* argv[]){    IMath * piMath = CreateInstance( );    int nAdd = piMath->Add( 100, 100 );     return 0;}  3 接口的动态导出
    3.1 DLL的实现
      3.1.1 接口的的定义
      3.1.2 接口的实现
      3.1.3 创建接口的函数
      3.1.4 导出创建接口函数
    3.2 DLL的使用
      3.2.1 加载DLL和获取创建接口的函数
      3.2.2 创建接口
      3.2.3 使用接口的函数
      4 接口的生命期
    4.1 问题
     在DLL中使用new创建接口后,在用户
     程序使用完该接口后,如果使用delete
     直接删除,会出现内存异常.
     
     每个模块有自己的内存堆(crtheap)
       EXE - crtheap
       DLL - crtheap
     new/delete/malloc/free默认情况
     下都是从自己所在模块内存堆(crtheap)
     中分配和施放内存.而各个模块的
     这个内存堆是各自独立.所以在DLL中
     使用new分配内存,不能在EXE中delete.
     
   4.2 引用计数和AddRef/Release函数(解决同一个接口多处使用时的删除问题)
   
    引用计数 - 就是一个整数,作用是
      表示接口的使用次数
    AddRef - 增加引用计数  +1
    Release - 减少引用计数 -1, 如果
      当引用计数为0,接口被删除
   4.3 创建接口并使用
     4.3.1 创建接口
    接口使用:
     4.3.2 调用AddRef,增加引用计数
     4.3.3 使用接口
     4.3.4 调用Release,减少引用计数
   4.4 注意
     4.4.1 在调用Release之后,接口指针
      不能再使用
     4.4.2 多线程情况下,接口引用计数
      要使用原子锁的方式进行加减
      接口升级方式:
新增一个抽象结构体(或者抽象类)接口n,然后让负责功能实现的子类以多继承的方式继承n,并实现n的虚函数功能
但创建接口函数只能创建一种接口,新增的接口如何获取,这就需要接口查询函数(在各个接口中定义,在功能子类中实现),
和接口唯一标识GUID,在每一个接口定义前都定义一个GUID唯一标识一个接口,然后在接口查询函数中根据GUID标识,将子类对象指针转换为
相应的父类接口,所以接口的使用方式变为:先调用接口创建函数创建一个接口a,然后用a调用接口查询函数,传入接口GUID标识,
查询得到其余的接口并使用
由于创建接口和查询接口后都要调用AddRef,所以将AddRef的调用放在创建接口和查询接口函数的内部  5 接口的查询
接口查询实现了通过GUID来改变接口,每个接口对应一个GUID,接口查询就是通过GUID查询到对应的接口
这样在以后增加新的接口后,不用改变创建接口的函数,只需要修改接口查询函数即可。
    5.1 每个接口都具有唯一标识 GUID
    5.2 实现接口查询函数      QueryInterface
接口声明
#ifndef _MATH_H_#define _MATH_H_ #include "objbase.h" // {9080F9E3-19B6-4fe7-B47A-47431C3D35BE}static const GUID IID_IBase = { 0x9080f9e3, 0x19b6, 0x4fe7, { 0xb4, 0x7a, 0x47, 0x43, 0x1c, 0x3d, 0x35, 0xbe } }; interface IBase{public:    virtual int AddRef( ) = 0;    virtual int Release( ) = 0;    virtual int QueryInterface( GUID iid, void ** ppiInterface ) = 0;};//定义接口// {B188B6AC-4DE6-4776-97B7-FB1F3C7BA102}static const GUID IID_IMath = { 0xb188b6ac, 0x4de6, 0x4776, { 0x97, 0xb7, 0xfb, 0x1f, 0x3c, 0x7b, 0xa1, 0x2 } }; interface IMath : IBase{public:    virtual int Add( int nAdd1, int nAdd2 ) = 0;    virtual int Sub( int nSub1, int nSub2 ) = 0;}; // {1A8B9047-F601-4b98-9383-86D78D94134A}static const GUID IID_IMath2 = { 0x1a8b9047, 0xf601, 0x4b98, { 0x93, 0x83, 0x86, 0xd7, 0x8d, 0x94, 0x13, 0x4a } }; interface IMath2 : IBase{public:    virtual int Mud( int nMud1, int nMud2 ) = 0;    virtual int Div( int nDiv1, int nDiv2 ) = 0;};  #endif //_MATH_H_接口实现// DllInterface.cpp : Defines the entry point for the DLL application.// #include "stdafx.h"#include "math.h" BOOL APIENTRY DllMain( HANDLE hModule,                        DWORD  ul_reason_for_call,                        LPVOID lpReserved                     ){    return TRUE;} //接口的实现1class CMath : public IMath,              public IMath2{public:    CMath( );    virtual int AddRef( );    virtual int Release( );    virtual int QueryInterface( GUID iid, void ** ppiInterface );     virtual int Add( int nAdd1, int nAdd2 );    virtual int Sub( int nSub1, int nSub2 );    virtual int Mud( int nMud1, int nMud2 );    virtual int Div( int nDiv1, int nDiv2 ); public:    LONG m_nRef; //引用计数}; CMath::CMath( ){    m_nRef = 0;} int CMath::AddRef( ){    InterlockedIncrement( &m_nRef ); //增加引用计数    return m_nRef;} int CMath::Release( ){    InterlockedDecrement( &m_nRef ); //减少引用计数    //如果为0,删除对象    if( m_nRef == 0 )    {        delete this;    }    return m_nRef;} int CMath::QueryInterface( GUID iid, void ** ppiInterface ){    if( iid == IID_IMath )    {        *ppiInterface = static_cast<IMath *>(this);        AddRef( );    }    else if( iid == IID_IMath2 )    {        *ppiInterface = static_cast<IMath2 *>(this);        AddRef( );    }    else if( iid == IID_IBase )    {        *ppiInterface = static_cast<IMath *>(this);        AddRef( );    }    return 0;}  int CMath::Add( int nAdd1, int nAdd2 ){    return ( nAdd1 + nAdd2 );} int CMath::Sub( int nSub1, int nSub2 ){    return ( nSub1 - nSub2 );} int CMath::Mud( int nMud1, int nMud2 ){    return ( nMud1 * nMud2 );} int CMath::Div( int nDiv1, int nDiv2 ){    return ( nDiv1/nDiv2 );}   //创建接口IMath * CreateInstance( ){    IMath * piMath =  new CMath;    piMath->AddRef( );    return piMath;}接口使用// UseDll.cpp : Defines the entry point for the console application.// #include "stdafx.h"#include "../DllInterface/math.h" typedef IMath * ( * CREATEINSTANCE)( ); IMath * CreateInterface( ){   //加载动态库    HMODULE hDll = ( HMODULE )        LoadLibrary( "DllInterface.dll" );    //获取创建接口的函数    CREATEINSTANCE CreateInstance =         (CREATEINSTANCE)GetProcAddress(            hDll, "CreateInstance" );    //创建接口    IMath * piMath = CreateInstance( );    //返回接口    return piMath;} int main(int argc, char* argv[]){       //创建接口    IMath * piMath = CreateInterface( );    //使用接口    int nAdd = piMath->Add( 100, 100 );    printf( "%d\n", nAdd );     /* 无法转换    IMath2 * p = (IMath2 *)piMath;    int nMud2 = p->Mud( 100, 100 );    printf( "Mud2: %d\n", nMud2 );    */    //通过查询函数获取IMath2接口    IMath2 * piMath2 = NULL;    piMath->QueryInterface( IID_IMath2,        (LPVOID *)&piMath2 );     //减少引用计数        piMath->Release( );     int nMud = piMath2->Mud( 100, 100 );    printf( "Mud: %d\n", nMud );     piMath2->Release( );      return 0;}IUnknown 接口

由于每个接口都必有QueryInterface 接口查询函数,AddRef 增加引用计数,Release 减少引用计数的声明,
所以将这三个函数向上抽象为一个父接口,微软已经抽象好了,就是IUnknown 接口
  6 IUnknown 接口   #include "unknwn.h"
    6.1 IUnknown是微软定义的标准接口
     我们实现所有接口都是继承这个接口
     
    6.2 IUnknown定义了三个函数
     QueryInterface 接口查询函数
     AddRef 增加引用计数
     Release 减少引用计数      所以以后定义接口时,都要直接或间接的继承IUnknown 接口(上面例子中的IBase就扮演者IUnknow的角色,微软内部已定义IUnknown 接口的GUID  为 IID_IUnknown)
————————————————
版权声明:本文为CSDN博主「Barry__」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/rankun1/article/details/50850432

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值