Você Sabia?

O Delphi que você (talvez) não conhecia

Você certamente deve ter um amigo, ou mesmo um parente, que conhece há muito tempo (dez anos ou mais) e que, mesmo convivendo freqüentemente com ele, às vezes se surpreende ao descobrir alguma característica ou história “do arco da velha”. Você pensa consigo mesmo: “Mas esse é o Fulano que eu conheço?”

Pois é... Todos nós, leitores da ClubeDelphi, temos um grande amigo em comum chamado Delphi, que já completou seus dez anos e ainda terá uma vida longa pela frente. Entretanto, nos recônditos mais escuros das diversas camadas que compõem o produto, podemos encontrar relíquias antiqüíssimas, que remontam à época pré-Windowsiana, onde reinavam os terríveis monstros verdes (não, não são dinossauros, mas os monitores CGA com a tela de fósforo verde).

Prepare-se, pois a partir deste artigo você vai conhecer alguns dos pontos mais obscuros do Delphi. Você nunca mais verá seu companheiro da mesma forma!

Nos tempos da linha de comando

O que é conhecido hoje como o “prompt de comando” , ativado no Windows NT e XP ao executar o arquivo cmd.exe (ou mesmo o antigo command.com, este também disponível nos Windows 9x), é um “emulador” do ambiente MS-DOS, que foi durante muitos anos a plataforma de trabalho de muita gente por aí (alô Clippeiros!).

Tudo era feito a partir da linha de comando: a passagem de parâmetros, a construção da interface (geralmente em modo texto), a interação com o usuário (por exemplo, pegar o código da tecla pressionada), a entrada e saída de dados, a exibição de erros etc. Alguém perguntou sobre o mouse? Isso seria coisa do futuro ainda. Calma que chegaremos lá.

Bom, comecemos pelo começo então. Quando executamos um programa, a primeira forma de interagir com ele é pela passagem de parâmetros. Mas o que são parâmetros para um programa? Certamente você sabe muito bem como passar parâmetros para um procedimento ou função, mas para um programa?

O esquema a seguir ilustra como o sistema operacional (S.O.) entende a linha de comando ao ativar um programa:

 

Figura 1.

O nome do programa executável é o parâmetro número zero e é o único obrigatório (claro, senão como o S.O. saberia o que executar, né?). O resto fica a critério do usuário.

Desde os tempos mais remotos o Turbo Pascal já fornecia uma forma bastante elegante e prática para acessar os parâmetros passados na linha de comando, através das funções ParamCount (que retorna o número de parâmetros) e ParamStr(N) (que retorna o parâmetro N).

 

Curiosidade: se os parâmetros são separados por espaços, como passar um parâmetro que precisa conter espaços? Hoje em dia é só usar as aspas (?) para delimitar o texto desejado. Mas naquela época... Tínhamos que fazer isso “na unha”...

 

A Listagem 1 mostra uma aplicação do tipo console no Delphi, mas que era o único tipo de aplicação que podíamos fazer no Turbo Pascal. Você pode testar esse exemplo em qualquer versão do Delphi (incluindo .NET) ou do Turbo (visite o museu da Borland para baixar algumas versões e se divertir).

 

Listagem 1. Trabalhando com parâmetros da linha de comando

program SuperParam;

 

{$APPTYPE CONSOLE}

 

var

  p: Integer;

 

begin

  WriteLn('Super-Param V. 1.0');

  WriteLn;

  if ParamCount = 0 then

    WriteLn('Nenhum parametro digitado.')

  else

  begin

    WriteLn('Numero de parametros: ', ParamCount);

    WriteLn;

    for p := 0 to ParamCount do

      WriteLn('Parametro ', p, ': ', ParamStr(p));

  end;

  ReadLn; // só necessário se for executar dentro do IDE

end.

 

Para brincar com o programa depois de compilado, abra um prompt de comando, mude o diretório atual para aquele onde o executável foi gerado e escreva apenas o nome do arquivo. A resposta deverá ser:

 

C:\...>SuperParam

Super-Param V. 1.0

 

Nenhum parametro digitado.

 

Agora execute-o com alguns parâmetros na frente, como por exemplo:

 

C:\...>SuperParam 123 /n:7 “Oi, tudo bem?”

Super-Param V. 1.0

 

Numero de parametros: 3

 

Parametro 0: C:\...\SuperParam.exe

Parametro 1: 123

Parametro 2: /n:7

Parametro 3: Oi, tudo bem?

 

Como disse, o parâmetro zero é o caminho completo para o arquivo executável (o que é bastante útil) e o parâmetro digitado entre aspas aparece inteiro (mas note que as aspas são eliminadas da string). Interessante: na VCL, o código para a propriedade ExeName da classe TApplication (na unit Forms) é:

 

function TApplication.GetExeName: string;

begin

  Result := ParamStr(0);

end;

 

Para executar a aplicação dentro do IDE, utilize o menu Run | Parameters... para poder especificar os parâmetros a serem passados para ela. Também é importante deixar como última linha do programa o comando ReadLn (conforme comentário na Listagem 1), senão você não verá a tela de saída. Para terminar o programa basta pressionar a tecla Enter.

Você pode trabalhar com parâmetros nas aplicações Windows “normais”. As funções são exatamente as mesmas.

Se quiser acessar a linha de comando inteira, tal como digitada pelo usuário, acesse a variável CmdLine (definida na unit System como do tipo PAnsiChar, mas que pode ser manipulada como uma string).

Canais de entrada e saída

Uma das formas de comunicação entre programas é através da utilização de certos “arquivos” especiais, chamados de padrões para entrada e saída (standard input and output). Através de um mecanismo conhecido como tubo (pipe, simbolizado pela barra vertical | ) podemos direcionar a saída de um programa para a entrada de outro.

Usando as variáveis Input e Output, definidas na unit System como sendo do tipo Text, podemos ler e escrever, respectivamente, nos arquivos padrões. A Listagem 2 mostra um pequeno exemplo.

 

Listagem 2. Arquivos padrões para entrada e saída

program SuperPipe;

 

{$APPTYPE CONSOLE}

 

var

  Linha: string;

 

begin

  WriteLn(Output, 'Super-Pipe V. 1.0');

  WriteLn(Output);

  WriteLn(Output, 'Inicio da entrada');

  WriteLn(Output, '-----------------');

  while not EOF(Input) do

  begin

    ReadLn(Input, Linha);

    WriteLn(Output, Linha);

  end;

  WriteLn(Output, '-----------------');

  WriteLn(Output, 'Fim da entrada');

end.

 

Para testar, abra um prompt de comando, mude para o diretório do executável e digite a seguinte linha (após o prompt, claro):

 

C:\...>dir | SuperPipe

Super-Pipe V. 1.0

 

Inicio da entrada

-----------------

 Volume in drive C is Adail.

 Volume Serial Number is 38B1-C70C

 

 Directory of C:\...

 

04/12/2005  00:57    <DIR>          .

04/12/2005  00:57    <DIR>          ..

04/12/2005  00:45    <DIR>          ModelSupport_SuperPipe

04/12/2005  00:57             8.370 SuperPipe.bdsproj

04/12/2005  00:57               383 SuperPipe.bdsproj.local

04/12/2005  00:57               494 SuperPipe.cfg

04/12/2005  00:57               404 SuperPipe.dpr

04/12/2005  00:57            19.968 SuperPipe.exe

               5 File(s)         29.619 bytes

               3 Dir(s)  28.282.093.568 bytes free

-----------------

Fim da entrada

 

A saída do comando dir foi direcionada para a entrada do nosso programa. Legal, né?!

Se você digitar apenas o nome do programa (sem usar o pipe antes), a entrada padrão será o teclado. Nesse caso, para dizer que o “arquivo” chegou ao fim, tecle Ctrl+Z (o caracter ASCII correspondente ao EOF) e Enter. Veja o exemplo abaixo:

 

C:\...>SuperPipe

Super-Pipe V. 1.0

 

Inicio da entrada

-----------------

Batatinha quando nasce

Batatinha quando nasce

Se esparrama pelo chao

Se esparrama pelo chao

^Z

-----------------

Fim da entrada

 

As linhas aparecerão duplicadas, pois a primeira linha é a que você digitou e a segunda é a repetição que o programa faz.

Agora repita o exemplo acima, porém executando o programa assim:

 

C:\...>SuperPipe > saida.txt

Batatinha quando nasce

Se esparrama pelo chao

^Z

 

Com isso estamos dizendo que a saída padrão não mais será a tela, mas um arquivo chamado saida.txt. O operador > provoca o redirecionamento da saída padrão para um arquivo. Se esse arquivo já existir, seu conteúdo será apagado. Para manter o conteúdo anterior e apenas concatenar o novo conteúdo, utilize o operador >>.

Ao indicar o fim de arquivo (Ctrl+Z e Enter), o prompt retornará. Execute o comando dir e note que agora existe um arquivo chamado saida.txt no mesmo diretório. Digite o comando type saida.txt e observe o conteúdo do arquivo:

 

C:\...>type saida.txt

Super-Pipe V. 1.0

 

Inicio da entrada

-----------------

Batatinha quando nasce

Se esparrama pelo chao

-----------------

Fim da entrada

 

Podemos fazer algo mais útil do que isso, claro, como por exemplo, ordenar alfabeticamente as linhas, filtrar por algum critério, enviar a entrada por e-mail ou gravar num banco de dados. Use a criatividade!

Existe ainda uma terceira variável, chamada de ErrOutput, que permite acessar a saída padrão para mensagens de erro.

Nota: as variáveis Input, Output e ErrOutput só estão disponíveis em aplicações do tipo console. Acessá-las em aplicações Windows gerará uma exceção!

Compilador de linha de comando

Já que estamos falando do prompt de comando, você sabia que há uma versão do compilador Delphi para ser chamada no modo console? (OK, se sabia então pode pular para o próximo tópico...)

Na pasta Bin da instalação do Delphi você encontrará um arquivo chamado DCC32.EXE. Ao executá-lo aparecerá a versão do compilador e uma descrição da sintaxe e das opções. Por exemplo, para o Delphi 2006:

 

C:\Program Files\Borland\BDS\4.0\Bin>dcc32

Borland Delphi for Win32 compiler version 18.0

Copyright (c) 1983,2005 Borland Software Corporation

 

Syntax: dcc32 [options] filename [options]

 

Quem nunca usou esta versão do compilador deve estar pensando: para que existe isso, se no IDE é muito mais rápido e fácil compilar um programa? Minha resposta é outra pergunta: quem nasceu primeiro: o compilador ou o IDE?

O compilador, claro! O IDE é uma aplicação feita na própria linguagem Delphi, que teve que ser compilada como qualquer outra aplicação. Uma vez pronto o núcleo do compilador, a equipe desenvolveu a biblioteca de componentes (a VCL) e com ela criou a aplicação que usamos, o conhecido IDE, que, entre outras funções, também compila nossas aplicações.

E quando é que você usaria o compilador de linha de comando? Quando não puder (ou não quiser) usar o IDE. Se você usa outro editor de código, quando quiser compilar deverá chamar o DCC32. Numa organização que possui um processo de gerência de configuração bastante controlado, geralmente o código final que vai para o cliente é compilado de forma automática num ambiente separado. Os arquivos são baixados do repositório (do StarTeam, por exemplo) e a compilação é disparada automaticamente. Se forem encontrados erros, alguém é avisado.

Antigamente, nas versões Turbo Pascal, devido às restrições de memória, era necessário compilar pela linha de comando, se o código fonte fosse muito grande. O fato de não ter o IDE carregado na memória liberava muito espaço para o compilador executar seu trabalho. Hoje isso não é mais um problema.

Para compilar um de nossos exemplos acima, abra o prompt de comando, altere o diretório atual para o do exemplo desejado e digite dcc32 nome_do_arquivo_dpr (o diretório Bin da instalação do Delphi geralmente consta na variável de ambiente PATH; se não estiver, você terá que digitar o caminho completo para o arquivo dcc32.exe).

 

C:\...>dcc32 SuperParam

Borland Delphi for Win32 compiler version 18.0

Copyright (c) 1983,2005 Borland Software Corporation

SuperParam.dpr(22)

23 lines, 0.28 seconds, 13432 bytes code, 12168 bytes data.

 

Caso haja algum erro, o compilador terminará com um código de saída maior que zero, que pode ser monitorado pela variável ErrorLevel dentro de um arquivo .BAT, por exemplo, ou pelo código de retorno do processo, se for chamado por outro programa executável. A Listagem 3 demonstra a execução do compilador dentro de um arquivo batch. Criei um arquivo chamado CompDelphi.bat dentro do diretório Bin da instalação do Delphi.

 

Listagem 3. Arquivo batch para compilação

@echo off

echo Batch para compilacao com Delphi

echo.

dcc32 %1 %2 %3 %4 %5 %6 %7 %8 %9

if ErrorLevel 1 goto ERRO

echo.

echo Compilacao OK

goto FIM

 

:ERRO

echo.

echo Erro na compilacao!

 

:FIM

 

Veja a seguir o resultado da utilização do arquivo batch, sendo que introduzi um erro proposital no código fonte:

 

C:\...>CompDelphi SuperParam

Batch para compilacao com Delphi

 

Borland Delphi for Win32 compiler version 18.0

Copyright (c) 1983,2005 Borland Software Corporation

SuperParam.dpr(8) Error: E2029 ';' expected but 'BEGIN' found

SuperParam.dpr(22)

 

Erro na compilacao!

 

Para uma utilização mais “séria” você pode incluir outras ações se algo der errado, como disparar e-mails, criar uma solicitação no StarTeam, soar uma sirene etc.

Programando em Assembler

Assembler? A esta altura do campeonato? Pra quê?

Calma... Lembre-se que estamos a, pelo menos, 15 anos atrás, onde convivíamos com os processadores Intel 8086/8088 (PC-XT) e 80286 (PC-AT), cujos relógios funcionavam às estonteantes freqüências na faixa de 8 a 20 MHz (compare com seu P4 a 2 GHz). Cada ciclo de máquina economizado poderia significar a diferença entre um desempenho medíocre e um excepcional!

Mesmo sendo o compilador Pascal da Borland extremamente eficiente em sua geração de código de máquina, não podemos esperar o mesmo desempenho de um bom código escrito em assembler nativo.

Para permitir que o programador aproveite as facilidades que o Pascal oferece e ainda conseguir uma velocidade máxima, foi adicionada a funcionalidade conhecida como Inline Assembler, que possibilita a escrita de trechos, ou mesmo funções inteiras, usando as instruções nativas (mnemônicos) do processador Intel.

Vamos a um exemplo bem simples: adicionar dois números. Na Listagem 4 temos duas formas para a mesma função: uma em Pascal e uma usando inline assembler. Note o uso da palavra asm no lugar de begin para iniciar o bloco em assembler.

 

Listagem 4. Programando com inline assembler

program Assembler;

 

{$APPTYPE CONSOLE}

 

function SomaPascal(X, Y: Integer): Integer;

begin

  Result := X + Y;

end;

 

function SomaAssembler(X, Y: Integer): Integer;

asm

  MOV EAX, X

  ADD EAX, Y

end;

 

begin

  WriteLn(SomaPascal(123, 321));

  WriteLn(SomaAssembler(123, 321));

  ReadLn;

end.

 

Execute o programa para verificar se está tudo certo. Devem aparecer duas linhas na tela com o número 444. Legal, mas o que está acontecendo aqui? E afinal de contas, que coisa é esse tal de assembler? (De novo... se você já sabe, JMP Conclusão).

Cada processador possui uma linguagem própria, comumente chamada de linguagem de máquina (machine language), que é formada por instruções de baixíssimo nível, que usam registradores, acessam a memória, fazem operações aritméticas simples, desviam a execução para outro ponto etc. Essa linguagem, apesar de simples, exige muito esforço do programador, que escreve o código usando mnemônicos de umas poucas letras para identificar as instruções, como no caso acima (MOV para mover alguma coisa para algum lugar, ADD para adicionar operandos, JMP para pular (jump) a execução para outro lugar).

 

Nota de esclarecimento: a linguagem de máquina na forma de mnemônicos é comumente chamada de linguagem Assembler. Entretanto, a denominação mais apropriada é linguagem Assembly, que em inglês significa “montagem”, enquanto que assembler significa “montador”. O programa responsável por montar o código escrito em Assembly é que é o assembler, tal como o épico Turbo Assembler, da Borland.

 

A função de um compilador, como o Delphi, é transformar o código fonte, escrito em uma linguagem de alto nível, como Pascal, em código objeto (linguagem de máquina), no caso para os processadores Intel de 32 bits.

Para vermos o resultado do trabalho do compilador, coloque um breakpoint na linha do primeiro WriteLn da Listagem 4 e execute-o (F9). O IDE irá parar a execução exatamente nesta linha. Agora abra a ilustre desconhecida janela CPU (menu View | Debug Windows | CPU). Você deve ver algo parecido com a Figura 2 (estou usando o Delphi 2006). Na grande área à esquerda está o código compilado, juntamente com o fonte em Delphi. Os painéis no canto superior direito exibem os valores atuais dos registradores, sendo que o painel mais à direita exibe cada bit do registrador de flags (EFL).

 

Figura 2. A janela CPU

Depois de compilado, seu código real agora é o que está logo abaixo da linha com o código em Pascal. Você pode depurar normalmente, com F8 (Step Over) e F7 (Trace Into). Vamos, experimente! Ao chegar à chamada da função SomaPascal, use o F7 para ver o código dela. Faça o mesmo para ver o código da função SomaAssembler. A Figura 3 mostra o código compilado das duas funções.

 

Figura 3. Comparando o código das duas funções

Mesmo que você nunca tenha visto um código em Assembly antes, procure comparar com calma esses dois trechos. Note que são quase idênticos. Mas é claro que a função SomaPascal é muito mais intuitiva do que a SomaAssembler.

Uma grande vantagem de se programar em Assembly dentro do Delphi é a facilidade no acesso a variáveis, parâmetros e constantes, bem como na chamada de funções e procedimentos, através de seus nomes em Pascal, sem precisar referenciar registradores ou posições de memória. Essa tradução é feita automaticamente pelo inline assembler.

Se você abrir o código fonte de várias units do Delphi Win32 (no sub-diretório Source da instalação), principalmente as da RTL (RunTime Library), verá que muitas são feitas inteiramente ou contêm trechos em Assembly. E ainda tem gente que não entende porque um programa compilado em Delphi é tão rápido...

Para saber mais procure o tópico inline assembler ou asm na ajuda do Delphi. Lembre-se que essa funcionalidade somente está disponível na personalidade Delphi Win32.

Existem ótimos sites que ensinam a programar em Assembly. Eu recomendo esse exercício, mesmo para os mais “feras” em Delphi.

Conclusões

Para alguns (com mais tempo de casa) talvez essas coisas não sejam novidades, mas sempre é divertido lembrar! Aos mais novos, entretanto, sugiro que experimentem essas “relíquias da antiguidade”. Bons tempos aqueles... Divirtam-se e até a próxima!

 

Link

bdn.borland.com/museum

Museu da Borland, onde podem ser baixadas verdadeiras relíquias como o Turbo Pascal 1.0, 3.02 e 5.5.