VB 5/6-Tipp 0148: Warten auf das Beenden einer anderen Anwendung
von ActiveVB
Beschreibung
Sehr oft wird aus einen Programm heraus eine fremde Anwendung aufgerufen. Manchmal muss man mit der weiteren Ausführung warten, bis das aufgerufene Programm fertig ist oder beendet wurde (Bsp. PK-zip). Windows bietet für solcherlei Vorgänge eine eigene Funktion.
Ergänzung am 10.03.2003 von Helge Rex (Helge@ActiveVB.de): Die Routinen wurden dahingehend verändert, dass nun einstellbar ist, ob das aufgerufene Programm sichtbar ist.
Update am 23. September 2004 von Florian Rittmeier: Die Anwendung ist nun weniger fehleranfällig und reagiert noch, wärend sie auf die andere Anwendung wartet.
Schwierigkeitsgrad: | Verwendete API-Aufrufe: CloseHandle, CreateProcessA (CreateProcess), WaitForSingleObject | Download: |
'Dieser Quellcode stammt von http://www.activevb.de 'und kann frei verwendet werden. Für eventuelle Schäden 'wird nicht gehaftet. 'Um Fehler oder Fragen zu klären, nutzen Sie bitte unser Forum. 'Ansonsten viel Spaß und Erfolg mit diesem Source! '------------- Anfang Projektdatei Project1.vbp ------------- '--------- Anfang Formular "Form1" alias Form1.frm --------- ' Steuerelement: Schaltfläche "Command1" ' Steuerelement: Beschriftungsfeld "Label1" Option Explicit 'Diverse APIs deklarieren Private Declare Function CreateProcess Lib "Kernel32" Alias _ "CreateProcessA" ( _ ByVal lpAppName As Long, _ ByVal lpCmdLine As String, _ ByVal lpProcAttr As Long, _ ByVal lpThreadAttr As Long, _ ByVal lpInheritedHandle As Long, _ ByVal lpCreationFlags As Long, _ ByVal lpEnv As Long, _ ByVal lpCurDir As Long, _ lpStartupInfo As STARTUPINFO, _ lpProcessInfo 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 'Einige Konstanten benennen Private Const NORMAL_PRIORITY_CLASS As Long = &H20& Private Const INFINITE As Long = -1& Private Const WAIT_TIMEOUT As Long = 258& 'Einige Datentypen erstellen 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 Integer 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 Public Function ShellWait(cmdline As String, Optional ByVal _ bShowApp As Boolean = False) As Boolean 'Diese Funktion führt einen Befehl (in CmdLine) aus. 'Dabei wird das sich öffnende Fenster unsichtbar gemacht. 'Diese Funktion wird erst beendet, wenn der Befehl 'vollständig abgearbeitet ist. 'Speicher reservieren Dim uProc As PROCESS_INFORMATION Dim uStart As STARTUPINFO Dim lRetVal As Long 'Die Datentypen initialisieren uStart.cb = Len(uStart) uStart.wShowWindow = Abs(bShowApp) uStart.dwFlags = 1 'Statusmeldung Label1.Caption = "Starte Notepad" Label1.Refresh 'Fenster erzeugen lRetVal = CreateProcess(0&, cmdline, 0&, 0&, 1&, _ NORMAL_PRIORITY_CLASS, 0&, 0&, uStart, uProc) If lRetVal = 0 Then Call MsgBox("Starten der Anwendung ist fehlgeschlagen!", _ vbExclamation + vbOKOnly, App.Title) ShellWait = False Exit Function End If 'Statusmeldung Label1.Caption = "Warte auf das Ende der Anwendung" Label1.Refresh 'Warten, bis Fenster beendet wurde 'Dabei das eigene Fenster aktualisieren Do While WaitForSingleObject(uProc.hProcess, 10) = WAIT_TIMEOUT DoEvents Loop 'Wenn man solange warten will, bis die Anwendung beendet 'wird und nicht darauf achtet, dass die wartende Anwendung 'dabei absolut zum Stillstand kommt. 'lRetVal = WaitForSingleObject(uProc.hProcess, INFINITE) 'Statusmeldung Label1.Caption = "Anwendung beendet" Label1.Refresh 'Fenster schließen lRetVal = CloseHandle(uProc.hProcess) 'Rückgabewert setzen ShellWait = (lRetVal <> 0) End Function Private Sub Command1_Click() ShellWait "notepad.exe", True MsgBox "NotePad geschlossen" End Sub '---------- Ende Formular "Form1" alias Form1.frm ---------- '-------------- Ende Projektdatei Project1.vbp --------------
Tipp-Kompatibilität:
Windows/VB-Version | Win32s | Win95 | Win98 | WinME | WinNT4 | Win2000 | WinXP |
VB4 | |||||||
VB5 | |||||||
VB6 |
Ihre Meinung
Falls Sie Fragen zu diesem Artikel haben oder Ihre Erfahrung mit anderen Nutzern austauschen möchten, dann teilen Sie uns diese bitte in einem der unten vorhandenen Themen oder über einen neuen Beitrag mit. Hierzu können sie einfach einen Beitrag in einem zum Thema passenden Forum anlegen, welcher automatisch mit dieser Seite verknüpft wird.
Archivierte Nutzerkommentare
Klicken Sie diesen Text an, wenn Sie die 24 archivierten Kommentare ansehen möchten.
Diese stammen noch von der Zeit, als es noch keine direkte Forenunterstützung für Fragen und Kommentare zu einzelnen Artikeln gab.
Aus Gründen der Vollständigkeit können Sie sich die ausgeblendeten Kommentare zu diesem Artikel aber gerne weiterhin ansehen.
Kommentar von Florian Rittmeier am 23.12.2005 um 12:42
Hallo Ronny,
am besten stellst Du deine Frage nochmal im VBA-Forum.
Gruß Florian
Kommentar von Ronny am 22.12.2005 um 10:40
Erstmal dankke für das Super Tutorial. Wie kann ich das einrichten das wenn ich MS Access starte ein Formular mit geöffnet wird? Geht das überhaupt?
Danke
Kommentar von Cornflake am 21.03.2005 um 16:35
Ich hätte da noch eine Erweiterung für den Tip.
Der Tip hat noch ein Manko, er kann keine Batchdateien sauber ausführen. Dazu müssten aber nur folgende Zeilen eingebaut werden.
'... vorheriger code vom Tip
Label1.Refresh
ChDrive$ (cmdline)'kommt neu dazu
ChDir$ ("C:\")'kommt neu dazu; muss aber noch ein wenig verändert werden, so das nur der Pfadname hier steht
'Fenster erzeugen
lRetVal = CreateProcess(0&, cmdline, 0&, 0&, 1&, _
NORMAL_PRIORITY_CLASS, 0&, 0&, uStart, uProc)
'... weitere code vom tip
Dann klappts auch mit dem Nachb... der Batchdatei. ;)
Mit chdir wird vor dem aufruf von CreateProcess der richtigen Ordner für die Batchdatei als aktueller Ordner festgelegt.
Schön wäre noch wenn jemand dieses Environ$ ("COMSPEC") & " /c "(wie ich vom Florian Rittmeier erfahren habe) einbaut, damit sich das Fenster bei einem Batchaufruf auch noch automatisch zu Schluß schließt.(Bei Win98 muss das ja eh noch ein wenig kompilizierter gemacht werden, ich glaube mit einem start vor dem /c)
Kommentar von Florian Rittmeier am 09.03.2005 um 15:34
@Cornflake:
Das ist eine Frage die ins Hauptforum gehört.
Das ganze erfordert auch keine Änderung am Tipp sondern an deinem Aufruf. Du musst dem Kommandozeileninterpreter noch einen Parameter mit geben, wodurch sich dieses wieder komplett schließt. Einfach mal command /? ausführen und schauen welcher Parameter das genau ist.
Gruß Florian
Kommentar von Cornflake am 09.03.2005 um 11:31
Unter win98 wird das Dos-Fenster einer Batchdatei nicht per default geschlossen. Hier hängt dann der Tip. Da er nicht weitermacht, obwohl der Status des Dos-Fenster den Status beendet besitzt. Kann man den Tip dahingehend erweitern. das er damit keine Probleme mehr hat?
Kommentar von Florian Rittmeier am 25.02.2005 um 19:33
Hallo Peter,
der hier vorgestellte Code (in der aktuellen Version) sollte keine Handles offenlassen.
Bist Du hunderprozentig sicher, dass es an diesem Aufruf liegt. Alternativfrage wäre natürlich, wieviele tausend Aufrufe Du den in den drei Tagen machst. Vielleicht wäre da etwas anderes eine bessere Lösung.
Das Thema sollte jedoch nicht an dieser Stelle sondern im VB-Forum weiter diskutiert werden.
Gruß Florian
Kommentar von Peter Krause am 25.02.2005 um 16:39
Die Funktion "CreateProcess" führt bei mir nach häufigen Aufrufen zu einem Speicherüberlauf.
Grund: bei ihrem Aufruf werden irgendwo 2 Handles angelegt, die nicht wieder entfernt werden. Die Handle-Anzahl meines Programms steigt also bei jedem Aufruf um 2 an. Nach 3 Tagen ist dann der virtuelle Speicher voll.
Ich habe es mit verschiedener Parametrierung versucht - nichts half.
Hat jemand eine Idee, woran es liegt?
Kommentar von Florian Rittmeier am 26.01.2005 um 12:04
Hallo Volker,
schau Dir zu deiner Frage mal folgende Seite an. Die dort vorgestellte Funktion des Win32-API bietet Dir die gewünschte Funktionalität an. D.h. Du musst sie dann nur noch mit diesem Tipp kombinieren, aber dass sollte dann nicht mehr so schwierig sein.
http://www.activevb.de/rubriken/apikatalog/deklarationen/findexecutable.html
Gruß Florian
Kommentar von Volker am 25.01.2005 um 10:10
Hi,
sehr sehr hilfreich, dieses Programm. Wie geht es allerdings, wenn ich nicht weiß, welches Programm ich öffnen muß ? Wenn ich z.B. eine *.txt Datei habe und ich diese über das Programm automatisch mit dem Windows- Standardprogramm für diesen Dateitypen öffnen will ?
Gruß Volker
Kommentar von Florian Rittmeier am 15.10.2004 um 14:56
Hallo Christoph,
der Zugriff auf das Label-Steuerelement ist natürlich nicht zwingend notwendig. Aber es ist für den Anfang ganz praktisch, wenn man den Status nochmal mitgeteilt bekommt.
Gruß Florian
Kommentar von Christoph Friedrich am 15.10.2004 um 12:34
1. Warum die Label1. Anweisungen? Die bringen in der Funktion doch irgendwie nix?
Kommentar von Florian Rittmeier am 13.08.2004 um 18:16
Hallo Cornflake,
Du solltest den Pfad sicherheitshalber in die Kurzschreibweise umwandeln lassen. Einen Tipp dazu haben wir auch.
Ansonsten wende Dich am Besten ans Forum.
Gruß Florian
Kommentar von Cornflake am 12.08.2004 um 17:07
Irgendwie funktioniert das bei Dosanwendungen bei mir nicht. das dosfenster plobt kurz auf und das wars. aber die angeblich ausgeführte anwendung hat mir kein ergebnis geliefert. Bei der dosanwendung handelt es sich um ein kleines progrämle das eine textdatei ein den aufrufenden ordner erzeugt.
Vielleicht liegt es ja aber auch an dem langen Pfad mit Leerzeichen drinnen, den habe ich aber schon in Anführungszeichen gesetzt.
So jetzt habe ich keine Ahnung warum das nicht geht.
Kommentar von Florian Rittmeier am 09.10.2003 um 14:41
Hallo Dietmar,
ich würde Dir empfehlen, das WaitForSingleObject in eine Schleife zu setzen. Anstatt des Parameters übergibst Du dann als zweiten Parameter eine Wartezeit in Millisekunden. Empfehlenswert wäre hier ein Wert von 50-100 Millisekunden.
Der Rückgabewert der Funktion WaitForSingleObject zeigt Dir an, ob die Funktion aufgrund des Timeouts von n Millisekunden oder aufgrund der Beendigung des Zielprogrammes beendet wurde.
Die entsprechenden Konstanten findest Du im ApiViewer und sie lauten WAIT_TIMEOUT bzw. WAIT_OBJECT_0.
Solange Wait_TIMEOUT zurückgegeben wird, führst Du zunächst ein DoEvents aus und anschließend lässt Du die Schleife wieder WaitForSingleObject aufrufen.
Hierdurch (durch das DoEvents) erhält Access die Zeit auf eingehende Fensternachrichten zu reagieren und es wird nicht als "Ohne Rückmeldung" gekennzeichnet.
Warum FoxPro nicht gestartet wird, ist mir ein Rätsel. Vielleicht löst sich das Problem aber schon durch die oben vorgeschlagene Änderung.
Gruß Florian
Kommentar von Dietmar Nagel am 23.06.2003 um 17:25
Ich benutze grundsätzlich den oben vorgeschlagenen Code in einem Acces97-Modul (mein Beispiel stammt aus dem Buch'Access97 Programmierung' von Microsoft Press). Er funktioniert auch, z.B. wenn ich Excel oder Word starte. Wenn ich aber versuche den Veteranen MS-FoxPro 2.6 (Windows) zu starten, hängt sich Access auf (keine Rückmeldung) und lässt sich nur noch im Task-Manager beenden. Nach dem Beenden von Access wird FoxPro dann endlich fehlerfrei gestartet. Hat jemand eine Idee?
Gruß, Dietmar.
Kommentar von Peter am 26.05.2003 um 10:00
Man kann den ExitCode auslesen, indem man die API Funktion einbindet:
Private Declare Function GetExitCodeProcess Lib "Kernel32" (ByVal hProcess As Long, lpExitCode As Long) As Long
und den Aufruf (GetExitCodeProcess) hier plaziert:
'Warten, bis Fenster beendet wurde
lRetVal = WaitForSingleObject(uProc.hProcess, INFINITE)
GetExitCodeProcess uProc.hProcess, RetVal
MsgBox RetVal
Das ganze ist übrigens eine feine Sache. Bitte benutzt den code von der Seite http://vb-tec.de/xshell.htm nicht, da dieser nicht zuverlässig funktioniert. Ich habe das am eigenem Leib erfahren müssen.
Kommentar von Peter am 23.05.2003 um 15:18
Hallo,
kann ich mit dem Programm auch den ExitCode des aufgerufenen Programms auslesen?
Gruß
Peter
Kommentar von cooljam am 30.08.2002 um 10:53
hat sich glaub ich erledigt. ich war zu blind den tipp 147 direkt in der lsite drüber zu finden :-))
Kommentar von cooljam am 30.08.2002 um 10:50
Ich würde gerne x Minuten warten nach start eines externen programms per shell. wenn das programm dann nicht beendet ist, dann soll der prozess gekillt werden. wie würd ich da vorgehen?
merci schonmal
Kommentar von ak am 29.06.2002 um 05:46
...
z = Timer + 2
Do
DoEvents
Loop Until Timer z
...
Kommentar von Joe am 12.06.2002 um 20:50
Ein anderes VB Programm ist gerade geöffnet. Ich will ein zweites VB-Programm öffnen, das soll aber nur mögl. wenn das erste (das schon geöffnete)geschlossen ist. Bin ich da richtig hier bei eurem Beispiel? Wenn nicht könnt ihr mir da ein Vorschlag machen ?? Danke euch finde eure Seite Top!!
Kommentar von Turbo24prg am 23.03.2002 um 18:56
Gibt es zu diesem Trick auch ein Gegenteil? Ich möchte etwas bestimmtes machen wenn ein Programm gestartet wird. ------- DRINGEND ! -------
Kommentar von Dänu am 29.01.2002 um 15:28
Die Lösung - es muss eine Warteschlaufe her :
Handle = OpenProcess(SYNCHRONIZE, False, Task)
z = Timer
Do
DoEvents
zz = Timer
Loop Until zz z + 2
Call WaitForSingleObject(Handle, INFINITE)
Kommentar von Daenu am 28.01.2002 um 13:57
Wenn ich aus meiner Anwendung mit diesem tip Word starte, bockt es bei der Status-Meldug "Word wartet, bis Access die DDE-Anfrage bearbeitet", und dann geht nichts mehr