Как освободить объект интерфейса (Delphi 7) - PullRequest
14 голосов
/ 27 января 2011

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

В конце концов, я мог бы получить другой экземпляр этого типа, и первый должен быть отброшен и заменен новым.Для этого мне нужно освободить память, которую использует интерфейсный объект (мой интерфейс предоставляет метод 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.

Итак, вопрос в том, как правильно освободить этот объект?

Ответы [ 4 ]

27 голосов
/ 28 января 2011

Предполагая, что у вас есть законная причина для этого (и, используя 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;

НО

Ключевой вопрос здесь заключается в том, почему вы явно освобождаете объект, который связан (и, вероятно,erence count).

Когда вы изменяете код для кэширования ссылки на объект и NIL интерфейса до явного освобождения объекта, вы можете обнаружить, что obj.Free будет вызывать нарушение доступа как NIL'ingссылка на интерфейс сама может привести к свободному объекту.

ЕДИНСТВЕННЫЙ способ убедиться, что явное освобождение интерфейсного объекта безопасно, это:

1) Что интерфейсобъект переопределил / переопределил IUnknown и устранил подсчет ссылок на управление временем жизни

И

2) что у вас нет других сопряженных ссылок на этот объект в другом месте вашего кода.

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

Сказав это, поскольку вы используете интерфейсный класс TComponent, то доваш класс TComponent не инкапсулирует COM-объект, тогда TComponent удовлетворяет условию № 1, поэтому остается только убедиться, что остальная часть кода соответствует условию № 2.

5 голосов
/ 28 января 2011

Вы не должны использовать 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 голосов
/ 28 января 2011

Вы смешиваете это. Все зависит от _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;
3 голосов
/ 27 января 2011

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

Прочитайте ответ Роба Кеннеди на этот вопрос: Delphi7, передавая интерфейс объекта - вызывает Invalid Pointer Operation при освобождении объекта

С http://delphi.about.com/od/beginners/l/aa113004a.htm

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

Если вы сомневаетесь, попробуйте использовать диспетчер памяти FastMM и настроить обнаружение утечки памяти, чтобы проверить, не протекает ли объект.

...