Подтвердить что ты не робот

Как дождаться завершения процесса оболочки перед выполнением дополнительного кода в VB6

У меня есть небольшое приложение VB6, в котором я использую команду Shell для выполнения программы. Я сохраняю вывод программы в файле. Затем я читаю этот файл и помещаю вывод на экран с помощью msgbox в VB6.

Вот как выглядит мой код:

sCommand = "\evaluate.exe<test.txt "
Shell ("cmd.exe /c" & App.Path & sCommand)

MsgBox Text2String(App.Path & "\experiments\" & genname & "\freq")

Проблема заключается в том, что вывод, который программа VB печатает с помощью msgbox, является старым состоянием файла. Есть ли способ удерживать выполнение кода VB до тех пор, пока моя командная программа оболочки не завершится, чтобы получить правильное состояние выходного файла, а не предыдущее состояние?

4b9b3361

Ответ 1

Секретный соус, необходимый для этого, - это WaitForSingleObject функция, которая блокирует выполнение вашего прикладного процесса до тех пор, пока указанный процесс не завершится (или время вышло). Это часть Windows API, которую легко вызывать из приложения VB 6 после добавления соответствующего объявления в ваш код.

Эта декларация будет выглядеть примерно так:

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

Требуется два параметра: дескриптор процесса, который вы хотите подождать, и интервал времени ожидания (в миллисекундах), который указывает максимальный период времени, который вы хотите подождать. Если вы не укажете интервал времени ожидания (значение нуля), функция не будет ждать и немедленно возвращается. Если вы укажете бесконечный интервал тайм-аута, функция вернется только тогда, когда процесс сигнализирует о завершении этого процесса.

Вооруженный этими знаниями, единственная задача, которая остается, - это выяснить, как получить дескриптор процесса, который вы начали. Это оказывается довольно простым, и его можно выполнить несколькими способами:

  • Одна из возможностей (и способ, которым я это сделаю) заключается в использовании функции ShellExecuteEx, также из Windows API, в качестве замены для функции Shell, встроенной в VB 6. Эта версия гораздо более универсальна и мощна, но так же легко вызвана с использованием соответствующего объявления.

    Он возвращает дескриптор процесса, который он создает. Все, что вам нужно сделать, это передать этот дескриптор функции WaitForSingleObject в качестве параметра hHandle, и вы находитесь в бизнесе. Выполнение вашего приложения будет заблокировано (приостановлено), пока процесс, который вы назвали, не завершится.

  • Другая возможность - использовать функцию CreateProcess (еще раз, из Windows API). Эта функция создает новый процесс и его основной поток в том же контексте безопасности, что и вызывающий процесс (т.е. Ваше приложение VB 6).

    Microsoft опубликовала статью базы знаний, в которой подробно описывается этот подход, который даже обеспечивает полную реализацию выборки. Вы можете найти эту статью здесь: Как использовать 32-битное приложение для определения, когда завершенный процесс завершается.

  • Наконец, возможно, самый простой подход заключается в том, чтобы использовать тот факт, что встроенное значение функции Shell возвращает идентификатор задачи приложения. Это уникальный номер, который идентифицирует запущенную вами программу и может быть передан функции OpenProcess, чтобы получить дескриптор процесса, который может быть передается функции WaitForSingleObject.

    Однако простота этого подхода стоит дорого. Очень существенным недостатком является то, что это приведет к тому, что ваше приложение VB 6 станет полностью невосприимчивым. Поскольку он не будет обрабатывать сообщения Windows, он не будет реагировать на взаимодействие пользователя или даже перерисовывать экран.

    Хорошие люди в VBnet внесли полный образец кода, доступный в следующей статье: WaitForSingleObject: определить, когда завершено приложение Shelled.
    Я хотел бы иметь возможность воспроизводить код здесь, чтобы помочь предотвратить гниль ссылки (VB 6 встает туда через годы, нет гарантии, что эти ресурсы будут навсегда), но лицензия на распространение в сам код явно запрещает это.

Ответ 2

Нет необходимости прибегать к дополнительным усилиям вызова CreateProcess() и т.д. Это более или менее дублирует старый код Randy Birch, хотя он не был основан на его примере. Существует так много способов скинуть кошку.

Здесь у нас есть предварительно упакованная функция для удобного использования, которая также возвращает код выхода. Внесите его в статический (.BAS) модуль или включите его в строку в форме или классе.

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

Ответ 3

Я знаю, что это старый поток, но...

Как использовать метод запуска Windows Script? Он имеет параметр bWaitOnReturn.

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

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

intWindowStyle = 0, поэтому cmd будет скрыт

Ответ 4

Сделайте следующее:

    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

Ссылка: http://support.microsoft.com/kb/129796

Ответ 5

Отличный код. Только одна небольшая проблема: вы должны объявить в ExecCmd (после запуска Dim как STARTUPINFO):

Dim ret as Long

При попытке компиляции в VB6 вы получите сообщение об ошибке, если вы этого не сделаете. Но он отлично работает:)

С уважением

Ответ 6

В моих руках решение csaba зависает с intWindowStyle = 0 и никогда не передает управление обратно в VB. Единственный выход - завершить процесс в taskmanager.

Настройка intWindowStyle = 3 и закрытие окна вручную передает управление назад

Ответ 7

Я нашел лучше & более простое решение:

Dim processID = Shell("C:/path/to/process.exe " + args
Dim p As Process = Process.GetProcessById(processID)
p.WaitForExit()

и тогда вы просто продолжаете свой код. Надеюсь, это поможет ;-)