http://www.vckbase.com/index.php/wv/60php
问题: 服务器
我用MFC编写COM程序有一段时间了,知道如何使用宏和嵌套类,以及如何在嵌套类中处理IUnknown接口,但对IUnknown的使用仍是不太老练。函数
假设CMyClass是一个COM服务器,从CCmdTarget派生。它实现了IMyInterface。CMyClass的定义以下:spa
class CMyClass: public CCmdTarget { BEGIN_INTERFACE_PART(...) STDMETHOD.... END_INTERFACE_PART DECLARE_INTERFACE_MAP } ;
由于CCmdTarget没有QueryInterface方法,CMyClass也没有这个方法。个人问题是:这个COM服务器的客户端老是要调用QueryInterface,它不调用ExternalQueryInterface, 也不调用 InternalQueryInterface。所以若是QueryInterface不是一个CMyClass的有效方法。这种状况该怎么处理?指针
解答: code
关于QueryInterface的这个问题,除你以外的许多人都对它感到困惑。可是不用怕,读完本文就能够见分晓。致使困惑的一个潜在的问题是CCmdTarget自己不从CObject派生,而且二者都有不一样于QueryInterface的虚函数——例如,CObject中的第一个虚函数是GetRuntimeClass,CCmdTarget中的第一个虚函数是OnCmdMsg。因此从CCmdTarget派生出来的类怎么多是COM类呢?并且还必须实现头三个虚函数是AddRef, Release, 和 QueryInterface的IUnknown接口:对象
实际上,解开这个谜团的方法很简单,但要求深刻了解MFC类库。为此,咱们必须进入MFC考察一番,主要目的是看看在调用CoCreateInstance建立类实例的时候会发生一些什么事情。blog
首先,CoCreateInstance所作的第一件事情(或多或少)是到注册表中检查哪一个DLL实现你的类。为简单起见,假设使用的是进程内服务器。CoCreateInstance加载你的DLL并调用特殊函数DllGetClassObject.。继承
// DLL 函数建立COM对象类工厂 DllGetClassObject(REFCLSID rclsid, // 类 ID REFIID riid, // 接口 ID LPVOID* ppv) // 返回的接口指针
实际上,DllGetClassObject得不到你的类实例,它获得的是类工厂(IClassFactory)实例,经过这个类工厂来建立你得类实例。很怪是否是?MFC提供DllGetClassObject的实现,任务是搜索DLL中的全部类工厂,看看哪一个类的ID寓所请求的类ID匹配。MFC是如何知道搜索哪一个类工厂呢?当你在编写子记得类代码时,用宏DECLARE_OLECREATE 和 IMPLEMENT_OLECREATE声明并实现你本身这个类的类工厂。尤为是IMPLEMENT_OLECREATE声明一个COleObjectFactory的静态实例,这个对象将本身(经过调用类构造函数 COleObjectFactory::COleObjectFactory)添加到与模块或DLL关联的某个类工厂清单中。结果,给定某个类的ID,DllGetClassObject就知道如何发现与那个类关联的类工厂。接口
一旦CoCreateInstance有了某个类工厂,它便调用IClassFactory::CreateInstance。在此MFC的COleObjectFactory又提供缺省的实现。
//作了许多精简后 STDMETHODIMP COleObjectFactory::XClassFactory::CreateInstance(...) { METHOD_PROLOGUE_EX(COleObjectFactory, ClassFactory) …… // 这里省略了许多代码 CCmdTarget* pTarget = pThis->OnCreateObject(); return pTarget->InternalQueryInterface(&riid, ppvObject); }
这里省略了许多琐碎代码以便突出重点,这些代码包括:建立一个实例和调用InternalQueryInterface。OnCreateObject是个COleObjectFactory的虚拟函数,它经过MFC的运行时类建立你的类实例:
//一样也做了简化 CCmdTarget* COleObjectFactory::OnCreateObject() { return (CCmdTarget*)m_pRuntimeClass->CreateObject(); }
一旦COleObjectFactory::XClassFactory::CreateInstance有了你的类实例,便调用InternalQueryInterface获取类的IUnknown接口指针。这里省略了许多细节,例如,在实际代码中MFC要调用IClassFactory2::CreateInstanceLic并检查全部出错条件以及聚合。InternalQueryInterface 和 ExternalQueryInterface之间的差异是外部版本委派外部IUnknown(若是有的话),而内部版本则否则。但即便聚合,最终都要归到InternalQueryInterface。
若是还不明白,没关系。类工厂的CreateInstance函数建立了一个你的类实例,并调用CCmdTarget::InternalQueryInterface来获取类的IUnknown指针。InternalQueryInterface在哪里获得你的类IUnknown接口指针呢?难以捉摸的QueryInterface又在哪里?
经历了一些步骤和函数调用以后,CCmdTarget::InternalQueryInterface最终要调用一个函数:
LPUNKNOWN CCmdTarget::GetInterface(const void* iid) { LPUNKNOWN ptr = NULL; if (iid == IID_IUnknown) { ptr = // 接口映射的第一个接口 } else { ptr = // 在接口映射中查找iid } return ptr; // (若是没找到则为NULL) }
换句话说,若是请求的接口是IUnknown,则GetInterface返回接口映射中的第一个接口指针,不然它查找与请求的接口ID匹配的那一个。这里的诀窍在于:你的类所提供的每个接口都必须和基类接口同样实现IUnknown,而且还要以相同的方式实现,至于InternalQueryInterface返回哪个接口指针并不重要。CCmdTarget类自身没有QueryInterface函数,只有嵌套类有,这个嵌套类实现每一个接口,每一个接口又都实现IUnknown。
图一是一个典型的COM类实现。.CPP文件说明了你必须为你的类所提供的每个接口编写一样烦人IUnknown实现。每个IUnknown方法为父类(从CcmdTarget派生)调用相应的ExternalXxx(或者InternalXxx——若是你不想要聚合)方法。这个实现对于你编写的每个接口都同样。这是没有办法的,由于全部的接口都经过相同的单对象在内存中被实例化。AddRef 和Release必须增长和减小相同的物理指针——与AddRef 或Release实际属于哪一个接口(嵌套类)无关。此乃高招所在。
这就是为何在IUnknown的状况下只有InternalQueryInterface能返回你的接口映射中的第一个接 口指针。因为仅仅实现IUnknown的类没什么用,因此在你的映射中至少还要实现一个接口。若是你不明白,下面是具体步骤的解释:
一、客户端调用CoCreateInstance建立你的类实例。
二、CoCreateInstance查找并加载DLL,调用DllGetClassObject
三、DllGetClassObject搜索DLLs的类工厂清单找出与类工厂匹配的类ID,而后返回这个类工厂的指针。
四、CoCreateInstance调用IClassFactory::CreateInstance建立你的应用实例。
五、COleObjectFactory::XClassFactory::CreateInstance 调用 CRuntimeClass::CreateInstance在内存中建立你的MFC类实例。而后CreateInstance调用InternalQueryInterface获取你的类IUnknown接口指针。InternalQueryInterface依次调用CCmdTarget::GetInterface.。
六、若是所请求的接口是IUnknown之外的其它接口,则GetInterface在你的类接口映射中查找这个接口。不然,GetInterface返回你的类接口映射中的第一个接口指针。这个工做对于实现IUnknown的每个嵌套类都是同样的。
七、全部的函数返回、返回、返回……..而调用者只管接收某个接口指针。 谁说COM难学?
在结束本文的讨论前,我想指出两个有用的技巧和建议。重载MFC缺省行为的方式有不少。第一,你能够在类工厂中,有时你可能想从COleObjectFactory类派生本身专用的类工厂。只要你愿意,能够这么作,诀窍是将类工厂挂钩在对象上。不要使用DECLARE_OLECREATE 和 IMPLEMENT_OLECREATE,由于这些宏已经将ColeObjectFactory写死在里面了。但你能够拷贝这些宏、更名以及江类工厂的名字改成本身的名字。尤为是你可能要重载COleObjectFactory::OnCreateObject方法。例如,若是你的COM对象是单实例的,就要重载OnCreateObject返回一个且是惟一的一个对象实例(不要使用静态对象,要否则可能遇到引用计数问题,由于静态实例引用计数是1而且最终得不到Release释放。应该取而代之用new在堆中分配单实例)。
最后,一个很是有用的重载是GetInterfaceHook。记得GetInterface吗?这个函数在你的接口映射中查找接口,或若是请求的是IUnknown,则就返回第一个接口。下面是一段参考代码:
LPUNKNOWN CCmdTarget::GetInterface(const void* iid) { // 容许常规构子首先起来 LPUNKNOWN lpUnk; if ((lpUnk = GetInterfaceHook(iid)) != NULL) return lpUnk; …… // 如前所述 }
在作其它事情以前,GetInterface调用虚函数CCmdTarget::GetInterfaceHook,缺省CCmdTarget实现返回NULL,但若是你想以某种特别方式实现QueryInterface接口的话,只要重载GetInterfaceHook并返回别的东西就好了。一旦GetInterfaceHook的返回值为非空接口指针,则它首先调用QueryInterface,MFC将用到它。在个人另外一篇文章中,曾讨论过如何重载GetInterfaceHook来完成一个彻底不一样的COM实现,其中用了ATL风格的多继承代替了MFC的嵌套类。 谁说COM复杂难懂?