Unterschied Application vs. Mainform bei Botschaften

    Unterschied Application vs. Mainform bei Botschaften

    Hallöchen,

    folgendes reizvolles Problem:
    Ich schicke meiner Anwendung auf zweierlei Wegen Botschaften.
    Einmal mit Postmessage(Application.HANDLE, ...) , das wird in Applicationevents verarbeitet und kommt an.
    Zweitens aus einem Thread mit Sendmessage(Application.Handle, ...) an eine

    Delphi-Code

    1. procedure OnRotliche(var Messi:TMessage); message WM_ROTLICHT;
    ,
    kommt NICHT an.
    Schicke ich die Message aber an MainForm.Handle, kommt sie an und OnRotlicht() wird ausgeführt.
    Zum praktischen Erfolg hätte ich nun gerne eine Erklärung, wann welches Ziel notwendig ist und warum.
    (Nur so zur Beruhigung B) )

    ism
    Morgen ist Heute schon Gestern
    Zur Erklärung, wie ein Delphi-Programm aufgebaut ist (und in jeder Programmiersprache ist das ähnlich):
    • Im Hauptprogramm wird Application.Run ausgeführt. Da passieren ein paar vorbereitende Sachen, dass die MainForm angezeigt wird. Im Anschluss begibt sich das Programm in eine (fast)Endlosschleife, die Folgendes tut:

      Delphi-Code

      1. repeat
      2. try
      3. HandleMessage;
      4. except
      5. HandleException(Self);
      6. end;
      7. until Terminated;

      Sprich, so lange wie die Anwendung nicht beendet wird, wird einfach nur HandleMessage aufgerufen, d.h. auf Botschaften des Betriebssystems gewartet.
    • Was tut also HandleMessage? Nur eine einzige Sache:

      Delphi-Code

      1. if not ProcessMessage(Msg) then Idle(Msg);

      Es schaut, ob es eine Nachricht zu verarbeiten gibt; wenn nicht, wird die Idle-Prozedur aufgerufen. Die kann z.B. über das TApplication.OnIdle abgefragt und so die "Ruhezeit" für irgend etwas genutzt werden.
    • ProcessMessage ist die wesentliche Routine. Sie fragt mittels PeekMessage die nächste Nachricht in der Warteschlange ab. Hierbei findet keine Filterung statt, d.h. alle Messages, die an irgendein Handle gehen, was zum aktuellen Thread gehört, werden hier früher oder später landen.
    • Nachdem diese Nachricht abgefragt wurde, wird geschaut, ob TApplication.OnMessage zugewiesen ist und dieses (außer für WM_QUIT) zunächst ausgeführt.
    • Falls dort die Nachricht nicht abgefangen wird (Handled-Parameter) oder sie zu einer speziellen Gruppe von Messages gehört, wird sie im Anschluss weitergegeben (DispatchMessage). Das läuft entsprechend dem Handle dieses Controls ab.

    Fazit 1: TApplication.OnMessage ist der erste Einstiegspunkt für jede Nachricht. Jede Nachricht, die über das Nachrichtensystem von Windows übermittelt wird (PostMessage/SendMessage), kann in TApplication.OnMessage abgefangen werden. [Es gibt ja die Möglichkeit, direkt mit <Komponente>.Perform() eine "Nachricht" zu senden. Das läuft nicht über das Windows-Messaging-System und kann deshalb hier nicht abgefragt werden.

    Um nun zu verstehen, was mit diesen Message-Parametern geschieht und was "entsprechend dem Handle" bedeutet, müssen wir uns mit TWinControl beschäftigen. Wenn ich aus dem Quellcode zitiere, mache ich das für Delphi 7. Im Wesentlichen ändert sich da in späteren Versionen nichts, aber es gibt zusätzliche Wrapper-Klassen und andere für hier irrelevante Dinge.
    • Jede Komponente, die von Windows als eine eigenständige Einheit wahrgenommen werden soll, erbt von TWinControl oder aber sie implementiert zu mindest Auszüge dessen, was ich jetzt beschreibe, manuell. TWinControl macht das eben automatisch und auf ziemlich komplexe Art und Weise.
    • Ein WinControl (auch TForm erbt von TWinControl) besitzt die Eigenschaft Handle. Dieses Handle wird noch nicht im Konstruktor erzeugt, sondern erst bei Bedarf mittels TWinControl.CreateWnd. Insbesondere ist es aber vorhanden, sobald du mit <Komponente>.Handle darauf zugreifst.
    • CreateWnd ruft zunächst mal CreateParams auf. Das ist so eine Art "zweiter Konstruktor", bei dem man eben Dinge für das zu erstellende Handle vorgeben kann. Außerdem wird die Festerprozedur (WndProc) für dieses Handle gesetzt. Das ist die Prozedur, die ausgeführt wird, sobald eine Nachricht an dieses Handle weitergereicht wird und erklärt damit, was ich oben mit "entsprechend dem Handle" gemeint habe: Die für dieses Handle registierte Prozedur wird aufgerufen.
    • Welche Prozedur ist das? DefWindowProc. Diese Prozedur schaut nach, was für eine Klasse diesem Handle zugeordnet ist. (Die Delphi-Klassen werden beim ersten Aufruf in CreateWnd mittels RegisterClass für Windows bekannt gemacht. Insbesondere, welche WndProc zu der Klasse gehört.) Dann wird die WndProc, die dieser Klasse zugeordnet ist, mit der Message aufgerufen.
    • Die der Klasse zugeordnete WndProc ist anfangs Controls.InitWndProc. Diese Prozedur wiederum macht ein paar grundlegende Operationen und ändert dann die zugewiesene Fensterprozedur auf Classes.StdWndProc, welche dann auch gleich aufgerufen wird.
    • StdWndProc ruft dann TWinControl.MainWndProc auf. Der einzige Grund für die Existenz dieser Prozedur ist, dass DefWindowProc eine Windows-API-Methode ist und als solche jeden Prozedurenpointer mit der StdCall-Aufrufkonvention aufruft. Hingegen arbeitet Delphi mit der Register-Konvention und StdWndProc tut nichts weiter, als diese beiden Konventionen ineinander umzurechnen.
    • Wir nähern uns dem Entscheidenden. In MainWndProc wird (nebst ein paar Speicherschutzblöcken) die Methode WindowProc aufgerufen. WindowProc ist aber eine Eigenschaft von TControl und wird daher im Allgemeinen für jede Komponente eine andere sein. Auf diese Ebene wird also schlussendlich entschieden, welche komponentenspezifische Reaktion auf die Message zu erfolgen hat. Und wenn du den direkten Weg über TControl.Perform gehst, wird einfach direkt WindowProc aufgerufen, ohne diesen ganzen Vorbau.
    • Ich habe gerade gesagt, WindowProc ist eine Eigenschaft (ein Prozedurenpointer); standardmäßig wird diese Eigenschaft der Funktion "WndProc" zugewiesen, welche virtuell ist und von jeder Klasse überschrieben werden kann. Das ist der übliche Weg, eine eigene Fensterprozedur zu definieren, nicht die Eigenschaft WindowProc zu verändern. [Vorteil der Eigenschaft ist, dass es schneller ist: Der Compiler muss nur an einer definierten Stelle nachschauen, was in WindowProc steht und nicht bei jedem Aufruf durch die VMT durchblättern und schauen, auf welcher Ebene die Funktion jetzt implementiert wurde. Das ist nur einmal nötig, beim Standardinitialisieren in TControl.Create.]
    • Die WndProc der Komponente macht nun irgendetwas, um auf die spezifische Nachricht zu reagieren oder auch nicht. Am Ende wird auf jeden Fall inherited aufgerufen; die höchste Ebene ist schließlich TControl.WndProc. Und diese Prozedur ruft ganz am Ende TObject.Dispatch mit der Message auf.
    • TObject.Dispatch ist eine Assembler-Routine, die aus der VMT der Klasse ausliest, ob es zu der Message eine entsprechende Prozedur gibt, welche mit dem Schlüsselwort "message" und der passenden ID definiert wurde. Wenn dem so ist, dann wird endlich deine Routine OnRotlicht ausgeführt.

    Fazit 2: Die Message-Verarbeitung ist ziemlich kompliziert aufgebaut, um Kompatibilität zwischen dem Delphi-Klassensystem und dem Windows-Messaging zu ermöglichen.
    Fazit 3: Wenn du in deiner TForm eine Nachricht über eine "message"-Funktion abfängst, klappt das nur, wenn die Message auch explizit an genau diese Form gerichtet war. Sonst nicht. Hingegen fängt TApplication.OnMessage alles ab.

    Schließlich noch der Unterschied SendMessage/PostMessage:
    • SendMessage sendet die Nachricht ab, wartet, bis sie verarbeitet wurde und liefert dann als Rückgabewert das, was in Msg.Result geschrieben wurde. Ist also hervorragend zur gegenseitigen Kommunikation geeignet, allerdings ist es langsamer, falls man gar nicht an einem Warten interessiert ist.
    • PostMessage schickt die Nachricht ab und kehrt sofort zurück. Wann sie verarbeitet wird, ist völlig egal. Entsprechend gibt es auch keinen Rückgabewert. Die Nachricht landet einfach in der Messaging-Queue. Dementsprechend ist es mit PostMessage auch möglich, die Queue zu überfüllen: Sende einfach mal zehntausend Nachrichten unmittelbar hintereinander ab. Das ist sozusagen mein Beitrag zu Silvester ;).
    Master of the EDH ;)