本节内容:

1、现实中的组件与接口

2、把现实中的思想融入到软件中

3、C++程序中的组件与接口

4、COM组件与COM接口

5、QueryInterface函数,HRESULT类型,IID类型,数据类型转换


1、现实中的组件与接口

组件的定义:

含有可独立性,可通用性,可组合性,可替换性的事物,我们把它称作组件。

现实世界中存在各种各样的具备组件概念的设备。如:电脑主机内的设备: CPU、内存条、硬盘、光驱。还比如:5号电池、7号电池、插座。

为什么说这些设备具备组件概念呢?因为这些设备都具有可独立性,可通用性,可组合性,可替换性。

 接口的定义:

是组件与组件之间,组件与外部事物之间进行交互的协议。

组件与组件,组件与其它设备的交互工作是通过接口进行的。 CPU跟主板之间有固定的接口,内存条跟主板之间也有固定的接口。如果CPU要更换,被更换的CPU必须与旧的CPU拥有相同的使用接口。


* 主板不直接认识CPU,只认识CPU的接口。主板也不直接认识内存条,只认识内存条的接口。

*所以接口的约定是很重要的。在组件的开发之前,必须先约定组件与外界交互的接口协议。

* 接口协议只要确定后,往往是不能再改变的,比如5号电池不能再做得大一点或再小一点,然后去替换旧的5号电池。


采用组件与接口的思想来开发设备,体现了社会分工的一个现象,也是社会生产力发展的必然过程,做CPU的厂商只管做CPU,不用告诉主板关于CPU本身的实现细节,也不用去了解主板的实现细节。


2、把现实中的思想融入到软件中

面向对象思想来源于大自然,让我们面向着是一个一个的对象,不是面向一个一个的过程(面向过程思想)。

黑格尔说“存在的就是合理的”,大自然存在各种各样的对象,每类对象具有自己的特性,对象存在继承关系。这些存在是合理的。最后把这些合理的思想演变成面向对象思想,所以面向对象思想是合理的。


社会的发展促使人类在劳动上的分工,分工又以约定的接口协议来交互。社会的发展产生了这种“组件-接口”的开发思想,这种思想又是长期没有被替换过的思想,这是一种好的思想,合理的思想。

若我们以组件方式架构我们的软件。我们软件中的组件将具有可独立性,可通用性,可组合性,可替换性;我们的软件也将具有更好的灵活性,可拓展性和可维护性。我们软件的开发过程也会变得更加的简单,更好的分工,更加的规范。


3、C++程序中的组件与接口


• 接口,是一种约定,一种协议。它是抽象的,指明了具体含义,但却没有实现这个定义。


  我们看一下C++的纯虚函数:求最大公约数,virtual int GreatestCommonDivisor(int a,int b) = 0; //求a与b的最大公约数。

这个函数的定义很明确,但没有实现这个含义的具体方法,所以,是抽象的。


• 我们一般采用interface这个英文单词表示 C ++中的接口,它在Microsoft Visual Studio安装目录\VC\PlatformSDK\include\objbase.h中被预定义。


    #define  interface   struct


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

//接口IX
interface IX
{  
  virtual void Fx1() = 0;
  virtual void Fx2() = 0;
};
//接口IY
interface IY
{  
  virtual void Fy1() = 0;
  virtual void Fy2() = 0;
};

组件是派生于接口的,现在我们定义一个组件CA,它派生于IX和IY
//组件CA
class CA: public IX,public IY
{
public:
  virtual void Fx1() { cout <<"Fx1" << endl; }
  virtual void Fx2(){ cout <<"Fx2" << endl; }
  virtual void Fy1() {cout <<"Fy1" << endl; }
  virtual void Fy2() { cout <<"Fy2" << endl; }
};

客户端的调用查看Section1Demo1
 
 //预定义interface 
 
 #define interface struct

 //接口IX
 interface IX
 { 
   virtual void Fx1() = 0;
   virtual void Fx2() = 0;
 };

 //接口IY
 interface IY
 { 
   virtual void Fy1() = 0;
   virtual void Fy2() = 0;
 };

 //组件CA
 class CA: public IX,public IY
 {
 public:
   virtual void Fx1() { cout <<"Fx1" << endl; }
   virtual void Fx2() { cout <<"Fx2" << endl; }
   virtual void Fy1() { cout <<"Fy1" << endl; }
   virtual void Fy2() { cout <<"Fy2" << endl; }
 };

 int main()
 {
     //创建组件的实例
     CA *pCA = new CA;

     //获取IX的接口
     IX *pIX = pCA;
     //使用IX的接口
     pIX->Fx1();
     pIX->Fx2();
     
     //获取IY的接口
     IY *pIY = pCA;
     //使用IY的接口
     pIY->Fy1();
     pIY->Fy2();

     //销毁组件
     delete pCA;
     pCA = NULL;
     
     return 0;
 }




4、COM组件与COM接口


• COM 的定义:是 Component Object Model ( 组件对象模型 )的缩写



• COM组件是可以以二进制的形式发布,具有指定规则的二进制结构;



• COM组件是可以被其它应用程序来调用,以实现二进制代码的共享(跨应用);



• COM组件是完全与编程语言无关的。(跨语言);



• COM 组件只能被运行在 Windows 操作系统平台上面, Linux , Mac 不能适用。



• COM组件的内存结构和 C ++编译器为抽象基类所生成的内存结构是相同的。因此可以用 C ++的抽象基类来定义COM接口。



• COM组件必须继承于最基本的COM接口:I U nknow。



• IUnknow有三个函数,为别是QueryInterface,AddRef, Release。



interface IUnknown

{

  virtual HRESULT QueryInterface(const IID &iid, void **ppv) = 0;

  virtual ULONG AddRef() = 0;

  virtual ULONG Release() = 0;

};




5、QueryInterface函数,HRESULT类型,IID类型,数据类型转换
QueryInterface

      可以通过QueryInterface函数来查询某个组件是否支持某个特定的接口。若支持,QueryInterface返回一个指向此接口的指针。

      这里我们看到函数返回类型为HRESULT,参数其中一个的类型是constIID&。

      HRESULT跟IID是什么呢?

HRESULT



win10对com组件的调用返回了错误hresult_#include



IID


• IID,接口标识符,每个接口都可以设置一个IID,用于标志该接口,若标志了某个接口后,IID的值不能再修改。



• IID其实是:


typedefGUID IID;
GUID
typedef struct _GUID
{
  DWORD  Data1;  //随机数        4字节
  WORD  Data2;  //和时间相关    2字节
  WORD  Data3;  //和时间相关    2字节
  BYTE  Data4[8];    //和网卡MAC相关  8字节
} GUID;  //一共16字节


• GUID有16个字节,共128位二进制数。



• GUID的生成方法,可以采用Windows SDKv6.0A的Tools文件夹下的GUID生成器生成。



• 从理论上讲,它是不能保证唯一,但由于重复的可能性非常非常非常。。。非常小。有句夸张的说法是: “ 在每秒钟产生一万亿个GUID的情况下,即使太阳变成白矮星的时候,它仍是唯一的 ”
GUID生成器的界面截图



win10对com组件的调用返回了错误hresult_#include_02






• GUID的表示方法:


//{0E04C466-6CE9-4513-B306-43E8F7025EB9}
static const GUIDguid = 
{ 0xe04c466, 0x6ce9,0x4513, { 0xb3, 0x6, 0x43, 0xe8, 0xf7, 0x2, 0x5e, 0xb9 } };


 
  • 
   查看Section1Demo2例子,讲解QueryInterface的实现方法。
#include "stdafx.h"
 #include <iostream>
 #include <Unknwn.h>

 using namespace std;

 // {A348FBDD-E765-4b41-8477-6D8B7038FCC6}
 static const IID IID_IX = 
 { 0xa348fbdd, 0xe765, 0x4b41, { 0x84, 0x77, 0x6d, 0x8b, 0x70, 0x38, 0xfc, 0xc6 } };

 // {10A90ED2-FCDE-4067-92DA-ABA38F5C1B12}
 static const IID IID_IY = 
 { 0x10a90ed2, 0xfcde, 0x4067, { 0x92, 0xda, 0xab, 0xa3, 0x8f, 0x5c, 0x1b, 0x12 } };


 //接口IX
 interface IX : public IUnknown
 {    
     virtual void Fx1() = 0;
     virtual void Fx2() = 0;
 };

 //接口IY
 interface IY : public IUnknown
 {    
     virtual void Fy1() = 0;
     virtual void Fy2() = 0;
 };

 //组件CA
 class CA: public IX, public IY
 {
 //实现
 public:
     virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void **ppv)
     {
         //查看PPT中CA的内存结构讲解如下的转换过程

         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
     }

     virtual ULONG STDMETHODCALLTYPE AddRef()
     {
         //暂时不实现,暂时返回0
         return 0;
     }

     virtual ULONG STDMETHODCALLTYPE Release()
     {
         //暂时不实现,暂时返回0
         return 0;
     }

     virtual void Fx1()    { cout << "Fx1" << endl;    }
     virtual void Fx2()    { cout << "Fx2" << endl;    }
     virtual void Fy1()    { cout << "Fy1" << endl;    }
     virtual void Fy2()    { cout << "Fy2" << endl;    }

 //数据
 public:
     long m_lAA;
     long m_lAB;
     long m_lAC;
 };


 int main( )
 {
     HRESULT hr;

     //创建组件
     CA *pA = new CA();

     //从组件查询IUnknown接口
     IUnknown *pIUnknown = NULL;
     hr = pA->QueryInterface(IID_IUnknown, (void**)&pIUnknown);
     if (SUCCEEDED(hr))        //对HRESULT返回值的判断,一般采用SUCCEEDED
     {
         //从IUnknown查询IX接口
         IX *pIX = NULL;
         hr = pIUnknown->QueryInterface(IID_IX, (void**)&pIX);
         if (SUCCEEDED(hr))
         {
             //调用IX接口的方法
             pIX->Fx1();
             pIX->Fx2();
         }

         //从IUnknown查询IY接口
         IY *pIY = NULL;
         hr = pIUnknown->QueryInterface(IID_IY, (void**)&pIY);
         if (SUCCEEDED(hr))
         {
             //调用IY接口的方法
             pIY->Fy1();
             pIY->Fy1();
         }

         if ((void*)pIX != (void*)pIY)
         {
             cout << "pIX != pIY" <<endl;
         }
         
         if ((void*)pIUnknown != (void*)pIY)
         {
             cout << "pIUnknown != pIY" <<endl;
         }

         if ((void*)pIUnknown == (void*)pIX)
         {
             cout << "pIUnknown == pIX" <<endl;
         }

         //从IX查询IY
         IY *pIY2 = NULL;
         hr = pIX->QueryInterface(IID_IY, (void**)&pIY2);
         if (SUCCEEDED(hr))
         {
             pIY2->Fy1();
             pIY2->Fy2();
         }

         //从IX也可以查询到IUnknown
         //从IY也可以查询到IX
         //从IY也可以查询到IUnknown
         //从CA也可以查询到IX
         //从CA也可以查询到IY
         //总结:
             //只要是CA所继承的接口,从CA的组件里都可以查询到;
             //只要是CA所继承的接口,从CA所继承的其它接口里都可以查询到。
     }

     //释放组件
     delete pA;

     return 0;
 }


win10对com组件的调用返回了错误hresult_#include_03