Если вы попробуете делать там слишком много - у вас могут быть неприятности.
К примеру, если ваш деструктор передаёт ссылки на себя другим функциям, то эти функции могут решить вызывать ваши
IUnknown._AddRef и IUnknown._Release во время своей работы. Посмотрите на этот код:
function TMyObject._Release: Integer;
begin
Result := InterlockedDecrement(FRefCount);
if Result = 0 then
Destroy;
end;
destructor TMyObject.Destroy;
begin
if FNeedSave then
Save;
inherited;
end;
Это не выглядит очень уж страшным, не так ли? Объект просто сохраняет себя перед разрушением.Но метод
Save мог бы выглядеть примерно так:
function TMyObject.Save: HRESULT;
var
spstm: IStream;
spows: IObjectWithSite;
begin
Result := GetSaveStream(spstm);
if SUCCEEDED(hr) then
begin
Supports(spstm, IObjectWithSite, spows);
if Assigned(spows) then
spows.SetSite(Self);
Result := SaveToStream(spstm);
if Assigned(spows) then
spows.SetSite(nil);
end;
end;
Сам по себе он выглядит нормально. Мы получаем поток (stream) и сохраняем себя в него, дополнительно устанавливая сведения о контексте (site) - на случай если потоку нужна будет дополнительная информация.Но этот простой код в сочетании с тем фактом, что он запущен из деструктора, даёт нам рецепт для катастрофы. Посмотрите что при этом происходит:
- Метод
_Releaseуменьшает счётчик ссылок до нуля и выполняет удалениеSelf. - Деструктор пытается сохранить объект.
- Метод
Saveполучает поток для сохранения и устанавливаетSelfв качестве контекста. Это увелививает счётчик ссылок с нуля до единицы. - Метод
SaveToStreamсохраняет объект в поток. - Метод
Saveочищает контекст потока. Это приводит к уменьшению счётчика ссылок нашего объекта с единицы до нуля. - Поэтому метод
_Releaseвызывает деструктор объекта второй раз.
Поэтому, как минимум, вы должны вставить
Assert в ваш метод AddRef , чтобы гарантировать, что вы не увеличиваете счётчик ссылок с нуля:
function TMyObject._AddRef: Integer; begin Assert(FRefCount > 0); Result := InterlockedIncrement(FRefCount); end;Это поможет вам легко отлавливать "случаи загадочного двойного вызова деструктора объекта". Но когда вы идентифицируете проблему, то что же вам с ней делать? Мы поговорим об этом в следующий раз.
Примечание переводчика:
Сказанное здесь в точности применимо и к объектам, реализующим интерфейсы в Delphi (просто потому, что интерфейсы COM - это единственные интерфейсы в Delphi). Однако в Delphi объект может существовать, даже когда его счётчик равен 0. Например:
var MyObj: TMyObject; begin MyObj := TMyObject.Create; FreeAndNil(MyObj); end;В этом примере счётчик ссылок будет равен 1 во время работы конструктора и опустится до 0 после выхода из Create, после чего счётчик будет равен 0 до конца жизни объекта.
Сравните это с:
var MyObj: IMyObject; begin MyObj := TMyObject.Create; MyObj := nil; end;В этом примере счётчик ссылок будет равен 1 во время работы конструктора и опустится до 0 после выхода из Create, после чего снова поднимется до 1 из-за копирования интерфейса в переменную
MyObj.С учётом этого момента код выше нужно немного изменить. Например, так:
function TMyObject._AddRef: Integer;
begin
Assert(FRefCount >= 0);
Result := InterlockedIncrement(FRefCount);
end;
function TMyObject._Release: Integer;
begin
Result := InterlockedDecrement(FRefCount);
if Result = 0 then
Destroy;
end;
procedure TMyObject.BeforeDestruction;
begin
if RefCount <> 0 then
System.Error(reInvalidPtr);
FRefCount := -1;
end;
См. также этот запрос в QC.
В вызове TMyObject._Release не нужно добавлять строку FRefCount := -1, она уже добавлена в BeforeDestruction, который будет вызван при вызове Destroy.
ОтветитьУдалитьСпасибо, исправил.
ОтветитьУдалить