quarta-feira, 6 de maio de 2015

AccessViolation em Interface

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  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.

Nenhum comentário :