COM+ 发布方法未降为 0

COM+ Release Method Not Dropping to 0

我想与您分享我面临的这个问题。长话短说,我有这个小代码(仅用于测试):

    int main ()
    {
        IXMLDOMDocument *pDoc(nullptr);
        CoCreateInstance(CLSID_DOMDocument, nullptr, CLSCTX_ALL, IID_IXMLDOMDocument, reinterpret_cast<LPVOID*>(&pDoc));
        DWORD d = pDoc->AddRef();
        std::cout << "pDoc: add ptr=" << pDoc << " d=" << d << std::endl;
        d = pDoc->Release();
        std::cout << "pDoc: rel ptr=" << pDoc << " d=" << d << std::endl;

        IUnknown *pUnk(nullptr);
        pDoc->QueryInterface(IID_IUnknown, reinterpret_cast<LPVOID*>(&pUnk));
        d = pUnk->AddRef();
        std::cout << "pUnk: add ptr=" << pUnk << " d=" << d << std::endl;
        d = pUnk->Release();
        std::cout << "pUnk: rel ptr=" << pUnk << " d=" << d << std::endl;

        /*Release objects*/
        d = pUnk->Release();
        std::cout << "pUnk: rel ptr=" << pUnk << " d=" << d << std::endl;
        d = pDoc->Release();
        std::cout << "pDoc: rel ptr=" << pDoc << " d=" << d << std::endl;

        return 0;
    }

我希望最后 2 个 cout 打印 0 作为返回计数,在它们的位置我看到:

pDoc: add ptr=004A4628 d=2
pDoc: rel ptr=004A4628 d=1
pUnk: add ptr=004A3A10 d=4
pUnk: rel ptr=004A3A10 d=3
pUnk: rel ptr=004A3A10 d=2
pDoc: rel ptr=004A4628 d=0

为什么 QueryInterface 返回一个 IUnknown 内部计数从 3 开始? 为什么 IUnknown 对象的最后一个 Release 方法没有返回异常 0

我可能遗漏了什么?

当一个对象被具体查询其 IUnknown 接口时,COM 期望每次都返回同一个对象,以确保身份测试有效(您可以测试两个接口是否指向内存中的同一个对象通过查询两个接口的 IUnknown 然后比较查询的指针)。 QueryInterface() documentation:

中指出了这一点

For any one object, a specific query for the IUnknown interface on any of the object's interfaces must always return the same pointer value. This enables a client to determine whether two pointers point to the same component by calling QueryInterface with IID_IUnknown and comparing the results. It is specifically not the case that queries for interfaces other than IUnknown (even the same interface through the same pointer) must return the same pointer value.

因此,当通过 QueryInterface() 请求 DOMDocument 对象的 IUnknown 接口时,人们会期望引用计数增加 1,而不是增加 2。在这种情况下,您 应该 在你的输出中得到以下数字:

int main ()
{
    IXMLDOMDocument *pDoc(nullptr);
    CoCreateInstance(CLSID_DOMDocument, nullptr, CLSCTX_ALL, IID_IXMLDOMDocument, reinterpret_cast<LPVOID*>(&pDoc));
    // DOMDoc refcnt=1

    DWORD d = pDoc->AddRef();
    // DOMDoc refcnt=2
    std::cout << "pDoc: add ptr=" << pDoc << " d=" << d << std::endl;

    d = pDoc->Release();
    // DOMDoc refcnt=1
    std::cout << "pDoc: rel ptr=" << pDoc << " d=" << d << std::endl;

    IUnknown *pUnk(nullptr);
    pDoc->QueryInterface(IID_IUnknown, reinterpret_cast<LPVOID*>(&pUnk));
    // DOMDoc refcnt=2

    d = pUnk->AddRef();
    // DOMDoc refcnt=3
    std::cout << "pUnk: add ptr=" << pUnk << " d=" << d << std::endl;

    d = pUnk->Release();
    // DOMDoc refcnt=2
    std::cout << "pUnk: rel ptr=" << pUnk << " d=" << d << std::endl;

    /*Release objects*/
    d = pUnk->Release();
    // DOMDoc refcnt=1
    std::cout << "pUnk: rel ptr=" << pUnk << " d=" << d << std::endl;

    d = pDoc->Release();
    // DOMDoc refcnt=0
    std::cout << "pDoc: rel ptr=" << pDoc << " d=" << d << std::endl;

    return 0;
}

然而,实际上,当您查询 DOMDocument 对象的 IUnknown 接口时,显然有对该对象的额外内部引用,并且直到所有对查询的IUnknown接口已发布。这将解释您看到的数字:

int main ()
{
    IXMLDOMDocument *pDoc(nullptr);
    CoCreateInstance(CLSID_DOMDocument, nullptr, CLSCTX_ALL, IID_IXMLDOMDocument, reinterpret_cast<LPVOID*>(&pDoc));
    // DOMDoc refcnt=1

    DWORD d = pDoc->AddRef();
    // DOMDoc refcnt=2
    std::cout << "pDoc: add ptr=" << pDoc << " d=" << d << std::endl;

    d = pDoc->Release();
    // DOMDoc refcnt=1
    std::cout << "pDoc: rel ptr=" << pDoc << " d=" << d << std::endl;

    IUnknown *pUnk(nullptr);
    pDoc->QueryInterface(IID_IUnknown, reinterpret_cast<LPVOID*>(&pUnk));
    // DOMDoc refcnt=3, not 2!

    d = pUnk->AddRef();
    // DOMDoc refcnt=4
    std::cout << "pUnk: add ptr=" << pUnk << " d=" << d << std::endl;

    d = pUnk->Release();
    // DOMDoc refcnt=3
    std::cout << "pUnk: rel ptr=" << pUnk << " d=" << d << std::endl;

    /*Release objects*/
    d = pUnk->Release();
    // DOMDoc refcnt=1, not 2!
    std::cout << "pUnk: rel ptr=" << pUnk << " d=" << d << std::endl;

    d = pDoc->Release();
    // DOMDoc refcnt=0
    std::cout << "pDoc: rel ptr=" << pDoc << " d=" << d << std::endl;

    return 0;
}

当查询 IUnknown 时,DOMDocument 对象可能会返回一个指向内部帮助对象的指针,并且该帮助对象会从 AddRef()Release() 返回拥有 DOMDocument 的引用计数而不是返回它自己的引用计数。

Why QueryInterface returned me an IUnknown which internal count begins in 3?

pDocpUnk 本质上是访问单个对象的两种方式。由于它是单个对象,这反映在引用计数中并解释了为什么它不从 1 开始。

但是根据该解释,您可能希望引用计数从 2 开始而不是 3。它从 3 开始的事实可能是由 DOMDocument 用来处理 IUnknown 接口,内部帮助对象在其中维护一个额外的引用。

Why last Release method of the IUnknown object isn't returning 0 as excepted?

同理:pDocpUnk本质上是同一个对象。由于此时您仍有未发布的引用(可通过 pDoc 访问),因此该对象仍然存在。

根据 AddRef and Release 的 MDSN 文档,return 值仅用于测试目的。它可能无法准确反映实际参考文献数量;特别是针对 0 进行测试并不能保证对象已完成。

Under what conditions will the IUnknown::AddRef method return 0?.

您查看的是 Com Aggregation,这里 pUnkinner 对象,pDocaggregable对象。同样有趣的是,当您在 inner 对象上查询 IXMLDOMDocument 接口时 - 他每次都分配 new aggregable对象,实现了这个接口


让我们开始创建用于打印对象引用计数的实用函数,并从 com 视图比较 2 个对象指针(此指针的二进制值可以不同,但​​ IUnknown 两个对象相同)

ULONG GetRefCount(IUnknown *pUnk, BOOLEAN bPrint = TRUE)
{
    pUnk->AddRef();
    ULONG d = pUnk->Release();
    if (bPrint) DbgPrint("%p>%u\n", pUnk, d);
    return d;
}

BOOLEAN IsSameObjects(IUnknown *p, IUnknown *q)
{
    BOOLEAN f = FALSE;

    IUnknown *Unk1, *Unk2;
    if (0 <= p->QueryInterface(IID_PPV_ARGS(&Unk1)))
    {
        if (0 <= q->QueryInterface(IID_PPV_ARGS(&Unk2)))
        {
            f = Unk1 == Unk2;
            Unk2->Release();
        }
        Unk1->Release();
    }

    DbgPrint("%p[%u] %s %p[%u]\n", p, GetRefCount(p, FALSE), f ? "==" : "!=", q, GetRefCount(q, FALSE));
    return f;
}

现在让我们做第一个测试:

void test1 ()
{
    IXMLDOMDocument *pDoc, *pDoc2;
    if (0 <= CoCreateInstance(__uuidof(DOMDocument), 0, CLSCTX_ALL, IID_PPV_ARGS(&pDoc)))
    {
        GetRefCount(pDoc);

        IUnknown *pUnk;

        if (0 <= pDoc->QueryInterface(IID_PPV_ARGS(&pUnk)))
        {
            IsSameObjects(pDoc, pUnk);

            if (0 <= pUnk->QueryInterface(IID_PPV_ARGS(&pDoc2)))
            {
                IsSameObjects(pDoc, pDoc2);
                GetRefCount(pUnk);
                pDoc2->Release();
                GetRefCount(pUnk);
            }

            pUnk->Release();
        }

        GetRefCount(pDoc);

        DbgPrint("Final Release=%u\n", pDoc->Release());
    }
}

它输出:

000001DD8DCE71A0>1
000001DD8DCE71A0[1] == 000001DD8DCE5950[3]
000001DD8DCE71A0[1] == 000001DD8DCE7270[1]
000001DD8DCE5950>4
000001DD8DCE5950>3
000001DD8DCE71A0>1
Final Release=0

这里可见pUnkpDocpDoc2)指向不同的内存位置,但这是同一个com对象


在此基础上做更多的对称测试:

void test2 ()
{
    IUnknown *pUnk;
    if (0 <= CoCreateInstance(__uuidof(DOMDocument), 0, CLSCTX_ALL, IID_PPV_ARGS(&pUnk)))
    {
        GetRefCount(pUnk);
        IXMLDOMDocument *pDoc, *pDoc2;

        if (0 <= pUnk->QueryInterface(IID_PPV_ARGS(&pDoc)))
        {
            IsSameObjects(pUnk, pDoc);

            if (0 <= pUnk->QueryInterface(IID_PPV_ARGS(&pDoc2)))
            {
                IsSameObjects(pDoc2, pDoc);
                GetRefCount(pUnk);

                pDoc2->Release();
            }

            if (0 <= pDoc->QueryInterface(IID_PPV_ARGS(&pDoc2)))
            {
                IsSameObjects(pDoc2, pDoc);
                GetRefCount(pUnk);
                pDoc2->Release();
            }

            pDoc->Release();
        }

        GetRefCount(pUnk);

        DbgPrint("Final Release=%u\n", pUnk->Release());
    }
}

并输出:

000001DD8DCE5950>1
000001DD8DCE5950[3] == 000001DD8DCE7270[1]
000001DD8DCE71A0[1] == 000001DD8DCE7270[1]
000001DD8DCE5950>4
000001DD8DCE7270[2] == 000001DD8DCE7270[2]
000001DD8DCE5950>3
000001DD8DCE5950>1
Final Release=0

在这里更清楚地看到第一个创建的内部对象。每次我们在这个对象上查询 IXMLDOMDocument - 创建新的可聚合对象,并返回指向它的指针。


这是如何在代码中实现的?简单演示

struct __declspec(novtable) __declspec(uuid("78979DF1-A166-4797-AF2B-21BBE60D0B2E")) IDemo : public IUnknown
{
    virtual void Demo() = 0;
};

class CDemo : public IDemo
{
    IUnknown* _pUnkOuter;
    LONG _dwRef;

    ~CDemo()
    {
        DbgPrint("%s<%p>\n", __FUNCTION__, this);
        _pUnkOuter->Release();
    }

public:

    CDemo(IUnknown* pUnkOuter) : _pUnkOuter(pUnkOuter)
    {
        DbgPrint("%s<%p>\n", __FUNCTION__, this);
        _dwRef = 1;
        pUnkOuter->AddRef();
    }

    virtual void Demo()
    {
        DbgPrint("%s<%p>\n", __FUNCTION__, this);
    }

    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void**ppvObject)
    {
        if (riid == __uuidof(IDemo))
        {
            AddRef();
            *ppvObject = static_cast<IUnknown*>(this);
            return S_OK;
        }

        return _pUnkOuter->QueryInterface(riid, ppvObject);
    }

    virtual ULONG STDMETHODCALLTYPE AddRef()
    {
        return InterlockedIncrement(&_dwRef);
    }

    virtual ULONG STDMETHODCALLTYPE Release()
    {
        ULONG dwRef = InterlockedDecrement(&_dwRef);
        if (!dwRef) delete this;
        return dwRef;
    }
};

class CObject : public IUnknown
{
    LONG _dwRef;

    ~CObject()
    {
        DbgPrint("%s<%p>\n", __FUNCTION__, this);
    }
public:

    CObject()
    {
        DbgPrint("%s<%p>\n", __FUNCTION__, this);
        _dwRef = 1;
    }

    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void**ppvObject)
    {
        *ppvObject = 0;

        if (riid == __uuidof(IUnknown))
        {
            AddRef();
            *ppvObject = static_cast<IUnknown*>(this);
            return S_OK;
        }
        else if (riid == __uuidof(IDemo))
        {
            if (CDemo* pDoc = new CDemo(this))
            {
                *ppvObject = static_cast<IUnknown*>(pDoc);
                return S_OK;
            }

            return E_OUTOFMEMORY;
        }

        return E_NOINTERFACE;
    }

    virtual ULONG STDMETHODCALLTYPE AddRef()
    {
        return InterlockedIncrement(&_dwRef);
    }

    virtual ULONG STDMETHODCALLTYPE Release()
    {
        ULONG dwRef = InterlockedDecrement(&_dwRef);
        if (!dwRef) delete this;
        return dwRef;
    }
};

void test3()
{
    if (CObject* pUnk = new CObject)
    {
        GetRefCount(pUnk);
        IDemo *pDoc, *pDoc2;

        if (0 <= pUnk->QueryInterface(IID_PPV_ARGS(&pDoc)))
        {
            IsSameObjects(pUnk, pDoc);

            if (0 <= pUnk->QueryInterface(IID_PPV_ARGS(&pDoc2)))
            {
                IsSameObjects(pDoc2, pDoc);
                GetRefCount(pUnk);

                pDoc2->Release();
            }

            if (0 <= pDoc->QueryInterface(IID_PPV_ARGS(&pDoc2)))
            {
                IsSameObjects(pDoc2, pDoc);
                GetRefCount(pUnk);
                pDoc2->Release();
            }

            pDoc->Release();
        }

        GetRefCount(pUnk);

        DbgPrint("Final Release=%u\n", pUnk->Release());
    }
}

并输出:

CObject::CObject<000001DD8C340970>
000001DD8C340970>1
CDemo::CDemo<000001DD8C33B950>
000001DD8C340970[2] == 000001DD8C33B950[1]
CDemo::CDemo<000001DD8C338930>
000001DD8C338930[1] == 000001DD8C33B950[1]
000001DD8C340970>3
CDemo::~CDemo<000001DD8C338930>
000001DD8C33B950[2] == 000001DD8C33B950[2]
000001DD8C340970>2
CDemo::~CDemo<000001DD8C33B950>
000001DD8C340970>1
CObject::~CObject<000001DD8C340970>
Final Release=0