Code-Beispiel
Externes Programm starten und wieder beenden [Windows]
Der folgende Code demonstriert, wie sich unter Windows eine externe Anwendung zunächst aufrufen und anschließend wieder beenden lässt. Beim Beenden wird zunächst versucht, die Zielanwendung "sanft" zu beenden (entspricht einem Klick auf das [X] in der Titelleiste). Sind nicht gespeicherte Daten vorhanden, wird so eventuell ein Nachfragedialog wie "Möchten Sie die Änderungen vor dem Beenden speichern?" angezeigt. Nach einer bestimmten Wartezeit wird das Zielprogramm schließlich abgeschossen. Nicht gespeicherte Änderungen in dem Programm gehen dabei verloren.
Beachte folgende zwei Unzulänglichkeiten:
- Anhand eines Beispiels: Wird ein neues Fenster des Browsers Google Chrome geöffnet (anstelle von Notepad im Quellcode unten), dann "heftet" dieses sich an den Mutterprozess aller
Browserfenster. Genau: Der Mutterprozess erstellt einen neuen Prozess, das neue Browserfenster. Der neue Prozess hat dabei eine neue Prozess-ID, die unserem Programm nicht
bekannt ist. Somit kann das neue Browserfenster auch nicht mit der unten implementierten Methode geschlossen werden.
- Beendet sich der gestartete Prozess von selbst, dann ist es theoretisch möglich, dass in der Zwischenzeit (bis unser Programm versucht den Prozess zu schließen) die Prozess-ID
an einen neuen Prozess vergeben wird. Dadurch würden wir anschließend den falschen Prozess beenden. Im Programm unten wird nach dem Aufruf von ShellExecuteEx das
zurückgegebene Prozess-Handle seInfo.hProcess nicht mittels CloseHandle(seInfo.hProcess) geschlossen. Dadurch besteht das Prozessobjekt mit zugehöriger Prozess-ID im
Speicher fort. Erst wenn alle offenen Handles zu einem Prozess geschlossen wurden, existiert dieser nicht mehr und Windows kann die Prozess-ID wieder vergeben (siehe MSDN: Blog).
Somit wird das beschriebene Problem hier nicht auftreten. Im Allgemeinen sollte man jedoch vom eigenen Programm geöffnete Handles wieder schließen,
sofern man sie nicht mehr braucht. Dies wird jedoch auch von Windows automatisch getan, sobald das eigene Programm beendet wird (siehe auch MSDN: ExitProcess function, "All of the object handles opened by the process are closed.")
Links zu den verwendeten WinAPI-Funktionen und -Strukturen:
'
' BEISPIELPROGRAMM:
' Externes Programm unter Windows starten, seine ProcessID ermitteln
' und das Programm nach einer bestimmten Zeit automatisch wieder beenden
'
' Getestet mit Windows 7 Professional 64 Bit
'
' Compiliert mit FreeBASIC 0.23.0 stable fuer Windows
'
' Lizenz: FBPSL
'
' Datum: 06.06.2012 - FreeBASIC-Portal.de
'
#include "windows.bi"
#include "win\shellapi.bi"
#define TA_FAILED 0
#define TA_SUCCESS_CLEAN 1
#define TA_SUCCESS_KILL 2
' Funktionen zum Beenden einer Anwendung
Declare Function TerminateAppEnum (ByVal hwnd As HWND, ByVal lParam As LPARAM ) As Integer
Declare Function TerminateApp (ByVal dwPID As Integer, ByVal dwTimeout As Integer) As Integer
' Funktion zum Starten der Anwendung und Ermitteln der ProcessID
Declare Function startProgramAndGetPID (programToStart As String, parameters As String) As Integer
Type TERMINFO
dwID As Integer
dwThread As Integer
End Type
dim as string program = "notepad.exe" 'Beispielprogramm Windows-Editor Notepad.exe
dim as integer countdown = 10 'Programm fuer 10 Sekunden offen lassen und dann wieder beenden
dim as integer processID, closeResult
Print "Starte " & program & "..."
processID = startProgramAndGetPID (program, "")
If (processID = -1) Then
Print "Fehler: Programm konnte nicht gestartet werden. Dateiname/Pfad korrekt?"
Sleep: End 1
Else
Print "Das Programm wurde gestartet. Die Prozess-ID ist " & processID & "."
End If
Print 'Leerzeile ausgeben
Print "In " & countdown & " Sekunden versuchen wir, das Programm wieder zu beenden..."
For i As Integer = countdown To 1 Step -1
Print i; " ";
Sleep 1000, 1
Next i
Print " [GO!]"
Print
Print "Beende das Programm..."
' Der Parameter 3000 bedeutet, dass wir dem Programm 3000ms (= 3 Sekunden) Zeit
' geben, sich freiwillig zu schliessen (entspricht einem Klick auf das [X] oben
' in der Fensterleiste). Nach Ablauf dieser Frist wird das Programm abgeschossen
' ("kill"), wenn es dann immer noch laeuft.
' Wichtig: Wird das Programm abgeschossen, gehen nicht gespeicherte Daten in dem
' Programm verloren.
closeResult = TerminateApp (processID, 3000)
Print
Select Case closeResult
Case TA_SUCCESS_CLEAN:
Print "Erfolg: Das Programm wurde innerhalb der vorgegebenen Wartezeit"
Print " sanft beendet."
Case TA_SUCCESS_KILL:
Print "Erfolg: Das Programm wurde nach Ablauf der vorgegebenen max. Wartezeit"
Print " abgeschossen. Nicht gespeicherte Daten in dem Programm gingen"
Print " eventuell verloren."
Case TA_FAILED:
Print "Fehler: Das Programm konnte weder geschlossen noch abgeschossen werden."
End Select
Print
Print "Druecken Sie eine beliebige Taste zum Beenden."
sleep
end
' Diese Funktion wird NICHT durch den Benutzer aufgerufen, sondern nur intern verwendet!
Function TerminateAppEnum (ByVal hwnd As HWND, ByVal lParam As LPARAM) As Integer
Dim As Integer dwID
GetWindowThreadProcessId (hwnd, @dwID)
if (dwID = Cast(LPARAM,lParam)) Then PostMessage(hwnd, WM_CLOSE, 0, 0)
return TRUE
End Function
' Diese Funktion wird durch den Benutzer aufgerufen. Ihr wird die Prozess-ID des zu
' beendenden Programms uebergeben. Zunaechst wird fuer dwTimeout Sekundene lang probiert,
' das Programm "sanft" zu beenden. Klappt das nicht im vorgegebenen Zeitraum, wird der
' Zielprozess abgeschossen.
' Quelltext basiert auf C-Code-Beispiel: http://support.microsoft.com/kb/178893/en-us
Function TerminateApp (ByVal dwPID As Integer, ByVal dwTimeout As Integer) As Integer
Dim hProc As HANDLE
Dim dwRet As Integer
' Wenn Prozess nicht mit PROCESS_TERMINATE-Rechten geoeffnet werden
' kann, aufgeben und Funktion verlassen.
hProc = OpenProcess (SYNCHRONIZE Or PROCESS_TERMINATE, FALSE, Cast(DWORD,dwPID))
if (hProc = NULL) then return TA_FAILED
' Callback-Funktion TerminateAppEnum() fuer alle Fenster aufrufen, deren
' PID der uebergebenen dwPID entspricht. TerminateAppEnum() sendet dann
' an diese Fenster WM_CLOSE.
EnumWindows (@TerminateAppEnum, dwPID)
' Fuer die in dwTimeout angegebene Zeit auf das Beenden warten. Wenn
' das nichts bringt, anschliessend versuchen, den Prozess abzuschiessen
' mit TerminateProcess ("Kill").
if (WaitForSingleObject(hProc, dwTimeout) <> WAIT_OBJECT_0) Then
if (TerminateProcess(hProc,0)) Then
dwRet = TA_SUCCESS_KILL
else
dwRet = TA_FAILED
end if
else
dwRet = TA_SUCCESS_CLEAN
end if
CloseHandle (hProc)
' Statuscode zurueckgeben an Aufrufer:
' - TA_FAILED - wenn das Beenden fehlgeschlagen ist
' - TA_SUCCESS_CLEAN - wenn der Prozess sauber ueber WM_CLOSE beendet wurde
' - TA_SUCCESS_KILL - wenn der Prozess mit TerminateProcess() abgeschossen wurde
return dwRet
End Function
' Startet ein Programm (optional mit Parametern) und gibt seine PID zurueck.
' Im Fehlerfall wird -1 zurueckgegeben.
Function startProgramAndGetPID (programToStart As String, parameters As String) As Integer
Dim result As BOOL
' Siehe: SHELLEXECUTEINFO - http://msdn.microsoft.com/en-us/library/bb759784.aspx
Dim seInfo As SHELLEXECUTEINFO
Dim workingDir As String = EXEPATH 'Arbeitsverzeichnis standardmaessig auf das Verzeichnis
'festlegen, in dem die EXE-Datei hier liegt.
With seInfo
.cbSize = SizeOf(SHELLEXECUTEINFO)
.fMask = SEE_MASK_NOCLOSEPROCESS
.hWnd = 0
.lpVerb = StrPtr("open")
.lpFile = StrPtr(programToStart)
.lpParameters = StrPtr(parameters)
.lpDirectory = StrPtr(workingDir)
.nShow = SW_SHOWNORMAL ' Siehe: http://msdn.microsoft.com/en-us/library/bb762153.aspx
End With
' Siehe: ShellExecuteEx - http://msdn.microsoft.com/en-us/library/bb762154.aspx
result = ShellExecuteEx (@seInfo)
If (result = 0) Then
Return -1 'Fehler
Else
Dim As Integer processID = GetProcessID(seInfo.hProcess)
Return processID
End if
End Function
Zusätzliche Informationen und Funktionen |
- Das Code-Beispiel wurde am 06.06.2012 von Sebastian angelegt.
- Die aktuellste Version wurde am 01.11.2014 von grimlax gespeichert.
|
|