我可以向 Windows 上的应用程序发送 ctrl-C (SIGINT) 吗?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/813086/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Can I send a ctrl-C (SIGINT) to an application on Windows?
提问by Matthew Murdoch
I have (in the past) written cross-platform (Windows/Unix) applications which, when started from the command line, handled a user-typed Ctrl-Ccombination in the same way (i.e. to terminate the application cleanly).
我已经(过去)写入了,在命令行启动时,处理用户输入的跨平台(Windows / Unix的)应用Ctrl-C以同样的方式组合(即彻底结束应用程序)。
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.)?
是否可以在 Windows 上向来自另一个(无关)进程的进程发送Ctrl- C/SIGINT/ 等效项,以请求它干净地终止(让它有机会整理资源等)?
采纳答案by arolson101
The closest that I've come to a solution is the SendSignal3rd party app. The author lists 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).
我最接近解决方案的是SendSignal3rd 方应用程序。作者列出了源代码和一个可执行文件。我已经验证它可以在 64 位 Windows 下工作(作为 32 位程序运行,杀死另一个 32 位程序),但我还没有弄清楚如何将代码嵌入到 Windows 程序(32 位或 64 位)。
How it works:
这个怎么运作:
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.
在调试器中进行大量挖掘后,我发现实际执行与 ctrl-break 等信号相关的行为的入口点是 kernel32!CtrlRoutine。该函数与 ThreadProc 具有相同的原型,因此可以直接与 CreateRemoteThread 一起使用,无需注入代码。但是,这不是导出的符号!它在不同版本的 Windows 上位于不同的地址(甚至有不同的名称)。该怎么办?
这是我最终想出的解决方案。我为我的应用程序安装了一个控制台 ctrl 处理程序,然后为我的应用程序生成一个 ctrl-break 信号。当我的处理程序被调用时,我回头查看堆栈顶部以找出传递给 kernel32!BaseThreadStart 的参数。我抓取第一个参数,它是线程所需的起始地址,也就是 kernel32!CtrlRoutine 的地址。然后我从我的处理程序返回,表明我已经处理了信号并且我的应用程序不应该被终止。回到主线程,我一直等到 kernel32!CtrlRoutine 的地址被检索到。一旦我得到它,我就会在目标进程中创建一个具有发现的起始地址的远程线程。这会导致目标进程中的 ctrl 处理程序被评估,就像按下了 ctrl-break 一样!
好消息是只有目标进程受到影响,任何进程(甚至是窗口进程)都可以成为目标。一个缺点是我的小应用程序不能在批处理文件中使用,因为它会在发送 ctrl-break 事件以发现 kernel32!CtrlRoutine 的地址时杀死它。
(Precede it with start
if running it in a batch file.)
(start
如果在批处理文件中运行它,则在它之前。)
回答by Stanislav
I have done some research around this topic, which turned out to be more popular than I anticipated. KindDragon's reply was one of the pivotal points.
我围绕这个话题做了一些研究,结果证明它比我预期的更受欢迎。KindDragon的回复是关键点之一。
I wrote a longer blog poston the topic and created a working demo program, which demonstrates using this type of system to close a command line application in a couple of nice fashions. That post also lists external links that I used in my research.
我写了一篇关于这个主题的更长的博客文章并创建了一个工作演示程序,它演示了使用这种类型的系统以几种不错的方式关闭命令行应用程序。该帖子还列出了我在研究中使用的外部链接。
In short, those demo programs do the following:
简而言之,这些演示程序执行以下操作:
- Start a program with a visible window using .Net, hide with pinvoke, run for 6 seconds, show with pinvoke, stop with .Net.
- Start a program without a window using .Net, run for 6 seconds, stop by attaching console and issuing ConsoleCtrlEvent
- 使用 .Net 启动带有可见窗口的程序,使用 pinvoke 隐藏,运行 6 秒,使用 pinvoke 显示,使用 .Net 停止。
- 使用 .Net 启动一个没有窗口的程序,运行 6 秒,通过附加控制台并发出 ConsoleCtrlEvent 来停止
Edit:The amended solution from KindDragon for those who are interested in the code here and now. If you plan to start other programs after stopping the first one, you should re-enable Ctrl-C handling, otherwise the next process will inherit the parent's disabled state and will not respond to Ctrl-C.
编辑:KindDragon 修改后的解决方案,适用于对这里和现在的代码感兴趣的人。如果您打算在停止第一个程序后启动其他程序,则应重新启用 Ctrl-C 处理,否则下一个进程将继承父进程的禁用状态,并且不会响应 Ctrl-C。
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool AttachConsole(uint dwProcessId);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern bool FreeConsole();
[DllImport("kernel32.dll")]
static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate HandlerRoutine, bool Add);
delegate bool ConsoleCtrlDelegate(CtrlTypes CtrlType);
// Enumerated type for the control messages sent to the handler routine
enum CtrlTypes : uint
{
CTRL_C_EVENT = 0,
CTRL_BREAK_EVENT,
CTRL_CLOSE_EVENT,
CTRL_LOGOFF_EVENT = 5,
CTRL_SHUTDOWN_EVENT
}
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GenerateConsoleCtrlEvent(CtrlTypes dwCtrlEvent, uint dwProcessGroupId);
public void StopProgram(Process proc)
{
//This does not require the console window to be visible.
if (AttachConsole((uint)proc.Id))
{
// Disable Ctrl-C handling for our program
SetConsoleCtrlHandler(null, true);
GenerateConsoleCtrlEvent(CtrlTypes.CTRL_C_EVENT, 0);
//Moved this command up on suggestion from Timothy Jannace (see comments below)
FreeConsole();
// Must wait here. If we don't and re-enable Ctrl-C
// handling below too fast, we might terminate ourselves.
proc.WaitForExit(2000);
//Re-enable Ctrl-C handling or any subsequently started
//programs will inherit the disabled state.
SetConsoleCtrlHandler(null, false);
}
}
Also, plan for a contingency solution if AttachConsole()
or the sent signal should fail, for instance sleeping then this:
此外,如果AttachConsole()
或发送的信号失败,则计划应急解决方案,例如睡眠,然后:
if (!proc.HasExited)
{
try
{
proc.Kill();
}
catch (InvalidOperationException e){}
}
回答by Shakta
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 thisquestion.
我想我在这个问题上有点晚了,但无论如何我都会为有同样问题的人写一些东西。这与我对这个问题给出的答案相同。
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.
我的问题是我希望我的应用程序是一个 GUI 应用程序,但执行的进程应该在后台运行,而不附加任何交互式控制台窗口。我认为当父进程是控制台进程时,这个解决方案也应该有效。不过,您可能必须删除“CREATE_NO_WINDOW”标志。
I managed to solve this using GenerateConsoleCtrlEvent() with a wrapper app. The tricky part is just that the documentation is not really clear on exactly how it can be used and the pitfalls with it.
我设法使用带有包装应用程序的GenerateConsoleCtrlEvent()解决了这个问题。棘手的部分只是文档并不清楚它的确切使用方式以及它的陷阱。
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.
创建一个新的帮助应用程序“Helper.exe”。此应用程序将位于您的应用程序(父)和您希望能够关闭的子进程之间。它还将创建实际的子进程。您必须有这个“中间人”进程,否则 GenerateConsoleCtrlEvent() 将失败。
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.
使用某种 IPC 机制从父进程向助手进程传达助手应该关闭子进程的信息。当助手获得此事件时,它会调用“GenerateConsoleCtrlEvent(CTRL_BREAK, 0)”,从而关闭自身和子进程。我为此自己使用了一个事件对象,当它想要取消子进程时,父进程会完成该对象。
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.
要创建 Helper.exe,请使用 CREATE_NO_WINDOW 和 CREATE_NEW_PROCESS_GROUP 创建它。在创建子进程时,创建它时没有标志 (0),这意味着它将从其父进程派生控制台。不这样做将导致它忽略该事件。
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.
像这样完成每一步是非常重要的。我一直在尝试各种不同的组合,但这种组合是唯一有效的组合。您不能发送 CTRL_C 事件。它会返回成功,但会被进程忽略。CTRL_BREAK 是唯一有效的方法。并不重要,因为它们最终都会调用 ExitProcess()。
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.
您也不能直接使用子进程 id 的进程分组 id 调用 GenerateConsoleCtrlEvent(),从而允许辅助进程继续存在。这也会失败。
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.
我花了一整天试图让这个工作。此解决方案适用于我,但如果有人要添加任何其他内容,请执行。我在网上找了很多有类似问题的人,但没有明确的问题解决方案。GenerateConsoleCtrlEvent() 的工作原理也有点奇怪,所以如果有人知道更多细节,请分享。
回答by Reed Copsey
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.
对于 GUI 应用程序,在 Windows 开发中处理此问题的“正常”方法是向进程的主窗口发送 WM_CLOSE 消息。
For a console app, you need to use SetConsoleCtrlHandlerto add a CTRL_C_EVENT
.
对于控制台应用程序,您需要使用SetConsoleCtrlHandler添加一个CTRL_C_EVENT
.
If the application doesn't honor that, you could call TerminateProcess.
如果应用程序不遵守这一点,您可以调用TerminateProcess。
回答by KindDragon
Somehow GenerateConsoleCtrlEvent()
return error if you call it for another process, but you can attach to another console application and send event to all child processes.
GenerateConsoleCtrlEvent()
如果您为另一个进程调用它,不知何故返回错误,但您可以附加到另一个控制台应用程序并将事件发送到所有子进程。
void SendControlC(int pid)
{
AttachConsole(pid); // attach to process console
SetConsoleCtrlHandler(NULL, TRUE); // disable Control+C handling for our app
GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0); // generate Control+C event
}
回答by 56ka
Here is the code I use in my C++ app.
这是我在 C++ 应用程序中使用的代码。
Positive points :
积极点:
- Works from console app
- Works from Windows service
- No delay required
- Does not close the current app
- 从控制台应用程序工作
- 从 Windows 服务工作
- 无需延迟
- 不关闭当前应用
Negative points :
负面因素:
- The main console is lost and a new one is created (see FreeConsole)
- The console switching give strange results...
- 主控制台丢失并创建了一个新控制台(请参阅FreeConsole)
- 控制台切换给出了奇怪的结果......
// Inspired from http://stackoverflow.com/a/15281070/1529139
// and http://stackoverflow.com/q/40059902/1529139
bool signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent)
{
bool success = false;
DWORD thisConsoleId = GetCurrentProcessId();
// Leave current console if it exists
// (otherwise AttachConsole will return ERROR_ACCESS_DENIED)
bool consoleDetached = (FreeConsole() != FALSE);
if (AttachConsole(dwProcessId) != FALSE)
{
// Add a fake Ctrl-C handler for avoid instant kill is this console
// WARNING: do not revert it or current program will be also killed
SetConsoleCtrlHandler(nullptr, true);
success = (GenerateConsoleCtrlEvent(dwCtrlEvent, 0) != FALSE);
FreeConsole();
}
if (consoleDetached)
{
// Create a new console if previous was deleted by OS
if (AttachConsole(thisConsoleId) == FALSE)
{
int errorCode = GetLastError();
if (errorCode == 31) // 31=ERROR_GEN_FAILURE
{
AllocConsole();
}
}
}
return success;
}
Usage example :
用法示例:
DWORD dwProcessId = ...;
if (signalCtrl(dwProcessId, CTRL_C_EVENT))
{
cout << "Signal sent" << endl;
}
回答by Тарик Ялауи
void SendSIGINT( HANDLE hProcess )
{
DWORD pid = GetProcessId(hProcess);
FreeConsole();
if (AttachConsole(pid))
{
// Disable Ctrl-C handling for our program
SetConsoleCtrlHandler(NULL, true);
GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0); // SIGINT
//Re-enable Ctrl-C handling or any subsequently started
//programs will inherit the disabled state.
SetConsoleCtrlHandler(NULL, false);
WaitForSingleObject(hProcess, 10000);
}
}
回答by BullyWiiPlaza
Yes. The windows-kill
project does exactly what you want:
是的。该windows-kill
项目正是您想要的:
windows-kill -SIGINT 1234
回答by ioikka
It should be made crystal clear because at the moment it isn't. There is a modified and compiled version of SendSignal to send Ctrl-C(by default it only sends Ctrl+Break). Here are some binaries:
应该说清楚,因为目前还不是。 有一个经过修改和编译的 SendSignal 版本来发送 Ctrl-C(默认情况下它只发送 Ctrl+Break)。以下是一些二进制文件:
(2014-3-7) : I built both 32-bit and 64-bit version with Ctrl-C, it's called SendSignalCtrlC.exe and you can download it at: https://dl.dropboxusercontent.com/u/49065779/sendsignalctrlc/x86/SendSignalCtrlC.exehttps://dl.dropboxusercontent.com/u/49065779/sendsignalctrlc/x86_64/SendSignalCtrlC.exe-- Juraj Michalak
(2014-3-7) :我用 Ctrl-C 构建了 32 位和 64 位版本,它被称为 SendSignalCtrlC.exe,您可以在以下位置下载它:https://dl.dropboxusercontent.com/u/49065779/ sendsignalctrlc/x86/SendSignalCtrlC.exe https://dl.dropboxusercontent.com/u/49065779/sendsignalctrlc/x86_64/SendSignalCtrlC.exe-- Juraj Michalak
I have also mirrored those files just in case:
32-bit version: https://www.dropbox.com/s/r96jxglhkm4sjz2/SendSignalCtrlC.exe?dl=0
64-bit version: https://www.dropbox.com/s/hhe0io7mcgcle1c/SendSignalCtrlC64.exe?dl=0
我还镜像了这些文件以防万一:
32 位版本:https: //www.dropbox.com/s/r96jxglhkm4sjz2/SendSignalCtrlC.exe?dl=0
64 位版本:https: //www.dropbox.com /s/hhe0io7mcgcle1c/SendSignalCtrlC64.exe?dl=0
Disclaimer: I didn't build those files. No modification was made to the compiled original files. The only platform tested is the 64-bit Windows 7. It is recommended to adapt the source available at http://www.latenighthacking.com/projects/2003/sendSignal/and compile it yourself.
免责声明:我没有构建这些文件。未对编译后的原始文件进行任何修改。测试的唯一平台是 64 位 Windows 7。建议改编http://www.latenighthacking.com/projects/2003/sendSignal/ 上提供的源代码并自行编译。
回答by Patimo
In Java, using JNA with the Kernel32.dll library, similar to a C++ solution. Runs the CtrlCSender main method as a Process which just gets the console of the process to send the Ctrl+C event to and generates the event. As it runs separately without a console the Ctrl+C event does not need to be disabled and enabled again.
在 Java 中,使用 JNA 和 Kernel32.dll 库,类似于 C++ 解决方案。将 CtrlCSender 主方法作为进程运行,它只是获取进程的控制台以将 Ctrl+C 事件发送到并生成事件。由于它在没有控制台的情况下单独运行,因此不需要禁用和再次启用 Ctrl+C 事件。
CtrlCSender.java- Based on Nemo1024's and KindDragon's answers.
CtrlCSender.java- 基于Nemo1024和KindDragon 的回答。
Given a known process ID, this consoless application will attach the console of targeted process and generate a CTRL+C Event on it.
给定一个已知的进程 ID,这个无控制台应用程序将附加目标进程的控制台并在其上生成一个 CTRL+C 事件。
import com.sun.jna.platform.win32.Kernel32;
public class CtrlCSender {
public static void main(String args[]) {
int processId = Integer.parseInt(args[0]);
Kernel32.INSTANCE.AttachConsole(processId);
Kernel32.INSTANCE.GenerateConsoleCtrlEvent(Kernel32.CTRL_C_EVENT, 0);
}
}
Main Application- Runs CtrlCSender as a separate consoless process
主应用程序- 将 CtrlCSender 作为单独的控制台进程运行
ProcessBuilder pb = new ProcessBuilder();
pb.command("javaw", "-cp", System.getProperty("java.class.path", "."), CtrlCSender.class.getName(), processId);
pb.redirectErrorStream();
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
Process ctrlCProcess = pb.start();
ctrlCProcess.waitFor();