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 знает, как использовать интерфейс? Выполняет ли он правильный подсчет ссылок? Я видел выше, что это не так. Есть ли небольшая ошибка, ожидающая сбоя для клиента?
Так что у меня осталось четыре возможных способа позвонить по одной линии. Какой из них действителен?
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
, Вы даже не видите два других моих решения. Один из которых страдает от циклического подсчета ссылок, и разрывы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;
Есть, пожалуй, несколько вещей, на которые стоит обратить внимание:
- Убедитесь, что объекты, которые вы делегируете, имеют соответствующее время жизни.
- Обязательно держите ссылку на делегата. Помните, что интерфейсы подсчитываются по ссылкам и будут уничтожены, как только число упадет до нуля. Это может быть причиной ваших головных болей.