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 как неизвестно );

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


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

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

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

Причина в том, что TAggregatedObject откладывает все QueryInterface (т.е. приведение типа) обратно к родительскому объекту. В моем случае я снимаю 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;

Boom! Переполнение стека.


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

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

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

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

  1. Result := TRobotStream.Create(Self);
  2. Result := TRobotStream.Create(Self as IUnknown);
  3. Result := TRobotStream.Create(Self) as IStream;
  4. Result := TRobotStream.Create(Self as IUnknown) as IStream;

Настоящий вопрос

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

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

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

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

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

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

Редактировать: грамматически

Редактировать 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);

2 ответа

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

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.
enter code here

Нет необходимости в том, чтобы ваш класс наследовал от какого-либо конкретного класса. Вы можете наследовать от 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;

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

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