Vamos falar de alguns cuidados que temos que ter ao usar interface para não deixarmos acontecer AccessViolation em rotinas que utilizem Interface.
Imaginamos a seguinte situação. Você tem uma interface que implementa uma rotina básica para salvar informações do banco de dados. No entanto você está salvando duas classes, TNotaFiscal e TItemNF, cada uma herdando diretamente de IClasseBase(Interface que implementa a rotina de salvar). Abaixo, o código fonte da Unit com a Interface e as classes montadas.
unit UInterface; interface uses Vcl.Dialogs; type // Interface que implementa o método Salvar que será usado nas demais classes. IClasseBase = Interface ['{7BAE5AE6-E856-4977-B156-71614AD72528}'] procedure MetodoSalvarNaInterface; end; TNotaFiscal = class(TInterfacedObject, IClasseBase) procedure MetodoSalvarNaInterface; function MetodoDaClasseNotaFiscal: string; end; TItemNF = class(TInterfacedObject, IClasseBase) procedure MetodoSalvarNaInterface; function MetodoDaClasseItemNF: string; end; implementation { TNotaFiscal } function TNotaFiscal.MetodoDaClasseNotaFiscal: string; begin Result := 'SalvarClasseNotaFiscal'; end; procedure TNotaFiscal.MetodoSalvarNaInterface; begin ShowMessage(MetodoDaClasseNotaFiscal); end; { TItemNF } function TItemNF.MetodoDaClasseItemNF: string; begin Result := 'SalvarClasseItemNF'; end; procedure TItemNF.MetodoSalvarNaInterface; begin ShowMessage(MetodoDaClasseItemNF); end; end.Agora no form principal, coloque um botão e deixe a Unit com o código o mais parecido com o da listagem abaixo:
unit fPrincipal; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, UInterface; type TfrmPrincipal = class(TForm) Button1: TButton; procedure FormCreate(Sender: TObject); procedure Button1Click(Sender: TObject); private Nota: TNotaFiscal; Item: TItemNF; procedure ChamadaDeMetodoDaInterface(pClasseBase: IClasseBase); { Private declarations } public { Public declarations } end; var frmPrincipal: TfrmPrincipal; implementation {$R *.dfm} procedure TfrmPrincipal.Button1Click(Sender: TObject); begin ChamadaDeMetodoDaInterface(Nota); ChamadaDeMetodoDaInterface(Item); end; procedure TfrmPrincipal.ChamadaDeMetodoDaInterface( pClasseBase: IClasseBase); begin pClasseBase.MetodoEspecificoInterface; end; procedure TfrmPrincipal.FormCreate(Sender: TObject); begin Nota := TNotaFiscal.Create; Item := TItemNF.Create; end; end.
Execute a aplicação, clique no botão, verifique as duas mensagens. Clique no botão novamente: BINGO!!!!. Um AccessViolation ocorreu. Correto?!
Entendendo o problema:
Um dos conceitos de interfaces é garantir o gerenciamento de memória dos objetos que os implementam. Os métodos _AddRef e _Release implementam esse gerenciamento, incrementam a contagem de referência sobre o objeto e destroem o mesmo quando o contador de referência é zero.
O erro ocorre porque ao final da execução do método a Interface automaticamente zera esse contador destruindo a referência sobre o objeto criado(somente a referência), no caso TNotaFiscal e TItemNF, e quando executamos novamente a referência não existe mais e então ocorre o AccessVIolation.
Contornando o problema de duas formas diferentes:
Exemplo 1:
Veja a rotina abaixo:
procedure ChamadaDeMetodoDaInterface(pClasseBase: IClasseBase);Utilizando a reservada const na declaração do método, contornamos esse problema
procedure ChamadaDeMetodoDaInterface(const pClasseBase: IClasseBase);
O que é isso?
Quando passamos o parâmetro com a reservada const, o mesmo é passado por valor e não por referência, o que tira das mãos da Interface a destruição da referência deixando por conta do usuário implementar a rotina de liberação como na rotina abaixo:
procedure TfrmPrincipal.FormDestroy(Sender: TObject); begin Nota.Free; end;Exemplo 2
Para não precisarmos nos preocupar com o const nem com o Free, declaramos as variáveis com o tipo da nossa Interface: IClasseBase. Com isso, deixamos a Interface gerenciar a destruição da referência e dos objetos relacionados. Lembrando que a criação das variáveis deve continuar sendo dos seus tipos específicos, ou seja:
Nota := TNotaFiscal.Create; Item := TItemNF.Create;
Era isso o que tínhamos para o momento. Abraço a todos, dúvidas no final do post e até a próxima.