管理对象的生存期

对于 COM 接口,有一个规则,我们尚未提及。 每个 COM 接口都必须直接或间接继承自名为 IUnknown 的接口。 此接口提供一些所有 COM 对象都必须支持的基线功能。

IUnknown 接口定义了三种方法:

QueryInterface 方法使程序能够在运行时查询对象的功能。 我们将在下一主题“ 请求对象获取接口”中对此进行详细介绍。 AddRefRelease 方法用于控制对象的生存期。 这是本主题的主题。

引用计数

无论程序执行任何其他操作,它都会在某个时候分配和释放资源。 分配资源非常简单。 知道何时释放资源很困难,尤其是在资源的生存期超出当前范围时。 此问题并非特定于 COM。 分配堆内存的任何程序都必须解决相同的问题。 例如,C++ 使用自动析构函数,而 C# 和 Java 使用垃圾回收。 COM 使用称为 引用计数的方法。

每个 COM 对象都维护一个内部计数。 这称为引用计数。 引用计数跟踪当前对对象的引用数。 当引用数下降到零时,对象将删除自身。 最后一部分值得重复:对象删除自身。 程序永远不会显式删除 对象。

下面是引用计数的规则:

  • 首次创建对象时,其引用计数为 1。 此时,程序有一个指向 对象的指针。
  • 程序可以通过复制 (复制) 指针来创建新的引用。 复制指针时,必须调用 对象的 AddRef 方法。 此方法将引用计数递增 1。
  • 使用完指向 对象的指针后,必须调用 ReleaseRelease 方法将引用计数递减一。 它还会使指针失效。 调用 Release 后,不要再次使用该指针。 (如果你有指向同一对象的其他指针,则可以继续使用这些 pointers.)
  • 使用每个指针调用 Release 时,对象的对象引用计数将达到零,并且对象会删除自身。

下图显示了一个简单但典型的案例。

显示引用计数的简单事例的关系图。

程序创建一个 对象,并将指向 对象的指针 (p) 存储。 此时,引用计数为 1。 当程序使用指针完成时,它将调用 Release。 引用计数递减为零,对象将自行删除。 现在 p 无效。 将 p 用于任何进一步的方法调用是错误的。

下图显示了一个更复杂的示例。

显示引用计数的插图

在这里,程序创建一个 对象并存储指针 p,就像以前一样。 接下来,程序将 p 复制到新的变量 q。 此时,程序必须调用 AddRef 来递增引用计数。 引用计数现在为 2,并且有两个指向 对象的有效指针。 现在假设程序使用 p 完成。 程序调用 Release,引用计数为 1, p 不再有效。 但是, q 仍然有效。 稍后,程序使用 q 完成。 因此,它再次调用 Release 。 引用计数将变为零,对象将删除自身。

你可能想知道为什么程序会复制 p。 main有两个原因:第一,你可能想要将指针存储在数据结构(如列表)中。 其次,你可能希望将指针保持在原始变量的当前范围之外。 因此,应将其复制到范围更广的新变量。

引用计数的一个优点是,可以在代码的不同部分之间共享指针,而无需各种代码路径协调删除对象。 相反,当代码路径使用 对象完成时,每个代码路径仅调用 Release 。 对象在正确的时间处理删除自身。

示例

下面是“ 打开”对话框示例中 的代码。

HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED |
    COINIT_DISABLE_OLE1DDE);

if (SUCCEEDED(hr))
{
    IFileOpenDialog *pFileOpen;

    hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL,
            IID_IFileOpenDialog, reinterpret_cast<void**>(&pFileOpen));

    if (SUCCEEDED(hr))
    {
        hr = pFileOpen->Show(NULL);
        if (SUCCEEDED(hr))
        {
            IShellItem *pItem;
            hr = pFileOpen->GetResult(&pItem);
            if (SUCCEEDED(hr))
            {
                PWSTR pszFilePath;
                hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);
                if (SUCCEEDED(hr))
                {
                    MessageBox(NULL, pszFilePath, L&quot;File Path&quot;, MB_OK);
                    CoTaskMemFree(pszFilePath);
                }
                pItem->Release();
            }
        }
        pFileOpen->Release();
    }
    CoUninitialize();
}

引用计数在此代码中的两个位置发生。 首先,如果程序成功创建 Common Item Dialog 对象,则必须在 pFileOpen 指针上调用 Release

hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL, 
        IID_IFileOpenDialog, reinterpret_cast<void**>(&pFileOpen));

if (SUCCEEDED(hr))
{
    // ...
    pFileOpen->Release();
}

其次,当 GetResult 方法返回指向 IShellItem 接口的指针时,程序必须在 pItem 指针上调用 Release

hr = pFileOpen->GetResult(&pItem);

if (SUCCEEDED(hr))
{
    // ...
    pItem->Release();
}

请注意,在这两种情况下, 发布 调用都是在指针超出范围之前发生的最后一件事。 另请注意,仅在测试 HRESULT 是否成功后,才会调用 Release。 例如,如果对 CoCreateInstance 的调用失败, 则 pFileOpen 指针无效。 因此,在指针上调用 Release 将是一个错误。

下一步

请求对象以获取接口