views:

2612

answers:

3

I have a Windows Service written in Delphi which runs a number of programs.

On Stopping the service, I want to also close these programs. When the service was originally written, this worked fine, but I think I've updated the tProcess component and now - The subordinate programs are not being closed.

in tProcess - Here's the code which starts the new processes.

  if CreateProcess( nil , PChar( FProcess.Command ) , nil , nil , False ,
    NORMAL_PRIORITY_CLASS , nil , Directory ,
    StartupInfo , ProcessInfo ) then
    begin
      if FProcess.Wait then
        begin
          WaitForSingleObject( ProcessInfo.hProcess , Infinite );
          GetExitCodeProcess( ProcessInfo.hProcess , ExitCode );
          if Assigned( FProcess.FOnFinished ) then
            FProcess.FOnFinished( FProcess , ExitCode );
        end;
      CloseHandle( ProcessInfo.hProcess );
      CloseHandle( ProcessInfo.hThread );
    end;

Each of the executables called by this are Windows GUI Programs (With a close button at the top).

When I stop the service, I also want to stop (not kill) the programs I've started up via the createProcess procedure.

How would you do this?

+3  A: 

I'd use TJvCreateProcess component of JVCL which wraps about any process related functionality of win32 in a graceful way. This answer comes from Dont-touch-winapi-unless-really-required department :-)

utku_karatas
I think he already uses such a class, named TProcess
Lars Truijens
+1  A: 

The only generic way to stop a process is to use TerminateProcess. But that's as far from graceful as you can get. To gracefully close a process, you need to tell the process that you'd like it to stop, and then hope it obeys. There is no way to do that in general, though.

For a GUI program, the usual way to tell it that you want it to stop running is to close its main window. There's no formal idea of "main window," though. A program can have zero or more windows, and there's no way to know externally which one you're supposed to close in order to make the program stop working.

You could use EnumWindows to cycle through all the windows and select the ones that belong to your process. (They'd be the ones for which GetWindowThreadProcessId gives the same process ID that CreateProcess gave you.)

Closing a window might not be enough. The program might display a dialog box (prompting for confirmation, or asking to save changes, etc.). You would need to know in advance how to dismiss that dialog box.

Non-GUI programs can have similar problems. It might be enough to simulate a Ctrl+C keystroke. It might catch and handle that keystroke differently, though. It might have a menu system that expects you to type "Q" to quit the program.

In short, you cannot gracefully close a program unless you know in advance how that program expects to be closed.

Rob Kennedy
+1  A: 

You want to enumerate open windows that match your launched ProcessId and tell those windows to close. Here's some sample code for that:

uses Windows;

interface   

function MyTerminateAppEnum(hHwnd:HWND; dwData:LPARAM):Boolean; stdcall;

implementation

function MyTerminateAppEnum(hHwnd:HWND; dwData:LPARAM):Boolean; 
var   
  vID:DWORD; 
begin   
  GetWindowThreadProcessID(hHwnd, @vID);   
  if vID = dwData then   
  begin
    PostMessage(hHwnd, WM_CLOSE, 0, 0); //tell window to close gracefully
    Result := False;  //can stop enumerating    
  end   
  else   
  begin
    Result := TRUE;  //keep enumerating until you find your id   
  end; 
end;

Then you'll want to utilize this in your code when you want to shut down the launched applications:

Procedure TerminateMe(YourSavedProcessInfo:TProcessInformation);
var
  vExitCode:UINT;
begin
  GetExitCodeProcess(YourSavedProcessInfo.hProcess, vExitCode);
  if (vExitCode = STILL_ACTIVE) then  //launched app still running..
  begin
    //tell it to close
    EnumWindows(@MyTerminateAppEnum, YourSavedProcessInfo.dwProcessId);

    if WaitForSingleObject(YourSavedProcessInfo.hProcess, TIMEOUT_VAL) <> WAIT_OBJECT_0 then
    begin
      if not TerminateProcess(YourSavedProcessInfo.hProcess, 0) then  //didn't close, try to terminate
      begin
         //uh-oh   Didn't close, didn't terminate..
      end;
    end;
  end;
  CloseHandle(YourSavedProcessInfo.hProcess);
  CloseHandle(YourSavedProcessInfo.hThread);
end;
Darian Miller