terça-feira, 30 de abril de 2013

Interfaces no Delphi e destruição prematura de objetos / access violation


Recentemente me perguntaram no site da DevMedia se é correto ou não misturar interfaces e objetos em uma conversão temporária, como esta abaixo:

var C: TCarro;
begin

  C := TCarro.Create; //tenho um objeto, mas C não conta referencias
  C.Placa := 'ABC1234';
  (C as ICarro).Rodar; //converte e roda na mesma linha, mas zera o contador de referencias na proxima linha
  C.Parar;  //isso dá erro
Segue links da Thread:
http://www.devmedia.com.br/poo-dominando-o-uso-de-interfaces-revista-clube-delphi-134/22569
http://www.vitorrubio.com.br/downloads/TesteInterfaces.zip
http://www.vitorrubio.com.br/downloads/CD134_Interfaces_V2_Reformulado.zip

Isso por causa do problema de se zerar o contador de referências em interfaces tendo mais uma referência ao objeto em uma variável do tipo objeto e destruir esse objeto prematuramente.

Basicamente nesses casos o comportamento esperado seria um erro.
Pode acontecer de o objeto continuar acessível na memória, ou pelo menos alguns métodos que não usem dados internos do objeto, mas esse comportamento não é o esperado e é causado simplesmente por "sujeira" na memória.
Fiz um teste aqui e também funcionou, mesmo assim eu não recomendo. Diferenças na versão do Delphi ou na plataforma podem ocasionar erros.
Na época em que escrevi esse artigo eu usava Delphi XE em um computador de 64 bits, mas não me lembro a versão do Windows.
Hoje fiz o teste tanto no Delphi XE como no Delphi XE 4 usando Windows 7, mas num computador de 32 bits.

O porquê de eu esperar que essa construção cause um erro eu explico:
Dado que interfaces contam referências, então são "Reference Counted" e dado que você cria, converte e usa um objeto dentro de um mesmo método (como o evento click de um botão).

Se você criar o seu objeto já atribuindo ele à uma variável do tipo interface, assim:
var i: ICarro;
begin
 i:= TCarro.Create; //1 referencia a um TCarro
 (i as ICarro).DeslocarSe; //2 referencias ao TCarro //não precisa disso, apenas mostro como funciona normalmente
  i.OutroMetodo;
end; //final do método, 0 referencias ao mesmo TCarro, então  o objeto "hospedado" em I é destruido com free.
É esperado que um objeto apresente erro nesse tipo de conversão principalmente quando ele possui outro objeto dentro. Considere um objeto que tenha um StringList como um de seus campos.
var i: TCarro;
begin
i:= TCarro.Create; //1 referencia a um TCarro, no entanto I é um objeto, portanto não conta referências

(i as ICarro).DeslocarSe; //1 referencias ao TCarro hospedado em I convertido para ICarro

 i.OutroMetodo; //antes de chegar aqui já temos 0 referências (pois não existe mais a referência temporária que estava sendo usada na conversão para ICarro), portanto Free é acionado e o objeto em I é destruído "por engano". Aqui deve ocorrer um erro ao se executar OutroMetodo, pois o próprio objeto está destruído .
end;


Veja esse exemplo que eu criei no Delphi Xe 4, links:
http://www.devmedia.com.br/poo-dominando-o-uso-de-interfaces-revista-clube-delphi-134/22569
http://www.vitorrubio.com.br/downloads/TesteInterfaces.zip
http://www.vitorrubio.com.br/downloads/CD134_Interfaces_V2_Reformulado.zip

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type

  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    Button5: TButton;
    Button6: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
    procedure Button5Click(Sender: TObject);
    procedure Button6Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;



  IVeiculo = interface  (iinterface)
  ['{1D2F0F1D-60CC-47BE-AD22-4DA59D87C7CA}']
    procedure DeslocarSe;
    procedure Parar;
  end;

  IVeiculoTerrestre = interface (IVeiculo)
  ['{CD718BB9-FE3A-4C19-8CE2-01EBB1843B96}']
    procedure DeslocarSe;
    procedure Parar;
    procedure Rodar;
  end;

  ICarro = interface (IVeiculoTerrestre)
  ['{3B167780-8EA0-4F59-9DBF-A9F68F54F595}']
    procedure DeslocarSe;
    procedure Parar;
    procedure Rodar;
  end;

  //TCarro = class(TInterfacedObject,  ICarro)  //nessa forma você pode atribuir a criação de um TCarro a uma variável ICarro, IVeiculoTerrestre e IVeiculo, mas não pode fazer conversões do tipo AS entre eles
  TCarro = class(TInterfacedObject,  ICarro, IVeiculoTerrestre, IVeiculo) //esta forma permite qualquer tipo de atribuição e conversão.
  private
    FPlaca: TStringList;
    function GetPlaca: string;
    procedure SetPlaca(value: string);
  public
    constructor Create;
    destructor Destroy; override;

    procedure DeslocarSe;
    procedure Parar;
    procedure Rodar;
    property Placa: string read GetPlaca write SetPlaca;
  end;


var
  Form1: TForm1;


implementation

{$R *.dfm}

{ TCarro }

constructor TCarro.Create;
begin
  FPlaca := TStringList.Create;
end;

procedure TCarro.DeslocarSe;
begin
  ShowMessage(Placa + ' Deslocando-se. ' + self.RefCount.ToString() + ' referências');
end;

destructor TCarro.Destroy;
begin
  FPlaca.Free;
  inherited;
end;

function TCarro.GetPlaca: string;
begin
  Result := FPlaca.Text;
end;

procedure TCarro.Parar;
begin
  ShowMessage(Placa + ' Parando.' + self.RefCount.ToString() + ' referências');
end;

procedure TCarro.Rodar;
begin
  DeslocarSe;
end;

procedure TCarro.SetPlaca(value: string);
begin
  FPlaca.Text := value;
end;

procedure TForm1.Button1Click(Sender: TObject);
var C: TCarro;
begin

  C := TCarro.Create; //tenho um objeto, mas C não conta referencias
  C.Placa := 'ABC1234';
  (C as ICarro).Rodar; //converte e roda na mesma linha, mas zera o contador de referencias na proxima linha
  C.Parar;  //isso dá erro
  C.Rodar;
  C.Parar;

end;

procedure TForm1.Button2Click(Sender: TObject);
var C: TCarro;
  I: IVeiculoTerrestre;
begin

  C := TCarro.Create; //tenho um objeto, mas C não conta referencias
  C.Placa := 'ABC1234';
  I := (C as ICarro); //Conversão joga  para I o controle da contagem de referencias
  I.Rodar; //uso normal
  I.Parar; //uso normal
  I.Rodar; //uso normal
  I.Parar; //uso normal

  C.Rodar; //Funciona normal, mas não se deve misturar objetos comuns com interfaces, por isso para de usar C uma vez convertido para I
  C.Parar; //Funciona normal, mas não se deve misturar objetos comuns com interfaces, por isso para de usar C uma vez convertido para I

end;

procedure TForm1.Button3Click(Sender: TObject);
var C: TCarro;
  I: IVeiculo;
begin

  C := TCarro.Create; //tenho um objeto, mas C não conta referencias
  C.Placa := 'ABC1234';
  I := (C as ICarro); //Conversão joga  para I o controle da contagem de referencias

  I.DeslocarSe;
  I.Parar;

  //I agora pode ser convertido para qualquer uma das interfaces ancestrais de ICarro sem perder o controle de referência
  (I as ICarro).Rodar;
  (I as ICarro).Parar;
  (I as IVeiculoTerrestre).Rodar;
  (I as IVeiculoTerrestre).Parar;
  (I as IVeiculo).DeslocarSe;
  (I as IVeiculo).Parar;

  C.Rodar; //Funciona normal, mas não se deve misturar objetos comuns com interfaces, por isso para de usar C uma vez convertido para I
  C.Parar; //Funciona normal, mas não se deve misturar objetos comuns com interfaces, por isso para de usar C uma vez convertido para I

end;

procedure TForm1.Button4Click(Sender: TObject);
var C: TCarro;
  iv1, iv2: IVeiculo;
  ivt1, ivt2: IVeiculoTerrestre;
  ic: ICarro;
begin

  C := TCarro.Create;
  C.Placa := 'ABC1234';
  ic  := (C as ICarro);

  ivt1 := (C as IVeiculoTerrestre);
  iv1  :=  (C as IVeiculo);

  ivt2 := (ic as IVeiculoTerrestre);
  iv2  :=  (ic as IVeiculo);

  ic.DeslocarSe;
  ic.Parar;
  ivt1.Rodar;
  ivt1.Parar;
  iv1.DeslocarSe;
  iv1.Parar;

  ivt2.Rodar;
  ivt2.Parar;
  iv2.DeslocarSe;
  iv2.Parar;

end;

procedure TForm1.Button5Click(Sender: TObject);
var C: TCarro;
  iv1, iv2: IVeiculo;
  ivt1, ivt2: IVeiculoTerrestre;
  ic: ICarro;
begin

  C := TCarro.Create;
  C.Placa := 'ABC1234';
  ic  := (C as ICarro);

  ic.DeslocarSe;
  ic.Parar;

  if Supports(c, IVeiculoTerrestre, ivt1) then
  begin
    ivt1.Rodar;
    ivt1.Parar;
  end;

  if Supports(c, IVeiculo, iv1) then
  begin
    iv1.DeslocarSe;
    iv1.Parar;
  end;

  if Supports(ic, IVeiculoTerrestre, ivt2) then
  begin
    ivt2.Rodar;
    ivt2.Parar;
  end;

  if Supports(ic, IVeiculo, iv2) then
  begin
    iv2.DeslocarSe;
    iv2.Parar;
  end;

end;

procedure TForm1.Button6Click(Sender: TObject);
var C: TCarro;
  iv1, iv2: IVeiculo;
  ivt1, ivt2: IVeiculoTerrestre;
  ic: ICarro;
begin

  C := TCarro.Create;
  C.Placa := 'ABC1234';
  ic  := ICarro(C);

  ivt1 := IVeiculoTerrestre(C);
  iv1  := IVeiculo(C);

  ivt2 :=  IVeiculoTerrestre(ic);
  iv2  :=  IVeiculo(ic);

  ic.DeslocarSe;
  ic.Parar;
  ivt1.Rodar;
  ivt1.Parar;
  iv1.DeslocarSe;
  iv1.Parar;

  ivt2.Rodar;
  ivt2.Parar;
  iv2.DeslocarSe;
  iv2.Parar;
end;

end.

Ele não deu erro como eu esperava no primeiro botão, mesmo assim eu não aconselho a fazer um uso de interfaces + objetos como aquele, principalmente quando seus objetos são passados para métodos ou retornados deles.
Infelizmente não consegui reproduzir esse erro hoje. Compilei e rodei esse código no Delphi XE, no Delphi XE4 e no Lazarus, e em todos os casos funcionou.
Mesmo assim, cuidado redobrado ao se passar variáveis do tipo interface como parâmetros.

quarta-feira, 17 de abril de 2013

Diferença entre Log Shipping, Mirroring e Replication

Li alguns artigos sobre a diferença entre Log Shipping, Mirroring e Replication e gostaria de compartilhar.
http://www.replicationanswers.com/ReplicationLogShippingMirroring.asp
Difference between Log Shipping, Mirroring and Replication
http://simplesql.blogspot.com.br/2011/01/replication-vs-mirroring-and-what-to.html

Basicamente, o Log Shipping é uma estratégia só de Disaster Recovery, mas não muito eficiente para relatórios ou replicação/distribuição dos dados. Ele nada mais faz do que restaurar em uma outra base os logs de transação periodicamente. Esta base fica com lock exclusivo, então consultas nela só podem ser feitas usando-se nolock (dirty reads) ou isolation level snapshot.
Log Shipping restaura o banco inteiro, inclusive tabelas de sistema, views, procedures etc.

Mirroring também é uma solução para Disaster Recovery e funciona para o banco todo, inclusive dados de sistema. Sua vantagem é que pode ser configurado com failover automático, ou seja, para entrar no ar assim que o servidor principal apresentar algum problema. Sua desvantagem é que não pode ser usado como um banco de dados de relatórios sem o uso de um custoso snapshot, cusando uma certa latência ou restringindo os relatórios a D-1.
Mirroring é a melhor solução para disaster recovery.

Replication é a melhor solução para relatórios com baixa latência e alta performance, distribuição, replicação e integração de dados. Pode ser usado como disaster recovery também, com baixíssima latência, mas não replica dados de sistema, apenas dados de aplicação, e não permite failover automático, necessitando de reconfiguração das aplicações. Portanto Replicação é melhor para os cenários onde você deseja ter uma cópia de uma ou mais tabelas de um banco A em outro banco B para consultas e relatórios mais rápidos, integrações ou mesmo uma replicação em duas vias para bancos de dados distribuídos.

Conclusão: Use Mirroring para Disaster Recovery se seu ambiente permitir. Mesmo assim o backup periódico do transaction log sempre é bem vindo.
Para aquela sua tabela, daquela sua outra aplicação, que precisa puxar ou copiar quase em tempo real os dados de uma tabela de um banco legado, use Replication.

Postagens populares

Marcadores

delphi (60) C# (31) poo (21) Lazarus (19) Site aos Pedaços (15) sql (13) Reflexões (10) .Net (9) Humor (9) javascript (9) ASp.Net (8) api (8) Básico (6) Programação (6) ms sql server (5) Web (4) banco de dados (4) HTML (3) PHP (3) Python (3) design patterns (3) jQuery (3) livros (3) metaprogramação (3) Ajax (2) Debug (2) Dicas Básicas Windows (2) Pascal (2) games (2) linguagem (2) música (2) singleton (2) tecnologia (2) Anime (1) Api do Windows (1) Assembly (1) Eventos (1) Experts (1) GNU (1) Inglês (1) JSON (1) SO (1) datas (1) developers (1) dicas (1) easter egg (1) firebird (1) interfaces (1) introspecção (1) memo (1) oracle (1) reflexão (1)