The story behind
The problem of starting and stopping console/command-line applications from a host program is much more widespread, than one would think at the first glance. Starting an application and even redirecting its output is relatively easy to achieve. Stopping it nicely, on the other hand, is hard. “Nicely” is the keyword here. One can always call Kill() member function of a Process object. Yet that would terminate the child process immediately, without giving it a chance to do any clean-up, which in some cases can be disastrous.
On a Unix environment, a programmer would typically issue a SIGINT and/or SIGTERM signal, before resorting to SIGKILL. Windows has something almost, but not entirely, like this. It can generate a ConsoleControlEvent, which translates from a Ctrl-C or Ctrl-Break key press. These events are sent not to an application, but to the console/terminal with witch an application is registered and then propagated to all handlers in each application until one of the applications acknowledges the event. So, in reality, one sends a termination signal to all application, which are started from/with a particular console, and not just to a specific application. This, too, can spell trouble.
A popular work-around, which almost never works is to locate the window handle of a process (this assumes that the command line process has a window, which is not always the case) and sending a series of messages, to it, simulating key-down and key-up events, mapping to the ‘Ctrl’ and to the ‘C’ keys on the keyboard.
I was aiming for this approach, until I stumbled across a more elegant, and seemingly more fool-proof solution. Each solution will be outlined and briefly explained in the list below. Download and study the source code for the actual implementation (all worker functions are concentrated in a static class in Experiments.cs). If you happen to have a working solution for the PostMessage scenario, please share it with the community though the comments in this post.
Source code: [download]
Outline of the scenarios
If the started application (
ping.exe) fails to stop (mainly as a result of your own experiments or of Scenario 2), hit the yellow Panic button to forcefully terminate it.
Only two scenarios represent real solutions to the problem – 1 and 4, with #4 being preferred. Scenario #2 was never completed due to time constraints (feel free to fill in the blanks ;)), while Scenario #3 is there for the sake of completeness.
- Start with a visible window using .Net, hide with pinvoke, run for 6 seconds, show with pinvoke, stop with .Net.
This method is a mix of .Net and pinvoke. It stops the process by closing its command window, similar to clicking the ‘X’ button in the upper right corner. This generates a Ctrl-C event to all programs, registered with that console. To find the window, it must exist and be visible. Here I use method
Experimentsclass, which uses .Net functionality. Alternatively I could have used a pinvoke method, which makes use of EnumWindows. This is implemented in
FindWindowHandleFromPid()in the Experiments class.
The downside of this method is that it briefly shows the commend window when starting and when stopping the process. Another problem is that a process takes long to close, it will be terminated. This method works fine for
ping, but for my needs it was terminating the process to quickly.
- Pinvoke only: Start with invisible window, get window handle, run for 6 seconds, stop using window handle. (Unfinished!)
It turned out that it was impossible to start a command window with an invisible, but still existing window using pinvoke CreateProcess. The start sequence ended up being the same as for Scenario 1, but bypassing .Net. Stopping a process with PostMessage did not work out either, even though I read of successful attempts. I abandoned this scenario as I did not want to use an inordinate amount of time on what started to look like a dead end.
- Brutal .Net: Start without window; run for 6 seconds; stop by killing the process.
This scenario is is here for completeness. This is the most brutal method, which relies entirely on .Net functionality. When stopping the process, it simply kills it, without giving it any chance to clean up. Note that
pingcommand does not display any summary when it is stopped in this fashion.
- Start without a window using .Net, run for 6 seconds, stop by attaching console and issuing ConsoleCtrlEvent.
After finding a hint at the bottom of this thread, I finally bit the bullet and started the process without a window. When the time comes to stop a process, the parent attaches its console to the child, stops itself listening to Ctrl-C event and issues the event to the console, so that the attached child terminates. It then reinstates the Ctrl-C handler for itself and frees the console.
During the research phase, I got ideas and inspiration from the following posts and sites. Some them were dead ends, while others lead to something usable. The post in particular (the first link) was a real revelation, and is incorporated in the fourth scenario in the demo application.
- Can I send a ctrl-C (SIGINT) to an application on Windows? – scroll to the bottom for the real answer from KindDragon!
- GenerateConsoleCtrlEvent function (Windows)
- Pinvoke.net – Look-up all the really useful Windows user32 and kernel32 functions, like CreateProcess, AttachConsole, SendMessage, PostMessage, and more.
- How do I trap ctrl-c in a C# console app
- Graceful shutdown of console apps in Win Vista, 7, 2008
- How to send CTRL+C signal to detached command-line process? – some really interesting ideas here!
- Process Creation Flags (Windows)
- .NET – WindowStyle = hidden vs. CreateNoWindow = true?
- Kill child process when parent process is killed
- How to send keys instead of characters to a process?
- Sending Ctrl+C to Apache
- c# PostMessage, RegisterHotkeys, FindWindow – problems!
- CreatePipe function (Windows)
- WM_KEYDOWN : how to use it?
- Send ctrl+c to a cmd.exe process in c# – An answer by Chris lower down in that thread was an inspiration for the second scenario
- A complete library – Windows Input Simulator (C# SendInput Wrapper – Simulate Keyboard and Mouse)
- SendSignal – A complete working program in C++, which utilises some intersting stack peeking techniques. With source.
- Controlling another process’s console with C#
- Kill child process when parent process is killed
- Working example of CreateJobObject/SetInformationJobObject pinvoke in .net?
- Sending CTRL-S message to a window
- post message – Visual C# General
- Send Ctrl+C to process