AccessViolation em Interface
Autor: Rafael Stange | Publicado: 2015-05-06T14:36:00-03:00

Olá pessoal.

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 &nbsp;montadas.

unit UInterface;

interface

uses

&nbsp; Vcl.Dialogs;

type

&nbsp; // Interface que implementa o método Salvar que será usado nas demais classes.

&nbsp; IClasseBase = Interface
&nbsp; ['{7BAE5AE6-E856-4977-B156-71614AD72528}']
&nbsp; &nbsp; procedure MetodoSalvarNaInterface;
&nbsp; end;

&nbsp; TNotaFiscal = class(TInterfacedObject, &nbsp;IClasseBase)
  &nbsp; procedure MetodoSalvarNaInterface;
&nbsp; &nbsp; function MetodoDaClasseNotaFiscal: string;
&nbsp; end;


&nbsp; TItemNF = class(TInterfacedObject, &nbsp;IClasseBase)
&nbsp; &nbsp; procedure MetodoSalvarNaInterface;
&nbsp; &nbsp; function MetodoDaClasseItemNF: string;
&nbsp; end;

implementation

{ TNotaFiscal }

function TNotaFiscal.MetodoDaClasseNotaFiscal: string;
begin
&nbsp; Result := 'SalvarClasseNotaFiscal';
end;

procedure TNotaFiscal.MetodoSalvarNaInterface;
begin
&nbsp; ShowMessage(MetodoDaClasseNotaFiscal);
end;

{ TItemNF }

function TItemNF.MetodoDaClasseItemNF: string;
begin
&nbsp; Result := 'SalvarClasseItemNF';
end;

procedure TItemNF.MetodoSalvarNaInterface;
begin
&nbsp; 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
&nbsp; Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, UInterface;

type
&nbsp; TfrmPrincipal = class(TForm)
&nbsp; &nbsp; Button1: TButton;
&nbsp; &nbsp; procedure FormCreate(Sender: TObject);
&nbsp; &nbsp; procedure Button1Click(Sender: TObject);
&nbsp; private
&nbsp; &nbsp; Nota: TNotaFiscal;
&nbsp; &nbsp; Item: TItemNF;
&nbsp; &nbsp; procedure ChamadaDeMetodoDaInterface(pClasseBase: IClasseBase);
&nbsp; &nbsp; { Private declarations }
&nbsp; public
&nbsp; &nbsp; { Public declarations }
&nbsp; end;

var
&nbsp; frmPrincipal: TfrmPrincipal;

implementation

{$R *.dfm}

procedure TfrmPrincipal.Button1Click(Sender: TObject);
begin
&nbsp; ChamadaDeMetodoDaInterface(Nota);
&nbsp; ChamadaDeMetodoDaInterface(Item);
end;

procedure TfrmPrincipal.ChamadaDeMetodoDaInterface(
&nbsp; pClasseBase: IClasseBase);
begin
&nbsp; pClasseBase.MetodoEspecificoInterface;
end;

procedure TfrmPrincipal.FormCreate(Sender: TObject);
begin
&nbsp; Nota := TNotaFiscal.Create;
&nbsp; Item := TItemNF.Create;
end;

end.


Execute a aplicação, clique no botão, verifique as duas mensagens. Clique no botão novamente: BINGO!!!!. Um&nbsp;AccessViolation&nbsp;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.