我已经(过去)写入了,在命令行启动时,处理用户输入的跨平台(Windows / Unix的)应用Ctrl-C以同样的方式组合(即彻底结束应用程序)。
在Windows上是否可以从另一个(不相关的)进程中向一个进程发送Ctrl- C/ SIGINT /等效项,以请求该进程完全终止(从而使它有机会整理资源等)?
我已经(过去)写入了,在命令行启动时,处理用户输入的跨平台(Windows / Unix的)应用Ctrl-C以同样的方式组合(即彻底结束应用程序)。
在Windows上是否可以从另一个(不相关的)进程中向一个进程发送Ctrl- C/ SIGINT /等效项,以请求该进程完全终止(从而使它有机会整理资源等)?
Answers:
我最接近解决方案的是SendSignal 3rd party应用程序。作者列出了源代码和可执行文件。我已经验证它可以在64位Windows(可以作为32位程序运行,杀死另一个32位程序)下运行,但是我还没有弄清楚如何将代码嵌入Windows程序(可以是32位)或64位)。
这个怎么运作:
在调试器中进行了大量研究之后,我发现实际上执行与ctrl-break之类的信号相关的行为的入口点是kernel32!CtrlRoutine。该函数与ThreadProc具有相同的原型,因此可以直接与CreateRemoteThread一起使用,而无需注入代码。但是,这不是导出的符号!在不同版本的Windows上,它位于不同的地址(甚至具有不同的名称)。该怎么办?
这是我最终想出的解决方案。我为我的应用安装了控制台ctrl处理程序,然后为我的应用生成了ctrl-break信号。当我的处理程序被调用时,我回头查看堆栈的顶部以查找传递给kernel32!BaseThreadStart的参数。我抓取了第一个参数,它是线程的所需起始地址,即kernel32!CtrlRoutine的地址。然后,我从处理程序中返回,表明我已经处理了信号,并且我的应用程序不应终止。回到主线程中,我等到已检索kernel32!CtrlRoutine的地址。一旦找到它,便在目标进程中使用发现的起始地址创建一个远程线程。这将导致对目标进程中的ctrl处理程序进行评估,就像按下了ctrl-break一样!
令人高兴的是,只有目标进程受到影响,任何进程(甚至是窗口进程)都可以成为目标。一个缺点是我的小应用程序不能在批处理文件中使用,因为它会在发送ctrl-break事件以发现kernel32!CtrlRoutine的地址时将其杀死。
(start
如果在批处理文件中运行它,请先添加。)
我已经围绕该主题进行了一些研究,结果比我预期的要受欢迎。KindDragon的答复是关键点之一。
我写了一篇有关该主题的更长的博客文章,并创建了一个有效的演示程序,该程序演示了如何使用这种系统以几种不错的方式关闭命令行应用程序。该帖子还列出了我在研究中使用的外部链接。
简而言之,这些演示程序执行以下操作:
编辑: 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);
}
}
另外,如果AttachConsole()
或发送的信号应该失败,请计划应急解决方案,例如休眠,然后执行以下操作:
if (!proc.HasExited)
{
try
{
proc.Kill();
}
catch (InvalidOperationException e){}
}
wait()
我删除了重新启用Ctrl-C(SetConsoleCtrlHandler(null, false);
)。我做了一些测试(多次调用,也对已经终止的进程进行了测试),但没有发现任何副作用(还可以吗?)。
我想我在这个问题上有点迟了,但我还是会为遇到相同问题的任何人写点东西。这与我对这个问题的回答相同。
我的问题是我希望我的应用程序是GUI应用程序,但是执行的进程应该在没有附加任何交互式控制台窗口的情况下在后台运行。我认为当父进程是控制台进程时,此解决方案也应该起作用。不过,您可能必须删除“ CREATE_NO_WINDOW”标志。
我设法通过带有包装器应用程序的GenerateConsoleCtrlEvent()解决了这个问题。棘手的是,文档尚不清楚如何使用以及存在的陷阱。
我的解决方案基于此处描述的内容。但这并没有真正解释所有细节,并且有错误,因此这里是有关如何使其工作的细节。
创建一个新的帮助程序“ Helper.exe”。该应用程序将位于您的应用程序(父级)和您希望能够关闭的子进程之间。它还将创建实际的子进程。您必须具有此“中间人”进程,否则GenerateConsoleCtrlEvent()将失败。
使用某种IPC机制从父级与帮助程序进程进行通信,即帮助程序应关闭子进程。当帮助程序收到此事件时,它将调用“ GenerateConsoleCtrlEvent(CTRL_BREAK,0)”,它会关闭自身和子进程。我自己为此使用了一个事件对象,当它想取消子进程时,父进程会完成该事件。
要创建Helper.exe,请使用CREATE_NO_WINDOW和CREATE_NEW_PROCESS_GROUP创建它。在创建子进程时,请不要创建任何标志(0),这意味着它将从其父级派生控制台。否则,将忽略事件。
如此完成每个步骤非常重要。我一直在尝试各种不同的组合,但是这种组合是唯一可行的组合。您无法发送CTRL_C事件。它会返回成功,但将被该过程忽略。CTRL_BREAK是唯一可行的工具。没关系,因为它们最后都会调用ExitProcess()。
您也不能直接使用子进程ID的进程分组ID调用GenerateConsoleCtrlEvent(),以允许助手进程继续运行。这也会失败。
我花了整整一天的时间来使它工作。此解决方案对我有用,但是如果有人要添加其他内容,请执行。我四处走动,发现很多有类似问题的人,但没有确定的解决方案。GenerateConsoleCtrlEvent()的工作方式也有些怪异,因此,如果有人知道它的更多详细信息,请分享。
编辑:
对于GUI App,在Windows开发中处理此问题的“常规”方法是将WM_CLOSE消息发送到进程的主窗口。
对于控制台应用程序,您需要使用SetConsoleCtrlHandler添加CTRL_C_EVENT
。
如果应用程序不遵守该要求,则可以调用TerminateProcess。
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
}
这是我在C ++应用程序中使用的代码。
积极点:
缺点:
// 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;
}
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);
}
}
应该使它非常清晰,因为目前还不是。 有一个SendSignal的修改和编译版本,用于发送Ctrl-C(默认情况下,它仅发送Ctrl + Break)。以下是一些二进制文件:
(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
我还镜像了这些文件,以防万一:
32位版本:https : //www.dropbox.com/s/r96jxglhkm4sjz2/SendSignalCtrlC.exe?dl=0
64位版本:https : //www.dropbox.com /s/hhe0io7mcgcle1c/SendSignalCtrlC64.exe?dl=0
免责声明:我没有构建那些文件。未对已编译的原始文件进行任何修改。唯一经过测试的平台是64位Windows7。建议改编http://www.latenighthacking.com/projects/2003/sendSignal/上提供的源,并自行进行编译。
在Java中,将JNA与Kernel32.dll库一起使用,类似于C ++解决方案。将CtrlCSender main方法作为一个进程运行,它仅获取该进程的控制台以将Ctrl + C事件发送到并生成该事件。由于它无需控制台即可单独运行,因此Ctrl + C事件无需禁用并再次启用。
CtrlCSender.java-基于Nemo1024和KindDragon的答案。
给定已知的进程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);
}
}
主应用程序-将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();
是。该windows-kill
项目正是您想要的:
windows-kill -SIGINT 1234
我从这里找到的解决方案如果您在命令行中可以使用python 3.x,非常简单。首先,保存包含以下内容的文件(ctrl_c.py):
import ctypes
import sys
kernel = ctypes.windll.kernel32
pid = int(sys.argv[1])
kernel.FreeConsole()
kernel.AttachConsole(pid)
kernel.SetConsoleCtrlHandler(None, 1)
kernel.GenerateConsoleCtrlEvent(0, 0)
sys.exit(0)
然后致电:
python ctrl_c.py 12345
如果那行不通,我建议尝试Windows-kill项目:https : //github.com/alirdn/windows-kill
我的一个朋友提出了一种解决问题的完全不同的方法,它为我工作。使用如下的vbscript。它启动并运行,让它运行7秒钟,然后使用ctrl + c关闭它。
'VBScript示例
Set WshShell = WScript.CreateObject("WScript.Shell")
WshShell.Run "notepad.exe"
WshShell.AppActivate "notepad"
WScript.Sleep 7000
WshShell.SendKeys "^C"
// Send [CTRL-C] to interrupt a batch file running in a Command Prompt window, even if the Command Prompt window is not visible,
// without bringing the Command Prompt window into focus.
// [CTRL-C] will have an effect on the batch file, but not on the Command Prompt window itself -- in other words,
// [CTRL-C] will not have the same visible effect on a Command Prompt window that isn't running a batch file at the moment
// as bringing a Command Prompt window that isn't running a batch file into focus and pressing [CTRL-C] on the keyboard.
ulong ulProcessId = 0UL;
// hwC = Find Command Prompt window HWND
GetWindowThreadProcessId (hwC, (LPDWORD) &ulProcessId);
AttachConsole ((DWORD) ulProcessId);
SetConsoleCtrlHandler (NULL, TRUE);
GenerateConsoleCtrlEvent (CTRL_C_EVENT, 0UL);
SetConsoleCtrlHandler (NULL, FALSE);
FreeConsole ();
SIGINT可以使用windows-kill通过语法发送到程序windows-kill -SIGINT PID
,其中PID
可以通过Microsoft的pslist获得。
关于捕获SIGINT,如果您的程序使用Python,则可以像在此解决方案中一样实现SIGINT处理/捕获。
根据进程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/
jstack
可以可靠地代替此特定事项使用:stackoverflow.com/a/47723393/603516