There is a single-threaded worker application — it runs with arguments in the command line and adds the results of the work to the specified folder. There is also a manager application, it launches several workers (to use multi-threading) and in a separate thread looks at the folders with the results of their work.

With the launch of the employee application there are no problems (I use CreateProcess ). How does an application manager kill an employee application? In other words - how to launch another application from Delphi, and unilaterally "kill" it or order it to close?

PS Both applications are written by me. Murder by process name will not work, tk all employees have the same name.

  • In my opinion, just killing the process is a bad idea. Once both processes are written by you, it is better to organize the exchange of messages between them (through windows messages, shared memory, pipes, tcp, etc.) and from the main application, order the child to complete. And as a last resort, when a subsidiary suddenly refuses to respond to commands, use TerminateProcess. - zed
  • @zed, I'm still interested in the most cost-effective process, since both utility applications for personal use - Kromster
  • application worker is a console application or window? - kot-da-vinci
  • @ kot-da-vinci At the moment, the window (although the console would be enough, but redoing longer) - Kromster

4 answers 4

The clean kill option with TerminateProcess:
As mentioned above, this is not a very good option, but maybe you don’t need more, so I’ll give an example.

 program KillTest; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils, System.SyncObjs, WinApi.Windows; procedure DoWork(); begin Writeln('Worker'); while True do begin Sleep(500); end; end; procedure CreateWaitKillWorker(const ExeName: String); var Si: TStartupInfo; Pi: TProcessInformation; CommandLine: String; begin Writeln('Manager'); CommandLine := ExeName + ' -C'; Si := Default(TStartupInfo); Si.cb := sizeof(si); if CreateProcess(nil, PChar(CommandLine), nil, nil, false, CREATE_NEW_CONSOLE, nil, nil, Si, Pi) then begin try Sleep(5000); TerminateProcess(Pi.hProcess, 0); finally CloseHandle(Pi.hProcess); CloseHandle(Pi.hThread); end; end; Readln; end; var ChildProcExeName: String; begin try ChildProcExeName := ParamStr(0); if ParamCount > 0 then DoWork() else CreateWaitKillWorker(ChildProcExeName); except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; end. 

TEvent polite kill option:
This option is better if you need to close the child process more accurately.

 program KillTest; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils, System.SyncObjs, WinApi.Windows; procedure DoWork(); var Event: TEvent; begin Writeln('Worker'); Event := TEvent.Create(nil, True, False, 'Test'); try while Event.WaitFor(0) = TWaitResult.wrTimeout do begin Sleep(2000); end; finally Event.Free; end; end; procedure CreateWaitKillWorker(const ExeName: String); var Si: TStartupInfo; Pi: TProcessInformation; CommandLine: String; Event: TEvent; begin Writeln('Manager'); CommandLine := ExeName + ' -C'; Si := Default(TStartupInfo); Si.cb := sizeof(si); Event := TEvent.Create(nil, True, False, 'Test'); try if CreateProcess(nil, PChar(CommandLine), nil, nil, false, CREATE_NEW_CONSOLE, nil, nil, Si, Pi) then begin try Sleep(5000); Event.SetEvent(); //WaitForSingleObject(Pi.hProcess, INFINITE); // если не обходимо ждать finally CloseHandle(Pi.hProcess); CloseHandle(Pi.hThread); end; end; finally Event.Free; end; Readln; end; var ChildProcExeName: String; begin try ChildProcExeName := ParamStr(0); if ParamCount > 0 then DoWork() else CreateWaitKillWorker(ChildProcExeName); except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; end. 

PS: Examples in the console, but nothing prevents them from being used in the window application, although for windows I would prefer the option that described kot-da-vinci

  • It is difficult to choose which answer is better. I would answer all the answers correctly, but I will choose yours - it will not hurt you and turnips, and the TerminateProcess method is the easiest. - Kromster

For accurate completion, you need to provide some kind of interprocess communication (IPC).

One of the easiest ways is Windows messages.

To identify an employee, since the command line is used, you can send his identifier on the command line (a unique number, in this example, WORKER_ID )

If necessary, the manager sends a message

 PostMessage(HWND_BROADCAST, WM_STOPMYWORK, 0, WORKER_ID); 

In order not to tinker with the search window, a broadcast message is used here (for all top-level windows), but using it requires ( part of Remarks ) registering the message in the system (in both programs the same name) using RegisterWindowMessage.

If there is no window, you can create an invisible AllocateHWND function, providing it with a message handler function (see the TTimer sources in ExtCtrls.pas )

By accepting such a message, the employee checks that his identifier (the M.LParam message M.LParam matches WORKER_ID ), and makes a stop — if possible, immediately, if there is a recording — then sets a stop flag to be checked at the end of the record

Test code:

Manager:

 WM_STOPWORK: Integer procedure TForm1.Button1Click(Sender: TObject); begin PostMessage(HWND_BROADCAST, WM_STOPWORK, 0, 1); end; procedure TForm1.FormCreate(Sender: TObject); begin WM_STOPWORK := RegisterWindowMessage('WM_STOPWORK'); end; 

customer:

 procedure TForm1.ApplicationEvents1Message(var Msg: tagMSG; var Handled: Boolean); begin if (Msg.message = WM_STOPWORK) then begin if Msg.LParam = Worker_Id then if AState = 1 then AFlag := True else Close; Handled := True; end; end; procedure TForm1.FormCreate(Sender: TObject); begin WM_STOPWORK := RegisterWindowMessage('WM_STOPWORK'); end; procedure TForm1.Timer1Timer(Sender: TObject); begin AState := 1; Label1.Caption := 'Working'; Application.ProcessMessages; Sleep(3000); if AFlag then Close; AState := 2; Label1.Caption := 'Waiting'; end; 

    To close the application correctly, you need to send a WM_CLOSE message to its main window. To send a message to a window, you need to know its handle. If you yourself created these processes, then you already have the process ID ( CreateProcess returns it in the _PROCESS_INFORMATION structure). It remains to determine the handle of the main window by process ID. To do this, you can use the EnumWindows function, which iterates through all top-level windows, i.e. the main ones.

     type TProcessIDsArray = array of Cardinal; procedure TForm2.FormClick(Sender: TObject); var ProcessID: Cardinal; ProcessIDs: TProcessIDsArray; begin // для примера возьмем свой собственный ID процесса GetWindowThreadProcessId(Application.MainFormHandle, ProcessID); // задаем размер массива по количеству имеющихся ID процессов SetLength(ProcessIDs, 1); ProcessIDs[0] := ProcessID; // указатель на нашу функцию колбэка и параметр (наш массив с ID) EnumWindows(@EnumWindowsProc, lParam(ProcessIDs)); end; 

    The callback function itself, having already received the handle of the window, checks whether it belongs to one of our processes and, if so, sends a WM_CLOSE message to WM_CLOSE . Let's do a little hack. Since the second parameter passed our list of IDs, then the type of the second parameter in the signature of the callback function is not written by lParam , but by TProcessIDsArray , since the size of these types in bytes is the same.

     function EnumWindowsProc(WindowHandle: HWND; ProcessIDs: TProcessIDsArray): BOOL; stdcall; var ProcessID: Cardinal; i: Integer; begin Result := True; // будем перебирать все окна // получаем ID процесса по хэндлу его главного окна GetWindowThreadProcessId(WindowHandle, ProcessID); // сравниваем полученный ID с нашим списком for i := 0 to Length(ProcessIDs) - 1 do if ProcessID = ProcessIDs[i] then begin // если нашли, отправляем сообщение о закрытии окна PostMessage(WindowHandle, WM_CLOSE, 0, 0); Exit; // перебирать список процессов дальше не имеет смысла end; end; 
    • If you redo your workers into console applications, you will need to invent something else :) - kot-da-vinci

    You can use the inter-stream synchronization method by extending it to processes.

    For example through mutexes (Delphi 7):

     program Runner; uses Windows, SysUtils; const N_MUTEX = 'WRKRN'; var Index: Integer; Controls: array [0..4] of THandle; MutexName: string; StartupInfo: TStartupInfo; ProcessInfo: TProcessInformation; begin for Index := 0 to 4 do begin MutexName := N_MUTEX + IntToStr(Index); Controls[Index] := CreateMutex(nil, TRUE, PChar(MutexName)); FillChar(StartupInfo, SizeOf(TStartupInfo), 0); StartupInfo.cb := SizeOf(TStartupInfo); StartupInfo.dwFlags := STARTF_USESHOWWINDOW; StartupInfo.wShowWindow := SW_SHOWNORMAL; CreateProcess( nil, PChar('worker.exe ' + N_MUTEX + IntToStr(Index)), nil, nil, FALSE, 0, nil, PChar(ExtractFilePath(ParamStr(0))), StartupInfo, ProcessInfo); end; MessageBox(0, 'Press [Enter] to stop workers...', 'Action', MB_APPLMODAL or MB_OK); for Index := 0 to 4 do begin ReleaseMutex(Controls[Index]); CloseHandle(Controls[Index]); Sleep(500); { чтобы визуально видеть что работники отключаются по одному } end; end. 

    And the worker process:

     program Worker; {$APPTYPE CONSOLE} uses Windows, SysUtils; const WORK: array [0..3] of Char = ('-', '\', '|', '/'); var hMutex: THandle; WorkIdx: Integer; begin WorkIdx := 0; hMutex := CreateMutex(nil, FALSE, PChar(ParamStr(1))); { 250 для замедления работы, в реальном приложении - 0 } while WaitForSingleObject(hMutex, 250) = WAIT_TIMEOUT do begin Write(#8 + WORK[WorkIdx]); Inc(WorkIdx); if WorkIdx > 3 then WorkIdx := 0; end; CloseHandle(hMutex); WriteLn; WriteLn('Worker terminated! Press [Enter] to exit!'); ReadLn; end. 

    Error handling and closing handles remain independent.

    • In a real application, the loop with WaitForSingleObject must be placed in a separate stream and in no case should you pass 0 as a timeout. - zed
    • What is dictated by this? - Alekcvp
    • Performance considerations. When the thread is inside WaitForSingleObject it does not use a processor, so you need to emerge from this function as rarely as possible (as a timeout, you can generally specify INFINITE, so that control can return only on the expected event). If the timeout is zero, then the control returns instantly and the flow is about to waste the CPU with one hundred percent load. - zed
    • In this context, Wait is used to check the stop flag, this loop is analogous to: while not Terminated do SomeWork(); , therefore, there is also 0. It is precisely that waiting is not required. - Alekcvp