荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: DuMiYue (TH@Figo), 信区: Visual
标  题: IUnknown—COM和MFC
发信站: 荔园晨风BBS站 (Tue Mar 19 15:01:07 2002), 转信

IUnknown—COM和MFC
文/赵湘宁
问题:
    我用MFC编写COM程序有一段时间了,知道如何使用宏和嵌套类,以及如何在嵌套类
中处理IUnknown接口,但对IUnknown的使用还是不太老练。
假设CMyClass是一个COM服务器,从CCmdTarget派生。它实现了IMyInterface。CMyClas
s的定义如下:
class CMyClass: public CCmdTarget
{
    BEGIN_INTERFACE_PART(...)
    STDMETHOD....
    END_INTERFACE_PART
    DECLARE_INTERFACE_MAP
} ;
因为CCmdTarget没有QueryInterface方法,CMyClass也没有这个方法。我的问题是:这
个COM服务器的客户端总是要调用QueryInterface,它不调用ExternalQueryInterface,
 也不调用 InternalQueryInterface。因此如果QueryInterface不是一个CMyClass的有
效方法。这种情况该怎么处理?
解答:
    关于QueryInterface的这个问题,除你之外的许多人都对它感到困惑。但是不用怕
,读完本文就可以见分晓。导致困惑的一个潜在的问题是CCmdTarget本身不从CObject派
生,并且两者都有不同于QueryInterface的虚函数——例如,CObject中的第一个虚函数
是GetRuntimeClass,CCmdTarget中的第一个虚函数是OnCmdMsg。所以从CCmdTarget派生
出来的类怎么可能是COM类呢?而且还必须实现头三个虚函数是AddRef, Release, 和 Q
ueryInterface的IUnknown接口:
实际上,解开这个谜团的方法很简单,但要求深入了解MFC类库。为此,我们必须进入M
FC考察一番,主要目的是看看在调用CoCreateInstance创建类实例的时候会发生一些什
么事情。
首先,CoCreateInstance所做的第一件事情(或多或少)是到注册表中检查哪个DLL实现
你的类。为简单起见,假设使用的是进程内服务器。CoCreateInstance加载你的DLL并调
用特殊函数DllGetClassObject.。
// DLL 函数创建COM对象类工厂
DllGetClassObject(REFCLSID rclsid, // 类 ID
                  REFIID riid,    // 接口 ID
                  LPVOID* ppv)    // 返回的接口指针
    实际上,DllGetClassObject得不到你的类实例,它得到的是类工厂(IClassFacto
ry)实例,通过这个类工厂来创建你得类实例。很怪是不是?MFC提供DllGetClassObje
ct的实现,任务是搜索DLL中的所有类工厂,看看哪个类的ID寓所请求的类ID匹配。MFC
是如何知道搜索哪个类工厂呢?当你在编写子记得类代码时,用宏DECLARE_OLECREATE
和 IMPLEMENT_OLECREATE声明并实现你自己这个类的类工厂。尤其是IMPLEMENT_OLECRE
ATE声明一个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);
}
这里省略了许多琐碎代码以便突出重点,这些代码包括:创建一个实例和调用Internal
QueryInterface。OnCreateObject是个COleObjectFactory的虚拟函数,它通过MFC的运
行时类创建你的类实例:
//同样也作了简化
CCmdTarget* COleObjectFactory::OnCreateObject()
{
    return (CCmdTarget*)m_pRuntimeClass->CreateObject();
}
    一旦COleObjectFactory::XClassFactory::CreateInstance有了你的类实例,便调
用InternalQueryInterface获取类的IUnknown接口指针。这里省略了许多细节,例如,
在实际代码中MFC要调用IClassFactory2::CreateInstanceLic并检查所有出错条件以及
聚合。InternalQueryInterface 和 ExternalQueryInterface之间的差别是外部版本委
派外部IUnknown(如果有的话),而内部版本则不然。但即使聚合,最终都要归到Inte
rnalQueryInterface。
    如果还不明白,不要紧。类工厂的CreateInstance函数创建了一个你的类实例,并
调用CCmdTarget::InternalQueryInterface来获取类的IUnknown指针。InternalQueryI
nterface在哪里得到你的类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类自身没有QueryI
nterface函数,只有嵌套类有,这个嵌套类实现每个接口,每个接口又都实现IUnknown

   图一是一个典型的COM类实现。.CPP文件说明了你必须为你的类所提供的每一个接口
编写同样烦人IUnknown实现。每一个IUnknown方法为父类(从CcmdTarget派生)调用相
应的ExternalXxx(或者InternalXxx——如果你不想要聚合)方法。这个实现对于你编
写的每一个接口都一样。这是没有办法的,因为所有的接口都通过相同的单对象在内存
中被实例化。AddRef 和Release必须增加和减少相同的物理指针——与AddRef 或Relea
se实际属于哪个接口(嵌套类)无关。此乃高招所在。
    这就是为什么在IUnknown的情况下只有InternalQueryInterface能返回你的接口映
射中的第一个接 口指针。由于仅仅实现IUnknown的类没什么用,所以在你的映射中至少
还要实现一个接口。如果你不明白,下面是具体步骤的解释:
1、客户端调用CoCreateInstance创建你的类实例。
2、CoCreateInstance查找并加载DLL,调用DllGetClassObject
3、DllGetClassObject搜索DLLs的类工厂清单找出与类工厂匹配的类ID,然后返回这个
类工厂的指针。
4、CoCreateInstance调用IClassFactory::CreateInstance创建你的应用实例。
5、COleObjectFactory::XClassFactory::CreateInstance 调用 CRuntimeClass::Crea
teInstance在内存中创建你的MFC类实例。然后CreateInstance调用InternalQueryInte
rface获取你的类IUnknown接口指针。InternalQueryInterface依次调用CCmdTarget::G
etInterface.。
6、如果所请求的接口是IUnknown以外的其它接口,则GetInterface在你的类接口映射中
查找这个接口。否则,GetInterface返回你的类接口映射中的第一个接口指针。这个工
作对于实现IUnknown的每一个嵌套类都是一样的。
7、所有的函数返回、返回、返回……..而调用者只管接收某个接口指针。
谁说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复杂难懂?

--
他能够不知不觉令到我倾心
    也能够一句说话而留下痛恨
        感情最光辉一刹热暖我一生
            爱人远走了以后仍然是爱人

※ 来源:·荔园晨风BBS站 bbs.szu.edu.cn·[FROM: 218.30.30.9]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店