J'ai une petite application VB6 dans laquelle j'utilise la commande Shell
pour exécuter un programme. Je stocke la sortie du programme dans un fichier. Je suis alors en train de lire ce fichier et d’afficher la sortie à l’écran à l’aide d’une boîte aux lettres dans VB6.
Voici à quoi ressemble mon code:
sCommand = "\evaluate.exe<test.txt "
Shell ("cmd.exe /c" & App.Path & sCommand)
MsgBox Text2String(App.Path & "\experiments\" & genname & "\freq")
Le problème est que la sortie imprimée par le programme VB à l'aide de la msgbox correspond à l'ancien état du fichier. Existe-t-il un moyen de suspendre l'exécution du code VB jusqu'à la fin du programme de commande Shell afin d'obtenir l'état correct du fichier de sortie et non un état précédent?
La recette secrète nécessaire à cette opération est la fonction WaitForSingleObject
, qui bloque l'exécution du processus de votre application jusqu'à ce que le processus spécifié soit terminé (ou expire). Cela fait partie de l'API Windows, facilement appelé depuis une application VB 6 après avoir ajouté la déclaration appropriée à votre code.
Cette déclaration ressemblerait à quelque chose comme ceci:
Private Declare Function WaitForSingleObject Lib "kernel32" (ByVal hHandle _
As Long, ByVal dwMilliseconds As Long) As Long
Deux paramètres sont nécessaires: un descripteur du processus que vous souhaitez attendre et l'intervalle de temporisation (en millisecondes) qui indique la durée maximale pendant laquelle vous souhaitez attendre. Si vous ne spécifiez pas de délai d'expiration (une valeur de zéro), la fonction n'attend pas et retourne immédiatement. Si vous spécifiez un intervalle de temps infini, la fonction renvoie uniquement lorsque le processus en indique la fin.
Armée de cette connaissance, la seule tâche qui reste à faire est de déterminer comment gérer le processus que vous avez démarré. Cela s'avère assez simple et peut être accompli de différentes manières:
Une possibilité (et la façon dont je le ferais) consiste à utiliser la fonction ShellExecuteEx
, également à partir de l'API Windows, en remplacement instantané de la fonction Shell
intégrée à VB 6. Cette version est beaucoup plus polyvalente et puissante, mais elle est tout aussi facilement appelée à l’aide de la déclaration appropriée.
Il renvoie un descripteur au processus qu'il crée. Tout ce que vous avez à faire est de transmettre ce descripteur à la fonction WaitForSingleObject
en tant que paramètre hHandle
et vous êtes en affaires. L'exécution de votre application sera bloquée (suspendue) jusqu'à la fin du processus que vous avez appelé.
Une autre possibilité consiste à utiliser la fonction CreateProcess
(à nouveau, à partir de l'API Windows). Cette fonction crée un nouveau processus et son thread principal dans le même contexte de sécurité que le processus appelant (c'est-à-dire votre application VB 6).
Microsoft a publié un article de la base de connaissances détaillant cette approche, qui fournit même un exemple complet d'implémentation. Vous pouvez trouver cet article ici: Comment utiliser une application 32 bits pour déterminer la fin d'un processus shells .
Enfin, l’approche la plus simple consiste peut-être à tirer parti du fait que la valeur de retour de la fonction Shell
intégrée est un ID de tâche d’application. Il s'agit d'un numéro unique qui identifie le programme que vous avez démarré. Il peut être transmis à la fonction OpenProcess
pour obtenir un descripteur de processus pouvant être transmis à la fonction WaitForSingleObject
.
Cependant, la simplicité de cette approche ne a un coût. Un inconvénient très important est que votre application VB 6 ne répondra plus du tout. Dans la mesure où il ne traitera pas les messages Windows, il ne répondra pas aux interactions de l'utilisateur et ne redessinera même pas l'écran.
Les bonnes personnes de VBnet ont mis à disposition un exemple de code complet dans l’article suivant: WaitForSingleObject: Détermine le moment où une application shells est terminée .
J'adorerais pouvoir reproduire le code ici pour aider à lutter contre la pourriture des liens (VB 6 y monte depuis des années; rien ne garantit que ces ressources seront toujours présentes), mais la licence de distribution dans le code lui-même apparaît d'interdire explicitement cela.
Il n'est pas nécessaire de recourir à l'effort supplémentaire d'appeler CreateProcess (), etc. Cela duplique plus ou moins l'ancien code de Randy Birch, bien qu'il ne soit pas basé sur son exemple. Il n'y a pas beaucoup de façons de peler un chat.
Ici, nous avons une fonction préemballée pour une utilisation pratique, qui renvoie également le code de sortie. Déposez-le dans un module statique (.BAS) ou incluez-le en ligne dans un formulaire ou une classe.
Option Explicit
Private Const INFINITE = &HFFFFFFFF&
Private Const SYNCHRONIZE = &H100000
Private Const PROCESS_QUERY_INFORMATION = &H400&
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
Private Declare Function OpenProcess Lib "kernel32" ( _
ByVal dwDesiredAccess As Long, _
ByVal bInheritHandle As Long, _
ByVal dwProcessId As Long) As Long
Private Declare Function WaitForSingleObject Lib "kernel32" ( _
ByVal hHandle As Long, _
ByVal dwMilliseconds As Long) As Long
Public Function ShellSync( _
ByVal PathName As String, _
ByVal WindowStyle As VbAppWinStyle) As Long
'Shell and wait. Return exit code result, raise an
'exception on any error.
Dim lngPid As Long
Dim lngHandle As Long
Dim lngExitCode As Long
lngPid = Shell(PathName, WindowStyle)
If lngPid <> 0 Then
lngHandle = OpenProcess(SYNCHRONIZE _
Or PROCESS_QUERY_INFORMATION, 0, lngPid)
If lngHandle <> 0 Then
WaitForSingleObject lngHandle, INFINITE
If GetExitCodeProcess(lngHandle, lngExitCode) <> 0 Then
ShellSync = lngExitCode
CloseHandle lngHandle
Else
CloseHandle lngHandle
Err.Raise &H8004AA00, "ShellSync", _
"Failed to retrieve exit code, error " _
& CStr(Err.LastDllError)
End If
Else
Err.Raise &H8004AA01, "ShellSync", _
"Failed to open child process"
End If
Else
Err.Raise &H8004AA02, "ShellSync", _
"Failed to Shell child process"
End If
End Function
Je sais que c'est un vieux fil, mais ...
Comment utiliser la méthode Run de Windows Script Host? Il a un paramètre bWaitOnReturn.
object.Run (strCommand, [intWindowStyle], [bWaitOnReturn])
Set oShell = CreateObject("WSCript.Shell")
oShell.run "cmd /C " & App.Path & sCommand, 0, True
intWindowStyle = 0, donc cmd sera caché
Fait comme ça :
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
Private Declare Function WaitForSingleObject Lib "kernel32" (ByVal _
hHandle As Long, ByVal dwMilliseconds As Long) As Long
Private Declare Function CreateProcessA Lib "kernel32" (ByVal _
lpApplicationName As String, 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 String, _
lpStartupInfo As STARTUPINFO, lpProcessInformation As _
PROCESS_INFORMATION) 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
Private Const NORMAL_PRIORITY_CLASS = &H20&
Private Const INFINITE = -1&
Public Function ExecCmd(cmdline$)
Dim proc As PROCESS_INFORMATION
Dim start As STARTUPINFO
' Initialize the STARTUPINFO structure:
start.cb = Len(start)
' Start the shelled application:
ret& = CreateProcessA(vbNullString, cmdline$, 0&, 0&, 1&, _
NORMAL_PRIORITY_CLASS, 0&, vbNullString, start, proc)
' Wait for the shelled application to finish:
ret& = WaitForSingleObject(proc.hProcess, INFINITE)
Call GetExitCodeProcess(proc.hProcess, ret&)
Call CloseHandle(proc.hThread)
Call CloseHandle(proc.hProcess)
ExecCmd = ret&
End Function
Sub Form_Click()
Dim retval As Long
retval = ExecCmd("notepad.exe")
MsgBox "Process Finished, Exit Code " & retval
End Sub
Référence: http://support.Microsoft.com/kb/129796
Excellent code. Juste un petit problème: vous devez déclarer dans ExecCmd (après Dim start As STARTUPINFO):
Dim ret aussi long
Si vous ne le faites pas, vous obtiendrez une erreur en essayant de compiler dans VB6. Mais ça marche très bien :)
Sincères amitiés
Dans mes mains, la solution csaba est suspendue avec intWindowStyle = 0 et ne rend jamais le contrôle à VB. Le seul moyen de sortir est de mettre fin au processus dans le gestionnaire de tâches.
La définition de intWindowStyle = 3 et la fermeture manuelle de la fenêtre redonnent le contrôle