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á chamandoIntfClear
no 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ãoEu 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);