(C / C ++,Windows)AttachConsole和FreeConsole的问题只能使用一次

时间:2016-09-21 12:19:54

标签: c++ windows winapi signals

当点击一个按钮时,我试图将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不会退出。

1 个答案:

答案 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发生某种不希望的互动。)