O que é uma bomba de mensagem?

104

Em esta discussão (publicado há um ano), há uma discussão dos problemas que podem vir com a executar o Word em uma sessão não-interativo. O conselho (bastante forte) dado ali é para não fazer isso. Em uma postagem é declarado "Todas as APIs do Office pressupõem que você está executando o Office em uma sessão interativa em um desktop, com um monitor, teclado e mouse e, o mais importante, uma bomba de mensagem." Não tenho certeza do que é. (Eu tenho programado em C # por apenas cerca de um ano; minha outra experiência de programação foi principalmente com ColdFusion.)

Atualizar:

Meu programa executa um grande número de arquivos RTF para extrair duas informações usadas para construir um número de relatório médico. Em vez de tentar descobrir como funcionam as instruções de formatação em RTF, decidi apenas abri-las no Word e retirar o texto de lá (sem realmente iniciar a GUI). Ocasionalmente, o programa travava no meio do processamento de um arquivo e deixava um encadeamento do Word aberto anexado a esse documento (ainda tenho que descobrir como encerrar aquele). Quando executei o programa novamente, é claro que recebi uma notificação de que havia um encadeamento usando aquele arquivo e eu queria abrir uma cópia somente leitura? Quando eu disse Sim, a GUI do Word apareceu de repente do nada e começou a processar os arquivos. Eu estava me perguntando por que isso aconteceu;

Matt Gutting
fonte
3
Por que isso está marcado como win32? - O sistema de mensagens estava no Windows V1 (que, pelo que me lembro, era de 8 bits.)
Hogan

Respostas:

187

Um loop de mensagem é um pequeno trecho de código que existe em qualquer programa nativo do Windows. É mais ou menos assim:

MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{ 
   TranslateMessage(&msg); 
   DispatchMessage(&msg); 
} 

A API Win32 GetMessage () recupera uma mensagem do Windows. Seu programa normalmente passa 99,9% do tempo lá, esperando que o Windows diga que algo interessante aconteceu. TranslateMessage () é uma função auxiliar que traduz mensagens do teclado. DispatchMessage () garante que o procedimento de janela seja chamado com a mensagem.

Cada programa .NET habilitado para GUI tem um loop de mensagem, ele é iniciado por Application.Run ().

A relevância de um loop de mensagem para o Office está relacionada ao COM. Os programas do Office são programas habilitados para COM, é assim que funcionam as classes Microsoft.Office.Interop. COM cuida do threading em nome de um coclass COM, ele garante que as chamadas feitas em uma interface COM sejam sempre feitas do thread correto. A maioria das classes COM tem uma chave de registro no registro que declara seu ThreadingModel, de longe as mais comuns (incluindo Office) usam "Apartment". O que significa que a única maneira segura de chamar um método de interface é fazendo a chamada do mesmo thread que criou o objeto de classe. Ou, colocando de outra forma: de longe, a maioria das classes COM não é segura para threads.

Cada segmento habilitado para COM pertence a um compartimento COM. Existem dois tipos, Single Threaded Apartments (STA) e um Multi Threaded Apartment (MTA). Uma classe COM threaded apartment deve ser criada em um thread STA. Você pode ver isso nos programas .NET, o ponto de entrada do thread de interface do usuário de um Windows Forms ou programa WPF tem o atributo [STAThread]. O modelo de apartamento para outros threads é definido pelo método Thread.SetApartmentState ().

Grandes partes do encanamento do Windows não funcionarão corretamente se o thread da interface do usuário não for STA. Notavelmente Drag + Drop, a área de transferência, diálogos do Windows como OpenFileDialog, controles como WebBrowser, aplicativos de automação de interface do usuário como leitores de tela. E muitos servidores COM, como o Office.

Um requisito difícil para um thread STA é que ele nunca deve bloquear e deve bombear um loop de mensagem. O loop de mensagem é importante porque é o que COM usa para empacotar uma chamada de método de interface de um thread para outro. Embora o .NET facilite as chamadas de empacotamento (Control.BeginInvoke ou Dispatcher.BeginInvoke, por exemplo), na verdade é uma coisa muito complicada de fazer. O thread que executa a chamada deve estar em um estado conhecido. Você não pode simplesmente interromper um thread arbitrariamente e forçá-lo a fazer uma chamada de método, o que causaria problemas de reentrância horríveis. Um thread deve estar "ocioso", não ocupado executando qualquer código que esteja alterando o estado do programa.

Talvez você possa ver aonde isso leva: sim, quando um programa está executando o loop de mensagem, ele está ocioso. O empacotamento real ocorre por meio de uma janela oculta que COM cria, ele usa PostMessage para que o procedimento de janela dessa janela execute o código. No tópico da STA. O loop de mensagem garante que esse código seja executado.

Hans Passant
fonte
Resposta muito agradável e detalhada. Só para adicionar - há também uma STA especial chamada STA principal, que é a primeira STA criada. Que deve ser idealmente criado por seu thread de IU. O STA principal é onde os componentes com modelo de threading = none são criados. Se o seu STA principal não for aquele criado pelo thread de IU - você pode ter problemas interessantes ao usar controles Activex mais antigos que não têm nenhum modelo de threading.
Quixver
12

A "bomba de mensagens" é uma parte central de qualquer programa do Windows responsável por enviar mensagens em janelas para as várias partes do aplicativo. Este é o núcleo da programação da IU do Win32. Por causa de sua onipresença, muitos aplicativos usam a bomba de mensagem para passar mensagens entre módulos diferentes, razão pela qual os aplicativos do Office serão interrompidos se forem executados sem nenhuma interface do usuário.

A Wikipedia tem uma descrição básica .

JSB ձոգչ
fonte
Acredito que seja impossível escrever um aplicativo do Windows sem um loop de mensagem, portanto, todos os aplicativos usam a bomba de mensagem.
Hogan
2
Você também pode escrever aplicativos GUI simples sem um - por exemplo, você pode abrir caixas de mensagens pop-up sem que seu próprio aplicativo tenha um loop de mensagem em seu aplicativo.
Se você criar uma caixa de diálogo via DialogBox ou DialogBox indireta - você não precisa de um loop de mensagem, você só precisa fornecer uma função (dlgproc) que será chamada pelo Windows. (e uma caixa de mensagem é apenas um diálogo simples)
quixver
6

John está falando sobre como o sistema Windows (e outros sistemas baseados em janela - X Window , Mac OS original ....) implementam interfaces de usuário assíncronas usando eventos por meio de um sistema de mensagens.

Nos bastidores de cada aplicativo, há um sistema de mensagens em que cada janela pode enviar eventos para outras janelas ou ouvintes de eventos - isso é implementado adicionando uma mensagem à fila de mensagens. Há um loop principal que sempre executa olhando para essa fila de mensagens e, em seguida, despacha as mensagens (ou eventos) para os ouvintes.

O artigo da Wikipedia Message loop no Microsoft Windows mostra o código de exemplo de um programa básico do Windows - e como você pode ver no nível mais básico, um programa do Windows é apenas a "bomba de mensagem".

Então, para juntar tudo. O motivo pelo qual um programa do Windows projetado para oferecer suporte a uma IU não pode atuar como um serviço é porque ele precisa do loop de mensagens em execução o tempo todo para habilitar o suporte à IU. Se você implementá-lo como um serviço conforme descrito, ele não será capaz de processar o tratamento de eventos assíncronos internos.

Hogan
fonte
6

No COM , uma bomba de mensagem serializa e desserializa as mensagens enviadas entre apartamentos. Um apartamento é um miniprocesso no qual os componentes COM podem ser executados. Os apartamentos vêm em modos single threaded e free threaded. Os apartamentos de thread único são principalmente um sistema legado para aplicativos de componentes COM que não oferecem suporte a multi-threading. Eles eram normalmente usados ​​com o Visual BASIC (já que não suportava código multithread) e aplicativos legados.

Eu acho que o requisito de bomba de mensagem para o Word deriva da API COM ou de partes do aplicativo que não são thread-safe. Lembre-se de que os modelos de threading e de coleta de lixo .NET não funcionam bem com o COM pronto para uso. COM tem um mecanismo de coleta de lixo muito simplista e um modelo de threading que exige que você faça as coisas da maneira COM. O uso dos PIAs padrão do Office ainda exige que você feche explicitamente as referências de objeto COM, portanto, você precisa controlar cada identificador COM criado. Os PIAs também criarão coisas nos bastidores, se você não tomar cuidado.

A integração .NET-COM é um tópico por si só, e existem até livros escritos sobre o assunto. Até mesmo o uso de APIs COM para Office em um aplicativo de área de trabalho interativo exige que você passe por muitos obstáculos e certifique-se de que as referências sejam liberadas explicitamente.

Pode-se presumir que o Office não é seguro para threads, portanto, você precisará de uma instância separada do Word, Excel ou outros aplicativos do Office para cada thread. Você teria que incorrer na sobrecarga inicial ou manter um pool de threads. Um pool de threads teria que ser testado meticulosamente para garantir que todas as referências COM foram lançadas corretamente. Até mesmo iniciar e encerrar instâncias exige que você certifique-se de que todas as referências sejam liberadas corretamente. Deixar de pontuar seus is e cruzar seus t aqui resultará em um grande número de objetos COM mortos e até mesmo instâncias inteiras do Word vazando.

ConcernedOfTunbridgeWells
fonte
1
Existem algumas imprecisões em sua resposta. Existem 3 tipos de apartamentos - STA (Single threaded), MTA (Multi Threaded) e NTA (Neutral Threaded). Free threaded é usado para descrever um componente que agrega o free threaded marshaller, não existe um appartment free threaded. COM faz uso de mensagens para se comunicar com STAs. Para componentes que vivem em um MTA (ou que agregam o marshaller encadeado livre), nenhum loop de mensagem é necessário. AFAIK - lpc é usado para empacotar dados de um thread de chamada para um thread do pool de threads de RPC que, na verdade, invoca o método.
Quixver
0

Acho que essa discussão do Canal 9 tem uma boa explicação sucinta:

Este processo de comunicação de janela é possibilitado pelo chamado Windows Message Pump. Pense no Message Pump como uma entidade que permite a cooperação entre as janelas do aplicativo e a área de trabalho.

Richard Ev
fonte
2
uau ... essa é uma citação horrível e enganosa. (Uma "entidade"? Err .. não.)
Hogan
4
entidade - objeto: algo que existe ou é percebido como um único objeto separado encarta.msn.com/dictionary_1861608661/entity.html
Matthew Whited