Chamando funções de DLLs externas

Escrito em 29 de julho de 2007 em Delphi por Leonel Togniolli

Muitas vezes é necessário chamar código externo a uma aplicação, seja do sistema operacional, código de terceiros em DLLs, ou mesmo seu próprio código modularizado por diversos motivos. Fazer essas chamadas de uma aplicação Delphi é bem simples, mas mesmo assim é interessante conhecer alguns fundamentos:

Uma DLL não possui informação de metadados sobre seu conteúdo. Tudo que ela possui é uma tabela de símbolos exportados que lista os procedimentos dentro dela. Não existe informação sobre número de parâmetros, seus tipos ou propósitos,  valores de retorno, convenção de chamada ou qualquer coisa parecida. A única forma de acessar métodos de uma DLL qualquer é possuir documentação sobre ela, contendo explicitamente a assinatura de funções. Para APIs do Windows, isso é documentado pela Microsoft, enquanto DLLs de terceiros precisam de documentação que as acompanhem.

Mesmo que a DLL tenha sido escrita em outra linguagem, é perfeitamente possível usá-la em Delphi, convertendo a declaração para os tipos equivalentes. Tomando como exemplo uma função da API do Windows, GetLastInputInfo, que retorna o tempo decorrido que o computador está sem uso, podemos ver a declaração dela na MSDN:

typedef struct tagLASTINPUTINFO {
    UINT cbSize;
    DWORD dwTime;
} LASTINPUTINFO, *PLASTINPUTINFO;

BOOL GetLastInputInfo(
    PLASTINPUTINFO plii
);

E a conversão em windows.pas:

type
  PLastInputInfo = ^TLastInputInfo;
  {$EXTERNALSYM tagLASTINPUTINFO}
  tagLASTINPUTINFO = packed record
    cbSize: UINT;
    dwTime: DWORD;
  end;
  TLastInputInfo = tagLASTINPUTINFO;

{$EXTERNALSYM GetLastInputInfo}
function GetLastInputInfo(var plii: TLastInputInfo): BOOL; stdcall;

function GetLastInputInfo; external user32 name 'GetLastInputInfo';

Analizando esse código, podemos ver que a estrutura foi convertida em um packed record. Packed indica que o compilador não deve efetuar otimizações colocando os campos do record em posições alinhadas de memória. Essa não é uma otimização que o compilador C faz, e precisamos ter uma estrutura do mesmo tamanho. Em seguida vemos que o ponteiro sendo passado como parâmetro foi convertido para var, e foi adicionada a convenção de chamada stdcall, o padrão da API do Windows. Finalmente, em vez da implementação da função, uma declaração que ela é external, na user32.dll (user32 é uma constante contendo esse valor). Não se importe com as diretivas EXTERNALSYM – elas são para compatibilidade com C++Builder, indicando que eles já tem essa declaração em outro lugar.

Assim como a tabela de símbolos exportados que mencionei anteriormente, um executável ou DLL no windows possui uma tabela de símbolos importados de outros módulos. Ao utilizarmos GetLastInputInfo no nosso código, ou qualquer outra função declarada como external, o compilador adiciona uma entrada nessa tabela indicando que precisamos dessa função. Durante o carregamento do seu programa, o sistema operacional efetuará a ligação de todos os métodos na tabela de simbolos importados. Isso é chamado de ligação estática, e se qualquer um desses módulos não for encontrado, a sua aplicação não será carregada.

Como em alguns casos movemos código para uma DLL externa justamente para poder distribuí-la em apenas alguns casos, servindo como plugins, isso não é o comportamento esperado. Outros casos onde não queremos essa dependência é quando estamos chamando APIs do Windows que não se encontram em todas as versões. GetLastInputInfo é exatamente uma delas – a documentação indica que ela só existe a partir do Windows 2000. Se utilizarmos a ligação estática, nossa aplicação nem inicializaria no Windows 98, por exemplo.

A solução é utilizar ligação dinâmica – carregar a DLL desejada, verificar se ela possui o procedimento que desejamos e chamá-lo caso exista. Obviamente não é possível chamar um método que não existe no Windows 98 mesmo dessa forma, então é necessário prever esta condição no código.

Para isso, declaramos um tipo procedural, e uma variável desse tipo:

type
  TGetLastInputInfo = function(var plii: TLastInputInfo): BOOL; stdcall;
var
  GetLastInputInfo: TGetLastInputInfo;

A partir daí, basta carregar a user32.dll, verificar se a função desejada está presente, e chamá-la. O código que implementa esta lógica fica assim:

type
  TGetLastInputInfo = function(var plii: TLastInputInfo): BOOL; stdcall;
var
  GetLastInputInfo: TGetLastInputInfo;
  LastInputInfo: TLastInputInfo;
  User32dll: Cardinal;
begin
  User32dll := LoadLibrary(user32);
  GetLastInputInfo := GetProcAddress(User32dll, 'GetLastInputInfo');
  if Assigned(GetLastInputInfo) then
  begin
    GetLastInputInfo(LastInputInfo);
    // processar LastInputInfo
  end;
end;

Desta forma não temos uma ligação estática, podendo seguir um outro caminho em sistemas não suportados.

Se você é novo por aqui, não deixe de assinar o feed RSS ou notificações por email. Não perca novos artigos!

Guardando Métodos, Funcões e Procedimentos em Variáveis com Tipos Procedurais

Escrito em 28 de julho de 2007 em Linguagem Delphi por Leonel Togniolli

Tipos Procedurais é um recurso interessante da linguagem Delphi que permite armazenar métodos, funções e procedimentos em variáveis e manipulá-los normalmente, passando como parâmetros, e chamando a função quando for necessário.

A sintaxe é simples:

type
  TProcedimento = procedure;
  TFuncao = function:string;
  TFuncaoComParametro = function(Param: Integer):string;
  TMetodo = procedure of object;

As duas primeiras declarações definem um tipo que armazena um procedimento e uma função sem parâmetros, respectivamente. A terceira mostra como especificar parâmetros. A ultima declaração mostra como armazenar um método de uma classe, como TForm1.MeuMetodo, por exemplo. A partir daí, basta utilizar o método normalmente:

program Procedurais;

{$APPTYPE CONSOLE}

type
  TFuncao = function(Nome: string):string;

procedure ChamaProcedimento(Proc: TFuncao);
begin
  if Assigned(Proc) then
    WriteLn(Proc('teste'));
end;

function Duplica(Nome: string): string;
begin
  Result := Nome + Nome;
end;

function Triplica(Nome: string): string;
begin
  Result := Nome + Nome + Nome;
end;

begin
  ChamaProcedimento(Duplica);
  ChamaProcedimento(Triplica);
  ReadLn;
end.

Veja no exemplo que ChamaProcedimento chama a função que ele recebe por parâmetro e escreve no console o valor retornado. O programa principal chama esse procedimento passando as duas funções ali implementadas.

Vale lembrar que esses tipos são type-safe, ou seja, o compilador verifica se o procedimento que você está passando corresponde exatamente ao esperado, ou seja, possui os mesmos parâmetros, o mesmo tipo de retorno, a mesma convenção de chamada, e assim por diante.

Se isso faz lembrar os eventos de componentes da VCL, é porque é exatamente este o mecanismo usado.

Convenção de Chamadas de Procedimentos e Funções

Escrito em 27 de julho de 2007 em Delphi por Leonel Togniolli

A execução de um programa não é nada mais do que uma série de instruções geradas pelo compilador sendo executadas em sequência. Algumas delas desviam a execução para um trecho anterior ou posterior de um código, criando laços de repetição ou condicionais. Outras, desviam o fluxo para um trecho diferente de código, que é executado e retorna a execução para onde foi chamado.

Esse trecho diferente de código não é nada mais do que um procedimento que foi chamado. É interessante pensar como ele sabe para onde desviar a execução quando terminado, voltando para onde estava: o código chamador deve colocar na pilha (que nada mais é do que um trecho de memória reservado pelo processo) o endereço de retorno.

Só que um procedimento, para ter uma maior utilidade, pode aceitar parâmetros ou retornar valores. O código chamador deve ter uma forma de passar esses parâmetros para que o novo procedimento possa usá-los. Uma forma é colocá-los na pilha, da mesma forma foi feito com o endereço de retorno. Outra é passá-los nos registradores do processador (possivelmente de uma forma mais eficiente que a anterior). Tendo vários parâmetros, em que ordem são passados? Quem vai remover os valores da pilha, o procedimento chamado ou o chamador?

Tanto faz, desde que o código que está efetuando a chamada e o que está sendo chamado concordem com isso. Existem alguns padrões, e eles são chamados de “convenção de chamadas”. Eles diferem nos items citados acima, definindo a forma da chamada.

São convenções de chamada suportadas pelo Delphi: register, pascal, cdecl, stdcall e safecall. Para especificar a convenção de chamada de um procedimento qualquer, basta colocar a convenção desejada após a declaração da função:

procedure Teste(Parametro: Integer) ; stdcall;

A convenção padrão no Delphi, caso não seja especificada, é register, que como o nome sugere, passa quantos parâmetros forem possível em registradores e o resto na pilha, ao contrário da convenção geralmente padrão com compiladores C, cdecl, que utiliza a pilha exclusivamente. Não é necessário saber exatamente os detalhes de cada uma, podendo deixar isso para o compilador, desde que saibamos quando usar cada uma delas.

Não especificamos a convenção geralmente por falta de necessidade – a padrão é o suficiente para a maioria dos casos, e se o compilador está gerando o código que efetuará a chamada e o que será chamado, ele deve saber como passar os parâmetros. Conhecer a convenção de chamada é importante quando for necessário chamar código que é externo à sua aplicação – em uma DLL geralmente. Possívelmente escrita em outra linguagem. Só sabendo a convenção de chamada e a especificando corretamente você irá estar passando os parâmetros de uma forma que ela reconheça e retorne corretamente. Mas isso é assunto para outro artigo.

Desabilitando Procura na Web de Associação de Arquivos

Escrito em 27 de julho de 2007 em Windows por Leonel Togniolli

Um recurso do Windows XP que sempre me incomodou, bastante mas não o suficiente para fazer algo a respeito até agora, é o prompt que sugere fazer uma busca em um serviço da Microsoft para encontrar um programa para abrir um determinado arquivo, toda vez que tento abrir um arquivo com extensão desconhecida para o sistema operacional.

É bem provavel que eu saiba que tipo de arquivo estou tentando abrir, e sei que programa quero usar. Mas ainda assim o Windows insiste, toda vez, em sugerir que eu procure na Web uma aplicação compatível. Como essa opção é a padrão, já acidentalmente confirmei tal sugestão. E em nenhuma dessas vezes havia qualquer informação útil no serviço da Microsoft.

Se você é como eu e também sabe o que fazer com seus arquivos, aproveite essa oportunidade para desligar essa caixa de diálogo de uma vez por todas:

  1. Abra o regedit.
  2. Navegue para:
    HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System
  3. Crie um novo item, do tipo DWORD, chamado NoInternetOpenWith.
  4. Altere o conteúdo dele para 1.

E pronto. Nunca mais precisará de um passo a mais para abrir um arquivo com extensão desconhecida. (Até a próxima reinstalação do sistema operacional, pelo menos)

Para mais informações sobre este recurso tão útil, File Association Web Service na Technet.

Eliminando o ViewState de Páginas ASP.Net

Escrito em 19 de julho de 2007 em Asp.Net por Leonel Togniolli

Quando iniciei a explicação sobre o funcionamento do ViewState, mostrei que conforme a complexidade de uma página aumenta, a informação armazenada no ViewState cresce de forma correspondente. Se chegar o ponto que o ViewState está em um tamanho que prejudique o carregamento de páginas, e não existe mais nenhum controle que possa ter seu ViewState desabilidade, ainda há uma solução: É possível remover o ViewState completamente da página.

Como podemos remover o ViewState completamente e ainda permitir que os componentes mantenham seu estado? Simples, podemos gravar o ViewState em algum outro local – um banco de dados, ou talvez em algum outro recurso de persistência que o ASP.Net nos disponibilize. Neste caso, vamos utilizar o Cache.

Páginas ASP.Net possuem dois métodos que permitem o programador redirecionar o conteúdo que está no ViewState para outro lugar. Esses métodos são SavePageStateToPersistenceMedium e LoadPageStateFromPersistenceMedium. Partindo desses métodos, é bem simples imaginar o que deve ser feito: no primeiro salvamos os dados no Cache, e no segundo buscamos esses dados. Precisamos de uma chave única para os ViewStates de usuários diferentes e páginas diferentes não sejam misturados. Vamos utilizar uma GUID. A declaração desses métodos dentro da sua página fica assim:

strict protected
  function LoadPageStateFromPersistenceMedium: System.Object; override;
  procedure SavePageStateToPersistenceMedium(ViewState:
		System.Object); override;

E a implementação deles, assim:

const
  HiddenFieldViewState = 'ChaveViewState';

procedure TWebForm1.SavePageStateToPersistenceMedium(ViewState: TObject);
var
  ChaveViewState: string;
begin
  ChaveViewState := Guid.NewGuid.ToString;
  Cache.Add(ChaveViewState, ViewState, nil,
    System.DateTime.Now.AddMinutes(Session.Timeout),
	Cache.NoSlidingExpiration,
    	CacheItemPriority.Default, nil);
  RegisterHiddenField(HiddenFieldViewState, ChaveViewState);
end;

function TWebForm1.LoadPageStateFromPersistenceMedium: System.Object;
var
  ChaveViewState: string;
begin
  ChaveViewState := Request.Form[HiddenFieldViewState];
  Result := Cache[ChaveViewState];
end;

Veja como geramos um novo GUID, e salvamos ele em um campo oculto no formulário para podermos recuperar o ViewState depois.

Esta alteração tem uma vantagem óbvia: páginas ficam menores e mais rápidas de serem baixadas pelos visitantes da sua página. Algumas desvantagens aparecem, uma delas sendo o fato que toda essa informação está sendo gravada  na memória do servidor pelo tempo determinado pelo timeout da sessão. Por esse motivo, a informação que antes estava no ViewState e antes era válida mesmo que o usuário mantivesse a página aberta por dias e dias, e só então enviasse o formulário, agora tem uma data de expiração determinada.

O cache não é o único lugar para se manter o ViewState, mas o mais prático por ser esvaziado automaticamente após determinado tempo mesmo que o visitante feche o browser e nunca mais visite sua página. É fácil adaptar esse método para salvar informações em um banco de dados ou algum mecanismo parecido.

Redirecionando a saída de comandos DOS para área de transferência

Escrito em 13 de julho de 2007 em Utilitários por Zote

Pessoal, to um pouco sem tempo pra escrever, mas prometo que em breve voltarei a falar sobre ECO.

Ontem tava fazendo uma pesquisa e acabei encontrando este post e achei bastante útil para quem usa programas DOS e precisa do resultado em outro lugar. Para isto basta digitar o comando com | clip no final ex: dir /s | clip

Este executável não está disponível em Windows 2000 e XP mas basta copiar que funciona.

Abraço
Zote

Compilando projetos Delphi por linha de comando

Escrito em 06 de julho de 2007 em IDE por Jeferson Oliveira

Introdução

Neste artigo será demonstrada a utilização do compilador do Delphi por linha de comando: como acioná-lo manualmente, como automatizar a compilação de um projeto, e as diferentes possibilidades de configurações permitidas.

Para os exemplos apresentados aqui será utilizado o Delphi 7, mas as mesmas configurações poderão ser utilizadas sem muitas alterações em qualquer versão do Delphi.

O problema

A compilação de um projeto precisa ser um processo rápido e eficiente. O tempo gasto para abrir o IDE do Delphi, aguardar que ele esteja completamente carregado, abrir o projeto que precisa ser liberado, para só então dar início à compilação, será sempre maior do que o tempo realmente necessário pelo compilador para a geração do arquivo final.

A solução

Para simplificar essa tarefa, tornando-a mais ágil e confiável, temos a possibilidade de acionar o compilador por linha de comando. Esse processo, se corretamente configurado, produzirá o mesmo resultado que seria obtido ao compilar o projeto pelo IDE do Delphi.

O compilador

O compilador do Delphi é o arquivo dcc32.exe que fica localizado em $(DELPHI)\Bin. Para listar os parâmetros do compilador, basta digitar dcc32 no prompt de comando do Windows.

  • Nota: Se houver mais de uma versão do Delphi instalada no computador, digitar apenas dcc32 acionará a primeira versão do compilador encontrada na variável de ambiente “PATH” do Windows. Nesses casos, para acionar uma versão específica do compilador, indique o caminho completo do arquivo dcc32.exe.

Abaixo um exemplo das informações exibidas no prompt de comando ao executar o dcc32.exe sem informar qualquer parâmetro:

C:\\>dcc32
Borland Delphi Version 15.0
Copyright (c) 1983,2002 Borland Software Corporation

Syntax: dcc32 [options] filename [options]

  -A<unit>=<alias> = Set unit alias  -LU<package> = Use package
  -B = Build all units               -M = Make modified units
  -CC = Console target               -N<path> = DCU output directory
  -CG = GUI target                   -O<paths> = Object directories
  -D<syms> = Define conditionals     -P = look for 8.3 file names also
  -E<path> = EXE output directory    -Q = Quiet compile
  -F<offset> = Find error            -R<paths> = Resource directories
  -GD = Detailed map file            -U<paths> = Unit directories
  -GP = Map file with publics        -V = Debug information in EXE
  -GS = Map file with segments       -VR = Generate remote debug (RSM)
  -H = Output hint messages          -W = Output warning messages
  -I<paths> = Include directories    -Z = Output 'never build' DCPs
  -J = Generate .obj file            -$<dir> = Compiler directive
  -JP = Generate C++ .obj file       --help = Show this help screen
  -K<addr> = Set image base addr     --version = Show name and version
Compiler switches: -$<letter><state> (defaults are shown below)
  A8  Aligned record fields           P+  Open string params
  B-  Full boolean Evaluation         Q-  Integer overflow checking
  C+  Evaluate assertions at runtime  R-  Range checking
  D+  Debug information               T-  Typed @ operator
  G+  Use imported data references    U-  Pentium(tm)-safe divide
  H+  Use long strings by default     V+  Strict var-strings
  I+  I/O checking                    W-  Generate stack frames
  J-  Writeable structured consts     X+  Extended syntax
  L+  Local debug symbols             Y+  Symbol reference info
  M-  Runtime type info               Z1  Minimum size of enum types
  O+  Optimization

Todos os parâmetros do compilador devem ser precedidos por hífen (-) ou por barra (/).

Entre os diversos parâmetros existentes, os mais relevantes para nosso exemplo são:

B: Indica que deverão ser compiladas todas as units do projeto. É equivalente à opção “Build” disponível no menu “Projects” do Delphi;

H: Habilita (-H+) ou desabilita (-H-) a exibição de hints.

Q: será realizada uma compilação silenciosa, não emitindo informações sobre todos os arquivos processados pelo compilador. Hints, warnings e erros serão exibidos normalmente.

R: Lista de diretórios nos quais o compilador irá buscar por arquivos de recursos (.RC, .RES) utilizados no projeto;

U: Lista de diretórios nos quais o compilador irá procurar arquivos .PAS ou .DCU utilizados no projeto;

W: Habilita (-W+) ou desabilita (-W-) a exibição de mensagens do compilador. Para habilitar ou desabilitar um alerta específico, basta citar os alertas após o parâmetro, precedidos de + ou -. Exemplo: -W-UNIT_PLATFORM +UNIT_DEPRECATED

  • Nota: Nos parâmetros que indicam diretórios, caso mais de um diretório seja listado, eles deverão estar separados por ponto e vírgula (;).

Nosso projeto de exemplo

Vamos criar um novo projeto, que não fará nada além de produzir algumas situações que nos permitirão tirar proveito dos recursos do compilador. No Delphi 7 acesse File/New/Aplication. Adicione ao form um TFileListBox e um TButton. No evento OnClick do botão, defina o código abaixo: 

procedure TfrmPrincipal.btnFazNadaClick(Sender: TObject);
var
  i: Integer;
  s: string;
begin
  s := 'alo mamae';
  ShowMessage(s);
end;

Salve a unit como Principal.pas, salve o projeto como Compila.dpr e defina a propriedade Name do form como frmPrincipal.

Compilação manual

Vamos então começar o uso do compilador. Inicie o prompt de comando do Windows e digite o caminho do compilador seguido do caminho completo do arquivo DPR. Por exemplo:

C:\\>dcc32 T:\\ProjetoExemplo\\Compila.dpr

Ao executar esse comando será retornado um erro, indicando que o arquivo Principal.dcu não foi encontrado. Esse erro ocorre porque nenhum Principal.pas nem Principal.dcu foram encontrados nos diretórios conhecidos pelo dcc32. Uma forma simples de evitar esse problema é sempre iniciar a compilação a partir do diretório do projeto. Para isso basta alterar o diretório corrente antes de executar o compilador. Como exemplificado abaixo: 

C:\\>T:
T:\\>cd \\ProjetoExemplo
T:\\ProjetoExemplo>dcc32 Compila.dpr

Se tudo correu bem, nesse ponto a compilação foi executada sem erros, e o arquivo Compila.exe foi gerado corretamente. Porém, algumas mensagens foram exibidas pelo compilador:

Principal.pas(7) Warning: Unit 'FileCtrl' is specific to a platform
Principal.pas(29) Hint: Variable 'i' is declared but never used in TfrmPrincipal.btnFazNadaClick'

Os hints foram exibidos porque nas propriedades do projeto a opção “Show hints” está marcada, o que seria equivalente a passar o  parâmetro /H+. Os hints são importantes, pois indicam situações que podem ser melhoradas no nosso código. Afinal, se a variável “s” não é utilizada em nenhum ponto do código, para quê declará-la? É possível desabilitar a exibição de hints passando o parâmetro /H-, mas a melhor forma mesmo de evitar essas mensagens é fazendo os ajustes necessários no seu código. No nosso caso vamos então remover a declaração da variável i, que é desnecessária.

Mas, para quem desenvolve somente aplicações para Windows, qual a relevância do alerta que indica que a unit FileCtrl é específica dessa plataforma? Nenhuma. Nesse caso, para deixarmos o log enxuto, contendo apenas as mensagens realmente necessárias, podemos desabilitar esse warning listando-o no parâmetro W. Como o arquivo Principal.dcu já se encontra no diretório e nenhuma alteração foi realizada em Principal.pas ele não precisará ser compilado novamente. Para forçar a compilação de todos os arquivos do projeto vamos inserir também o parâmetro B, que indica ao compilador que deve ser realizada uma compilação de todos os arquivos. O compilador exibe informações sobre cada arquivo sendo compilado. Se desejar omitir essas informações acrescente o parâmetro Q. Ficamos então com o seguinte comando:

T:\\ProjetoExemplo>dcc32 -B Compila.dpr -Q -W-UNIT_PLATFORM

Nesse ponto a compilação deve ser executada sem qualquer dica ou alerta, ficando a saída parecida com o exemplo abaixo:

Borland Delphi Version 15.0
Copyright (c) 1983,2002 Borland Software Corporation
52 lines, 0.20 seconds, 358352 bytes code, 7749 bytes data.

Automatizando a compilação

Já conseguimos compilar nosso projeto invocando manualmente o compilador pelo prompt de comando. Mas, como o processo de compilação precisa ser repetido inúmeras vezes, a qualquer tempo, não seria conveniente a cada compilação digitarmos os comandos no prompt. Para automatizar esse processo, podemos criar um arquivo de script com os comandos necessários. Crie um novo arquivo .CMD e insira nele os comandos. Segue o conteúdo do meu arquivo Compila.cmd:

@echo off
set PRJ=Compila.dpr
set DRIVE_PRJ=T:
set DIR_PRJ=\\ProjetoExemplo
set DIR_DELPHI=c:\\Progra~1\\Borland\\Delphi7\\Bin
set COMP_MSGS=-UNIT_PLATFORM

%DRIVE_PRJ%
cd %DIR_PRJ%
%DIR_DELPHI%\\dcc32.exe -B %PRJ% -Q -W%COMP_MSGS%

Algumas variáveis foram criadas no arquivo para simplificar a manutenção dos comandos de compilação. Embora a definição dessas variáveis não seja indispensável para esse processo, seu uso é recomendável.

Agora temos uma forma automatizada de compilar nosso projeto, bastando para tanto executar o arquivo Compila.cmd, seja pelo prompt de comando, ou mesmo pelo Windows Explorer.

Definindo os diretórios de componentes

Se utilizássemos em nosso projeto componentes de terceiros, os diretórios onde se encontram seus arquivos de recursos e units precisariam ser informados para o compilador. Para isso são disponibilizados os parâmetros R e U.

Se, por exemplo, utilizássemos um TJvFileListBox, que é um componente da JVCL, bastaria indicar os diretórios da JVCL em nosso arquivo. Como no exemplo abaixo:

@echo off
set PRJ=Compila.dpr
set DRIVE_PRJ=T:
set DIR_PRJ=\\ProjetoExemplo
set DIR_DELPHI=c:\\Progra~1\\Borland\\Delphi7\\Bin
set COMP_MSGS=-UNIT_PLATFORM
set SEARCH_PATH=D:\\Delphi\\jcl\\lib\\d7;D:\\Delphi\\jvcl\\lib\\d7
set RES_PATH=D:\\Delphi\\jvcl\\resources

%DRIVE_PRJ%
cd %DIR_PRJ%
%DIR_DELPHI%\\dcc32.exe -U%SEARCH_PATH% -R%RES_PATH% -B %PRJ% -Q -W%COMP_MSGS%

Configurações por projeto

Em alguns projetos é comum utilizarmos vários componentes de terceiros, habilitarmos ou desabilitarmos mensagens do compilador, entre tantas outras configurações que podem ser alteradas com freqüência. Embora o nosso arquivo de script permita configurar todas essas opções atualizá-lo à cada mudança das configurações do projeto não será uma tarefa tão cômoda. Uma maneira mais simples de realizar essas configurações é através das opções do projeto (menu Project/Options). As configurações alteradas pelas opções do projeto serão salvas nos arquivos Compila.cfg e Compila.res, que serão automaticamente lidos pelo dcc32.exe.

  • Nota: ao utilizar configurações personalizadas por projeto, não se esqueça de adicionar os arquivos .CFG .RES ao controle de versão.

Vamos tirar proveito da facilidade das configurações visuais e organizar um pouco nosso projeto. Crie os diretórios bin e dcu dentro do diretório do projeto. Nesses diretórios serão colocados respectivamente o arquivo de saída do nosso projeto (.EXE, .DLL, etc.) e os arquivos .DCU. Para indicar ao compilador como usar esses diretórios acesse as opções do projeto. Na aba “Directories/Conditionals” preencha os campos “Output directory” e “Unit output directory”.

ProjectOptions

No campo “Search path” indique os diretórios dos componentes utilizados no projeto.

ProjectOptions_SearchPath 

Caso no projeto sejam utilizadas diretivas de compilação, bastaria indicá-las no campo “Conditional defines”.

Na aba “Compiler messages” certifique-se que as opções “Show hints” e “Show warnings” estejam selecionadas. Uma lista de warnings é exibida nessa aba, permitindo desabilitar aquelas que não são relevantes para o projeto.

ProjectOptions_CompMsgs

Após concluir essas configurações não será mais necessário indicar no nosso arquivo de script os diretórios de recuros e de units, nem mesmo indicar os alertas que deverão ser desabilitados, o que reduzirá o conteúdo do arquivo:

@echo off
set PRJ=Compila.dpr
set DRIVE_PRJ=T:
set DIR_PRJ=\\ProjetoExemplo
set DIR_DELPHI=c:\\Progra~1\\Borland\\Delphi7\\Bin

%DRIVE_PRJ%
cd %DIR_PRJ%
%DIR_DELPHI%\\dcc32.exe -B %PRJ% -Q

Configurações por máquina

Com a utilização do arquivo de configurações do projeto ganhamos a possibilidade de realizar uma edição visual das configurações da compilação.

Normalmente um build completo só é realizado na máquina de liberação de versão, o que fica a cargo do administrador do projeto, mas é possível que um projeto possua vários desenvolvedores, trabalhando cada um em sua máquina, ou mesmo trabalhando remotamente, em ambientes diferentes, e seja necessário ou desejado que todos possam utilizar as mesmas configurações de compilação nos diversos ambientes. Nesses casos, utilizar um único arquivo de configuração por projeto exigirá que em todas as máquinas os componentes estejam instalados nos mesmos diretórios, ou ao menos estejam em uma estrutura de diretórios que possa ser referenciada por caminhos relativos. O que pode ser um problema se no projeto trabalharem desenvolvedores free-lancers. Uma forma de contornar esse problema é referenciando os componentes por diretórios virtuais, criados pelo comando subst no prompt de comando do Windows. Mas, ainda assim, seria necessário que os mesmos diretórios virtuais fossem criados em todos os computadores. Outra maneira é utilizar configurações gerais por máquina.

Sempre que o compilador é acionado ele busca por configurações no arquivo dcc32.cfg que se encontra no mesmo diretório que o arquivo dcc32.exe, depois procura pelo arquivo dcc32.cfg no diretório do projeto e, finalmente, pelo arquivo NomeDoProjeto.cfg, no nosso caso Compila.cfg, no diretório do projeto. Todas as configurações são consideradas, o que significa que podemos fazer uso de mais de um arquivo ao mesmo tempo. Para o problema citado acima, onde cada máquina pode possuir configurações diferentes, os parâmetros referentes a diretórios componentes poderiam ser definidos em $(Delphi)\Bin\dcc32.cfg, pois podem variar por máquina, e as demais configurações que são gerais, como exibição de alertas, diretivas de compilação, e diretórios para geração de dcus e binários, poderiam ser mantidas no arquivo de configuração do projeto.

O dcc32.cfg pode também ser utilizado para definir configurações e diretórios que se apliquem a todos os projetos utilizados em um determinado ambiente. Fazendo uma analogia com o IDE do Delphi, os diretórios definidos no dcc32.cfg seriam equivalentes aqueles definidos no library path (Tools/Environment options/Library/Library path) tendo, portanto, visibilidade global, válida para todos os projetos; já os diretórios definidos em NomeDoProjeto.cfg são aqueles visíveis pelo Search path(Project/Options/Directories/Conditionals/Search path) e são específicas do projeto em questão.

O arquivo dcc32.cfg deve ser editado manualmente. Para facilitar pode-se copiar o conteúdo do arquivo de configurações do projeto, mas a cada alteração em diretórios ele precisará ser atualizado novamente.

Recursos não disponíveis

Um recurso do qual você pode sentir falta ao utilizar o compilador por linha de comando é o incremento automático do número da versão do arquivo. Ainda que essa opção tenha sido selecionada nas configurações do projeto, ela não terá efeito, pois não se trata de uma funcionalidade do compilador, mas sim do IDE do Delphi. Uma saída para continuar incrementando a versão do arquivo, seria desenvolver um utilitário com essa funcionalidade, e invocá-lo no arquivo de script, mas isso é assunto para outro artigo.

Considerações finais

Como vimos, a compilação de projetos por linha de comando é um recurso simples de ser utilizado que pode nos auxiliar muito na padronização do processo de compilação e liberação de versões, tornando essa tarefa mais ágil e confiável.

Caso precise de uma automação completa do processo de liberação de versão, vale a pena conhecer algumas das ferramentas interessantes disponíveis atualmente para essa finalidade, entre as quais tem destaque a comercial FinalBuilder, e as gratuitas Rake e MSBuild, sendo essa última a ferramenta padrão de build do Delphi2007.

Espero com esse artigo ter contribuído de alguma forma para a melhoria do seu processo de compilação.

Comentários, críticas e sugestões serão muito bem-vindos para que os próximos artigos possam ser aprimorados.