在《COM本质论》第162页下面在讲二进制复合时有段话说“如果外部对象可以请求任何类型的初始接口,那么内部对象必须保持两套vptr,一套委托他的QueryInterface、AddRef和Release实现,另一套不委托这些实现。在限制了初始接口是IUnknown后,对象的实现者只需隔离一个vptr作为非委托IUnknown即可。”
这段话是什么意思,怎么理解?能给出个例子吗?不胜感激。

解决方案 »

  1.   

    在潘爱民的《COM原理与应用》一书中有关聚合与包容一节有详细的解答!!!
      

  2.   

    这是对聚合的解释,《COM原理与应用》一书中有详细的答案。例如,如果A组件有x接口,B组件有y接口。 A聚合了B,A将拥有一个B的非委托接口指针。因为如果从a查找y接口的话,他将调用非委托接口指针来查找。b将拥有一个外部组件a的unknow接口指针,由b的委托接口指针来调用。如果从y查找x,那么委托接口将调用外部的接口指针。如果我讲的不清楚。你自己可以看看书。
      

  3.   

    我的意思是为什么外部接口只能对内部接口请求IUnknown.
      

  4.   

    因为在聚合的情况下,除了创建内部组件的时候的QueryInterface请求,以后的QueryInterFace请求都会被简单转发到外部组件。只有第一次请求得到内部组件的非委托IUnkown接口,以后外部组件才能使用这个非委托IUnkown接口的QueryInterFace方法得到内部组件的其他接口指针。
      

  5.   

    因为外部组件只有一次机会,所以他要拿到最重要的。否则,如果拿到其他接口指针,那么以后想要unkown指针也办不到了。
      

  6.   

    ultraboy给出了为什么只能请求IUnknown的合理解释,那么“如果外部对象可以请求任何类型的初始接口,那么内部对象必须保持两套vptr,一套委托他的QueryInterface、AddRef和Release实现,另一套不委托这些实现。在限制了初始接口是IUnknown后,对象的实现者只需隔离一个vptr作为非委托IUnknown即可。”如何解释呢?
      

  7.   

    要解释倒是不容易,不过看看实现还是蛮清楚,看一下ATL的实现代码就知道了。
      

  8.   

    如果你理解了第一点,第二点是一个合理的推论。如果你想通过第一次得到的其他指针查询到非委托的IUnkown接口或者是内部组件的其他接口,那么很明显,这些实现跟委托IUnkown接口必须分开,不然内部组件无法判断哪些调用是外部租件的非委托调用,哪些是客户的需要委托的调用,也就是说,内部组件给外部租件的视图应该和给客户的视图是不同的。你可以试着自己实现一下,就会很明白了。
      

  9.   

    把你email告诉我,我给你一个sdk实现的COM,一切就会明白了。
      

  10.   

    那就不客气了,小弟先谢过了[email protected]
      

  11.   

    麻烦Ultraboy 帮我看一下我的这些代码能够说明问题吗,我写的注释能说明我已经理解了你的意思了吗?
    另外感谢arxing,虽然你发给我的代码我没看懂。真实抱歉,我对sdk编成只是个七七八八的水平
    class Outer:public IOuter{
    public:
        IUnknown* m_pUnkInner;
        Outer(){
            m_pUnkInner = CoCreateInstance(CLSID_Inner,this,CLSCTX_ALL,IID_IUnknown,(void**)&m_pUnkInner);
            /*
            Attention: m_pUnkInner's QI must route the QI request from Outer to Inner::InternalQI
            by Inner::InnerIUnknown, otherwise, m_pUnkInner::QI will be routed to Outer::QI,this
            will cause a infinite loop
            so if you request other interface Instead of IUnkonw at here, Inner must return a pointer
            point to a Object who has the ability of route the Inner::QI request Inner::InternalQI
            */
        }
        QI(REFIID riid,void** ppv){
            if(riid == IID_IInner)
                /*
                Attention: this time m_pUnkInner is point to Inner::m_innerUnknown
                so this call is routered to Inner::InternalQI at last
                */
                return m_pUnkInner->QI(riid,ppv);
            else
                ...
        }
    };
    class Inner:public Inner{
        IUnkown* m_UnkOuter;
        QI(REFIID riid,void** ppv){return m_pUnkOuter->QI(riid,ppv);}
        AddRef()                  {m_pUnkOuter->AddRef();}
        Release()                 {m_pUnkOuter->Release();}
        InternalAddRef();
        InternalRelease();
        InternalQI(REDIID riid,void** ppv){
            if(riid == IID_UNKNOWN)
                *ppv = static_cast<IUnknown*>(&m_innerUnknown);
                /*Attention:if QI IID_IUnkown, it return &m_innerUnknown
                it means that if you get a IUnknown pointer, it's point to
                InnerUnknown and you call QI on it, it will call this method
                m_innerUnkown::QI,then Inner::InternalQI();
                */
            else if(riid == IID_IInner)
                *ppv = static_cast<IInner*>(this);
                /*
                but if you call QI on this *ppv later, it will call Inner::QI
                then Outer::QI.
                */
        }
        class InnerIUnknown:public IUnknown{
            Inner* This(){}
            QI(REFIID riid,void** ppv){return This()->InternalQI(riid,ppv);}
            AddRef()                  {This()->InternalAddRef();}
            Release()                 {This()->InternalRelease();}
        };
        IUnknown m_innerUnknown;
        Inner(IUnknown* pUnkOuter){
            m_pUnkOuter = pUnkOuter == 0 ? &m_innerUnknown : pUnkOuter;
        }
    };class InnerObject:public IClassFactory{
        STDMETHODIMP CreateInstance(IUnknown* pUnkOuter,REFIID riid,void** ppv){
            if(pUnkOuter != 0&& riid != IID_IUnknown) return (*ppv = 0),E_INVALIDARG;
            Inner* p = new Inner(pUnkOuter);
            p->InternalAddRef();
            p->InternalQI(riid,ppv);
            p->InternalRelease();
        }
    };
    int main(){
        IOuter* pOuter;
        IInner* pInner;
        CoCreateInstance(CLSID_IOuter,0,CLSCTX_ALL,IID_IUnknown,(void**)&pOuter);
        pInner = pOuter->QI(CLSID_IInner,(void**)&pInner);
        pOuter = pInner->QI(CLSID_IOuter,(void**)&pOuter);
    }
      

  12.   

    我再补充一下:其实COM本质论中说因为避免让内部对象拥有两套vptr而只允许请求IUnkown好像错了。为了说明这一点,先做一些准备:
    非委托接口指针:内部对象的QI方法调用自己的InternalQI
    委托接口指针:内部对象的QI方法委托给外部对象
    还要明确两点:
    1、外部对象不能在QI时返回内部对象的非委托指针,因为这样最终用户得到的接口只指针的QI方法将无法返回外部对象的接口指针
    2、外部对象所包含的内部对象的接口指针必须是非委托接口指针,否则外部对象对内部对象的接口指针的QI方法的调用将造成死循环。
    那么,如果外部对象可以用任意内部对象所支持的非委托接口指针作为它所包含的内部对象的代表,则当对外部对象QI与外部对象所保存的内部对象的代表的接口指针的类型一致的接口指针时,将出现矛盾。
      

  13.   

    抱歉,看不懂你实现的意思。看COM本质论以前最好看看COM技术内幕,掌握一些基础的COM知识。对于你说明的看法:
    1。不明白非委托指针的意思,是只IUnkown指针?还是其他的指针如IX?其他指针并不存在委托与非委托之分。实际上聚合对象的IUnkown实现只是根据OuterUnkown指针是否为NULL来决定是把请求转发到外部对象还是调用非委托IUnkown实现。
    2。因为限定第一次请求必须为IUnkown的情况下,除了IUnkown接口的三个函数需要考虑是否聚合的情况外,其他接口不需要考虑聚合,所以不会出现你说的情况,很明显你把所有的接口都理解为有委托与非委托之分了。
      

  14.   

    这里存在一个违背com object的一般性原则的东西:
    各接口之间应该是STR(对称,自反,传递,一个群)。但是inner object的IUnknown却不能有这样的性质。虽然,从它可以query其他的接口,但是其他的接口却无法query到这个non-delegated IUnknown,事实上这个IUnknown既不对称,也不传递。 从外面(client of outer object)看很美,从里面(outer object as a client)看,却不怎么样。我想,inner object由于不符合这个原则,此时不能视为一个完全意义的com object,因而outer object 对它的使用也会有许多不同于未聚合(但不是不可以聚合)的object的特殊要求。与其说这个IUnknown是个接口,不如说它是个句柄,当然是在一定条件下(punkouter!=NULL )。这里我们遇到的实质问题是,无法让两个不同的接口集合通过正常的com查询(queryinterface),得到对方的引用,这是很显然的,因为每一个集合都是自封闭的,如果可以的话,按照STR,那就是一个接口集合。因此即便我们实现两套接口,我们仍要在CoCreateInstance(由outer object 调用)时,明确指定是INondelegatedXXX, 还是 IXXX, 仍然不可能自由,要想得到另一套接口的指针,还要通过其他的途径,比如INondelegetedIf->GetDelegetedIUnknown。所以,从实现上还不如把inner object的IUnknown变成handle实用。