Informática Numaboa - Tutoriais e Programação

Janelas (masm)

Dom

7

Jan

2007


10:34

  • Imprimir
(12 votos, média 4.83 de 5) 


Nível Avançado No tutorial anterior aprendemos como pôr uma janelinha na tela. Só que era uma janela do tipo message box, que não permite adicionar certas funcionalidades como um menu por exemplo. Neste tutorial vamos aprender a criar uma janela "de verdade". Além disso, vamos explorar a "mecânica" da coisa.

Projeto

Como sempre, nosso primeiro passo é planejar o programa. O projeto até que é pequeno, mas, em compensação... o tutorial é um mastodonte!

  1. O sistema operacional é o Windows de 32 bits.
  2. Não precisamos mais do que o conjunto de instruções do processador 386.
  3. Vamos criar apenas uma janela "nua", com um mínimo de funcionalidade (apenas pode ser fechada).

Vamos por partes que a coisa há de ficar clara. Um dos aspectos mais importantes é entender o sistema de comunicação do Windows. Antes de começar pra valer este tutorial que deve ficar um pouco longo, abra o QEditor do MASM32 e digite (ou copie e cole) o esqueleto de um programa que possa ser finalizado com ExitProcess:

.386
.MODEL FLAT,STDCALL
option casemap:none

include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib

.CODE
inicio:
invoke ExitProcess,0
end inicio

A teoria das janelas

A Interface Gráfica dos programas Windows, conhecida como GUI (Graphical User Interface), depende essencialmente de funções da API. O uso desta interface padrão beneficia usuários e programadores. Para os usuários, as GUIs dos programas Windows são todas parecidas facilitando a navegação. Para os programadores, os códigos da GUI estão testados e prontos para uso. A desvantagem para os programadores é a complexidade crescente envolvida. Para criar ou manipular qualquer objeto GUI, como janelas, menus ou ícones, os programadores precisam seguir regras rígidas definidas pelo sistema Windows.

Logo abaixo estão os passos necessários para se criar uma janela no desktop:

  1. Obter um manipulador de instância - HANDLE (obrigatório).
  2. Pegar as instruções da linha de comando (dispensável, a não ser que o programa necessite de parâmetros iniciais fornecidos pelo usuário).
  3. Criar e Registrar a classe da janela (requerido, a não ser que se use tipos de janelas predefinidos, por exemplo MessageBox ou uma Dialog Box).
  4. Criar a janela (Obrigatório).
  5. Mostrar a janela no desktop.
  6. Atualizar a área cliente da janela.
  7. Entrar num loop infinito checando as mensagens do Windows.
  8. Processar as mensagens da janela - o gerente de mensagens.
  9. Encerrar o programa se o usuário fechar a janela.

Handle

Como usuário do Windows você sabe que pode rodar várias instâncias de um mesmo programa. Se você abrir a calculadora do Windows duas vezes, obterá duas janelas distintas, cada uma delas rodando uma instância do programa. Se você fizer cálculos na primeira janela, a segunda não é afetada. Portanto, cada uma das instâncias, aos olhos do sistema, é um aplicativo independente. Como é que o Windows consegue individualizar cada uma das instâncias? Através de um identificador ou manipulador de instância. Este manipulador é apenas um número que identifica a instância para o sistema. Por exemplo, "a janela da instância 1 precisa ser atualizada", "a janela da instância 2 foi ativada", "a janela da instância 2 foi minimizada", etc, permite que o sistema efetue as tarefas nas janelas corretas. Se não existisse o número de identificação, bem... a bagunça seria inevitável. E se duas instâncias possuírem o mesmo número... o Windows vai dar pau.

Existe uma função da API que nos permite obter um manipulador de instância que não conflite com outros já existentes (as duas janelas que abrimos para a calculadora não são as únicas que estão abertas). Esta função é a GetModuleHandle, que faz parte da kernel32.lib.

info Aproveite e familiarize-se um pouco mais com o MASM32: clique no item de menu [Tools / API Library List] para ativar uma ferramenta muito útil que nosso amigo hutch colocou à nossa disposição - a "API to Library list". Esta janelinha é o mapa da mina que relaciona uma grande quantidade de funções e as bibliotecas correspondentes. Digite "getmoduleh" e você já estará na linha que corresponde à função procurada: GetModuleHandle lib ==> kernel32.lib

Nós (e o sistema) vamos precisar do manipulador de instância quando quisermos atualizar ou alterar alguma coisa na(s) janela(s) do programa. É um número que será usado numa porção de funções diferentes, portanto, precisamos preparar um lugar onde vamos guardar este identificador e que possa ser acessado de qualquer ponto do programa . Um endereço de memória que guarda um valor e que pode ser acessado de qualquer ponto do programa é chamado de VARIÁVEL GLOBAL. Ao invés de lembrar do endereço, podemos dar um nome a ele. Por exemplo, você pode ir para a casa do "Zé" ou para a "rua dos bits, número 135" que dá na mesma, pois o Zé mora neste endereço.

Já que precisamos deixar a critério do sistema o número que será designado como manipulador de instância para o programa, o valor da variável que conterá o manipulador da instância só pode ser obtido em tempo de execução, portanto, não podemos (e não devemos!) inicializar esta variável. A seção para variáveis não inicializadas, conforme já foi visto no tutorial "Por onde começar", é a seção .DATA?. Nesta seção damos nomes às variáveis, indicamos seu tipo e informamos que não são inicializadas através de um ponto de interrogação (?).

A função GetModuleHandle

Qual é o tipo da variável que precisamos declarar? A referência da API nos diz que a função GetModuleHandle retorna um manipulador de módulo (módulo é igual a instância no win32) para o módulo especificado se o arquivo tiver sido mapeado no espaço de endereços do processo chamador e que o tipo do valor de retorno é HMODULE - apenas um dos muitos nomes que o Windows dá a DWORD.
HMODULE GetModuleHandle(
LPCTSTR lpModuleName // endereço do nome do módulo para o qual se solicita um manipulador
);
  • Parâmetro lpModuleName: Aponta para uma string terminada em zero com o nome de um módulo Win32 (uma .DLL ou arquivo .EXE). Se a extensão do nome do arquivo for omitida, a extensão default é .DLL. A string do nome do arquivo por ter um caracter finalizador ponto (.) para indicar que o nome do módulo não possui extensão. A string não precisa especificar um caminho (path). O nome é comparado (sem considerar maiúsculas e minúsculas) aos nomes dos módulos que já estejam mapeados no espaço de endereços do processo chamador.
    Se o parâmetro for NULL, GetModuleHandle retorna um manipulador do arquivo usado para criar o processo da chamada.

Então vamos digitar um pouco. Certifique-se de que os arquivos kernel32.inc e kernel32.lib constam na lista de include e includelib. Uma vez familiarizados com a função, podemos chamá-la com o parâmetro NULL. É tudo o que queremos: um manipulador de instância para o nosso programa.

.386
.MODEL FLAT,STDCALL
option casemap:none

include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib

.DATA?
mInstancia DWORD ?


.CODE
inicio:
invoke GetModuleHandle, NULL
mov mInstancia, eax

invoke ExitProcess,0
end inicio

A diretiva invoke você já conhece do tutorial "O Folgado" e a função GetModuleHandle já foi mais do que explicada. A linha seguinte é que são elas: usa o mnemônico MOV com os operandos mInstancia e eax. Vamos por partes.

info Um mnemônico é um nome reservado de uma família de códigos operacionais que realizam tarefas semelhantes no processador. MOV pede ao processador para MOVer ou copiar um valor de um local para outro. Dê uma olhada no texto acessório "Códigos Operacionais" para entender melhor.

EAX é um registrador de uso geral, ou seja, é um tipo especial de memória DENTRO do processador e que serve para o armazenamento temporário de dados (a informação pode ser colocada num determinado instante e de lá ser retirada quando isso se fizer necessário). Existem vários registradores dentro do processador - para maiores detalhes leia o texto acessório "Registradores".

Em todo caso, esta nova linha pode ser traduzida da seguinte maneira: MOVa o valor que se encontra no registrador EAX para a posição de memória referente à nossa variável mInstancia. Quando esta linha for executada, a variável global mInstancia é inicializada.

O motivo pelo qual transferimos o valor de EAX para mInstancia é que o valor de retorno das funções da API são armazenados no registrador EAX. Neste caso, assim que se volta da função GetModuleHandle, o registrador EAX contém o valor do manipulador de instância solicitado.


Instruções da linha de comando

A linha de comando geralmente se resume no nome do programa que queremos executar, ou seja, não contém parâmetros adicionais. Existem alguns raros casos em que é necessário enviar um ou alguns parâmetros para que o programa funcione corretamente ou de forma personalizada. Somente nestes raros casos é que precisamos usar a função da API GetCommandLine, também da kernel32.lib. Apenas a título de ilustração vamos inserir esta chamada.

.386
.MODEL FLAT,STDCALL
option casemap:none

include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib

.DATA?
mInstancia DWORD ?
linhaComando DWORD ?

.CODE
inicio:
invoke GetModuleHandle, NULL
mov mInstancia, eax
invoke GetCommandLine
mov linhaComando, eax

invoke ExitProcess,0
end inicio

A classe da janela

O Windows só consegue criar objetos a partir de um modelo - é como se o sistema precisasse da "planta da casa" para que poder construir a "casa". Primeiro é preciso criar a "planta" e depois entregá-la (fazer o registro) para que o Windows possa usá-la como modelo.

Normalmente a função padrão utilizada para esta dupla tarefa (criar e registrar) é a WinMain, que é chamada pelo sistema como ponto de entrada para um aplicativo win32. A referência da API nos mostra o seguinte:

int WINAPI WinMain(
HINSTANCE hInstance, // manipulador da instância
HINSTANCE hPrevInstance, // manipulador da instância anterior
LPSTR lpCmdLine, // ponteiro para a linha de comando
int nCmdShow // modo de apresentação da janela
);

Para poder utilizar esta função precisamos criar o protótipo da mesma. Se você esqueceu o que é um protótipo de função, refresque a memória relendo "O Folgado". Para criar o protótipo é necessário conhecer os tipos dos parâmetros que a função espera receber: HINSTANCE, LPSTR e int. Na verdade, todos eles são nomes diferentes que o Windows dá ao DWORD. Esta função recebe quatro parâmetros: o manipulador da instância do nosso programa, o manipulador de instância da instância anterior do nosso programa, a linha de comando e o estado da janela da primeira vez em que aparecer. No win32, NÃO existe uma instância anterior. Cada programa está sozinho no seu espaço de endereços, de modo que o valor de hPrevInstance será sempre 0 (NULL). Isto é uma sobra da época do win16 quando todas as instâncias de diversos programas rodavam no mesmo espaço de endereços e uma instância queria saber se era a primeira. No win16, se hPrevInstance for NULL, então esta instância é a primeira. O nome da função também pode ser um da nossa escolha, portanto, nosso protótipo pode ser

    gerenteJanela proto :DWORD, :DWORD, :DWORD, :DWORD

O manipulador da instância do nosso programa já está armazenado em mInstancia, o manipulador da instância anterior é NULL, o ponteiro para a linha de comando já está em linhaComando e o modo de exibição pode ser o padrão (SW_SHOWDEFAULT). Nosso código fonte passa a ser o seguinte:

.386
.MODEL FLAT,STDCALL
option casemap:none

include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib

gerenteJanela proto :DWORD, :DWORD, :DWORD, :DWORD

.DATA?
mInstancia DWORD ?
linhaComando DWORD ?

.CODE
inicio:
invoke GetModuleHandle, NULL
mov mInstancia, eax
invoke GetCommandLine
mov linhaComando, eax
invoke gerenteJanela, mInstancia, NULL, linhaComando, SW_SHOWDEFAULT
invoke ExitProcess,0
end inicio

gerenteJanela proc mInst:DWORD, mInstAnt:DWORD, linhaCmd:DWORD, Mostra:DWORD
...
gerenteJanela endp

A função gerenteJanela é um procedimento que separamos do corpo principal do código (que fica entre o par de rótulos "inicio:" e "end inicio"). Fiz isto apenas para destacar e individualizar este procedimento do resto do código. Se você quiser, não precisa criar o protótipo da função, não precisa fazer o invoke e nem criar o procedimento gerenteJanela. Pode simplesmente colocar todo o código que vem a seguir no corpo principal.

Além disso, se você suprimiu o código correspondente à linha de comando, basta enviar um parâmetro NULL no lugar de linhaComando.

A estrutura WNDCLASSEX

A "planta" da classe da nossa janela é "desenhada" numa estrutura. Uma estrutura agrupa dados de tal forma que possam ser endereçados num bloco único (Leia mais sobre estruturas em "Trabalhando com Estruturas"). Usaremos uma estrutura predefinida no Windows, chamada WNDCLASSEX.

A estrutura WNDCLASSEX foi planejada para conter todas as informações de uma classe janela e a referência da API nos mostra o seguinte:

typedef struct _WNDCLASSEX {
UINT cbSize;
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HANDLE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
HICON hIconSm;
} WNDCLASSEX;

A seguir, a explicação para cada um dos membros desta estrutura:

  • cbSize: O tamanho da estrutura WNDCLASSEX em bytes. Podemos usar o operador SIZEOF para obter este valor.
  • style: O estilo da janela criada a partir desta classe. Você pode combinar diversos estilos usando o operador "or".
  • lpfnWndProc: O endereço do procedimento da janela (window procedure) responsável pelas janelas criadas a partir desta classe.
  • cbClsExtra: Especifica o número de bytes extras que podem ser alocados logo após a estrutura. O sistema operacional inicializa os bytes com zero. Aqui você pode armazenar dados específicos da classe.
  • cbWndExtra: Especifica o número de bytes extras que podem ser alocados logo após a instância da janela. O sistema operacional inicializa estes bytes com zero. Se o aplicativo usar a estrutura WNDCLASS para registrar uma caixa de diálogo usando a diretiva CLASS do arquivo de recursos, ele precisa indicar este membro como DLGWINDOWEXTRA.
  • hInstance: O manipulador da instância do módulo (programa).
  • hIcon: O manipulador do ícone. Obtenha-o através da chamada de LoadIcon.
  • hCursor: O manipulador do cursor. Obtenha-o através da chamada de LoadCursor.
  • hbrBackground: A cor de fundo das janelas criadas a partir desta classe.
  • lpszMenuName: O manipulador de menu default para janelas criadas a partir desta classe.
  • lpszClassName: O nome desta classe.
  • hIconSm: O manipulador do ícone pequeno associado a esta classe. Se este membro for NULL, o sistema procura nos recursos de ícones especificado pelo membro hIcon por um ícone de tamanho apropriado que possa ser usado.

Declaração

Como só vamos precisar desta estrutura no procedimento da função gerenteJanela, vamos declará-la como variável LOCAL com o nome de ej (de estrutura janela - ou qualquer outro da sua escolha). A diretiva LOCAL aloca memória da pilha para esta variável e precisa estar situada imediatamente após a diretiva PROC. A sintaxe é LOCAL :. Vamos usar LOCAL ej:WNDCLASSEX, que pede ao MASM para alocar uma quantidade de memória de pilha correspondente ao tamanho da estrutura WNDCLASSEX para a variável de nome ej. A vantagem é que podemos referenciar ej no nosso código sem nos preocuparmos com o realinhamento da pilha, o que é mordomia pura. Uma desvantagem é que variáveis locais não podem ser usadas fora das funções onde foram criadas e que serão imediatamente destruídas quando se retorna ao chamador. Outra desvantagem é que variáveis locais não podem ser inicializadas automaticamente porque elas são apenas memória de pilha alocada dinamicamente na entrada da função. Precisamos atribuir seus valores manualmente após a diretiva LOCAL.

Entre os membros da estrutura encontramos o que deve conter o ponteiro para o nome da classe que desejamos criar. Para preencher este requisito, precisamos inicializar uma variável na seção .DATA que contenha o nome da classe.

.386
.MODEL FLAT,STDCALL
option casemap:none

include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib

gerenteJanela proto :DWORD, :DWORD, :DWORD, :DWORD

.DATA
NomeClasse db "JanelaNua",0


.DATA?
mInstancia DWORD ?
linhaComando DWORD ?

.CODE
inicio:
invoke GetModuleHandle, NULL
mov mInstancia, eax
invoke GetCommandLine
mov linhaComando, eax
invoke gerenteJanela, mInstancia, NULL, linhaComando, SW_SHOWDEFAULT
invoke ExitProcess,0
end inicio

gerenteJanela proc mInst:DWORD, mInstAnt:DWORD, linhaCmd:DWORD, Mostra:DWORD
LOCAL ej:WNDCLASSEX
gerenteJanela endp

A função

A partir deste ponto listarei apenas a porção da função gerenteJanela. O primeiro membro da estrutura é o cbSize, que deve conter o tamanho da estrutura. Podemos obter este valor usando o operador SIZEOF. Como estilo da janela usaremos CS_HREDRAW OR CS_VREDRAW. O terceiro membro, lpfnWndProc, é o mais importante de todos. O significado de lpfn é "long pointer to function", ou seja, ponteiro longo para função. No win32 não existem ponteiros "near" (perto) ou "far" (distante); devido ao modelo de memória FLAT, existem apenas ponteiros. Mas isto, novamente, é sucata da época do win16. Cada classe janela precisa estar associada a uma função que gerencie o comportamento das janelas criadas a partir desta classe. Esta função, que chamaremos de gerenteMensagem, é tão importante que será discutida em separado. Por enquanto vamos atribuir valores aos primeiros membros da estrutura usando o mnemônico MOV:

gerenteJanela proc mInst:DWORD, mInstAnt:DWORD, linhaCmd:DWORD, Mostra:DWORD
LOCAL ej:WNDCLASSEX
mov ej.cbSize, SIZEOF WNDCLASSEX
mov ej.style, CS_HREDRAW or CS_VREDRAW
mov ej.lpfnWndProc, OFFSET gerenteMensagem
mov ej.cbClsExtra, NULL
mov ej.cbWndExtra, NULL

gerenteJanela endp

O ej.hInstance, o próximo membro da estrutura, deve conter o manipulador da instância do programa. Podemos usar a variável global mInstancia ou o parâmetro mInst recebido pela função gerenteJanela pois ambos apontam para o mesmo endereço, ou seja, contém o mesmo valor. Como não é possível transferir diretamente o valor de uma posição de memória para outra posição de memória e tanto ej.hInstance quanto mInst são posições de memória, será preciso usar o auxílio de um registrador. O mais fácil é utilizar o registrador da pilha: usar o mnemônico push para colocar o valor na pilha e o mnemônico pop para transferí-lo da pilha para ej.hInstance.

gerenteJanela proc mInst:DWORD, mInstAnt:DWORD, linhaCmd:DWORD, Mostra:DWORD
LOCAL ej:WNDCLASSEX
mov ej.cbSize, SIZEOF WNDCLASSEX
mov ej.style, CS_HREDRAW or CS_VREDRAW
mov ej.lpfnWndProc, OFFSET gerenteMensagem
mov ej.cbClsExtra, NULL
mov ej.cbWndExtra, NULL
push mInst
pop ej.hInstance

gerenteJanela endp

Ícone e Cursor

Para obter o manipulador do ícone e do cursor basta fazer uma chamada para LoadIcon e LoadCursor. A função LoadIcon carrega o recurso do ícone especificado a partir do executável associado a uma instância do aplicativo.

HICON LoadIcon(
HINSTANCE hInstance,
LPCTSTR lpIconName // string com o nome do ícone ou identificador do recurso do ícone
);

A função LoadCursor funciona como a anterior, apenas direcionada para o cursor.

HCURSOR LoadCursor(
HINSTANCE hInstance,
LPCTSTR lpCursorName // string com o nome do cursor ou identificador do recurso do cursor
);

Nas duas funções, HINSTANCE identifica uma instância do módulo cujo arquivo executável contém o ícone ou cursor que deve ser carregado. Como ainda não programamos os recursos do nosso aplicativo (vamos ver isto em tutoriais posteriores), nosso executável está "vazio" de recursos e HINSTANCE pode ser NULL. Neste caso serão usados os recursos do Windows e podemos usar os parâmetros default (veja mais detalhes na referência da API). Lembre-se de que o valor de retorno destas funções vai sempre para o registrador EAX.

Inicialização

A ordem de inicialização dos membros da estrutura WNDCLASSEX não é importante. Como estamos trabalhando o ícone do programa, aproveitaremos a chamada a LoadIcon e inicializaremos hIcon e hIconSm numa tacada.

gerenteJanela proc mInst:DWORD, mInstAnt:DWORD, linhaCmd:DWORD, Mostra:DWORD
LOCAL ej:WNDCLASSEX
mov ej.cbSize, SIZEOF WNDCLASSEX
mov ej.style, CS_HREDRAW or CS_VREDRAW
mov ej.lpfnWndProc, OFFSET gerenteMensagem
mov ej.cbClsExtra, NULL
mov ej.cbWndExtra, NULL
push mInst
pop ej.hInstance
invoke LoadIcon, NULL, IDI_WINLOGO
mov ej.hIcon, eax
mov ej.hIconSm, eax
invoke LoadCursor, NULL, IDC_ARROW
mov ej.hCursor, eax

gerenteJanela endp

A referência da API para WNDCLASSEX nos diz que, se usarmos uma cor para o membro hbrBackground, o valor da cor precisa ser um dos valores das cores padrão do sistema, acrescido de 1. Escolhemos a cor padrão COLOR_WINDOW, portanto usaremos COLOR_WINDOW+1.

Como não projetamos os recursos, também não temos um menu para o nosso aplicativo - a string com o nome do menu, por enquanto, será NULL.

E, finalmente, o nome da nossa classe de janela já foi definida na seção .DATA e o ponteiro para a string que contém o nome é OFFSET NomeClasse.

gerenteJanela proc mInst:DWORD, mInstAnt:DWORD, linhaCmd:DWORD, Mostra:DWORD
LOCAL ej:WNDCLASSEX
mov ej.cbSize, SIZEOF WNDCLASSEX
mov ej.style, CS_HREDRAW or CS_VREDRAW
mov ej.lpfnWndProc, OFFSET gerenteMensagem
mov ej.cbClsExtra, NULL
mov ej.cbWndExtra, NULL
push mInst
pop ej.hInstance
invoke LoadIcon, NULL, IDI_WINLOGO
mov ej.hIcon, eax
mov ej.hIconSm, eax
invoke LoadCursor, NULL, IDC_ARROW
mov ej.hCursor, eax
mov ej.hbrBackground, COLOR_WINDOW+1
mov ej.lpszMenuName, NULL
mov ej.lpszClassName, OFFSET NomeClasse

gerenteJanela endp

Foi extenso, porém não foi complicado. Nossa estrutura WNDCLASSEX está com todos os valores inicializados, ou seja, nossa classe de janela está definida. Mantendo a comparação inicial, a "planta da casa" está pronta. Agora podemos registrá-la.

Esta função é responsável pelo gerenciamento das mensagens provenientes de todas as janelas criadas a partir da classe associada. O Windows enviará mensagens à função para notificá-la de eventos importantes (entradas de teclado, cliques do mouse, etc) relativos às janelas pelas quais é responsável e a função deve responder adequadamente cada mensagem recebida.


Registro da classe

Após criar uma classe (a "planta de uma casa"), é obrigatório registrá-la para que o sistema permita usá-la como modelo para criar uma ou mais instâncias de objetos ("uma ou mais casas") baseados nesta classe. Como utilizamos a estrutura WNDCLASSEX para definir as caracteríticas da nossa classe de janela, para registrá-la precisamos usar a função que "faz par" com ela: a RegisterClassEx. Caso tivéssemos utilizado WNDCLASS, a função para registro seria RegisterClass.

ATOM RegisterClassEx(
CONST WNDCLASSEX *lpwcx // ponteiro para a estrutura com os dados da classe
);

Tranquilo. Temos a estrutura pronta e a função pede um ponteiro. Usando invoke podemos usar o operador ADDR para fornecê-lo. Acontece que esta função faz parte da user32.lib e esta biblioteca, assim como o arquivo include correspondente, precisa ser incluída:

...
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
...
gerenteJanela proc mInst:DWORD, mInstAnt:DWORD, linhaCmd:DWORD, Mostra:DWORD
LOCAL ej:WNDCLASSEX
mov ej.cbSize, SIZEOF WNDCLASSEX
mov ej.style, CS_HREDRAW or CS_VREDRAW
mov ej.lpfnWndProc, OFFSET gerenteMensagem
mov ej.cbClsExtra, NULL
mov ej.cbWndExtra, NULL
push mInst
pop ej.hInstance
invoke LoadIcon, NULL, IDI_WINLOGO
mov ej.hIcon, eax
mov ej.hIconSm, eax
invoke LoadCursor, NULL, IDC_ARROW
mov ej.hCursor, eax
mov ej.hbrBackground, COLOR_WINDOW+1
mov ej.lpszMenuName, NULL
mov ej.lpszClassName, OFFSET NomeClasse
invoke RegisterClassEx, ADDR ej
gerenteJanela endp

Criando a janela

Se a nossa classe foi aceita pelo sistema para registro, isto significa que possuímos um "alvará de construção". Podemos criar quantos objetos quisermos usando a classe registrada como modelo. Registra-se apenas uma vez, usa-se quantas vezes forem necessárias.

A função CreateWindowEx

A função para criar uma janela de acordo com a classe que registramos é do grupo Ex, ou seja, CreateWindowEx (WNDCLASSEX -> RegisterClassEx -> CreateWindowEx). Seria CreateWindow caso tivéssemos usado uma estrutura WNDCLASS.

HWND CreateWindowEx(
DWORD dwExStyle,
LPCTSTR lpClassName,
LPCTSTR lpWindowName,
DWORD dwStyle,
int x,
int y,
int nWidth,
int nHeight,
HWND hWndParent,
HMENU hMenu,
HINSTANCE hInstance,
LPVOID lpParam
);

Esta função pede um caminhão de parâmetros (12 ao todo, se você tiver o trabalho de contar). Então, vamos lá:

  • dwExStyle: Estilo extra da janela. Este é um parâmetro novo, adicionado ao CreateWindow antigo. Aqui podemos colocar os novos estilos das janelas do Windows 9x, NT e XP. Você pode especificar o estilo de janela comum em dwStyle porém, se você desejar estilos especiais, como janelas sempre no topo, você precisa especificá-los aqui. Usa-se NULL caso estilos extras não sejam requeridos.
  • lpClassName: (Requerido) Endereço da string ASCIIZ (string terminada em zero) que contém o nome da classe que serve de modelo para esta janela. A classe pode ser uma que você tenha criado e registrado ou uma classe predefinida do Windows.
  • lpWindowName: Endereço da string ASCIIZ que contém o nome da janela. O nome mostrado na barra de título da janela. Se este parâmetro for NULL, a barra de título ficará em branco.
  • dwStyle: Estilos da janela. Aqui você pode especificar a aparência da janela. Passar NULL também é aceito mas, neste caso, a janela não terá uma caixa de menu do sistema, botões minimizar-maximizar e botão fechar (a janela não teria muito uso e você teria que usar Alt+F4 para fechá-la). O estilo mais comum é o WS_OVERLAPPEDWINDOW. Um estilo de janela é apenas um bit de flag, portanto, você pode combinar diversos estilos com o operador "or" para obter a aparência desejada. O estilo WS_OVERLAPPEDWINDOW nada mais é do que a combinação dos estilos mais comuns obtido através deste método.
  • X, Y: As coordenadas do canto superior esquerdo da janela. Normalmente, estes valores deveriam ser CW_USERDEFAULT, ou seja, você deixa o Windows decidir onde colocar a janela no desktop.
  • nWidth, nHeight: A largura e a altura da janela em pixels. Aqui você também pode usar CW_USERDEFAULT e deixar o Windows escolher a largura e a altura apropriadas.
  • hWndParent: O manipulador da janela-mãe (se existir). Este parâmetro indica ao Windows se esta janela é uma janela-filha (subordinada) ou algum outro tipo de janela e, se for, qual é a janela-mãe. Este relacionamento é apenas para uso interno do Windows. Se a janela-mãe é destruída, todas as janelas-filhas serão automaticamente destruídas. É realmente simples. Como no nosso exemplo há apenas uma janela, nós especificamos este parâmetro como NULL.
  • hMenu: Um manipulador para o menu da janela. NULL se for para usar a classe menu já definida na classe da janela. Dê novamente uma olhada no membro lpszMenuName da estrutura WNDCLASSEX. Este membro especifica o menu *default* para a classe. Toda janela criada a partir desta classe terá o mesmo menu por default, a não ser que você especifique aqui em hMenu um menu que se sobreponha ao menu default - isto se chamda overriding. hMenu é na verdade um parâmetro com dois objetivos. Se a janela que está sendo criada é do tipo predefinido, como button (botão) ou edit box (caixa de edição), esta janela/controle não pode possuir um menu. Neste caso, o hMenu é usado como identificador (ID) do controle. O Windows distingue se hMenu é realmente um manipulador de menu ou um ID de controle olhando no parâmetro lpClassName. Se for o nome de uma classe predefinida, o hMenu é um ID de controle. Se não for, então é um manipulador do menu da janela.
  • hInstance: O manipulador da instância para o módulo do programa que cria a janela.
  • lpParam: Um ponteiro opcional para uma estrutura de dados passada para a janela. É usado por janelas MDI para passar os dados de CLIENTCREATESTRUCT. Normalmente este valor é NULL, significando que não há dados sendo passados via CreateWindow(). A janela pode obter o valor deste parâmetro através da chamada da função GetWindowLong.

Chamando a função

É explicação que não acaba mais e tudo isso só para chamar uma funçãozinha! Para todos os parâmetros já temos os valores, exceto para o título da janela. Este vocês já tiram de letra: basta inicializar uma variável na seção .DATA. Também vamos precisar do valor de retorno da função CreateWindowEx, que é o manipulador da instância da janela que acabamos de criar. Sem este manipulador, ou seja, sem o número identificador desta janela recém criada, não teremos como acessá-la. Como vamos precisar deste manipulador apenas no âmbito da função gerenteJanela, podemos declarar uma variável LOCAL para armazená-lo.

...
.DATA
NomeClasse db "JanelaNua",0
TituloJanela db "Janelinha NumaBoa",0
...

gerenteJanela proc mInst:DWORD, mInstAnt:DWORD, linhaCmd:DWORD, Mostra:DWORD
LOCAL ej:WNDCLASSEX
LOCAL mJanela:HWND

mov ej.cbSize, SIZEOF WNDCLASSEX
mov ej.style, CS_HREDRAW or CS_VREDRAW
mov ej.lpfnWndProc, OFFSET gerenteMensagem
mov ej.cbClsExtra, NULL
mov ej.cbWndExtra, NULL
push mInst
pop ej.hInstance
invoke LoadIcon, NULL, IDI_WINLOGO
mov ej.hIcon, eax
mov ej.hIconSm, eax
invoke LoadCursor, NULL, IDC_ARROW
mov ej.hCursor, eax
mov ej.hbrBackground, COLOR_WINDOW+1
mov ej.lpszMenuName, NULL
mov ej.lpszClassName, OFFSET NomeClasse
invoke RegisterClassEx, ADDR ej
invoke CreateWindowEx, NULL, ADDR NomeClasse, ADDR TituloJanela,
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, mInst, NULL
mov mJanela,eax

gerenteJanela endp

Você deve estar pensando "até que enfim a janela está na tela"... ledo engano. A janela foi criada (a "casa foi construída"), está prontinha para uso, só que ninguém contou ao sistema que é para abrí-la ao público.


Pilotando a janela

Planta da casa elaborada, planta registrada, casa construída... é hora de inaugurá-la.

Vamos mostrar nossa janela no desktop com a função ShowWindow

BOOL ShowWindow(
HWND hWnd, // manipulador da janela
int nCmdShow // modo de apresentação
);

adicionando-a ao nosso código:

gerenteJanela proc mInst:DWORD, mInstAnt:DWORD, linhaCmd:DWORD, Mostra:DWORD
LOCAL ej:WNDCLASSEX
LOCAL mJanela:HWND
...

invoke RegisterClassEx, ADDR ej
invoke CreateWindowEx, NULL, ADDR NomeClasse, ADDR TituloJanela,
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, mInst, NULL
mov mJanela,eax
invoke ShowWindow, mJanela, SW_SHOWNORMAL
gerenteJanela endp

Atualizando a janela

Pode-se chamar UpdateWindow para refazer a pintura da área cliente da nossa janela. Esta função é útil quando se quer atualizar o conteúdo da área cliente. Esta chamada pode ser omitida, sem problemas... mas sempre é bom garantir. E, já que estamos garantindo algumas coisas, também vamos providenciar o retorno da função gerenteJanela para a área do procedimento chamador. O mnemônico ret garante esta volta.

gerenteJanela proc mInst:DWORD, mInstAnt:DWORD, linhaCmd:DWORD, Mostra:DWORD
LOCAL ej:WNDCLASSEX
LOCAL mJanela:HWND
...

invoke RegisterClassEx, ADDR ej
invoke CreateWindowEx, NULL, ADDR NomeClasse, ADDR TituloJanela,
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, mInst, NULL
mov mJanela,eax
invoke ShowWindow, mJanela, SW_SHOWNORMAL
invoke UpdateWindow, mJanela
ret

gerenteJanela endp

Estrutura das mensagens

Se interrompermos nosso código neste ponto, teremos uma surpresa: executando o programa, nossa linda janelinha é mostrada no desktop uma fração de segundo e desaparece. Faça o teste: Clique no item de menu do QEditor [Project / Assemble & Link] para criar o executável e depois em [Project / Run Program].

O que falta é um trechinho de código cuja função é a de manter a janela no desktop e, o que é mais importante, fazer com que ela saiba o que acontece com ela.

Imagine o seguinte: quem controla todos os eventos que ocorrem com esta janela é o Windows. Aliás, o Windows tem uma espécie de central que controla todos os eventos referentes a todas as janelas. É como se fosse uma agência de correio onde cada instância de janela possui sua caixa postal. Quando o gerente da janela solicita sua correspondência, a agência lhe envia um malote com a mensagem recebida - uma atrás da outra e sempre a primeira da fila - inclusive com o número da mensagem e a hora! O gerente da janela confere o recebimento e envia a mensagem para o gerente de mensagens. O gerente de mensagens da janela pode responder a mensagem a seu modo (ao nosso modo wink) ou pode devolvê-la para o remetente (o Windows) e deixar que ele processe a mensagem de acordo com as regras do sistema (procedimento padrão). Observação: no sistema Windows, o gerente de mensagens é conhecido como window procedure.

Ainda bem que a "agência de correio" cuida da correspondência da nossa janela. Só precisamos contratar os gerentes e fornecer o malote. O malote traz diversas informações que podem ser agrupadas, portanto o ideal é usar uma estrutura. A estrutura utilizada como "malote" é a MSG:

typedef struct tagMSG { // msg
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG;

Os membros que constituem a estrutura MSG são:

  • hwnd: Identifica a janela cujo procedimento de janela (nosso gerenteMensagem) recebe a mensagem.
  • message: Especifica o número da mensagem.
  • wParam: Informação adicional sobre a mensagem. O sentido exato depende do valor do membro mensagem.
  • lParam: Informação adicional sobre a mensagem. O sentido exato depende do valor do membro mensagem.
  • time: A hora que a mensagem foi enviada.
  • pt: A posição do cursor, em coordenadas de janela, quando a mensagem foi enviada.

Pois bem, então vamos criar o "malote" da nossa janela:

gerenteJanela proc mInst:DWORD, mInstAnt:DWORD, linhaCmd:DWORD, Mostra:DWORD
LOCAL ej:WNDCLASSEX
LOCAL mJanela:HWND
LOCAL malote:MSG
...

invoke RegisterClassEx, ADDR ej
invoke CreateWindowEx, NULL, ADDR NomeClasse, ADDR TituloJanela,
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, mInst, NULL
mov mJanela,eax
invoke ShowWindow, mJanela, SW_SHOWNORMAL
invoke UpdateWindow, mJanela
ret
gerenteJanela endp

Solicitando e enviando mensagens

O gerente da janela solicita o envio de um "malote" com uma mensagem através da função GetMessage:

BOOL GetMessage(
LPMSG lpMsg, // endereço da estrutura com a mensagem
HWND hWnd, // manipulador da janela
UINT wMsgFilterMin, // primeira messagem
UINT wMsgFilterMax // última messagem
);

Existem algumas mensagens que chegam à "agência de correio" com características especiais. São como cartas registradas ou SEDEX: não podem ser entregues via caixa postal ou malote. Se usarmos o manipulador da janela (no exemplo, mJanela) como parâmetro hWnd da função GetMessage, apenas as "cartas normais" serão entregues. Se quisermos obter todas as mensagens, as "normais" E as "especiais", passamos este parâmetro como NULL.

Se não quisermos selecionar nossa correspondência (não queremos filtrar mensagens), passamos os parâmetros wMsgFilterMin e wMsgFilterMax como zero e a "agência" entregará TODAS as mensagens.

Se a função mandar um "malote" ao gerente de mensagens contendo a mensagem WM_QUIT, o valor de retorno será zero; caso contrário, o valor de retorno será diferente de zero. Se ocorrer um erro, o valor de retorno será -1 (por exemplo, quando o manipulador da janela não for válido).

Como já foi dito anteriormente, o Windows controla todos os eventos referentes a todas as janelas, inclusive os relativos à janela que acabamos de criar: a janela foi ativada, o cursor do mouse passou sobre ela, clique do mouse, tecla pressionada, janela minimizada, maximizada... tudo e mais alguma coisa gera uma mensagem. Dê uma olhada na referência da API do Windows e procure por WM_ (vem de Windows Message). Existe uma quantidade enorme de tipos de mensagens.

Caso a mensagem seja referente a uma tecla que foi pressionada, ela vem com um código de varredura do teclado. É muito chato trabalhar com estes códigos - mais fácil é trabalhar com o ASCII da tecla. Existe uma função que transforma uma mensagem WM_KEYDOWN (código de varredura) em uma mensagem WM_CHAR (código ASCII). É a função TranslateMessage:

BOOL TranslateMessage(
CONST MSG *lpMsg // endereço da estrutura com a mensagem (o "malote")
);

Para o gerente da janela mandar a mensagem ao gerente de mensagens, usamos a função DispatchMessage:

LONG DispatchMessage(
CONST MSG *lpmsg // endereço da estrutura com a mensagem (o "malote")
);

Loop infinito

Bem, então vamos ao trabalho. Queremos que nosso gerente de janela, depois de tudo que já fez, fique o tempo todo solicitando, conferindo e mandando suas mensagens para o gerente de mensagens. Para isto faremos uso de macros de pseudo alto nível do MASM: .WHILE, .BREAK e .IF

gerenteJanela proc mInst:DWORD, mInstAnt:DWORD, linhaCmd:DWORD, Mostra:DWORD
LOCAL ej:WNDCLASSEX
LOCAL mJanela:HWND
LOCAL malote:MSG
...

invoke RegisterClassEx, ADDR ej
invoke CreateWindowEx, NULL, ADDR NomeClasse, ADDR TituloJanela,
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, mInst, NULL
mov mJanela,eax
invoke ShowWindow, mJanela, SW_SHOWNORMAL
invoke UpdateWindow, mJanela
.WHILE TRUE
invoke GetMessage, ADDR malote, NULL, 0, 0
.BREAK .IF (eax < 1)
invoke TranslateMessage, ADDR malote
invoke DispatchMessage, ADDR malote
.ENDW

ret
gerenteJanela endp

Trocando em miúdos, este trecho de código diz o seguinte:

enquanto (.WHILE) verdadeiro (TRUE)
chame MandeMessagem, pelo malote, mensagens normais e especiais, sem filtro, sem filtro
páre (.BREAK) se (.IF) o valor de retorno for menor que 1
chame TraduzaMensagem, do malote
chame DespacheMensagem, do malote
fim do enquanto (.ENDW)

Este loop infinito só é interrompido quando o valor de retorno de GetMessage for 0 ou -1 (ou seja, menor que 1). Quando é 0, a mensagem recebida foi WM_QUIT e o programa deve ser encerrado. O valor -1 indica erro e o melhor é... também terminar ao invés de dar pau.

Saindo do loop, o gerente da janela ainda deve realizar uma tarefa antes de devolver o controle ao corpo principal do código: deve armazenar o código de saída em eax para devolvê-lo ao Windows. Até o presente momento, o Windows não faz uso deste valor de retorno, mas é melhor agir com segurança e jogar conforme a regra:

gerenteJanela proc mInst:DWORD, mInstAnt:DWORD, linhaCmd:DWORD, Mostra:DWORD
...

.WHILE TRUE
invoke GetMessage, ADDR malote, NULL, 0, 0
.BREAK .IF (eax < 1)
invoke TranslateMessage, ADDR malote
invoke DispatchMessage, ADDR malote
.ENDW
mov eax, malote.wParam
ret
gerenteJanela endp

Agora o gerente da janela está com todo o seu esquema de trabalho definido. Uma de suas funções é enviar as mensagens recebidas para o gerente de mensagens para que sejam processadas. Então, vamos para a última função...

Processando mensagens recebidas

Agora é a vez do nosso window procedure - o gerente de mensagens. Você pode dar a ele o nome que quiser - não precisa chamá-lo de WindowProc:

LRESULT CALLBACK WindowProc(
HWND hwnd, // manipulador da janela
UINT uMsg, // identificador da mensagem
WPARAM wParam,
LPARAM lParam
);

O primeiro parâmetro, hWnd, é o manipulador da janela para a qual a mensagem é destinada. uMsg é a mensagem. Observe que uMsg NÃO é uma estrutura MSG, é apenas um número identificador. O Windows define centenas de mensagens, a maioria sem maior interesse para o nosso programa, e irá enviar uma mensagem apropriada apenas quando ocorrer um fato relevante para a nossa janela. wParam e lParam são apenas parâmetros extras usados por algumas mensagens que enviam dados acessórios.

No nosso exemplo, vamos chamar o WindowProc de gerenteMensagem:

gerenteMensagem proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
...
gerenteMensagem endp

O gerente de mensagens precisa checar cada mensagem recebida do Windows para verificar se é de interesse. Se for de interesse, deve dar a resposta adequada; se não for, PRECISA chamar DefWindowProc, passando todos os parâmetros que recebeu, para que o processamento default seja efetuado. Esta DefWindowProc é uma função da API que processa as todas as mensagens que não são do interesse do seu programa.

gerenteMensagem proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
invoke DefWindowProc, hWnd, uMsg, wParam, lParam
ret

gerenteMensagem endp

Encerrando o programa

A única mensagem que SEMPRE PRECISA ser respondida é a WM_DESTROY. Esta mensagem é enviada toda vez a janela for fechada - é o sinal de final de expediente. No momento em que esta mensagem é recebida, a janela já foi removida da tela. Esta é apenas uma notificação de que a janela foi destruída e que os "gerentes" devem encerrar o expediente e se liberar do controle do Windows. Ainda há tempo de fazer alguma "ordem no escritório" mas, quando se chegou neste ponto, não há outra opção a não ser terminar. Se você quiser ter uma chance de impedir que o usuário feche a janela, você deve processar a mensagem WM_CLOSE.

gerenteMensagem proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage, NULL
xor eax,eax
ret
.ENDIF

invoke DefWindowProc, hWnd, uMsg, wParam, lParam
ret
gerenteMensagem endp

Bem, voltando à WM_DESTROY... após a eventual "ordem no escritório", o gerente de mensagens precisa chamar PostQuitMessage (a qual remeterá uma mensagem WM_QUIT), zerar o valor do registrador EAX com xor eax,eax ou mov eax,0 e retornar (ret) para o gerente da janela. O gerente da janela vai chamar GetMessage pela última vez, obtendo a mensagem WM_QUIT que acabou de ser enviada pelo gerente de mensagens. GetMessage retorna o valor zero em eax quando recebe WM_QUIT o que, por seu lado, encerra o loop infinito. O gerente da janela se despede enviando o malote.wParam para o Windows e o fluxo do programa volta para o corpo principal para executar a saída do processo chamando ExitProcess.


Código fonte completo

.386
.MODEL FLAT,STDCALL
option casemap:none

include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc

includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib

gerenteJanela proto :DWORD, :DWORD, :DWORD, :DWORD

.DATA?
mInstancia DWORD ?
linhaComando DWORD ?

.DATA
NomeClasse db "JanelaNua",0
TituloJanela db "Janelinha NumaBoa",0

.CODE
inicio:
invoke GetModuleHandle, NULL
mov mInstancia, eax
invoke GetCommandLine
mov linhaComando, eax
invoke gerenteJanela, mInstancia, NULL, linhaComando, SW_SHOWDEFAULT
invoke ExitProcess,0

gerenteJanela proc mInst:DWORD, mInstAnt:DWORD, linhaCmd:DWORD, Mostra:DWORD
LOCAL ej:WNDCLASSEX
LOCAL mJanela:HWND
LOCAL malote:MSG

mov ej.cbSize, SIZEOF WUNDCLASSEX
mov ej.style, CS_HREDRAW or CS_VREDRAW
mov ej.lpfnWdnProc, OFFSET gerenteMensagem
mov ej.cbClsExtra, NULL
mov ej.cbWndExtra, NULL
push mInst
pop ej.hInstance
invoke LoadIcon, NULL, IDI_WINLOGO
mov ej.hIcon, eax
mov ej.hIconSm, eax
invoke LoadCursor, NULL, IDC_ARROW
mov ej.hCursor, eax
mov ej.hbrBackground, COLOR_WINDOW+1
mov ej.lpszMenuName, NULL
mov ej.lpszClassName, OFFSET NomeClasse
invoke RegisterClassEx, ADDR ej
invoke CreateWindowEx, NULL, ADDR NomeClasse, ADDR TituloJanela,
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, mInst, NULL
mov mJanela,eax
invoke ShowWindow, mJanela, SW_SHOWNORMAL
invoke UpdateWindow, mJanela
.WHILE TRUE
invoke GetMessage, ADDR malote, NULL, 0, 0
.BREAK .IF (eax < 1)
invoke TranslateMessage, ADDR malote
invoke DispatchMessage, ADDR malote
.ENDW
mov eax, malote.wParam
ret
gerenteJanela endp

gerenteMensagem proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage, NULL
xor eax,eax
ret
.ENDIF
invoke DefWindowProc, hWnd, uMsg, wParam, lParam
ret
gerenteMensagem endp

end inicio

Conferindo o trabalho do MASM

O QEditor não só é capaz de assemblar um código fonte, como também de dessassemblar um executável. Esta opção se encontra no menu [Tools / Dis-assemble EXE file]. Experimente usá-la com o texto deste programa ainda no editor. O QEditor abre uma nova instância dele mesmo e apresentará o seguinte:

:\masm32\ICZTUTES\TUTE03\WIN1.exe     (hex)     (dec)
.EXE size (bytes) 490 1168
Minimum load size (bytes) 450 1104
Overlay number 0 0
Initial CS:IP 0000:0000
Initial SS:SP 0000:00B8 184
Minimum allocation (para) 0 0
Maximum allocation (para) FFFF 65535
Header size (para) 4 4
Relocation table offset 40 64
Relocation entries 0 0

Portable Executable starts at b0
Signature 00004550 (PE)
Machine 014C (Intel 386)
Sections 0003
Time Date Stamp 3C4CEBF4 Tue Jan 22 01:35:00 2002
Symbol Table 00000000
Number of Symbols 00000000
Optional header size 00E0
Characteristics 010F
Relocation information stripped
Executable Image
Line numbers stripped
Local symbols stripped
32 bit word machine
Magic 010B
Linker Version 5.12
Size of Code 00000200
Size of Initialized Data 00000400
Size of Uninitialized Data 00000000
Address of Entry Point 00001000
Base of Code 00001000
Base of Data 00002000
Image Base 00400000
Section Alignment 00001000
File Alignment 00000200
Operating System Version 4.00
Image Version 0.00
Subsystem Version 4.00
reserved 00000000
Image Size 00004000
Header Size 00000400
Checksum 00000000
Subsystem 0002 (Windows)
DLL Characteristics 0000
Size Of Stack Reserve 00100000
Size Of Stack Commit 00001000
Size Of Heap Reserve 00100000
Size Of Heap Commit 00001000
Loader Flags 00000000
Number of Directories 00000010

Directory Name VirtAddr VirtSize
Export 00000000 00000000
Import 00002040 0000003C
Resource 00000000 00000000
Exception 00000000 00000000
Security 00000000 00000000
Base Relocation 00000000 00000000
Debug 00000000 00000000
Decription/Architecture 00000000 00000000
Machine Value (MIPS GP) 00000000 00000000
Thread Storage 00000000 00000000
Load Configuration 00000000 00000000
Bound Import 00000000 00000000
Import Address Table 00002000 00000040
Delay Import 00000000 00000000
COM Runtime Descriptor 00000000 00000000
(reserved) 00000000 00000000

Section Table
01 .text Virtual Address 00001000
Virtual Size 00000196
Raw Data Offset 00000400
Raw Data Size 00000200
Relocation Offset 00000000
Relocation Count 0000
Line Number Offset 00000000
Line Number Count 0000
Characteristics 60000020
Code
Executable
Readable

02 .rdata Virtual Address 00002000
Virtual Size 000001C2
Raw Data Offset 00000600
Raw Data Size 00000200
Relocation Offset 00000000
Relocation Count 0000
Line Number Offset 00000000
Line Number Count 0000
Characteristics 40000040
Initialized Data
Readable

03 .data Virtual Address 00003000
Virtual Size 00000024
Raw Data Offset 00000800
Raw Data Size 00000200
Relocation Offset 00000000
Relocation Count 0000
Line Number Offset 00000000
Line Number Count 0000
Characteristics C0000040
Initialized Data
Readable
Writeable

Imp Addr Hint Import Name from USER32.dll - Not Bound
00002010 19B LoadIconA
00002014 1DD PostQuitMessage
00002018 128 GetMessageA
0000201C 94 DispatchMessageA
00002020 27D TranslateMessage
00002024 28B UpdateWindow
00002028 197 LoadCursorA
0000202C 83 DefWindowProcA
00002030 58 CreateWindowExA
00002034 1EF RegisterClassExA
00002038 265 ShowWindow

Imp Addr Hint Import Name from KERNEL32.dll - Not Bound
00002000 111 GetModuleHandleA
00002004 B6 GetCommandLineA
00002008 75 ExitProcess

IAT Entry
00000000: 000021A0 0000218E - 00002180 00000000 - 00002110 0000211C
00000018: 000020F4 000020E0 - 00002150 00002164 - 00002102 000020CE
00000030: 000020BC 0000212E - 00002142 00000000

Disassembly
00401000 start:
00401000 6A00 push 0
00401002 E889010000 call fn_00401190
00401007 A31C304000 mov [40301Ch],eax
0040100C E879010000 call fn_0040118A
00401011 A320304000 mov [403020h],eax
00401016 6A0A push 0Ah
00401018 FF3520304000 push dword ptr [403020h]
0040101E 6A00 push 0
00401020 FF351C304000 push dword ptr [40301Ch]
00401026 E806000000 call fn_00401031
0040102B 50 push eax
0040102C E853010000 call fn_00401184

00401031 fn_00401031: ; o nosso gerenteJanela
00401031 55 push ebp
00401032 8BEC mov ebp,esp
00401034 83C4B0 add esp,0FFFFFFB0h
00401037 C745D030000000 mov dword ptr [ebp-30h],30h
0040103E C745D403000000 mov dword ptr [ebp-2Ch],3
00401045 C745D816114000 mov dword ptr [ebp-28h],401116h
0040104C C745DC00000000 mov dword ptr [ebp-24h],0
00401053 C745E000000000 mov dword ptr [ebp-20h],0
0040105A FF7508 push dword ptr [ebp+8]
0040105D 8F45E4 pop [ebp-1Ch]
00401060 68057F0000 push 7F05h
00401065 6A00 push 0
00401067 E8F4000000 call fn_00401160
0040106C 8945E8 mov [ebp-18h],eax
0040106F 8945FC mov [ebp-4],eax
00401072 68007F0000 push 7F00h
00401077 6A00 push 0
00401079 E8DC000000 call fn_0040115A
0040107E 8945EC mov [ebp-14h],eax
00401081 C745F006000000 mov dword ptr [ebp-10h],6
00401088 C745F400000000 mov dword ptr [ebp-0Ch],0
0040108F C745F800304000 mov dword ptr [ebp-8],403000h
00401096 8D45D0 lea eax,[ebp-30h]
00401099 50 push eax
0040109A E8CD000000 call fn_0040116C ; registra a classe
0040109F 6A00 push 0
004010A1 FF7508 push dword ptr [ebp+8]
004010A4 6A00 push 0
004010A6 6A00 push 0
004010A8 6800000080 push 80000000h
004010AD 6800000080 push 80000000h
004010B2 6800000080 push 80000000h
004010B7 6800000080 push 80000000h
004010BC 680000CF00 push 0CF0000h
004010C1 680A304000 push 40300Ah
004010C6 6800304000 push 403000h
004010CB 6A00 push 0
004010CD E870000000 call fn_00401142 ; cria a janela
004010D2 8945B0 mov [ebp-50h],eax
004010D5 6A01 push 1
004010D7 FF75B0 push dword ptr [ebp-50h]
004010DA E893000000 call fn_00401172 ; mostra a janela
004010DF FF75B0 push dword ptr [ebp-50h]
004010E2 E897000000 call fn_0040117E

004010E7 loc_004010E7: ; o loop infinito do gerenteJanela
004010E7 6A00 push 0
004010E9 6A00 push 0
004010EB 6A00 push 0
004010ED 8D45B4 lea eax,[ebp-4Ch]
004010F0 50 push eax
004010F1 E85E000000 call fn_00401154
004010F6 83F801 cmp eax,1
004010F9 7214 jb loc_0040110F
004010FB 8D45B4 lea eax,[ebp-4Ch]
004010FE 50 push eax
004010FF E874000000 call fn_00401178
00401104 8D45B4 lea eax,[ebp-4Ch]
00401107 50 push eax
00401108 E841000000 call fn_0040114E ; chama o gerenteMensagem
0040110D EBD8 jmp loc_004010E7

0040110F loc_0040110F: ; o nosso gerenteMensagem
0040110F 8B45BC mov eax,[ebp-44h]
00401112 C9 leave
00401113 C21000 ret 10h
00401116 55 push ebp
00401117 8BEC mov ebp,esp
00401119 837D0C02 cmp dword ptr [ebp+0Ch],2
0040111D 750D jnz loc_0040112C
0040111F 6A00 push 0
00401121 E840000000 call fn_00401166
00401126 33C0 xor eax,eax
00401128 C9 leave
00401129 C21000 ret 10h

0040112C loc_0040112C:
0040112C FF7514 push dword ptr [ebp+14h]
0040112F FF7510 push dword ptr [ebp+10h]
00401132 FF750C push dword ptr [ebp+0Ch]
00401135 FF7508 push dword ptr [ebp+8]
00401138 E80B000000 call fn_00401148
0040113D C9 leave
0040113E C21000 ret 10h
00401141 CC int 3

00401142 fn_00401142:
00401142 FF2530204000 jmp dword ptr [CreateWindowExA]
00401148 fn_00401148:
00401148 FF252C204000 jmp dword ptr [DefWindowProcA]
0040114E fn_0040114E:
0040114E FF251C204000 jmp dword ptr [DispatchMessageA]
00401154 fn_00401154:
00401154 FF2518204000 jmp dword ptr [GetMessageA]
0040115A fn_0040115A:
0040115A FF2528204000 jmp dword ptr [LoadCursorA]
00401160 fn_00401160:
00401160 FF2510204000 jmp dword ptr [LoadIconA]
00401166 fn_00401166:
00401166 FF2514204000 jmp dword ptr [PostQuitMessage]
0040116C fn_0040116C:
0040116C FF2534204000 jmp dword ptr [RegisterClassExA]
00401172 fn_00401172:
00401172 FF2538204000 jmp dword ptr [ShowWindow]
00401178 fn_00401178:
00401178 FF2520204000 jmp dword ptr [TranslateMessage]
0040117E fn_0040117E:
0040117E FF2524204000 jmp dword ptr [UpdateWindow]
00401184 fn_00401184:
00401184 FF2508204000 jmp dword ptr [ExitProcess]
0040118A fn_0040118A:
0040118A FF2504204000 jmp dword ptr [GetCommandLineA]
00401190 fn_00401190:
00401190 FF2500204000 jmp dword ptr [GetModuleHandleA]

Download

Você pode fazer o download de tutNB03.zip que contém o texto deste tutorial, além do código fonte e do executável ou ir para a seção Downloads/Tutoriais/Assembly Numaboa onde você encontra este e outros zips de tutoriais.

игровые аппараты вулкан играть бесплатнокосметический набор кисточекотзывы полигонкакой производительноутбуки lenovo цены в россииvsemsmart александр лобановский