我可以发送一个ctrl-C(SIGINT)到Windows上的应用程序吗?
我有(过去)编写的跨平台(Windows / Unix)应用程序,从命令行启动时,以相同的方式处理用户键入的Ctrl – C组合(即干净地终止应用程序)。
是否有可能在Windows上发送一个Ctrl – C / SIGINT /等同于另一个(不相关的)进程的进程来请求它干净地终止(给它一个整理资源的机会等)?
SendSignal第三方应用程序最接近我的解决scheme。 作者列出了源代码和一个可执行文件。 我已经validation,它在64位窗口下工作(作为32位程序运行,杀死另一个32位程序),但我还没有想出如何将代码embedded到Windows程序(32位或64位)。
怎么运行的:
在debugging器中进行了大量的挖掘之后,我发现实际上与ctrl-break等信号相关的入口点是kernel32!CtrlRoutine。 该函数具有与ThreadProc相同的原型,因此可以直接与CreateRemoteThread一起使用,而不必注入代码。 但是,这不是一个导出的符号! 它在不同版本的Windows上有不同的地址(甚至有不同的名称)。 该怎么办?
这是我终于想出的解决scheme。 我为我的应用程序安装一个控制台ctrl处理程序,然后为我的应用程序生成一个ctrl-break信号。 当我的处理程序被调用时,我回头看堆栈的顶部,以查找传递给kernel32!BaseThreadStart的参数。 我抓住第一个参数,这是线程所需的起始地址,它是kernel32!CtrlRoutine的地址。 然后我从我的处理程序返回,表明我已经处理了信号,我的应用程序不应该被终止。 回到主线程,我等待kernel32!CtrlRoutine的地址被取回。 一旦得到它,我就在目标进程中用发现的起始地址创build一个远程线程。 这会导致目标进程中的ctrl句柄被评估,就像ctrl-break被按下一样!
好处是只有目标进程受到影响,并且任何进程(甚至是窗口进程)都可以作为目标。 一个缺点是我的小应用程序不能在batch file中使用,因为当它发送ctrl-break事件以便发现kernel32!CtrlRoutine的地址时,它将会终止它。
(如果在batch file中运行它,则以start
)。
我围绕这个话题做了一些研究,结果比我预想的更受欢迎。 KindDragon的回答是关键之一。
我在这个主题上写了一篇更长的博客文章 ,并创build了一个工作演示程序,演示了如何使用这种types的系统以几个不错的方式closures命令行应用程序。 这篇文章还列出了我在研究中使用的外部链接。
简而言之,这些演示程序执行以下操作:
- 使用.Net启动一个带有可视窗口的程序,用pinvoke隐藏,运行6秒,用pinvoke显示,用.Net停止。
- 使用.Net启动一个没有窗口的程序,运行6秒钟,停止附加控制台并发出ConsoleCtrlEvent
编辑:从KindDragon修改的解决scheme对于那些在这里和现在的代码感兴趣的人。 如果您打算在停止第一个程序之后启动其他程序,则应该重新启用Ctrl-C处理,否则下一个进程将inheritance父级的禁用状态,并且不会响应Ctrl-C。
[DllImport("kernel32.dll", SetLastError = true)] static extern bool AttachConsole(uint dwProcessId); [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] static extern bool FreeConsole(); // 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); // Must wait here. If we don't and re-enable Ctrl-C // handling below too fast, we might terminate ourselves. proc.WaitForExit(2000); FreeConsole(); //Re-enable Ctrl-C handling or any subsequently started //programs will inherit the disabled state. SetConsoleCtrlHandler(null, false); } }
此外,如果AttachConsole()
或发送的信号失败,则计划应急解决scheme,例如睡觉那么这个:
if (!proc.HasExited) { try { proc.Kill(); } catch (InvalidOperationException e){} }
我想我在这个问题上有点晚了,但是对于有同样问题的人,我还是会写点东西。 这与我给这个问题的答案是一样的。
我的问题是,我想我的应用程序是一个GUI应用程序,但执行的过程应该在后台运行,没有任何交互式控制台窗口附加。 我认为这个解决scheme也应该在父进程是一个控制台进程时工作。 你可能不得不删除“CREATE_NO_WINDOW”标志。
我设法解决这个使用GenerateConsoleCtrlEvent ()与包装的应用程序。 棘手的部分只是文档不清楚如何使用以及它的缺陷。
我的解决scheme是基于这里所描述的。 但是,这并没有真正解释所有的细节和一个错误,所以这里是如何让它工作的细节。
创build一个新的帮助程序“Helper.exe”。 此应用程序将位于您的应用程序(父级)和您想要closures的subprocess之间。 它也将创build实际的subprocess。 你必须有这个“中间人”过程或GenerateConsoleCtrlEvent()将失败。
使用某种IPC机制从父进程与辅助进程进行通信,帮助者应closuressubprocess。 当帮助者得到这个事件时,它调用closures自己和subprocess的“GenerateConsoleCtrlEvent(CTRL_BREAK,0)”。 我为这个自己使用了一个事件对象,当它想要取消subprocess时父进程完成。
要创build您的Helper.exe,请使用CREATE_NO_WINDOW和CREATE_NEW_PROCESS_GROUP进行创build。 而创buildsubprocess时创build它没有标志(0),这意味着它将派生从它的父母的控制台。 不这样做会导致它忽略事件。
每一步都是这样完成是非常重要的。 我一直在尝试所有不同types的组合,但是这个组合是唯一可行的组合。 您无法发送CTRL_C事件。 它将返回成功,但会被stream程忽略。 CTRL_BREAK是唯一有效的工具。 这并不重要,因为他们最后都会调用ExitProcess()。
你也不能直接调用GenerateConsoleCtrlEvent()和subprocessid的进程组id,直接允许helper进程继续生存。 这也会失败。
我花了一整天的时间试图让这个工作。 这个解决scheme适用于我,但如果有人有任何其他的补充,请做。 我走遍了networking,发现了许多有类似问题的人,但没有明确的解决scheme。 如何GenerateConsoleCtrlEvent()的作品也有点怪异,如果有人知道更多的细节,请分享。
不知怎的, GenerateConsoleCtrlEvent()
返回错误,如果您调用另一个进程,但您可以附加到另一个控制台应用程序并发送事件到所有subprocess。
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 }
编辑:
对于GUI应用程序来说,在Windows开发中处理这个问题的“常规”方法是将WM_CLOSE消息发送到进程的主窗口。
对于控制台应用程序,您需要使用SetConsoleCtrlHandler来添加CTRL_C_EVENT
。
如果应用程序没有兑现,您可以调用TerminateProcess 。
这应该是清楚的,因为目前不是。 有一个SendSignal的修改和编译版本发送Ctrl-C (默认情况下它只发送Ctrl + Break)。 这里有一些二进制文件:
(2014-3-7):我使用Ctrl-C构build了32位和64位版本,名为SendSignalCtrlC.exe,您可以通过以下url下载: https ://dl.dropboxusercontent.com/u/49065779/ 发送信号/ x86 / SendSignalCtrlC.exe https://dl.dropboxusercontent.com/u/49065779/sendsignalctrlc/x86_64/SendSignalCtrlC.exe – Juraj Michalak
我也反映了这些文件以防万一:
32位版本: https : //www.dropbox.com/s/r96jxglhkm4sjz2/SendSignalCtrlC.exe?dl = 0
64位版本: https : //www.dropbox.com/s/hhe0io7mcgcle1c/SendSignalCtrlC64.exe?dl = 0
免责声明:我没有build立这些文件。 没有修改编译的原始文件。 唯一testing的平台是64位Windows 7.build议您调整http://www.latenighthacking.com/projects/2003/sendSignal/上的源代码并自行编译。;
这是我在我的C ++应用程序中使用的代码。
积极的一点:
- 从控制台应用程序工程
- 从Windows服务工作
- 没有延迟要求
- 不closures当前的应用程序
负面点:
- 主控制台丢失,并创build一个新的控制台(请参阅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; }
用法示例:
DWORD dwProcessId = ...; if (signalCtrl(dwProcessId, CTRL_C_EVENT)) { cout << "Signal sent" << endl; }
在Java中,使用JNA和Kernel32.dll库,类似于C ++解决scheme。 将CtrlCSender主方法运行为一个Process,该进程只获取进程的控制台以发送Ctrl + C事件并生成事件。 由于它在没有控制台的情况下单独运行,因此不需要禁用和再次启用Ctrl + C事件。
CtrlCSender.java – 基于Nemo1024和KindDragon的答案。
给定一个已知的进程ID,这个无configuration的应用程序将连接目标进程的控制台,并在其上生成一个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); } }
主要应用程序 – 将CtrlCSender作为单独的无configuration进程运行
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();
我的一个朋友提出了一个完全不同的方式来解决这个问题,它为我工作。 使用下面的VBScript。 它启动和应用程序,让它运行7秒,并使用Ctrl + Cclosures它。
'VBScript示例
Set WshShell = WScript.CreateObject("WScript.Shell") WshShell.Run "notepad.exe" WshShell.AppActivate "notepad" WScript.Sleep 7000 WshShell.SendKeys "^C"
基于进程ID,我们可以发送信号来处理强制或优雅地终止或任何其他信号。
列出所有过程:
C:\>tasklist
杀死进程:
C:\>Taskkill /IM firefox.exe /F or C:\>Taskkill /PID 26356 /F
细节:
http://tweaks.com/windows/39559/kill-processes-from-command-prompt/
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); } }
我发现所有这些太复杂了,并且使用SendKeys来发送一个CTRL – C键盘命令行窗口(即cmd.exe窗口)作为一种解决方法。