Проблема с выпуском Интерфейсных Объектов, когда удерживается в Массиве - PullRequest
1 голос
/ 03 апреля 2012

Чтобы предоставить как можно больше информации, вот очень простой пример того, что я делаю

type
  IMyInterface = interface
  [THE_GUID_HERE]
    // some methods
  end;

  TMyInterfaceArray = Array of IMyInterface;

  TMyInterfacedObject = class(TInterfacedObject, IMyInterface)
    // implementation of the Interface etc. here
  end;

  TContainingObject = class
  private
    FIObjectArray: TMyInterfaceArray;
  public
    constructor Create;
    destructor Destroy; override;
    procedure NewInstanceOfInterfacedObject;
  end;

  implementation

  constructor TContainingObject.Create;
  begin
    inherited;
    // Just to illustrate that an Instance is being created...
    NewInstanceOfInterfacedObject;
  end;

  destructor TContainingObject.Destroy;
  var
    I: Integer;
  begin
    for I := Low(FIObjectArray) to High(FIObjectArray) do
      FIObjectArray[I] := nil;
    SetLength(FIObjectArray, 0); // Array collapsed

    inherited;
  end;

  procedure TContainingObject.NewInstanceOfInterfacedObject;
  var
    LIndex: Integer;
  begin
    LIndex := Length(FIObjectArray);
    SetLength(FIObjectArray, LIndex + 1);
    FIObjectArray[LIndex] := TMyInterfacedObject.Create;
  end;

Хорошо, значит, экземпляр TContainingObject создан и, в свою очередь, создает экземпляр TMyInterfacedObject, хранящийся в массиве IMyInterface.

Когда вызывается TContainingObject s destructor, это нулевая ссылка и сворачивает массив.

Проблема, с которой я столкнулся, заключается в том, что, без каких-либо других ссылок, TMyInterfacedObject destructor никогда не вызывается, и, таким образом, происходит утечка памяти.

Я что-то не так делаю, или система подсчета ссылок Delphi не в состоянии справиться с простой концепцией взаимодействия объектов, содержащихся в массиве типа интерфейса?

Спасибо за любой совет!

ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ

TContainingObject предоставляет свойство Array для доступа к отдельным экземплярам IMyInterface, содержащимся в массиве.

В моем реальном коде есть циклические ссылки между несколькими типами интерфейса. Предположим, что IMyInterface содержит функцию GetSomething: IAnotherInterface, а IAnotherInterface содержит GetMyInterface: IMyInterface (циклическая ссылка). Может ли это быть причиной моей проблемы? Если это так, то круговая ссылка абсолютно обязательна , так что будет с этим решением?

Ответы [ 2 ]

5 голосов
/ 03 апреля 2012

Если реализация для IMyInterface содержит элемент IAnotherInterface, а реализация для IAnotherInterface содержит элемент IMyInterface, и они ссылаются друг на друга, то их счетчики ссылок никогда не смогут упасть до 0 если вы не очистите одну из ссылок, что, вероятно, означает добавление методов к вашим интерфейсам, например:

type
  IAnotherInterface = interface;

  IMyInterface = interface
  ['{guid}']
    function GetAnotherInterface: IAnotherInterface;
    procedure SetAnotherInterface(Value: IAnotherInterface);
    property AnotherInterface: IAnotherInterface read GetAnotherInterface write SetAnotherInterface;
  end;

  IAnotherInterface = interface
  ['{guid}']
    function GetMyInterface: IMyInterface;
    procedure SetMyInterface(Value: IMyInterface);
    property MyInterface: IMyInterface read GetMyInterface write SetMyInterface;
  end;

.

type
  TMyInterface = class(TInterfacedObject, IMyInterface)
  private
    FAnotherInterface: IAnotherInterface;
  public
    function GetAnotherInterface: IAnotherInterface;
    procedure SetAnotherInterface(Value: IAnotherInterface);
  end;

  TAnotherInterface = class(TInterfacedObject, IAnotherInterface)
  private
    FMyInterface: IMyInterface;
  public
    function GetMyInterface: IMyInterface;
    procedure SetMyInterface(Value: IMyInterface);
  end;

  function TMyInterface.GetAnotherInterface;
  begin
    Result := FAnotherInterface;
  end;

  procedure TMyInterface.SetAnotherInterface(Value: IAnotherInterface);
  begin
    if FAnotherInterface <> Value then
    begin
      if FAnotherInterface <> nil then FAnotherInterface.SetMyInterface(nil);
      FAnotherInterface := Value;
      if FAnotherInterface <> nil then FAnotherInterface.SetMyInterface(Self);
    end;
  end;

  function TAnotherInterface.GetMyInterface: IMyInterface;
  begin
    Result := FMyInterface;
  end;

  procedure TAnotherInterface.SetMyInterface(Value: IMyInterface);
  begin
    if FMyInterface <> Value then
    begin
      if FMyInterface <> nil then FMyInterface.SetAnotherInterface(nil);
      FMyInterface := Value;
      if FMyInterface <> nil then FMyInterface.SetAnotherInterface(Self);
    end;
  end;

Теперь смотрите количество ссылок, если вы явно не освобождаете одну из ссылок:

var
  I: IMyInterface;
  J: IAnotherInterface;
begin
  I := TMyInterface.Create; // I.RefCnt becomes 1
  J := TAnotherInterface.Create; // J.RefCnt becomes 1
  I.AnotherInterface := J; // I.RefCnt becomes 2, J.RefCnt becomes 2
  ...
  {
  // implicit when scope is cleared:
  I := nil; // I.RefCnt becomes 1, I is NOT freed
  J := nil; // J.RefCnt becomes 1, J is NOT freed
  }
end;

Теперь добавьте явный выпуск к одной из ссылок:

var
  I: IMyInterface;
  J: IAnotherInterface;
begin
  I := TMyInterface.Create; // I.RefCnt becomes 1
  J := TAnotherInterface.Create; // J.RefCnt becomes 1
  I.AnotherInterface := J; // I.RefCnt becomes 2, J.RefCnt becomes 2
  ...
  I.AnotherInterface := nil; // I.RefCnt becomes 1, J.RefCnt becomes 1
  {
  // implicit when scope is cleared:
  I := nil; // I.RefCnt becomes 0, I is freed
  J := nil; // J.RefCnt becomes 0, J is freed
  }
end;

.

var
  I: IMyInterface;
  J: IAnotherInterface;
begin
  I := TMyInterface.Create; // I.RefCnt becomes 1
  J := TAnotherInterface.Create; // J.RefCnt becomes 1
  I.AnotherInterface := J; // I.RefCnt becomes 2, J.RefCnt becomes 2
  J := nil; // I.RefCnt still 2, J.RefCnt becomes 1, J is NOT freed yet
  ...
  I.AnotherInterface := nil; // I.RefCnt becomes 1, J.RefCnt becomes 0, J is freed
  {
  // implicit when scope is cleared:
  I := nil; // I.RefCnt becomes 0, I is freed
  }
end;
0 голосов
/ 03 апреля 2012

Когда я запускаю ваш пример кода в Delphi XE2 (после исправления небольшой опечатки - см. Редактирование), он работает нормально и вызывает деструктор TMyInterfacedObject, как и ожидалось.

...