8 de outubro de 2009

Comunicação com a Thread principal usando mensagens do Windows

No fonte do programa que eu publiquei no post sobre sincronização de Threads eu usei o recurso de enviar mensagens do Windows para comunicação com a Thread principal, isto é, aquela Thread onde a janela do programa é controlada. Para não misturar as técnicas, não toquei nesse assunto lá mas vou explicar aqui como é que funciona o mecanismo.

O advento da programação visual - onde você simplesmente arrasta botões, caixas de edição e outros componentes para desenhar as janelas de uma aplicação e responder a eventos associdados a eles - escondeu do programador um fato básico a respeito do funcionamento do Windows : tudo em uma janela acontece através do envio de mensagens. Desde a criação e destruição da janela, passando pela pintura e alterações no tamanho até o clique de botões, digitação de textos e mesmo o simples passar do mouse por sobre a janela geram uma infinidade de eventos chamados de "Mensagens".

Cada janela é criada com uma fila interna onde as mensagens recebidas são colocadas pelo Windows para que a janela possa recuperá-las e tratá-las. Por isso, nos primórdios da programação para Windows, todo programa era montado em cima de um laço para obter cada mensagem e uma estrutura do tipo switch/case para tratar cada uma delas - muitas tratadas automaticamente pelo próprio Windows, como mover ou redimensionar a janela. Hoje, esse mecanismo continua existindo mas é encapsulado por classes dentro de frameworks como a VCL ou o .NET.

Uma mensagem é constituida de um código que a define (ex: WM_PAINT quando a janela precisa ser desenhada ou WM_MOUSEMOVE quando passamos o mouse sobre uma janela), o handle que indica a qual janela a mensagem é direcionada e parâmetros com informações extras para complementar a descrição do evento. O significado dos parâmetros varia de acordo com a mensagem.

Além das mensagens definidas pelo próprio sistema operacional, é permitido às aplicações registrar tipos de mensagens privadas, cujo siginificado é restrito ao escopo da própria aplicação. Foi isso que fiz no programa de exemplo das Threads, declarando duas novas mensagens:
var
WM_NOTIFY_THREAD_STATUS : Cardinal;
WM_NOTIFY_THREAD_TERMINATED : Cardinal;

{ ... }

WM_NOTIFY_THREAD_STATUS := RegisterWindowMessage ('WM_NOTIFY_THREAD_STATUS');
WM_NOTIFY_THREAD_TERMINATED := RegisterWindowMessage ('WM_NOTIFY_THREAD_TERMINATED');

O Windows atribuirá ao nome passado para a função RegisterWindowMessage um código interno de modo que novas chamadas a ela sempre retornarão o mesmo valor. Não é o caso do exemplo mas eu poderia ter usado esse mecanismo para comunicar com outro programa meu em execução no mesmo computador e que registrasse uma mensagem com o mesmo nome.

Com a mensagem registrada, minha Thread pode notificar o seu status à janela principal enviando a ela uma mensagem. Cada Thread de classificação no exemplo é associada a uma estrutura chamada _Status que, entre outras coisas, possui o handle da janela do programa, usado no envio da mensagem.
PostMessage (_Status.OwnerForm.Handle,
WM_NOTIFY_THREAD_STATUS,
Integer (_Status), 0);

A função PostMessage da API do Windows coloca uma mensagem na fila de mensagens da janela indicada pelo Handle mas não aguarda seu tratamento. Os demais parâmetros são o código da mensagem e informações adicionais específicas dessa mensagem - no caso, o ponteiro para toda a estrutura de status da Thread.

No caso do Delphi e do C++ Builder, o tratamento de mensagens direcionadas a uma janela pode ser estendido criando uma sobreposição da função WndProc, que existe em todas as janelas.
{ Definição na classe da Janela }
protected
procedure WndProc(var Message: TMessage);override;

{ ... }

{ Corpo da função }
procedure TWSyncThread.WndProc(var Message: TMessage);
var lMsg : TWMsgNotifyStatus;
begin
if Message.Msg = WM_NOTIFY_THREAD_STATUS then
begin
lMsg := TWMsgNotifyStatus(Message);
lblMsg.Caption := lMsg.Status.Texto;

{ ... }

lMsg.Result := -1;
end else
inherited;
end;

Veja o uso da palavra-chave inherited. Isto repassa o tratamento de todas as outras mensagens à mesma função existente na classe pai (a própria classe TForm). Sem isso, a janela deixa de funcionar.

A estrutura TWMsgNotifyStatus é definida por mim para conter os parâmetros esperados por mensagens do tipo WM_NOTIFY_THREAD_STATUS. A estrutura é construída de forma que os parâmetros passados à função PostMessage se encaixem nela, facilitando o acesso e a interpretação do significado de cada parâmetro.

Um comentário :

Anônimo disse...

muito bom, parcero..

Postar um comentário

OBS: Os comentários enviados a este Blog são submetidos a moderação. Por isso, eles serão publicados somente após aprovação.

Observação: somente um membro deste blog pode postar um comentário.