Delphi: Как реализация интерфейса делегата для дочернего объекта?

У меня есть объект, который делегирует реализацию особо сложного интерфейса для дочернего объекта. Это точно, я думаю, это работа TAggregatedObject. Объект дочерний хранит слабую ссылку на свой " контроллер", и все запросы QueryInterface передаются родительскому элементу. Это подтверждает правило, что IUnknown  всегда является одним и тем же объектом.

Итак, мой родительский объект (т.е. "Контроллер" ) объявляет, что он реализует интерфейс IStream:

type
   TRobot = class(TInterfacedObject, IStream)
   private
      function GetStream: IStream;
   public
      property Stream: IStream read GetStrem implements IStream;
   end;

Примечание: Это гипотетический пример. я выбрал слово Robotпотому что это звучит сложно, и слово - всего 5 букв - это короткая. я также выбрал IStream, потому что его короткий. я собирался использовать IPersistFile или IPersistFileInit, но они длиннее и делают пример кода сложнее для реального. В других слова: Это гипотетический пример.

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

type
   TRobotStream = class(TAggregatedObject, IStream)
   public
      ...
   end;

Все, что осталось, и вот где начинается моя проблема: создав RobotStream, когда он попросил:

function TRobot.GetStream: IStream;
begin
    Result := TRobotStream.Create(Self) as IStream;
end;

Этот код не скомпилируется с ошибкой Operator not applicable to this operand type..

Это связано с тем, что delphi пытается выполнить as IStream объект, который не реализует IUnknown:

TAggregatedObject = class
 ...
   { IUnknown }
   function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
   function _AddRef: Integer; stdcall;
   function _Release: Integer; stdcall;
 ...

Возможно, существуют методы IUnknown, но объект не сообщает, что поддерживает IUnknown. Без интерфейса IUnknown Delphi не может вызывать QueryInterface для выполнения трансляции.

Итак, я меняю класс TRobotStream на рекламу того, что он реализует недостающий интерфейс (который он наследует от своего предка):

type
   TRobotStream = class(TAggregatedObject, IUnknown, IStream)
   ...

И теперь он компилируется, но сбой во время выполнения на линии:

Result := TRobotStream.Create(Self) as IStream;

Теперь я вижу , что, но я не могу объяснить почему. Delphi вызывает IntfClear, на родительском объекте Robot, на выходе из конструктора дочерних объектов.

Я не знаю, как правильно это предотвратить. я мог бы заставить форсировать приведение:

Result := TRobotStream.Create(Self as IUnknown) as IStream;

и надеемся, что он сохранит ссылку. Оказывается, что он сохраняет ссылку - без сбоев на выходе из конструктора.

Примечание: Это меня сбивает с толку. Поскольку я передаю объект, где ожидается интерфейс. я бы предположим, что компилятор неявно префикс типа, т.е.:

Result := TRobotStream.Create(Self как IUnknown );

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


Но аварии еще не закончились. Я изменил строку на:

Result := TRobotStream.Create(Self as IUnknown) as IStream;

И код действительно возвращается из конструктора TRobotStream без уничтожения моего родительского объекта, но теперь я получаю переполнение стека.

Причина в том, что TAggregatedObject возвращает все QueryInterface (например, cast) обратно к родительскому объекту. В моем случае я набрасываю TRobotStream на IStream.

Когда я задаю TRobotStream для своего IStream в конце:

Result := TRobotStream.Create(Self as IUnknown) as IStream;

Он поворачивается и запрашивает свой контроллер для интерфейса IStream, который вызывает вызов:

Result := TRobotStream.Create(Self as IUnknown) as IStream;
   Result := TRobotStream.Create(Self as IUnknown) as IStream;

который поворачивается и вызывает:

Result := TRobotStream.Create(Self as IUnknown) as IStream;
   Result := TRobotStream.Create(Self as IUnknown) as IStream;
      Result := TRobotStream.Create(Self as IUnknown) as IStream;

Бум! Переполнение стека.


Слепо, я пытаюсь удалить окончательный прилив на IStream, пусть Delphi попытается неявно применить объект к интерфейсу (который я только что видел выше, не работает правильно):

Result := TRobotStream.Create(Self as IUnknown);

И теперь нет краха; и я этого не понимаю. Я построил объект, объект, который поддерживает несколько интерфейсов. Как теперь Delphi знает, как использовать интерфейс? Выполняет ли он правильный подсчет ссылок? я видел выше, что это не так. Есть ли тонкая ошибка, ожидающая аварии для клиента?

Итак, я остался с четырьмя возможными способами вызова моей одной строки. Какой из них действителен?

  • Result := TRobotStream.Create(Self);
  • Result := TRobotStream.Create(Self as IUnknown);
  • Result := TRobotStream.Create(Self) as IStream;
  • Result := TRobotStream.Create(Self as IUnknown) as IStream;

Реальный вопрос

я ударил довольно много тонких ошибок, и трудно понять сложности компилятора. Это заставляет меня поверить, что я сделал все совершенно неправильно. При необходимости проигнорируйте все, что я сказал, и помогите мне ответить на вопрос:

Каков правильный способ делегирования реализации интерфейса дочернему объекту?

Может быть, я должен использовать TContainedObject вместо TAggregatedObject. Возможно, они работают в тандеме, где родитель должен быть TAggregatedObject, а ребенок - TContainedObject. Может быть, наоборот. Возможно, в этом случае они не применяются.

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

Реальная цель - делегировать интерфейс реализация для дочернего объекта. Эта вопрос содержит мои подробные попытки при решении задачи с TAggregatedObject. Вы даже не см. мои другие два шаблона решения. Один из которых страдает от кругового refernce counts, и разрывы IUnknown правило эквивалентности.

Роб Кеннеди мог бы помнить; и спросил мне задать вопрос, который требует решение проблемы, а не решение проблемы в одном из моих решения.

Изменить: grammerized

Изменить 2: Нет такой вещи, как контроллер робота. Ну, есть - я постоянно работал с контроллерами Funuc RJ2. Но не в этом примере!

Изменить 3 *

  TRobotStream = class(TAggregatedObject, IStream)
    public
        { IStream }
     function Seek(dlibMove: Largeint; dwOrigin: Longint;
        out libNewPosition: Largeint): HResult; stdcall;
     function SetSize(libNewSize: Largeint): HResult; stdcall;
     function CopyTo(stm: IStream; cb: Largeint; out cbRead: Largeint; out cbWritten: Largeint): HResult; stdcall;
     function Commit(grfCommitFlags: Longint): HResult; stdcall;
     function Revert: HResult; stdcall;
     function LockRegion(libOffset: Largeint; cb: Largeint; dwLockType: Longint): HResult; stdcall;
     function UnlockRegion(libOffset: Largeint; cb: Largeint; dwLockType: Longint): HResult; stdcall;
     function Stat(out statstg: TStatStg; grfStatFlag: Longint): HResult; stdcall;
     function Clone(out stm: IStream): HResult; stdcall;

     function Read(pv: Pointer; cb: Longint; pcbRead: PLongint): HResult; stdcall;
     function Write(pv: Pointer; cb: Longint; pcbWritten: PLongint): HResult; stdcall;
  end;

  TRobot = class(TInterfacedObject, IStream)
  private
      FStream: TRobotStream;
      function GetStream: IStream;
  public
     destructor Destroy; override;
      property Stream: IStream read GetStream implements IStream;
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);
var
    rs: IStream;
begin
    rs := TRobot.Create;
    LoadRobotFromDatabase(rs); //dummy method, just to demonstrate we use the stream
    rs := nil;
end;

procedure TForm1.LoadRobotFromDatabase(rs: IStream);
begin
    rs.Revert; //dummy method call, just to prove we can call it
end;

destructor TRobot.Destroy;
begin
  FStream.Free;
  inherited;
end;

function TRobot.GetStream: IStream;
begin
  if FStream = nil then
     FStream := TRobotStream.Create(Self);
  result := FStream;
end;

Проблема заключается в том, что объект "родительский" TRobot уничтожается во время вызова:

FStream := TRobotStream.Create(Self);

Ответ 1

Вы должны добавить экземпляр поля для созданного дочернего объекта:

type
  TRobot = class(TInterfacedObject, IStream)
  private
     FStream: TRobotStream;
     function GetStream: IStream;
  public
     property Stream: IStream read GetStream implements IStream;
  end;

destructor TRobot.Destroy;
begin
  FStream.Free; 
  inherited; 
end;

function TRobot.GetStream: IStream;
begin
  if FStream = nil then 
    FStream := TRobotStream.Create(Self);
  result := FStream;
end;

Обновление TRobotStream должен быть получен из TAggregatedObject, как вы уже догадались. Декларация должна быть:

type
  TRobotStream = class(TAggregatedObject, IStream)
   ...
  end;

Нет необходимости упоминать IUnknown.

В TRobot.GetStream строка result := FStream подразумевает FStream as IStream, поэтому выписывать это тоже не нужно.

FStream должен быть объявлен как TRobotStream, а не как IStream, поэтому он может быть уничтожен при уничтожении экземпляра TRobot. Примечание: TAggregatedObject не имеет подсчета ссылок, поэтому контейнер должен заботиться о его времени жизни.

Обновите (код Delphi 5):

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, activex, comobj;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Edit1: TEdit;
    procedure Button1Click(Sender: TObject);
  private
    procedure LoadRobotFromDatabase(rs: IStream);
  public
  end;

type
  TRobotStream = class(TAggregatedObject, IStream)
  public
    { IStream }
    function Seek(dlibMove: Largeint; dwOrigin: Longint;
       out libNewPosition: Largeint): HResult; stdcall;
    function SetSize(libNewSize: Largeint): HResult; stdcall;
    function CopyTo(stm: IStream; cb: Largeint; out cbRead: Largeint; out cbWritten: Largeint): HResult; stdcall;
    function Commit(grfCommitFlags: Longint): HResult; stdcall;
    function Revert: HResult; stdcall;
    function LockRegion(libOffset: Largeint; cb: Largeint; dwLockType: Longint): HResult; stdcall;
    function UnlockRegion(libOffset: Largeint; cb: Largeint; dwLockType: Longint): HResult; stdcall;
    function Stat(out statstg: TStatStg; grfStatFlag: Longint): HResult; stdcall;
    function Clone(out stm: IStream): HResult; stdcall;
    function Read(pv: Pointer; cb: Longint; pcbRead: PLongint): HResult; stdcall;
    function Write(pv: Pointer; cb: Longint; pcbWritten: PLongint): HResult; stdcall;
  end;

type
  TRobot = class(TInterfacedObject, IStream)
  private
    FStream: TRobotStream;
    function GetStream: IStream;
  public
    destructor Destroy; override;
    property Stream: IStream read GetStream implements IStream;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  rs: IStream;
begin
  rs := TRobot.Create;
  LoadRobotFromDatabase(rs); //dummy method, just to demonstrate we use the stream
  rs := nil;
end;

procedure TForm1.LoadRobotFromDatabase(rs: IStream);
begin
  rs.Revert; //dummy method call, just to prove we can call it
end;

function TRobotStream.Clone(out stm: IStream): HResult;
begin
end;

function TRobotStream.Commit(grfCommitFlags: Integer): HResult;
begin
end;

function TRobotStream.CopyTo(stm: IStream; cb: Largeint; out cbRead, cbWritten: Largeint): HResult;
begin
end;

function TRobotStream.LockRegion(libOffset, cb: Largeint; dwLockType: Integer): HResult;
begin
end;

function TRobotStream.Read(pv: Pointer; cb: Integer; pcbRead: PLongint): HResult;
begin
end;

function TRobotStream.Revert: HResult;
begin
end;

function TRobotStream.Seek(dlibMove: Largeint; dwOrigin: Integer;
  out libNewPosition: Largeint): HResult;
begin
end;

function TRobotStream.SetSize(libNewSize: Largeint): HResult;
begin
end;

function TRobotStream.Stat(out statstg: TStatStg; grfStatFlag: Integer): HResult;
begin
end;

function TRobotStream.UnlockRegion(libOffset, cb: Largeint; dwLockType: Integer): HResult;
begin
end;

function TRobotStream.Write(pv: Pointer; cb: Integer; pcbWritten: PLongint): HResult;
begin
end;

destructor TRobot.Destroy;
begin
  FStream.Free;
  inherited;
end;

function TRobot.GetStream: IStream;
begin
  if FStream = nil then
     FStream := TRobotStream.Create(Self);
  result := FStream;
end;

end.

Ответ 2

Нет необходимости в том, чтобы ваш класс наследовал от какого-либо конкретного класса. Вы можете наследовать от TObject, если соответствующие методы были реализованы. Я сделаю все просто и проиллюстрирую, используя TInterfacedObject, который предоставляет 3 основных метода, которые вы уже определили.

Кроме того, вам не нужно TRobotStream = class(TAggregatedObject, IUnknown, IStream). Вместо этого вы можете просто объявить, что IStream наследуется от IUnknown. Кстати, я всегда даю своим интерфейсам GUID (нажмите сочетание клавиш Ctrl + Shift + G).

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

  • Делегирование по типу интерфейса
  • Делегирование в класс Type
  • Метод псевдонимов

Самое простое делегирование - это интерфейс.

TRobotStream = class(TinterfacedObject, IStream)

TRobot = class(TInterfacedObject, IStream)
private
  //The delegator delegates the implementations of IStream to the child object.
  //Ensure the child object is created at an appropriate time before it is used.
  FRobotStream: IStream;
  property RobotStream: IStream read FRobotStream implements IStream;
end;

Возможно, есть несколько вещей, на которые стоит обратить внимание:

  • Убедитесь, что объекты, которые вы делегируете, имеют соответствующее время жизни.
  • Обязательно держите ссылку на делегата. Помните, что интерфейсы подсчитываются по ссылкам и будут уничтожены, как только число упадет до нуля. Это может быть причиной ваших головных болей.