Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Veröffentlicht: 15. Dez 2001 | Aktualisiert: 18. Jun 2004
Von Paul DiLascia
Das Versenden von Nachrichten kann mit verschiedenen Funktionen geschehen, die alle ihre Vor- und Nachteile haben.
Auf dieser Seite
Frage
Antwort
Diesen Artikel können Sie hier lesen dank freundlicher Unterstützung der Zeitschrift:
.gif)
Frage
Was ist eigentlich der Unterschied zwischen PostMessage und SendMessage? Ich habe schon Programmcode gesehen, in dem beide Funktionen eingesetzt wurden. Können Sie mir sagen, wann PostMessage besser als SendMessage ist?
Antwort
Es gibt einige feine Unterschiede in der Art und Weise, in der diese beiden Funktionen Nachrichten an andere Fenster verschicken. Der wichtigste Unterschied zwischen PostMessage und SendMessage ist aber, dass die Funktion SendMessage die Nachricht direkt an das andere Fenster schickt, indem sie die Fensterfunktion des Zielfensters aufruft (ich nenne diese Fensterfunktion hier übrigens "Funktion", da es in C nur Funktionen gibt. Aus der Sicht der Windows-Programmierschnittstelle ist es eine "Prozedur"). Das Programm wartet solange, bis der Aufruf zurückkehrt. PostMessage stellt die Nachricht dagegen in eine MSG-Struktur und kehrt sofort zum Aufrufer zurück, ohne auf die Bearbeitung der Nachricht zu warten. MSG ist übrigens die Abkürzung für Message (Nachricht).
Wenn Sie so wollen, schickt oder sendet SendMessage dem Empfänger die Nachricht, während PostMessage die Nachricht irgendwo für den Empfänger hinterlegt.
Mit SendMessage kann der Empfängerprozess die Nachricht sofort bearbeiten, statt die Bearbeitung solange aufschieben zu müssen, bis er die Nachricht aus der Warteschlange geholt hat. Nehmen wir folgende Zeilen als Beispiel:
CString s; pWnd->SendMessage(WM_SETTEXT, 0, "Fooblitzky"); pWnd->GetWindowText(&s); // s is "Fooblitzky"
Dieses Beispiel schickt dem Fenster, das durch pWnd repräsentiert wird, die Nachricht WM_SETTEXT. Windows ruft die Fensterfunktion dieses Fensters mit WM_SETTEXT als Nachrichtencode auf, mit einer Null als WPARAM und einen Zeiger auf den Text "Fooblitzky" als LPARAM. Die Fensterfunktion bearbeitet diese Nachricht und kehrt zum Aufrufer zurück. Die Kontrolle geht an die nächste Zeile über, die GetWindowText aufruft. Anschließend enthält s den Wert "Fooblitzky", wie zu erwarten war. SendMessage verhält sich wie ein Funktionsaufruf, wobei WM_SETTEXT die Funktion ist.
Und was geschieht, wenn man statt dessen PostMessage benutzt?
CString s; pWnd->PostMessage(WM_SETTEXT, 0, "Fooblitzky"); pWnd->GetWindowText(&s); // s is ????
PostMessage ruft nicht die Fensterfunktion auf. Statt dessen stellt diese Funktion eine MSG-Struktur in die Nachrichtenschlange des Threads oder Prozesses, zu dem das Fenster gehört, und kehrt zum Aufrufer zurück. Die Struktur enthält den Nachrichtencode und die Parameter. Der Thread oder die Anwendung bearbeitet diese Nachricht erst, nachdem sie die Nachricht aus der Nachrichtenschlange geholt hat. Also erst dann, wenn die Nachrichtenpumpe GetMessage aufruft und dieses WM_SETTEXT findet (Es könnte ja sein, dass vor WM_SETTEXT noch andere Nachrichten in der Schlange stehen.).
Jedenfalls kehrt PostMessage nach der Übergabe der Nachricht direkt wieder zum Aufrufer zurück, ohne auf deren Bearbeitung zu warten. Wenn also die nächste Programmzeile ausgeführt wird (GetWindowText), wird s nicht mit "Fooblitzky" dienen können. Es sei denn, dass dies bereits vor dem Aufruf von PostMessage der gültige Fenstertext war. Wenn das Fenster zu einem anderen Thread oder Prozess gehört, ist es natürlich theoretisch denkbar, dass Windows Ihren Thread genau zwischen den Aufrufen von PostMessage und GetWindowText unterbricht und dem anderen Thread hinreichend Zeit zur Bearbeitung von WM_SETTEXT gibt. Das ist wohl ungefähr so häufig wie ein Sechser im Lotto, aber unmöglich ist es nicht. (Falls Sie an der Steuerung eines Kernkraftwerks arbeiten, sollten Sie eine ausgeprägte Allergie gegen solche "Möglichkeiten" entwickeln. Aber für unsere Zwecke spielt es hier keine Rolle.)
Es gibt also zwei Wege, einem Fenster eine Nachricht zukommen zu lassen. SendMessage ruft direkt die Fensterfunktion des Ziels auf, während PostMessage die Nachricht in der Warteschlange des Threads oder Prozesses hinterlegt. Bild B1 verdeutlicht diesen Unterschied.
.gif)
B1 SendMessage versus PostMessage
Wenden wir uns nach dieser Vorgeschichte wieder der ursprünglichen Frage zu: wann soll man SendMessage benutzen und wann PostMessage? In den meisten Fällen dürfte SendMessage die Funktion der Wahl sein. Sie ruft ein Fenster auf, um die gewünschte Arbeit sofort erledigen zu lassen. Sie möchten den Text verändern oder ein Bitmap setzen oder ein Attribut abfragen oder was auch immer, und es soll sofort geschehen, weil schon die nächste Codezeile vom Ergebnis abhängig ist. Die MFC ist voll von Hüllfunktionen, die nichts weiter tun als SendMessage aufzurufen. Ein Beispiel:
// typische MFC-Hüllfunktion
inline void CEdit::Cut()
{
::SendMessage(m_hWnd, WM_CUT, 0, 0);
}
Der Vorteil dieser Hüllfunktionen ist, dass sie SendMessage mehr wie den normalen Funktionsaufruf aussehen lassen, der er auch ist (Ein anderer Vorteil ist die verringerte Schreibarbeit.). Diese Hüllen - und SendMessage - werden normalerweise zur Kommunikation zwischen Eltern- und Kindfenstern benutzt.
Wann sollten Sie zu PostMessage greifen? Normalerweise hinterlegen Sie die Nachricht für den Empfänger, wenn die geforderte Aktion nicht dringend ist oder sich über einige Zeit erstrecken kann oder wenn man die aktuellen Arbeiten nicht unterbrechen will, bis das Ergebnis eintrifft. Die Nachrichten haben mit PostMessage den Charakter eines Hinweises: "Hallihallo, es ist etwas geschehen - falls es Sie überhaupt interessiert..." Wenn Sie zum Beispiel Windows mit InvalidateRect oder InvalidateRgn darüber informieren, dass ein bestimmter Bereich auf dem Bildschirm aktualisiert werden muss, macht sich Windows keineswegs unverzüglich an die Arbeit, sondern nimmt den angegebenen Bereich in eine Liste auf und hinterlegt eine WM_PAINT-Nachricht (falls noch keine hinterlegt wurde). Sollte Ihre Anwendung vor der Durchführung der Malerarbeiten erneut InvalidateRgn aufrufen, wird der zweite Bereich ebenfalls in die Liste aufgenommen. Das Fenster wird erst später aktualisiert, bei der Bearbeitung der Nachricht WM_PAINT. Auf diese Weise werden also eine ganze Reihe von Bereichen in einem Rutsch auf den neusten Stand gebracht.
Eine ähnliche Situation ergibt sich, wenn ein Arbeitsthread seinen UI-Thread darüber informiert, dass Daten für die Darstellung auf dem Bildschirm bereitstehen. Das macht er sinnvollerweise, indem er die betreffende Nachricht für den UI-Thread hinterlegt. Danach fährt der Arbeitsthread mit seiner Arbeit fort und die UI bearbeitet irgendwann die Nachricht, sobald sie die Zeit dafür findet. Asynchrone Aktualisierung ist ein übliches Programmier-Paradigma und ein klarer Kandidat für PostMessage. Im Allgemeinen ist PostMessage die sicherere Lösung, wenn es um die Übermittlung von Nachrichten an einen anderen Prozess oder Thread geht, weil SendMessage bei solch einer Aktion nämlich blockieren kann. Dazu gleich mehr.
Eine weitere Gelegenheit für den Einsatz von PostMessage ergibt sich, wenn Sie die Bearbeitung der aktuellen Nachricht erst abschließen möchten, bevor die nächste Nachricht an die Reihe kommt. Nehmen wir zum Beispiel an, Sie stellen bei der Bearbeitung einer Nachricht fest, dass es an der Zeit ist, die Anwendung herunterzufahren (vielleicht handelt es sich bei der aktuellen Nachricht um den Befehl Beenden). Nun möchten Sie WM_QUIT aber nicht mit SendMessage verschicken, weil sonst mitten in der laufenden Arbeit die Nachrichtenfunktion aufgerufen würde und das Programm in die Knie zwänge, bevor Sie fertig sind. Da ist es schon besser, WM_QUIT für die nächste passende Gelegenheit zu hinterlegen. Dann kann Ihre Anwendung nämlich alle Abschiedsgrüße bearbeiten, wie WM_CLOSE, WM_DESTROY und WM_POSTNCDESTROY, bevor sie in der vorgesehenen Weise terminiert. Dieses Problem tritt praktisch ständig auf, so dass es für die Hinterlegung von WM_QUIT sogar eine eigene Funktion namens PostQuitMessage gibt.
PostMessage ist auch dann die bessere Lösung, wenn Sie zum Beispiel ein Eingabeereignis oder einen Befehl simulieren möchten. Dann stellen Sie einfach mit PostMessage die entsprechende WM_COMMAND-Nachricht oder eine der Tastatur- oder Mausnachrichten (was übrigens immer etwas riskant ist) in die Nachrichtenschlange. Das ist deswegen möglich, weil auch die "echten" Eingabeereignisse normalerweise durch eine bestimmte Nachrichtensequenz gemeldet werden (zum Beispiel durch Taste-gedrückt/Taste-losgelassen-Nachrichtenpaare). Ihre Anwendung könnte nun kräftig durcheinander geraten, wenn Sie mitten in solchen Paaren per SendMessage andere Eingabenachrichten abschicken. PostMessage ist zur Simulation der Eingaben einfach besser geeignet.
Gelegentlich müssen Sie auch zu PostMessage greifen, um irgendeine Macke oder einen Fehler zu umgehen, der Sie sonst zum Beispiel in eine unendliche Rekursion führt. Nehmen wir zum Beispiel an, Ihre OnSetFocus-Funktion (sie bearbeitet die Nachricht WM_SETFOCUS) kommt zu der Überzeugung, das neue Fokusfenster tauge nichts, und Sie müssen den Fokus auf ein anderes Fenster legen. Wenn Sie nun in OnSetFocus die Funktion SetFocus aufrufen, schickt Windows sofort die nächste WM_SETFOCUS-Nachricht, obwohl Sie noch mitten in der Bearbeitung der alten WM_SETFOCUS-Nachricht stecken. Das Ergebnis ist eine Art unendlicher Rekursion, bis Ihnen der Stapel um die Ohren fliegt. Um solche Geschichten zu vermeiden, hinterlegen Sie einfach die Nachricht MYWM_SWITCHFOCUS für sich selbst, damit OnSetFocus die laufenden Arbeiten in aller Ruhe abschließen kann, bevor die Nachricht mit der Fokusänderung bearbeitet wird. Das ist aber eines dieser Beispiele, die in der Praxis viel einfacher zu verstehen sind als auf dem Papier. Man darf nur nicht vergessen, dass Windows allergisch reagiert, wenn man mitten in der WM_SETFOCUS-Funktion SetFocus aufruft.
Da SendMessage direkt die Fensterfunktion des Ziels aufruft, braucht sie eine HWND. Woher sollte sie sonst auch wissen, welche Fensterfunktion aufzurufen ist? PostMessage stellt die Nachricht aber in die Nachrichtenschlange und die ist mit einem Thread oder Prozess verknüpft, nicht mit einem Fenster. In der Theorie braucht PostMessage kein Fenster. Und in der Praxis ist dies auch tatsächlich machbar:
// hinterlege eine Nachricht für mich PostMessage(NULL, WM_HI_THERE_HANDSOME, ...);
Wenn der HWND-Wert eine schlichte Null ist, stellt PostMessage die Nachricht einfach in die Nachrichtenschlange des aktuell laufenden Threads. In der Praxis ist diese Variante aber nicht so wahnsinnig nützlich, weil der Empfänger meistens in einem anderen Thread liegt (vielleicht mit PostThreadMessage). Ab und zu gibt es aber die seltene Situation, in der es sehr bequem ist, eine Nachricht für sich selbst zu hinterlegen, ohne auf ein Fenster angewiesen zu sein (Falls Ihnen solch eine Situation einfällt, sagen Sie mir bitte Bescheid.).
Falls Ihnen inzwischen so langsam der Unterschied zwischen SendMessage und PostMessage dämmert, können Sie sich auch gleich auf die nächsten drei Nachrichtenfunktionen stürzen, die da heißen SendMessageCallback, SendNotifyMessage und SendMessageTimeout. Diese Funktionen sind in der Welt von Win32 und Multithreading von Nutzen. Wenn Sie im Win32 SendMessage aufrufen, blockiert Ihr Thread so lange, bis der Zielthread mit der Bearbeitung der Nachricht fertig ist. Sollte der Zielthread selbst aus irgendeinem Grund blockiert sein, kehrt SendMessage leider nie zurück. Oops...
SendNotifyMessage, SendMessageTimeout und SendMessageCallback wurden erfunden, um dieses Problem zu vermeiden. SendNotifyMessage funktioniert wie SendMessage, sofern das Zielfenster zum aktuellen Thread gehört (also von diesem Thread angelegt wurde). Sie funktioniert aber wie PostMessage, wenn das Zielfenster zu einem anderen Thread gehört. SendMessageTimeout verhält sich ähnlich, lässt aber die Angabe einer maximalen Wartezeit für die Antwort des anderen Threads zu. Die Windows-Funktion ExitWindowsEx schickt den Hauptanwendungen (top-level) die WM_QUERYENDSESSION-Nachricht mit SendMessageTimeout. Mit anderen Worten, sie versucht, zu jeder Anwendung höflich zu sein und ihr die Gelegenheit zu geben, stilvoll abzutreten. Wenn die Anwendung aber nicht rechtzeitig antwortet, schlägt ExitWindowsEx eben mit dem großen Holzhammer drauf. SendMessageTimeout wartet, aber nicht in alle Ewigkeit.
Wie Sie wohl schon ahnen, erwartet SendMessageCallback als Argument eine Rückruffunktion (einen "Callback") für den Rückruf ins Programm. SendMessageCallback stellt ihre Nachricht zu und kehrt sofort wieder zum Aufrufer zurück. Nachdem die Nachricht bearbeitet wurde, ruft Windows nun die angegebene Funktion auf, die normalerweise in Ihrer eigenen Anwendung liegt. SendMessageCallback ist recht praktisch, wenn man eigentlich mit PostMessage arbeiten möchte, aber auch wissen möchte, wann die Nachricht bearbeitet wurde. Stellen Sie sich diese Funktion als eine Art PostMessage mit Ausführungsquittung vor.
PostMessage, SendMessageTimeout und SendNotifyMessage sind alles gute Kandidaten, wenn Sie mit HWND_TOPMOST als HWND eine Nachricht an alle Hauptfenster verschicken möchten. Dagegen ist es keine gute Idee, HWND_TOPMOST mit SendMessage unter das Volk zu bringen, weil schon eine einzige tote Anwendung ausreicht, um Ihre eigene Anwendung völlig auszubremsen.
Tabelle T1 fasst die Unterschiede zwischen den verschiedenen Funktionen zusammen, mit denen man den Fenstern Nachrichten zukommen lassen kann. Sie haben die Qual der Wahl.
T1 Windows-Funktionen zum Verschicken von Nachrichten
Funktion |
Aufgabe |
Wann eingesetzt? |
Windows-Version |
SendMessage |
Schickt die Nachricht direkt ans Zielfenster, indem sie die Fensterfunktion des Zielfensters aufruft. Kehrt erst nach der Bearbeitung der Nachricht zum Aufrufer zurück. Funktioniert im Prinzip wie ein Funktionsaufruf. SendMessage kann blockieren, wenn das Ziel auf einem anderen Thread läuft. |
Wird benutzt, um eigenen Fenstern einen Befehl zu geben, der sofort ausgeführt werden muss, weil schon die nächste Codezeile vom Ergebnis abhängt.SendMessage wird zum Beispiel für die Kommunikation zwischen Eltern- und Kindfenstern benutzt. Passen Sie aber auf, wenn Sie Nachrichten an Fenster schicken, die auf anderen Threads laufen, weil diese Funktion dann blockieren kann. |
Alle Versionen |
PostMessage |
Hinterlegt die Nachricht für das Zielfenster, indem sie die Nachricht in einer MSG-Struktur in die Nachrichtenschlange des Threads oder Prozesses stellt, dem das Fenster gehört. Kehrt dann direkt zum Aufrufer zurück, ohne die Bearbeitung der Nachricht abzuwarten. Kann mit einer Null als HWND aufgerufen werden und ermöglicht daher die Kommunikation ohne Einschaltung eines Fensters. |
Wird für Nachrichten benutzt, die nicht sofort bearbeitet werden sollen oder eher informativer Natur sind (zum Beispiel ein Hinweis). Diese Funktion eignet sich auch zur Simulation von Befehlen oder Eingaben, zum Verschicken von Nachrichten ohne Blockiergefahr und zum Rundruf mit HWND_TOPMOST an alle Hauptfenster. |
Alle Versionen |
SendNotifyMessage |
Ruft die Fensterfunktion des Zielfensters auf und wartet auf die Bearbeitung der Nachricht, sofern das Zielfenster zum selben Thread gehört (vom selben Thread angelegt wurde). Gehört das Fenster dagegen zu einem anderen Thread, kehrt diese Funktion unverzüglich wieder zum Aufrufer zurück. |
Wird zum Versand von Nachrichten an Fenster auf anderen Threads oder in anderen Prozessen eingesetzt, wenn ein Blockieren vermieden werden muss. Eignet sich mit HWND_TOPMOST zum Rundruf an alle Hauptfenster. |
Win32 |
SendMessageTimeout |
Sofern das Zielfenster zum selben Thread gehört, ruft diese Funktion die Fensterfunktion des Zielfensters auf und wartet auf die Bearbeitung der Nachricht. Gehört das Fenster zu einem anderen Thread, schickt SendMessageTimeout die Nachricht und wartet auf die Bearbeitung der Nachricht, allerdings höchstens für die angegebene Zeitspanne. Sollte die Nachricht nicht innerhalb dieser Zeitspanne bearbeitet werden, kehrt diese Funktion wieder zum Aufrufer zurück. |
Wird zum Versand von Nachrichten an Fenster auf anderen Threads oder in anderen Prozessen eingesetzt, wenn ein Blockieren vermieden werden muss. Eignet sich mit HWND_TOPMOST zum Rundruf an alle Hauptfenster. |
Win32 |
SendMessageCallback |
Übermittelt dem Zielfenster eine Nachricht und kehrt sofort wieder zum Aufrufer zurück. Sobald die Nachricht bearbeitet wurde, ruft Windows die angegebene Funktion mit den Informationen über das Ergebnis auf. |
Greifen Sie zu dieser Funktion, wenn Sie eigentlich PostMessage benutzen möchten, aber auch über die Bearbeitung der Nachricht informiert werden möchten. |
Win32 |