Os primeiros passos (catastróficos)
As primeiras experiências com assembler, delphi e windows

Então você resolveu que vai mesmo desafiar os deuses da programação e bulir com a mais mortal das ferramentas de programação: o assembler. Também já decidiu que irá fazê-lo de dentro da sua versão do Delphi, quer os entendidos gostem ou não dele. Ok, bem vindo ao mais fechado e restrito clube de criação do mundo: aquele onde as pessoas realmente entendem o que estão fazendo. Vamos lá...

Eu disse anteriormente que os mnemômicos indicam (ou tentam indicar) o que a instrução representa. Como usaremos eles, não precisamos nos preocupar com seus respectivos códigos numéricos, mas com o que eles fazem. No menu de páginas temos um para uma tabela que lista as principais instruções e o que elas fazem.

Uma vez que tudo no mundo moderno da programação tem a ver com visual, vamos começar por aí mesmo. Também já vimos que uma das formas mais eficientes de lidar com animações (e os jogos são basicamente animações) é trabalhar com um buffer de conteúdo de tela que, somente em determinados momentos, é enviado para o vídeo.

Podemos então estabelecer o seguinte: o fundo do formulário (form1) será nossa "tela de jogo" e criaremos um TBitmap de tamanho idêntico (seja ele qual for) para servir como back buffer de tela. Ou seja, vamos plotar, pokear, escrever e pintar o sete e o dezessete no TBitmap e quando for o caso, transferimos o conteúdo para o form1. Neste esquema, o "tamanho" da tela do jogo será o tamanho que você, programador, der ao formulário em tempo de desenvolvimento.

Primeiro, no evento OnCreate estabelecemos os parâmetros iniciais:

var
Tela: TBitmap;
Difr, Init: dword;
procedure TForm1.FormCreate(Sender: TObject);
begin
  Tela:= TBitmap.Create;
  Tela.Width:= ClientWidth;
  Tela.Height:= ClientHeight;
  Tela.PixelFormat:= pf32bit;
  Tela.Canvas.FillRect(bounds(0,0,Tela.Width-1,
                                  Tela.Height-1));
  Init:= integer(Tela.ScanLine[0]);
  Difr:= Init-integer(Tela.ScanLine[1]);
end;

Definimos Tela com 32 bits de profundidade de cor apenas para que fique mais fácil trabalhar no assembler (os registradores tem 32 bits). Criamos duas variáveis de 32 bits, para guardar o endereço do início, na memória, da linha 0 da imagem. Uma vez que iremos atuar diretamente nos endereços, temos que saber duas coisas: onde o TBitmap é criado e qual a distância entre uma linha e outra, em bytes.

Ora, perguntaria o leitor mais atento: temos a largura da imagem para nos dar essa "distância". Não é bem assim, cara pálida. O sistema procura sempre organizar as coisas de tal forma a facilitar as operações, então, um número impar de pixels na largura pode criar problemas para o processador lidar com endereços quebrados. Fazemos o cálculo da forma como indiquei apenas para nos certificarmos de que estamos mesmo usando as "distâncias" corretas.

Mas, mas, mas... o cálculo deveria ser endereço da linha 1 menos o endereço da linha 0. Não é assim por uma simples razão: a imagem bitmap (bmp) é colocada na memória do micro de cabeça para baixo. Na verdade o conceito é um pouco mais sofisticado do que isso: os endereços decrescem na medida em que avançamos nas linhas. Não é bem a imagem que está de cabeça para baixo, mas os endereços que são tratados na ordem inversa.

Feito isso, não devemos nos esquecer de arrumar a casa, quando o ilustre usuário terminar o uso do seu programa:

procedure TForm1.FormDestroy(Sender: TObject);
begin
Tela.Free;
end;

Para fazer um refresh de imagem, ou seja, mandar o conteúdo do buffer Tela para o form1, basta usar a função Draw:

  Canvas.Draw(0,0,Tela);

Vamos agora desenhar uma linha horizontal, a partir da coordenada 0,0 (canto superior esquerdo) com 200 pixels de largura e em vermelho para destacar do fundo branco. Coloque a programação, por exemplo, no evento OnClick, do form1:

procedure TForm1.FormClick(Sender: TObject);
begin
asm

push ebx //preserva ebx
mov ebx,Init //início da tela
mov ecx,200 //tamanho da linha
mov eax,00FF0000h //cor do pixel
@Lp1:
mov [ebx],eax //coloca o pixel
add ebx,4 //salta 4 bytes
loop @Lp1 //200 vezes até ecx = 0
pop ebx //restaura ebx
end;
Canvas.Draw(0,0,Tela);
end;

Para entender o que está acontecendo, vamos analisar linha a linha:

    push ebx             //preserva ebx

Para usar o assembler dentro do Delphi, temos que respeitar algumas regras. Uma das mais importantes diz o seguinte:

An asm statement must preserve the EDI, ESI, ESP, EBP, and EBX registers, but can freely modify the EAX, ECX, and EDX registers.

Ou seja, se pretendermos usar algum daqueles registradores indicados, temos que preservar o seu conteúdo original. PUSH e POP são instruções especiais que colocam e retiram os respectivos conteúdos (valores) numa pilha de dados chamada stack. Como se trata de uma pilha (dados empilhados um em cima do outro) é preciso tomar cuidado com a regra: o primeiro a entrar é o último a sair.

    mov  ebx,Init        //início da tela

Estamos colocando o endereço inicial da tela (linha 0) no regristrador ebx, pois é ele que usaremos como "apontador".

    mov  ecx,200         //tamanho da linha

O registrador recebe o valor 200, porque faremos 200 vezes a mesma operação (colocar os bytes/pixels uma a um no seu devido lugar). Por que usei ecx? Simples: esse registrado é uma espécie de contador automático e é sempre usado em operações repetitivas.

    mov  eax,00FF0000h   //cor do pixel

Essa é a cor vermelha, já apresentada com as suas três componentes RGB.

    mov [ebx],eax        //coloca o pixel

Aqui estamos colocando um pixel na posição de memória correspondente.

    add ebx,4            //salta 4 bytes

Saltamos 4 bytes (endereços) porque cada pixel é formado por essa quantidade de bytes (cor de 32 bits).

    loop @Lp1            //200 vezes até ecx = 0

Trata-se de uma operação automática que desvia a execução do programa para o ponto indicado no label (@Lp1), decrementando o regristrador ecx até que o mesmo fique com o valor zero, quando o desvio não é mais realizado.

    pop  ebx             //restaura ebx

Recupera o valor original do registrador ebx e segue em frente.

E se quiséssemos fazer a linha vertical, com 200 pixels de altura, partindo do ponto 0,0? Simples:

    mov  ebx,Init        //início da tela
mov ecx,200 //tamanho da linha
mov eax,00FF0000h //cor do pixel
@Lp2:
mov [ebx],eax //coloca o pixel
sub ebx,Difr //salta para a linha seguinte
loop @Lp2 //200 vezes até ecx = 0

A única diferença é que subtraímos o valor da diferença, em bytes, entre as linhas, do registrador ebx.

Mas atenção criançada: ao contrário das mordomias que tivemos até hoje, programando seguramente em Delphi, quando passamos para o asm estamos de certa forma meio órfãos de pai e mãe. Assim, se a nossa rotina asm for plotar uma linha que esteja fora dos limites da tela, vai dar um chabú legal. Na melhor das hipóteses vai ocorrer um erro operacional e parar o programa. Na pior, o micro trava e será necessário ressetá-lo.

A regra de ouro é: mantenha atenção redobrada em tudo que fizer, ou então insira códigos e testes na programação afim de evitar as situações limites.


Download...
Clique no link para fazer o download dos arquivos fonte tratados nessa página.

asm1.zip... (160 Kb) Fontes do project1.
 
online