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