Anwendungen starten und ihr Ende abwarten
Veröffentlicht: 18. Jul 2000 | Aktualisiert: 16. Jun 2004
Von Mathias Schiffer
Werden aus einem VB-Programm heraus andere Anwendungen gestartet, so ist es nicht selten, dass diese eine Aufgabe für das eigene Programm erledigen sollen. Dabei entsteht oft die Notwendigkeit, darauf zu warten, dass das gestartete Fremdprogramm sich beendet hat, bevor die eigene Anwendung weiter ablaufen soll.
Visual Basic selber bietet hierfür keine umfassende Lösungsmöglichkeit an, da die Shell-Funktion ausschließlich asynchron arbeitet, also sofort nach dem Start des Fremdprogramms die Kontrolle wieder an das eigene Programm übergibt. Ein Ausweg aus dieser Situation findet sich allerdings durch einen Ersatz der Shell-Funktion durch eine Routine, die die API-Funktion CreateProcess verwendet. Bei Verwendung von CreateProcess erhält man nicht nur (wie bei der Shell-Funktion) die Prozess-ID der aufgerufenen Anwendung, sondern neben einigen weiteren Informationen ein Handle auf den Prozess. Für die Nutzung dieser Vorgehensweise sind jedoch zunächst umfangreiche API-Deklarationen notwendig:
' Notwendige Typdeklarationen Private Type STARTUPINFO cb As Long lpReserved As String lpDesktop As String lpTitle As String dwX As Long dwY As Long dwXSize As Long dwYSize As Long dwXCountChars As Long dwYCountChars As Long dwFillAttribute As Long dwFlags As Long wShowWindow As Integer cbReserved2 As Integer lpReserved2 As Long hStdInput As Long hStdOutput As Long hStdError As Long End Type Private Type PROCESS_INFORMATION hProcess As Long hThread As Long dwProcessId As Long dwThreadID As Long End Type ' Deklaration notwendiger API-Funktionen: Private Declare Function CreateProcess Lib "kernel32" Alias _ "CreateProcessA" (ByVal lpApplicationName As Long, _ ByVal lpCommandLine As String, ByVal lpProcessAttributes As Long, _ ByVal lpThreadAttributes As Long, ByVal bInheritHandles As Long, _ ByVal dwCreationFlags As Long, ByVal lpEnvironment As Long, _ ByVal lpCurrentDirectory As Long, lpStartupInfo As STARTUPINFO, _ lpProcessInformation As PROCESS_INFORMATION) As Long Private Declare Function WaitForSingleObject Lib "kernel32" (ByVal _ hHandle As Long, ByVal dwMilliseconds As Long) As Long Private Declare Function CloseHandle Lib "kernel32" _ (ByVal hObject As Long) As Long Private Declare Function GetExitCodeProcess Lib "kernel32" _ (ByVal hProcess As Long, lpExitCode As Long) As Long Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, _ ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long ' Deklaration notwendiger Konstanter: Private Const NORMAL_PRIORITY_CLASS = &H20& Private Const INFINITE = -1& Private Const WAIT_TIMEOUT = &H102 Private Const STARTF_USESHOWWINDOW = &H1
Um mit CreateProcess eine Anwendung nach unseren Wünschen starten zu können, sind zwei Variablen des Typs PROCESS_INFORMATION und des Typs STARTUPINFO notwendig. In der STARTUPINFO-Struktur werden allerlei Details zu Oberfläche und Kontext der zu startenden Anwendung festgelegt. Im direkten Vergleich mit der VB-Funktion Shell betrachten wir hier nur die Möglichkeit, einen initialen Fensterstil festzulegen:
Dim ProcInfo As PROCESS_INFORMATION, StartInfo As STARTUPINFO With StartInfo .cb = Len(StartInfo) ' Größe der Struktur übergeben .dwFlags = STARTF_USESHOWWINDOW ' Angeben, dass .wShowWindow benutzt wird .wShowWindow = WindowStyle ' Fensterstil setzen End With
Die PROCESS_INFO-Struktur enthält nach dem Aufruf von CreateProcess die Handles und IDs für Prozess und ersten Thread der dann gestarteten Anwendung, so dass wir sie nicht vorbelegen müssen. Ist der Aufruf der Funktion erfolgreich, liefert CreateProcess einen Wert <> 0 zurück:
If CreateProcess(0&, "notepad.exe", 0&, 0&, 1&, _ NORMAL_PRIORITY_CLASS, 0&, 0&, StartInfo, ProcInfo) = 0 Then _ MsgBox "CreateProcess konnte Notepad.exe nicht ausführen."
Nachdem die Anwendung gestartet wurde, erhalten wir aus PROCESS_INFO im Parameter hProcess ein Handle auf ihren Prozess. Dies können wir in der API-Funktion WaitForSingleObject nutzen, um darauf zu warten, dass der zugehörige Prozess beendet wird. Als zweiten Parameter können wir die Zeitspanne (in Millisekunden) angeben, die maximal auf dieses Ereignis gewartet werden soll. Durch die Angabe der Konstanten INFINITE lässt sich dabei festlegen, dass diese Zeitspanne bis zum Eintritt des Ereignisses andauern soll:
WaitForSingleObject ProcInfo.hProcess, INFINITE
Durch diesen Aufruf wird der gesamte aufrufende Thread blockiert, bis der Prozess des gestarteten Programms beendet wurde. Dies muss nicht immer vorteilhaft sein – beispielsweise ist es bei einer solchen Vollblockade auch nicht mehr möglich, zwischenzeitlich verdeckte Fensterbereiche wieder auf den Bildschirm zu zeichnen. Deswegen sollte diese Blockade in einem solchen Fall in einem eigenen Thread ablaufen. Nicht ganz so elegant wie eine Multithreading-Lösung, dafür aber deutlich einfacher zu realisieren ist eine Schleife, die dem eigenen Thread genügend Luft zumindest für so elementare Dinge wie das Zeichnen seiner grafischen Oberfläche ermöglicht: Hierfür geben wir als Zeitspanne 0 Millisekunden an, so dass WaitForSingleObject sofort zurückkehrt. Ob die Fremdanwendung noch läuft erkennen wir dann daran, dass der Rückgabewert der Funktion WAIT_TIMEOUT ist:
While WaitForSingleObject(ProcInfo.hProcess, 0) = WAIT_TIMEOUT DoEvents ' Zeitscheibe fortführen, danach Kontrolle an Windows Wend
Weitere Informationen zum dargestellten Lösungsansatz finden Sie in der MSDN Library:
- "HOWTO: 32-Bit App Can Determine When a Shelled Process Ends’
Visual Basic Knowledgebase, Article ID: Q129796
https://support.microsoft.com/support/kb/articles/Q129/7/96.asp