oicìliS © - Assembly para Iniciantes
(ver 1.0 de 19.01.02)

JANELAS
Módulo I - Projetando e Registrando uma Classe

 

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.

 

O PROJETO

 

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

  1. nosso 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).
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 disponíveis, 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 (itens 1 a 9) e os finalmentes do tutorial (itens 10 a 12):

  1. Obter o manipulador (handle) da instância do programa (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. 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 (Módulo II).
  6. Atualizar a área cliente da janela (Módulo II).
  7. Entrar num laço infinito (infinite loop), checando as mensagens do Windows (Módulo II).
  8. Processar as mensagens da janela - o gerente de mensagens (Módulo II).
  9. Encerrar o programa se o usuário fechar a janela (Módulo II).
  10.  

  11. O código fonte completo (Módulo II)
  12. Conferindo o trabalho do MASM (Módulo II)
  13. Atélogo: o resumo do tutorial (Módulo II)

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

 

1. O manipulador (handle) da instância do programa

 

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 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. Aproveite e familiarize-se um pouco mais com o MASM32: clique no item de menu [Tools / API Library List] para ativar um 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 suas bibliotecas correspondentes. Digite "getmoduleh" e você já estará na linha correspondente à 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 termos que lembrar que endereço é este, 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 nosso 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 (?).

 

Como saber que o tipo da variável deve ser DWORD? 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 da sua 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 mneumônico MOV com os operandos mInstancia e eax. Vamos por partes.

Um mneumô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 uma localização para outra. Dê uma olhada no texto acessório "Códigos Operacionais" para entender melhor.

EAX é um registrador de uso geral do processador, 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á 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.

 

2. Pegar as instruções da linha de comando

 

Geralmente a linha de comando 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

 

3. Registrar a classe da janela

 

O Windows só consegue criar objetos à 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 inicial 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. Sob 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. Sob o 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 passe 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 de 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 do código.

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

 

3a. Criar a classe da nossa janela

 

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.

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 <nome da variável local>:<tipo da variável>. 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 retornamos 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 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 detalhes no módulo II deste tutorial. Por enquanto vamos atribuir valores aos primeiros membros da estrutura usando o mneumô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 mneumônico push para colocar o valor na pilha e o mneumô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

 

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

);

 

Em ambas as 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 encontra-se no registrador EAX.

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 a cada mensagem recebida.

 

3b. Registrar nossa classe de janela

 

Após criar uma classe (a "planta de uma casa"), é obrigatório registrá-la para que o sistema nos 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

 

4. Criar a janela

 

Se 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 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 e NT. 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 não se desejar estilos extras.
  • 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.

É 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. No Módulo II deste tutorial vamos dar os retoques finais e iremos aprender como "pilotar" a janela.

 


| AAAA | Página Inicial | Mapa do Site | Novidades | Busca | Indique esta página | Mestre da Teia | Voltar |
| Localizador || @ Info NumaBoa > oicìliS > Assembly > Janelas
Créditos: vovó Vicki

webdesign sobMedida by vickiSoft - /informatica/oiciliS/assembler/tutJanelas/janelas.php (19.01.02) versão 1.0 de 25.01.02
Licença Creative Commons 1998-2006 Aldeia NumaBoa
Exceto onde especificamente declarado, todo material deste site é disponibilizado de acordo com a Licença Creative Commons.