Cómo esperar a que finalice un proceso de shell antes de ejecutar otro código en VB6

Tengo una pequeña aplicación VB6 en la que utilizo el comando Shell para ejecutar un progtwig. Estoy almacenando el resultado del progtwig en un archivo. Estoy leyendo este archivo y poniendo el resultado en la pantalla usando un msgbox en VB6.

Así es como se ve mi código ahora:

 sCommand = "\evaluate.exe<test.txt " Shell ("cmd.exe /c" & App.Path & sCommand) MsgBox Text2String(App.Path & "\experiments\" & genname & "\freq") 

El problema es que la salida que el progtwig VB está imprimiendo usando el msgbox es el estado anterior del archivo. ¿Hay alguna manera de mantener la ejecución del código VB hasta que el progtwig de comandos de mi shell finalice para que obtenga el estado correcto del archivo de salida y no un estado previo?

La salsa secreta necesaria para hacer esto es la función WaitForSingleObject , que bloquea la ejecución del proceso de su aplicación hasta que el proceso especificado se complete (o agote el tiempo de espera). Es parte de la API de Windows, que se llama fácilmente desde una aplicación VB 6 después de agregar la statement adecuada a su código.

Esa statement se vería así:

 Private Declare Function WaitForSingleObject Lib "kernel32" (ByVal hHandle _ As Long, ByVal dwMilliseconds As Long) As Long 

Toma dos parámetros: un identificador para el proceso que desea esperar, y el intervalo de tiempo de espera (en milisegundos) que indica la cantidad máxima de tiempo que desea esperar. Si no especifica un intervalo de tiempo de espera (un valor de cero), la función no espera y regresa inmediatamente. Si especifica un intervalo de tiempo de espera infinito, la función solo regresa cuando el proceso indica que se ha completado.

Armado con ese conocimiento, la única tarea que queda es descubrir cómo manejar el proceso que comenzó. Eso resulta ser bastante simple y se puede lograr de diferentes maneras:

  1. Una posibilidad (y la forma en que lo haría) es usar la función ShellExecuteEx , también de la API de Windows, como un reemplazo directo para la función Shell que está incorporada en VB 6. Esta versión es mucho más versátil y poderosa. , pero igual de fácil se llama usar la statement apropiada.

    Devuelve un identificador al proceso que crea. Todo lo que tienes que hacer es pasar ese asa a la función WaitForSingleObject como el parámetro hHandle , y estás en el negocio. La ejecución de su aplicación será bloqueada (suspendida) hasta que finalice el proceso que ha llamado.

  2. Otra posibilidad es usar la función CreateProcess (una vez más, desde la API de Windows). Esta función crea un nuevo proceso y su hilo principal en el mismo contexto de seguridad que el proceso de llamada (es decir, su aplicación VB 6).

    Microsoft ha publicado un artículo de la base de conocimiento que detalla este enfoque que incluso proporciona una implementación de muestra completa. Puede encontrar ese artículo aquí: Cómo usar una aplicación de 32 bits para determinar cuándo finaliza un proceso de lanzamiento .

  3. Finalmente, quizás el enfoque más simple hasta el momento es aprovechar el hecho de que el valor de retorno de la función Shell incorporada es una ID de tarea de aplicación. Este es un número único que identifica el progtwig que inició y se puede pasar a la función OpenProcess para obtener un identificador de proceso que se puede pasar a la función WaitForSingleObject .

    Sin embargo, la simplicidad de este enfoque tiene un costo. Una desventaja muy importante es que hará que su aplicación VB 6 deje de responder por completo. Debido a que no procesará los mensajes de Windows, no responderá a la interacción del usuario ni siquiera redibujará la pantalla.

    Las buenas personas de VBnet han hecho que el código de muestra completo esté disponible en el siguiente artículo: WaitForSingleObject: determine cuándo ha finalizado una aplicación Shelled .
    Me encantaría poder reproducir el código aquí para ayudar a evitar Link Rot (VB 6 se está poniendo en marcha en los últimos años, no hay garantía de que estos recursos estarán disponibles para siempre), pero la licencia de distribución en el código en sí aparece para prohibir explícitamente eso.

No hay necesidad de recurrir al esfuerzo extra de llamar a CreateProcess (), etc. Esto duplica más o menos el antiguo código de Randy Birch, aunque no se basó en su ejemplo. Solo hay muchas maneras de despellejar a un gato.

Aquí tenemos una función preempaquetada para uso práctico, que también devuelve el código de salida. Colóquelo en un módulo estático (.BAS) o inclúyalo en línea en un Formulario o Clase.

 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 

Sé que es un hilo viejo, pero …

¿Qué le parece usar el método Run de Windows Script Host? Tiene un parámetro bWaitOnReturn.

object.Run (strCommand, [intWindowStyle], [bWaitOnReturn])

 Set oShell = CreateObject("WSCript.shell") oShell.run "cmd /C " & App.Path & sCommand, 0, True 

intWindowStyle = 0, por lo que cmd estará oculto

Hazlo así :

  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 

Referencia: http://support.microsoft.com/kb/129796

Gran código Solo un pequeño problema: debes declarar en el ExecCmd (después de iniciar Dim como STARTUPINFO):

Dim ret como Long

Obtendrá un error al intentar comstackr en VB6 si no lo hace. Pero funciona genial 🙂

Saludos cordiales

En mis manos, la solución csaba se cuelga con intWindowStyle = 0, y nunca pasa el control a VB. La única salida es finalizar el proceso en taskmanager.

Establecer intWindowStyle = 3 y cerrar la ventana manualmente pasa el control de vuelta