9

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)

Martin Melka
  • 7,177
  • 16
  • 79
  • 138
  • 3
    I don't get it. You want to wait 100 ms and then do `ShellExecute`? Well, then do so! `Sleep(100); ShellExecute(...)`. Or do you want `ShellExecute` *not to return until the newly created process has exited*? If so, in theory, you want the `Sleep` ***after*** `ShellExecute`, not *before* it. But that's an ugly solution. Instead you should `WaitForSingleObject` or something like that. – Andreas Rejbrand May 15 '11 at 16:24
  • 1
    I will try to rephrase it: 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) -> Neither of your suggestions. I need the ShellExecute _not to execute_ until the current application is fully closed (so that I can delete its files) – Martin Melka May 15 '11 at 16:29
  • You can't do that. After App 1 has closed, but before App 2 has opened, you cannot do anything, because you have no program running. What you can do is to call some app, sleep, and then call some other app, and then terminate. But instead of a fixed `Sleep`, you should `WaitForSingleObject` or something else more robust. – Andreas Rejbrand May 15 '11 at 16:32
  • Why not let ShellExecute call a third application, that does nothing but `sleep(1000); shellexecute(...` – Johan May 15 '11 at 16:32
  • @Johan possible, but I thought that there would be something better :/ – Martin Melka May 15 '11 at 16:34

3 Answers3

6

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.

user703016
  • 37,307
  • 8
  • 87
  • 112
  • 4
    What if you are copying a WTV file to an external HDD at the same time? Then 100 ms is likely not enough, and you still fail. A solution based on `WaitForSingleObject` is better. Alternatively, the second application can do `while not done do sleep(100)` or something like that at startup. – Andreas Rejbrand May 15 '11 at 16:43
  • What I do when running my "re-runner" is simply pass in params with the files to copy (source, dest) and then retry if it fails due to the app still running. Thus solves the timing as it just sleeps a bit more then tries again. – mj2008 May 15 '11 at 18:30
3

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;
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • 1
    Thank you for the answer, but I think you misunderstood my question. I needed to put an executable in a 'query', exit the current application (the calling one), and then run the exec from the query. – Martin Melka May 15 '11 at 17:25
  • Ok.... thinking... your current app spawns the updater to do the work and quits. The updater then checks to make sure the old app has terminated (I have code you can use to check for this), then it updates, renames, deletes, etc. When complete it spawns new app and itself terminates? – Simvector Technologies May 15 '11 at 19:22
2

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@

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
Nat
  • 5,414
  • 26
  • 38