views:

3961

answers:

4

I have (in the past) written cross-platform (Windows/Unix) applications which, when started from the command line, handled a user-typed Ctrl-C combination in the same way (i.e. to terminate the application cleanly).

Is it possible on Windows to send a Ctrl-C/SIGINT/equivalent to a process from another (unrelated) process to request that it terminate cleanly (giving it an opportunity to tidy up resources etc.)?

+1  A: 

Edit:

For a GUI App, the "normal" way to handle this in Windows development would be to send a WM_CLOSE message to the process's main window.

For a console app, you need to use SetConsoleCtrlHandler to add a CTRL_C_EVENT.

If the application doesn't honor that, you could call TerminateProcess.

Reed Copsey
I'm wanting to do this in a command-line app (no windows). TerminateProcess is a force kill mechanism (similar to SIGKILL) so doesn't give the terminating application any opportunity to clean up.
Matthew Murdoch
@Matthew Murdoch: Updated to include information on how to handle this in a console app. You can also catch other events, including windows shutdown, or console closed, etc, via the same mechanism. See MSDN for full details.
Reed Copsey
@Reed Copsey: But I'd like to be initiate this from a separate process. I've had a look at GenerateConsoleCtrlEvent (referenced from SetConsoleCtrlHandler) but this seems to only work if the process being terminated is in the same 'process group' as the process doing the terminating... Any ideas?
Matthew Murdoch
Matthew: The only idea I'd have would be to find the process, find it's parent (the console), get the parent's main window handle (the console) and use SendKeys to send a Ctrl+C directly to it. That should cause your console process to receive a CTRL_C_EVENT. Haven't tried it, though - not sure how well it'd work.
Reed Copsey
+2  A: 

The closest that I've come to a solution is SendSignal. The author has source code and an executable. I've verified that it works under 64-bit windows (running as a 32-bit program, killing another 32-bit program), but I've not figured out how to embed the code into a windows program (either 32-bit or 64-bit).

FTA:

After much digging around in the debugger I discovered that the entry point that actually does the behavior associated with a signal like ctrl-break is kernel32!CtrlRoutine. The function had the same prototype as ThreadProc, so it can be used with CreateRemoteThread directly, without having to inject code. However, that's not an exported symbol! It's at different addresses (and even has different names) on different versions of Windows. What to do?

Here is the solution I finally came up with. I install a console ctrl handler for my app, then generate a ctrl-break signal for my app. When my handler gets called, I look back at the top of the stack to find out the parameters passed to kernel32!BaseThreadStart. I grab the first param, which is the desired start address of the thread, which is the address of kernel32!CtrlRoutine. Then I return from my handler, indicating that I have handled the signal and my app should not be terminated. Back in the main thread, I wait until the address of kernel32!CtrlRoutine has been retrieved. Once I've got it, I create a remote thread in the target process with the discovered start address. This causes the ctrl handlers in the target process to be evaluated as if ctrl-break had been pressed!

The nice thing is that only the target process is affected, and any process (even a windowed process) can be targeted. One downside is that my little app can't be used in a batch file, since it will kill it when it sends the ctrl-break event in order to discover the address of kernel32!CtrlRoutine.

arolson101
+1 - The linked code uses Ctrl-Break rather than Ctrl-C but on the face of it (looking at the documentation for GenerateConsoleCtrlEvent) could be adapted for Ctrl-C.
Matthew Murdoch
+2  A: 

I guess I'm a bit late on this question but I'll write something anyway for anyone having the same problem. This is the same answer as I gave to this question.

My problem was that I'd like my application to be a GUI application but the processes executed should be run in the background without any interactive console window attached. I think this solution should also work when the parent process is a console process. You may have to remove the "CREATE_NO_WINDOW" flag though.

I managed to solve this using GenerateConsoleCtrlEvent(). The tricky part is just that the documentation is not really clear on exactly how it can be used and the pitfalls with it.

My solution is based on what is described here. But that didn't really explain all the details either and with an error, so here is the details on how to get it working.

Create a new helper application "Helper.exe". This application will sit between your application (parent) and the child process you want to be able to close. It will also create the actual child process. You must have this "middle man" process or GenerateConsoleCtrlEvent() will fail.

Use some kind of IPC mechanism to communicate from the parent to the helper process that the helper should close the child process. When the helper get this event it calls "GenerateConsoleCtrlEvent(CTRL_BREAK, 0)" which closes down itself and the child process. I used an event object for this myself which the parent completes when it wants to cancel the child process.

To create your Helper.exe create it with CREATE_NO_WINDOW and CREATE_NEW_PROCESS_GROUP. And when creating the child process create it with no flags (0) meaning it will derive the console from its parent. Failing to do this will cause it to ignore the event.

It is very important that each step is done like this. I've been trying all different kinds of combinations but this combination is the only one that works. You can't send a CTRL_C event. It will return success but will be ignored by the process. CTRL_BREAK is the only one that works. Doesn't really matter since they will both call ExitProcess() in the end.

You also can't call GenerateConsoleCtrlEvent() with a process groupd id of the child process id directly allowing the helper process to continue living. This will fail as well.

I spent a whole day trying to get this working. This solution works for me but if anyone has anything else to add please do. I went all over the net finding lots of people with similar problems but no definite solution to the problem. How GenerateConsoleCtrlEvent() works is also a bit weird so if anyone knows more details on it please share.

Shakta
A: 

All good suggestions that answer the question. My situation is a bit different. I have an already-existing Windows console app (Mongoose.exe) that runs forever, pretending it's a Service.

I want to write a Batch/Command file that can close this window (remember that it can call rundll32.exe, which is part of Windows). I don't want to waste my time designing and debugging a C program. Ideally, I'd like to send the equivalent of a user-typed Ctrl+C to the console window. If that is not possible, I'd like to kill the process (its threads will stop automatically).

Thanks, David Spector

You'll probably get a better response by asking this as a new question. You can always add a link back to this one if you think it's relevant.
Matthew Murdoch