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

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

Итак, мой родительский объект (т.е. "Controller" ) объявляет, что он реализует интерфейс 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 (то есть приведение типов) обратно к родительскому объекту.В моем случае я разыгрываю 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 ]

9 голосов
/ 14 августа 2010

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

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
3 голосов
/ 14 августа 2010

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

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

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