WND - der Windows Nachrichten Dienst

Hilfreiche Erklärungen und Tipps zum Lernen von Autohotkey

Moderator: jNizM

just me
Posts: 9406
Joined: 02 Oct 2013, 08:51
Location: Germany

WND - der Windows Nachrichten Dienst

26 Feb 2014, 03:47

Vorwort

Das Folgende bezieht sich nicht auf die 'geheime' Datensammelei von Microsoft und anderen bekannten 'Global Playern', obwohl es die aktuellen Zustände nahe legen würden. Es soll lediglich eine Beschreibung von internen Abläufen innerhalb des beliebten Betriebssystems 'Microsoft Windows' liefern.
Hinter einigen der im folgenden Text als eingebetteter Code markierten Bereiche verbergen sich weiterführende Links auf die Online-Hilfe oder das MSDN (Microsoft Developer Network). Man erkennt sie daran, dass der Mauszeiger wechselt, wenn sich die Maus darüber befindet, und der Text unterstrichen angezeigt wird.

Allgemeines

Alle Elemente der graphischen Benutzeroberfläche (GUI) werden in Windows durchgängig als Fenster bezeichnet, daher auch der Name (Fenster = windows). Im Gegensatz zu AHK, das die Oberflächenelemente begrifflich in Fenster (Gui) und Controls teilt und dabei den Begriff Fenster normalerweise nur für das/die Hauptfenster der Anwendung verwendet, unterscheidet Windows zwischen Eltern- (parent window) und Kindfenstern (child window). Kindfenster werden mit dem Stil WS_CHILD (0x40000000) gekennzeichnet. Der sorgt u.a. dafür, dass dem Fenster bestimmte Eigenschaften wie z.B. eine Titelleiste oder ein Fenstermenü verweigert werden und das Fenster nur innerhalb des Elternfensters angezeigt werden kann. Als eindeutiges Identitätsmerkmal wird dem Kindfenster bei seiner Erstellung das systemweit eindeutige Handle (HWND) des Elternfensters übergeben.

Fenster werden in Windows und auch in anderen Betriebssystemen über Nachrichten gesteuert. Sobald das System selbst oder auch untergeordnete Elemente eine Veränderung feststellen, die möglicherweise von einem Element der GUI verarbeitet bzw. zur Kenntnis genommen werden muss, wird eine Nachricht generiert, die die Veränderung nach Auffassung von Microsoft ausreichend genau beschreibt, und an das/die betroffene/n Element/e geschickt. Für die Abarbeitung dieser Nachrichten müssen alle Anwendungen mit GUI eine Nachrichtenschleife enthalten, in der in regelmäßig kurzen Abständen geprüft wird, ob eine neue Nachricht eingetroffen ist. Für AHK-Anwendungen wird diese Nachrichtenschleife bequemerweise automatisch vom Interpreter erstellt. Darüber hinaus stellt AHK die Funktion OnMessage() zur Verfügung, die es einer Anwendung ermöglicht, den Nachrichtenverkehr zu belauschen und ggf. anders als die Standardschleife auf diese Nachrichten zu reagieren.

Windows unterscheidet dabei zwischen zwei Nachrichtentypen:
  • Message: Nachricht / Botschaft
    Nachrichten sind in der Regel Handlungsanweisungen, die dem Fenster mitteilen, dass eine bestimmte Reaktion erwartet wird.
  • Notification: Benachrichtigung / Information
    Benachrichtigungen dienen in der Regel der Übermittlung von Informationen. Dabei bleibt es dem Fenster überlassen, ob es auf diese Informationen reagiert oder auch nicht.
Nachrichten werden intern über Nummern gekennzeichnet. Die Namen wie z.B. WM_SETTITLE sind lediglich eine Hilfestellung für Programmierer und können deshalb in AHK nicht direkt als Kennzeichen der Nachrichten verwendet werden. Viele der für AHK brauchbaren Nachrichtennummern finden sich im alten Forum in /board/topic/79703-super-global-gui-constants/. Darüber hinaus gibt es noch viele mehr. Für die meisten davon gibt es Beschreibungen im MSDN, bei einigen ist Microsoft aber anscheinend der Meinung, dass solches Wissen besser geheim bleibt.

Für den Versand der Nachrichten werden hauptsächlich zwei API-Funktionen (API = Application Programming Interface / Programmierschnittstelle für Anwendungen) genutzt:
  1. SendMessage()
  2. PostMessage()
Die Funktionen unterscheiden sich darin, dass SendMessage wartet, bis die Anwendung die Nachricht verarbeitet hat und eine entsprechende Bestätigung zurückgibt, während PostMessage die Nachricht einfach abschickt und nicht wartet. Daraus ergibt sich, dass SendMessage immer dann verwendet werden muss, wenn die Nachricht einen Wert zurückliefern soll.

Beide Funktionen haben vier Parameter:
  • hWnd : Das Handle (in AHK sowohl als Hwnd als auch als ID bezeichnet) des Empfängerfensters.
  • Msg : Die Nummer der Nachricht.
  • wParam : Nachrichtenabhängiger Inhalt.
  • lParam : Nachrichtenabhängiger Inhalt.
Die Namen der letzten beiden Parameter deuten auf die Windows Anfänge als 16-Bit System hin. So steht wohl das w in wParam für den Typ word (ein 16-Bit Wert) und das l in lParam für den Typ long (ein 32-Bit Wert). Heute haben beide Parameter die Größe von Pointern (Zeigern auf Speicheradressen), d.h. 4 Bytes in 32-Bit und 8 Bytes in 64-Bit Anwendungen. Sie können sowohl numerische Werte als auch Pointer aufnehmen. Der tatsächlich benötigte Wert ist im MSDN bei der Beschreibung der jeweiligen Nachricht dokumentiert.

In AHK-Anwendungen muss man die API-Funktionen nicht per DllCall() aufrufen, obwohl das durchaus Vorteile haben kann. AHK hat dafür die Anweisungen mit den Namen (wen wundert's) SendMessage und PostMessage. Ein wichtiger Unterschied im Verhalten der API-Funktionen und der eingebauten Anweisungen besteht darin, dass die Anweisungen den aktuellen Einstellungen für das Erkennen versteckter Fenster (DetectHiddenWindows) unterliegen, während die API-Funktionen die Nachrichten unabhängig davon zustellen. Außerdem kann man bei Aufruf über DllCall() direkt auf den Rückgabewert der Nachricht zugreifen, ohne den Umweg über die interne Variable ErrorLevel gehen zu müssen, in der die Anweisungen den Rückgabewert verpacken.

Für die Parameter von Anweisungen und API-Funktionen gilt folgende Zuordnung:
  • Msg <-> Msg
  • wParam <-> wParam
  • lParam <-> lParam
  • WinTitle <-> hWnd, wenn mit ahk_id %VarContainingID% das Handle des Fensters bzw. des Controls übergeben wird.
Die übrigen zusätzlichen Parameter mit Ausnahme von TimeOut sollen es dem Programmierer erleichtern, Fenster und/oder Controls eindeutig zu identifizieren, falls das Handle nicht bekannt ist. Wenn in WinTitle ein Handle übergeben wird, werden sie nicht gebraucht.

Die Parameter wParam und lParam werden von AHK wie folgt versorgt:
  • Numerische Werte werden unverändert in den Parameter übernommen.
  • Für Zeichenfolgen (strings) wird ein Pointer auf den zugehörigen Speicherbereich erzeugt und in den Parameter gestellt.
Wenn in einem der Parameter ein Pointer erwartet wird, der nicht wie o.a. automatisch erstellt wird, muss ein Variablenname mit vorangestelltem Adressoperator & übergeben werden, d.h. &VarName.

Sonderfall Controls

Die weitaus meisten AHK bzw. Windows Controls informieren ihre Elternfenster bei bestimmten Ereignissen per Benachrichtigung (notification). Dafür werden ausschließlich zwei Nachrichten genutzt: Wenn einem Control ein gLabel zugewiesen ist, ruft AHK diese Routine bei einigen Benachrichtigungen automatisch auf. Die Meisten bleiben aber unter der Decke und werden von der internen Nachrichtenschleife abgearbeitet, sofern AHK das für erforderlich hält. Wenn man sich etwas genauer anschauer will, was die Controls so alles mitzuteilen haben, muss man dafür die Funktion OnMessage() bemühen. Die so zugewiesenen Funktionen erwarten bis zu vier Parameter. Und wenn man sich die Namen anschaut, wird schnell klar, was sie wohl beinhalten: Ja, es sind die vier Parameter der API-Funktionen SendMessage()/PostMessage().

Wenn man sich dazu entschließt, den Nachrichtenverkehr per OnMessage() zu belauschen, muss man sich darüber im Klaren sein, dass alle Controls und dazu auch noch Menüs dieselben Nachrichten WM_COMMAND bzw. WM_NOTIFY als Hülle nutzen. Man kann deshalb nicht direkt eine Benachrichtigung eines Edit Controls wie z.B. EN_SETFOCUS als Nachrichtennummer übergeben, sondern muss stattdessen für diesen Fall die Nummer der Nachricht WM_COMMAND := 0x0111 verwenden. Sobald man das getan hat, sammelt die in OnMessage() angegebene Funktion aber die WM_COMMAND Nachrichten aller Controls ein. Deshalb sind in solchen Funktionen zwei Dinge zu beachten:
  1. Die Funktion muss möglichst schnell abgearbeitet werden, um die Nachrichtenverarbeitung für andere Controls nicht zu blockieren oder schlimmstenfalls ganz zu verhindern.
  2. Die Funktion muss zuallererst klären, ob die Nachricht eine passende Benachrichtigung eines passenden Controls enthält.
WM_COMMAND:

Die Benachrichtigung per WM_COMMAND scheint die ältere der beiden Methoden zu sein. Bei dieser Nachricht enthalten die Parameter wParam und lParam nur recht dürftige Informationen:
  • wParam:
    Dieser Parameter enthält einen Integerwert (4 Bytes), der in zwei Bereiche a 2 Byte aufgeteilt ist.

    Der 'höherwertige' Bereich (high word) enthält
    • 0 : wenn der Absender ein Menü ist
    • 1 : wenn der Auslöser ein Tastaturkürzel ist
    • die Nummer der Benachrichtigung, wenn der Absender ein Control ist (Bingo!)
    Seinen Wert erhält man durch eine logische Und-Verknüpfung und ein Rechtsverschieben um 16 Bits (2 Bytes):

    Code: Select all

    Value := (wParam & 0xFFFF0000) >> 16
    ; oder
    Value := (wParam >> 16) & 0xFFFF
    Der 'niederwertige' Bereich (low word) enthält Informationen, die normalerweise nur für Menüs und Tastaturkürzel interessant sind:
    • für Menüs : die ID des Menüeintrags
    • für Tastaturkürzel : die ID des Kürzels
    Für Controls ist hier die interne auf das Elternfenster bezogene ID des Controls abgelegt, die man in AHK nur sehr selten brauchen kann.
    Seinen Wert erhält man durch eine einfache Und-Verknüpfung:

    Code: Select all

    Value := wParam & 0xFFFF
  • lParam:
    Dieser Parameter ist einfacher zu handhaben. Er enthält für Menüs und Tastaturkürzel nichts (0) und für Controls das Handle (HWND) des Controlfensters und damit genau die Information, die man braucht, um das Control eindeutig zu identifizieren.
WM_NOTIFY:

Die Benachrichtigung per WM_NOTIFY bietet wesentlich mehr Möglichkeiten zur Übergabe von Informationen. Das ist einerseits gut, weil man mit der Benachrichtigung Informationen erhält, die man sonst selbst mühselig einsammeln müsste, andererseits macht es die Auswertung der Informationen etwas komplizierter. Auch hier gibt es nur die bekannten zwei Parameter wParam und lParam:
  • wParam:
    Dieser Parameter enthält die - wie bereits oben gesagt - für AHK recht nutzlose interne ID des sendenden Controls. Sie ist auch noch einmal in lParam enthalten, und Microsoft empfiehlt, sich bei Bedarf lieber auf diese zu verlassen.
  • lParam:
    Dieser Parameter ist ein Pointer auf eine 'eierlegende Wollmilchsau'. Er zeigt auf einen zusammenhängenden Speicherbereich, der beliebige Informationen enthalten kann, und das leider auch oft tut. Weil die Informationen nachrichtenabhängig sind, führt in der Regel kein Weg daran vorbei, erst einmal im MSDN oder anderen Quellen nachzuschlagen, was da geliefert wird. Und weil nur eine Adresse geliefert wird, muss man die Funktionen NumGet() und oder StrGet() nutzen, um die im jeweiligen Einzelfall interessanten Informationen auszulesen.

    Eine Gemeinsamkeit haben die Benachrichrigungen aber dann doch. Der Speicherbereich, dessen Adresse lParam enthält, beginnt immer mit einer NMHDR (notify message header) Struktur mit folgenden Aufbau:

    Code: Select all

    typedef struct tagNMHDR {
      HWND     hwndFrom; <- das systemweit eindeutige Handle des sendenden Controls
      UINT_PTR idFrom;   <- die interne ID des Controls
      UINT     code;     <- die Nummer der Benachrichtigung
    } NMHDR;
    
    Die beiden Informationen, die man benötigt, um das sendende Control und die gesendete Nachricht zu idenfizieren, finden sich damit immer auf denselben Positionen. Um sie auszulesen, braucht es NumGet(). Dazu muss man überlegen, was man hat und wo das steht, was man will.

    Die NumGet() Funktion erwartet 3 Parameter:
    1. VarOrAddress : den Namen einer Variablen oder eine Adresse
    2. Offset : die Distanz zwischen dem Beginn des Speicherbereichs und dem gesuchten Wert
    3. Type : der Datentyp - er ist entscheidend dafür, in welcher Länge die / wieviele Bytes an Daten ausgelesen werden
    Haben:
    Wir haben in der Variablen lParam die Adresse des Speicherbereichs. Wenn man nun aber einfach NumGet(lParam, ..., ...) aufruft, wird man sich wundern, was da zurückkommt. Das liegt daran, das die Funktion für den Zugriff in AHK 1.1 die Adresse der übergebenen Variablen nutzt, und nicht ihren Inhalt (Achtung: Das wird sich mit v2 ändern!). An der Adresse der Variablen sind die Informationen aber nicht zu finden. Die Lösung besteht darin, die Adresse in Form des Ausdrucks lParam + 0 zu übergeben, d.h.Numget(lParam + 0, ..., ...), und schon liegen wir richtig.

    Wollen:
    Wollen wollen wir zuerst einmal das Handle des Controls und die Nachrichtennummer. Dafür brauchen wir deren Offset und Type.
    Das Handle liegt als erster Wert am Anfang des Speicherbereichs, Offset ist also 0. Ein Adresszeiger ist ein Pointer. An sich bräuchten wir deshalb Type nicht anzugeben, weil das der Standardrückgabetyp ist. Falls sich das wieder einmal ändert und weil es deutlich macht, dass man sich über den Typ im Klaren ist, setze ich allerdings auch in diesem Fall den Typ UPtr ein.
    Die Nachrichtennummer ist der dritte Wert. Die beiden ersten Werte sind Pointer. Die Länge von Pointern ist in 32-Bit und 64-Bit Anwendungen unterschiedlich. Deshalb gibt es die interne Variable A_PtrSize, die die aktuelle Länge enthält (4 Bytes für 32-Bit, 8 Bytes für 64-Bit). Der Offset beträgt damit zwei Pointerlängen, oder anders: 2 * A_PtrSize. Type ist eindeutig UInt. Wenn man mit negativen Nachrichtennummern arbeiten muss (die gibt es wirklich), kann es aber bequemer sein, hier Int zu benutzen.

    Damit haben wir Alles, was gebraucht wird, um das Handle und die Nachrichtennummer auszulesen:

    Code: Select all

    MeineOnMessageFunktion(wParam, lParam) {
       Static DieNachrichtenNummer := 0xABCD
       Handle := NumGet(lParam + 0, 0, "UPtr")
       Msg := NumGet(lParam + 0, 2 * A_PtrSize, "UInt") ; oder "Int"
       If (Handle = MeinControlHandle) && (Msg = DieNachrichtenNummer) {
          ...
       }
    
    Diese Funktion zeigt aber ein weiteres Problem. Die Handles der Controls werden in der Regel außerhalb der Funktion versorgt. Auf die Variable MeinControlHandle kann deshalb in der Funktion nicht ohne weiteres zugegriffen werden, weil alle Variablen in Funktionen per se funktionslokale Variablen sind. Als Abhilfe kann man alle Variablen innerhalb der Funktion per Global als global erklären, mit Global MeinControlHandle die spezielle Variable innerhalb der Funktion als global erklären oder die Variable außerhalb der Funktion per Global MeinControlHandle zu einer super-globalen machen, auf die in allen Funktionen zugegriffen werden kann.

    Die weiteren möglichen Inhalte des per lParam addressierten Speicherbereichs müssen hier leider ausgeklammert werden. Zum Einen sind sie nachrichtenabhängig, zum Anderen erfordert der Zugriff auf diese Bereiche einiges Wissen über die Windows Datentypen und vor allem -strukturen. Das sollte aber besser in einem eigenen Tutorial untergebracht werden.

    Einen Hinweis kann ich aber trotzdem geben. Der Offset des ersten auf die Struktur NMHDR folgenden Feldes ist zwar - wie eigentlich rechnerisch logisch - in 32-Bit Anwendungen 12 = (2 * A_PtrSize) + 4, in 64-Bit Anwendungen aber nicht 20 = (2 * A_PtrSize) + 4. Das liegt an der Ausrichtung von Strukturen. Wenn man ein in beiden Versionen lauffähiges Skript schreiben will, muss man deshalb als Offset des ersten Feldes 3 * A_PtrSize verwenden.
Fazit

Die Arbeit mit Nachrichten, sei es per SendMessage oder OnMessage() Funktionen ist keine unlösbare Aufgabe, erfordert aber einigen Aufwand. Die AHK-Hilfe hilft da nur in einigen wenigen Fällen, normalerweise muss man andere Informationsquellen finden, die Erklärungen für die im Einzelfall benötigten Daten liefern. Für viele dieser Daten muss man Variablen passender Größe mit VarSetCapacity() selbst einrichten, per NumPut()/StrPut() befüllen, und die Informationen im Erfolgsfall mit NumGet()/StrGet() auslesen. Der Mühe Lohn sind Steuerungsmöglichkeiten, die ohne die Nachrichten eben nicht möglich sind.

Auch für die Kennzeichnung des Absenders oder Empfängers von Nachrichten nutzt Windows nahezu ausschließlich die Handle (HWNDs) der Fenster/Controls. Es ist deshalb naheliegend, sie für die eigene Nachrichtenverarbeitung ebenfalls zu nutzen, indem man die hwnd Option der Gui-Anweisungen nutzt. Das Handle kann auch in Gui-Anweisungen anstelle des Namens verwendet werden. Statt den Namen einfach als Text zu übergeben, muss man das Handle allerdings mit %MeinControlHandle% referenzieren.
Last edited by just me on 02 Mar 2014, 02:14, edited 1 time in total.
just me
Posts: 9406
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: WND - der Windows Nachrichten Dienst

27 Feb 2014, 10:15

Das war's erst einmal. Korrekturen, Anregungen und Fragen sind erwünscht!
User avatar
nnnik
Posts: 4500
Joined: 30 Sep 2013, 01:01
Location: Germany

Re: WND - der Windows Nachrichten Dienst

27 Feb 2014, 10:52

Wir könnten ja Die wichtigsten System Messages + Beschreibung sammeln.
BTW UPtr existiert wie UInt64 nicht.
Recommends AHK Studio
just me
Posts: 9406
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: WND - der Windows Nachrichten Dienst

27 Feb 2014, 11:44

Die Hilfe wrote:NumGet(VarOrAddress [, Offset = 0][, Type = "UPtr"]) [v1.0.47+]

DllCall(): UPtr is also valid, but is only unsigned in 32-bit builds as AutoHotkey does not support unsigned 64-bit integers.
;)
User avatar
nnnik
Posts: 4500
Joined: 30 Sep 2013, 01:01
Location: Germany

Re: WND - der Windows Nachrichten Dienst

27 Feb 2014, 15:14

OK mein Fehler.
Recommends AHK Studio
just me
Posts: 9406
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: WND - der Windows Nachrichten Dienst

28 Feb 2014, 05:26

nnnik wrote:Wir könnten ja Die wichtigsten System Messages + Beschreibung sammeln.
Ein paar Beipiele wären sicher nicht verkehrt, was fällt Dir denn da so ein?
User avatar
nnnik
Posts: 4500
Joined: 30 Sep 2013, 01:01
Location: Germany

Re: WND - der Windows Nachrichten Dienst

28 Feb 2014, 07:59

Ich Stelle mir eher eine Sammlung wie bei der ComObj reference vor.
Erwähnenswert sind natürlich (NC sowie C) Mousemove Mousehover Lbuttondown Lbuttonup Keydown keyup paint.
Recommends AHK Studio

Return to “Tutorials”

Who is online

Users browsing this forum: No registered users and 12 guests