У меня есть объект, который делегирует реализацию особо сложного интерфейса для дочернего объекта. Это точно, я думаю, это работа 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);