当点击一个按钮时,我试图将ctrl + C信号发送到另一个进程的控制台,方法是将当前进程附加到其控制台,发送ctrl + C信号,然后释放当前进程一个控制台。这在第一次工作正常,但第二次没有做任何事情。
void abort(){
AttachConsole(processInfo.dwProcessId); //processInfo is of type PROCESS_INFORMATION
SetConsoleCtrlHandler(0, true);
GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
FreeConsole();
}
此外,即使手动点击控制台并按ctrl + C也可以第一次使用而不是第二次。手动关闭控制台始终有效。
process.exe是一个子进程,其创建方式与this post完全相同。
重新创建问题的完整代码(在Windows 10上使用Qt 4.8和vs2010编译器进行gui / threading):
main.cpp中:
#include "dialog.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Dialog w;
w.show();
return a.exec();
}
dialog.h:
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include "ui_dialog.h"
#include "worker.h"
namespace Ui {
class Dialog;
}
class Dialog : public QDialog
{
Q_OBJECT
public:
explicit Dialog(QWidget *parent = 0):
QDialog(parent),
ui(new Ui::Dialog),
w(new worker()),
t(new QThread(this))
{
ui->setupUi(this);
connect(ui->startButton, SIGNAL(clicked(bool)), this, SLOT(startProcess()));
connect(ui->abortButton, SIGNAL(clicked(bool)), this, SLOT(abortProcess()));
connect(w, SIGNAL(finished()), t, SLOT(quit()));
w->setup(t);
w->moveToThread(t);
}
~Dialog(){delete ui;}
private slots:
void startProcess(){
t->start();
}
void abortProcess(){
w->abort();
t->quit();
}
private:
Ui::Dialog *ui;
worker *w;
QThread *t;
};
#endif // DIALOG_H
worker.h:
#ifndef WORKER_H
#define WORKER_H
#include <QDebug>
#include <QObject>
#include <QThread>
#include <Windows.h>
class worker : public QObject
{
Q_OBJECT
public:
explicit worker(QObject *parent = 0):QObject(parent){}
void setup(QThread *t){
connect(t, SIGNAL(started()), this, SLOT(startProcess()));
}
void abort(){
if(!AttachConsole(p.dwProcessId)) qDebug() << "AttachConsole" << GetLastError();
if(!SetConsoleCtrlHandler(0, true)) qDebug() << "SetConsoleCtrlHandler" << GetLastError();
if(!GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0)) qDebug() << "GenerateConsoleCtrlEvent" << GetLastError();
if(!FreeConsole()) qDebug() << "FreeConsole" << GetLastError();
}
private slots:
void startProcess(){
ZeroMemory(&p, sizeof(p));
STARTUPINFOA s;
SECURITY_ATTRIBUTES sec;
HANDLE read = NULL;
HANDLE write = NULL;
ZeroMemory(&sec, sizeof(sec));
sec.nLength = sizeof(SECURITY_ATTRIBUTES);
sec.bInheritHandle = true;
sec.lpSecurityDescriptor = NULL;
if(!CreatePipe(&read, &write, &sec, 0)) qDebug() << "CreatePipe error" << GetLastError();
if(!SetHandleInformation(read, HANDLE_FLAG_INHERIT, 0)) qDebug() << "SetHandleInformation error" << GetLastError();
ZeroMemory(&s, sizeof(s));
s.cb = sizeof(s);
s.hStdOutput = write;
s.hStdError = write;
s.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
s.dwFlags |= STARTF_USESTDHANDLES;
if(!CreateProcessA("helloworld.exe", "helloworld", NULL, NULL, true, CREATE_NO_WINDOW, NULL, NULL, &s, &p)) qDebug() << "CreateProcessA error " << GetLastError() << "\n";
CloseHandle(write);
char buff[65];
DWORD bytesRead;
while(true){
if(!ReadFile(read, buff, 64, &bytesRead, NULL)){
qDebug() << "ReadFile error";
break;
}
if(bytesRead > 0){
buff[64] = '\0';
qDebug() << QByteArray(buff);
}
}
CloseHandle(p.hProcess);
CloseHandle(p.hThread);
emit finished();
}
private:
PROCESS_INFORMATION p;
signals:
void finished();
};
#endif // WORKER_H
UI只包含2个按钮,startButton和abortButton。 helloworld.exe只是一个打印&#34; Hello world!&#34;然后等待输入。单击startButton时,将创建该进程,并将输出正确地重定向到我的程序。单击abortButton时,进程将正确终止。 startProcess也是第二次完美运行,但是现在,单击abortButton不起作用。调用abort()函数,并且不打印任何错误消息,但helloworld进程无法终止,并且QThread t不会退出。
答案 0 :(得分:2)
鉴于您描述的症状,我的通灵调试能力告诉我目标进程(“process.exe”)是您进程的子进程。假设情况确实如此,那就是你的问题:
SetConsoleCtrlHandler(0, true);
如the documentation中所述:
如果HandlerRoutine参数为NULL,则TRUE值会导致调用进程忽略CTRL + C输入,而FALSE值会恢复CTRL + C输入的正常处理。 忽略或处理CTRL + C的此属性由子进程继承。
(强调我的。)这意味着正在启动子进程,并选择忽略已经打开的control-C。
不是将进程配置为完全忽略control-C,而是分配一个返回TRUE
的control-C处理函数;这样的处理程序不会被子进程继承。或者,如果您的应用程序是单线程的,您可以在退出abort()之前恢复正常配置:
SetConsoleCtrlHandler(0, FALSE);
您现在已发布了MCVE。请注意,我没有安装Qt,所以我必须简化代码才能测试它。很容易重现由abort()函数禁用control-C处理引起的问题,但是一旦我纠正了它,代码就能完美地工作。这是简化和更正的代码,它在我的系统上完美运行:
#include <Windows.h>
void fail(char *msg)
{
MessageBoxA(NULL, msg, "Oops", MB_OK);
ExitProcess(1);
}
PROCESS_INFORMATION p;
BOOL WINAPI HandlerRoutine(
_In_ DWORD dwCtrlType
)
{
if (dwCtrlType == CTRL_C_EVENT) return TRUE;
return FALSE;
}
void abortProcess(void)
{
static BOOL handler_assigned = FALSE;
if (handler_assigned)
{
if (!SetConsoleCtrlHandler(HandlerRoutine, false)) fail("Removing handler routine failed");
}
if (!AttachConsole(p.dwProcessId)) fail("AttachConsole");
if (!SetConsoleCtrlHandler(HandlerRoutine, true)) fail("SetConsoleCtrlHandler");
handler_assigned = TRUE;
if (!GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0)) fail("GenerateConsoleCtrlEvent");
if (!FreeConsole()) fail("FreeConsole");
}
DWORD WINAPI startProcess(LPVOID * dummy)
{
ZeroMemory(&p, sizeof(p));
STARTUPINFOA s;
SECURITY_ATTRIBUTES sec;
HANDLE read = NULL;
HANDLE write = NULL;
ZeroMemory(&sec, sizeof(sec));
sec.nLength = sizeof(SECURITY_ATTRIBUTES);
sec.bInheritHandle = true;
sec.lpSecurityDescriptor = NULL;
if(!CreatePipe(&read, &write, &sec, 0)) fail("CreatePipe error");
if(!SetHandleInformation(read, HANDLE_FLAG_INHERIT, 0)) fail("SetHandleInformation error");
ZeroMemory(&s, sizeof(s));
s.cb = sizeof(s);
s.hStdOutput = write;
s.hStdError = write;
s.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
s.dwFlags |= STARTF_USESTDHANDLES;
if(!CreateProcessA("test1.exe", "helloworld", NULL, NULL, true, CREATE_NO_WINDOW, NULL, NULL, &s, &p))
{
fail("CreateProcessA error ");
}
CloseHandle(write);
char buff[65];
DWORD bytesRead;
while(true){
if(!ReadFile(read, buff, 64, &bytesRead, NULL)){
DWORD dw = GetLastError();
if (dw == 0x0000006d) break;
fail("ReadFile error");
}
if(bytesRead > 0){
buff[64] = '\0';
MessageBoxA(NULL, buff, "Output", MB_OK);
}
}
MessageBox(NULL, L"Child has exited", L"Good news", MB_OK);
CloseHandle(p.hProcess);
CloseHandle(p.hThread);
return 0;
}
int CALLBACK WinMain(
_In_ HINSTANCE hInstance,
_In_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine,
_In_ int nCmdShow
)
{
HANDLE thread;
for (;;)
{
MessageBox(NULL, L"Press OK to launch child", L"so39616404", MB_OK);
thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)startProcess, NULL, 0, NULL);
if (thread == NULL) fail("CreateThread error");
MessageBox(NULL, L"Press OK to kill child", L"so39616404", MB_OK);
abortProcess();
if (WaitForSingleObject(thread, INFINITE) != WAIT_OBJECT_0) fail("Wait error");
}
}
请注意,在附加到每个新控制台之后,必须重新配置处理程序例程,否则父进程会在生成control-C时与子进程一起死亡。我不确定在重新添加之前是否有必要删除处理程序例程,但它似乎最安全。
如果您的MCVE仍无效,请编辑您的帖子以显示更正后的版本。 (我想,可能与Qt发生某种不希望的互动。)