简体   繁体   English

在全局单例对象中存储COM指针的问题

[英]Problem with storing COM pointers in global singleton object

Background 背景

The application I am working with has several COM DLLs. 我正在使用的应用程序有几个COM DLL。

One of the COM DLLs has a global singleton object, which stores pointers to COM interfaces in other DLLs. 一个COM DLL具有一个全局单例对象,该对象将指向COM接口的指针存储在其他DLL中。 Because it is a global singleton object, I have employed the lazy initialization idiom because it is possible that the interface I am trying to get a pointer to exists in a DLL which hasn't yet been loaded. 因为它是一个全局单例对象,所以我采用了惰性初始化方法,因为我试图获取指向的接口的可能存在于尚未加载的DLL中。

( Side-note: This is especially important when registering a single DLL, as the global objects will be constructed within the regsvr32 process, and I don't want the DLL to attempt to acquire an interface to another DLL during this process.) 旁注:这在注册单个DLL时特别重要,因为全局对象将在regsvr32进程内构造,并且我不希望DLL在此过程中尝试获取与另一个DLL的接口。)

For example, my lazy initialization method would do something like this: 例如,我的惰性初始化方法将执行以下操作:

CComPtr<IMyOtherObject>&
CGlobalSingleton::
GetMyOtherObject()
{
    // SNIP: Other code removed for clarity...

    if (! m_pMyOtherObject)
    {
        hr = pUnknown->QueryInterface(IID_IMyOtherObject,
            (void**) &m_pMyOtherObject);
    }

    return m_pMyOtherObject;
}

NOTE: m_pMyOtherObject is a member variable of the CComPtr type. 注意: m_pMyOtherObjectCComPtr类型的成员变量。

The lazy initialization may not be relevant to my problem here, but I'm including it for completeness. 延迟初始化可能与我的问题无关,但出于完整性考虑,我将其包括在内。

Problem 问题

What I have noticed is that in some circumstances, I get failed assertions when my application shuts down. 我注意到的是,在某些情况下,当我的应用程序关闭时,我会得到失败的断言。 However, if I change my code to call QueryInterface() every time I need to access IID_IMyOtherOBject (rather than storing it as a member variable) this prevents the assertions. 但是,如果我每次需要访问IID_IMyOtherOBject (而不是将其存储为成员变量)时都将代码更改为调用QueryInterface()IID_IMyOtherOBject阻止声明。

This appears to me to be a COM object lifetime issue. 在我看来,这是一个COM对象生存期问题。 My hypothesis is that because I am storing a COM pointer, there needs to be some sort of synchronisation between the destruction of the COM interface that I'm pointing to, and my own pointer to it. 我的假设是,因为我要storing COM指针,所以在我指向的COM接口的销毁与我自己的COM指针之间需要某种同步。

My understanding of the CComPtr class (which I am using) is that it takes away a lot of the headaches of dealing with lifetime issues (ie calling AddRef() and Release() ). 我对CComPtr类(我正在使用)的理解是,它消除了处理生命周期问题(即调用AddRef()Release() )的许多麻烦。 But it doesn't appear to be working in my case. 但这似乎不适用于我的情况。

Can anyone pick what I may be doing wrong? 有人可以挑我可能做错了吗?

You're returning a reference to the smart pointer which might not be increasing the reference count. 您正在返回对智能指针的引用,这可能不会增加引用计数。 Sorry, I'd check but it's late here. 抱歉,我要检查一下,但是已经晚了。 That's my hunch and it fits what you're describing - look into copy constructors for CComPtr. 这是我的直觉,适合您的描述-查看CComPtr的副本构造函数。

Hope that helps, 希望能有所帮助,

K ķ

Rather than implementing your own global singleton, look at using the IGlobalInterfaceTable interface instead. 与其实现自己的全局单例,不如使用IGlobalInterfaceTable接口。 It is a singleton that is provided by the OS at the process level. 它是OS在流程级别提供的单例。 Any of your DLLs can put their COM objects into the table, and the other DLLs can retreive them when needed. 您的任何DLL都可以将其COM对象放入表中,而其他DLL则可以在需要时检索它们。 All you would need to implement on your part is a way for the DLLs to exchange the table's DWORD cookies with each other. 您所需要做的只是让DLL相互交换表的DWORD cookie。

A wild stab in the dark: Is it possible that CGlobalSingleton could get destroyed after CoUninitialize() is called, under any circumstances? 黑暗中的CGlobalSingleton :在任何情况下,调用CoUninitialize() CGlobalSingleton是否有可能被破坏? If it were, and m_pMyOtherObject was therefore also destroyed after COM uninitialization, it would be another way of causing the access violation that Igor mentioned. 如果是这样,并且m_pMyOtherObject因此在COM初始化后也被销毁了,那将是Igor提到的另一种导致访问冲突的方式。

I suspect the problem is in your understanding of the copy / assignment semantics of the CComPtr class; 我怀疑问题出在您对CComPtr类的复制/赋值语义的理解上; I am not particularly familiar with CComPtr, but in my experience smart pointers tend to not work the way you might expect them to. 我对CComPtr并不是特别熟悉,但是根据我的经验,智能指针往往无法像您期望的那样工作。 First you should read the documentation for CComPtr and make sure you understand how it works (it wouldn't hurt to look at the source code, either). 首先,您应该阅读CComPtr的文档,并确保您了解它的工作原理(也可以查看源代码)。 You could also try putting some breakpoints in the AddRef() and Release() members of CComPtr to see what happens during and after the call to GetMyOtherObject(), particularly if you are temporarily storing the return value and it goes out of scope. 您还可以尝试在CComPtr的AddRef()和Release()成员中放置一些断点,以查看在调用GetMyOtherObject()期间和之后发生的情况,特别是在您临时存储返回值且超出范围的情况下。

Sounds like m_pMyOtherObject is still alive when you shut down your application. 当您关闭应用程序时,听起来m_pMyOtherObject仍然有效。 In addition to copy constructor issues m_pMyOtherObject should either be a CComPtr or CGlobalSingleton should call m_pMyOtherObject 's Release method upon destruction. 除了复制构造函数问题之外, m_pMyOtherObject应该是CComPtrCGlobalSingleton应该在销毁时调用m_pMyOtherObjectRelease方法。

Edited for clarity. 为清楚起见进行了编辑。

Edit Just did a quick test and didn't encounter any issues using function returning a reference to CComPtr . 编辑 Just进行了快速测试,使用返回CComPtr的引用的函数没有遇到任何问题。 Whilst this is a bit unusual it didn't cause any reference count issues. 虽然这有点不寻常,但没有引起任何引用计数问题。

I wanted to expand though on what happens if m_pMyOtherObject is not a smart pointer. 我想扩展一下如果m_pMyOtherObject不是智能指针会发生什么。 In this scenario it will never get released. 在这种情况下,它将永远不会被释放。 Let me show you why: 让我告诉你为什么:

  1. You call QueryInterface on some pointer. 您在某些指针上调用QueryInterface。 It will call AddRef on that object. 它将在该对象上调用AddRef。
  2. You return either CComPtr& CComPtr& or naked interface pointer. 您返回CComPtr&CComPtr&或裸接口指针。 That is largely irrelevant. 这在很大程度上无关紧要。 No ref count operations take place (unless you assign the return value to another CComPtr, which will AddRef it. But since that CComPtr will balance it out with a call to Release it doesn't matter). 不会进行引用计数操作(除非您将返回值分配给另一个CComPtr,它将添加AddRef。但是既然那样,CComPtr会将它与调用Release平衡下来就没关系了)。
  3. What you end up with is either 1 call to AddRef and 0 to Release or 2 calls to AddRef and 1 to Release. 您最终得到的结果是1调用AddRef,0调用Release或2调用AddRef,1调用Release。 In other words they are unbalanced and you have a reference leak. 换句话说,它们是不平衡的,您会有参考泄漏。

To avoid this you need to structure your program like this: 为了避免这种情况,您需要像下面这样构造程序:

class CGlobalSingleton{

CComPtr<IMyOtherObject> m_spMyOtherObject;

IMyOtherObject* GetMyOtherObject()
{
    // SNIP: Other code removed for clarity...

    if (! m_spMyOtherObject)
    {
        //pUnknown gets AddRef'ed, but that's OK, m_spMyOtherObject will call release when CGlobalSingleton goes out of scope
        hr = pUnknown->QueryInterface(IID_IMyOtherObject,
            (void**) &m_spMyOtherObject);
    }

    return m_pMyOtherObject;
}
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM