简体   繁体   English

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

[英](C/C++, Windows) Issues with AttachConsole and FreeConsole only working once

I'm trying to send a ctrl+C signal to the console of another process when a button is clicked, by attaching the current process to its console, sending a ctrl+C signal, then freeing the current process from a console. 我试图在单击按钮时将ctrl + C信号发送到另一个进程的控制台,方法是将当前进程附加到其控制台,发送ctrl + C信号,然后从控制台释放当前进程。 This works fine the first time, but doesn't do anything the second time. 第一次可以正常运行,但是第二次不执行任何操作。

void abort(){
    AttachConsole(processInfo.dwProcessId); //processInfo is of type PROCESS_INFORMATION
    SetConsoleCtrlHandler(0, true);
    GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
    FreeConsole();
}

Also, even manually clicking in the console and pressing ctrl+C works the first time but not the second. 同样,即使在控制台中手动单击并按ctrl + C组合键也可以第一次但不是第二次使用。 Closing the console manually always works. 手动关闭控制台始终有效。

process.exe is a child process, and is created in exactly the same way as in this post . process.exe是一个子进程,其创建方式与本帖子完全相同。


Full code to recreate the problem (using Qt 4.8 with vs2010 compiler on windows 10 for gui/threading): 完整的代码来重新创建问题(在Windows 10上使用Qt 4.8与vs2010编译器进行gui /线程化):

main.cpp: 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: 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: 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

The UI just contains 2 buttons, startButton and abortButton. UI仅包含2个按钮,startButton和abortButton。 helloworld.exe is just a program that prints "Hello world!" helloworld.exe仅仅是一个打印“ Hello world!”的程序。 and then waits for input. 然后等待输入。 When startButton is clicked, the process is created, and the output is redirected to my program correctly. 单击startButton时,将创建该过程,并将输出正确重定向到我的程序。 When abortButton is clicked, the process is terminated correctly. 单击abortButton时,该过程将正确终止。 startProcess works perfectly the second time as well, but now, clicking abortButton does not work. startProcess第二次也可以正常运行,但是现在,单击abortButton不起作用。 The abort() function is called, and no error messages are printed, but the helloworld process fails to terminate, and the QThread t does not quit. 将调用abort()函数,并且不会输出任何错误消息,但是helloworld进程无法终止,并且QThread t不会退出。

Given the symptoms you describe, my psychic debugging powers tell me that the target process ("process.exe") is a child of your process. 给定您描述的症状,我灵敏的调试能力告诉我目标进程(“ process.exe”)是您进程的子级。 Assuming that to be the case, this is your problem: 假设情况如此,这是您的问题:

SetConsoleCtrlHandler(0, true);

As described in the documentation : 文档所述

If the HandlerRoutine parameter is NULL, a TRUE value causes the calling process to ignore CTRL+C input, and a FALSE value restores normal processing of CTRL+C input. 如果HandlerRoutine参数为NULL,则TRUE值将导致调用进程忽略CTRL + C输入,而FALSE值将恢复CTRL + C输入的正常处理。 This attribute of ignoring or processing CTRL+C is inherited by child processes. 子进程继承了忽略或处理CTRL + C的属性。

(Emphasis mine.) This means that the child processes are being launched with the option to ignore control-C already turned on. (强调)。这意味着正在启动子进程,并且可以选择忽略已经打开的control-C。

Instead of configuring the process to ignore control-C altogether, assign a control-C handler function that returns TRUE ; 而不是将进程配置为完全忽略control-C,而是分配一个返回TRUE的control-C处理函数。 such a handler will not be inherited by child processes. 这样的处理程序将不会被子进程继承。 Alternatively, if your application is single-threaded, you could just restore the normal configuration before exiting abort(): 另外,如果您的应用程序是单线程的,则可以在退出abort()之前恢复正常配置:

SetConsoleCtrlHandler(0, FALSE);

You have now posted a MCVE. 您现在已经发布了MCVE。 Note that I don't have Qt installed, so I had to simplify the code in order to test it. 请注意,我没有安装Qt,因此为了测试它,我不得不简化代码。 It was easy enough to reproduce the problem caused by the abort() function disabling control-C processing, but as soon as I corrected that, the code worked perfectly. 重现由abort()函数禁用Control-C处理引起的问题很容易,但是一旦我纠正了这一点,代码就可以正常工作。 Here is the simplified and corrected code, which works perfectly on my system: 这是经过简化和更正的代码,可以在我的系统上完美运行:

#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");
    }
}

Note that it proved necessary to reconfigure the handler routine after attaching to each new console, otherwise the parent process died along with the child when the control-C was generated. 请注意,事实证明有必要在连接到每个新控制台后重新配置处理程序例程,否则在生成control-C时,父进程与子进程一起死亡。 I'm not sure whether it is actually necessary to remove the handler routine before re-adding it, but it seems safest. 我不确定实际上是否有必要在重新添加处理程序例程之前将其删除,但这似乎是最安全的。

If your MCVE still doesn't work, please edit your post to show the corrected version. 如果您的MCVE仍然不起作用,请编辑您的帖子以显示更正的版本。 (There might be some sort of undesired interaction with Qt, I suppose.) (我想可能与Qt有某种不希望的交互。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM