Wait before ShellExecute is carried out?

2020-06-12 06:35发布

问题:

I have a hopefully quick question: Is it possible to delay execution of ShellExecute a little bit?

I have an application with autoupdater. After it downloads all necessary files etc, it renames current files to *.OLD and the new as the previous. Simple enough. But then I need to delete those .OLD files. This 'cleanup' procedure is executed on MainForm.OnActivate (with a check if it is the first activate proc). But this apparently happens too fast (I get False from DeleteFile). This is the procedure:

procedure TUpdateForm.OKBtnClick(Sender: TObject);
const SHELL = 'ping 127.0.0.1 -n 2';
begin
  ShellExecute(0,'open',pchar(SHELL+#13+Application.ExeName),nil,nil,SW_SHOWNORMAL);
  Application.Terminate;
end;

This procedure is supposed to restart the application. I am certain that the deleting problem is caused by the quick start of the second application, because if I restart it myself, giving it a little time, the files get deleted normally.

tl;dr version: I need to call ShellExecute() which waits a bit (0.1 sec or so) and THEN executes the command.

Note

I tried using the -ping command to try to delay it, but it didn't work.

Thank you very much in advance

Edit: Rephrased

I need this to happen || First app closes; Wait 100 ms; second app opens ||. I need to call ShellExecute first, then wait until the calling application closes itself completely, then execute the shell (i.e. open second application)

回答1:

You're doing an autopatcher right ?

I've had the same problem and this is how I bypassed it :

You run second app with argument "--delay" or something like that. Second app handles argument "--delay" and sleeps for 100 ms, then continues running normally.



回答2:

This routine is some utils code in our game engine. It can run an executable and optionally wait for it to exit. It will return its exit code:

function TSvUtils.FileExecute(ahWnd: Cardinal; const aFileName, aParams, aStartDir: string; aShowCmd: Integer; aWait: Boolean): Integer;
var
  Info: TShellExecuteInfo;
  ExitCode: DWORD;
begin

  Result := -1;
  FillChar(Info, SizeOf(Info), 0);
  Info.cbSize := SizeOf(TShellExecuteInfo);
  with Info do begin
    fMask := SEE_MASK_NOCLOSEPROCESS;
    Wnd := ahWnd;
    lpFile := PChar(aFileName);
    lpParameters := PChar(aParams);
    lpDirectory := PChar(aStartDir);
    nShow := aShowCmd;
  end;

  if ShellExecuteEx(@Info) then
  begin
    if aWait then
    begin
      repeat
        Sleep(1);
        Application.ProcessMessages;
        GetExitCodeProcess(Info.hProcess, ExitCode);
      until (ExitCode <> STILL_ACTIVE) or Application.Terminated;
      CloseHandle(Info.hProcess);
      Result := ExitCode;
    end;
  end
end;

Here is some code that can check to see if a process exists. So... current app calls the updater and terminates. The updater can check to see if old app has terminated and do it's thing (rename, update, delete, etc):

function TSvUtils.ProcessExists(const aExeFileName: string; aBringToForgound: Boolean=False): Boolean;
var
  ContinueLoop: BOOL;
  FSnapshotHandle: THandle;
  FProcessEntry32: TProcessEntry32;
begin

  FSnapshotHandle := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  FProcessEntry32.dwSize := SizeOf(FProcessEntry32);
  ContinueLoop := Process32First(FSnapshotHandle, FProcessEntry32);
  Result := False;
  while Integer(ContinueLoop) <> 0 do
  begin
    if ((UpperCase(ExtractFileName(FProcessEntry32.szExeFile)) =
      UpperCase(aExeFileName)) or (UpperCase(FProcessEntry32.szExeFile) =
      UpperCase(aExeFileName))) then
    begin
      if aBringToForgound then
        EnumWindows(@BringToForgroundEnumProcess, FProcessEntry32.th32ProcessID);
      Result := True;
    end;
    ContinueLoop := Process32Next(FSnapshotHandle, FProcessEntry32);
  end;
  CloseHandle(FSnapshotHandle);
end;


回答3:

If you can use CreateProcess instead of ShellExecute, you can wait on the process handle. The process handle is signalled when the application exits. For example:

function ExecAndWait(APath: string; var VProcessResult: cardinal): boolean;
var
  LWaitResult : integer;
  LStartupInfo: TStartupInfo;
  LProcessInfo: TProcessInformation;
begin
  Result := False;

  FillChar(LStartupInfo, SizeOf(TStartupInfo), 0);

  with LStartupInfo do
  begin
    cb := SizeOf(TStartupInfo);

    dwFlags := STARTF_USESHOWWINDOW or STARTF_FORCEONFEEDBACK;
    wShowWindow := SW_SHOWDEFAULT;
  end;

  if CreateProcess(nil, PChar(APath), nil, nil, 
                   False, NORMAL_PRIORITY_CLASS,
                   nil, nil, LStartupInfo, LProcessInfo) then
  begin

    repeat
      LWaitResult := WaitForSingleObject(LProcessInfo.hProcess, 500);
      // do something, like update a GUI or call Application.ProcessMessages
    until LWaitResult <> WAIT_TIMEOUT;
    result := LWaitResult = WAIT_OBJECT_0;
    GetExitCodeProcess(LProcessInfo.hProcess, VProcessResult);
    CloseHandle(LProcessInfo.hProcess);
    CloseHandle(LProcessInfo.hThread);
  end;
end;

After ExecAndWait returns, then you can sleep for 100ms if you need to.

N@