Delphi: Como delegar a implementação da interface ao objeto filho?

Eu tenho um objeto que delega a implementação de uma interface particularmente complexa para um objeto filho. esteé exatamente eu acho que é o trabalho deTAggregatedObject. O "criança"objeto mantém uma fraca referência ao seu"controlador", e tudoQueryInterface pedidos são passados de volta para o pai. Isso mantém a regra de queIUnknown é sempre o mesmo objeto.

Então, meu pai (ou seja,"Controlador") declara que implementa o métodoIStream interface:

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

Nota: Este é um exemplo hipotético. eu escolhi a palavraRobot porque parece complicado, ee a palavra tem apenas 5 letras - é curta. eu também escolhiIStream porque é curto. eu ia usarIPersistFile ouIPersistFileInit, mas são mais longos e tornam o código de exemplo mais difícil de ser real. Em outras palavras: é um exemplo hipotético.

Agora eu tenho meu objeto filho que implementaráIStream:

type
   TRobotStream = class(TAggregatedObject, IStream)
   public
      ...
   end;

Tudo o que resta, e é aí que começa o meu problema: criar oRobotStream quando solicitado:

function TRobot.GetStream: IStream;
begin
    Result := TRobotStream.Create(Self) as IStream;
end;

Este código falha ao compilar, com o erroOperator not applicable to this operand type..

Isso ocorre porque o delphi está tentando executar oas IStream em um objeto que não implementaIUnknown:

TAggregatedObject = class
 ...
   { IUnknown }
   function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
   function _AddRef: Integer; stdcall;
   function _Release: Integer; stdcall;
 ...

O desconhecidométodos pode estar lá, mas o objeto nãoanunciar que suportaIUnknown. Sem umIUnknown interface, Delphi não pode chamarQueryInterface para executar o elenco.

Então eu mudo meuTRobotStream para anunciar que implementa a interface ausente (o que faz; a herda do ancestral):

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

E agora ele compila, mas trava em tempo de execução na linha:

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

Agora eu posso vero que é acontecendo, mas não sei explicarporque. Delphi está chamandoIntfClearno meu paiRobot objeto, na saída do construtor do objeto filho.

Eu não sei a maneira correta de evitar isso. eu poderia tentar forçar o elenco:

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

e espero que mantenha uma referência. Acontece que ele mantém a referência - sem travamentos na saída do construtor.

Nota: Isso é confuso para mim. Desde que eu estou passando umobjeto onde uminterface é esperado. Eu assumiria que o compilador está implicitamente pré-formando um typecast, ou seja:

Result := TRobotStream.Create(Self como IUnknown);

para atender a chamada. O fato de o verificador de sintaxe não ter reclamado permitiu-me supor que tudo estava correto.

Mas os acidentes não terminaram. eu mudei a linha para:

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

E o código realmente retorna do construtor deTRobotStream sem destruir meu objeto pai, mas agora recebo um estouro de pilha.

A razão é queTAggregatedObject adia tudoQueryInterface (ou seja, conversão de tipo) de volta ao objeto pai. No meu caso, estou lançando umTRobotStream para umIStream.

Quando eu pergunto aoTRobotStream por suaIStream no fim de:

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

Ele se vira e pede a suacontrolador para oIStream interface, que aciona uma chamada para:

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

que vira e chama:

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

Estrondo! Estouro de pilha.

Cegamente, tento remover o elenco final paraIStream, vamos Delphi tentar converter implicitamente o objeto para uma interface (que eu acabei de ver acima não funciona direito):

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

E agora não há colisão; que eu não entendo muito isso. Eu construí um objeto, um objeto que suporta múltiplas interfaces. Como é que agora a Delphi sabe lançar a interface? Está realizando a contagem de referência adequada? vi acima que não. Existe um bug sutil aguardando falha do cliente?

Então, eu tenho quatro maneiras possíveis de chamar minha linha única. Qual deles é válido?

Result := TRobotStream.Create(Self);Result := TRobotStream.Create(Self as IUnknown);Result := TRobotStream.Create(Self) as IStream;Result := TRobotStream.Create(Self as IUnknown) as IStream;A verdadeira questão

Eu bati em alguns bugs sutis e difíceis de entender os meandros do compilador. Isso me leva a acreditar que fiz tudo completamente errado. Se necessário, ignore tudo o que eu disse e me ajude a responder à pergunta:

Qual é a maneira correta de delegar a implementação da interface para um objeto filho?

Talvez eu deva estar usandoTContainedObject ao invés deTAggregatedObject. Talvez os dois trabalhem em conjunto, onde os pais devem estarTAggregatedObject e a criança éTContainedObject. Talvez seja o contrário. Talveznem aplicar neste caso.

Nota: Tudo na parte principal da minha postagem pode ser ignorado. Foi apenas para mostrar que eu pensei sobre isso. Há quem argumente que, ao incluir o que tentei, envenenei as respostas possíveis; em vez de responder à minha pergunta, as pessoas podem se concentrar na minha pergunta que falhou.

O objetivo real é delegar a implementação da interface a um objeto filho. Esta pergunta contém minhas tentativas detalhadas de resolver o problema comTAggregatedObject. Você nem vê meus outros dois padrões de solução. Um dos quais sofre de referência circular conta e quebra asIUnknown regra de equivalência.

Rob Kennedy pode se lembrar; e me pediu para fazer uma pergunta que pedisse uma solução para o problema, em vez de uma solução para um problema em uma das minhas soluções.

Editar: grammerificado

Edição 2: Não existe um controlador de robô. Bem, há - eu trabalhei com controladores Funuc RJ2 o tempo todo. Mas não neste exemplo!

Editar 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;

O problema aqui é que o "pai"TRobot O objeto é destruído durante a chamada para:

FStream := TRobotStream.Create(Self);

questionAnswers(2)

yourAnswerToTheQuestion