Поделиться через


Управление временем существования объекта

Существует правило для COM-интерфейсов, которое мы еще не упоминали. Каждый COM-интерфейс должен прямо или косвенно наследовать от интерфейса IUnknown. Этот интерфейс предоставляет некоторые базовые возможности, которые должны поддерживать все COM-объекты.

Интерфейс IUnknown определяет три метода:

Метод QueryInterface позволяет программе запрашивать возможности объекта во время выполнения. Подробнее об этом мы поговорим в следующем разделе , Запрос объекта для интерфейса. Методы AddRef и Release используются для управления временем существования объекта. Это тема этой статьи.

Подсчет ссылок

Что бы ни делала программа, в какой-то момент она будет выделять и освобождать ресурсы. Выделить ресурс очень просто. Знать, когда следует освободить ресурс, трудно, особенно если время существования ресурса выходит за пределы текущего область. Эта проблема не является уникальной для COM. Любая программа, выделяющая память кучи, должна решать ту же проблему. Например, В C++ используются автоматические деструкторы, а В# и Java — сборка мусора. В COM используется подход, называемый подсчетом ссылок.

Каждый COM-объект поддерживает внутреннее число. Это называется числом ссылок. Счетчик ссылок отслеживает количество активных ссылок на объект. Когда количество ссылок падает до нуля, объект удаляется сам. Следует повторить последнюю часть: объект удаляется сам. Программа никогда не удаляет объект явным образом.

Ниже приведены правила подсчета ссылок.

  • При первом создании объекта количество его ссылок равно 1. На этом этапе программа имеет один указатель на объект .
  • Программа может создать новую ссылку путем дублирования (копирования) указателя. При копировании указателя необходимо вызвать метод AddRef объекта . Этот метод увеличивает количество ссылок на единицу.
  • Завершив использование указателя на объект , необходимо вызвать Release. Метод Release уменьшает количество ссылок на один. Он также делает недействительным указатель. Не используйте указатель снова после вызова Release. (Если у вас есть другие указатели на тот же объект, вы можете продолжать использовать эти указатели.)
  • При вызове 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();
}

Подсчет ссылок происходит в двух местах в этом коде. Во-первых, если программа успешно создает объект диалогового окна общих элементов, она должна вызвать Release для указателя pFileOpen .

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

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

Во-вторых, когда метод GetResult возвращает указатель на интерфейс IShellItem , программа должна вызвать Release для указателя pItem .

hr = pFileOpen->GetResult(&pItem);

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

Обратите внимание, что в обоих случаях вызов Release — это последнее, что происходит до того, как указатель выйдет из область. Также обратите внимание, что release вызывается только после проверки HRESULT на успешность. Например, если вызов CoCreateInstance завершается ошибкой , недопустимый указатель pFileOpen . Поэтому вызов release для указателя будет ошибкой.

Следующая

Запрос объекта для интерфейса