Как мы видели в прошлый раз, попытка делать слишком много в деструкторе COM-объекта может привести к двойному вызову его деструктора. Стандартным способом избежать этого является ведение искусственного счётчика во время работы деструктора.
const DestructorFakeRefCount = Integer($80000000); type TMyObject = class(TObject, IUnknown) private function GetRefCount: Integer; protected FRefCount: Integer; function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall; function _AddRef: Integer; stdcall; function _Release: Integer; stdcall; public procedure AfterConstruction; override; procedure BeforeDestruction; override; class function NewInstance: TObject; override; destructor Destroy; override; property RefCount: Integer read GetRefCount; end; destructor TMyObject.Destroy; begin Save(Self); Assert(FRefCount = DestructorFakeRefCount); inherited; end; procedure TMyObject.AfterConstruction; begin // Release the constructor's implicit refcount InterlockedDecrement(FRefCount); end; procedure TMyObject.BeforeDestruction; begin if RefCount <> 0 then System.Error(reInvalidPtr); FRefCount := DestructorFakeRefCount; end; function TMyObject.GetRefCount: Integer; begin if FRefCount < 0 then Result := 0 else Result := FRefCount; end; class function TMyObject.NewInstance: TObject; begin Result := inherited NewInstance; TMyObject(Result).FRefCount := 1; end; function TMyObject.QueryInterface(const IID: TGUID; out Obj): HResult; begin if GetInterface(IID, Obj) then Result := 0 else Result := E_NOINTERFACE; end; function TMyObject._AddRef: Integer; begin Result := InterlockedIncrement(FRefCount); end; function TMyObject._Release: Integer; begin Result := InterlockedDecrement(FRefCount); if Result = 0 then begin FRefCount := DestructorFakeRefCount; Destroy; end; end;Если у вас есть общая реализация
IUnknown
, то вы можете устанавливать DestructorFakeRefCount
в базовой реализации IUnknown._Release
, как мы сделали здесь, и проверять его Assert
-ом в её же деструкторе. Поскольку унаследованный деструктор будет работать после деструктора любых наследников, то ваш базовый класс сможет проверить счётчик ссылок после того как наследник закончит очистку.Установка счётчика ссылок в искусственное ненулевое значение приводит к тому, что вызовы
_AddRef
и Release
, которые могут происходить во время уничтожения объекта, не приведут к повторному удалению объекта. Утверждение в конце деструктора гарантирует нам что в процессе очистки не было создано новых ссылок на объект, который находится в процессе удаления.Этот способ является лишь обходным решением, поскольку он предполагает, что во время работы деструктора ни одна вызываемая функция не сохраняет интерфейс объекта дольше своего вызова. В общем случае это не так - любой метод и функция вольны вызвать
_AddRef
и держать ссылку на интерфейс сколько они захотят (к примеру, чтобы выполнить запрошенную операцию позже).Упражнение: почему безопасно выполнять простое присваивание
FRefCount := DestructorFakeRefCount
вместо более сложного InterlockedExchangeAdd(FRefCount, DestructorFakeRefCount)
?
Комментариев нет:
Отправить комментарий
Можно использовать некоторые HTML-теги, например:
<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>
Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку.
Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.
Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.
Примечание. Отправлять комментарии могут только участники этого блога.