Frage zu (EN-) "FAQ # 24 How can I get a window handle when all I have is a PID?"

  • PID ... mir schwirrt der Kopf! :S

    Vorgeschichte

    Ich brauchte das HWND zu einem bestimmten Fenster, hatte aber nur eine PID. Im Netz gibt es viele (kontroverse) Code-Snippets á la "HwndFromPid". Nun ja, eine PID hatte ich ja (scheinbar!), denn

    "Run" gibt die PID des Prozesses zurück, der gestartet wurde.

    Hat aber nicht funktioniert, bis ich herausgefunden habe, dass es mehrere PIDs gibt, wenn beim Aufrufen per "Run" das zu startende Programm schon läuft. (Korrigiert mich, wenn das nicht stimmt!) In meinem Fall war das Prog schon gestartet (Anzeigen der Hilfe-Datei). Es sollte mit jedem weiteren Aufruf weitere Suchbegriffe geöffnet werden. Die von "Run" zurückgegebene PID war dann nutzlos.

    Ich habe das Probelm gelöst, indem ich mithilfe von WinList() das entsprechende PID gesucht habe. Mit dem richtigen PID konnte ich auch das entsprechende HWND ermitteln.

    Thema

    Bei meinen Recherchen habe ich im einen Code in den FAQ des EN Forums gefunden: "How can I get a window handle when all I have is a PID?" Dort gibt es am Ende des Abschnitts folgenden Code:

    Meine Frage ist, wozu ist die Do-Until Schleife da? Wahrscheinlich stehe ich auf dem Schlauch, aber wird da nicht der immergleiche Code ausgeführt, der zu immergleichen Ergebnis führt?

    Am Anfang wird das Array $winlist erstellt, es folgt die äußere Do-Until Schleife. Darin läuft die innere For-Next Schleife, die das Array durchläuft von 1 bis "gefunden" oder UBound. Die Do-Until Schleife wird mit Until $hWnd <> 0 abgebrochen, wenn das HWND gefunden wurde. Daraus ergeben sich zwei Möglichkeiten.

    1. Das HWND wird gefunden => alle Schleifen werden abgebrochen, Ergebnis returniert.

    2. Das HWND wird NICHT gefunden => die Do-Unti Schleife läuft bis zum jüngsten Tag.

    Wenn das HWND gefunden werden kann, würde das doch beim ersten Durchlauf der For-Next Schleife passieren, oder nicht? Und wenn das HWND beim ersten Durchlauf NICHT gefunden wird, wieso sollte es dann beim zweiten oder 1.001-ten Durchlauf gefunden werden?

    Hab ich da einen Denkfehler?

    Wenn jemand sagt: "Das geht nicht!" Denke daran: Das sind seine Grenzen, nicht deine.

  • Wenn du nen Prozess startest muss er nicht direkt das Fenster öffnen. Du könntest ja auch ein AutoIT-Script schreiben, nen Sleep(50000) nach ganz oben setzen und es dauert 50 Sekunden bis dein Fenster weiter unten erstellt wird.

    Deshalb kann es passieren, dass, je nachdem, was der Prozess vor dem erstellen des Fensters alles macht, deine Abfrage nach dem windowhandle läuft, bevor das Fenster erstellt wurde.

    Das ist ein typisches Problem, was bei paralleler Ausführung von Code passieren kann (Bei Multithreading,... muss man oft auf solche Dinge aufpassen, wobei man das Problem in AutoIT ja normal nicht bekommt).

    Bisher hattest du also nur immer das Glück, dass der gestartete Prozess das Fenster schneller erzeugt hat, als du es in AutoIT abgefragt hast.

  • Typisch Test-Szenarios: Auf manche Umstände kommt man nicht. X/

    Gerade habe ich meinen Code ausprobiert. Er ruft die "AutoIt3Help.exe" auf und übergibt dabei einen Suchbegriff. Damit wird dann wiederum die "AutoIt.chm" aufgerufen. Solange die chm geöffnet ist, bleibt auch die "AutoIt3Help.exe" geöffnet. In meinen Test-Szenario habe ich die chm nicht geschlossen, deshalb ist mir das auch nie aufgefallen.

    Ergebnis

    Für den Aufruf von "GetHlpWinHandle()" (ähnlich dem GetHwndFromPID()" ) hat sich gezeigt:

    - Sind exe und chm geöffnet, funtioniert der Aufruf, das HWND zum Hilfe-Fenster wird gefunden.

    - Sind exe und chm NICHT geöffnet, versagt der Aufruf, es wird kein Handle gefunden.

    - Mit WinWait() funktioniert es dann auch, wenn exe und chm beim Aufruf NICHT geöffnet sind.

    WinWait scheint also zu funktionieren. Kann es damit Probleme geben und wäre die Do-Until Schleife die bessere Wahl?

    Wenn jemand sagt: "Das geht nicht!" Denke daran: Das sind seine Grenzen, nicht deine.

    4 Mal editiert, zuletzt von Professor Bernd (23. Oktober 2019 um 20:57)

  • Naja, für das WinWait brauchst du ja dann den Handle, den du eigentlich von der Funktion bekommen möchtest, oder du kannst gleich komplett auf das GetHwndFromPID verzichten und warten, bis ein Fenster mit dem Titel existiert. Wobei WinWait im Hintergrund vermutlich ebenfalls die WinList-Funktion benutzt und nach Titeln durchsucht.

    Dort hast du dann die gleiche Schleife, wie in dem Code oben. Wobei die WinWait nen optionalen Timeout dabei hat um abzubrechen. Den kannst du bei der GetHwndFromPID natürlich auch hinzufügen.

    Der einzige Unterschied zwischen den beiden Methoden ist in diesem Fall eigentlich, dass bei WinWait der Titel/Handle des Fensters bekannt ist und du auf diese Weise darauf wartest, bis es erstellt wurde. (Wobei der Handle hier keinen Sinn macht... Wenn du den Handle kennst, existiert es ja definitiv schon.)

    Bei der GetHwndFromPID kennst du die Prozess-ID und möchtest damit ein Fenster bekommen, dass von dem Prozess erstellt wurde.

    Somit brauchst du den Titel nicht und kannst auch nicht ausversehen mit dem Titel das Fenster eines anderen Prozesses abfragen. Fenstertitel sind ja kein eindeutiges Erkennungsmerkmal. Es könnte ja immer ein anderes Fenster mit dem Titel existieren oder erstellt werden. Wenn du auf die Prozess-ID schaust, kannst du dir zumindest sicher sein, dass du kein "zufälliges" anderes Fenster änderst, was nichts mit dem von dir gestarteten Prozess zu tun hat.

    Natürlich solltest du überprüfen, dass der Prozess auch wirklich das von dir gewollte Fenster gestartet hat. Es könnte ja auch passieren, dass du den Prozess startest und der ne Fehlermeldung anzeigt. Da würdest du dann mit dem GetHwndFromPID das Handle des Fehlermeldungsfensters bekommen.

    Du kannst also mit nem WinWait warten, bis ein Fenster mit dem gewollten Titel existiert und prüfst dann, ob der Prozess dazu passt (WinGetProcess), könntest aber auch gleich ne Schleife bauen über WinGetProcess("TITEL"), solange dort -1 zurückkommt wartest du halt (ggf. mit Timeout).

    Du kannst aber auch die GetHwndFromPID nutzen und prüfst dann, dass der Titel passt, oder falls du den nicht kennst, ob die Controls die du steuern willst auch in dem zurückgegebenen Fenster existieren,... ggf. erstellt dein Prozess ja mehrere Fenster, die du mit WinList holen könntest (also so umbauen, dass du nicht nur einen Windowhandle, sondern nen Array von WindowHandles bekommst)m...

    Also du solltest auf jedenfall überprüfen, dass du das richtige Fenster bekommst. Welche der Methoden, die ich oben genannt habe, du nutzt hängt von den Infos ab, die du hast und wie genau du das gestartete Programm kennst,... es gibt auch noch andere Wege, aber die gibt es ja immer ;)

  • Eben war ich so in Gedanken, dass ich die Höflichkeit vergessen habe. Ich glaube, wir hatten noch nicht die Ehre, deshalb hier ein um so freundlicheres "Hallo Kanashius." und vielen Dank für die bisherigen Tipps!:)

    Eigentlich ist die ursprüngliche Frage des Threads beantwortet: Die äußere Do-Until Schleife hat also doch einen Sinn, denn sie ist zuständig für das Warten auf das gewünschte Fenster bzw. Prozess.

    Aber ohne deinen Tipp hätte ich vielleicht gar nicht bemerkt, dass das Warten wichtig ist. Wahrscheinlich hätte meine Routine die meiste Zeit funktioniert, weil man das Hilfe-Fenster oft geöffnet lässt, und ich hätte mich gewundert, warum es nicht funktioniert, wenn das HIlfe-Fenster noch nicht gestartet ist. Das hätte eine Sucherei ergeben, die du mir erspart hast! :thumbup:

    Nun will ich den konkreten Fall nicht vorenthalten, ohne zu sehr in (langweilige) Details zu gehen:

    In lang: Meine Routine "CallAutoIt3HelpExe" wird von PSPad (Editor) aufgerufen und startet per "Run" die "AutoIt3Help.exe" und übergibt ihr dabei einen Suchbegriff. Damit wird die AutoIt.chm geöffnet und die Infos zum Suchbegriff angezeigt. Leider kommt es manchmal vor, dass sich das Hilfe-Fenster hinter das aufrufende schiebt. Deshalb wird das Handle des Hilfe-Fensters gesucht und _WinAPI_SetForegroundWindow() durchgeführt.

    In kurz: PSPad startet "CallAutoIt3HelpExe" => startet die "AutoIt3Help.exe" => startet die "AutoIt.chm" => Handle suchen => SetForeground...

    Die beiden Fenster, um die es geht, sind "AutoIt3Help.exe" und "AutoIt.chm" (Hilfe-Fenster).

    Leider habe ich von der "AutoIt3Help.exe" kein Source-Code gefunden, aber den Titel des unsichtbaren Fensters konnte ich mithilfe von WinList() auslesen: "AutoIt3Help". Also habe ich den Titel des Parent-Fensters (Prozesses?*). Das hat als Child-Fenster/Prozess die AutoIt-Hilfe. (*Leider kann ich gerade nicht ganz zuordnen, ob es sich um Fenster und/oder Prozesse handelt.)

    Das Hilfe-Fenster kann ich nicht zuverlässig per Win-Titel ausfindig machen, da er sich je nach verwendeter Sprache ändert (DE, EN, FR, IT, ...) . Um also sprachunabhängig zu sein, habe ich "unveränderliche" Kriterien gesucht. Ob sie sich wirklich nicht verändern, kann ich nur annehmen. Dazu gehören folgende spekulative Annahmen:

    - Der Titel der "AutoIt3Help.exe" scheint sich nicht zu verändern.

    - Die "AutoIt3Help.exe" hat nur 1 Child.

    - Das Hilfe-Fenster ist das Child davon. Titel verändert sich je nach Sprache und Datei-Version.

    - Sowohl die "AutoIt3Help.exe", also auch das Hilfe-Fenster werden NICHT mehrfach ausgeführt!

    Laut WinList() (und Taskmanager) gehören beide zum selben Prozess (PID ist bei beiden gleich). Somit habe ich einen Titel, um die PID der "AutoIt3Help.exe" zu ermitteln, und damit dann das HWND des Hilfe-Fensters. WinList() durchlaufen => if (PID gleich der vom Parent) and not (Titel gleich dem vom Parent) then => Child gefunden. ;)

    Nachdem ich nun WinWait("AutoIt3Help", "", 4) eingebaut habe, scheint alles zu funktionieren. Verbesserungsvorschläge willkommen.

    Wenn jemand sagt: "Das geht nicht!" Denke daran: Das sind seine Grenzen, nicht deine.

  • Hallo Bernd :)


    Ich bin im moment eher lesend aktiv und schau mir fast jeden Thread an, schreibe aber nicht sehr viel, hauptsächlich, weil ich meistens zu spät bin und schon fast alles wichtige gesagt wurde :D

    _WinAPI_GetParentProcess ( [$iPID = 0] ) zusammen mit ProcessList gibt dir die Möglichkeit, alle Prozesse zu finden, die ein Childprozess von dem sind, den du gestartet hast. Vielleicht kannst du das in Zukunft mal benötigen.

    Generell ist es sehr Sinnvoll, bei diesem Themenbereich mal einen Blick in die WinAPI zu werfen. Sehr viele Funktionalitäten, die nicht mit direkten AutoIT-Aufrufen umgesetzt werden können finden sich dort wieder. Halt alle, die Windows so anbietet :)

    MfG Kanashius

  • Guter Tipp! :)

    _WinAPI_GetParentProcess zusammen mit ProcessList habe ich mir gerade angesehen und ein wenig getestet. Für mein oben beschriebenes Thema konnte ich nicht direkt eine Verwendung finden, aber es ist mir gleich ein anderes Thema eingefallen, wo das helfen könnte.

    Im Moment habe ich nur ein wenig mit dem Beispiel aus der Hilfe zu ProcessList() gespielt. Kannst du vielleicht ein besseres kleines Beispiel posten, damit ich später den Zusammenhang schneller wieder finde?

    Gruß,

    Bernd.

    Wenn jemand sagt: "Das geht nicht!" Denke daran: Das sind seine Grenzen, nicht deine.