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.

5 comments:

GunSmoker said...

Ins, для оформления кода подходит тэг blockquote (в угловых скобках) или кнопка "цитата".
Просто сейчас твой код выглядит нечитабельно :(

Ins said...

Спасибо! Пошаманил с тегами, стало немного лучше :)

GunSmoker said...

Кстати, вот соответствующий баг-репорт в QC: http://qc.codegear.com/wc/qcmain.aspx?d=9157

Anonymous said...

Интересный пост:
http://blogs.teamb.com/craigstuntz/2008/01/23/37786

Ins said...

Пост действительно интересный, анализ проведен хороший. Ясно то, что проблема существует, но не ясно, как ее лучше решить. Однако лично мне наименьшим из всех зол кажется вариант "The compiler could insert calls to _AddRef and _Release for a reference of an interface type which is a subtype of IUnknown, and not insert these calls for a reference of an interface type which is not a subtype of IUnknown", даже не смотря на проблемы, которые он влечет за собой. В конце концов, в этом случае ответственность скорее будет лежать на программисте, в данном же случае имеет место быть самый настоящий косяк.