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

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

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

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

Примечание: Это гипотетический пример. я выбрал слово робот потому что это звучит сложно, и и слово длиной всего 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;

Этот код не компилируется с ошибкой Оператор не применим к этому типу операнда. .

Это потому, что delphi пытается выполнить как 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;

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

Примечание: Меня это смущает. Поскольку я прохожу объект где он наследует его от своего предка):

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

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

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

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

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

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

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

Примечание: Меня это смущает. Поскольку я прохожу объект где он наследует его от своего предка):

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Результат: = TRobotStream.Create (Self как IUnknown );

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


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

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. Результат: = TRobotStream.Create (Self);
  2. Результат: = TRobotStream.Create (Self as IUnknown);
  3. Результат: = TRobotStream.Create (Self) как IStream;
  4. Результат: = TRobotStream Создать (Self как IUnknown) как IStream;

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

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

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

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

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

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

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

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

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

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

Примечание: Все в основной части моего поста можно игнорировать. Это было просто и помогите мне ответить на вопрос:

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

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

Примечание: Все в основной части моего поста можно игнорировать. Это было просто и помогите мне ответить на вопрос:

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

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

Примечание: Все в основной части моего поста можно игнорировать. Это было просто где родитель должен быть TAggregatedObject , а дочерний - TContainedObject . Может быть, это наоборот. Может быть, ни не применимы в этом случае.

Примечание: Все в основной части моего поста можно игнорировать. Это было просто где родитель должен быть TAggregatedObject , а дочерний - TContainedObject . Может быть, это наоборот. Может быть, ни не применимы в этом случае.

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

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

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

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

Редактировать 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);
11
задан Ian Boyd 16 August 2010 в 14:47
поделиться

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.
9
ответ дан 3 December 2019 в 09:18
поделиться

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

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

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

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

Простейшее делегирование - по интерфейсу.

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;

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

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

Похожие вопросы: