[Tutorial] Pawn Amx.

23 de dez. de 2012.
O que é o arquivo de extensão .amx que é gerado pela compilação do código escrito em Pawn ? Para que serve a diretiva #emit ? Qual a relação entre o programa samp-server e o arquivo .amx ?

O processamento tem sido desde muito tempo a parte mais difícil da programação em Pawn. Por essa razão, códigos como #emit se tornaram símbolos dos maiores mestres .

Particularmente penso que isso tenha acontecido devido ao pouco conhecimento sobre o funcionamento do computador que é adquirido com o estudo dos manuais da linguagem.

1 - Computador.

A idéia do computador surgiu com o cientista Blaise Pascal, o mesmo elaborador do Teorema de Pascal que é estudado em Hidróstática (Física) no Ensino Médio .


Graças a uma variação de tensão elétrica (uma espécie de "ligação" que existe entre dois pontos de um campo elétrico, espaço onde uma partícula com carga, como o elétron, exerce sua força elétrica) que ocorre nos circuitos do aparelho, surgem dois estados que são a base de tudo que o computador faz: ligado (true) e desligado (false).



Com a manipulação desses estados, o denominado processador (popularmente chamado de CPU, que significa Unidade Central de Processamento em inglês) consegue efetuar cálculos aritméticos (adição, subtração, etc) e lógicos (AND, OR, etc). 



Compatível com o processador, há a memória principal (popularmente conhecida como memória RAM), que é o lugar onde ficam os dados de todos os programas que estão sendo executados. Esses dados são os bits, que podem ser 0 ou 1, justamente devido à CPU .



O processador é composto por registradores, elementos que trabalham diretamente com os dados que ficam na memória.



Quando você cria uma variável em uma linguagem de programação, ela, durante o processamento (ou execução) do programa, irá para a memória principal e lá será controlada pelos registradores do processador, que farão acontecer o que você quis fazer com a variável.


2 - Máquina Abstrata.

Pawn é uma linguagem de programação de extensão, isto é, tem o objetivo de estender as funcionalidades de um programa. 

Por isso que quando um código pode ser escrito, a equipe SA-MP raramente disponibiliza uma função padrão que faça a mesma coisa que ele .

Além disso, Pawn, assim como Java, é uma linguagem que usa uma máquina abstrata (ou máquina virtual), um programa que executa o arquivo criado por ela. Pode haver diversos motivos para isso, como tecnologia multiplataforma e otimização.

Logo, o arquivo .amx (acrônimo para Abstract Machine Executor, executor da máquina abstrata, ou seja, aquilo que é responsável pelas operações da mesma) não é lido diretamente pelo sistema operacional como um arquivo .exe, e sim pela máquina abstrata que vem embutida no programa samp-server.

O código que é lido pela máquina abstrata é o bytecode (ou P-code), nada mais nada menos que o conteúdo do arquivo .amx e próprio código Pawn após a compilação .

A máquina abstrata também tem seu próprio espaço virtual na memória. Nesse local é que todos os dados programados em Pawn são trabalhados.

Isso explica a relação entre o samp-server e os arquivos .amx.


Continue Lendo >


3 - Memória.

Para o processador do computador, nomes de dados e outras informações similares são insignificantes, pois eles são organizados na memória de acordo com um sistema de endereços.

O endereço de uma variável é um número utilizado para localizá-la na memória. Cada byte (conjunto de 8 bits) tem seu próprio endereço:



Sendo cada número em vermelho o endereço do dado ao seu lado, a figura acima expressa a distribuição de endereços na memória.

Nota-se que os endereços são atribuídos de forma crescente e conforme o total de bytes ocupados da memória do computador. Isso significa que antes do dado de endereço 1200000 já existem outros dados, que podem ser até de outro programa.

Em programação, o nome de uma variável que contém o endereço de outra variável é ponteiro. Há também o termo referência, que é o mesmo que endereço.

Isso é a parte geral.

Como eu disse anteriormente, a máquina abstrata ocupa uma porção da memória. Todos os endereços que podem ser manipulados em Pawn são atribuídos levando em conta apenas essa parte da memória, ou uma seção dela.

Em outras palavras, jamais esses endereços terão alguma relação com os de outros programas, simplesmente pelo fato de a linguagem Pawn não ter sido desenvolvida para a produção de aplicações como os arquivos de extensão .exe.

A memória da máquina abstrata é dividida em dois blocos, o estático e o dinâmico .

O estático é constituído de seções chamadas prefix, data e code. Já o dinâmico engloba as seções stack e a heap:




Na figura, as seções foram colocadas nesta ordem pelo fato de haver uma de ligação entre todas elas. Por exemplo, se a data aumenta de tamanho, o endereço do topo da heap muda.

O bloco estático da memória tem esse nome porque não se pode modificar o seu tamanho durante a execução do .amx.

Então quer dizer que o tamanho do bloco dinâmico pode ser modificado ? 

De certa forma sim, mas ele tem um tamanho fixo também 

Na verdade, o que ocorre é que o bloco dinâmico tem um tamanho fixo definido na compilação, mas nele há um espaço livre e um espaço ocupado por dados. Estes que vão sendo alterados no processamento.
Isso porque o que fica no bloco estático é construído durante a compilação, e o que fica no dinâmico durante a execução.

* Prefix

O prefix é a seção da memória da máquina abstrata na qual estão armazenadas informações de criação de variáveis locais não declaradas com static, tamanho do bloco estático, nomes de funções declaradas com public e native, entre outras.


* Data

Na data ficam as variáveis globais, as declaradas com static (mesmo se forem locais) e as strings que são explicitamente passadas a funções.

* Code

Na seção code são colocadas todas as definições de funções.

* Stack

A stack ou pilha é a mais complexa de todas as seções. Ela é a região da memória na qual ficam as variáveis locais não declaradas comstatic, os argumentos de funções, entre outros.

Essa estrutura funciona conforme a lei de LIFO, sigla para "Last In, First Out", que em português pode ser interpretado como "Último a Entrar, Primeiro a Sair".

Isso quer dizer que no código abaixo, primeiro a variável x seria construída, depois a y e depois a z, mas no final da execução da função, primeiro a variável z seria destruída, depois a y e por fim a x:

pawn Code:
main() {     new x;     new y;     new z;     print("1");     print("2");     print("3"); }


Interessante, não  ?

Duas operações caracterizam a stack: a push, que é a adição de um dado ao topo da stack, e a pop, que é a remoção do dado no topo da stack.



A stack também pode guardar informações de outras funções no chamado frame da função. Por exemplo:

pawn Code:
main() {     new a;     new b;     Chamar(); } stock Chamar() {     new c; }


No caso acima, a stack da máquina abstrata assumiria este formato:



No frame da função main ficariam as variáveis a e b, e no da função Chamar ficaria a variável c.

Pouco antes da função Chamar ser executada, um dado especial é colocado na stack: o endereço de retorno.

É por meio dele que é possível voltar justamente à função main logo após a Chamar ser lida.

Quando chega o fim de uma função, as variáveis que foram criadas nela são destruídas. No caso do exemplo acima, isso na stack seria expresso pela remoção do frame da função Chamar e consequentemente a elevação do frame do main ao topo da stack.

Outro ponto importante é o adicionamento de argumentos de funções na stack. Isso é feito na ordem inversa, então, no código abaixo, primeiro o número 10 seria colocado na stack, depois o 20 e por fim o 30:

pawn Code:
main() {     Numeros(30, 20, 10); } stock Numeros(a, b, c) {     return 1; }


E o principal sobre a stack é isso. Mais detalhes serão abordados na parte do tutorial sobre #emit.
* Heap

A heap em si é pouco utilizada no SA-MP Pawn. Dentre os dados que vão para a heap, destacam-se os valores padrões de argumentos de funções que são arrays ou passados por referência.

Exemplo:

pawn Code:
stock Valores(x, &y = 20) {     printf("Valor em x: %d", x);     printf("Valor em y: %d", y);     return 1; } stock Outros(a[] = "Exemplo") {     printf("String em a: %s", a);     return 1; }
Se as funções acima fossem chamadas, o número 20 e a string "Exemplo" seriam enviados para a heap.

Agora, lembra daquela figura que mostrava como é a memória da máquina abstrata ? Não sei se percebeu, mas não havia um traço separando a stack e a heap .

Isso foi intencional, pois elas, diferentemente das outras seções, compartilham o bloco em que estão.

Se a stack fica muito grande ela acaba se colidindo com a heap (stack overflow), o que causa o run-time erro "stack/heap collision". O mesmo acontece se heap ficar muito grande (heap overflow).

Outro colapsos são o stack underflow e heap underflow, que são causados por uma remoção de dados maior que a adição.

4 - Registradores.

Os registradores são unidades que compõem a CPU. Os dados são movidos da memória para eles a fim de serem processados.

A quantidade desses elementos varia dependendo do processador, porém o número de registradores que atuam com a máquina abstrata é fixo .
São os registradores especiais da máquina abstrata:

* PRI

Esse é o registrador especial principal. Ele, junto com o registrador ALT, processa todos os códigos Pawn.

* ALT

É o registrador alternativo. Tem a mesma função do principal.


* DAT

Registrador que trabalha com o endereço para o início da seção data, sendo ele referente a ela, não à máquina abstrata.

* COD

Registrador que trabalha com o endereço para o início da seção code, sendo ele referente a ela, não à máquina abstrata.

* CIP

Registrador que trabalha com o endereço para o local de uma função o qual está sendo lido no momento. Esse endereço é relativo à seção code, não à máquina abstrata.

* STP

Registrador que trabalha com o endereço para o início da seção stack, sendo ele referente a ela, não à máquina abstrata.

* STK

Registrador que trabalha com o endereço do dado da stack que está sendo processado no momento, sendo ele referente à seção stack, não à máquina abstrata.


* FRM

Registrador que trabalha com o endereço para o fim do frame de função que está abaixo do frame que está no topo da stack no momento, sendo esse endereço referente à seção stack, não à máquina abstrata.

* HEA

Registrador que trabalha com o endereço para o início da seção heap, sendo ele referente a ela, não à máquina abstrata.

Através da diretiva #emit pode-se controlar esses registradores e consequentemente o processamento do arquivo .amx.

5 - Amx Assembly.

Entre todas as linguagens de programação, Assembly é considerada a de mais baixo nível, ou seja, a mais próxima da linguagem de máquina. Caracteriza-se por permitir a programação minuciosa das ações de registradores.

O Amx Assembly é uma sublinguagem da linguagem Pawn, e é constituído pelos diferentes códigos que podem ser formados pela diretiva de pré-processador #emit .

Cada passo do processamento, isto é, cada operação que a máquina abstrata efetua é denominada instrução.



Em #emit, uma instrução é representada por símbolos chamados mnemônicos. Dezenas destes estão disponíveis no SA-MP Pawn, por isso os mais úteis serão vistos nesta parte do tutorial.



O parâmetro de um mnemônico pode fazer uma coisa totalmente diferente dependendo se é ou não é uma variável ou função, então organizei as explicações de cada tipo.




* LOAD.pri ou LOAD.alt



Esse mnemônico move o valor de um dado para o registrador PRI ou ALT, dependendo de qual for especificado. 




* Seguido de um número



Considera-o como o endereço da variável da seção stack/heap cujo valor será passado ao registrador. Esse endereço seria relativo à seção stack/heap:

pawn Code:
#include <a_samp> main() {     new a = 105;     new b = 2005;     new x, y;     #emit LOAD.pri 16556  //Move para o registrador PRI o valor da variável "a".     #emit LOAD.alt 16552  //Move para o registrador ALT o valor da variável "b".     #emit STOR.S.pri x   //Armazena na variável "x" o valor em PRI.     #emit STOR.S.alt y   //Armazena na variável "y" o valor em ALT.     printf("Valor da variável a: %d", x);  //Mostra o número 105.     printf("Valor da variável b: %d", y);  //Mostra o número 2005. }


Note que para pegar o endereço da variável b diminuimos em 4 o endereço da variável a, que foi colocada antes dela na stack. 



O raciocínio de variar em 4 o endereço de uma cell para obter os endereços das cells que estão ao seu redor na memória pode ser aplicado a qualquer tipo de endereço.




* Seguido de um nome



Considera-o como o nome do dado da seção data/code (uma variável global, por exemplo) que terá seu valor passado ao registrador:

pawn Code:
#include <a_samp> new a = 125; main() {     new b;     #emit LOAD.pri a  //Move para o registrador PRI o valor da variável "a".     #emit STOR.S.pri b  //Armazena na variável "b" o valor em PRI.     printf("Valor da variável a: %d", b);  //Mostra o número 125. }




* LOAD.S.pri ou LOAD.S.alt



Tem a mesma função do LOAD.pri ou LOAD.alt, mas é um pouco diferente.




* Seguido de um número



Considera-o como o endereço da variável da seção stack/heap que terá seu valor passado ao registrador especificado. Mas esse endereço é relativo à função na qual o #emit é utilizado.



Por exemplo:

pawn Code:
#include <a_samp> main() {     Funcao(); } stock Funcao() {     new x = 62;     new y = 20;     new a, b;     #emit LOAD.S.pri 0xFFFFFFFC  //Move o valor da variável "x" para o registrador PRI.     #emit LOAD.S.alt 0xFFFFFFF8  //Move o valor da variável "y" para o registrador ALT.     #emit STOR.S.pri a  //Armazena na variável "a" o valor em PRI.     #emit STOR.S.alt b  //Armazena na variável "b" o valor em ALT.     printf("Valor da variável x: %d", a);  //Mostra o número 62.     printf("Valor da variável y: %d", b);  //Mostra o número 20.     return 1; }


E esses 0xFFFFFFFC e 0xFFFFFFF8 ?



Eles são os endereços das variáveis x e y, respectivamente. São os mesmos -4 e -8 em notação hexadecimal, mas não os coloquei assim porque o #emit não reconhece o sinal negativo .




* Seguido de um nome



Avalia-o como o nome do dado da seção stack/heap (uma variável local não declarada com static, por exemplo) que terá seu valor passado ao registrador:

pawn Code:
#include <a_samp> main() {     Funcao(); } stock Funcao() {     new i = 75;     new o;     #emit LOAD.S.pri i  //Move o valor da variável "i" para o registrador PRI.     #emit STOR.S.pri o  //Armazena o valor em PRI na variável "o".     printf("Valor da variável i: %d", o);  //Mostra o número 75.     return 1; }




* STOR.pri ou STOR.alt



Armazena em uma variável o valor que está em um registrador.




* Seguido de um número



Considera-o como o endereço da cell da seção data/code na qual será armazenado o valor do registrador indicado. Esse endereço é relativo à seção data/code.

pawn Code:
#include <a_samp> new x; new y; new z; main() {     #emit CONST.pri 123  //Move o número 123 para o registrador PRI.     #emit CONST.alt 321  //Move o número 321 para o registrador ALT.     #emit STOR.pri 4  //Armazena na variável "y" o valor em PRI.     #emit STOR.alt 8  //Armazena na variável "z" o valor em ALT.     printf("Valor da variável x: %d", x);  //Mostra o número 0.     printf("Valor da variável y: %d", y);  //Mostra o número 123.     printf("Valor da variável z: %d", z);  //Mostra o número 321. }




* Seguido de um nome



Avalia-o como o nome da cell da seção data/code na qual será armazenado o valor de um registrador.



Por exemplo:

pawn Code:
#include <a_samp> new x; new y; new z; main() {     #emit CONST.pri 14  //Move o número 14 para o registrador PRI.     #emit CONST.alt 19  //Move o número 19 para o registrador ALT.     #emit STOR.pri y  //Armazena na variável "y" o valor em PRI.     #emit STOR.alt z  //Armazena na variável "z" o valor em ALT.     printf("Valor da variável x: %d", x);  //Mostra o número 0.     printf("Valor da variável y: %d", y);  //Mostra o número 14.     printf("Valor da variável z: %d", z);  //Mostra o número 19. }




* STOR.S.pri ou STOR.S.alt



Atua de forma similar ao STOR.pri ou STOR.alt.




* Seguido de um número



Considera o número da mesma forma que o LOAD.S.pri ou LOAD.S.alt, mas passa o valor do registrador para a variável em vez de da variável para o registrador:

pawn Code:
#include <a_samp> main() {     Funcao(); } stock Funcao() {     new i;     new o;     #emit CONST.pri 30  //Move o número 30 para o registrador PRI.     #emit CONST.alt 40  //Move o número 40 para o registrador ALT.     #emit STOR.S.pri 0xFFFFFFFC  //Armazena na variável "i" o valor em PRI.     #emit STOR.S.alt 0xFFFFFFF8  //Armazena na variável "o" o valor em ALT.     printf("Valor da variável i: %d", i);  //Mostra o número 30.     printf("Valor da variável o: %d", o);  //Mostra o número 40.     return 1; }



* Seguido de um nome



Avalia o nome de maneira idêntica ao LOAD.S.pri ou LOAD.S.alt, mas passa o valor do registrador para a variável em vez de da variável para o registrador:

pawn Code:
#include <a_samp> main() {     Funcao(); } stock Funcao() {     new a;     new b;     #emit CONST.pri 60  //Move o número 60 para o registrador PRI.     #emit CONST.alt 70  //Move o número 70 para o registrador ALT.     #emit STOR.S.pri a  //Armazena na variável "a" o valor em PRI.     #emit STOR.S.alt b  //Armazena na variável "b" o valor em ALT.     printf("Valor da variável a: %d", a);  //Mostra o número 60.     printf("Valor da variável b: %d", b);  //Mostra o número 70.     return 1; }




* CONST.pri ou CONST.alt



Pode igualar o valor em um registrador a um número ou pode pegar o endereço de um dado.




* Seguido de um número



Iguala o registrador ao número especificado:

pawn Code:
#include <a_samp> main() {     new u;     #emit CONST.pri 9200  //Move o número 9200 ao registrador PRI.     #emit STOR.S.pri u  //Armazena o valor em PRI na variável "u".     printf("Valor da variável u: %d", u);  //Mostra o número 9200. }




* Seguido de um nome



Trata-o como o nome de um dado cujo endereço será passado ao registrador. Se esse dado for da seção stack/heap, o endereço é relativo à função na qual #emit for utilizado e se for da seção data/code, o endereço é relativo à essa seção.



Exemplo, se da seção stack/heap:

pawn Code:
#include <a_samp> main() {     Funcao(); } stock Funcao() {     new x;     new y;     new a;     new b;     #emit CONST.pri x  //Move o endereço da variável "x" para o registrador PRI.     #emit CONST.alt y  //Move o endereço da variável "y" para o registrador ALT.     #emit STOR.S.pri a  //Armazena na variável "a" o endereço em PRI.     #emit STOR.S.alt b  //Armazena na variável "b" o endereço em ALT.     printf("Endereço da variável x: %d", a);  //Mostra o número -4.     printf("Endereço da variável y: %d", b);  //Mostra o número -8.     return 1; }


Exemplo, se da seção data/code:

pawn Code:
#include <a_samp> new a; new b; main() {     new c, d;     #emit CONST.pri a  //Move o endereço da variável "a" para o registrador PRI.     #emit CONST.alt b  //Move o endereço da variável "b" para o registrador ALT.     #emit STOR.S.pri c  //Armazena na variável "c" o endereço em PRI.     #emit STOR.S.alt d  //Armazena na variável "d" o endereço em ALT.     printf("Endereço da variável a: %d", c);  //Mostra o número 0.     printf("Endereço da variável b: %d", d);  //Mostra o número 4. }




* ADDR.pri ou ADDR.alt



Move o endereço de uma variável ao registrador indicado.




* Seguido de um número



Ainda não descoberto.




* Seguido de um nome



Envia ao registrador o endereço de um dado da seção stack/heap ou da seção data/code. Esse endereço é relativo à seção stack/heap:

pawn Code:
#include <a_samp> main() {     new x = 184;     new y;     #emit ADDR.pri x  //Move o endereço da variável "x" para o registrador PRI.     #emit STOR.S.pri y  //Armazena o endereço em PRI na variável "y".     printf("Endereço da variável x: %d", y);  //Mostra o número 16472. }




* LCTRL



Esse é sem dúvidas é um dos mais importantes mnemônicos de todos .




* Seguido de um número



Ele envia ao registrador PRI (não é possível especificar o ALT) o valor que está em um dos outros registradores especiais da máquina abstrata. 



Isso dependerá do número que é parâmetro:

Code:
0  -  Registrador COD.
1  -  Registrador DAT.
2  -  Registrador HEA.
3  -  Registrador STP.
4  -  Registrador STK.
5  -  Registrador FRM.
6  -  Registrador CIP.


Exemplo:

pawn Code:
#include <a_samp> main() {     new x;     #emit LCTRL 3  //Move ao registrador PRI o valor no registrador STP.     #emit STOR.S.pri x  //Armazena o valor em PRI na variável "x".     printf("Endereço do início da stack: %d", x);  //Mostra o número 16508. }



* Seguido de um nome



Ainda não descoberto.





* SCTRL



Mnemônico capaz de modificar o valor de certos registradores especiais de acordo com o que está no registrador PRI.




* Seguido de um número



O número parâmetro determina o registrador a ser alterado:

Code:
2  -  Registrador HEA.
4  -  Registrador STK.
5  -  Registrador FRM.
6  -  Registrador CIP.


Exemplo:

pawn Code:
#include <a_samp> main() {     #emit LCTRL 6  //Move ao registrador PRI o valor no registrador CIP.     #emit ADD.C 52  //Aumenta em 52 o valor de PRI.     #emit SCTRL 6  //Iguala o registrador CIP ao registrador PRI.     print("1");     print("2"); }

Graças á mudança no registrador CIP, a primeira mensagem seria "pulada", fazendo com que apenas a segunda fosse mostrada.

* Seguido de um nome

Ainda não descoberto.


* XCHG

Troca o valor do registrador PRI pelo do registrador ALT e vice-versa :

pawn Code:
#include <a_samp> main() {     #emit CONST.pri 50  //Move o número 50 para o registrador PRI.     #emit CONST.alt 100  //Move o número 100 para o registrador PRI.     #emit XCHG  //Troca os valores dos registradores. PRI depois disso contém 100 e ALT contém 50. }
* PUSH.C

Cria uma cell e a coloca na stack da máquina abstrata.

* Seguido de um número

Avalia-o como o valor que será armazenado pela cell criada.

Exemplo:

pawn Code:
#include <a_samp> main() {     #emit PUSH.C 5  //Cria uma cell e armazena na mesma o número 5.     #emit STACK 4  //Destrói a cell criada anteriormente. }

* Seguido de um nome
Avalia-o como o nome da variável cujo endereço relativo à função na qual #emit for usado será armazenado pela cell criada:

pawn Code:
#include <a_samp> main() {     new x = 5;     new y = 7;     #emit PUSH.C x  //Cria uma variável armazenando nela o valor -4 e a coloca na stack.     #emit PUSH.C y  //Cria uma variável armazenando nela valor -8 e a coloca na stack.     #emit STACK 8  //Remove as cells criadas com #emit. }
* PUSH.S

Também cria uma cell e a coloca na stack da máquina abstrata.

* Seguido de um número
Ainda não descoberto.

* Seguido de um nome
Considera-o como o nome da variável da qual o valor será armazenado na cell criada.

Exemplo:

pawn Code:
#include <a_samp> main() {     new x = 5;     new y = 7;     #emit PUSH.S x  //Cria uma cell armazenando nela o número 5 e a coloca na stack.     #emit PUSH.S y  //Cria uma variável armazenando nela o número 7 e a coloca na stack.     #emit STACK 8  //Remove as cells criadas com #emit. }
* PUSH.ADR

Outro mnemônico para a criação de cells na stack.


* Seguido de um número
Ainda não descoberto.

* Seguido de um nome
Cria uma cell ponteiro armazenando o endereço da variável que tem o nome parâmetro:

pawn Code:
#include <a_samp> main() {     new a[] = "String";     #emit PUSH.ADR a  //Cria uma variável armazenando o endereço da variável "a" e a coloca na stack.     #emit STACK 4  //Remove a variável criada anteriormente com #emit. }

* STACK
Remove bytes da stack da máquina abstrata. Deve-se usá-lo em alguns casos para retirar dados criados por #emit que acabam não sendo destruídos automaticamente.

* Seguido de um número
Trata-o como o total de bytes a serem removidos através da operação pop da stack:

pawn Code:
#include <a_samp> main() {     #emit PUSH.C 1  //Cria uma variável na stack contendo o número 1.     #emit PUSH.C 2  //Cria uma variável na stack contendo o número 2.     #emit PUSH.C 3  //Cria uma variável na stack contendo o número 3.     #emit STACK 12  //Remove 12 bytes da stack, ou seja, destrói as últimas 3 cells que entraram nela. }
* Seguido de um nome
Ainda não descoberto.
Fora esses há mnemônicos especializados em cálculos matemáticos, formação de estruturas condicionais, entre outros.
A grande maioria desses, entretanto, pode ser substituída por outros códigos escritos em Pawn .
Uma lista completa deles pode ser encontrada no manual Pawn Implementer's Guide.


6 - Funções e o Amx.

Para chamar uma função nativa (obtida com native) por meio do #emit primeiramente deve-se ter certeza de que ela apareceu alguma vez antes no GM ou FS, caso contrário ela não será registrada na tabela de funções nativas do arquivo .amx e consequentemente não poderá ser utilizada.



Se a função nativa não tem parâmetros, basta colocar seu nome ao lado do mnemônico SYSREQ.C no #emit:

pawn Code:
public OnGameModeInit() {     #emit SYSREQ.C DisableInteriorEnterExits  //Chama a função nativa "DisableInteriorEnterExits".     return 1; }


Se tiver parâmetros, cria-se os argumentos da função a ser chamada e os coloca na stack na ordem inversa, ou seja, primeiro o último e por fim o primeiro.



Em seguida adiciona-se uma cell armazenando o número de argumentos colocados multiplicado por 4, que é o tamanho de uma cell em bytes, e por fim limpa-se a stack:

pawn Code:
public OnPlayerConnect(playerid) {     SendClientMessage(playerid, 0x33CCFFAA, "Texto 1");     new ID = playerid;     new Cor = 0x33FF00FF;     new Mensagem[] = "Texto 2";     #emit PUSH.ADR Mensagem  //Cria uma cell com o endereço da variável array "Mensagem" e a coloca na stack. A razão disso é o fato de variáveis arrays sempre serem passadas por referência em funções.     #emit PUSH.S Cor  //Cria uma cell com o mesmo valor da variável Cor e a coloca na stack.     #emit PUSH.S ID  //Cria uma cell com o mesmo valor da variável ID e a coloca na stack.     #emit PUSH.C 12  //Tendo passado todos os argumentos na ordem inversa, cria-se mais uma cell contendo o total deles multiplicado por 4.     #emit SYSREQ.C SendClientMessage  //Chama a função nativa "SendClientMessage".     #emit STACK 16  //Nesse caso temos que remover manualmente os dados colocados por #emit. Como foram criadas 4 cells com essa diretiva, ou seja, 16 bytes, removemos 16 bytes da stack.     SendClientMessage(playerid, 0xFF0000FF, "Texto 3");     return 1; }


Quanto às funções não-nativas, é um pouco mais complicado executá-las devido à necessidade de manipulação do registrador CIP. A definição das mesmas deve ficar acima dos locais nos quais elas são chamadas.



A técnica consiste em desviar a orientação do processamento para dentro da definição da função e depois para o local de onde esta foi chamada. Para isso, deve-se considerar mnemônicos e seus parâmetros tendo 4 bytes cada .



Na ausência de parâmetros na função, faz-se o seguinte:

pawn Code:
#include <a_samp> stock Mensagem() {     printf("Mensagem");     return 1; } main() {     print("Início");     #emit LCTRL 6  //Envia o valor do registrador CIP para o registrador PRI.     #emit ADD.C 36  //Aumenta em 36 o valor em PRI.     ADD.C = 4 bytes.   36 = 4 bytes.     #emit PUSH.C 0  //Cria uma cell com o número de argumentos da função multiplicado por 4.     PUSH.C = 4 bytes.   0 = 4 bytes.     #emit PUSH.pri  //Cria uma cell com o valor em PRI.    PUSH.pri = 4 bytes.     #emit CONST.pri Mensagem  //Pega o endereço da função "Mensagem".    CONST.pri = 4 bytes.  Mensagem = 4 bytes.     #emit SCTRL 6  //Iguala o registrador CIP ao registrador PRI. Isso desvia a orientação do processamento para o interior da função "Mensagem" através de seu endereço.    SCTRL = 4 bytes.  6 = 4 bytes.     print("Fim"); }


Aquela segunda cell que é criada no meio do código acima contém o endereço de retorno (lembra dele ?) da função chamada, que por sua vez é igual a: (valor que estava em CIP + número de bytes de código da linha do ADD.C até a linha do SCTRL).



O fato de ser até a parte do SCTRL pode ser explicado por ele ser o responsável pelo desvio para a função.



Graças ao endereço de retorno, a execução de códigos, após a leitura da função, continua a partir do local em que esta foi chamada.



Funções não-nativas com parâmetros são uma mistura das não-nativas sem parâmetros e das nativas com parâmetros:

pawn Code:
#include <a_samp> stock Numeros(x, y) {     printf("Números %d e %d", x, y);     return 1; } main() {     print("Início");     #emit LCTRL 6  //Envia o valor do registrador CIP para o registrador PRI.     #emit ADD.C 52  //Aumenta em 52 o valor em PRI.     ADD.C = 4 bytes.   52 = 4 bytes.     #emit PUSH.C 90  //Cria uma cell para o segundo argumento da função "Numeros".   PUSH.C = 4 bytes.  90 = 4 bytes.     #emit PUSH.C 45  //Cria uma cell para o primeiro argumento da função "Numeros".   PUSH.C = 4 bytes.  45 = 4 bytes.     #emit PUSH.C 8  //Cria uma cell para o número de argumentos multiplicado por 4.  PUSH.C = 4 bytes.  8 = 4 bytes.     #emit PUSH.pri  //Cria uma cell com o valor em PRI.    PUSH.pri = 4 bytes.     #emit CONST.pri Numeros  //Pega o endereço da função "Numeros".    CONST.pri = 4 bytes.  Numeros = 4 bytes.     #emit SCTRL 6  //Iguala o registrador CIP ao registrador PRI. Isso desvia a orientação do processamento para o interior da função "Numeros" através de seu endereço.    SCTRL = 4 bytes.  6 = 4 bytes.     print("Fim"); }
E este é o fim do tutorial.

0 comentários:

Postar um comentário