上一次说到了virtual void *Dynamic_Cast(const char *pszTypeName)=0; 函数
而实际中的COM却是这样的:
HRESULT QueryInterface(const GUID & iid,void ** ppObj) =0;
这里引入了GUID,即Globally Unique Identifier,即全球唯一标识符,来确定组件的唯一性.
引入GUID是基于这样考虑: 如果单纯根据接口名字去返回接口指针,肯定会存在重复的情况。即当不同的程序员开发同一个接口时,可能在实现方法的顺序和原型上存在差别,不兼容,就会产生错误.
当我们需要单个对象支持多个接口的时候.比如如下代码:
interface ITestOther :public IPulibcTest
{
virtual void DoSthOther() = 0;
}
class TestClass:public ITestOther ,public ITest
{
//具体实现
}
//使用该组件DoSthOther()方法
void fun(){
ITest *pTest = NULL;
ITestOther *pOther = NULL;
pTest = CreateTestInterface();
//该函数的实现可以采用一般dll引用导出函数方法调用前面的GetInterface函数
if(pTest ){
pOther = (ITestOther *)pTest->Dynamic_Cast("ITestOther");
if(!pOther) pTest->Release();//(1)
else {
pOther->DoSthOther(); pOther->Release(); //(2)
//1,2处,虽然调用的都是同一个函数,写法却不同
}
}
此时,必须记录下哪个指针与哪个对象联系在一起,并且每个对象只能调用一次Release方法.
当复杂的代码中,管理这些关系的成本将大幅度提升.一个简单的解决办法就是让每个实现对象维护一个引用计数,当接口指针复制的时候,该值增加;当接口指针销毁时,该值减少.(AddRef终于也面世了:))
即如下:
class TestClass:public ITestOther ,public ITest
{
long m_cRef;
TestClass: m_cRef(0) {}
int AddRef() {m_cRef++;} //要想改组件在多线程中也适用,可以改成InterlockedIncrement(&m_cRef);
int Release() { if(--m_cRef==0) delete this;} //改成多线程版本,同上
//具体实现
}
注:一般我们可以把AddRef()和Release隐藏到c++ smart pointer指针后面
这样fun函数就变成如下情况了:
void fun(){
ITest *pTest = NULL;
ITestOther *pOther = NULL;
pTest = CreateTestInterface();
if(pTest ){
pOther = (ITestOther *)pTest->Dynamic_Cast("ITestOther");
if(pOther) {
pOther->DoSthOther(); pOther->Release();
}
pTest->Release();
}
到这里,COM的3位大佬终于现身完毕了,挥把汗先:)
后记: COM 把接口与实现分开的动机,是把对象内部的工作细节隐藏起来。这样就可以允许实现类中的数据成员的数量和顺序随意变化,但使用改dll的程序无须编译就可以继续使用.也允许程序在运行时刻询问对象以便知道对象是否支持新的功能。并且采用COM生成的dll与编译器无关.
尽管如此,它还不足以"为二进制组件提供一个普遍的低层基础“。不管怎么样,该组件必须是使用C++编译器的程序才能引用它.为了组件达到与语言无关的目的,COM引入了一个跟C语法相似的语言:接口定义语言(Interface
Definition Language,IDL)