Как решить интерфейсный бардак - PullRequest
2 голосов
/ 28 октября 2011

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

Например: предположим, что у меня есть два разных класса: TMyObject и TMyDifferentObject. Они оба поддерживают этот интерфейс:

const
  IID_MyInterface: TGUID = '{4D91C27F-510D-4673-8773-5D0569DFD168}';

type
 IMyInterface = Interface(IInterface)
  ['{4D91C27F-510D-4673-8773-5D0569DFD168}']
  function GetID : Integer;
 end;

type
  TMyObject = class(TInterfacedObject, IMyInterface)
    function GetID: Integer;
  end;

function TMyObject.GetID: Integer;
begin
  Result := 1;
end;


type
  TMyDifferentObject = class(TInterfacedObject, IMyInterface)
    function GetID: Integer;
  end;

function TMyDifferentObject.GetID: Integer;
begin
  Result := 2;
end;

Теперь я хотел бы создать экземпляры этих классов в моей программе, а затем передать эти экземпляры этому методу:

procedure ShowObjectID(AObject: TObject);
var
  MyInterface: IMyInterface;
begin
  if Supports(AObject, IID_MyInterface, MyInterface) then
  begin
    ShowMessage(IntToStr(MyInterface.GetID));
  end;
end;  //Interface goes out of scope and AObject is freed but I still want to work with that object!

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

Привет.

Ответы [ 3 ]

8 голосов
/ 28 октября 2011

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

var
  MyObject: TObject;
begin
  MyObject := TMyObject.Create;
  ShowMessage('Before ShowObjectID MyObject RefCount: ' + IntToStr(MyObject.RefCount));
  ShowObjectID(MyObject);
  ShowMessage('After ShowObjectID MyObject RefCount: ' + IntToStr(MyObject.RefCount));
end;

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

var
  MyObject: TMyObject;
  MyIntf: IMyInterface;
begin
  MyObject := TMyObject.Create;
  MyIntf := MyObject;
  ShowMessage('Before ShowObjectID MyObject RefCount: ' + IntToStr(MyObject.RefCount));
  ShowObjectID(MyObject);
  ShowMessage('After ShowObjectID MyObject RefCount: ' + IntToStr(MyObject.RefCount));
  MyIntf := nil;
  ShowMessage('After nilling the interface MyObject RefCount: ' + IntToStr(MyObject.RefCount));
end;

или отключите повторный подсчет, как предложил Дэвид в комментариях.По сути, это означает объявление вашего собственного "TInterfacedObject" и реализацию трех методов IInterface:

function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;

Суть в том, чтобы вернуть -1 для _AddRef и _Release.Как сказал Дэвид: посмотрите, как это делает TComponent.И просто возьмите то, что он делает, когда FVCLComObject равен нулю.

3 голосов
/ 28 октября 2011

Еще одна опция, о которой никто не упомянул, - это явный вызов _AddRef для экземпляра объекта, чтобы он оставался в живых до тех пор, пока он вам нужен, а затем вызов * _Release.

3 голосов
/ 28 октября 2011

Один из подходов к решению вашей проблемы - изменить код так, чтобы вы когда-либо обращались к объекту только через ссылку на интерфейс. Другими словами вместо

var
  obj: TMyObject;
...
obj := TMyObject.Create;
try
  obj.DoStuff;
  //etc. etc.
finally
  obj.Free;
end;

Вы пишете

var
  obj: IMyObject;//NOTE: interface variable
...
obj := TMyObject.Create;
obj.DoStuff;
//etc. etc.
obj := nil;//or let it go out of scope and release that way

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

type
  TInterfacedObjectWithoutLifetimeManagement = class(TObject, IInterface)
  private
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
  end;

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

function TInterfacedObjectWithoutLifetimeManagement._AddRef: Integer;
begin
  Result := -1;
end;

function TInterfacedObjectWithoutLifetimeManagement._Release: Integer;
begin
  Result := -1;
end;

Затем вы можете получить свои классы из этого класса.

В этом подходе есть одно очень серьезное замечание. Предположим, что вы храните в переменных (локальных, глобальных, член класса) все интерфейсы, которые реализуются классом, производным от TInterfacedObjectWithoutLifetimeManagement. Все такие переменные интерфейса должны быть завершены за до вызова Free для реализующего объекта.

Если вы не будете следовать этому правилу, вы обнаружите, что когда эти переменные интерфейса выходят за пределы области видимости, компилятор по-прежнему генерирует код для вызова _Release, и вызывать метод объекта после его уничтожения - ошибка Это особенно неприятный тип ошибки, потому что он обычно не проявляется с ошибкой во время выполнения, пока ваш код не будет запущен на компьютере вашего самого важного клиента! Другими словами, такие ошибки могут иметь прерывистый характер.

...