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: