管理对象的生存期
对于 COM 接口,有一个规则,我们尚未提及。 每个 COM 接口都必须直接或间接继承自名为 IUnknown 的接口。 此接口提供一些所有 COM 对象都必须支持的基线功能。
IUnknown 接口定义了三种方法:
QueryInterface 方法使程序能够在运行时查询对象的功能。 我们将在下一主题“ 请求对象获取接口”中对此进行详细介绍。 AddRef 和 Release 方法用于控制对象的生存期。 这是本主题的主题。
引用计数
无论程序执行任何其他操作,它都会在某个时候分配和释放资源。 分配资源非常简单。 知道何时释放资源很困难,尤其是在资源的生存期超出当前范围时。 此问题并非特定于 COM。 分配堆内存的任何程序都必须解决相同的问题。 例如,C++ 使用自动析构函数,而 C# 和 Java 使用垃圾回收。 COM 使用称为 引用计数的方法。
每个 COM 对象都维护一个内部计数。 这称为引用计数。 引用计数跟踪当前对对象的引用数。 当引用数下降到零时,对象将删除自身。 最后一部分值得重复:对象删除自身。 程序永远不会显式删除 对象。
下面是引用计数的规则:
- 首次创建对象时,其引用计数为 1。 此时,程序有一个指向 对象的指针。
- 程序可以通过复制 (复制) 指针来创建新的引用。 复制指针时,必须调用 对象的 AddRef 方法。 此方法将引用计数递增 1。
- 使用完指向 对象的指针后,必须调用 Release。 Release 方法将引用计数递减一。 它还会使指针失效。 调用 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"File Path", 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 将是一个错误。