本节内容:
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
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生成器的界面截图
• 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;
}