Escrevendo um Expert para a IDE do Delphi

Escrito em 23 de fevereiro de 2007 em Experts por Leonel Togniolli

 

Já aconteceu de você ter uma idéia de algo que poderia ter na IDE do Delphi para facilitar sua vida? Uma opção é fazer uma solicitação e esperar que seja implementado. Uma outra opção é arregaçar a mangas e implementar você mesmo.

A IDE do Delphi é bastante extensível. Existe uma API chamada Open Tools API (OTA) que permite customizar vários pontos da IDE, integrando seu código em várias partes. Apesar de a documentação ser um pouco escassa e ser um pouco difícil de descobrir algumas coisas, é bem possível escrever experts para quase tudo na IDE.

Por enquanto, vou deixar a implementação do seu expert com você. Vou mostrar como passar pelo primeiro problema no meio do caminho, carregar seu expert integrado na IDE.

Um expert geralmente é uma BPL (pode ser uma DLL também, mas é um pouco mais complicado). Crie, então um novo package, salve um algum lugar e adicione uma unit nele.

A OTA é baseada em interfaces, e a maioria delas está definida em ToolsAPI.pas. Adicione então ToolsAPI no uses do seu package. Por padrão, se você compilar agora, o compilador não vai encontrar essa unit. Não saia procurando ela e alterando seu Library Path ou o Search Path do projeto – não é para você compilar essa unit – ela está dentro de DesignIDE.dcp. Vá no nó de Requires do project manager, clique com o direito e selecione Add Reference, e navegue até a pasta Lib dentro da instalação do seu Delphi, encontrando DesignIDE.dcp lá. Depois de adicionar essa referência, o projeto já compila corretamente encontrando ToolsApi sem problemas.

O Expert mais simples que podemos criar agora é chamado de “Wizard” pela OTA. Você pode abrir ToolsAPI.pas e procurar IOTAWizard lá para ver o que precisamos implementar para criar um Wizard:

  IOTAWizard = interface(IOTANotifier)
    ['{B75C0CE0-EEA6-11D1-9504-00608CCBF153}']
    { Expert UI strings }
    function GetIDString: string;
    function GetName: string;
    function GetState: TWizardState;

    { Launch the AddIn }
    procedure Execute;
  end;

As três primeiras funções devem ser implementadas para retornar um ID único para seu wizard, o nome dele, e o seu estado (que pode ser habilitado, assinalado, os dois ou nenhum). O quarto método que precisamos preencher é o Execute, que é onde vamos colocar nosso código para a ação vai acontecer realmente.

Não adianta muito ter um wizard se ele não aparecer em lugar algum. Vamos implementar a interface IOTAMenuWizard também, para que nosso wizard apareça no menu da IDE. Se você for dar uma olhada no ToolsAPI.pas, ele está declarado assim:

  IOTAMenuWizard = interface(IOTAWizard)
    ['{B75C0CE2-EEA6-11D1-9504-00608CCBF153}']
    function GetMenuText: string;
  end;

Nessa interface, só temos que implementar uma função que retorne o texto que vai aparecer no menu. A interface wizard é descendente de IOTANotifier. Para não precisarmos implementar mais métodos que não vamos usar por enquanto, vamos usar a implementação padrão dessa interface que o ToolsAPI inclui como base, o TNotifierObject. Minha implementação ficou assim:

uses
  Dialogs, ToolsAPI;

type
  TPrimeiroWizard = class(TNotifierObject, IOTAWizard, IOTAMenuWizard)
  public
    // IOTAWizard:
    function GetIDString: string;
    function GetName: string;
    function GetState: TWizardState;
    procedure Execute;
    // IOTAMenuWizard:
    function GetMenuText: string;
  end;

function TPrimeiroWizard.GetIDString: string;
begin
  Result := 'PrimeiroWizard';
end;

function TPrimeiroWizard.GetName: string;
begin
  Result := 'Primeiro Wizard';
end;

function TPrimeiroWizard.GetState: TWizardState;
begin
  Result := [wsEnabled];
end;

procedure TPrimeiroWizard.Execute;
begin
  ShowMessage('Primeiro Wizard!');
end;

function TPrimeiroWizard.GetMenuText: string;
begin
  Result := 'Primeiro Wizard';
end;

Vamos a implementação das duas interfaces, retornando as informações necessárias. Finalmente, precisamos registrar esse wizard para que possamos utilizá-lo. Vamos utilizar a seção de initialization da unit para isso:

initialization
  RegisterPackageWizard(TPrimeiroWizard.Create);

E pronto. Agora basta clicar com o direito na sua package dentro do Project Manager, selecionar Install. Se tudo deu certo, um menu com o caption “Primeiro Wizard” apareceu no item Help do Menu principal da IDE. Clicando nele, uma mensagem dizendo “Primeiro Wizard” aparece.

Partindo disso como modelo, é fácil implementar qualquer coisa que quiser agora dentro do Execute do seu expert. É claro, o menu Help não é o melhor lugar para ter um item de menu, e com certeza você quer acessar mais informações da IDE. Vamos fazer isso da próxima vez! 

FastMM e Delphi 2006

Continuando um tema anteriormente publicado, neste artigo vamos começar a detectar os vazamentos de memória (memory leaks) existentes em nossos aplicativos. (isso se existirem, é claro…)

A partir do Delphi 2006 o gerenciador de memória padrão que acompanha o Delphi passa a ser o FastMM. Quem não trabalha com Delphi 2006 pode baixar o FastMM aqui.

Se você estiver começando agora a desenvolver com Delphi deve estar se perguntando: Tá, e dai? O que é memory leak?

Memory leak é um objeto criado pela aplicação que não foi corretamente destruído.

Você pode simular esta situação criando qualquer objeto no OnCreate do formulário principal da sua aplicação. Exemplo:

procedure TForm1.FormCreate(Sender: TObject);
var
  lstVazamento : TStrings;
begin
  lstVazamento := TStringList.Create;
  try
    lstVazamento.Add('Linha 1');
    lstVazamento.Add('Linha 2');
    lstVazamento.Add('Linha 3');
  finally

  end;
end;

Execute o aplicativo e feche-o. Você notará que não acontece nada! :)

Esse teste ridículo foi feito só para você ver como é comum ocorrerem problemas que resultem em vazamentos de memória (memory leaks) como a falta de atenção (nesse caso), a pressa, Control+C / Control+V entre alguns outros.

Agora insira esta linha logo após o begin da sua procedure:

ReportMemoryLeaksOnShutdown := True;

Deixando o código assim:

procedure TForm1.FormCreate(Sender: TObject);
var
  lstVazamento : TStrings;
begin
  ReportMemoryLeaksOnShutdown := True;

  lstVazamento := TStringList.Create;
  try
    lstVazamento.Add('Linha 1');
    lstVazamento.Add('Linha 2');
    lstVazamento.Add('Linha 3');
  finally

  end;
end;

Repita a operação e veja o que acontece após fechar o aplicativo:

Veja que recebemos a notificação do TStringList que criamos e das 3 linhas que adicionamos (String x 3) a ele. A partir dai podemos começar a evitar esse tipo de erro na hora de desenvolvermos aplicativos.

Uma dica importante é você utilizar o bloco try…finally…end; sempre que criar um objeto dinamicamente. Isto evita que ocorra um erro no meio da execução e ele não seja destruído devidamente. Veja os dois exemplos:

Método não apropriado:

procedure TForm1.FormCreate(Sender: TObject);
var
  lstVazamento : TStrings;
begin
  ReportMemoryLeaksOnShutdown := True;

  lstVazamento := TStringList.Create;
  lstVazamento.Add('Linha 1');
  lstVazamento.Add('Linha 2');
  lstVazamento.Add('Linha 3');
  lstVazamento.Free;
end;

Método apropridado de utilização:

procedure TForm1.FormCreate(Sender: TObject);
var
  lstVazamento : TStrings;
begin
  ReportMemoryLeaksOnShutdown := True;

  lstVazamento := TStringList.Create;
  try
    lstVazamento.Add('Linha 1');
    lstVazamento.Add('Linha 2');
    lstVazamento.Add('Linha 3');
  finally
    lstVazamento.Free;
  end;
end;

Utilizando o bloco try…finally…end; mesmo que ocorra algum erro na execução do procedimento o objeto é destruído. Pelo método não apropriado lstVazamento não seria destruído caso ocorresse algum problema na execução da linha lstVazamento.Add(‘Linha 1′);, por exemplo.

Em tempo: utilizando ReportMemoryLeaksOnShutdown := True; a mensagem ocorrerá sempre que o aplicativo for terminado, inclusive fora do Delphi. Para evitarmos esse situação, substitua o True por DebugHook <> 0; deixando a linha assim:

ReportMemoryLeaksOnShutdown := DebugHook <> 0;

Para quem utiliza outras versões do Delphi isto deve ser configurado no arquivo FastMM4Options.inc.

No próximo artigo abordaremos mais funções do FastMM e configurações do arquivo FastMM4Options.inc para quem utiliza FastMM em versões mais antigas do Delphi. Até mais!

Novidades em Banco de Dados no Delphi 2007 for Win32

Escrito em 21 de fevereiro de 2007 em Bancos de Dados por Leonel Togniolli

A CodeGear anunciou o Delphi 2007 for Win32 para ser lançado em março, também conhecido como Spacely. Uma das novidades interessantes é a atualização do DbExpress, chamado de DBX4. O Steve Shaughnessy, líder da equipe de desenvolvimento de banco de dados para o Delphi, escreveu um post sobre essas novidades. Como também participei do desenvolvimento de alguns desses novos recursos, também quero falar um pouco sobre essas mudanças.

Componentes vs API

Ao pensarmos em DbExpress, pensamos primeiramente nos componentes de acesso à banco de dados. O TSqlConnection, TSqlQuery, e afins. É fácil de entender isso, pois até agora não existia uma API definida para utilizar facilmente o DBX sem ser através esses componentes. O que existia era uma interface COM exposta por drivers em DLLs, escritos em C++, que não era nem um pouco fácil de ser usada diretamente, e nem de escrever novos drivers. Isso afetava também o deployment de aplicações DbExpress, que precisavam levar junto as DLLs de cada driver que fosse eventualmente ser utilizado.

No DBX4 foi criada uma API definida, escrita em Delphi, com o mesmo código fonte para código nativo ou gerenciado, que pode ser utilizada diretamente na sua aplicação, ou indiretamente através dos componentes como antigamente, sem alteração alguma. Pra quem for recompilar sua aplicação em no Delphi 2007 for Win32, não vai ver nenhuma alteração na superfície. Quem for escrever uma aplicação nova, também não vai ver diferenças de imediato. Por baixo dos componentes, porém, começam as vantagens.

Drivers escritos em Delphi

Drivers podem agora ser escritos em Delphi, extendendo algumas classes diretamente. Não é mais necessário ter drivers em DLLs externas e ter que enviá-los com a sua aplicação. Drivers que forem escritos em Delphi podem simplesmente ser adicionados no uses da sua aplicação e compilados automaticamente. Se preferir carregar os drivers dinamicamente, você pode usar runtime packages e usar um LoadPackage() para carregar a BPL com o driver que quiser.

E os drivers para DBX3 que já tenho em DLLs? Eles continuam funcionando, através de um driver chamado Dynalink que carrega as DLLs e serve de ponte entre elas e a nova API, de forma transparente. Nessa primeira versão ainda estaremos distribuindo os oito drivers em DLLs, como na versão anterior, mas com melhorias e correções de problemas neles.

Delegate Drivers

O DBX também suporta o que são chamados de Delegate Drivers. Esses drivers podem ser usados no pré e pós processamento de qualquer método ou propriedade de um driver, e podem ser encadeados um após o outro. Delegate Drivers fazem alguma ação e delegam o comando para o driver real.

Dois Delegate Drivers que foram criados, que serão incluidos com todo o código fonte escrito em Delphi, são o TDBXTrace e TDBXPool. O TDbxPool dá a capacidade de pooling de conexões para o DBX, evitando a espera de criação e abertura de cada conexão, particularmente útil pra quem faz diversas conexões com o banco de dados, como em aplicações web.

Já o TDbxTrace permite monitorar todos os comandos que estão sendo passados para o driver (como o Sql Monitor), e, o mais interessante, gera código Delphi descrevendo todas as ações executadas (sendo possível compilar o código gerado e rodá-lo para repetir todas essas ações!). Um exemplo de saída desse driver dado pelo Steve:

{CONNECT        } Connection2.Open;
{COMMAND        } Command2_1 := Connection2.CreateCommand;
{COMMAND        } Command2_1.CommandType := Dbx.SQL;
{COMMAND        } Command2_1.Text := 'SELECT * FROM BASICDML order by F_VARCHAR';
{PREPARE        } Command2_1.Prepare;
{COMMAND        } Reader2_1_1 := Command2_1.ExecuteQuery;
{COMMAND        } Command2_2 := Connection2.CreateCommand;
{COMMAND        } Command2_2.CommandType := Dbx.MetaData;
{COMMAND        } Command2_2.Text := 'GetIndexes "workerbee:c:\database\employee.gdb"."sysdba"."BASICDML" ';
{COMMAND        } Reader2_2_1 := Command2_2.ExecuteQuery;
{COMMAND        } FreeAndNil(Command2_2);

Um código assim é bem mais fácil de acompanhar do que um log de baixo nível dado por um SQL Monitor. Outro ponto interessante é o fato desse monitoramento não estar fixo no framework – a flexibilidade: se o formato não for adequado para você, basta escrever um novo Delegate Driver, possivelmente partindo do código fonte deste já existente.

Delegate Drivers abrem possibilidades de diversos usos, como replicação, por exemplo. Quero ver o que a comunidade vai criar em cima disso.

Extended Commands

A saída do TDbxTrace dada como exemplo acima acaba apontando mais um novo recurso – os Comandos Extendidos. Anteriormente os CommandTypes aceitos por um driver eram restritos à uma query, stored procedure, ou uma table. Vários dados podem ser requisitados passando algumas parâmetros e representados em um resultset. Agora os drivers pode definir novos tipos de comandos extendidos que funcionam como queries, mas retornam esses dados. Veja que o log acima aponta o CommandType como sendo Dbx.MetaData, e usa uma “query” GetIndexes para retornar os indices de uma tabela.

DBX4

Esses são algumas das novidades da área de banco de dados com o DBX4 no Delphi 2007 for Win32, que acredito que vão cada vez mais permitir que novas e melhores coisas sejam feitas no futuro. Além disso estamos corrigindo bugs, melhorando o produto e trabalhando em coisas que ainda não estão prontas para serem divulgadas. Fique ligado para mais novidades.