Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I properly use the WaitForSingleObject method to wait for an external program to terminate?

I'm trying to launch an external application with elevated status, and wait until it exits before continuing:

var
  FProcess: THandle;
  ExecInfo: TShellExecuteInfo;
begin

  FillChar(ExecInfo, SizeOf(ExecInfo), 0);
  with ExecInfo do
  begin
    cbSize := SizeOf(ExecInfo);
    fMask := 0;
    Wnd := AWindow;
    lpVerb := 'runas';
    lpFile := PChar(APath);
    lpParameters := PChar(AParams);
    lpDirectory := PChar(AWorkDir);
    nShow := SW_NORMAL;
  end;

  Result := ShellExecuteEx(@ExecInfo);

  if Wait then
  begin
    while WaitForSingleObject(ExecInfo.hProcess, INFINITE) <> WAIT_TIMEOUT do
      Application.ProcessMessages;
  end;

This launches, but it just keeps waiting. The calling program never continues past the call to WaitForSingleObject, even after the called program exits.

I've tried WAIT_OBJECT_0 instead of WAIT_TIMEOUT, but I have the same problem. What am I doing wrong here?

like image 522
croceldon Avatar asked Mar 10 '11 14:03

croceldon


3 Answers

What the code

while WaitForSingleObject(ExecInfo.hProcess, INFINITE) <> WAIT_TIMEOUT do
  Application.ProcessMessages;

is supposed to do? It is an infinite loop.

Use just

WaitForSingleObject(ExecInfo.hProcess, INFINITE);

instead. And yes, you need

fMask:= SEE_MASK_NOCLOSEPROCESS;

to obtain the process handle.

like image 70
kludg Avatar answered Oct 20 '22 10:10

kludg


Your code is broken. You are not passing the SEE_MASK_NOCLOSEPROCESS flag to ShellExecuteEx(), so it will not return a valid process handle to you, and your loop is ignoring the errors that WaitForSingleObject() tells you because of that, so you end up in an endless loop.

Try this instead:

var
  ExecInfo: TShellExecuteInfo;
begin
  ZeroMemory(@ExecInfo, SizeOf(ExecInfo));
  with ExecInfo do
  begin
    cbSize := SizeOf(ExecInfo);
    fMask := SEE_MASK_NOCLOSEPROCESS;
    Wnd := AWindow;
    lpVerb := 'runas';
    lpFile := PChar(APath);
    lpParameters := PChar(AParams);
    lpDirectory := PChar(AWorkDir);
    nShow := SW_NORMAL;
  end;
  Result := ShellExecuteEx(@ExecInfo);
  if Result and Wait then
  begin
    if ExecInfo.hProcess <> 0 then // no handle if the process was activated by DDE
    begin
      repeat
        if MsgWaitForMultipleObjects(1, ExecInfo.hProcess, FALSE, INFINITE, QS_ALLINPUT) = (WAIT_OBJECT_0+1) then
          Application.ProcessMessages
        else
          Break;
      until False;
      CloseHandle(ExecInfo.hProcess);
    end;
  end; 
end;
like image 22
Remy Lebeau Avatar answered Oct 20 '22 09:10

Remy Lebeau


If you read description of ShellExecuteEx in MSDN, you will see this:

hProcess

Type: HANDLE

A handle to the newly started application. This member is set on return and is always NULL unless fMask is set to SEE_MASK_NOCLOSEPROCESS. Even if fMask is set to SEE_MASK_NOCLOSEPROCESS, hProcess will be NULL if no process was launched.

I.e. you simply don't have a valid handle. You need to set fMask as written above.

like image 38
Eugene Mayevski 'Callback Avatar answered Oct 20 '22 10:10

Eugene Mayevski 'Callback