Delphi - gracefully closing a created process in a service. (using tprocess / createProcess)
I have a Windows Service written in Delphi that runs several programs.
When the service stops, I want to close these programs as well. When the service was originally written it worked well, but I think I updated the tProcess component and now - subordinate programs are not closed.
in tProcess - here is the code that starts 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 that I started using the createProcess procedure.
How do you do it?
source to share
You want to list the open windows corresponding to your running process 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 will want to use this in your code if you want to close running 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;
source to share
The only common way to stop a process is using TerminateProcess . But it's not as graceful as you can get. To gracefully close a process, you need to tell the process that you want to stop and then hope that it obeys. But in general, this cannot be done.
For a GUI program, the usual way to say that you want to stop it is to close the main window. However, there is no formal idea of a "main window". A program can have zero or more windows, and there is no way of knowing from the outside which one you have to close for the program to stop working.
You can use EnumWindows to view all windows and select those that belong to your process. (These were the ones for which GetWindowThreadProcessId gives the same process ID that CreateProcess provided.)
Closing the window may not be enough. The program may display a dialog box (confirmation request or a request to save changes, etc.). You need to know in advance how to dismiss this dialog.
Non-GUI programs can have similar problems. This might be enough to simulate Ctrl + C key press. However, it does boast and handle this pressing. It may have a menu system that expects you to type "Q" to exit the program.
In short, you cannot gracefully close a program unless you know ahead of time how that program expects to close.
source to share