管理物件的存留期
我們尚未提及的 COM 介面有一個規則。 每個 COM 介面都必須直接或間接繼承自名為 IUnknown 的介面。 此介面提供所有 COM 物件必須支援的一些基準功能。
IUnknown介面會定義三種方法:
QueryInterface方法可讓程式在執行時間查詢物件的功能。 我們將在下一個主題中進一步討論 ,詢問物件以取得介面。 AddRef和Release方法可用來控制物件的存留期。 這是本主題的主題。
參考計數
程式可能執行的任何其他動作,在某個時間點,它將會配置和釋放資源。 配置資源很簡單。 瞭解何時釋放資源是硬式的,特別是當資源的存留期超出目前範圍時。 此問題對 COM 而言並不是唯一的。 配置堆積記憶體的任何程式都必須解決相同的問題。 例如,C++ 會使用自動解構函式,而 C# 和 JAVA 則使用垃圾收集。 COM 使用稱為 參考計數的方法。
每個 COM 物件都會維護內部計數。 這稱為參考計數。 參考計數會追蹤目前作用中物件的參考數目。 當參考數目捨棄為零時,物件會刪除本身。 最後一個部分值得重複:物件會自行刪除。 程式永遠不會明確刪除 物件。
以下是參考計數的規則:
- 第一次建立物件時,其參考計數為 1。 此時,程式具有 物件的單一指標。
- 程式可以複製 (複製指標) ,以建立新的參考。 複製指標時,您必須呼叫 物件的 AddRef 方法。 這個方法會將參考計數遞增一個。
- 當您完成使用 物件的指標時,必須呼叫 Release。 Release方法會將參考計數遞減一。 它也會使指標失效。 呼叫 Release之後,請勿再次使用指標。 (如果您有相同物件的其他指標,您可以繼續使用這些 pointers.)
- 當您以每個指標呼叫 Release 時,物件的物件參考計數會達到零,而且物件會刪除本身。
下圖顯示簡單但典型的案例。
程式會建立 物件,並將指標儲存 (p) 物件。 此時,參考計數為 1。 當程式使用指標完成時,它會呼叫 Release。 參考計數會遞減為零,而且物件會自行刪除。 現在 p 無效。 針對任何進一步的方法呼叫使用 p 是一個錯誤。
下圖顯示更複雜的範例。
在這裡,程式會建立 物件,並儲存指標 p,如同先前一樣。 接下來,程式會將 p 複製到新的變數 q。 此時,程式必須呼叫 AddRef 以遞增參考計數。 參考計數現在是 2,而且物件有兩個有效的指標。 現在假設程式已完成使用 p。 程式會呼叫 Release,參考計數會移至 1,而 p 已不再有效。 不過, q 仍然有效。 稍後,程式會使用 q完成。 因此,它會再次呼叫 Release 。 參考計數會移至零,而且物件會刪除本身。
您可能會想知道程式為何會複製 p。 有兩個主要原因:首先,您可能會想要將指標儲存在資料結構中,例如清單。 其次,您可能想要將指標保留在原始變數的目前範圍之外。 因此,您會將它複製到具有更廣泛範圍的新變數。
參考計數的其中一個優點是,您可以跨不同程式碼區段共用指標,而不需要協調各種程式碼路徑來刪除物件。 相反地,當程式碼路徑使用 物件完成時,每個程式碼路徑只會呼叫 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();
}
請注意,在這兩種情況下, Release 呼叫是在指標超出範圍之前發生的最後一件事。 另請注意,只有在測試HRESULT以取得成功之後,才會呼叫Release。 例如,如果 呼叫 CoCreateInstance 失敗, pFileOpen 指標無效。 因此,呼叫指標上的 Release 會是錯誤。