Monday, June 16, 2008

Ручное управление временем жизни для объектов, реализующих интерфейсы

У Алексея Михайличенко, есть статья, в которой показано, что при работе с интерфейсами, которые реализуют объекты не ведущие учет ссылок, может возникнуть AV на казалось бы пустом месте. Исключение возникает в функции _IntfClear, которая вызывается автоматически для интерфейсных ссылок при выходе их за область видимости. Однако если объект до этого был уничтожен, интерфейсная ссылка перестает быть валидной и попытка вызова метода этого интерфейса собственно и приводит к AV. Вот эта статья:
http://www.delphikingdom.com/asp/viewitem.asp?catalogid=1312

В процессе обсуждения, как мне кажется, родилось решение, которое может быть использовано для своих классов, которые должны реализовать интерфейс, однако управление временем жизни объектов должно быть ручное. Вот класс, от которого в таком случае можно унаследоваться:

TMyInterfacedObject = class(TObject, IInterface)
protected
FRefCount: Integer;
FDestroyed: Boolean;
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
public
procedure FreeInstance; override;
end;

procedure TMyInterfacedObject.FreeInstance;
begin
FDestroyed := True;
if RefCount = 0 then
inherited FreeInstance;
end;

function TMyInterfacedObject.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
if GetInterface(IID, Obj) then
Result := 0
else
Result := E_NOINTERFACE;
end;

function TMyInterfacedObject._AddRef: Integer;
begin
Result := InterlockedIncrement(FRefCount);
end;

function TMyInterfacedObject._Release: Integer;
begin
Result := InterlockedDecrement(FRefCount);
if (Result = 0) and FDestroyed then
FreeInstance;
end;


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