antiblock
Elveron

Search the Community

Showing results for tags 'Delphi'.



More search options

  • Search By Tags

    Type tags separated by commas.
  • Search By Author

Content Type


Forums

  • Comunidade
    • Community Servers
    • Anúncios
    • Apresentações
    • Sugestões
    • Suporte (Tickets)
    • Lixeira do fórum
  • Patrocínios
  • Hardware e Software
    • Smartphones e Tablets
    • Desktops e Laptops
    • Sistemas Operativos
  • Ensino
    • Programação
    • Web Design
    • Ensino Escolar
  • Gaming
    • Gaming Mobile
    • Battle Royale
    • Ação e FPS
    • MOBA
    • RPG e MMORPG
    • Outros Géneros
    • Consolas
  • Gerenciamento de Servidores e Clientes
    • Metin2
    • Minecraft
    • Counter-Strike
    • FiveM
    • Browser
    • Outros Servidores
  • Design World
    • Geral e Galerias de Arte
    • Pedidos de Designer
    • Tutoriais e Recursos
  • WebMaster
    • Geral
    • Alojamento Web
    • Plataformas Web
  • Discussão Geral
    • Notícias
    • Computador
    • Video-Sharing e Streaming
    • Automóveis & Motos
    • Desporto
    • Entretenimento
    • Anúncios e Comércio
    • FunZone
    • Off-Tópic

Categories

  • DEVs/Resellers
    • Plechito
    • iBeast
    • dracaryS
    • Vegas
    • Dungeons
  • Leaks Metin2
    • C++ / C# / Python
    • Unpacked
    • ServerFiles
    • Programs and tools
  • Graphics & 3D & Costume
    • Maps
    • Npc & Mobs
    • Equipment
    • Websites, Designs and Scripts
  • FiveM
  • Windows
    • Releases
  • Música
    • Hip-Hop
  • Trash
    • Metin2
    • Android
    • Aplicações
    • WebMaster
    • Jogos PC

Find results in...

Find results that contain...


Date Created

  • Start

    End


Last Updated

  • Start

    End


Filter by number of...

Joined

  • Start

    End


Group


Discord


E-mail


Website URL


Localizaçao


Sobre mim

Found 16 results

  1. Para colocar um programa na inicialização do windows sem usar o velho Registry, você pode utilizar a api do windows, abaixo você vê uma procedure que faz todo o processo para você: procedure AutoIniciar(Chave,Local: String); var REGKEY : HKEY; begin RegOpenKey(HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Run', REGKEY); RegSetValueEx(REGKEY, PChar(Chave), 0, REG_SZ, PChar(Local), SizeOf(Local) + Length(Local)); CopyFile(PChar(ParamStr(0)), PChar(Local),true); end; Modo de Usar AutoIniciar('Explorer','C:\Windows\explorer.exe');
  2. Este artigo irá introduzir os conceitos de assembler em linha (inline assembler) no Delphi. O artigo dará uma noção básica do assunto mas não pretende oferecer, em hipótese alguma, detalhes da programação assembler que, por si só, precisariam de um livro inteiro ou mais... Por que e Quando ================ Se você der uma olhada no código fonte da RTL e da VCL, você encontrará declarações assembler inline em vários pontos. Por que a Borland optou por escrever partes do código da RTL e da VCL em assembler? A resposta é bem simples: para alcançar velocidade na execução. Nós sabemos que o compilador produz código rápido mas um compilador jamais será tão bom quanto um programador assembler profissional. Agora, se o assembler é tão bom, por que não foi utilizado em toda a RTL e VCL? A resposta é igualmente simples: porque na programação de mais alto nível, é mais fácil codificar, depurar, ler e manter o código, de modo que o sacrifício em velocidade fica compensado pelas conveniências decorrentes. Isso ajuda a explicar quando o assembler deve ser utilizado. Para ser curto, além do acesso ao sistema em baixo nível, o assembler inline deve ser utilizado quando a diferença na velocidade de execução justifica o trabalho adicional da codificação em assembler. Por exemplo, na unidade Math.pas, há muito assembler, basicamente para chamadas de sistema em baixo nível (para acesso às funções do coprocessador); em System.pas, SysUtils.pas e Classes.pas há também diversos blocos em assembler, desta vez para priorizar velocidade; no é estranho já que essas podem ser consideradas as unidades centrais da RTL e VCL. Em geral, procedimentos e funções que tendem a ser chamadas de forma repetida por um programa devem ser altamente otimizadas, mas codificação em assembler deve ser evitada tanto quanto possível. Se desejamos ganhos em velocidade, antes de optar por assembler devemos otimizar o algoritmo propriamente dito; depois, otimizamos o código Pascal. Se optarmos por assembler, o código Pascal otimizado pode servir como documentação e pode ser utilizado como "código de contigência" no caso de problemas com a manutenção do código assembler. Os Registradores da CPU ======================= Os registradores da CPU são como variáveis predefinidas residindo na CPU e, por vezes, têm tarefas especiais. Eles não têm tipo e podem ser vistos como inteiros de 32 bits com ou sem sinal ou como ponteiros, dependendo da situação. Como estão na própria CPU, é muito mais rápido acessar valores contidos nos registradores do que na memória, fazendo dos registros ideais para fazer cache de valores. Como variáveis, os registradores também possuem nomes. Os nomes daqueles que usaremos são EAX, EBX, ECX, EDX, ESI, EDI, EBP e ESP. Cada registrador tem uma particularidade que o distingue dos demais: - Para algumas instruções, a CPU foi otimizada para utilizar o registrador EAX (também conhecido como acumulador) ou ao menos os opcodes são menores. EAX é usado nas multiplicações e as divisões, intructions de string, instruções de I/O, instruções de ajuste ASCII e decimal, e em algumas instruções especiais (como CDQ, LAHF, SAHF e XLAT). - EBX é um registrador de uso geral, e é usado implicitamente por XLAT. - ECX (também conhecido como contador) tem emprego especial nas instruções LOOP, de rotação e deslocamento de bits e de manipulação de literais. - EDX é utilizado para armazenar os 32 bits mais altos do resultado de uma multiplicação ou os os 32 bits mais altos do dividendo e do resto de uma divisão. - ESI e EDI (conhecidos como índice de origem (source index) e índice de destino ("destination index") respectivamente) são como ponteiros utilizados em instruções envolvendo strings. - EBP (conhecido como ponteiro base) é normalmente usado para endereçar valores na pilha (parâmetros e variáveis locais). - ESP (conhecido como ponteiro da pilha) é utilizado para controlar a pilha. é alterado automaticamente por instruções como PUSH, POP, CALL e RET. Os registradores EBX, ESI, EDI, EBP e ESP devem ser preservados, o que significa que antes de usá-los, devemos salvar seus valores em algum lugar (normalmente na pilha ou outro registradores) e, quando terminarmos de usá-los, devemos restaurar seus valores originais (essas operações implicam no uso de instruções e perda de algum tempo) de modo que o uso desses registradores será feito somente quando justificável ou quando houver uma necessidade inevitável. Provavelmente você percebeu que os nomes dos registradores iniciam com a letra "E". O "E" representa "Extended", estendido. Nos tempos do Intel 80286, os registradores tinham 16 bits e eram chamados AX, BX, CX, etc. Esses registradores ainda existem e são exatamente os 16 bits menos significativos dos registradores EAX, EBX, ECX, etc., respectivamente. A propósito disso, os registradores AX, BX, CX e DX são divididos em dois registradores de 8 bits. AL, BL, CL e DL são os bytes menos significativos de AX, BX, CX e DX respectivamente, enquanto AH, BH, CH e DH são os bytes mais significativos de AX, BX, CX e DX respectivamente. Por exemplo, se o valor de EAX é $7AFD503C, então o valor de AX é $503C, o valor de AH é $50 e o valor de AL é $3C: 7A FD 50 3C AH AL /----/ AX /------------/ EAX Se, na situação acima, armazenarmos o valor $99 em AH, então EAX passaria a ter o valor $7AFD993C. Existe um registrador especial, o registrador de indicadores (flags), que armazena indicadores binários alterados por instruções matemáticas e lógicas ou explicitamente por código, e que são normalmente usados em instruções de desvio condicional. O indicador carry também é usado em algumas instruções de rotação e o indicador de direção é utilizado em instruções envolvendo literais. Esse registrador não é acessível por nome como os demais registradores; mas pode ser copiado e restaurado através da pilha, utilizando PUSHF e POPF respectivamente, e pode também ser copiado e restaurado parcialmente através do registrador AH, utilizando LAHF e SAHF respectivamente. Instruções Assembler ==================== Instruções assembler são dispostas em blocos asm..end blocks e têm a seguinte forma: [identificador:] [prefixo] opcode [operando1 [, operando2 [, ...]]] Onde opcode é o nome da instrução como MOV, ADD, PUSH, etc. Instruções podem ser separadas por ponto e vírgula, quebras de linhas ou comentários. A propósito, comentários são no formato do Object Pascal, isto é, o ponto e vírgula não é considerado o início de um comentário até o final da linha, como no assembler tradicional. A seguir, um exemplo de bloco asm..end com vários dos possíveis tipos de instruções e separadores de comentários: asm xchg ebx, edx; add eax, [ebx]; {ponto e vírgula separa declaração} // quebra de linha separa declaração mov ebx, p sub eax, [ebx] (*comentário separa declaração*) mov ebx, edx end; A convenção é utilizar quebras d elinhas para separação: asm xchg ebx, edx add eax, [ebx] mov ebx, p sub eax, [ebx] mov ebx, edx end; No código da VCL, você verá que os opcodes e nomes de registradores são escritos em maiúsculas e que instruções são indentadas em uma tabulação (normalmente equivalente a oito caracteres), mas utilizaremos outra convenção neste artigo. Blocos asm..end podem ocorrer em qualquer ponto do código fonte onde uma declaração Pascal ordinária puder aparecer; além disso, é possível termos rotinas 100% assembler se, ao invés de "begin", utilizarmos "asm": procedure teste; asm // declarações assembler end; Note que as duas implementações abaixo não são equivalentes: function f(parâmetros): tipo; begin asm // declarações assembler end; end; function f(parâmetros): tipo; asm // declarações assembler end; A razão disso é que o compilador realiza certas otimizações quando implementamos rotinas inteiramente em assembler, sem utilizar um bloco begin..end. As etiquetas devem ser declaradas em uma seção Label, como em qualquer código Object Pascal, a menos que foram prefixadas por "@@": function ENumeroMagico(x: integer): boolean; asm cmp eax, NumeroMagico je @@Bingo xor eax, eax ret @@Bingo: mov eax, 1 end; As etiquetas prefixadas por "@@" são locais ao bloco asm..end em que são usadas. Isto gerará um erro da compilaçao: begin .... asm .... @@destino: .... end; .... asm .... jnz @@destino // Error .... end; .... end; Para corrigi-lo, necessitamos usar uma etiqueta convencional, local ao procedimento ou à função: label destino; begin .... asm .... destino: .... end; .... asm .... jnz destino // Correto .... end; .... end; Operandos ========= Certas vezes, um ou mais operandos são implícitos. Por exemplo, a instrução CDQ (Converta Dword para Qword) parece não utilizar operando algum; entretanto, essa instrução utiliza EDX e EAX: o bit mais alto de EAX, o bit de sinal, é copiado para EDX de forma que, EDX:EAX passa a representar o inteiro em EAX convertido para Int64, onde EAX carrega os 32 bits menos significativos e EDX os 32 bits mais significativos. Para a maioria das instruções, os operandos são registradores. Por exemplo: mov eax, ecx copia o valor de ECX para EAX. Operandos podem conter valores imediatos: mov eax, 5 mov eax, 2 + 3 // expressão constante, resolvida na compilação mov al, 'A' // o código ASCII de 'A' é $41 (65) mov eax, 'ABC' // equivalente a MOV EAX, $00414243 Operandos também podem conter referências de memória: mov [ebx], eax // EBX^ := EAX; Referências de memória aparecem de várias formas: mov eax, [$000FFFC] // Endereço absoluto mov eax, [ebx] // Registrador mov eax, [ebp-12] // Registrador mais/menos deslocamento // constante mov eax, [ebp+ebx] // Registrador mais deslocamento em registro mov eax, [ebp+ebx+8] // Registrador mais deslocamento em registro // mais/menos deslocamento constante mov eax, [ebp+ebx*4] // Registrador mais deslocamento em registro // multiplicado por constante mov eax, [ebp+ebx*4+8] // Registrador mais deslocamento em registro // multiplicado por constante, mais/menos // deslocamento constante Os identificadores usuais do Pascal são traduzidos para uma das formas: mov eax, parâmetro // mov eax, [ebp + deslocamento_constante] mov eax, varlocal // mov eax, [ebp - deslocamento_constante] mov eax, varglobal // mov eax, [endereço_absoulto] call procname // chama endereço absoluto Primeiro Exemplo ================ Agora que estamos prontos para aprender alguns opcodes, vamos aos exemplos. Podemos começar com uma função simples: function f(x: integer; y: integer): integer; // f(x,y) = (-x-y+5)*7 { begin Result := (-x - y + 5) * 7; end; } asm // os parâmetros são passados em EAX (x) e EDX (y); neg eax // EAX := -EAX; // EAX = -x sub eax, edx // EAX := EAX - EDX; // EAX = -x-y add eax, 5 // EAX := EAX + 5; // EAX = -x-y+5 imul 7 // EAX := EAX * 7; // EAX = (-x-y+5)*7 end; Os três primeiros parâmetros (da esquerda para a direita) são passados em EAX, EDX e ECX. Para métodos, o primeiro parâmetro é Self (passado em EAX) e o primeiro parâmetro explicitamente declarado é, de fato, o segundo parâmetro (passado em EDX) e o segundo parâmetro explícito é de fato o terceiro parâmetro (passado em ECX). O valor de retorno deve ser armazenado em EAX para valores ordinais de 32 bits (AX e AL devem ser utilizados para retornar valores de 16 e 8 bits respectivamente). Os comentários explicam os opcodes de forma clara mas, para IMUL, temos que acrescentar duas explicações: * IMUL considera os operandos (EAX e 7 no exemplo) como inteiros com sinal (devemos utilizar MUL quando os operandos não possuírem sinal). * O resultado da multiplicação é um inteiro de 64 bits sendo que os 32 bits mais significativos do resultado são armazenados em EDX. Multiplicações são relativamente caras em termos de tempo de CPU e, por vezes, é mais fácil substitui-las por deslocamentos de bits (quando a multiplicação ou divisão operarem com potências de dois), somas e subtrações. Por exemplo: a * 7 = a * (8 - 1) = a * 8 - a = a * 2^3 - a a * 7 = a shl 3 - a Ao invés de IMUL 7, podemos fazer o seguinte: mov ecx, eax // ECX := EAX; // ECX = -x-y+5 shl eax, 3 // EAX := EAX shl 3; // EAX = (-x-y+5)*8 sub eax, ecx // EAX := EAX - ECX; // EAX = (-x-y+5)*8 - (-x-y+5) // EAX = (-x-y+5)*7 Vejamos outro exemplo: function resto(x: integer; y: integer): integer; // Retorna o resto de x dividido por y { begin Result := x mod y; end; } asm // os parâmetros são passados em EAX (x) e EDX (y); mov ecx, edx // ECX := EDX; // EDX = y cdq // EDX:EAX := Int64(EAX); // EAX = x idiv ecx // divisão inteira com sinal em 32 bits: // EAX := Int64(EDX:EAX) div integer(ECX); // EDX := Int64(EDX:EAX) mod integer(ECX); mov eax, edx // Result := EDX; // resto end; A Pilha ======= Quando um programa é carregado, ele receve uma pilha, que é uma região de memória utilizada como uma estrutura LIFO, "Last In, First Out" (último a chegar, primeiro a sair), controlada pelo registrador ESP que aponta para o topo dessa pilha. ESP inicia apontando para o final da região de modo que, cada vez que empilhamos um novo valor de 32 bits, o registrador ESP é decrementado em 4 (bytes) e o valor é armazenado no local apontado por ESP. | | +-----------+ | | +-----------+ | $01234567 | <- ESP +-----------+ | | PUSH $89ABCDEF // SUB ESP,4; MOV [ESP],$89ABCDEF | | +-----------+ | $89ABCDEF | <- ESP +-----------+ | $01234567 | +-----------+ | | De forma análoga, quando retiramos um valor de 32 bits da pilha, o valor é recuperado do local apontado por ESP e ESP é incrementado em 4 (bytes). POP EAX // MOV EAX,[ESP]; ADD ESP,4 | | +-----------+ +-----------+ | $89ABCDEF | EAX | $89ABCDEF | +-----------+ +-----------+ | $01234567 | <- ESP +-----------+ | | A pilha é utilizada para armazenar endereços de retorno de rotinas, parâmetros, variáveis locais e resultados intermediários. No exemplo a seguir, utilizamos a pilha para salvar o valor de um registrador para uso posterior: function IntDiv(x: integer; y: integer; r: pinteger = NIL): integer; // Retorna o quociente inteiro x / y e o resto em r { begin Result := x div y; if r <> NIL then r^ := x mod y; end; } asm // os parâmetros são passados em EAX (x), EDX (y) e ECX ® push ecx // Salve ECX ® para uso posterior mov ecx, edx // ECX := EDX; // ECX = y cdq // EDX:EAX := Int64(EAX); // EAX = x idiv ecx // divisão inteira com sinal em 32 bits: // EAX := Int64(EDX:EAX) div integer(ECX); // EDX := Int64(EDX:EAX) mod integer(ECX); pop ecx // Restaura ECX (ECX := r) cmp ecx, 0 // if ECX = NIL then jz @@end // goto @@end; mov [ecx], edx // ECX^ := EDX; // resto @@end: // identificador local (precedido por "@@") end; Note que, para cada PUSH que executamos, temos que executar um POP correspondente de modo que ESP fique inalterado (ESP é um dos registradores que temos que preservar). A instrução CMP subtrai o segundo operador do primeiro (ECX-0 nesse caso), como a instrução SUB, mas o resultado não é armazenado em lugar algum, ainda que o indicador de Zero (Zero flag) seja marcado (ligado) ou limpo (desligado) dependendo do resultado ser zero ou não, como em qualquer instrução lógica ou matemática (com a exceção de certos casos). Podemos então tirar vantagem desse fato e, ao invés de escrevermos cmp ecx, 0 podemos escrever or ecx, ecx // ECX := ECX or ECX; O resultado de ECX Or ECX é o próprio ECX; portanto, o valor armazenado em ECX é o mesmo de antes, e como dissemos anteriormente o indicador de Zero será marcado se o resultado for zero (isto é, se ECX era zero). A instrução JZ, "Jump if Zero" (Desvie se Zero), desvia (salta) para o identificador indicado como operando se o valor do indicador de Zero estiver marcado (ligado) ou continua normalmente com o fluxo de execução se o indicador de Zero estiver desmarcado (desligado). Passando Parâmetros para a Pilha -------------------------------- Voltemos para a pilha. Dissemos que os três primeiros parâmetros de uma rotina são passados em EAX, EDX e ECX; mas, o que acontece quando temos mais de três parâmetros? Parâmetros adicionais são passados na pilha, da esquerda para a direita, de forma que o último parâmetro será sempre o primeiro da pilha. Suponha que temos a seguinte função function Soma(a, b, c, d, e: integer): integer; begin Result := a + b + c + d + e; end; e queremos fazer a chamada Sum(1,2,3,4,5); Em assembler, faríamos da seguinte forma: mov eax, 1 mov edx, 2 mov ecx, 3 push 4 push 5 call Sum A instrução CALL empilha o endereço de retorno na pilha e salta para (inicia a execução) da função. A instrução RET (RETorna) gerada pelo compilador quando o final de uma função é alcançado desempilha esse endereço da pilha e salta para ele para continuar a execução a partir desse ponto. Note que quando empilhamos parâmetros na pilha mas não os desempilhamos. Isso acontece pois limpar a pilha é responsabilidade da função chamada e não da função que chama (exceto na convenção de chamada CDECL). Para limpar os parâmetros, a instrução RET é utilizada com um operando que indica o número de bytes que ESP deve ser incrementado (8 nesse caso já que ESP foi decrementado em 4 bytes para cada parâmetro empilhado). O compilador fica encarregado dessa tarefa portanto não temos com que nos preocupar; mas, se você utilizar a janela de depuração da CPU e encontrar uma instrução RET $08, agora você já sabe do que se trata. Na entrada para Soma, a pilha estaria, em teoria, da seguinte forma: | | +-----------+ | Ret_Addr | <- ESP +-----------+ | $00000005 | (parâmetro e) +-----------+ | $00000004 | (parâmetro d) +-----------+ | | Quando uma função tem parâmetros na pilha (ou variáveis locais), o compilador gera algumas instruções chamadas de "stack frame", quadro da pilha. Na entrada da função (em "asm"), EBP é empilhado de modo a ser preservado e ESP é atribuído a ele; e, antes de deixar a função, (em "end"), o valor original de EBP é desempilhado: function Soma(a, b, c, d, e: integer): integer; asm // push ebp; mov ebp, esp; .... end; // pop ebp; ret 8; Assim, quando entramos em Soma, a pilha estaria de fato da seguinte forma: | | +-----------+ | Orig. EBP | <- EBP, ESP +-----------+ | Ret_Addr | +-----------+ | $00000005 | <- EBP+8 (parâmetro e) +-----------+ | $00000004 | <- EBP+12 (parâmetro d) +-----------+ | | Em [EBP] encontramos o valor original de EBP que foi empilhado para ser preservado quando da construção do quadro de pilha; em [EBP+4] encontramos o endereço de retorno da rotina; em [EBP+8] encontramos o último parâmetro (o último parâmetro é empilhado por último e, por isso, é o primeiro da pilha). O parâmetro seguinte (da direita para a esquerda) fica em [EBP+12], e assim por diante se houvesse outros parâmetros. Agora vamos escrever a rotina Soma em assembler: function Soma(a, b, c, d, e: integer): integer; { begin Result := a + b + c + d + e; end; } asm add eax, b add eax, c add eax, d add eax, e end; Note que no bloco asm..end nós utilizamos "b", "c", "d" e "e" ao invés de "EDX", "ECX", "[EBP+12]" e "[EBP+8]" respectivamente. Nós podemos fazer assim já que o compilador fará as substituições adequadas. Variáveis Locais na Pilha ------------------------- Se nossa função assembler inline tiver variáveis locais, o compilador criará espaço para essas variáveis na pilha, movendo o ponteiro da pilha de modo que o quadro da pilha para uma função com duas variáveis locais inteiras seria: push ebp mov ebp, esp sub esp, 8 // Desloca ESP como se empilhássemos 8 bytes ... add esp, 8 // Desloca ESP como se desempilhássemos 8 bytes pop ebp Para o propósito do exemplo, aqui vai uma variação da rotina Soma acima, utilizando duas variáveis locais: function SomaL(a, b, c, d, e: integer): integer; var f, g: integer; { begin f := b + c; g := d + e; Result := a + f + g; end; } asm // push ebp; mov ebp, esp; sub esp, 8; add edx, ecx mov f, edx // b + c mov edx, d add edx, e mov g, edx // d + e add eax, f add eax, g end; // add esp, 8; pop ebp; ret 8 Nessa função, a pilha teria o seguinte aspecto: | | +-----------+ | var. g | <- EBP-8, ESP +-----------+ | var. f | <- EBP-4 +-----------+ | Orig. EBP | <- EBP +-----------+ | Ret_Addr | +-----------+ | Param e | <- EBP+8 +-----------+ | Param d | <- EBP+12 +-----------+ | | O Que Vem Agora? ================ Na continuação deste artigo, aprenderemos mais instruções e veremos como passar e retornar outros tipos de parâmetros, como trabalhar com arrays, como acessar campos de registros e objetos, como chamar métodos e mais. Nesse capítulo iremos aprender algumas novas instruções assembler e o básico da manipulação de strings ANSI, também chamadas de strings longas. Novos opcodes ============= Abaixo os opcodes introduzidos neste atrigo: * JL (Jump if Lower, desvie se menor): A descrição mais adequada levaria muito tempo para ser explicada, então vamos dizer que JL salta (desvia) para o label especificado desde que na operação CMP (ou SUB) anterior o primeiro operando seja menor que o segundo numa comparação com sinal: // if signed(op1) < signed(op2) then goto @@label; cmp op1, op2 jl @@label JG (Jump if Greater, desvie se maior), JLE (Jump if Lower or Equal, desvie se menor ou igual) e JGE (Jump if Greater or Equal, desvie se maior ou igual) completa a família de desvios condicionais para comparações com sinal. * JA (Jump if Above, desvie se maior): salta (desvia) para o label especificado desde que na operação CMP (ou SUB) anterior o primeiro operando seja maior que o segundo numa comparação sem sinal: // if unsigned(op1) > unsigned(op2) then goto @@label; cmp op1, op2 ja @@label JB (Jump if Below, desvie se menor), JBE (Jump if Below or Equal, desvie se menor ou igual) e JAE (Jump if Above or Equal, desvie se maior ou igual) completam a família de desvios condicionais para comparações sem sinais. * LOOP: Decrementa ECX e, se não for zero, desvia para o label indicado. LOOP @@label é o equivalente mais curto e rápido de: dec ecx // ECX := ECX - 1; jnz @@label // if ECX <> 0 then goto @@label Examplo: xor eax, eax // EAX := EAX xor EAX; // EAX := 0; mov ecx, 5 // ECX := 5; @@label: add eax, ecx // EAX := EAX + ECX; // Executado 5 vezes loop @@label // Dec(ECX); if ECX <> 0 then goto @@label; // EAX seria 15 (5+4+3+2+1) Trabalhando com strings ANSI ============================ Uma variável string é representada por um ponteiro de 32 bits. Se a string é vazia (''), então o ponteiro é nil (zero), caso contrário, esse ponteiro aponta para o primeiro caractere dessa string. O tamanho da string e a contagem de referência são dois inteiros em deslocamentos negativos a partir do primeiro byte da string: +-----------+ | s: string |-------------------+ +-----------+ | V --+-----------+-----------+-----------+---+---+---+---+---+---+---+-- | allocSiz | refCnt | length | H | e | l | l | o | ! | #0| --+-----------+-----------+-----------+---+---+---+---+---+---+---+-- (longint) (longint) (longint) \-----------------v-----------------/ StrRec record const skew = sizeof(StrRec); // 12 Quando passamos uma string como um parâmetro para uma função, o que de fato é passado é o ponteiro de 32 bits. Os valores string são um pouco mais complicados de explicar. A rotina que chamou a rotina que retorna a string deve passar- como último e invisível parâmetro da chamada, um tipo PString-o endereço de uma variável string que receberá o resultado da função. d := Uppercase(s); // Internamente convertido para: Uppercase(s, @d); Se o resultado da função é usado em uma expressão ao invés de ser atribuído diretamente à variável, a rotina que chama deve utilizar uma variável temporária incializada com nil (string vazia). O compilador faz tudo isso automaticamente no nosso código Object Pascal mas, se temos que fazer isso por conta própria se optarmos por escrever código assembler que chame rotinas que retornam strings. Para algumas tarefas, não podemos chamar as clássicas funções de string diretamente. Por exemplo, a função Length não é o nome de uma função de verdade,. é uma construção interna do próprio compilador e o compilador gera o código para a função apropriada, dependendo do parâmetro ser uma string ou um array dinâmico. Em assembler, ao invés de Lenght, teríamos que usar a função LStrLen (declarada na unidade System) para obter o tamanho da string. Existem mais coisas que deveríamos saber a respeito das strings mas o que temos já é suficiente para um primeiro exemplo. Versão Assembler de Uppercase ============================= Eis a declaração da função: function AsmUpperCase(const s: string): string; O parâmetro "s" será passado em EAX e o endereço de "Result" será passado como o segundo parâmetro, ou seja, em EDX. Basicamente a função deve fazer: 1) Obter o comprimento da string a converter 2) Alocar memória para a string convertida 3) Copiar os caracteres um a um, convertidos para maiúsculas 1) Obter o comprimento da string a converter -------------------------------------------- Faremos isso através de uma chamada a System.@LStrLen. A função espera a string em EAX (ela já está lá) e o resultado será colocado em EAX; então, temos que salvar o valor de EAX (o parâmetro "s") em algum lugar antes de chamar a função de modo que "s" não seja perdido. Podemos salvar numa variável local "src". Já que funções são livres para utilizar os registradores EAX, ECX e EDX, presumimos que o valor em EDX ("@Result") poderia também ser destruído após uma chamada a System.@LStrLen, de modo que é útil salvar esse valor numa variável local, por exemplo, "psrc". O resultado da chamada a System.@LStrLen, deixado em EAX, servirá como parâmetro da chamada a System.@LStrSetLength (para alocar memória para o conteúdo da string de resultado), como contador dos bytes a copiar, de modo que esse valor também deve ser salvo, por exemplo, na variável "n": var pdst: Pointer; // Endereço da string resultado src: PChar; // String de origem n: Integer; // Comprimento da string de origem asm // O endereço da string de resultado é passado em EDX. // Salvamos esse valor na variável pdst: mov pdst, edx // pdst := EDX; // Salvamos EAX (s) na variável local (src) mov src, eax // src := EAX; // n := Length(s); call System.@LStrLen // EAX := LStrLen(EAX); mov n, eax // n := EAX; 2) Alocar memória para a string convertida ------------------------------------------ A alocação é realizada através de uma chamada a System.@LStrSetLength. O procedimento espera dois parâmetros: o endereço da string (que salvamos em "pdst") e o comprimento da string (que está em EAX). // SetLength(pdst^, n); // Alocar a string de resultado mov edx, eax // EDX := n; // Segundo parâmetro p/LStrSetLength mov eax, pdst // EAX := pdst; // Primeiro parâmetro p/LStrSetLength call System.@LStrSetLength // LStrSetLength(EAX, EDX); 3) Copiar os caracteres um a um, convertidos para maiúsculas ------------------------------------------------------------ Se o comprimento da string era zero, já terminamos: // if n = 0 then exit; mov ecx, n // ECX := n; test ecx, ecx // Fazer and de ECX com ECX para definir flags // (ECX inalterado) jz @@end // Ir para @@end se o flag zero está marcado (ECX=0) Não sendo esse o caso, devemos copiar os caracteres de uma string para a outra, convertendo-os para maiúsculas conforme necessário. Nós vamos utilizar ESI e EDX para apontar para os caracteres da string de origem e destino respectivamente, AL para carregar os caracteres da string de origem e realizar a mudança antes de armazená-los na string de destino e ECX para controlar a instrução de LOOP que contará os caracteres. Já que ESI é um registro que tem que ser preservado, devemos salvar seu valor para restaurá-lo mais tarde. Decidi salvar ESI colocando-o na pilha. push esi // Salve ESI na pilha // Inicializar ESI e EDX mov eax, pdst // EAX := pdst; // Endereço da string de resultado mov esi, src // ESI := src; // String de origem mov edx, [eax] // EDX := pdst^; // String de resultado @@cycle: mov al, [esi] // AL := ESI^; // if Shortint(AL) < Shortint(Ord('a')) then goto @@nochange cmp al, 'a' jl @@nochange // AL in ['a'..#127] // if Byte(AL) > Byte(Ord('a')) then goto @@nochange cmp al, 'z' ja @@nochange // AL in ['a'..'z'] sub al, 'a'-'A' // Dec(AL, Ord('a')-Ord('A')); @@nochange: mov [edx], al // EDX^ := AL; inc esi // Inc(ESI); inc edx // Inc(EDX); loop @@cycle // Dec(ECX); if ECX <> 0 then goto cycle pop esi // Restaurar ESI da pilha @@end: end;
  3. Para que um computador faça qualquer coisa, você precisa de um programa de computador. Para criar um programa de computador, você tem de informar ao computador, passo a passo, exatamente o que você espera que ele faça. O computador então "executa" o programa, seguindo cada passo mecanicamente para atingir o objetivo final. Quando você "diz" ao computador o que fazer, você também deve escolhercomo ele vai fazer. é aí que entram os algoritmos de computador. Algoritmo é a técnica básica usada para fazer o trabalho. Vamos acompanhar um exemplo para ajudar a entender o conceito de algoritmo. Digamos que você tem um amigo chegando ao aeroporto e ele precisa ir do aeroporto até sua casa. Aqui estão quatro algoritmos diferentes que você poderia dar a seu amigo para ele chegar à casa: [*]o algoritmo do táxi: [*]vá para o ponto de táxi; [*]entre em um táxi; [*]dê meu endereço ao motorista. [*]o algoritmo "ligue-me": [*]quando seu avião chegar, ligue para meu celular; [*]espere do lado de fora do terminal de bagagens (em inglês). [*]O algoritmo "alugue um carro": [*]pegue o circular até o aluguel de automóveis; [*]alugue um carro; [*]siga as instruções para chegar até minha casa. [*]O algoritmo do ônibus: [*]fora do terminal de bagagens, pegue o ônibus número 70; [*]faça uma baldeação para o ônibus 14 na Rua Dom Pedro; [*]desça na rua Aroeira; [*]ande duas quadras para norte até minha casa. Todos esses quatro algoritmos atingem exatamente a mesma meta, mas cada um deles o faz de modo completamente diferente. Cada algoritmo também possui um custo e um tempo de viagem diferentes. O táxi, por exemplo, é a maneira mais rápida e cara. Já o ônibus é definitivamente mais barato, mas bem mais lento. Você escolhe o algoritmo com base nas circunstâncias. Na programação de computadores, freqüentemente há diversos caminhos, ou algoritmos, para cumprir qualquer tarefa determinada. Cada algoritmo tem vantagens e desvantagens em situações diferentes. A ordenação é uma área onde se fez muita pesquisa, porque os computadores gastam muito tempo classificando listas. Aqui estão cinco algoritmos diferentes que são usados na ordenação: [*]ordenação por caixas [*]ordenação por mistura [*]ordenação por bolha [*]ordenação por aparência [*]ordenação rápida Se você tem um milhão de valores inteiros entre 1 e 10 e precisa ordená-los, a ordenação por caixa é o algoritmo certo a ser usado. Se você tem um milhão de títulos de livros, o método ordenação rápida poderia ser o melhor algoritmo. Conhecendo os pontos fortes e fracos dos diferentes algoritmos, você escolhe o melhor para a tarefa que tem em mãos.
  4. É muito comum o uso dos componentes ClientSocket e ServerSocket quando queremos trabalhar com aplicações que utilizam sockets em Delphi. Neste breve tutorial, iremos abordar o uso da API nativa do Windows para trabalharmos com sockets - WSA ou Winsock API. Na tentativa de simplificar e melhorar a compreensão do artigo, iremos trabalhar no modo console ao invÉs do modo gráfico - eu acho console muito estiloso ^^. Neste artigo, utilizei a versão 7 do Delphi. Para criar um novo programa console, faça o seguinte: 1. Vá atÉ o Menu File » New » Other (Arquivo, novo, outro); 2. Na aba New (Novo) selecione Console Application (Aplicação Console); 3. OK Bem, vejamos um simples programa para console em Delphi: program programa1; // Nome do programa {$APPTYPE CONSOLE} uses SysUtils; begin // Código aqui ! end. Assim como nas linguagens C, C++, Perl e Visual Basic, por exemplo, devemos incluir headers/módulos/bibliotecas para podermos trabalhar com sockets. Como iremos trabalhar com o Winsock, deveremos incluir todas as referências necessárias, veja: program programa1; {$APPTYPE CONSOLE} uses SysUtils,Winsock; // inclui-se a unit Winsock para trabalharmos com a API do Winsock begin // Código aqui ! end. O primeiro passo a ser tomado para que possamos trabalhar com o Winsock, É inicializar sua biblioteca - WSOCK32.DLL - atravÉs da função WSAStartup: program programa1; {$APPTYPE CONSOLE} uses SysUtils,Winsock; var wsa: WSADATA; begin if(WSAStartup($101,wsa) = -1) then begin writeln('Ocorreu um erro ao inicializar o Winsock.'); exit; end; end. Temos nossa primeira variável: var wsa: WSADATA; Uma variável do tipo WSADATA armazena informações sobre a inicialização do Winsock. Esta É passada como segundo argumento da função WSAStartup: if(WSAStartup($101,wsa) = -1) then // Se ocorrer um erro begin writeln('Ocorreu um erro ao inicializar o Winsock.'); // Mostra mensagem exit; // Encerra end; No trecho acima, tentamos inicializar a versão 1.1 do Winsock (o símbolo $ É utilizado para números hexadecimais. $101 = 257). Veja a sintaxe: WSAStartup(VERSãO: WORD,var VARIáVEL_WSA: WSADATA); VERSãO: versão do winsock a ser inicializada; VARIáVEL_WSA: variável do tipo WSADATA. A função irá retornar o valor -1 se falhar. Do contrário, retornará ZERO. A função WSAStartup(), na verdade, requer um ponteiro para uma variável do tipo WSADATA como segundo parâmetro, no entanto, só É necessário passar o nome da variável. Isso se explica devido à declaração do parâmetro: var VARIáVEL_WSA: WSADATA); Quando temos "var" antes do parâmetro, significa que será passado o endereço (ponteiro) da variável, e não o seu valor =) Muito bem, após inicializarmos o Winsock, podemos utilizar as funções contidas na Winsock API. Temos agora que criar um socket: program programa1; {$APPTYPE CONSOLE} uses SysUtils,Winsock; var wsa: WSADATA; sock: integer; begin if(WSAStartup($101,wsa) = -1) then begin writeln('Ocorreu um erro ao inicializar o Winsock.'); exit; end; sock := socket(AF_INET,SOCK_STREAM,0); if(sock = -1) then begin writeln('Ocorreu um erro ao criar o socket.'); exit; end; end. Temos mais uma variável declarada: sock: integer; Esta variável irá armazenar a identificação do nosso socket criado. Geralmente, ao invÉs do tipo Integer (inteiro), variáveis deste tipo são declaradas como SOCKET. Entretanto, "SOCKET" É apenas um "novo nome" para o tipo inteiro, ou seja, o tipo "SOCKET" É, na verdade, o tipo inteiro =) Vejamos a criação do socket: sock := socket(AF_INET,SOCK_STREAM,0); if(sock = -1) then begin writeln('Ocorreu um erro ao criar o socket.'); exit; end; O trecho acima tenta criar um socket para ser utilizado com o protocolo TCP. Isto É definido pelo segundo parâmetro: SOCK_STREAM. Sintaxe: sock := socket(FAMíLIA: Integer,PROTOCOLO: Integer,TIPO: Integer); sock: variável do tipo inteiro que irá identificar o socket; FAMíLIA: família do socket. Embora existam diversas constantes, use-se a AF_INET = INTERNET. PROTOCOLO: protocolo com o qual o socket irá trabalhar: SOCK_STREAM = TCP SOCK_DGRAM = UDP SOCK_RAW = RAW TIPO: opcional, define opções relacionados ao tipo do protocolo: IPPROTO_IP = protocolo IP; IPPROTO_TCP = protocolo TCP; IPPROTO_UDP = protocolo UDP; IPPROTO_RAW = protocolo RAW; IPPROTO_TCP = protocolo ICMP; O parâmetro só É obrigatório quando estamos trabalhando com raw sockets, podendo ser passado como ZERO, caso contrário. A função irá retornar o valor -1 se falhar. Do contrário, retornará ZERO. Iremos começar pelo protocolo TCP por ser mais utilizado Após termos criado o socket, iremos definir uma tarefa para este: atuar como cliente ou servidor. Para tal, teremos que configurar este socket de acordo com uma estrutura denominada "SOCKADDR_IN". Vamos começar definindo o socket para trabalhar com servidor: program programa1; {$APPTYPE CONSOLE} uses SysUtils,Winsock; var wsa: WSADATA; sock: integer; addr: sockaddr_in; begin if(WSAStartup($101,wsa) = -1) then begin writeln('Ocorreu um erro ao inicializar o Winsock.'); exit; end; sock := socket(AF_INET,SOCK_STREAM,0); if(sock = -1) then begin writeln('Ocorreu um erro ao criar o socket.'); exit; end; addr.sin_family := AF_INET; addr.sin_port := htons(1234); addr.sin_addr.S_addr := INADDR_ANY; if(bind(sock,addr,sizeof(addr)) = -1) then begin writeln('Ocorreu um erro ao configurar o socket.'); exit; end; if(listen(sock,1) = -1) then begin writeln('Ocorreu um erro ao colocar o socket na escuta.'); exit; end; sock := accept(sock,nil,nil); if(sock = -1) then begin writeln('Ocorreu um erro ao aceitar uma conexão.'); exit; end; writeln('Um cliente conectou-se!'); closesocket(sock); WSACleanup(); end. Antes de tudo, para configurarmos um socket, devemos declarar uma estrutura do tipo SOCKADDR_IN: addr: sockaddr_in; Vamos ver como o socket É configurado: addr.sin_family := AF_INET; // Corresponde à família a qual o socket pertence addr.sin_port := htons(1234); // Corresponde à porta na qual o socket irá aguardar conexões addr.sin_addr.S_addr := INADDR_ANY; // Permite que o socket aceite conexão de qualquer host Há dois detalhes que devem ser observados: addr.sin_port := htons(1234); // Correto O membro "sin_port", da estrutura "addr", É um inteiro de 2 bytes e requer um valor expresso em network byte. Para convertemos um valor para tal, utilizamos a função htons(). A forma abaixo estaria incorreta: addr.sin_port := 1234; // INCORRETO =( O membro "sin_addr" armazena o endereço IP do host remoto. Este membro, na verdade, pertence a uma estrutura chamada "in_addr" que armazena endereços IP. Quando queremos transformar um IP para network byte, utilizamos a função inet_addr(): addr.sin_addr.S_addr := inet_addr('127.0.0.1'); // Transforma o IP 127.0.0.1 para network byte. Se você observar bem, utilizamos um membro dentro desta estrutura: "S_addr". Note ainda que seria incorreto fazer: addr.sin_addr := inet_addr('127.0.0.1'); // Errado ;( Vamos ver o por quê. addr -> estrutura sockaddr_in; sin_addr -> estrutura in_addr dentro de "addr"; S_addr -> membro dentro de "sin_addr", um inteiro de 4 bytes. A função "inet_addr()" retorna um valor inteiro (tambÉm de 4 bytes), expresso em network byte, de um respectivo endereço IP. Quando tentamos fazer: addr.sin_addr := inet_addr('127.0.0.1'); // Errado ;( Estamos querendo atribuir um valor inteiro de 4 bytes de forma direta a uma estrutura, por isso ocorreria o erro. Já no outro exemplo: addr.sin_addr.S_addr := inet_addr('127.0.0.1'); // CERTO O membro "S_addr" (dentro de "sin_addr") É um inteiro de 4 bytes e a função inet_addr() tambÉm retorna um inteiro de 4 bytes, por isso a atribuição É válida É importante notar que só podemos utilizar a função inet_addr() com endereços IP: addr.sin_addr.S_addr := inet_addr('www.google.com.br'); // INCORRETO! Veremos como obter o endereço IP de um host pelo seu nome mais adiante. Após configuramos o socket, devemos chamar a função bind() para prepará-lo no computador local, permitindo-o aguardar conexões: if(bind(sock,addr,sizeof(addr)) = -1) then begin writeln('Ocorreu um erro ao configurar o socket.'); exit; end; Vejamos a sintaxe: bind(sock: Integer; var addr: sockaddr_in; tamanho: Integer); sock: nome do nosso socket; addr: variável pertencente à estrutura "sockaddr_in"; tamanho: tamanho da estrutura "sockaddr_in". A função irá retornar o valor -1 se falhar. Do contrário, retornará ZERO. Como já havia dito antes, um socket É identificado por uma variável do tipo inteiro. Isso pode ser constatado observando o tipo do primeiro parâmetro: "Integer" Havia dito tambÉm que, quando temos "var" antes de um parâmetro, signfica que iremos passar o endereço de uma variável e não seu valor - É justamente o endereço da variável "addr" que temos que passar. Veja que, para passar o tamanho da estrutura "sockaddr_in", utilizamos o operador sizeof(). Tanto faz escrever "sizeof(addr)" ou "sizeof(sockaddr_in)" - embora esta última forma seja mais adequada. Após configurado localmente, podemos colocar o socket em modo de escuta: if(listen(sock,1) = -1) then begin writeln('Ocorreu um erro ao colocar o socket na escuta.'); exit; end; No exemplo acima, fazemos com que o socket aguarde uma conexão na porta configurada previamente. Vejamos a sintaxe da função: listen(sock: Integer; num: Integer); sock: nome do nosso socket; num: número de conexões que podem ser aceitas; Assim como as outras funções, se a função listen() tiver êxito, esta retorna ZERO, senão, -1 É retornado. O trecho abaixo faz com que o programa fique aguardando atÉ que um pedido de conexão seja feito: sock := accept(sock,nil,nil); if(sock = -1) then begin writeln('Ocorreu um erro ao aceitar uma conexão.'); exit; end; Se, após recebido o pedido de conexão, o valor retornado for -1, É sinal que houve um erro ao aceitar esta conexão. Veja a sintaxe: novo_sock := accept(sock: Integer; var pt_addr: PSOCKADDR; pt_tamanho: PInteger); novo_sock: uma nova variável (inteiro) que irá armazenar a identificação do novo socket; se o nome do próprio socket for utilizado, não será possível aceitar novas conexões; sock: nome do nosso socket; pt_addr: ponteiro para uma variável do tipo "SOCKADDR" que irá armazenar informações sobre o cliente que se conectou; tamanho: ponteiro para uma variável do tipo inteiro que armazena o tamanho da estrutura "SOCKADDR"; A função irá retornar o valor -1 se falhar. Do contrário, retornará ZERO. No nosso exemplo, usamos: sock := accept(sock,nil,nil); No caso, a variável "sock" será utilizada para armazenar a identificação do novo socket após um pedido de conexão ter sido feito. Observe que, como não iremos armazenar informações sobre o cliente, passamos os dois últimos argumentos como NULOS, isto É, passando o ponteiro nulo: "nil". Vejamos agora: closesocket(sock); WSACleanup(); São duas funções ainda não vistas anteriormente. Utiliza-se a função closesocket() para fechar um socket após seu uso e WSACleanup() para finalizar o uso do Winsock. Sintaxe: closesocket(sock); sock: nome do socket. A função WSACleanup() não possui parâmetros. Mais adiante, quando abordaremos algumas funções, veremos como obter informações do cliente conectado. No exemplo acima, criamos um socket para atuar com servidor. A seguir, iremos criar um socket para atuar como cliente: program programa2; {$APPTYPE CONSOLE} uses SysUtils,Winsock; var wsa: WSADATA; sock: integer; addr: sockaddr_in; begin if(WSAStartup($101,wsa) = -1) then begin writeln('Ocorreu um erro ao inicializar o Winsock.'); exit; end; sock := socket(AF_INET,SOCK_STREAM,0); if(sock = -1) then begin writeln('Ocorreu um erro ao criar o socket.'); exit; end; addr.sin_family := AF_INET; addr.sin_port := htons(1234); addr.sin_addr.S_addr := inet_addr('127.0.0.1'); if(connect(sock,addr,sizeof(addr)) = -1) then begin writeln('Ocorreu um erro ao conectar-se.'); exit; end; writeln('Conectado!'); closesocket(sock); WSACleanup(); end. Como você pode observar, o código necessário para tal É bem menor Vejamos as diferenças: addr.sin_addr.S_addr := inet_addr('127.0.0.1'); A linha acima configura o socket para se conectar no IP "127.0.0.1". Em seguida, fazemos com que o socket tente conectar-se: if(connect(sock,addr,sizeof(addr)) = -1) then begin writeln('Ocorreu um erro ao conectar-se.'); exit; end; A sintaxe da função connect() É similar à da função bind() - que não É necessária quando estamos trabalhando com um socket cliente - veja: connect(sock: Integer; var addr: sockaddr_in; tamanho: Integer); sock: nome do nosso socket; addr: variável pertencente à estrutura "sockaddr_in"; tamanho: tamanho da estrutura "sockaddr_in". A função irá retornar o valor -1 se falhar. Do contrário, retornará ZERO. Novamente, fechamos o socket e finalizamos o winsock (respectivamente): closesocket(sock); WSACleanup(); O próximo passo É trabalhar com o envio e recebimento de dados atravÉs do socket. Quando estamos trabalhando com o protocolo TCP, utilizamos as funções recv() e send(), respectivamente: Enviando dados: var buffer: array[0..99] of char; begin buffer := 'Apenas um exemplo!'; send(sock,buffer,strlen(buffer),0); end. No exemplo acima, declaramos um array de caracteres (uma string em C) com capacidade de armazenar 100 elementos, isto É, 100 caracteres: buffer. Escrevemos "Apenas um exemplo!" neste array e o enviamos. A sintaxe É: send(sock: Integer; var Buf; tam_buffer: Integer; Flags: Integer); sock: nome do nosso socket; Buf: array de caracteres ou um ponteiro para char que contÉm os dados que serão enviados; tam_buffer: número de bytes que serão enviados; flags: valores opcionais que especificam o modo de envio. A função, em situação normal, retorna o número de bytes enviados. Se algum erro ocorrer, a função retorna -1. Note que utilizamos a função "strlen()" para retornar o tamanho do buffer: strlen(buffer); Recebendo dados: var buffer: array[0..99] of char; begin ZeroMemory(@buffer,100); // Limpa o buffer, É necessário incluir "Windows" na clásula "Uses". recv(sock,buffer,100,0); end. A sintaxe É: recv(sock: Integer; var Buf; tam_buffer: Integer; Flags: Integer); sock: nome do nosso socket; Buf: buffer que irá armazenar os dados recebidos; tam_buffer: tamanho do buffer; flags: valores opcionais que especificam o modo de recebimento. A função, em situação normal, retorna o número de bytes recebidos. Se algum erro ocorrer, a função retorna -1. Vejamos um exemplo: servidor: program servidor; {$APPTYPE CONSOLE} uses SysUtils,Winsock, Windows; // Windows -> para usar ZeroMemory() var wsa: WSADATA; sock: integer; addr: sockaddr_in; buffer: array[0..100] of char; // Buffer para enviar/receber dados envia: string; // Armazenar uma string digitada bytes: integer; // Número de bytes recebidos begin if(WSAStartup($101,wsa) = -1) then begin writeln('Ocorreu um erro ao inicializar o Winsock.'); exit; end; sock := socket(AF_INET,SOCK_STREAM,0); if(sock = -1) then begin writeln('Ocorreu um erro ao criar o socket.'); exit; end; addr.sin_family := AF_INET; addr.sin_port := htons(1234); addr.sin_addr.S_addr := INADDR_ANY; if(bind(sock,addr,sizeof(addr)) = -1) then begin writeln('Ocorreu um erro ao configurar o socket.'); exit; end; if(listen(sock,1) = -1) then begin writeln('Ocorreu um erro ao colocar o socket na escuta.'); exit; end; sock := accept(sock,nil,nil); if(sock = -1) then begin writeln('Ocorreu um erro ao aceitar uma conexão.'); exit; end; bytes := 0; // 0 byte recebido while(bytes <> -1) do // Enquanto o número de bytes retornando for diferente de -1 = conectado begin ZeroMemory(@buffer,100); // Zera buffer recv(sock,buffer,100,0); // Recebe dados do cliente writeln(buffer); // Mostra-os ZeroMemory(@buffer,100); // Limpa o buffer novamente readln(envia); // Lê uma string StrLCopy(buffer,PChar(envia),100); // Copia atÉ 100 caracteres para o buffer send(sock,buffer,strlen(buffer),0); // Envia os dados end; // Encerra closesocket(sock); WSACleanup(); end. cliente: program cliente; {$APPTYPE CONSOLE} uses SysUtils,Winsock, Windows; // Windows -> para usar ZeroMemory() var wsa: WSADATA; sock: integer; addr: sockaddr_in; buffer: array[0..100] of char; // Buffer para enviar/receber dados envia: string; // Armazenar uma string digitada bytes: integer; // Número de bytes recebidos begin if(WSAStartup($101,wsa) = -1) then begin writeln('Ocorreu um erro ao inicializar o Winsock.'); exit; end; sock := socket(AF_INET,SOCK_STREAM,0); if(sock = -1) then begin writeln('Ocorreu um erro ao criar o socket.'); exit; end; addr.sin_family := AF_INET; addr.sin_port := htons(1234); addr.sin_addr.S_addr := inet_addr('127.0.0.1'); if(connect(sock,addr,sizeof(addr)) = -1) then begin writeln('Ocorreu um erro ao conectar-se.'); exit; end; bytes := 0; // 0 byte recebido while(bytes <> -1) do // Enquanto o número de bytes retornando for diferente de -1 = conectado begin ZeroMemory(@buffer,100); // Limpa o buffer readln(envia); // Lê uma string StrLCopy(buffer,PChar(envia),100); // Copia atÉ 100 caracteres para o buffer send(sock,buffer,strlen(buffer),0); // Envia os dados ZeroMemory(@buffer,100); // Limpa o buffer novamente recv(sock,buffer,100,0); // Recebe dados do cliente writeln(buffer); // Mostra-os end; // Encerra closesocket(sock); WSACleanup(); closesocket(sock); WSACleanup(); end. O programa acima É o clássico chat cliente-servidor. Geralmente, exemplos como este são apresentados quando estamos estudando sockets Veremos agora duas funções para trabalharmos com endereços IP e hostname's: 1) gethostbyname() Vamos retomar: addr.sin_addr.S_addr := inet_addr('www.google.com.br'); // INCORRETO! Ocorreria um erro acima pois "www.google.com.br" não É um endereço IP, e sim um hostname. Antes de podermos configurar o socket para se conectar neste host, devemos obter o seu IP utilizando a função gethostbyname(): var ... host: PHostEnt; // PHostEnt = ponteiro para a estrutura hostent addr: sockaddr_in; begin ... host := gethostbyname('www.google.com.br'); if(host = nil) then writeln('Erro ao resolver o host!') else addr.sin_addr := PInAddr(host.h_addr^)^; Declaramos um ponteiro para a estrutura "hostent" que irá armazenar as informações sobre o computador remoto: "host". A função retorna um ponteiro nulo (nil) caso não consiga resolver o hostname. O endereço deste host É armazenando no membro "h_addr" desta estrutura, expresso em network byte. Veja: addr.sin_addr := PInAddr(host.h_addr^)^; A linha acima parece ser um pouco complicada, mas não É . Sabemos que "host" É um ponteiro para a estrutura "hostent". O membro "host.h_addr" É um ponteiro para CHAR que aponta para o endereço IP do host, expresso em network byte. Para acessar o valor de um membro apontado por um pointeiro, fazemos: ponteiro.membro^ que equivale a host.h_addr^; // retorna o endereço apontado pelo membro "h_addr"; No caso acima, como o membro "h_addr" É um ponteiro e não uma simples variável, o seu valor É o endereço para onde ele aponta. PInAddr É um ponteiro global para a estrutura "in_addr"(mesmo tipo do membro "sin_addr"). Quando temos "PInAddr(host.h_addr^)", estamos fazendo com que o ponteiro "PInAddr" aponte para o mesmo endereço que "host.h_addr" aponta, isto É, aquele endereço que armazena o endereço IP do host. A diferença É que o valor deste endereço será obtido como próprio para a estrutura "in_addr" e não mais como "CHAR". AtÉ aí, temos: addr.sin_addr = ENDEREçO apontado por "host.h_addr" já obtido como "in_addr"; No entanto, "sin_addr" requer um valor "in_addr" e não um ponteiro, por isso fazemos: PInAddr(host.h_addr^)^; // Valor "in_addr" do endereço apontado Portanto, quando temos: addr.sin_addr := PInAddr(host.h_addr^)^; Estamos atribuindo ao membro "sin_addr" o valor apontado pelo membro "host.h_addr" convertido para "in_addr". Para compreender melhor o processo, É recomendável o estudo de ponteiros 2) inet_ntoa() Com esta função, podemos transformar um IP expresso em network byte para string. Esta função faz o processo inverso da função inet_addr(). Veja: var ... addr: sockaddr_in; begin ... addr.sin_addr.S_addr = inet_addr("127.0.0.1"); // Converte o IP de string para network byte writeln('O IP e: ' + inet_ntoa(addr.sin_addr)); // Converte de network byte para string É importante notar que, para utilizar a função inet_ntoa(), devemos passar um valor do tipo "in_addr" como parâmetro. Veja outro exemplo: var ... host: PHostEnt; // PHostEnt = ponteiro para a estrutura hostent addr: sockaddr_in; begin ... host := gethostbyname('www.google.com.br'); if(host = nil) then writeln('Erro ao resolver o host!') else writeln('O IP e: ' + inet_ntoa(PInAddr(host.h_addr^)^)); No exemplo acima, tentamos resolver o host "www.google.com.br" e mostrar o seu respectivo IP. Como vimos anteriormente, a expressão "PInAddr(host.h_addr^)^" retorna o endereço IP de um host como valor "in_addr" - justamente o tipo de valor requirido pela função inet_ntoa(). FIM da primeira parte Na continuação do artigo, veremos melhor como trabalhar com a função gethostbyname() e outras funções. Veremos ainda o uso do protocolo UDP. Pois bem, continuemos com o nosso artigo. Abaixo segue uma pequena lista de aspectos abordados no artigo anterior: 1) Vimos o que É necessário para trabalhar com a API do Winsock (WSA) no Delphi; 2) Foram mostrados os passos que devem ser seguidos para criar um socket simples, abordando, exclusivamene, o protocolo TCP; 3) Ainda com base no protocolo TCP, foram ilustrados exemplos de um programa que atuava como cliente e um outro servidor; 4) Utilizando as funções send() e recv(), aprendemos a como enviar e receber dados, respectivamente atravÉs de um socket; 5) Algumas noções de variáveis, estruturas, funções e conversões; Neste último item, ficou pendente a explicação do uso de função gethostbyname() aplicada em outras situações, alÉm de outras funções do ramo, como getservbyport(), que tambÉm será aborda nesta parte do artigo. Para começar, vamos retomar o último exemplo da primeira parte do tutorial: var ... host: PHostEnt; // PHostEnt = ponteiro para a estrutura hostent addr: sockaddr_in; begin ... host := gethostbyname('www.google.com.br'); if(host = nil) then writeln('Erro ao resolver o host!') else writeln('O IP e: ' + inet_ntoa(PInAddr(host.h_addr^)^)); No exemplo acima, tentamos resolver o host "www.google.com.br" e mostrar o seu respectivo IP. Como vimos anteriormente, a expressão "PInAddr(host.h_addr^)^" retorna o endereço IP de um host como valor "in_addr" - justamente o tipo de valor requirido pela função inet_ntoa(). Como se sabe, devemos especificar dados sobre um host remoto para que seja possível uma conexão entre o computador local e este. Sabemos ainda que, para tanto, devemos utilizar uma estrutura denominada "sockaddr_in", que contÉm três principais membros: sin_family -> indica a família do socket; sin_port -> indica a porta na qual o socket irá atuar; sin_addr -> indica o endereço utilizado pelo socket, seja este local ou remoto; Dando ênfase ao último, sabemos que o utilizamos quando queremos, por exemplo, estabelecer uma conexão entre o computador local e um outro remoto cujo IP É 200.123.123.123: addr.sin_addr.S_addr = inet_addr('200.123.123.123'); Como se sabe, com o auxílio da função inet_addr(), podemos converter um endereço IP escrito na forma de string ('200.123.123.123') para seu valor correspondente na forma in_addr (network byte) que É a adequada. Existem casos, no entanto, em que não sabemos o endereço IP do computador remoto, e pior ainda: às vezes, atÉ sabemos, entretanto, este IP pode ser dinâmico, isto É, não-fixo. Em situações assim, se tivÉssimos que tomar como base apenas o endereço IP de hosts remotos, teríamos grande dificuldades de comunicação. Felizmente, podemos utlizar o "hostname" de um computador como referência, dessa forma, não importa qual endereço IP que este computador esteja utlizando, pois, ao contrário de endereços IPs, o hostname É fixo, ou seja, mesmo que o endereço IP de um servidor seja alterado, atravÉs do seu hostname podemos acessá-lo. Mas a questão É: como aplicar isso em nossos programas? É para responder a essa pergunta que este artigo foi começado com último exemplo da parte 1 do tutorial xD Vamos fazer uma análise rápida no seguinte trecho: var wsa: WSADATA; // Para inicializar o winsock host: PHostEnt; // PHostEnt = ponteiro para a estrutura hostent begin if(WSAStartup($101,wsa) = -1) then exit; // Encerra se falhar host := gethostbyname('www.google.com.br'); end. É um exemplo um pouco que repetitivo, eu diria. Mas continuemos: var wsa: WSADATA; host: PHostEnt; // PHostEnt = ponteiro para a estrutura hostent begin if(WSAStartup($101,wsa) = -1) then exit; host := gethostbyname('www.google.com.br'); if(host = nil) then // Erro else // Host resolvido com sucesso xD end. Quando a função gethostbyname() falha em resolver um hostname, um ponteiro nulo (nil) É retornado. Fazemos o tratamento de erros com base nessa propriedade. var wsa: WSADATA; host: PHostEnt; // PHostEnt = ponteiro para a estrutura hostent addr: sockaddr_in; // Uma estrutura do tipo sockaddr_in begin if(WSAStartup($101,wsa) = -1) then exit; host := gethostbyname('www.google.com.br'); if(host = nil) then exit // Encerra se falhar else addr.sin_addr := PInAddr(host.h_addr^)^; end. Com base no código acima, você consegue responder a questão inicial xD ? O que exatamente ocorre É que, ao utilizar a função gethostbyname() passando como parâmetro o hostname 'www.google.com.br', esta função tentará resolver este hostname, isto É, obter seu endereço IP, seu nome oficial, dentre outros dados. Como vimos na parte anterior do texto (e no início desta), a combinação PInAddr(host.h_addr^)^ nos retorna o endereço IP de um host já convertido para "in_addr" que nos possibilita usá-lo como valor para o membro "sin_addr" da estrutura "sockaddr_in". A partir deste ponto, já se pode utilizar a função connect(), por exemplo, para criar uma conexão com o computador cujo hostname utlizamos. Veja o exemplo: program exemplo; {$APPTYPE CONSOLE} uses SysUtils,Winsock; var wsa: WSADATA; sock: integer; addr: sockaddr_in; host: PHostEnt; begin if(WSAStartup($101,wsa) = -1) then begin writeln('Ocorreu um erro ao inicializar o Winsock.'); exit; end; sock := socket(AF_INET,SOCK_STREAM,0); if(sock = -1) then begin writeln('Ocorreu um erro ao criar o socket.'); exit; end; host := gethostbyname('www.google.com.br'); if(host = nil) then begin writeln('Ocorreu um erro ao resolver o hostname.'); exit; end; addr.sin_family := AF_INET; addr.sin_port := htons(80); addr.sin_addr := PInAddr(host.h_addr^)^; if(connect(sock,addr,sizeof(addr)) = -1) then begin writeln('Erro ao se conectar.'); exit; end else begin writeln('Conectado xD'); closesocket(sock); end; WSACleanup(); end. Um exemplo bem simples. Tentamos resolver o hostname 'www.google.com.br' e, posteriormente, utilizamos a função connect() para que o programa tente conectar-se ao host pela porta 80. Se a conexão falhar, o programa simplesmente encerra, do contrário, uma mensagem avisa que a conexão foi feita com sucesso e o socket É fechado logo em seguida. Uma outra função muito interessante existente na API do Winsock É a getservbyport(). Com esta função, podemos obter o serviço associado a uma determinada porta, como por exemplo, o serviço HTTP que geralmente roda sob a porta 80. Vejamos um exemplo de uso: program exemplo; {$APPTYPE CONSOLE} uses SysUtils,Winsock; var wsa: WSADATA; serv: PServEnt; // Ponteiro para a estrutura servent begin if(WSAStartup($101,wsa) = -1) then begin writeln('Ocorreu um erro ao inicializar o Winsock.'); exit; end; serv := getservbyport(htons(80),'tcp'); // Tenta obter o servico associado à porta 80 tcp if(serv = nil) then // Falha ao obter writeln('Servico desconhecido') else writeln('Servico: ' + serv.s_name); // Imprime o nome do serviço WSACleanup(); end. Neste exemplo, tentamos obter o nome do serviço associado à porta 80 sob o protocolo TCP. Para tal, usamos a função getservbyport(), cuja sintaxe simpificada É: getservbyname (porta_em_network_byte,protocolo_string); porta_em_network_byte: É a porta utilizada pelo serviço que queremos obter, convertida para network byte (htons()); protocolo_string: É o protocolo sob o qual o serviço atua; Geralmente, quando a função retorna um ponteiro nulo, É porque não foi encontrado um serviço associado à porta especificada. No entanto, a função tambÉm pode retornar este tipo de ponteiro mesmo que o serviço exista. Veja como isso É possível: program exemplo; {$APPTYPE CONSOLE} uses SysUtils,Winsock; var wsa: WSADATA; serv: PServEnt; begin { Comentamos esta parte do código responsável pela inicialização do Winsock; Como se sabe, sem esta inicialização prÉvia, todas as outras funções dependentes retornarão em erro, assim como getservbyname() if(WSAStartup($101,wsa) = -1) then begin writeln('Ocorreu um erro ao inicializar o Winsock.'); exit; end; } serv := getservbyport(htons(80),'tcp'); if (serv = nil) then writeln('Servico desconhecido.') else writeln('Servico: ' + serv.s_name); WSACleanup(); end. Provalmente, ao executar o primeiro código, a saída do programa deveria ter sido: Servico: http E neste último, a saída seria: Servico desconhecido Como se vê no código, o programa não chama pela função WSAStartup() e, por esta razão, a função getservbyname() retornou em erro. Se, por algum motivo, o código fosse aplicado em um programa, o bug estaria evidenciado. Para resolver este impasse, podemos utitilizar uma função muito útil: WSAGetLastError(), que nos retorna um inteiro (integer) com último código de erro ocorrido no uso do winsock. Veja: program exemplo; {$APPTYPE CONSOLE} uses SysUtils,Winsock; var wsa: WSADATA; serv: PServEnt; begin { Comentamos esta parte do código responsável pela inicialização do Winsock; Como se sabe, sem esta inicialização prÉvia, todas as outras funçõa dependentes retoraram em erro, assim como getservbyname() if(WSAStartup($101,wsa) = -1) then begin writeln('Ocorreu um erro ao inicializar o Winsock.'); exit; end; } serv := getservbyport(htons(80),'tcp'); if (serv = nil) and (WSAGetLastError() = 0) then writeln('Servico desconhecido.') else if(serv = nil) and (WSAGetLastError() <> 0) then begin writeln('Erro na funcao getservbyport(). ID: ' + IntToStr(WSAGetLastError())); exit end else writeln('Servico: ' + serv.s_name); WSACleanup(); end. Agora, o programa não mais mostra que o serviço É desconhecido, mas sim que um erro ocorreu e a ID deste erro. Quando a função WSAGetLastError() retorna ZERO, É sinal que nenhum erro ocorreu atÉ o ponto em que foi chamada. Então, a lógica seria: Se "serv" = nil e "WSALastError()" = 0 -> não ocorreram erros no winsock, no entanto, a função getservbyport() não conseguiu obter o serviço; Se "serv" = nil e "WSALastError" não for ZERO, ocorreu um erro no uso do Winsock; Se "serv' não retornar um ponteiro nulo, o serviço foi obtido com sucesso. No exemplo, foram utilizados if, elseif e else, mas você pode reorganizar a lógica tornando o código mais legível e bonito xD Uma curiosidade: existe um arquivo no qual existem todos os serviços/portas reconhecidos pelo sistema. Este arquivo É acessado pela função getservbyport() para nos retornar um serviço desejado. No Windows, sua localização É: C:\WINDOWS\SYSTEM32\Drivers\etc\services. Ainda falando sobre erros gerados durante o uso do winsock, a função WSAGetLastError() possui uma outra oposta: WSASetLastError. Esta última, por sua vez, É utilizada para definir a ID do último erro ocorrido. Exemplo: WSASetLastError(0); Na linha acima, simplesmente zeramos o status de erro. No final desta página, postarei uma tabela contendo os principais códigos de erro, suas constantes e seus respectivos significados. Voltando a falar um pouco sobre hostname's. Existe um endereço denominado loopback, que É o próprio endereço da máquina local, geralmente atribuído ao hostname "localhost". No entanto, podemos ter um nome alternativo para esse endereço, como por exemplo, o nome da máquina. É possível obter este nome atravÉs de uma função bem simples: gethostname(). Veja: program exemplo; {$APPTYPE CONSOLE} uses SysUtils,Winsock; var wsa: WSADATA; nome_local: array[0..99] of char; begin if(WSAStartup($101,wsa) = -1) then begin writeln('Ocorreu um erro ao inicializar o Winsock.'); exit; end; if(gethostname(nome_local,100) = 0) then writeln('Host Name local: ' + nome_local) else writeln('Erro ao obter o hostname local.'); WSACleanup(); end. Observe que declaramos o array de char "nome_local" de 100 caracteres. É neste buffer em que o hostname local do computador será armazenado. A sintaxe da função É: gethostname(buffer,tamanho); buffer: É um array de char no qual o hostname local será armazenado; tamanho: É o tamanho do buffer em bytes Se a função falhar, o valor retornado É -1; em caso de êxito, a função retornará ZERO. Muito bem. Vamos abordar o uso do protocolo UDP atravÉs do winsock. PorÉm, antes de fazê-lo, vejamos algumas diferenças básica entre os protocolo TCP e este último: Protocolo TCP 1) É um protocolo orientado a conexão, isto É, um fluxo de dados entre cliente/servidor só poderá ser feito caso haja uma conexão entre estes dois hosts; 2) Por retransmitir pacotes de dados perdidos, É considerado um protocolo confiável no aspecto de entrega/recibo de dados; 3) Como conseqüência das duas características acima, o protocolo TCP É mais lento do que o UDP Protocolo UDP 1) Não É orientado a conexão, ou seja, são somente necessárias as informações de porta/endereço remoto para que dados possam ser enviados/recebidos 2) Por não ser um protocolo dependente de conexão, não há garantia de entrega de um determinado pacote enviado (ao contrário do TCP) o que torna o protocolo não-confiável. 3) Em contra-partida, o protocolo É mais rápido do que o TCP quando se trata de fluxo de dados. Uma vez em que o protocolo UDP não É orientado a conexão, podemos deduzir que o uso da funções connect() e listen()/accept() são desnecessárias em sua aplicação. Como foi dito anteriormente, basta saber o endereço remoto de um host e sua porta para que possamos enviar dados para este, utilizando o protocolo UDP. O mesmo se aplica à entrada de dados. Com o protocolo TCP, utitlizamos, respectivamente, as funções send() e recv(). No protocolo UDP, as coisas mudam um pouco: utiliza-se sendto() para o envio de dados, e recvfrom() para recebermos dados. Segue abaixo um código que mostra a criação de um socket que trabalhe sob o protocolo UDP: program exemplo; {$APPTYPE CONSOLE} uses SysUtils,Winsock; var wsa: WSADATA; sock: integer; addr: sockaddr_in; buffer: array [0..99] of char; begin if(WSAStartup($101,wsa) = -1) then begin writeln('Ocorreu um erro ao inicializar o Winsock.'); exit; end; sock := socket(AF_INET,SOCK_DGRAM,0); // Utilizamos SOCK_DGRAM e não SOCK_STREAM if(sock = -1) then begin writeln('Ocorreu um erro ao criar o socket.'); exit; end; addr.sin_family := AF_INET; addr.sin_port := htons(1234); addr.sin_addr.S_addr := inet_addr('200.123.123.123'); buffer := 'Enviando um string atravÉs do protocolo UDP!'; if(sendto(sock,buffer,strlen(buffer), 0,addr,sizeof(addr)) = -1) then begin writeln('Erro ao enviar dados!'); exit; end; closesocket(sock); WSACleanup(); end. O primeiro ponto a ser observado no código acima É que, ao invÉs de utilizarmos SOCK_STREAM como segundo parâmetro da função socket(), utilizamos SOCK_DGRAM que É a constante correta para o protocolo UDP. Nota-se que, no código, apenas especificamos as informações do host e utilizamos a função sendto() para enviar a string contida no array "buffer" para este computador (200.123.123.123). A sintaxe da função sendto() É bastante semelhante à da função send(), com exceção apenas dos dois últimos parâmetros: send(sock: Integer; var Buf; tam_buffer: Integer; Flags: Integer, addr: sockaddr_in; tam_addr: integer); sock: nome do nosso socket; Buf: array de caracteres ou um ponteiro para char que contÉm os dados que serão enviados; tam_buffer: número de bytes que serão enviados; flags: valores opcionais que especificam o modo de envio. addr: variável do tipo sockaddr_in que contÉm as informações de endereço/porta do host remoto; tam_addr: tamanho da estrutura sockaddr_in; Assim como a função send(), sendto() tambÉm retorna o número de bytes enviados. Se algum erro ocorrer, a função retorna -1. Vejamos um exemplo de um servidor UDP: program exemplo; {$APPTYPE CONSOLE} uses SysUtils,Winsock,Windows; // ZeroMemory -> definida em Windows var wsa: WSADATA; sock: integer; addr,addr_remoto: sockaddr_in; buffer: array [0..99] of char; tam_addr: integer; begin if(WSAStartup($101,wsa) = -1) then begin writeln('Ocorreu um erro ao inicializar o Winsock.'); exit; end; sock := socket(AF_INET,SOCK_DGRAM,0); if(sock = -1) then begin writeln('Ocorreu um erro ao criar o socket.'); exit; end; addr.sin_family := AF_INET; addr.sin_port := htons(1234); addr.sin_addr.S_addr := INADDR_ANY ; if(bind(sock,addr,sizeof(addr))=-1) then begin writeln('Erro na funcao bind()'); exit; end; ZeroMemory(@buffer,100); tam_addr := sizeof(addr); if(recvfrom(sock,buffer,100,0,addr_remoto,tam_addr ) = -1) then begin writeln('Erro na funcao recvfrom()'); exit; end; writeln('Dados recebidos!' + #10); writeln('IP: ' + inet_ntoa(addr_remoto.sin_addr)); writeln('Porta: ' + inttostr(ntohs(addr_remoto.sin_port))); writeln('Dados:' + buffer); closesocket(sock); WSACleanup(); end. Neste exemplo, o programa utiliza a função bind() para que, posteriormente, possau utilizar a porta 1234 para receber dados de um possível cliente. Vamos ver mais detalhadamente: addr,addr_remoto: sockaddr_in; buffer: array [0..99] of char; tam_addr: integer; Observe que o código há pontos importantes em negrito. AlÉm da comum declaração da variável "addr", declare-se outra variável, do mesmo tipo (sockaddr_in), denominada "addr_remoto". Esta variável será utilizada como parâmetro para a função recvfrom(), seu uso será explicado mais adiante. A variável "buffer" É um array de caracteres de 100 bytes e será utilizada para armazenar os dados recebidos atravÉs da função recvfrom(). Por fim, declaramos "tam_addr" para passarmos seu valor como último parâmetro da função. O valor desta variável deve ser o tamanho da estrutura "sock_addr" em bytes. if(bind(sock,addr,sizeof(addr))=-1) then begin writeln('Erro na funcao bind()'); exit; end; Nada de novo. Apenas configura-se localmente o socket de tal forma que este utilize a porta 1234 para receber/enviados dados. ZeroMemory(@buffer,100); tam_addr := sizeof(sockaddr_in); if(recvfrom(sock,buffer,100,0,addr_remoto,tam_addr ) = -1) then begin writeln('Erro na funcao recvfrom()'); exit; end; Neste trecho, preenche-se com zero todo o buffer, ou podemos simplesmente dizer que limpa-se o "buffer" para que ele possa armazenar ocasionais dados. Na próxima linha, estamos atribuindo à variável "tam_addr" o tamanho, em bytes, da estrutura "sockaddr_in". E, desmembrando um pouco mais o código, temos: if(recvfrom(sock,buffer,100,0,addr_remoto,tam_addr ) = -1) then begin writeln('Erro na funcao recvfrom()'); exit; end; É no trecho acima que, de fato, aguardamos por dados vindos de algum cliente. Vejamos a sintaxe: recvfrom(sock: Integer; var Buf; tam_buffer: Integer; flags: Integer, addr:sockaddr_in, var tam); sock: nome do nosso socket; Buf: buffer que irá armazenar os dados recebidos; tam_buffer: tamanho do buffer; flags: valores opcionais que especificam o modo de recebimento. addr: variável do tipo sockaddr_in que contÉm as informações de endereço/porta do host remoto; tam_addr: variável que armazena o tamanho da estrutura sockaddr_in; A função, assim como recv(), retornará o número de bytes recebidos, exceto quando algum erro ocorrer, onde o valor -1 É retornado. Para finalizar: writeln('Dados recebidos!' + #10); writeln('IP: ' + inet_ntoa(addr_remoto.sin_addr)); writeln('Porta: ' + inttostr(ntohs(addr_remoto.sin_port))); writeln('Dados:' + buffer); Nesta parte, apenas são mostrados informações sobre um cliente remoto que enviou determinados dados: ip, porta e os dados em si. A função inet_ntoa() você já conhece: É responsável por transformar um IP expresso em network byte para string. Uma função que ainda não foi abordada É ntohs(). Esta faz o trabalho inverso ao da função htons(). Enquanto esta última transforma um valor denominado host byte order para network byte order, a função ntohs() obtÉm um valor em network byte e o transforma em host byte order. Network byte order É o tipo de valor utilizado para comunicação, sobretudo, nas estruturas de sockets, no qual o byte mais significativo (tambÉm chamado octeto) É o primeiro. Em host byte order, o byte menos significativo É o primeiro. Exemplo: Valor em network byte order: 5376 Valor em host byte order: 21 Vale lembrar que as funções htons() e ntohs() retornam valores numÉricos (de 2 bytes). Por esta razão, utilizamos IntToStr() - para conveter de inteiro para string - na seguinte linha: writeln('Porta: ' + inttostr(ntohs(addr_remoto.sin_port))); Bem, antes de encerrar esta parte do artigo, irei disponibilizar uma tabela contendo os tipos mais comuns de erros gerados pelo winsock, sobretudo, para serem usados em conjunto com as funções WSAGetLastError() e WSASetLastError(): Código Constante Significado WSA_INVALID_HANDLE 6 Identificador inválido. WSA_NOT_ENOUGH_MEMORY 8 Memória insuficiente. WSA_NOT_ENOUGH_MEMORY 8 Memória insuficiente. WSA_INVALID_PARAMETER 87 Algum parâmetro inválido foi passado a uma função. WSAEACCES 10013 Permissão negada. WSAEFAULT 10014 Endereço inválido. WSAENOTSOCK 10038 Uma função foi utilizada com um socket inválido. WSAEPROTOTYPE 10041 O tipo de socket não suporta o uso de uma determinada função. WSAENOPROTOOPT 10042 Protocolo inválido especificado. WSAESOCKTNOSUPPORTM 10044 Tipo de socket não suportado. WSAEPFNOSUPPORT 10046 Família de protocolo não suportada. WSAEADDRINUSE 10048 Endereço já em uso. WSAEADDRNOTAVAIL 10049 Erro ao atribuir endereço requisitado. WSAECONNRESET 10054 Conexão resetada pelo host remoto. WSAEISCONN 10056 Socket já conectado. WSAENOTCONN 10057 Socket não conectado. WSAECONNREFUSED 10061 Conexão recusada. WSANOTINITIALISED 10093 O winsock não foi inicializado. A tabela acima está bem simplificada.
  5. O registro do windows é onde todas as configurações são guardadas, tanto do próprio windows quanto da maioria dos outros softwares." Em que se aplica a utilização do registro? Se aplica simplismente a guardar informações e alterar informações de configurações do sistema operacional. Agora vamos por a mão na massa ou melhor no teclado. Escrevendo e lendo Strings no registro Escrevendo var Reg: TRegistry; begin Reg := TRegistry.Create; try { Define a chave-raiz do registro } Reg.RootKey := HKEY_CURRENT_USER; { Abre a chave (path). Se não existir, cria e abre. } Reg.OpenKey('MeuProgramaProgmaster\Configuração', true); { Escreve uma string } Reg.WriteString('Nome', 'teste......'); finally Reg.Free; end; end; Lendo var Reg: TRegistry; nome : string; begin Reg := TRegistry.Create; try Reg.RootKey := HKEY_CURRENT_USER; if Reg.KeyExists('MeuProgramaProgmaster\Configuração') then begin Reg.OpenKey('MeuProgramaprogmaster\Configuração', false); if Reg.ValueExists('Nome') then nome := Reg.ReadString('Nome') else ShowMessage('Não existe valor com o nome "Nome"'); end else ShowMessage('Não existe a chave no registro'); finally Reg.Free; end; end; Escrevendo e lendo Inteiros no registro Escrevendo var Reg: TRegistry; begin Reg := TRegistry.Create; try { Define a chave-raiz do registro } Reg.RootKey := HKEY_CURRENT_USER; { Abre a chave (path). Se não existir, cria e abre. } Reg.OpenKey('MeuProgramaProgmaster\Configuração', true); { Escreve um Inteiro/integer } Reg.WriteInteger('Numero', 76); finally Reg.Free; end; end; Lendo var Reg: TRegistry; numero : integer; begin Reg := TRegistry.Create; try Reg.RootKey := HKEY_CURRENT_USER; if Reg.KeyExists('MeuProgramaProgmaster\Configuração') then begin Reg.OpenKey('MeuProgramaprogmaster\Configuração', false); if Reg.ValueExists('Numero') then numero := Reg.ReadInteger('Numero') else ShowMessage('Não existe valor com o nome "Numero"'); end else ShowMessage('Não existe a chave no registro'); finally Reg.Free; end; end; Escrevendo e lendo dados binarios no registro Escrevendo var Reg: TRegistry; Ficha: TFicha; begin { Coloca alguns dados na variável Ficha } Ficha.Codigo := 1; Ficha.Nome := 'Jefferson Farias'; Ficha.DataCadastro := StrToDate(FormatDateTime('dd/m/yyyy',now)); Reg := TRegistry.Create; try { Define a chave-raiz do registro } Reg.RootKey := HKEY_CURRENT_USER; { Abre uma chave (path). Se não existir cria e abre. } Reg.OpenKey('Cadastro\Pessoas\', true); { Grava os dados (o registro) } Reg.WriteBinaryData('Dados', Ficha, SizeOf(Ficha)); finally Reg.Free; end; end; Lendo var Reg: TRegistry; Ficha: TFicha; codigo : integer; nome : string; data : TDate; begin Reg := TRegistry.Create; try { Define a chave-raiz do registro } Reg.RootKey := HKEY_CURRENT_USER; { Se existir a chave (path)... } if Reg.KeyExists('Cadastro\Pessoas') then begin { Abre a chave (path) } Reg.OpenKey('Cadastro\Pessoas', false); { Se existir o valor... } if Reg.ValueExists('Dados') then begin { Lê os dados } Reg.ReadBinaryData('Dados', Ficha, SizeOf(Ficha)); codigo := Ficha.Codigo; nome := Ficha.Nome; Data := Ficha.DataCadastro; end else ShowMessage('Valor não existe no registro.') end else ShowMessage('Chave (path) não existe no registro.'); finally Reg.Free; end; end; Colocando sua aplicação para auto iniciar com o windows Insira o seguinte código em um tbutton ou em um TTimer var Reg: TRegistry; S: string; begin Reg := TRegistry.Create; S:=ExtractFileDir(Application.ExeName)+'\'+ExtractFileName(Application.ExeName); Reg.rootkey:=HKEY_LOCAL_MACHINE; Reg.Openkey('SOFTWARE\MICROSOFT\WINDOWS\CURRENTVERSION\RUN',false); Reg.WriteString('Nome_Sistema',S); Reg.closekey; Reg.Free; Observação: Para usar a classe TRegistry é nessessário adicionar Registry na cláusula Uses
  6. 1) O que é Code Injetion?? Code injection é o ato de introduzir rotinas a um processo remoto, de modo a mudar o sua funcionabilidade. No caso, nós simplesmente iremos introduzir uma rotina e fazer o processo remoto executá-la, sem interferir em sua funcionabilidade. 2) Entendendo a lógica Bom, para os que não estão muito familiarizados com manipulação de memória eu sugiro que estudem um pouco mais antes de tentar entender completamente o conceito de Code Injection. Bom, como estaremos mexendo com a memória de outro processo, quando formos usar algum parametro que está em NOSSO programa, não podemos simplesmente tentar chamá-lo de um processo para outro. Então nós escreveremos na memória do target (tratarei o processo remoto como "target" por questão de conveniência ao escrever) tudo que ele for usar que está em nosso processo. A rotina que injetaremos no target deve estar numa DLL e sendo exportada. Outro fator importante são os privilégios necessários para o nosso programa executar essa ação. Se tentarmos fazer isso com privilégios normais, é provável que, em algum momento do código, haja problemas de permissão. Para evitar quaisquer eventuais problemas, definiremos o privilégio como o de debug. Depois de executada a rotina, não temos mais porque deixar os parametros utilizados na execução da mesma no target, isso só iria ocupar espaço, então nós liberaremos toda a memória escrita no processo remoto. 3) O código Bom, antes de mais nada, vamos criar a estrutura que armazenará os parametros utilizados na execução da rotina. Mas que parametros são esses afinal?? Esses parametros devem incluir as funções LoadLibraryA (para carregar a DLL na memória) e GetProcAddress (para saber o endereço de memória da nossa rotina quando for injetada no target). Além dessas funções, também devemos por nessa estrutura os parametros de cada uma: o nome da DLL e o nome da rotina. Tendo isso em mente, definam a estrutura do seguinte modo: Código: type TInjectParams = record LoadLibrary: function (lpLibFileName: PAnsiChar): Cardinal; stdcall; LibName: PAnsiChar; GetProcAddress: function (hModule: Cardinal; lpProcName: PAnsiChar): Pointer; stdcall; ProcName: PAnsiChar; end; PInjectParams = ^TInjectParams; Agora, como na função final do Code Injection o usuário irá apenas especificar o nome do processo, da dll e da rotina a ser injetada, precisaremos de uma função que pega o Handle do processo a partir do nome dele. Para isso adicionem "tlhelp32" no uses e utilizem o seguinte método: Código: function GetProcess(proc: string): Cardinal; var Snap: THandle; pe: TProcessEntry32; begin Snap:=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0); if Snap = 0 then Exit; if Process32First(Snap,pe) then begin repeat if proc = pe.szExeFile then begin Result:=pe.th32ProcessID; break; end; until not Process32Next(Snap,pe) end end; Como dito acima, será necessário escrever na memória do target os parametros e a rotina em si. Para isso vamos criar duas funções, uma para escrever os parametros (WriteString) e uma para escrever a rotina (WriteData). Notem que elas são funções e não procedimentos pois precisamos do endereço de memória onde esses dados foram escritos para chamá-los a partir do target. Código: function WriteString(Process: Cardinal; s: string): Pointer; var bytes: Cardinal; begin Result:=VirtualAllocEx(Process, nil, length(s) + 1, MEM_COMMIT or MEM_RESERVE, PAGE_EXECUTE_READWRITE); WriteProcessMemory(Process, Result , pchar(s), length(s) + 1, bytes); end; Código: function WriteData(Process, dwSize: Cardinal; RemoteData: pointer): pointer; var bytes: Cardinal; begin Result:=VirtualAllocEx(Process, nil, dwSize, MEM_COMMIT or MEM_RESERVE, PAGE_EXECUTE_READWRITE); WriteProcessMemory(Process, Result, RemoteData, dwSize, bytes); end; Agora precisaremos de uma rotina para executar a rotina injetada no target. Vamos chamá-la de RemoteFunction. Essa rotina precisará dos parametros injetados no target para executar a rotina desejada e, como os parametros já estarão definidos na memória, utilizaremos PInjectParams para pegar os valores. Código: procedure RemoteFunction(Parametros: PInjectParams); stdcall; var proc: procedure; stdcall; begin proc:=Parametros^.GetProcAddress(Parametros^.LoadLibrary(Parametros^.LibName),Parametros^.ProcName); proc; end; Agora precisaremos de um método para saber o tamanho dessa rotina descrita acima, pois na hora de escrevê-la no target é necessário por o tamanho. Para isso faremos outro método logo abaixo desse, e depois é só pegar o endereço dele e subtrair do endereço da rotina "RemoteFunction". Código: procedure RemoteFunctionEnd; stdcall; begin; end; Como já disse anteriormente, é necessário mudar o privilégio para evitar eventuais problemas. Utilizem o seguinte método: Código: procedure ChangePrivilege(szPrivilege: PChar; fEnable: Boolean); var NewState: TTokenPrivileges; luid: TLargeInteger; hToken: THandle; ReturnLength: DWord; begin OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, hToken); LookupPrivilegeValue(nil, szPrivilege, luid); NewState.PrivilegeCount := 1; NewState.Privileges[0].Luid := luid; if (fEnable) then NewState.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED else NewState.Privileges[0].Attributes := 0; AdjustTokenPrivileges(hToken, False, NewState, SizeOf(NewState), nil, ReturnLength); CloseHandle(hToken); end; Pronto, agora só falta montar a função final, mas depois de tudo isso, para quem realmente entendeu a lógica fica fácil. Não vou comentar parte por parte dela no post, pois o código já está comentado. Código: procedure Inject(process, dll, code: string); var PID, hProcess, ThreadId, ThreadHandle: Cardinal; RemoteData,RemoteFunc,LibFileName,ProcName: pointer; Parametros: TInjectParams; begin {Pega o Handle do processo} PID:=GetProcess(Process); {Seta o privilégio de debug} ChangePrivilege('SeDebugPrivilege', True); {Abre o processo} hProcess := OpenProcess(PROCESS_ALL_ACCESS, False, PID); {Define os parâmetros que serão usados para executar a procedure} LibFileName:=WriteString(hProcess, dll); ProcName:=WriteString(hProcess, code); Parametros.LoadLibrary:=GetProcAddress(GetModuleHandle('kernel32'), 'LoadLibraryA'); Parametros.LibName:=LibFileName; Parametros.GetProcAddress:=GetProcAddress(GetModuleHandle('kernel32'), 'GetProcAddress'); Parametros.ProcName:=ProcName; {Abre um novo espaço de memória para guardar os parâmetros} RemoteData:=WriteData(hProcess, sizeof(Parametros), @Parametros); {Abre um novo espaço de memória para guardar a procedure} RemoteFunc:=WriteData(hProcess, integer(@RemoteFunctionEnd) - integer(@RemoteFunction), @RemoteFunction); {Cria a thread que executará a procedure} ThreadHandle:=CreateRemoteThread(hProcess, nil, 0, RemoteFunc, RemoteData, 0, ThreadId); WaitForSingleObject(ThreadHandle, 3000); {Libera as memórias criadas} VirtualFreeEx(hProcess,LibFileName,0,MEM_RELEASE); VirtualFreeEx(hProcess,ProcName,0,MEM_RELEASE); VirtualFreeEx(hProcess,RemoteFunc,0,MEM_RELEASE); VirtualFreeEx(hProcess,RemoteData,0,MEM_RELEASE); end; 3) Pondo tudo em prática Criem uma DLL e adicionem uma rótina simples, como: Código: procedure Msg; begin MessageBox(0,'Teste','Code Injected',mb_ok); end;**Não esqueçam de declarar "windows" no uses Depois exportem essa função. Digitem o seguinte abaixo de todas as eventuais rotinas que sua DLL tem: Código: exports Msg;Compilem essa DLL com o nome 'progmaster.DLL'. Depois criem um projeto novo e coloquem um botão, além de todo o procedimento descrito acima. No onClick desse botão coloquem: Código: Inject('notepad.exe','C:\Projects\progmaster.DLL','Msg');**Não se esqueçam de ao invés de "C:\Projects\progmaster.DLL", colocar o path da sua dll e de abrir o notepad antes de clicar no botão Espero ter sido claro na explicação... Não me aprofundei muito na parte teórica pois teria que falar de assembly e ia acabar ficando ruim pra mim, vendo que eu não sou expert nessa linguagem, deixo este trabalho para quem tem mais experiência e estiver disposto!!
  7. function SoNumeros(Const Texto:String):String; // // Remove caracteres de uma string deixando apenas numeros // var I: integer; S: string; begin S := ''; for I := 1 To Length(Texto) Do begin if (Texto in ['0'..'9']) then begin S := S + Copy(Texto, I, 1); end; end; result := S; end;
  8. Pra quem precisar fazer com que uma rotina seja repetida a cada intervalo de tempo e não quer usar o componente TTimer eu criei esse código. Alguns vão me perguntar: "Não é mais fácil simplesmente usar o componente?" Sim! é mais fácil, porém com essas funções vc não vai precisar declarar todos os recursos do componente TTimer, utilizando menos memória (é pouca coisa mas sempre temos que visar a melhor performace para o sistema)} //Declare as procedures que vamos utilizar procedure Timer(Timer: Integer; Intervalo: Integer; Ativado: Boolean); procedure TimerMensagem(var Msg: TWMTimer); message WM_TIMER; procedure TForm1.TimerMensagem(var Msg: TWMTimer); begin //Aqui vc digita qualquer rotina que deseja executar (mesma coisa que o evento OnTimer do componente TTimer) end; procedure TForm1.Timer(Timer: Integer; Intervalo: Integer; Ativado: Boolean); begin if Ativado then SetTimer(Handle, Timer, Intervalo, nil) else KillTimer(Handle, Timer); end; //Ativando o nosso timer Timer(1, //ID do timer, pode ser qualquer número inteiro 100, //Intervalo de repetição True); //Se passar True ativa o timer, se passar false desativa //Desativando o nosso timer Timer(1, 100, False); {bom galera é isso aí, espero que tenham gostado da dica. Por favor comentem, isso nos inspira a cada vez postar mais dicas}
  9. Um computador é constituído de quatro unidades básicas: unidade de entrada, unidade de saída, unidade de processamento central e memória. Como indica sua denominação, uma unidade de entrada é um dispositivo que permite que o usuário interaja com o computador, fornecendo-lhe dados e informações que serão processadas, sendo o teclado o seu exemplo mais trivial. Uma unidade de saída, por seu turno, serve para que sejam fornecidos ao usuário do computador os resultados do processamento realizado. O monitor de vídeo e uma impressora são exemplos de unidades de saída. A unidade central de processamento é responsável por todo o processamento requerido, sendo muito conhecida por cpu, acrossemia de central processing unit. Já a memória armazena dados e informações que serão utilizados no processamento, armazenamento temporário, pois quando o computador é desligado tudo que está nela armazenado deixa desê-lo (dizemos que toda a memória é "apagada"). Linguagem de máquina Evidentemente, há a necessidade de que as unidades que compõem um computador se comuniquem umas com as outra. Por exemplo, um dado fornecido pelo teclado deve ser armazenado na memória; para a cpu realizar uma operação aritmética, ela vai “buscar” valores que estão armazenados na memória, e assim por diante. Para que haja comunicação entre as unidades do computador é necessário que se estabeleça uma linguagem. Os seres humanos se comunicam basicamente através de duas linguagens:a linguagem escrita e a fala. Uma comunicação através de uma linguagem escrita é constituída de parágrafos, os quais contêm períodos, que contêm frases, que são constituídas de palavras, sendo cada uma das palavras formadas por letras e esta sequência termina aí. Assim, uma letra é um ente indivisível da linguagem escrita e, em função disto, é chamada símbolo básico desta linguagem. Este exemplo foi apresentado para que se justifique a afirmação de que toda linguagem requer a existência de símbolos básicos, como - e para mais um exemplo - os fonemas para a linguagem falada. A linguagem de comunicação entre as unidades Como a comunicação entre as unidades do computador teria que ser obtida através de fenômenos físicos, os cientistas que conceberam os computadores atuais estabeleceram dois símbolos básicos para a linguagem. Esta quantidade de símbolos foi escolhida pelo fato de que através de fenômenos físicos é muito fácil obter dois estados distintos e não confundíveis, como passar corrente elétrica/não passar corrente elétrica, estar magnetizado/não estar magnetizado, etc., podendo cada um destes estados ser um dos símbolos. Assim a linguagem utilizada para comunicação interna num computador, chamada linguagem de máquina, possui apenas dois símbolos. Cada um destes símbolos é denominado bit (binary digit) e eles são representados por 0 (zero) e 1 (um). Esta forma de representar os bit's justifica a sua denominação: binary digit, que significa dígito binário (além disto, bit em inglês significa fragmento). Portanto, as palavras da linguagem de máquina são sequências de bits, ou seja, sequências de dígitos zero e um.
  10. Quando utilizamos dbGrid e este recebe muitos registros fica dificil ler toda a linha, principalmente se possuimos um grande número de colunas, um recurso que podemos lançar mão para ao menos amenizar essa situação é o efeito "zebrado", onde temos linhas com cores diferentes, na verdade duas cores diferentes que se intercalam. Então vamos ao exemplo, no evento OnDrawColumnCell: if odd(ClientDataSet4.RecNo) then DBGrid1.Canvas.Brush.Color:= clmenubar else DBGrid1.Canvas.Brush.Color:= clCream; TDbGrid(Sender).Canvas.font.Color:= clBlack; if gdSelected in State then with (Sender as TDBGrid).Canvas do begin Brush.Color := clmoneygreen; FillRect(Rect); Font.Style := [fsbold] end; TDbGrid(Sender).DefaultDrawDataCell(Rect, TDbGrid(Sender).columns[datacol].field, State);
  11. - Reproduzindo sons com o PC speaker As instruções assembler IN e OUT permitem ler e escrever em uma porta de E/S respectivamente. Para ler de uma porta: IN acumulador, porta IN le um byte, word ou doubleword da porta especificada para o registro acumulador, isto é, AL, AX, ou EAX para byte, word ou doubleword respectivamente. O número de porta pode ser uma constante do tipo byte (0..255) um o registro DX (para acesso a todas as portas de E/S). Para escrever em uma porta: OUT porta, acumulador OUT escreve o byte, word ou doubleword no registro acumulador para a porta especificada. Novamente, o número da porta pode ser uma constate do tipo byte ou o valor do registro DX. NOTA: Uma vez que IN e OUT implicam no acesso direto ao hardware, e sabendo que os Windows NT/2000/XP não permitem que aplicativos do usuário acessem o hardware diretamente, essas instruções não funcionarão (irão gerar exceções) nesses sistemas operacionais (a não ser que estejam rodando em modo protegido no anel 0, ou modo kernel). Abaixo, veremos um exemplo do uso de assembler na leitura e escrita às portas de E/S para programar os chips 8253/8254 Contador/Timer e o 8255 Programmable Peripheral Interface (PPI) na placa-mãe de modo a fazer o PC speaker reproduzir um som. Os passos para tocar um som usando os chips são os seguintes: 1) Prepare os 8253/8254 para receber a freqüência. Isso é feito ao escrever o valor $B6 no Timer Control Register (porta $43). 2) Escrever a freqüência do som no Frequency Register do 8253/8254 (porta $42). Na verdade, não é a freqüência em Hertz que devemos escrever no registro, mas o resultado de 1331000 dividido nesta freqüência. Primeiramente devemos escrever o byte de mais baixa ordem do resultado para, em seguida, o byte de mais alta ordem. 3) Ligar o PC speaker e fazê-lo utilizar o 8253/8254 Counter/Timer. Isso é feito ao definir os dois primeiros bits da Porta B do 8255 PPI (porta $61). 4) Desligar o PC speaker quando o som finalizar. Isso é feito ao zerar o segundo bit da porta B do 8255 PPI (porta $61). A função assembler inline abaixo implementa os passos listados acima: procedure SpeakerSound(Frequency: word; Duration: longint); // Copyright © 2003 Ernesto De Spirito // Visit: http://www.latiumsoftware.com // Plays a tone thru the PC speaker using the 8253/8254 // Counter/Timer chip and the 8255 Programmable Peripheral // Interface (PPI) chip on the motherboard. // NOTE: This code won't work under Windows NT/2000/XP. asm push edx // Push Duration on the stack (for Sleep) mov cx, ax // CX := Frequency; // Prepare the 8253/8254 to receive the frequency data mov al, $B6 // Function: Expect frequency data out $43, al // Write to Timer Control Register // Compute the frequency data mov dx, $14 // DX:AX = $144F38 mov ax, $4F38 // = 1331000 div cx // AX := 1331000 / Frequency; // Send the frequency data to the 8253/8254 Counter/Timer chip out $42, al // Write low byte to the Frequency Address mov al, ah // AL := High byte of AX out $42, al // Write high byte to the Frequency Address // Tell the 8255 PPI to start the sound in al, $61 // Read Port B of the 8255 PPI or al, $03 // Set bits 0 and 1: // bit 0 --> use the 8253/8254 // bit 1 --> turn speaker on out $61, al // Write to Port B of the 8255 PPI // Wait call Sleep // Sleep(Duration); // requires Windows unit // Tell the 8255 PPI to stop the sound in al, $61 // Read Port B of the 8255 PPI and al, NOT 2 // Clear bit 1 (turn speaker off) out $61, al // Write to Port B of the 8255 PPI end; Exemplo de chamada: procedure TForm1.Button1Click(Sender: TObject); var i: integer; begin Randomize; for i := 1 to 3 do SpeakerSound(Random(900)+100, 200); end; NOTA: Usar o PC speaker para gerar som é OBSOLETO. Aplicações devem usar sons MIDI ou WAVE ao invés. Download: http://depositfiles.com/files/gqthylyug --- http://www.4shared.com/rar/RcPgIn6i/Delphi_e_Assembly__by_-_cyber-.html
  12. Aritmética Inteira de 128 bits (3) Nessa terceira e última parte da série sobre inteiros de 128-bit (que chamamos de inteiros gigantes, inteiros enormes ou inteiros largos) vamos finalmente lidar com a aritimética propriamente dita- as quatro operações fundamenteais (adição, subtração, multiplicação e divisão). Antes de iniciar, gostaria de avisar que as rotinas introduzidas nas duas partes anteriores foram corrigidas e também otimizadas. Ainda não pude testá-las tanto quanto gostarias. Se você encontrar erros ou tiver algum comentário sobre os fontes, por favor me envie um e-mail. Adição ====== Como somamos dois números, cada um formado de quatro inteiros de 32-bit? Bem, é bem fácil. Simplesmente somamos como faríamos com dois números de quatro dígitos decimais (como 3597 e 0015, por exemplo), exceto que cada dígito aqui pode ter aproximadamente 4 bilhões (2^32) de valores distintos ao invés de apenas dez. O algoritmo é algo como: function AddWithCarry(x: Longint; y: Longint; var Carry: Boolean): Longint; forward; function HugeAdd(x: Hugeint; y: Hugeint): Hugeint; // Result := x + y; var Carry: Boolean; begin Carry := False; Result[0] := AddWithCarry(x[0], y[0], Carry); Result[1] := AddWithCarry(x[1], y[1], Carry); Result[2] := AddWithCarry(x[2], y[2], Carry); Result[3] := AddWithCarry(x[3], y[3], Carry); end; AddWithCarry é uma função fictícia que retorna um inteiro com os 32 bits de mais baixa ordem do resultado da adição dos dois argumentos, mais um se Carry (o terceiro argumento) for True. A rotina também altera o valor de Carry com True o False dependendo da adição ter gerado um excesso ou não (ou se o carry é 1 ou 0, se você prefere ver dessa forma). Na prática, essa função não precisa ser fictícia: function AddWithCarry(x: Longint; y: Longint; var Carry: Boolean): integer; asm // if Carry then CF := 1 else CF := 0; test byte ptr [ecx], -1 // efeito colateral: CF := 0; jz @@NoCarry stc // CF := 1; @@NoCarry: // Result := x + y + CF; CF := GeneratedCarry; adc eax, edx // Carry := CF; setc byte ptr [ecx] end; é mais eficiente codificar HugeAdd inteiramente em assembler: function HugeAdd(x: Hugeint; y: Hugeint): Hugeint; // Result := x + y; // Parameters: EAX = @x; EDX = @y; ECX = @Result asm push esi mov esi, [eax+_0_] // ESI := x[0]; add esi, [edx+_0_] // ESI := ESI + y[0]; mov [ecx+_0_], esi // Result[0] := ESI; mov esi, [eax+_1_] // ESI := x[1]; adc esi, [edx+_1_] // ESI := ESI + y[1] + Carry; mov [ecx+_1_], esi // Result[1] := ESI; mov esi, [eax+_2_] // ESI := x[2]; adc esi, [edx+_2_] // ESI := ESI + y[2] + Carry; mov [ecx+_2_], esi // Result[2] := ESI; mov esi, [eax+_3_] // ESI := x[3]; adc esi, [edx+_3_] // ESI := ESI + y[3] + Carry; mov [ecx+_3_], esi // Result[3] := ESI; pop esi end; Subtração ========= A subtração acontece de forma semelhante à adição mas, ao invés de gerar um excesso, a operação gera um "empréstimo" (também representado pelo flag Carry) se o minuendo (primeiro operando) é menor que o subtraendo (segundo operando): function SubtractWithBorrow(x: Longint; y: Longint; var Borrow: Boolean): Longint; forward; function HugeSub(x: Hugeint; y: Hugeint): Hugeint; // Result := x - y; var Borrow: Boolean; begin Borrow := False; Result[0] := SubtractWithBorrow(x[0], y[0], Borrow); Result[1] := SubtractWithBorrow(x[1], y[1], Borrow); Result[2] := SubtractWithBorrow(x[2], y[2], Borrow); Result[3] := SubtractWithBorrow(x[3], y[3], Borrow); end; function SubtractWithBorrow(x: Longint; y: Longint; var Borrow: Boolean): Longint; asm // if Borrow then CF := 1 else CF := 0; test byte ptr [ecx], -1 // Side-effect: CF := 0; jz @@NoBorrow stc // CF := 1; @@NoBorrow: // Result := x - y - CF; CF := NeededBorrow; sbb eax, edx // Borrow := CF; setc byte ptr [ecx] end; Você deve estar pronto para escrever uma versão de HugeSub inteiramente em assembler já que é análoga à HugeAdd; tudo que você deverá fazer é substituir ADD e ADC com SUB e SBB respectivamente. Número Oposto ============= Dado um número, essas implementações de HugeNeg retornam seu oposto (complemento de dois): function HugeNeg(x: Hugeint): Hugeint; begin // Result := (Not x) + 1; Result := HugeAdd(HugeNot(x), IntToHuge(1)); end; function HugeNeg(x: Hugeint): Hugeint; begin // Result := 0 - x; Result := HugeSub(IntToHuge(0), x); end; A segunda é mais simples e rápida pois involve uma operação apenas e, já que sabemos subtrair, podemos implementá-la em assembler: function HugeNeg(x: Hugeint): Hugeint; // Result := -x; // Parameters: EAX = @x; EDX = @Result asm // Result := 0 - x; push esi xor esi, esi mov ecx, [eax+_0_] // x[0] sub esi, ecx // 0 - x[0] mov ecx, 0 mov [edx+_0_], esi // Result[0] mov esi, [eax+_1_] // x[1] sbb ecx, esi // 0 - x[1] - Borrow mov esi, 0 mov [edx+_1_], ecx // Result[1] mov ecx, [eax+_2_] // x[2] sbb esi, ecx // 0 - x[2] - Borrow mov ecx, 0 mov [edx+_2_], esi // Result[2] mov esi, [eax+_3_] // x[3] sbb ecx, esi // 0 - x[3] - Borrow mov [edx+_3_], ecx // Result[3] pop esi end; Multiplicação ============= Uma forma de multiplicar números é através de um laço de adições: function HugeMul(x: Hugeint; y: Hugeint): Hugeint; begin SetZero(Result); while not HugeIsZero(y) do begin Result := HugeAdd(Result, x); HugeSub(y, 1) end; end; Em termos computacionais, esse algoritmo é bastante pobre. Por exemplo, se o valor de "y" for 4 milhões, o laço se repetiria 4 milhões de vezes! De qualquer forma, a idéia ainda é boa se pudéssemos de alguma forma acelerar o processo. Vamos então brincar um pouco com álgebra de bits: x * y = x * (y[3]*2^96 + y[2]*2^64 + y[1]*2^32 + y[0]*2^0) = (x*y[3])*2^96 + (x*y[2])*2^64 + (x*y[1])*2^32 + (x*y[0])*2^0 Agora nós reduzimos o problema de multiplicar dois inteiros gigantes ao de multiplicar um inteiro gigante por um inteiro de 32-bit. Quando multiplicamos o primeiro operando pelos quatro inteiros que compõem o segundo operando, então deslocamos os resultados parciais por 0, 32, 64 e 96 bits (multiplicação por 2^0, 2^32, 2^64 e 2^96) e finalmente somamos esses valores para obter o resultado final. function HugeMulInt(x: Hugeint; y: Longint): Hugeint; forward; function HugeMul(x: Hugeint; y: Hugeint): Hugeint; begin Result := HugeShl(HugeMulInt(x, y[3]), 96) + HugeShl(HugeMulInt(x, y[2]), 64) + HugeShl(HugeMulInt(x, y[1]), 32) + HugeMulInt(x, y[0]); end; Essa é a exata forma que multiplicamos números decimais quando usamos papel, exceto que aqui a base é 2^32 ao invés de dez. Vejamos como multiplicar um inteiro gigante por um inteiro: function MultiplyWithCarry(x: Longint; y: Longint; var Carry: Longint): Longint; forward; function HugeMulInt(x: Hugeint; y: Longint): Hugeint; // Result := x * y; var Carry: Longint; begin Carry := 0; Result[0] := MultiplyWithCarry(x[0], y, Carry); Result[1] := MultiplyWithCarry(x[1], y, Carry); Result[2] := MultiplyWithCarry(x[2], y, Carry); Result[3] := MultiplyWithCarry(x[3], y, Carry); end; function MultiplyWithCarry(x: Longint; y: Longint; var Carry: Longint): integer; // Result := LoDWord(x * y + Carry); // Carry := HiDWord(x * y + Carry); asm // EDX:EAX := EAX * EDX; // x * y mul edx // Inc(EDX:EAX, Carry); add eax, [ecx] adc edx, 0 // Carry := EDX; // High order 32 bits of the result mov [ecx], edx; end; MultiplyWithCarry é muito semelhante a AddWithCarry mas realiza uma multiplicação ao invés de uma adição, e gera um excesso de 32-bit ao invés de um bit apenas (a multiplicação de dois valores de 32-bit gera um valor de 64-bit enquanto sua soma gera um valor de 33-bit). MultiplyWithCarry primeiramente realiza a multiplicação sem sinal de "x" (EAX) por "y" (EDX), usando o opcode MUL. O resultado de 64-bit é um inteiro sem sinal contido em EDX:EAX, ao qual a função soma o valor do parâmetro Carry. A função retorna os 32 bits mais baixos do resultado final (localizado em EAX) e os 32 bits mais altos (EDX) constituem o excesso para a próxima multiplicação, que é armazenado no parâmetro Carry (passado por referência). Uma implementação em assembler de HugeMul e HugeMulInt encontra-se no código em anexo. Por razões de simplificação, nos exemplos acima, as funções consideram que os números não possuem sinais, mas o código anexo considera os sinais. O código de HugeMul também não chama HugeMulInt ou HugeShl e está bastante otimizado. Ao invés de considerar um inteiro gigante como quatro inteiros de 32-bit, podemos considerá-lo como 128 inteiros de 1-bit multiplicados por 128 potências de 2: bit127 * 2^127 + bit126 * 2^126 + ... + bit1 * 2^1 + bit0 * 2^0 Como cada bit só pode ser 0 ou 1, o algoritmo acima pode ser muito simplificado: function HugeMul(x: Hugeint; y: Hugeint): Hugeint; // Result := x * y; var i: Longint; begin SetZero(Result); for i := 0 to 127 do if BitTest(y, i) then Result := HugeAdd(Result, HugeShl(x, i)); end; A idéia é somar diferentes potências de 2 de "x", dependendo essas potências nos bits de "y". Por exemplo, se "y" for 20, os bits 5 e 3 estariam ligados (20 em decimal é 10100 em binário), de modo que apenas duas adições são realizadas e o resultado é HugeShl(x, 3) mais HugeShl(x, 5). Esse algoritmo pode ser codificado de forma bastante eficiente em assembler mas o primeiro algoritmo será ainda mais rápido. A razão de mostrá-lo é que facilitará o entendimento do algoritmo que usaremos para divisões. Divisão ======= Vamos primeiro ver o caso da divisão de um Hugeint por um inteiro de 32-bit, que deve ser fácil de entender: function DivideWithRemainder(x: Longint; y: Longint; var Remainder: Longint): Longint; forward; function HugeDivInt(x: Hugeint; y: Longint): Hugeint; // Result := x div y; var Remainder: Longint; begin Remainder := 0; Result[0] := DivideWithRemainder(x[3], y, Remainder); Result[1] := DivideWithRemainder(x[2], y, Remainder); Result[2] := DivideWithRemainder(x[1], y, Remainder); Result[3] := DivideWithRemainder(x[0], y, Remainder); asm mov edx, Remainder end; end; function DivideWithRemainder(x: Longint; y: Longint; var Remainder: Longint): Longint; // Result := Remainder:x div y; // Remainder := Remainder:x mod y; asm push esi mov esi, edx // y mov edx, [ecx] // Remainder // EAX := EDX:EAX div ESI; // EDX := EDX:EAX mod ESI; div esi // Remainder := EDX; mov [ecx], edx; pop esi end; HugeDivInt deixa o resto da divisão em EDX, de modo que possa ser usado em uma função que retorne o resto da divisão: function HugeModInt(dividend: Hugeint; divisor: Longint): Longint; // Result := dividend mod divisor; // Parameters: EAX = @dividend; EDX = @divisor; asm sub esp, TYPE(Hugeint) // Abrir espaço na pilha para um Hugeint mov ecx, esp // com o resultado da divisão call HugeDivInt // Realizar a divisão add esp, TYPE(Hugeint) // Restaurar o ponteiro da pilha mov eax, edx // Result := Remainder; // o resto foi deixado em EDX end; Para o caso de dois inteiros gigantes, temos que pensar no algoritmo que usaríamos para dividir um número de quatro dígitos decimais no papel. Mas esse procedimento é complexo e lento demais pois envolve além das divisões, multiplicações, subtrações, antecipação de etapas, etc. Há alternativa possível para o algoritmo? Sim: function HugeDiv(dividend: Hugeint; divisor: Hugeint): Hugeint; // Result := dividend div divisor; begin if HugeIsZero(divisor) then raise EDivByZero.CreateRes(@sDivByZero); Result := 0; while HugeCmp(dividend, divisor) >= 0 do begin dividend := HugeSub(dividend, divisor); Result := HugeAdd(Result, IntToHuge(1)); end; end; Claro, o algoritmo acaba sendo extremamente lento (se dividirmos 12 milhões por 3, o laço executará 4 milhões de vezes) mas podemos acelerá-lo se subtrarimos do dividendo o divisor multiplicado por diferentes potências de 2, da maior para a menor, definindo o bit correspondente do resultado toda vez que efetuarmos a subtração (o bit na posição da potência de 2 que foi usado). é o inverso do que foi feito no caso da multiplicação acima. O processo de divisão seria reduzido ao máximo de 128 subtrações. No exemplo a seguir, o dividendo é 20 (10100 em binário) e o divisor é 3 (11 em binário): 10100 - 11 * 2^2 = 10100 - 1100 = 1000 Result := 100 1000 - 11 * 2^1 = 1000 - 110 = 10 Result := 110 Inicialmente, 11 * 2^2 é o maior valor que é menor ou igual ao dividendo então subtraímos esse valor do dividendo e definimos o bit 2 do resultado pois subtraímos o divisor multiplicado por 2^2. Até o momento, o resto da divisão é 8 (1000 em binário) e 11 * 2^1 é o maior valor que é menor ou igual ao resto. Então subtraímos esse valor do resto e definimos o bit 1 do resultado pois subtraímos o divisor multiplicado por 2^1. O resto agora é 2 (10 em binátio) e como o divisor é maior que esse valor, a divisão é interrompida. O resto da operação seria 2 (10 em binário) e, como os bits 2 e 1 do resultado estão marcados, o resultado é 110 em binário, ou 6 em notação decimal. function HugeDiv(dividend: Hugeint; divisor: Hugeint): Hugeint; var _r_: Hugeint; // remainder _d_: Hugeint; // divisor _q_: Hugeint; // quotient BitPosR, BitPosD, count: integer; begin _r_ := dividend; _d_ := divisor; HugeSetZero(_q_); BitPosD := HugeBitScanReverse(_d_); if BitPosD = -1 then RaiseDivByZero; BitPosR := HugeBitScanReverse(_r_); count := BitPosD - BitPosR; if count > 0 then _d_ := HugeShl(_d_, count); repeat if HugeCmp(_d_, _r_) <= 0 then begin _r_ := HugeSub(_r_, _d_); HugeBitSet(_q_, count); end; _d_ := HugeShr(_d_, 1); dec(count); until count < 0; Result := _q_; asm lea edx, _r_ end; end; HugeBitScanReverse é a função que retorna a posição do primeiro bit não zero, realizando a busca a partir do bit 127 até o bit 0. Se todos os bits são zero, o resultado é -1. Usamos HugeBitScanReverse para determinar a primeira potência de dois que devemos multiplicar o divisor para iniciar a divisão. A implementação em assembler de HugeDiv no código anexo suporta inteiros com sinais. é apenas uma primeira aproximação que pode ainda ser muito melhorada. A função deixa em EDX o endereço do resto de modo que ele possa ser usado por uma função que retorna o módulo da divisão: function HugeMod(dividend: Hugeint; divisor: Hugeint): Hugeint; // Result := dividend Mod divisor; // Parameters: EAX = @dividend; EDX = @divisor; ECX = @Result asm push ecx // @Result call HugeDiv // EDX := @remainder; pop eax // EAX := @Result; call HugeMov // EAX^ := EDX^; end;
  13. -Aritmética Inteira de 128 bits (2) ================================== No código-fonte exemplo (anexado) você encontrará a implementação de algumas funções para operações com o tipo Hugeint que foi introduzido no artigo passado. A proposta é exemplificar as instruções que nós vimos há muito tempo com algumas novas: BT (Bit Test): BT dword ptr [eax], edx --> CF = valor do EDX-ésimo bit na memória apontada por EAX BTS (Bit Test and Set): BTS dword ptr [eax], edx --> atribui 1 para o EDX-ésimo bit na memória apontado por EAX CF = valor anterior deste bit BTR (Bit Test and Reset): BTR dword ptr [eax], edx --> atribui 0 para o EDX-ésimo bit na memória apontado por EAX CF = valor anterior deste bit BTC (Bit Test and Complement): BTC dword ptr [eax], edx --> inverte o valor do EDX-ésimo bit na memória apontada por EAX CF = valor anterior deste bit Não reproduziremos as funções aqui, uma vez que você pode encontrá-los no código-fonte anexo, mas mostraremos diferentes implementações da função _IsNeg, simplesmente apresentando mais exemplos das instruções que vimos tempos atrás: function _IsNeg(x: Hugeint): boolean; // Resultado := x < 0; // Se x < 0 retorna True (1) senão retorna False (0) // Parâmetros: EAX = @x asm mov eax, [eax+_3_] // EAX := 32 bits de alta ordem de x shr eax, 31 // AL := Bit de alta ordem de EAX (sign bit) end; function _IsNeg(x: Hugeint): boolean; asm cmp dword ptr [eax+_3_], 0 // if x[3] < 0 then jl @@negative // goto @@negative mov al, 0 // Result := False; ret // exit; @@negative: // @@negative: mov al, 1 // Result := True; end; function _IsNeg(x: Hugeint): boolean; asm // atribuir o Sign Flag e colocá-lo em AL mov eax, [eax+_3_] // EAX := 32 bits de alta ordem de x or eax, eax // SF := Sign bit de EAX // alt.: add eax, 0 // ou: sub eax, 0 // ou: and eax, eax // ou: and eax, -1 // ou qq valor negativo // ou: test eax, eax // ou: test eax, -1 // ou qq valor negativo sets al // AL := SF; // Sign Flag // alt.: lahf; shr ax, 31 // ou: lahf; rol ax, 1; and al, $1 end; function _IsNeg(x: Hugeint): boolean; asm // atribui o Carry Flag com o Sign Bit e colocá-lo em AL mov eax, [eax+_3_] // EAX := 32 bits de alta ordem de x bt eax, 31 // CF := Sign bit de EAX // alt.: shl/rol/rcl eax, 1 setc al // AL := CF; // Carry Flag // alt.: mov al, 0; rcl, 1 // ou: mov al, 0; adc al, al // ou: lahf; mov al, ah; and al, $1 // ou: lahf; ror/rcr/shr/sar ax, 1; shr al, 7 // ou: lahf; ror/shr/sar ax, 8; and al, $1 // ou: lahf; rol ax, 8; and al, $1 // ou: lahf; rcl ax, 9; and al, $1 end; function _IsNeg(x: Hugeint): boolean; asm // atribuir o Parity Flag e negá-lo em AL mov al, [eax+_3_+3] // EAX := 8 bits de alta ordem de x or al, $7F // PF := Not Sign bit // alt.: and eax, $80000000 setnp al // AL := Not PF; // Not Parity Flag // alt.: lahf; rol/shl ax, 6 / rcl ax, 7; xor al,-1 / not al; and al, $1; // ou: lahf; ror/shr/sar ax, 10 / rcr ax, 11; xor al,-1 / not al; and al, $1; end; Na próxima parte veremos funções para adicionar, subtrair, multiplicar e dividir inteiro HugeInt.
  14. Aritmética Inteira de 128 bits (1) Introdução ========== Com 32 bits podemos representar 2^32 números diferentes, isto é, 4294967296 (~4 bilhões) números diferentes, tais como inteiros com sinal de -2147483648 até +2147483647 ou inteiros sem sinal de 0 até 4294967295 (Longint e Longword respectivamente). Isso é suficiente para muitas finalidades como, por exemplo, guardar a posição de um byte em um arquivo de 4GB. Mas às vezes precisamos representar inteiros maiores e então podemos usar TLargeInteger (Windows.pas) e Int64 (a partir do Delphi 4) para representar inteiros de 64-bits (2^64 valores diferentes): 18446744073709551616 (~18 sestilhões) de valores, de -9223372036854775808 até +9223372036854775807 (~9 sestilhões, 17-18 dígitos). Essa quantidade de dígitos é muito mais do que eu preciso e não consigo imaginar qualquer uso prático para mais do que isso- a não ser Bill Gates que conta seu dinheiro em sestilhões! ;) Mas de tempos em tempos eu vejo alguém no fórum perguntar por mais dígitos que o Int64 oferece... De qualquer maneira, se válido ou completamente inútil numa proposta prática, veremos a implementação de várias procedures e functions desenvolvidas para trabalhar com inteiros de 128-bits, que nos servirão para mostrar exemplos de instruções básicas de assembler. Este "inteiros longos", "inteiros grandes" ou "inteiros enormes" podem manter 2^128 diferentes valores (38-39 dígitos). Representação dos inteiros de 128-bits ====================================== Eu chamo o novo tipo Hugeint mas, por exemplo, Bigint (big integer) ou Int128 podem ser nomes igualmente bons. Largeint pode ser confundido com o tipo da unit Windows.pas que refere-se ao inteiro de 64-bits. Quando precisamos representar o novo tipo, também existem várias formas de fazê-lo. Eu optei pela representação mais simples- um array de 4 inteiros de 32 bits: type Hugeint: packed array [0..3] of longword; Também decidi pelo formato little-endian já que é o padrão na arquitetura Intel e isto significa que o primeiro elemento do array (endereço mais baixo) guardará os 32 bits de ordem mais baixa (menos significativos) e o último elemento do array (endereço mais alto) guardará os 32 bits de mais alta ordem (mais significativos). Abaixo é a forma que os números 5 e 5000000000 ($12A05F200) serão representados: +---- 32 bits de baixa ordem | v +-------------+-------------+-------------+-------------+ | $00000005 | $00000000 | $00000000 | $00000000 | = 5 +-------------+-------------+-------------+-------------+ 0 1 2 3 +-------------+-------------+-------------+-------------+ | $2A05F200 | $00000001 | $00000000 | $00000000 | = 5000000000 +-------------+-------------+-------------+-------------+ $12A05F200 ^ | 32 bits de alta ordem ----+ Os próprios inteiros são armazenados no formato little-endian (primeiro o byte de mais baixa ordem). Se você verificar a representação em bytes de um número na memória, ela se pareceria com isso (valores em bytes representados na notação hexadecimal): $00000005 +-------------+-------------+-------------+-------------+ | 05 00 00 00 | 00 00 00 00 | 00 00 00 00 | 00 00 00 00 | = 5 +-------------+-------------+-------------+-------------+ 0 1 2 3 +-------------+-------------+-------------+-------------+ | 00 F2 05 2A | 01 00 00 00 | 00 00 00 00 | 00 00 00 00 | = 5000000000 +-------------+-------------+-------------+-------------+ $12A05F200 $2A05F200 $00000001 Entretanto, para quase todas as operações, podemos abstrair a ordem do byte e considerar que os inteiros de 32-bits são unidades atômicas, desde que a ordem do byte seja mantida transparentemente. Algumas instruções úteis ======================== Antes de começarmos, vejamos algumas instruções úteis que usaremos neste artigo (principalmente na continuação desta parte); antes, porém, é preciso enfatizar que a proposta deste artigo não é ensiná-lo assembler. Tudo que eu posso fazer neste espaço limitado é simplesmente mostrar exemplos de algumas instruções. Como material de referência, recomendo estes links: * Intel 80386 Reference Programmer's Manual Um versão HTML deste manual da Intel. O pseudocódigo ajuda a entender as instruções e seus efeitos sobre os flags. Excelente. http://people.freebsd.org/~jhb/386htm/toc.htm Existem alguns links quebrados, mas as páginas estão lá. Tente encontrá-los no diretório: http://people.freebsd.org/~jhb/386htm/ * iAPx86 - Norton Guide Não é tão didático quanto o documento acima, mas contém todas as instruções do 8086 do Pentium e Pentium Pro, com informações de tamanho e tempo não inclusas no link acima. http://www.clipx.net/ng/iapx86/index.php * The IA-32 Intel Architecture Software Developer's Manual, Volume 2: Instruction Set Reference Manual em PDF descrevendo as instruções dos processadores IA-32 (Pentium, Pentium Pro, Pentium II, Pentium III, Pentium 4 e Xeon). Inclui pseudocódigo que explica as instruções e como elas afetam os flags dos registradores. http://www.intel.com/design/pentium4/manuals/245471.htm * Otimização - How to optimize for the Pentium family of microprocessors Excelente guia de otimização escrito por Agner Fog http://fatphil.org/x86/pentopt/index.html - Optimizations for Intel's 32-Bit Processors Outro excelente guia de otimização. http://x86.ddj.com/ftp/manuals/686/optimgd.pdf OK, agora vamos ver as instruções. Referência: Z/ZF: Flag Zero S/SF: Flag de Sinal C/CF: Flag de Transporte (Carry) P/PF: Flag de Paridade A/AF: Flag Auxiliar s: bit de sinal (bit de ordem mais alta) o: bit ímpar (bit de ordem mais baixa) x: valor do bit 0: o valor 0 1: o valor 1 r: bit invertido em relação ao valor anterior u: bit não alterado desde o valor anterior XX: valor desconhecido Nos exemplos presumimos que o valor de AL antes de cada operação é sxxxxxxo (bit de sinal, 6 bits desconhecidos e bit ímpar). Aqui temos algumas instruções para começar: SHL al,1 AL := xxxxxxo0 CF := s Deslocamento à esquerda SAL al,1 AL := xxxxxxo0 CF := s Mesmo que SHL SHR al,1 AL := 0sxxxxxx CF := o Deslocamento à direita SAR al,1 AL := ssxxxxxx CF := o Deslocamento aritmético à direita SAR al,7 AL := ssssssss CF := x Reprodução do bit de sinal ROL al,1 AL := xxxxxxos CF := s Rotação à esquerda ROR al,1 AL := osxxxxxx CF := o Rotação à direita RCL al,1 AL := xxxxxxoC CF := s Rotação completa com transporte à esquerda RCR al,1 AL := Csxxxxxx CF := o Rotação completa com transporte à direita AND al,al AL := uuuuuuuu CF := 0 Define flags (veja abaixo) AND al,-1 AL := uuuuuuuu CF := 0 -1 = $FF = 1111111 Define flags (veja abaixo) AND al,$01 AL := 0000000u CF := 0 $01 = 00000001 AND al,$80 AL := u0000000 CF := 0 $80 = 10000000 AND al,$5A AL := 0u0uu0u0 CF := 0 $5A = 01011010 AND al,0 AL := 00000000 CF := 0 XOR AL,AL ou MOV AL,0 são melhores TEST AL,XX AL := uuuuuuuu TEST é como AND mas o resultado não é armazenado no destino. O resultado é usado para definir as flags (veja abaixo) TEST AL,-1 é melhor que AND AL,-1 e OR AL,AL porque não escreve em AL, o que permite certas otimizações em alguns casos. OR al,al AL := uuuuuuuu CF := 0 Define as flags (veja abaixo) OR al,$01 AL := uuuuuuu1 CF := 0 $01 = 00000001 OR al,$80 AL := 1uuuuuuu CF := 0 $80 = 10000000 OR al,$5A AL := u1u11u1u CF := 0 $5A = 01011010 OR al,-1 AL := 11111111 CF := 0 Mesmo que MOV AL,1 XOR al,al AL := 0 CF := 0 Utilize MOV AL,0 para preservar as flags XOR al,$5A AL := ururruru CF := 0 $5A = 01011010 XOR al,-1 AL := rrrrrrrr CF := 0 Mesmo que NOT AL Exceto pelas instruções de rotação (ROL, RCL, ROR e RCR), todas acima definem SF, ZF e PF baseados no resultado das operações: SF = valor do bit de mais alta ordem do resultado ZF = 1 ("definido") se o resultado for zero, 0 ("limpo") nos demais casos PF = 1 ("definido") se o o byte de mais baixa ordem do resultado contém um número par de bits 1, senão 0 ("limpo") Veja mais algumas instruções: STC CF := 1 Define o flag de Transporte CLC CF := 0 Limpa o flag de Transporte CMC CF := r Complementa o flag de Transporte LAHF AH := SZxAxPxC SAHF Presumindo que AH é SZxAxPxC: ZF := S; ZF := Z; AF := A; PF := P; CF := C SETc AL AL := CF Define se carry SETs AL AL := SF Define se sign SETz AL AL := ZF Define se zero SETe AL AL := ZF Define se equal (sinônimo de SETZ) SETp AL AL := PF Define se paridade SETpe AL AL := PF Define se a paridade é par (sinônimo de SETP) SETo AL AL := OF Define se overflow SETnc AL AL := NOT CF Define se não carry SETns AL AL := NOT SF Define se não sign SETnz AL AL := NOT ZF Define se não zero SETne AL AL := NOT ZF Define se não equal (sinônimo de SETNZ) SETnp AL AL := NOT PF Define se não paridade SETpo AL AL := NOT PF Define se a paridade é ímpar (sinônimo de SETNP) SETno AL AL := NOT OF Define se não overflow SETa (ou SETNbe), SETae (ou SETnb), SETb (ou SETnae), SETbe (SETna), SETg (ou SETNle), SETge (ou SETnl), SETl (ou SETnge) e SETle (SETng) definem o byte de destino para 1 ou 0 dependendo da condição específica ser satisfeita ou não. ADD AL,XX AL := AL+XX CF := 1 se a operação gera transporte; 0 outros casos SUB AL,XX AL := AL-XX CF := 1 se a operação precisa de um empréstimo; 0 outros casos SUB AL,0 AL := uuuuuuuu Define os flags baseados no AL SUB AL,AL AL := 0 Mesmo que XOR AL,AL ou MOV AL,0 CMP AL,XX CMP é como SUB mas o resultado não é armazenado no destino. A operação simplesmente define os flags. ADC AL,XX AL := AL+XX+C CF := 1 se a operação gera um transporte; 0 outros casos SBB AL,XX AL := AL-C-XX CF := 1 se a operação necessitade um empréstimo; 0 outros casos NEG AL AL := -AL CF := 1 se previamente AL <> 0 NOT AL; INC AL é o mesmo NOT AL AL := rrrrrrrr CF := u Mesmo que XOR AL,-1 Funções de Conversão ==================== Estas funções nos ajudarão a entender a representação dos inteiros 128-bits. Longword para Hugeint --------------------- Vamos começar convertendo de Longword para HugeInt. Os 32 bits de mais baixa ordem do resultado serão os 32 bits do parâmetro e os 96 bits de mais alta ordem serão zerados. function UToHugeint(const x: Longword): Hugeint; overload; // Result := Hugeint(x); // Parâmetros: EAX = x; EDX = @Result; asm xor ecx, ecx // ECX := 0; mov [edx+_0_], eax // Result[0] := x; mov [edx+_1_], ecx // Result[1] := 0; mov [edx+_2_], ecx // Result[2] := 0; mov [edx+_3_], ecx // Result[3] := 0; end; Comentários: * "_0_", "_1_", "_2_", e "_3_"? O que são? São constantes que representam os deslocamentos dos quatro elementos do vetor, permitindo-nos escrever um código mais limpo. const _0_ = 0; _1_ = 4; _2_ = 8; _3_ = 12; Longint para Hugeint -------------------- Os 32 bits inferiores do resultado serão os 32 bits do parâmetro. Se o número for positivo ou zero, então os 96 bits superiores serão 0, senão os 96 bits superiores serão 1. Isso nos força a fazer uma comparação ou teste do sinal e então executar um jump condicional baseado no resultado: function ToHugeint(const x: Longint): Hugeint; overload; // Result := Hugeint(x); // Parâmetros: EAX = x; EDX = @Result; asm or eax, eax // EAX := EAX or EAX; // EAX não é alterado // Efeito colateral: // SF (Flag de Sinal) := EAX < 0; mov ecx, 0 // ECX := 0; jns @@not_negative // if not SF then goto @@not_negative; dec ecx // ECX := ECX - 1; // 0 - 1 = -1 = $FFFFFFFF @@not_negative: mov [edx+_0_], eax // Result[0] := x; mov [edx+_1_], ecx // Result[1] := ECX; // 0 or $FFFFFFFF mov [edx+_2_], ecx // Result[2] := ECX; // 0 or $FFFFFFFF mov [edx+_3_], ecx // Result[3] := ECX; // 0 or $FFFFFFFF end; Comentários: * Observe o uso do "MOV ECX, 0" no lugar de "XOR ECX, ECX" para evitar alterar o estado do Flag de Sinal (SF) definido na instrução anterior (OR) e então usado no jump condicional que aparece na instrução seguinte (JNS). é claro que era desnecessário trocar a ordem das operações para isto. * Em vez de: or eax, eax jns @@not_negative os pares de instruções abaixo realizariam o mesmo: * and eax, eax // EAX mantém o valor, mas SF recebe o sinal jns @@not_negative // se SF = 0 então goto @@not_negative * test eax, $80000000 // somente zero se o bit de sinal (bit 31) é 0 jz @@not_negative // se ZF então goto @@not_negative * test eax, $87654321 // qualquer valor com o bit 31 definido jns @@not_negative // se SF = 0 então goto @@not_negative * cmp eax, 0 // compara eax com 0 jge @@not_negative // se maior ou igual então goto @@not_negative * Observe o uso de "DEC ECX" para alterar o valor de ECX de $00000000 para $FFFFFFFF (decrementando o valor do registro). "NOT ECX" faz a mesma coisa invertendo os bits, na mesma velocidade, e com o mesmo número de bytes para codificar a instrução, mas não é uma instrução par como o DEC. Por esta razão NOT é normalmente evitado e substituido por: - Se você sabe de antemão que o valor é zero, utilize DEC Dest - Se você sabe de antemão que o valor é 1, use INC Dest - Se você não conhece o valor, use XOR Dest, -1 * Também preste atenção na ordem das instruções para jamais utilizar um registro que foi definido por um instrução imediatamente anterior. Essa é uma das condições para que ocorra o pareamento. Você encontrará mais informações sobre instruções de pareamento nos documentos sobre otimização recomendados anteriormente. Podemos simplificar a função obrigando a instrução CDQ que estende o sinal de EAX para EDX. Veja o funcionamento (simplificado) de CDQ: if EAX >= 0 then EDX := $0 else EDX := $FFFFFFFF; Aqui é uma pequena e simples implementação usando CDQ: function ToHugeint(const x: Longint): Hugeint; overload; // Result := Hugeint(x); // Parâmetros: EAX = x; EDX = @Result; asm mov ecx, edx // ECX := @Result; cdq // EDX := IIF(x>=0, 0, $FFFFFFFF); mov [ecx+_0_], eax // Result[0] := x; mov [ecx+_1_], edx // Result[1] := EDX; // 0 or $FFFFFFFF mov [ecx+_2_], edx // Result[2] := EDX; // 0 or $FFFFFFFF mov [ecx+_3_], edx // Result[3] := EDX; // 0 or $FFFFFFFF end; CDQ é usualmente substituído usando MOV e SAR, que oferecem a vantagem de que a origem não precisa ser EAX e o destino não precisa ser EDX (mas são instruções pareadas). Vejamos um exemplo: function ToHugeint(const x: Longint): Hugeint; overload; // Result := Hugeint(x); // Parâmetros: EAX = x; EDX = @Result; asm mov ecx, eax // ECX := x; sar ecx, 31 // ECX := IIF(x>=0, 0, $FFFFFFFF); mov [edx+_0_], eax // Result[0] := x; mov [edx+_1_], ecx // Result[1] := EDX; // 0 or $FFFFFFFF mov [edx+_2_], ecx // Result[2] := EDX; // 0 or $FFFFFFFF mov [edx+_3_], ecx // Result[3] := EDX; // 0 or $FFFFFFFF end; Hugeint para Longint -------------------- Um Hugeint pode ser convertido para Longint simplesmente pegando os 32 bits de mais baixa ordem. Os 96 bits altos do Hugeint podem ser todos definidos para 0 ou 1 igualando o bit de sinal para que o resultado (bit 31) do Hugeint esteja dentro dos limites do Longint, mas a função não checa isto e executa a conversão cegamente (da mesma forma que um Longint é convertido para um Shortint, por exemplo). function ToLongint(const x: Hugeint): Longint; overload; // Result := Longint(x); // Nenhuma exceção é gerada se o valor não estiver dentro dos limites // (os 96 bits de mais alta ordem são descartados). // Parâmetros: EAX = @x; asm mov eax, [eax+_0_] // Result := x[0]; end; Int64 para Hugeint ------------------ Parâmetros Int64 são passados para a pilha; assim, funções com parâmetros Int64 automaticamente criam um quadro de pilha (stack frame). Os 64 bits mais baixos do resultado serão os 64 bits do parâmetro enquanto os 64 bits mais altos serão obtidos a partir do bit de sinal do inteiro (32 bits) de mais alta ordem do Int64. {$IFDEF DELPHI4} function ToHugeint(const x: Int64): Hugeint; overload; // Result := Hugeint(x); // Parâmetros: x na pilha; EAX = @Result; asm mov edx, dword[x+_0_] // EDX := x[0]; mov ecx, dword[x+_1_] // ECX := x[1]; mov [eax+_0_], edx // Result[0] := x[0]; mov [eax+_1_], ecx // Result[1] := x[1]; sar ecx, 31 // ECX := IIF(x[1]>=0, 0, $FFFFFFFF); mov [eax+_2_], ecx // Result[2] := ECX; // 0 or $FFFFFFFF mov [eax+_3_], ecx // Result[3] := ECX; // 0 or $FFFFFFFF end; {$ENDIF} Valores Int64 são armazenados no formato little-endian; assim, o inteiro inferior é o primeiro, com um deslocamento 0 do endereço base da variável e o inteiro superior é o segundo, com um deslocamento 4 do endereço base da variável. Nesse caso, o endereço base da variável é EBP+8 (veja o primeiro capítulo desta série de artigos) e, assim, o primeiro elemento está em EBP+8 (EBP+8+0) e o segundo elemento está em EBP+12 (EBP+8+4). Eu poderia usar EBP+8 e EBP+12 para endereçar estes elementos mas "x+_0_" e "x+_1_" referem-se aos endereços de forma mais transparente. O especificador de tamanho (DWORD) é já que o assembler recebe "x+_0_" e "x+_1_" como ponteiros para dados de 64 bits ("x" é considerado um ponteiro de dados de 64-bits) e não permite mover o valor referenciado para um registrador de 32-bits. Hugeint para Int64 ------------------ Um Hugeint pode ser convertido para um Int64 simplesmente pegando os 64 bits inferiores. Os 64 bits superiores do HugeInt podem ser definidos como 0 ou 1 para igualar o bit de sinal para que o resultado (bit 32) do valor Hugeint fique dentro dos limites de um Int64, mas a função não checa isto e executa a conversão cegamente: {$IFDEF DELPHI4} function ToInt64(const x: Hugeint): Int64; overload; // Result := Int64(x) // Nenhuma exceção é gerada se o valor não estiver dentro dos limites // (os 64 bits de mais alta ordem são descartados). // Parâmetros: EAX = @x; asm mov edx, [eax+_1_] // EDX := x[1]; mov eax, [eax+_0_] // EAX := x[0]; // Result = EDX:EAX = x[1]:x[0] end; {$ENDIF} Commentários: * Int64 retorna valores colocados em EDX (32 bits de alta ordem) e EAX (32 bits de baixa ordem). Por enquanto é isso. Nas próximas edições veremos funções que executam funções lógicas e matemáticas com inteiros gigantes.
  15. Objetos são registros ===================== Do ponto de vista do assembler, um objeto é como um registro, cujos campos são seus próprios campos mais os campos de seus ancestrais, mais um ponteiro à VMT (Virtual Methods Table - Tabela de Métodos Virtuais). Vejamos isto através de um exemplo: type TClass1 = class FieldA: integer; FieldB: string; end; TClass2 = class(TClass1) FieldC: integer; end; No exemplo, TClass2 é de certo modo como um registro com quatro campos: TClass2 = record VMT: pointer; // campo invisível, sempre o primeiro FieldA: integer; // herdado de TClass1 FieldB: string; // herdado de TClass1 FieldC: integer; // declarado em TClass2 end; Variáveis objeto são ponteiros ============================== Uma variável objeto é somente um ponteiro para um objeto, ou seja, um ponteiro para um registro. var a, b: TClass2; begin a := TClass2.Create; b := a; // somente uma declaração de ponteiro a.Free; end; Um construtor aloca memória para uma instância (objeto) de sua classe, inicializa-a e retorna um ponteiro para a memória alocada. Assim, após a chamada a TClass.Create a variável "a" aponta para o registro (o objeto): +---+ +--------+ | a | ----------> | VMT | +---+ +--------+ | FieldA | +--------+ | FieldB | +--------+ | FieldC | +--------+ A declaração "b := a" não cria um novo objeto, cópia do primeiro, mas realmente faz com que ambas as variáveis apontem para o mesmo objeto: +---+ +--------+ +---+ | a | ----------> | VMT | <---------- | b | +---+ +--------+ +---+ | FieldA | +--------+ | FieldB | +--------+ | FieldC | +--------+ Métodos assembler ================= Os métodos recebem um primeiro parâmetro invisível, chamado Self, que é um ponteiro para o objeto sobre o qual o método deve operar. type TTest = class FCode: integer; public procedure SetCode(NewCode: integer); end; procedure TTest.SetCode(NewCode: integer); begin FCode := NewCode; end; var a: TTest; begin : a.SetCode(2); : end; O código em Objetc Pascal acima é traduzido para para o Pascal padrão como segue: type TTest = record VMT: pointer; FCode: integer; end; procedure SetCode(Self: TTest; NewCode: integer); begin Self.FCode := NewCode; end; var a: ^TTest; begin : SetCode(a, 2); : end; O exemplo serve para explicar que os métodos recebem o ponteiro Self como seu primeiro parâmetro, ou seja, eles recebem o ponteiro Self no registrador EAX e o primeiro parâmetro declarado é passado como um segundo parâmetro em EDX etc. (o segundo parâmetro declarado é passado como terceito em ECX e o resto dos parâmetros são passados em pilha). O método SetCod pode ser escrito em assembler como: procedure TTest.SetCode(NewCode: integer); asm // EAX = Self = endereço da instância TTest // EDX = parâmetro NewCode // FCode := NewCode; mov TTest[eax].FCode, edx // TTest(EAX)^.FCode := EDX; end; Como se pode ver, os campos de objeto são acessados da mesma forma que os campos de registro. NOTA: Propriedades não são campos e não podem ser acessadas diretamente a partir do assembler inline. Eis um exemplo de um método chamando outro método: procedure TTest.Increment; asm // SetCode(Code+1); mov edx, TTest[eax].FCode // ECX := TTest(EAX)^.FCode; inc edx call TTest.SetCode; end; Não fixamos o valor de EAX antes de fazer a chamada já que EAX já contém o valor desejado (Self), assim o método chamado vai operar no mesmo objeto. NOTAS: * Métodos virtuais podem ser chamados somente estaticamente, já que uma referência à classe é necessária na declaração de chamada. * Métodos sobrecarregados não podem ser distinguidos em assembler inline. Construtores assembler ====================== Construtores são métodos muito especiais. Os construtores podem ser chamados para criar uma instância de uma classe (isto é, para alocar a memória para o objeto e inicializá-lo), ou simplesmente para reinicializar um objeto já criado: a := TTest.Create; // aloca memória a.Create; // apenas reinicializa um objeto existente Para distinguir entre estas duas situações, os construtores passam um segundo parâmetro invisível do tipo byte (ou seja, no registrador DL) que pode ser positivo ou negativo respectivamente (o compilador usa 1 e -1 respectivamente). Se temos de chamar um construtor a partir do código assembler com DL = $01 (para alocar memória para o objeto), temos de passar uma referência à classe em EAX. Já que não há nenhum símbolo para acessá-lo diretamente do assembler, temos que fazer algo similar ao que fizemos com o tipo de informação dos registros: var TTest_TypeInfo: pointer; : initialization TTest_TypeInfo := TTest; Agora que inicializamos uma variável global com a referência à classe a partir do nosso código Pascal, podemos usa-la em nosso código assembler: var a: TTest; begin // a := TTest.Create(2); asm mov eax, TTest_TypeInfo mov dl, 1 mov ecx, 2 call TTest.Create mov a, eax end; : end; Chamar um construtor para reinicializar o objeto é mais simples já que não precisamos de uma referência à classe: var a: TTest; begin : // a.Create(2); asm mov eax, a mov dl, -1 mov ecx, 2 call TTest.Create end; : end; Não temos nada com que nos preocupar se temos que escrever um construtor assembler já que o Delphi manuseia a alocação para nós na entrada do construtor e, após isso, o registrador EAX aponta para o objeto, como acontece com qualquer outro método. O que é relevante é que se o construtor tem parâmetros, o primeiro parâmetro declarado será internamente passado como terceito, ou seja, em ECX (ao invés de segundo, em EDX, como acontece com outros métodos) e o resto dos parâmetros serão passados em ordem, na pilha. constructor TTest.Create(NewCode: integer); asm // FCode := NewCode mov TTest[eax].FCode, ecx end; __________________ NOTA: Um exemplo com código fonte completo está anexado Funções API e a convenção de chamada Stdcall ============================================ As funções API são chamadas de forma transparente a partir do assembler nativo com a declaração CALL. Contudo, devemos levar em consideração que passar parâmetros para funções API é diferente já que elas normalmente usam o convenção de chamada Stdcall, ao invés da convenção de chamada Register, que é a que vimos em várias ocasiões anteriores já que ela é a convenção padrão. Na convenção de chamada Stdcall, todos os parâmetros são passados para a pilha, da direita para a esquerda, ou seja, o último parâmetro (o mais à direita) é enviado primeiro e o primeiro (o mais à esquerda) é enviado por último, e, portanto, ele será o primeiro no topo da pilha. Eis um exemplo de um procedimento que chama uma função API: procedure HideForm(Handle: THandle); // Windows.ShowWindow(Handle, SW_HIDE); asm push SW_HIDE // push 0 // passa o segundo parâmetro push Handle // push eax // passa o primeiro parâmetro call Windows.ShowWindow // chama a API ShowWindow end; Se temos que chamar um método que usa a convenção Stdcall, devemos lembrar que o ponteiro Self é o primeiro parâmetro invisível, logo ele será passado por último na pilha. Se temos que escrever funções que usam a convenção Stdcall, não há nada de especial para nos preocuparmos. O compilador sempre criará uma pilha e referências aos nomes dos parâmetros serão convertidas em endereços relativos ao ponteiro base: function AddAndMultiply(i1, i2, i3: integer): integer; stdcall; asm // ==> push ebp; mov ebp, esp // Result := (i1 + i2) * i3; mov eax, i1 // mov eax, [ebp+8] add eax, i2 // add eax, [ebp+12] imul i3 // imul [ebp+16] end; // ==> pop ebp; ret 12 Este é um exemplo de chamada para a função: asm // a := AddAndMultiply(1, 2, 3); // o resultado seria 9 push 3 push 2 push 1 call AddAndMultiply mov a, eax end; Após a entrada na função, a pilha se pareceria com isto: | | +----------------+ | Old EBP | [EBP], [ESP] +----------------+ | Return Address | [EBP+4] +----------------+ | i1 = 1 | [EBP+8] +----------------+ | i2 = 2 | [EBP+12] +----------------+ | i3 = 3 | [EBP+16] +----------------+ | | Bibliotecas C/C++ e a convenção de chamada Cdecl ================================================ Algumas vezes precisamos acessar funções em arquivos objeto (.OBJ), bibliotecas estáticas (.LIB) ou bibliotecas dinâmicas (.DLL) escritas em C ou C++ e, muito freqüentemente, estas funções usam a convenção de chamada Cdecl. Ela é muito parecida com a convenção Stdcall, mas a pilha deve ser limpa por quem a chama, isto é, quem a chama deve invocar os parâmetros que ela inicia ou- ainda melhor- incrementar o ponteiro da pilha. function AddAndMultiply(i1, i2, i3: integer): integer; cdecl; asm // ==> push ebp; mov ebp, esp // Result := (i1 + i2) * i3; mov eax, i1 // mov eax, [ebp+8] add eax, i2 // add eax, [ebp+12] imul i3 // imul [ebp+16] end; // ==> pop ebp; ret Note-se no comentário da última linha que a função não move o ponteiro da pilha como ocorreu no exemplo anterior que usou a convenção Stdcall; logo, quem chamar esta função é que será o responsável por isso. Este é um exemplo de chamada para esta função: asm // a := AddAndMultiply(1, 2, 3); // seria 9 push 3 push 2 push 1 call AddAndMultiply add esp, 12 // limpa a pilha mov a, eax end; Note-se que se os parâmetros fossem do tipo Byte ao invés de Integer, moveríamos ainda o ponteiro da pilha de 12 bytes já que cada parâmetro usaria 32 bits (4 bytes) nos dois casos. A convenção de chamada Pascal ============================= Muitos programadores C/C++ preferem a convenção de chamada Pascal ao invés da Cdecl porque ela é mais compacta e também mais rápida já que a função chamada limpa a pilha na declaração RET, como ocorre na convenção Stdcall. A convenção Pascal é como a Stdcall, mas os parâmetros são passados da esquerda para a direita ao invés da direita para a esquerda, isto é, o primeiro parâmetro (o mais a esquerda) é passado primeiro e o último (o mais a direita) é passado por último: function AddAndMultiply(i1, i2, i3: integer): integer; pascal; asm // ==> push ebp; mov ebp, esp // Result := (i1 + i2) * i3; mov eax, i1 // mov eax, [ebp+16] add eax, i2 // add eax, [ebp+12] imul i3 // imul [ebp+8] end; // ==> pop ebp; ret 12 Note-se como os endereços dos parâmetros são traduzidos de modo diferente que nos exemplos anteriores. Este é um exemplo de chamada desta função: asm // a := AddAndMultiply(1, 2, 3); // seria 9 push 1 push 2 push 3 call AddAndMultiply mov a, eax end; Após a chamada da função, a pilha se pareceria com isto: | | +----------------+ | EBP | [EBP], [ESP] +----------------+ | Return Address | [EBP+4] +----------------+ | i3 = 3 | [EBP+8] +----------------+ | i2 = 2 | [EBP+12] +----------------+ | i1 = 1 | [EBP+16] +----------------+ | |
  16. Passando arrays estáticas como parâmetros ========================================= Parâmetros de arrays estáticas são passados como ponteiros ao primeiro elemento do array, independentemente do parâmetro ser passado por valor ou por referência (como "var" ou como "const"). Dadas as seguintes declarações... const ARRAY_MAX = 5; type TArrayOfInt = packed array [0..ARRAY_MAX] of longint; var a, b: TArrayOfInt; procedure InitializeArray(var a: TArrayOfInt); var i: integer; begin for i := 0 to ARRAY_MAX do a := i; end; ... a chamada à procedure InitializeArray em assembler seria: // Em Object Pascal: // InitializeArray(a); // Em Assembler Inline: asm mov eax, offset a // EAX := @a; call InitializeArray // InitializeArray; end; OFFSET é um operador unário assembler que retorna o endereço de um símbolo. O OFFSET não é aplicável para símbolos locais. Deve-se usar o opcode LEA (veja abaixo), que é mais "universal". Arrays estáticas passadas por valor ----------------------------------- Se o array é passado por valor, é responsabilidade da função chamada preservar o array. Quando a função precisa mudar os valores de um ou mais elementos de um array passado por valor, normalmente ela cria uma cópia local e trabalha com a cópia. O compilador cria uma cópia para nós no "begin" das procedures e funções Pascal, mas em procedures e funções em assembler puro temos de fazer isto nós mesmos. Um modo de se fazer isto seria o seguinte: procedure OperateOnArrayPassedByValue(a: TArrayOfInt); var _a: TArrayOfInt; asm // Copia os elementos de "a" (parâmetro) em "_a" (cópia local) push esi // Salva ESI na pilha push edi // Salva EDI na pilha mov esi, eax // ESI := EAX; // @a lea edi, _a // EDI := @_a; mov eax, edi // EAX := EDI; // @_a mov ecx, type TArrayOfInt // ECX := sizeof(TArrayOfInt); rep movsb // Move(ESI^, EDI^, ECX); pop edi // Restaura EDI da pilha pop esi // Restaura ESI da pilha // Aqui vai o resto da função. Trabalharemos sobre o "_a" (a // cópia local), cujo primeiro elemento está agora apontado por EAX. end; O que encontramos de novo aqui são os opcods LEA e MOVSB, o prefixo REP e o operador TYPE, descritos abaixo: LEA (Load Effective Address) ---------------------------- Move para o primeiro operando o endereço do segundo. Aqui comparamos LEA com MOV: Instrução Traduzida como Efeito ------------------------------------------------------------------- lea eax, localvar lea eax, [ebp-$04] EAX := @localvar; EAX := EBP - $04; mov eax, localvar mov eax, [ebp-$04] EAX := localvar; EAX := (EBP - $04)^; MOVSB (MOVe String Byte) ------------------------ Copia o byte apontado por ESI ao local apontado por EDI, e incrementa ESI e EDI de tal forma que eles apontem para o próximo byte. O trabalho do MOVSB pode ser descrito como segue: ESI^ := EDI^; // Assume que ESI e EDI são do tipo PChar Inc(ESI); Inc(EDI); Notas: * MOVSW e MOVSD são as versões Word (16-bit) e DWord (32-bit) respectivamente (ESI e EDI são incrementadas de 2 e 4 respectivamente). * Os registradores são decrementados se o Direction Flag é setado. REP --- O prefixo REP é usado em operações de string para repetir a operação de decremento ECX até que ECX seja zero. O trabalho do REP poderia ser descrito como segue: // rep string_instruction @@rep: string_instruction loop @@rep Notas: * O REP não é um atalho para um código como o acima. Ele trabalha muito mais rápido. * O valor de ECX não é checado no começo do loop (se ECX fosse zero, a instrução seria repetida 2^32 vezes, mas geraria um extenso AV antes disto, tão logo ESI ou EDI apontassem para uma localição de memória inválida). TYPE ---- O operador TYPE é um operador unário avaliado em tempo de compilação que retorna o tamanho em bytes de um operando, que deve ser um tipo de dados. Por exemplo, TYPE WORD retornará 2 e TYPE INTEGER retornará 4. Acessando os elementos de um array ================================== Para acessar um elemento a precisamos dos valores "@a[0]" e "i" nos registradores (como EDX e ECX, por exemplo) para então podermos usar o endereçamento de memória como segue: lea edx, a // EDX := @a; mov ecx, i // ECX := i; mov ax, [edx+ecx*type integer] // AX := EDX[ECX]; // a; // PWord(EDX + ECX * SizeOf(integer))^ No exemplo, presumimos que os elementos têm 2 bytes (movemos o valor de a para AX, um registrador de 16 bits), que a array não é agrupada (cada elemento realmente ocupa 4 bytes, o tamanho de um inteiro, logo este valor foi usado para calcular a posição do elemento) e que o array não começa pelo elemento 0, ou seja, não é um array "zero-based". Por exemplo: var a: array [0..N] of word = (1, 2, 3, 6, ...); +------ EDX = @a | v +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+-- | 1 | 0 | | | 2 | 0 | | | 3 | 0 | | | 6 | 0 | | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+-- a[0] a[1] a[2] a[3] [edx] [edx+04] [edx+08] [edx+12] Se o array não é zero-based, temos que ajustar o valor do índice para torná-lo zero-based antes de endereçarmos o elemento. Exemplos: // a[1..100] : mov ecx, i // ECX := i; dec ecx // Dec(ECX); // Ajusta ECX : // a[-10..10] : mov ecx, i // ECX := i; add ecx, 10 // Inc(ECX, 10); // Ajusta ECX : A procedure InitializeArray (apresentada acima) pode ser implementada em assembler do seguinte modo: procedure InitializeArray(var a: TArrayOfInt); asm // EAX = PByte(@a[0]); xor ecx, ecx // ECX := 0; @@loop: mov [eax+ecx*type integer], ecx // PInteger(EAX+ECX*4)^ := ECX; // ...or EAX[ECX] := ECX; inc ecx // ECX := ECX + 1; cmp ecx, ARRAY_MAX // if ECX <= ARRAY_MAX then jle @@loop // goto @@loop; end; Ou assim: procedure InitializeArray(var a: TArrayOfInt); asm // EAX = @a[0]; xor ecx, ecx // ECX := 0; @@loop: mov [eax], ecx // EAX^ := ECX; inc ecx // Inc(ECX); add eax, type integer // Inc(EAX); // Aponta para o próximo elemento cmp ecx, ARRAY_MAX // if ECX <= ARRAY_MAX then jle @@loop // goto @@loop; end; Valores de retorno de arrays ============================ As funções que retornam arrays recebem um último parâmetro adicional que é o ponteiro para a locação de memória onde deveam colocar seu valor de retorno (a memória é alocada e liberada se necessário por quem acessou). Por exemplo, consideremos a seguinte função: function ReverseArray(const a: TArrayOfInt): TArrayOfInt; var i: integer; begin for i := 0 to ARRAY_MAX do Result := a[ARRAY_MAX-i]; end; A função recebe dois parâmetros: 1) EAX = endereço do primeiro elemento da array "a" 2) EDX = endereço do primeiro elemento de Result A função pode ser reescrita em assembler como segue: function ReverseArray(const a: TArrayOfInt): TArrayOfInt; asm // EAX = @a[0]; EDX = @Result[0]; push ebx // Save EBX mov ebx, eax // EBX := EAX; xor ecx, ecx // ECX := 0; @@loop: mov eax, ARRAY_MAX sub eax, ecx // EAX := ARRAY_MAX-ECX; mov eax, [ebx+eax*type integer] // EAX := EBX[EAX]; mov [edx+ecx*type integer], eax // EDX[ECX] := EAX; inc ecx // ECX := ECX + 1; cmp ecx, ARRAY_MAX // if ECX <= ARRAY_MAX then jle @@loop // goto @@loop; pop ebx // Restore EBX end; Bem, isto é tudo por enquanto. No próximo artigo veremos como trabalhar com registros. Passando records como parâmetros ================================ Como arrays estáticos, records são internamente passados como ponteiros para os dados, independentemente se o parâmetro é passado por valor ou por referência (também como "var" ou como "const"). Dada as seguintes declarações... type TRecord = record Id: integer; Name: string; end; var a, b: TRecord; procedure InitRecord(var r: TRecord; Id: integer; const Name: string); begin r.Id := Id; r.Name := Name; end; ...uma chamada para a procedure InitRecord em assembler seria assim: // Em Object Pascal: // InitRecord(a, n, s); // Em Inline Assembler: asm lea eax, a // EAX := @a; // 1st parameter in EAX mov edx, n // EDX := n; // 2nd parameter in EDX mov ecx, s // ECX := s; // 3rd parameter in ECX call InitRecord // InitRecord; end; Acessando os campos de um record ================================ Campos de records estão localizados em um certo offset de um endereço do record (o endereço do primeiro campo). No exemplo, assumindo que nós temos o endereço do record do tipo TRecord no registrador EAX, o campo Id está localizado em [EAX+0] (ou simplesmente [EAX]), e o campo Name está localizado em [EAX+4], mas normalmente nós não escrevemos código usando números explicitamente. Ao invés disto, para produzir código auto-explicável e de fácil manutenção, nós temos cinco alternativas: mov edx, [eax + TRecord.Name] mov edx, (TRecord PTR [eax]).Name mov edx, (TRecord [eax]).Name mov edx, TRecord[eax].Name mov edx, [eax].TRecord.Name As cinco sentenças anteriores seriam montadas como: mov edx, [eax + 4] No lugar de um registrador (como EAX), as sintaxes também se aplicam para nome de variáveis locais ou globais. Você pode deduzir da primeira sintaxe que em inline assembler a expressão RecordType.Field é avaliada em tempo de compilação como uma constante representando o offset no qual o campo está localizado no RecorType. Por exemplo, a seguinte sentença é válida: mov ecx, TRecord.Name // mov ecx, 4 Voltando ao assunto, a procedure InitRecord (apresentada acima) pode ser implementada em assembler desta forma: procedure InitRecord(var r: TRecord; Id: integer; const Name: string); asm // EAX = @r; EDX = Id; ECX = @Name[1] mov (TRecord PTR [eax]).Id, edx // EAX^.Id := EDX; // Id // _LStrAsg(@EAX^.Name, @Name) --> EAX^.Name := Name lea eax, (TRecord PTR [eax]).Name // EAX := @(EAX^.Name); mov edx, ecx // EDX := @Name[1]; call System.@LStrAsg // _LStrAsg(EAX, EDX) end; Na entrada da procedure, nós temos EAX apontando para o registro (primeiro parâmetro), EDX contendo o Id (segundo parâmetro), EDX apontando para o dado da string Name (terceiro parâmetro). Atribuir um inteiro é bem simples, mas atribuir uma string é um pouco mais complicado. Se a string destino não é uma string vazia então begin Decremente a contagem de referência da string destino; Se a contagem de referência da string destino chegou a zero então Libere a string destino; end; Se a String Origem não for uma string vazia então Incremente a contagem de referência da String origem; Designe origem para o destino; A procedure _LStrAsg (da Unit System) implementa esta lógica para nós. A procedure recebe dois parâmetros: o primeiro (em EAX) é a string destino passada por referência e o segundo (em EDX) é a string origem passada por valor (o que é passado na verdade é o ponteiro, visto que strings são ponteiros para os caracteres de fato). Então, no nosso caso, EAX deveria ser o endereço de uma variável string que será atribuída (isto é @r.Name), enquanto EDX deveria ser o valor a ser atribuído: EAX --> r.Name --> r.Name[1] ==> EAX = @r.Name EDX --> Name[1] ==> EDX = @Name[1] Ref.: "-->" significa "aponta para" Então, preparamos EAX e EDX e então chamamos _LStrAsg: lea eax, (TRecord PTR [eax]).Name // EAX := @(EAX^.Name); mov edx, ecx // EDX := @Name[1]; call System.@LStrAsg // _LStrAsg(EAX, EDX) Funções de baixo nível para trabalhar com records ================================================= Como arrays estáticos, se o record é passado por valor, é responsabilidade da função chamada preservar o record. Quando uma função precisa trocar o valor de um ou mais campos do record passado por valor, normalmente ela cria uma cópia local e trabalha com a cópia. O compilador cria uma cópia para nós no "begin" das funções Pascal, mas nas funções puramente assembler temos que fazê-lo nós mesmos. Um jeito de fazer isto é como mostrado na parte III com arrays estáticos. Aqui está outro modo: procedure OperateOnRecordPassedByValue(r: TRecord); var _r: TRecord; asm // Copia os elementos de "r" (parâmetros) em "_r" (cópia local) // Move(r, _r, sizeof(TRecord)); lea edx, _r // EDX := @_r; mov ecx, type TRecord // ECX := sizeof(TRecord); call Move // Move(EAX^, EDX^, ECX); lea eax, _r // EAX := @_r; mov edx, TRecord_TypeInfo // EDX := TRecord_TypeInfo; call System.@AddRefRecord // System._AddRefRecord(EAX,EDX); lea eax, _r // EAX := @_r; // optional // Aqui vai o resto da função. Nós trabalharemos no // record "_r" (a cópia local), agora apontada por EAX. end; Desta vez nós chamamos a procedure Move ao invés de copiarmos os dados com REP MOVSB. Deste modo, nós escrevemos menos código. IMPORTANTE: Copiar os valores da memória apenas funciona com records que não contém campos do tipo reference-counted tais como strings, arrays dinâmicos ou variantes do tipo string ou arrays dinâmicos. Se nós tivermos um ou mais campos string, ou campos de algum outro tipo reference-counted, depois de copiar os valores de memória, nós temos que incrementar seus respectivos contadores de referência. A procedure _AddRefRecord (da Unit System) realiza isto. Ela possui dois parâmetros: um ponteiro para o record (em EAX) e um ponteiro para informação do tipo de dado para o record gerado pelo compilador (em EDX). A informação de tipo para o record é basicamente uma estrutura de dados que contém as posições e tipos de campos reference-counted do registro. As procedures que trabalham com records declaradas na Unit System, (_InitializeRecord, _AddRefRecord, _CopyRecord, e _FinalizeRecord) requerem um ponteiro para a informação do tipo de dado como seu último parâmetro. Mas, onde estão os dados? Bem, infelizmente, não há um símbolo para acessar sua localização diretamente. Nós temos que conseguir seu endereço através de uma chamada para a função TypeInfo, mas não há uma função que nós possamos chamar através do código assembler porque não é uma função verdadeira, e sim uma função interna que o compilador resolve em tempo de compilação. Um possível contorno é inicializar uma variável global, chamando a função TypeInfo de nosso código Pascal: var TRecord_TypeInfo: pointer; : initialization TRecord_TypeInfo := TypeInfo(TRecord); E então podemos usá-la como: procedure OperateOnRecordPassedByValue(r: TRecord); var _r: TRecord; asm // Copiar os elementos de "r" (parâmetro) para "_r" (cópia local) // Move(_r, r, sizeof(TRecord)); lea edx, _r // EDX := @_r; mov ecx, TYPE TRecord // ECX := sizeof(TRecord); call Move // Move(EAX^, EDX^, ECX); // System._AddRefRecord(@_r, TypeInfo(TRecord)); lea eax, _r // EAX := @_r; mov edx, TRecord_TypeInfo // EDX := TypeInfo(TRecord); call System.@AddRefRecord // System._AddRefRecord(EAX, EDX); lea eax, _r // EAX := @_r; // opcional // Aqui vai o resto da função. Nós trabalharemos no // record "_r" (a cópia local), agora apontada em EAX. // Nós temos que finalizar a cópia local antes de retornarmos // System._FinalizeRecord(@_r, TypeInfo(TRecord)); lea eax, _r // EAX := @_r; mov edx, TRecord_TypeInfo // EDX := TypeInfo(TRecord); call System.@FinalizeRecord // System._FinalizeRecord(EAX, EDX); end; Note que antes da função retornar, nós temos que fazer a chamada a _FinalizeRecord para destruir o record local (por exemplo, isto decrementará a contagem de referência de strings apontadas por campos string). Chamar Move e então _AddRefRecord é um jeito válido de copiar records se e apenas se o record de destino tenha sido inicializado (depois de chamar _AddRefRecord, o record é inicializado). Se o record de destino já estiver inicializado, então nós temos que chamar _CopyRecord ao invés disto. Por Exemplo: procedure proc(const r: TRecord); var _r: TRecord; begin // _r := r; asm mov edx, eax // EDX := @r; lea eax, _r // EAX := @_r; mov ecx, TRecord_TypeInfo // ECX := TypeInfo(TRecord); call System.@CopyRecord // System._CopyRecord(EAX, EDX, ECX); end; end; Note que como isto é uma função Pascal normal (não uma função Assembler completa), o compilador automaticamente gera código para inicializar e finalizar a variável record local (no "begin" e "end" da procedure respectivamente). A combinação Move mais _AddRefRecord é idêntica em efeito a _InitializeRecord mais _CopyRecord: procedure OperateOnRecordPassedByValue(r: TRecord); var _r: TRecord; asm // Copiar os elementos de "r" (parâmetro) para "_r" (cópia local) // Move(_r, r, sizeof(TRecord)); // System._InitializeRecord(@_r, TypeInfo(TRecord)); push eax // Push(EAX); // @r lea eax, _r // EAX := @_r; mov edx, TRecord_TypeInfo // EDX := TypeInfo(TRecord); call System.@InitializeRecord // System._InitializeRecord(EAX, EDX); // _r := r; lea eax, _r // EAX := @_r; pop edx // EDX := Pop(); // @r mov ecx, TRecord_TypeInfo // EDX := TypeInfo(TRecord); call System.@CopyRecord // System._CopyRecord(EAX, EDX, ECX); lea eax, _r // EAX := @_r; // optional // Aqui vai o resto da função. Nós trabalharemos no // record "_r" (a cópia local), agora apontada em EAX. // Nós temos que finalizar a cópia local antes de retornarmos // System._FinalizeRecord(@_r, TypeInfo(TRecord)); lea eax, _r // EAX := @_r; mov edx, TRecord_TypeInfo // EDX := TypeInfo(TRecord); call System.@FinalizeRecord // System._FinalizeRecord(EAX, EDX); end; Como _AddRefRecord, a procedure _InitializeRecord é apenas destinada para ser usada com records não inicializados. Retornando valores de records ============================= Retornar valores de records é exatamente o mesmo que retornar valores de array estático. Funções que retornam records recebem um último parâmetro adicional que é o ponteiro para a localização em memória onde o valor de retorno deve ser armazenado, isto é, o valor do último parâmetro é @Result. A memória para o record de resultado deveria ser alocada, inicializada e liberada pelo chamador (não é de responsabilidade da função chamada). Por exemplo, vamos considerar a seguinte função: function MakeRecord(Id: integer; const Name: string): TRecord; begin Result.Id := Id; Result.Name := Name; end; A função é declarada para receber dois parâmetros e retornar um record, mas internamente é como uma procedure com três parâmetros: 1) EAX = O Id para o novo record 2) EDX = O nome para o novo record 3) ECX = O endereço do record de resultado (@Result) A função pode ser reescrita em assembler como segue : function MakeRecord(Id: integer; const Name: string): TRecord; asm // EAX = Id; EDX = @Name[1]; ECX = @Result mov (TRecord PTR [ecx]).Id, eax // ECX^.Id := EAX; // Id // (@Result)^.Id := EAX; // Result.Id := EAX; // Result.Name := Name; // System.@LStrAsg(@(Result.Name), @Name[1]) // System.@LStrAsg(@(ECX^.Name), @Name[1]) lea eax, (TRecord PTR [ecx]).Name // EAX := @(ECX^.Name); call System.@LStrAsg // _LStrAsg(EAX, EDX) end; NOTA: Nós não designamos o valor EDX antes de chamar _LStrAsg porque EDX já contém o valor desejado (passado como parâmetro) Chamando funções que retornam records ===================================== Considere o seguinte código: a := MakeRecord(n, s); Alguém seria tentado a pensar que o compilador traduz como: asm mov eax, n mov edx, s lea ecx, a // ECX := @a; // @Result call MakeRecord end; Mas as coisas não acontecem deste jeito, ao menos no Delphi 5. O compilador aloca e inicializa uma variável local que armazena o resultado e então copia o resultado do record para o record de destino. Nós não temos apenas ineficiência realizando uma cópia que seria desnecessária se usássemos um código como o acima, mas- como nós temos visto acima- a cópia por si mesma não é tão inocente como uma chamada para a procedure Move (_CopyRecord checa a informação de tipo de dado em runtime para localizar os campos que requerem tratamento especial). é claro, a variável local invisível é primeiro inicializada e eventualmente finalizada. Este modo é extremamente ineficiente. Se você precisa de velocidade, chame funções record-returning usando assembler como mostrado acima, passando diretamente o endereço da variável que irá guardar o resultado como o último parâmetro (@Result). Bem, é isto por enquanto. Na próxima parte, veremos algumas coisas básicas sobre o trabalho com objetos. __________________ NOTA: O Código fonte e a aplicação DEMO estaram anexados no último post desse artigo.