Despite the fact that using GenerateConsoleCtrlEvent()
for sending Ctrl+C signal is the right answer, it needs significant clarification to get it to work in different .NET application types.
If your .NET application doesn't use its own console (Windows Forms/WPF/Windows Service/ASP.NET), the basic flow is:
- Attach the main .NET process to the console of the process that you want to signal with Ctrl+C.
- Prevent the main .NET process from stopping because of Ctrl+C event by disabling handling of the signal with
SetConsoleCtrlHandler()
.
- Generate the console event for the current console with
GenerateConsoleCtrlEvent()
(processGroupId
should be zero! The answer with code that sends p.SessionId
will not work and is incorrect).
- Wait for the signaled process to respond (e.g. by waiting for it to exit)
- Restore Ctrl+C handling by main process and disconnect from console.
The following code snippet illustrates how to do that:
Process p;
if (AttachConsole((uint)p.Id)) {
SetConsoleCtrlHandler(null, true);
try {
if (!GenerateConsoleCtrlEvent(CTRL_C_EVENT,0))
return false;
p.WaitForExit();
} finally {
SetConsoleCtrlHandler(null, false);
FreeConsole();
}
return true;
}
where SetConsoleCtrlHandler()
, FreeConsole()
, AttachConsole()
and GenerateConsoleCtrlEvent()
are native WinAPI methods:
internal const int CTRL_C_EVENT = 0;
[DllImport("kernel32.dll")]
internal static extern bool GenerateConsoleCtrlEvent(uint dwCtrlEvent, uint dwProcessGroupId);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool AttachConsole(uint dwProcessId);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
internal static extern bool FreeConsole();
[DllImport("kernel32.dll")]
static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate HandlerRoutine, bool Add);
// Delegate type to be used as the Handler Routine for SCCH
delegate Boolean ConsoleCtrlDelegate(uint CtrlType);
Note that waiting for the targeted process to respond, typically by waiting for the process to exit, is critical. Otherwise, the Ctrl+C signal will remain in the current process's input queue and when handling is restored by the second call to SetConsoleCtrlHandler()
, that signal will terminate the current process, rather than the targeted one.
Things become more complex if you need to send Ctrl+C from .NET console application. The above approach will not work because AttachConsole()
returns false
in this case (the main console app already has a console). It is possible to call FreeConsole()
before AttachConsole()
call, but doing so will result in the original .NET app console being lost, which is not acceptable in most cases.
Here is my solution for this case; it works and has no side effects for the .NET main process console:
- Create small supporting .NET console program that accepts process ID from command line arguments, loses its own console with
FreeConsole()
before the AttachConsole()
call and sends Ctrl+C to the target process with code mentioned above.
- The main .NET console process just invokes this utility in a new process when it needs to send Ctrl+C to another console process.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…