【问题标题】:COM Reference Counting QuestionsCOM 引用计数问题
【发布时间】:2016-09-10 08:03:39
【问题描述】:

我正在编写利用 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的值时(原来是在类构造函数中设置的),@ 987654326@ 被自动调用。在将新值分配给pUnknown 后,它的引用计数为零。我需要在重新分配后立即致电pUnknown->AddRef()。我的理解正确吗?

【问题讨论】:

  • 当使用原始 com 指针时,永远不会自动调用 addref 和 release。
  • *m_pUnknown = *pUnknown 没有意义。您只是在对没有成员的 IUnknown 实例进行切片,因此充其量是无操作。此外,m_pUnknownNULL(至少最初是这样)并且不能被取消引用(当你尝试时,你可能会崩溃)。你想要if (m_pUnknown) { m_pUnknown->Release(); } m_pUnknown = pUnknown;
  • AddRef 可能隐含地发生在“在这里为 pUnknown 赋值 - 与我的问题无关”部分(这恰好与您的问题相关,与您的保证相反)。大多数获取 COM 指针的方法(例如CoCreateInstance)都会产生一个已经适合AddRefed 的方法。
  • "覆盖pUnknown的值(原来在类构造函数中设置)," pUnknownAssignValue中的一个局部变量。它当然没有在类构造函数中设置,甚至也不存在。 Release() 会自动调用。” 不,不是。智能指针可以做到这一点 - 但你故意不使用它们。

标签: c++ com


【解决方案1】:

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

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

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

2a) 原始指针是原始指针。在您安排将它​​们设置为有效值之前,它们的价值是垃圾。特别是,给 one 赋值不会自动神奇地调用函数。分配给接口的原始指针不会调用 Release() - 您需要在适当的时候执行此操作。在您的帖子中,您似乎正在“覆盖”以前设置为 NULL 的原始指针,因此图片中没有现有的 COM 接口实例。不存在的东西不可能有 AddRef(),不存在的东西也不能有 Release()。

2b)

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

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

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

第一个,最直接和正确的方法(其他人已经正确指出并且我在这个答案的第一个版本中跳过了)是每个指针一个 AddRef() 和一个 Release()。您的代码缺少 m_pUnknown 的这个。这需要在分配给 m_pUnknown 之后立即添加 m_pUnknown->AddRef(),并在使用来自 m_pUnknown 的当前接口指针完成后对 Release()“其他地方”进行 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-> Release() “其他地方”,就像第一个替代方案一样。

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

【讨论】:

    【解决方案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 的引用计数,即使引用计数实际上并未以这种方式实现。

    【讨论】:

    • 谢谢。我承认智能指针确实有它们的好处。我这样做并提出这些问题的原因是为了让我能够理解基本概念和程序。你们所有人都提供了很好的意见并提供了很多帮助。
    【解决方案3】:

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

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

    2) 任何对 COM 指针的值重新分配,假设“之前”值不是NULL,必须立即由Release() 进行。然后AddRef() 将需要,如 #1 中所述。

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

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

    【讨论】:

    • COM 对象不能分配NULL。我认为您在谈论接口指针;和过于复杂的事情
    猜你喜欢
    • 2014-05-28
    • 2011-04-20
    • 1970-01-01
    • 1970-01-01
    • 2011-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多