Как освободить объект интерфейса (Delphi 7)
В какой-то части моего приложения у меня есть ситуация, когда я получаю интерфейс, который, как я знаю, является объектом, хотя я не знаю точного класса. Я должен сохранить этот объект в переменной типа интерфейса.
В конце концов, я мог бы получить еще один экземпляр этого типа, и первый должен быть отброшен и заменен новым. Для этого мне нужно освободить память, которую использует связанный объект (мой интерфейс предоставляет метод AsObject, поэтому я могу использовать методы TObject на нем). Моя проблема заключается в том, что когда я хочу снова присвоить эту переменную "nil", я получаю нарушение доступа.
Я написал небольшую программу, которая воспроизводит мою ситуацию. Я размещаю его здесь, чтобы прояснить ситуацию.
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils, Classes;
type
ISomeInterface = interface
function SomeFunction : String;
function AsObject : TObject;
end;
TSomeClass = class(TComponent, ISomeInterface)
public
called : Integer;
function SomeFunction : String;
function AsObject : TObject;
end;
var
SomeInterface : ISomeInterface;
i : Integer;
function TSomeClass.SomeFunction : String;
begin
Result := 'SomeFunction called!';
end;
function TSomeClass.AsObject : TObject;
begin
Result := Self;
end;
begin
try
SomeInterface := nil;
for i := 1 to 10 do
begin
if SomeInterface <> nil then
begin
SomeInterface.AsObject.Free;
SomeInterface := nil; // <-- Access Violation occurs here
end;
SomeInterface := TSomeClass.Create(nil);
SomeInterface.SomeFunction; // <-- if commented, Access
// Violation does not occur
end;
except on e : Exception do
WriteLn(e.Message);
end;
end.
Итак, вопрос в том, как правильно я освободить этот объект?
Ответы
Ответ 1
Предполагая, что у вас есть законная причина для этого (и, используя TComponent, вполне возможно, что вы это делаете - см. конец ответа для чего), тогда проблема возникает в результате изменения ссылки на переменную интерфейса после вас уничтожили объект, который он в настоящее время ссылается.
Любое изменение ссылки на интерфейс генерирует такой код:
intfA := intfB;
становится (простыми словами):
if Assigned(intfA) then
intfA.Release;
intfA := intfB;
if Assigned(intfA) then
intfA.AddRef;
Если вы связываете это с вашим кодом, вы должны увидеть проблему:
SomeInterface.AsObject.Free;
SomeInterface := nil;
становится:
SomeInterface.AsObject.Free;
if Assigned(SomeInterface) then
SomeInterface.Release;
SomeInterface := nil;
if Assigned(SomeInterface) then
SomeInterface.AddRef;
Итак, вы можете видеть, что это сгенерированный вызов Release(), который возникает при назначении NIL интерфейсу, который вызывает нарушение вашего доступа.
Вы также должны быстро увидеть, что есть простой способ избежать этого, просто отложите освобождение объекта до тех пор, пока у вас не будет ссылки на NIL:
obj := SomeInterface.AsObject;
SomeInterface := NIL;
obj.Free;
НО
Ключевым вопросом здесь является то, почему вы явно освобождаете объект, который сопряжен (и, предположительно, подсчитывается ссылкой).
Когда вы меняете код для кэширования ссылки на объект и NIL для интерфейса перед явным освобождением объекта, вы можете обнаружить, что obj.Free затем приведет к нарушению доступа, так как сам NIL-интерфейс ссылки интерфейса может в свободном объекте.
Единственный способ убедиться, что явное освобождение сопряженного объекта безопасно:
1) Что сопряженный объект переопределил/переопределил IUnknown и исключил ориентированное на результат управление жизненным циклом
и
2) У вас нет других ссылок на этот объект в другом месте вашего кода.
Если первое из этих условий не имеет для вас никакого смысла, то, не желая покровительствовать, это, вероятно, хороший признак того, что вы не должны явно освобождать объект, поскольку он почти наверняка управляется ссылкой кол.
Сказав это, поскольку вы используете сопряженный класс TComponent, то до тех пор, пока ваш класс TComponent не инкапсулирует COM-объект, тогда TComponent удовлетворяет условию №1, поэтому все, что остается, это то, что вы обеспечиваете остальную часть кода соответствует условию №2.
Ответ 2
Вы не должны использовать TComponent в качестве базового класса для ваших сопряженных объектов, вместо этого вы должны использовать TInterfacedObject. TInerfacedObject реализовал необходимые функции для управления пожизненным управлением для интерфейсов в Delphi. Вы также никогда не должны смешивать доступ к интерфейсу в качестве интерфейса и объекта. Вот модификация вашего кода, которая работает отлично, без утечек памяти.
program Project2;
{$APPTYPE CONSOLE}
uses
SysUtils, Classes;
type
ISomeInterface = interface
function SomeFunction: string;
end;
TSomeClass = class(TInterfacedObject, ISomeInterface)
public
called: Integer;
function SomeFunction: string;
end;
var
SomeInterface: ISomeInterface;
i: Integer;
function TSomeClass.SomeFunction: string;
begin
Result := 'SomeFunction called!';
end;
begin
try
SomeInterface := nil;
for i := 1 to 10 do
begin
if SomeInterface <> nil then
begin
SomeInterface := nil;
end;
SomeInterface := TSomeClass.Create;
SomeInterface.SomeFunction;
end;
except
on e: Exception do
WriteLn(e.message);
end;
end.
Ответ 3
Если у вас есть переменная интерфейса, такая как ваш ISomeInterface var, вам не нужно ее освобождать, так как она подсчитывается и освобождает ее, когда она выходит из области.
Прочтите Роба Кеннеди на этот вопрос:
Delphi7, передающий интерфейс объекта - вызывает недопустимую операцию указателя при освобождении объекта
От http://delphi.about.com/od/beginners/l/aa113004a.htm
Как только интерфейс выходит из области, Delphi фактически освободит интерфейс для вас автоматически! Интерфейс, объявленный в процедура или функция будут выпадают из сферы действия, когда процедура заканчивается. Интерфейс, объявленный в класса или объявлены глобально естественно, выпадают из сферы действия, когда объект освобождается или программа заканчивается.
Если вы сомневаетесь, попробуйте использовать диспетчер памяти FastMM и настройте обнаружение утечки памяти, чтобы узнать, просочился ли объект.
Ответ 4
Вы смешиваете его. Все зависит от методов _AddRef
и _Release
. Проверьте, как объявлен TInterfacedObject
в system.pas
.
Delphi просто вызывает методы _AddRef и _Release во время использования Interafaces, вызов Free зависит от того, как объект реализует метод _Relase.
TComponent
не будет автоматически уничтожен (кроме компонентов объекта Com).
var
o: TSomeClass;
begin
..
..
begin
o := SomeInterface.AsObject as TSomeClass;
SomeInterface := nil; // now we decrease reference counter, but it will not free anything unless you rewrite _AddRef and _Release methods
o.Free; // just free object by your own
end;