COM参考计数问题

时间:2016-09-08 02:27:53

标签: c++ com

我正在编写利用COM接口的代码。我的代码基于我在网上找到的例子。我不想在这种情况下使用智能指针,因为我想了解COM的基础知识而不只是让智能指针类为我做所有的工作。

为了构建我的问题,我们假设我有一个类似于以下的类:

public class TestClass
{
    private:
        IUnknown *m_pUnknown;

    public:
        TestClass();
        void AssignValue();
}

TestClass::TestClass()
{
    m_pUnknown = NULL;
}

void TestClass::AssignValue()
{
    IUnknown *pUnknown  = NULL;

    //Assign value to pUnknown here - not relevant to my questions

    m_pUnknown = pUnknown;

    pUnknown->Release();
}

现在谈谈我的具体问题。

1)我在初始化值时看到的示例不使用AddRef(),例如在类构造函数中。首次为COM指针指定值时,AddRef()是否会在幕后“自动”发生?

2)虽然我的代码示例没有显示,但我理解在AssignValue()方法中,当您指定第二个值来覆盖pUnknown的值时(最初在类中设置)构造函数),Release()被自动调用。将新值分配给pUnknown后,其引用计数为零。我需要在重新分配后立即致电pUnknown->AddRef()。我的理解是否正确?

3 个答案:

答案 0 :(得分:2)

注意:我假设我们在这里忽略了简单性的例外。如果这是真的,你会想要使用智能指针来帮助在出现异常时保持正确。同样,我并不担心正确复制或破坏您的示例类或多线程的实例。 (你的原始指针不能像你想象的那样在不同的线程中使用。)

首先,您需要对COM进行任何必要的调用。在幕后“自动”发生任何事情的唯一方法就是使用智能指针来完成它们。

1)您引用的示例必须从某处获取其COM接口指针。这可以通过进行COM调用,例如CoCreateInstance()和QueryInterface()。这些调用将传递给原始指针的地址,并将该原始指针设置为适当的值。如果它们也不是隐式AddRef'ed,则引用计数可能为0,并且COM可以在程序可以执行任何操作之前删除关联的COM对象。因此,此类COM调用必须代表您包含隐式AddRef()。您负责使用Release()来匹配您使用其中一个其他调用发起的隐式AddRef()。

2a)原始指针是原始指针。它们的价值是垃圾,直到你安排它们被设置为有效的东西。特别是,为一个值赋值不会自动调用函数。分配给接口的原始指针不会调用Release() - 您需要在适当的时候执行此操作。在您的帖子中,您似乎“覆盖”以前设置为NULL的原始指针,因此图片中没有现有的COM接口实例。对于不存在的东西,可能没有AddRef(),并且对于那些不存在的东西,不能是Release()。

2b)

您在示例中通过注释指示的某些代码非常相关,但可以很容易地推断出来。你有一个本地原始指针变量pUnknown。在缺少的代码中,您可能使用COM调用获取接口指针,隐式地使用AddRefs,并使用适当的值填充原始指针以使用它。这使您在完成相应的Release()后负责。

接下来,使用相同的值设置成员原始指针变量(m_pUnknown)。根据以前使用此成员变量的情况,您可能需要在执行此操作之前使用其以前的值调用Release()。

由于1个隐式AddRef()调用,您现在有2个原始指针设置为该值以使用此COM接口实例并负责一个Release()。有两种方法可以解决这个问题,但这两种方法都不是你的样本。

第一个,最直接,最恰当的方法(其他人已正确指出并且我在本答案的第一个版本中跳过了)是每个指针一个AddRef()和一个Release()。您的代码在m_pUnknown中缺少此代码。这需要在分配给m_pUnknown后立即添加m_pUnknown-> AddRef(),并在使用m_pUnknown中的当前接口指针完成后,对Release()“someplace else”进行1次相应的调用。代码中“其他地方”的一个常见候选者是类析构函数。

第二种方法更有效,但不太明显。即使你决定不使用它,你也可以看到它,所以至少应该知道它。在第一种方法之后,您将拥有代码序列:

m_pUnknown = pUnknown;
m_pUnknown->AddRef();
pUnknown->Release();

由于pUnknown和m_pUnknown在此处设置相同,因此Release()会立即撤消AddRef()。在这种情况下,将此AddRef / Release对删除是引用计数中性并保存2次往返COM。我的心理模型是将接口和引用计数从一个指针转移到另一个指针。 (使用智能指针,它看起来像newPtr.Attach(oldPtr.Detach()); )这种方法让你使用原始/未显示的隐式AddRef()并且需要添加相同的m_pUnknown-&gt ;释放()“在其他地方”,如第一种选择。

在任何一种方法中,您都会将AddRefs(隐式或显式)与每个接口的Releases完全匹配,并且在完成接口之前永远不会转到0引用计数。一旦命中0,就不会尝试使用指针中的值。

答案 1 :(得分:2)

Avi Berger已经发布了一个很好的答案,但是如果它有助于理解,那么就是另一种说法。

在COM中,引用计数在COM对象中完成。 COM运行时将破坏并释放引用计数达到0的对象。 (这可能会从计数点击0)开始延迟一段时间。

其他一切都是惯例。 C ++ COM程序员通常的惯例是原始接口指针应被视为拥有指针。这个概念意味着只要指针指向COM对象,指针就拥有该对象。

使用这个术语,对象在任何时候都可能有多个所有者,当没有人拥有它时,该对象将被销毁。

但是,C ++中的原始指针没有内置的所有权语义。所以你必须通过调用函数来自己实现它:

  • 当该指针获取对象的所有权时,在接口指针上调用AddRef。 (您需要知道哪些Windows API函数或其他库函数已经执行此操作,以避免您执行此操作两次)
  • 当指针即将停止拥有对象时,在接口指针上调用Release

智能指针的好处是,当接口指针停止拥有对象时,它们使您无法忘记调用Release。这包括以下情况:

  • 指针超出范围。
  • 使用赋值运算符使指针停止指向对象。

所以,看看你的示例代码。你有指针m_pUnknown。您希望此指针获取对象的所有权,因此代码应为:

m_pUnknown = pUnknown;
m_pUnknown->AddRef();

您还需要向类析构函数和类赋值运算符添加代码以调用m_pUnknown->Release()。我会非常强烈建议在最小的类中包装这些调用(也就是说,编写自己的智能指针并使TestClass将该智能指针作为成员变量)。当然,假设出于教学原因,您不希望使用现有的COM智能指针类。

调用pUnknown->Release();是正确的,因为pUnknown当前拥有该对象,并且指针即将停止拥有该对象,因为它将在功能块结束时被销毁。

您可能会发现可以删除m_pUnknown->AddRef()pUnknown->Release()两行。代码的行为完全相同。但是,最好遵循上述惯例。坚持约定有助于避免错误,也有助于其他程序员理解您的代码。

换句话说,通常的惯例是将指针视为具有01的引用计数,即使引用计数实际上没有以这种方式实现。 / p>

答案 2 :(得分:0)

首先,我道歉。我为了清晰起见而简化代码的尝试被证明是错误的。但是,我相信我的问题得到了解答。如果可以的话,我会总结一下。

1)除了NULL被隐式处理之外,任何分配了AddRef()以外的值的COM对象都需要紧跟AddRef()(就像某些Windows API一样)呼叫)。

2)假设“之前”值不是NULL,必须立即由Release()继续向COM指针重新分配值。然后,如#1中所述,需要AddRef()

3)任何需要在其当前范围之外保留其值的COM变量要求在退出其所述范围时其引用计数至少为1。这可能意味着需要AddRef()

这是一个公平的总结吗?我错过了什么吗?