I have recently worked on a windows application that consists of multiple services communicating with each other through D-Bus. Each service could run as a console app and during development this is how we were using them. The problem was that managing 7 console windows is not an easy task especially when you have to start and stop them frequently. This application I am posting now is a simple tool that allows running console apps in a tabbed layout, and allows shutting them down gracefully through sending them Ctrl-C.
Most of the application is quite trivial although the bit that sends the process a Ctrl-C took me quite some time to figure out. Unfortunately it seems that there is no elegant way to send a Ctrl-C to a process from code but more on this a bit later.
So this application is written in C# .NET using WinForms. It uses an XML config file which is called ConsoleLauncher.conf by default (if no other config file is specified as command line argument). The config file should look like this:
&lt;!-- Configuration file for the Console Launcher --&gt; &lt;ConsoleLauncherConfiguration&gt; &lt;Service Path=&quot;&quot; Executable=&quot;TestService.exe&quot; Arguments=&quot;&quot; ProcessName=&quot;Test Service 1&quot; StartDelay=&quot;2000&quot; /&gt; &lt;Service Path=&quot;&quot; Executable=&quot;TestService.exe&quot; Arguments=&quot;&quot; ProcessName=&quot;Test Service 2&quot; StartDelay=&quot;2000&quot; /&gt; &lt;Service Path=&quot;&quot; Executable=&quot;TestService.exe&quot; Arguments=&quot;&quot; ProcessName=&quot;Test Service 3&quot; StartDelay=&quot;2000&quot; /&gt; &lt;Service Path=&quot;&quot; Executable=&quot;TestService.exe&quot; Arguments=&quot;&quot; ProcessName=&quot;Test Service 4&quot; StartDelay=&quot;2000&quot; /&gt; &lt;/ConsoleLauncherConfiguration&gt;
The application creates a separate process from each <Service/> element. The Path, Executable and Arguments tags speak for themselves. The ProcessName tag defines what tag will appear on the tab in the application. StartDelay defines how much to wait before starting the process (in milliseconds). The processes are started in the same order as they appear in the config file, so if Service #2 has a StartDelay of 2 seconds it delays all the services that stand after in in the conf file. In the above example the fourth service starts with 8 seconds delay altogether.
This is how it looks when it is up and running:
The app can make difference between messages sent to stderr and stdout, stdout appears in white and stderr appears in yellow. It has an internal buffer of 1000 lines so that it will not start using huge amounts of memory if left running over the weekend. The size of the buffer can be changed in the source code (I think I should allow setting this size from the config file – probably in the next version).
The app offers three different ways of stopping each process:
- send Ctrl-C to the process,
- kill the process,
- kill all processes with the same name.
#3 is a safety feature, just in case one of the services could not stop before it offers a way to get rid of all running instances.
#1 is the most interesting part though, it offers a way to gracefully shut down your service by sending it a Ctrl-C keystroke. Now this is the least trivial feature of the application because it requires an external process to work properly.
On Windows sending a Ctrl-C can be done by the GenerateConsoleCtrlEvent syscall, but for some reason it does not work properly if you pass the PID of the target process to it. Instead you should attach to the console process by the AttachConsole syscall first, and then call GenerateConsoleCtrlEvent with PID=0 – but this will kill the sender process too unfortunately. So it appears that there is no other easy way around this, the Ctrl-C has to be sent from an external process if we want to keep our main application alive. This is why this applicaion has a secondary executable called SendCtrlC.exe. It takes the pid of the target app as a command line argument and sends a Ctrl-C to it.
Here is the main bit of the code:
// ... AttachConsole(processId); GenerateConsoleCtrlEvent(ConsoleCtrlEvent.CTRL_C, 0); // ...