在 Windows 上向 QProcess 发送 Ctrl+C

Posted

技术标签:

【中文标题】在 Windows 上向 QProcess 发送 Ctrl+C【英文标题】:Sending a Ctrl+C to a QProcess on Windows 【发布时间】:2020-11-09 22:50:57 【问题描述】:

抓住你的马鞍,这是一个长的!如果您不想阅读所有内容,请跳至“MCVE”部分。

我正在尝试使一个以QProcess 优雅退出的进程开始。我不控制有问题的进程如何退出,它只接受 Ctrl+C 信号。让我感到困惑的是,这在QProcess 的 API 中听起来非常简单明了。然而,我在这里:D

这是我目前得到的:

就像我说的,QProcess 并不真正支持这一点。所以我必须深入 Windows 生态系统并尝试在本地实现它。我在Microsoft Docs 中找到了GenerateConsoleCtrlEvent。它似乎完全符合我的需要,所以我尝试使用它。在努力处理 Windows API 中的错误消息之后,这就是我得到的:

QProcess myprocess = new QProcess(this);
myprocess->setReadChannel(QProcess::StandardOutput);

// I'm sorry that I have to be vague here. I can't really share this part.
myprocess->start("myexec", "arg1", "arg2");

//...

auto success = GenerateConsoleCtrlEvent(CTRL_C_EVENT, myprocess->pid()->dwProcessId);
if (!success) 
    LPVOID lpMsgBuf;
    auto err = GetLastError();

    FormatMessage(
            FORMAT_MESSAGE_ALLOCATE_BUFFER |
            FORMAT_MESSAGE_FROM_SYSTEM |
            FORMAT_MESSAGE_IGNORE_INSERTS,
            nullptr,
            err,
            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
            reinterpret_cast<LPTSTR>(&lpMsgBuf),
            0, nullptr );

    // probably could have used wcerr, but after making this work I was happy enough with it :D
    auto error_string = QString::fromWCharArray((reinterpret_cast<LPTSTR>(lpMsgBuf)));
    std::cerr << error_string.toStdString();
    LocalFree(lpMsgBuf);

这只是将the handle is invalid. 打印到标准错误。我有点期待它,因为GenerateConsoleCtrlEvent 的文档说:

dwProcessGroupId [in] 接收信号的进程组的标识符。当 CREATE_NEW_PROCESS_GROUP 标志在对 CreateProcess 函数的调用中指定时,将创建一个进程组。新进程的进程标识符也是新进程组的进程组标识符。

...我一直在支持 Qt 已经通过该标志。这让我停留了一段时间,这似乎是关于这个的大多数问题的地方(是的,我见过他们都 - 我认为)似乎也死了。然后我找到了QProcess::setCreateProcessArgumentsModifier(有一个很好的用法示例here),它允许我将参数注入CreateProcess 调用。然后我更新了我的代码来做到这一点:

QProcess myprocess = new QProcess(this);
myprocess->setCreateProcessArgumentsModifier([this] (QProcess::CreateProcessArguments *args) 
        args->flags |= CREATE_NEW_PROCESS_GROUP;
);
myprocess->setReadChannel(QProcess::StandardOutput);

myprocess->start("myexec", "arg1", "arg2");

//...

auto success = GenerateConsoleCtrlEvent(CTRL_C_EVENT, myprocess->pid()->dwProcessId);
if (!success) 
    LPVOID lpMsgBuf;
    auto err = GetLastError();

    FormatMessage(
            FORMAT_MESSAGE_ALLOCATE_BUFFER |
            FORMAT_MESSAGE_FROM_SYSTEM |
            FORMAT_MESSAGE_IGNORE_INSERTS,
            nullptr,
            err,
            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
            reinterpret_cast<LPTSTR>(&lpMsgBuf),
            0, nullptr );

    auto error_string = QString::fromWCharArray((reinterpret_cast<LPTSTR>(lpMsgBuf)));
    std::cerr << error_string.toStdString();
    LocalFree(lpMsgBuf);

然而,这给了我同样的错误 (the handle is invalid)。从那里,我尝试了其他事情,比如注入我自己的 PROCESS_INFORMATION 结构以确保我有正确的进程标识符,或者甚至将 CREATE_NEW_PROCESS_GROUP 添加到 lpStartupInfo 代替 - 我现在知道这是错误的地方,因为这导致some strange behavior(此链接中的提问者不是我:D)

有什么想法吗?我可以采取不同的做法吗?

我使用的是 Qt 5.14.2,使用 MSVC 2017(64 位)编译。


MCVE

为此制作一个“最小”的 MCVE 并不容易 :)

我创建了一个简单的 Windows 应用程序,它通过简单地打印一条消息来处理 Ctrl+C。目标是让 QProcess 触发这个处理程序,没有副作用。这是子进程的源代码:

#include <atomic>
#include <chrono>
#include <iostream>
#include <thread>

#include "windows.h"

std::atomic<bool> should_stop = false;

BOOL WINAPI consoleHandler(DWORD signal) 
    if (signal == CTRL_C_EVENT) 
        std::cout << "\nThank you for your Ctrl+C event!\n";
        should_stop.store(true);
    

    return TRUE;


int main() 

    if (!SetConsoleCtrlHandler(consoleHandler, TRUE)) 
        std::cout << "Failed to set console handler\n";
        return 1;
    

    while (!should_stop) 
        std::cout << "I'll keep printing this message until you stop me." << std::endl; // Yes, I want to flush every time.
        std::this_thread::sleep_for(std::chrono::seconds(1));
    

    return 0;

我的父应用程序的“MVCE”有一个简单的main.cpp,以及一个带有头文件和源文件的ProcessHolder 类。这是必需的,以便我可以有一个事件循环,并且 Qt 能够正确地moc 类(用于所述事件循环)。

main.cpp

#include <QCoreApplication>
#include <QTimer>

#include <memory>

#include "processholder.h"

int main(int argc, char *argv[])

    QCoreApplication a(argc, argv);

    std::unique_ptr<ProcessHolder> ph(new ProcessHolder());

    // Just so I can get the event loop running
    QTimer::singleShot(0, ph.get(), &ProcessHolder::waitForInput);

    return a.exec();

processholder.h

#ifndef PROCESSHOLDER_H
#define PROCESSHOLDER_H

#include <QObject>
#include <QProcess>

class ProcessHolder : public QObject

    Q_OBJECT
public:
    explicit ProcessHolder(QObject *parent = nullptr);

signals:

public slots:
    void waitForInput();
private:
    QProcess* p;
;

#endif // PROCESSHOLDER_H

processholder.cpp

#include "processholder.h"

#include <iostream>

#include <QTimer>

#include "Windows.h"

void tryFinishProcess(QProcess* p) 
    auto success = GenerateConsoleCtrlEvent(CTRL_C_EVENT, p->pid()->dwProcessId);
    if (!success) 
        LPVOID lpMsgBuf;
        auto err = GetLastError();

        FormatMessage(
                FORMAT_MESSAGE_ALLOCATE_BUFFER |
                FORMAT_MESSAGE_FROM_SYSTEM |
                FORMAT_MESSAGE_IGNORE_INSERTS,
                nullptr,
                err,
                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                reinterpret_cast<LPTSTR>(&lpMsgBuf),
                0, nullptr );

        // probably could have used wcerr, but after making this work I was happy enough with it :D
        auto error_string = QString::fromWCharArray((reinterpret_cast<LPTSTR>(lpMsgBuf)));
        std::cerr << error_string.toStdString();
        LocalFree(lpMsgBuf);
    


ProcessHolder::ProcessHolder(QObject *parent) : QObject(parent), p(new QProcess(this))

    connect(p, &QProcess::readyReadStandardOutput, [this]() 
        auto lines = p->readAllStandardOutput();
        std::cout << lines.toStdString();
    );

    // Doing this for this example makes things fail miserably when trying to close the parent program.
    // An when not doing it, the CtrlC event that is generated on tryFinishProcess actually ends the
    // parent program, rather than the child one.
    /*p->setCreateProcessArgumentsModifier([this] (QProcess::CreateProcessArguments *args) 
            args->flags |= CREATE_NEW_PROCESS_GROUP;
    );*/

    std::cout << "starting process...\n";

    p->start(R"(path\to\TrivialConsoleApp.exe)");


void ProcessHolder::waitForInput()
   char c;
   bool quit = false;
   // Print a small prompt just so we can differentiate input from output
   std::cout << "> ";
   if (std::cin >> c) 
       switch(c) 
       case 'k':
           p->kill();
           break;
       case 't':
           p->terminate();
           break;
       case 'c':
           p->close();
           break;
       case 'g':
           tryFinishProcess(p);
       
       // any other character will just reliquinsh the hold on standard io for a small time, enough for the
       // messages that were sent via cout to show up.

       if (!quit) 
           QTimer::singleShot(0, this, &ProcessHolder::waitForInput);
       
   

几个示例运行:

使用QProcess::kill()。子进程已终止,但没有 CtrlC 消息。

使用tryFinishProcess(参见上面的实现)实际上使父进程退出:

再次使用tryFinishProcess,但这次添加了CREATE_NEW_PROCESS_GROUP(参见ProcessHolder 构造函数的注释)。这里需要注意的是,按终端要求的 RETURN 键不再起作用(它什么也不做),所以那里出了点问题:

我对上述三个示例(或至少对最后两个示例)的期望是在请求进程完成后在某处看到"Thank you for your Ctrl+C event!" 消息(查看子进程上的consoleHandler)。如果我在控制台上运行它然后按 Ctrl+C:

【问题讨论】:

我假设您已经尝试过QProcess::terminateQProcess::kill,对吧? 这是一个不使用句柄的 api 函数的奇怪错误代码。我的水晶球说这不是混淆代码中建议的标准过程,而是实际上的服务。不起作用,服务没有控制台。请改用 ControlService()。 @JarMan 是的。它确实杀死了进程,但它没有给它时间优雅地关闭自己,正如你可以猜到的那样,这会导致几个问题。 @HansPassant 不是,它不是服务。它确实是一个进程,我可以从它的控制台读取并写入它。事实上,我认为任何处理这样的 CtrlC 事件的进程都可以用于测试。我会看看我是否可以仅仅为了使我的示例可验证而想到一个,或者可能实现一个微不足道的示例。感谢您的评论! 添加了一个(不是那么小)MCVE。它没有.pro,但创建一个新的 Qt 控制台应用程序应该会为您生成一个 :) 【参考方案1】:

CTRL+C被输入到控制台进程时,系统会在这个进程中创建线程并带有入口点

EXTERN_C
WINBASEAPI
ULONG 
WINAPI 
CtrlRoutine(_In_ DWORD dwCtrlEvent);

这个函数由kernel32.dll导出(可以转发导出到另一个dll,比如kernelbase.dll

这个CtrlRoutine 下一步: 如果正在调试进程 - 引发 DBG_CONTROL_C 异常,则 由SetConsoleCtrlHandler 回调注册的调用。如果没有注册回调或全部返回 false - 调用 DefaultHandler,它只是调用 ExitProcess(STATUS_CONTROL_C_EXIT)按 CTRL+C 退出应用程序

但可以通过自己直接调用目标进程中的CreateRemoteThread,入口点位于CtrlRoutineCTRL_C_EVENT 作为参数。如果目标进程具有与我们相同的数字容量 - 32 位或 64 位 - 完全没有任何问题 - 我们可以在链接时导入 CtrlRoutine(它在 kernel32.lib 中定义)或获取它的地址是GetProcAddress。但是如果我们的进程是 64 位本机并且目标进程是 32 位 (WoW64) - 这里的问题 - 我们需要在 32-bit kernel32.dll 中获取 CtrlRoutine 的地址em> - 但我们不能直接在自己的 64 位进程中加载​​它并调用GetProcAddress。需要自己映射这个dll,然后自己获取它的基础并解析导出。这个任务已经不简单了。如果我们在 wow64 进程(64 位窗口上的 32 位进程)中运行并且目标进程是 64 位(本机) - 任务已经变得非常困难(尽管也存在解决方案,但没有创建额外的进程)。但我假设控制过程是本机的(64 位窗口上的 64 位)

#pragma warning( disable : 4201)

#include <Windows.h>
#include <malloc.h>
#define LDR_DATA_TABLE_ENTRY _LDR_DATA_TABLE_ENTRY_
#define PLDR_DATA_TABLE_ENTRY _PLDR_DATA_TABLE_ENTRY_
#include <winternl.h>
#undef PLDR_DATA_TABLE_ENTRY
#undef LDR_DATA_TABLE_ENTRY

#define MAXUSHORT 0xffff 
#define MAXULONG 0xffffffff 

#define RtlOffsetToPointer(B,O) ((PCHAR)( ((PCHAR)(B)) + ((ULONG_PTR)(O)) ))
#define RtlPointerToOffset(B,P)  ((ULONG)( ((PCHAR)(P)) - ((PCHAR)(B))  ))

#define RTL_CONSTANT_STRING(s)  sizeof( s ) - sizeof( (s)[0] ), sizeof( s ), const_cast<PWSTR>(s) 

#define NtCurrentProcess() ( (HANDLE)(LONG_PTR) -1 )

typedef enum _SECTION_INHERIT 
    ViewShare = 1,
    ViewUnmap = 2
 SECTION_INHERIT;

typedef enum _SECTION_INFORMATION_CLASS

    SectionBasicInformation, // q; SECTION_BASIC_INFORMATION
    SectionImageInformation, // q; SECTION_IMAGE_INFORMATION
    SectionRelocationInformation, // name:wow64:whNtQuerySection_SectionRelocationInformation
    SectionOriginalBaseInformation, // PVOID BaseAddress
    SectionInternalImageInformation, // SECTION_INTERNAL_IMAGE_INFORMATION // since REDSTONE2
    MaxSectionInfoClass
 SECTION_INFORMATION_CLASS;

typedef struct SECTION_IMAGE_INFORMATION

    PVOID TransferAddress;
    ULONG ZeroBits;
    SIZE_T MaximumStackSize;
    SIZE_T CommittedStackSize;
    ULONG SubSystemType;
    union
    
        struct
        
            USHORT SubSystemMinorVersion;
            USHORT SubSystemMajorVersion;
        ;
        ULONG SubSystemVersion;
    ;
    union
    
        struct
        
            USHORT MajorOperatingSystemVersion;
            USHORT MinorOperatingSystemVersion;
        ;
        ULONG OperatingSystemVersion;
    ;
    USHORT ImageCharacteristics;
    USHORT DllCharacteristics;
    USHORT Machine;
    BOOLEAN ImageContainsCode;
    union
    
        UCHAR ImageFlags;
        struct
        
            UCHAR ComPlusNativeReady : 1;
            UCHAR ComPlusILOnly : 1;
            UCHAR ImageDynamicallyRelocated : 1;
            UCHAR ImageMappedFlat : 1;
            UCHAR BaseBelow4gb : 1;
            UCHAR ComPlusPrefer32bit : 1;
            UCHAR Reserved : 2;
        ;
    ;
    ULONG LoaderFlags;
    ULONG ImageFileSize;
    ULONG CheckSum;
 *PSECTION_IMAGE_INFORMATION;

typedef struct LDR_DATA_TABLE_ENTRY

    LIST_ENTRY InLoadOrderLinks;
    LIST_ENTRY InMemoryOrderLinks;
    LIST_ENTRY InInitializationOrderLinks;
    void *DllBase;
    void *EntryPoint;
    ULONG SizeOfImage;
    UNICODE_STRING FullDllName;
    UNICODE_STRING BaseDllName;
    // .. more members. trucated
 *PLDR_DATA_TABLE_ENTRY;

EXTERN_C_START

NTSYSAPI
NTSTATUS
NTAPI
LdrFindEntryForAddress(
                       _In_ PVOID DllHandle,
                       _Out_ PLDR_DATA_TABLE_ENTRY *Entry
                       );


NTSYSAPI
PIMAGE_NT_HEADERS
NTAPI
RtlImageNtHeader(
                 _In_ PVOID Base
                 );

EXTERN_C
NTSYSAPI
PVOID
NTAPI
RtlImageDirectoryEntryToData(
                             _In_ PVOID Base,
                             _In_ BOOLEAN MappedAsImage,
                             _In_ USHORT DirectoryEntry,
                             _Out_ PULONG Size
                             );

NTSYSAPI
NTSTATUS
NTAPI
RtlMultiByteToUnicodeSize(
                          _Out_ PULONG BytesInUnicodeString,
                          _In_reads_bytes_(BytesInMultiByteString) const CHAR *MultiByteString,
                          _In_ ULONG BytesInMultiByteString
                          );

NTSYSAPI
NTSTATUS
NTAPI
RtlMultiByteToUnicodeSize(
                          _Out_ PULONG BytesInUnicodeString,
                          _In_reads_bytes_(BytesInMultiByteString) const CHAR *MultiByteString,
                          _In_ ULONG BytesInMultiByteString
                          );

NTSYSAPI
NTSTATUS
NTAPI
RtlMultiByteToUnicodeN(
                       _Out_writes_bytes_to_(MaxBytesInUnicodeString, *BytesInUnicodeString) PWCH UnicodeString,
                       _In_ ULONG MaxBytesInUnicodeString,
                       _Out_opt_ PULONG BytesInUnicodeString,
                       _In_reads_bytes_(BytesInMultiByteString) const CHAR *MultiByteString,
                       _In_ ULONG BytesInMultiByteString
                       );

NTSYSAPI
NTSTATUS
NTAPI
RtlAppendUnicodeToString (
                          _Inout_ PUNICODE_STRING Destination,
                          _In_opt_z_ PCWSTR Source
                          );

NTSYSAPI
NTSTATUS
NTAPI
RtlAppendUnicodeStringToString (
                                _Inout_ PUNICODE_STRING Destination,
                                _In_ PCUNICODE_STRING Source
                                );

NTSYSAPI
BOOLEAN
NTAPI
RtlPrefixUnicodeString(
                       _In_ PCUNICODE_STRING String1,
                       _In_ PCUNICODE_STRING String2,
                       _In_ BOOLEAN CaseInSensitive
                       );

NTSYSAPI
NTSTATUS
NTAPI
ZwUnmapViewOfSection(
                     _In_ HANDLE ProcessHandle,
                     _In_opt_ PVOID BaseAddress
                     );

NTSYSAPI
NTSTATUS
NTAPI
ZwMapViewOfSection(
                   _In_ HANDLE SectionHandle,
                   _In_ HANDLE ProcessHandle,
                   _Outptr_result_bytebuffer_(*ViewSize) PVOID *BaseAddress,
                   _In_ ULONG_PTR ZeroBits,
                   _In_ SIZE_T CommitSize,
                   _Inout_opt_ PLARGE_INTEGER SectionOffset,
                   _Inout_ PSIZE_T ViewSize,
                   _In_ SECTION_INHERIT InheritDisposition,
                   _In_ ULONG AllocationType,
                   _In_ ULONG Win32Protect
                   );

NTSYSAPI
NTSTATUS
NTAPI
ZwOpenSection(
              _Out_ PHANDLE SectionHandle,
              _In_ ACCESS_MASK DesiredAccess,
              _In_ POBJECT_ATTRIBUTES ObjectAttributes
              );

NTSYSAPI
NTSTATUS
NTAPI
ZwQuerySection(
               _In_ HANDLE SectionHandle,
               _In_ ULONG SectionInformationClass,
               _Out_ PVOID SectionInformation,
               _In_ ULONG SectionInformationLength,
               _Out_ PSIZE_T ResultLength OPTIONAL
               );

NTSYSAPI
NTSTATUS
NTAPI
LdrLoadDll(
           _In_opt_ PWSTR DllPath,
           _In_opt_ PULONG DllCharacteristics,
           _In_ PUNICODE_STRING DllName,
           _Out_ HMODULE *DllHandle
           );

NTSYSAPI
NTSTATUS
NTAPI
LdrUnloadDll(
             _In_ PVOID DllHandle
             );

NTSYSAPI
NTSTATUS
NTAPI
LdrGetProcedureAddress(
                       _In_ PVOID DllHandle,
                       _In_opt_ PANSI_STRING ProcedureName,
                       _In_opt_ ULONG ProcedureNumber,
                       _Out_ PVOID *ProcedureAddress
                       );

EXTERN_C_END

ULONG GetNameOrdinal(PVOID Base, PDWORD AddressOfNames, DWORD NumberOfNames, PCSTR Name)

    if (NumberOfNames)
    
        DWORD a = 0, o;

        do 
        
            o = (a + NumberOfNames) >> 1;

            int i = strcmp(RtlOffsetToPointer(Base, AddressOfNames[o]), Name);

            if (!i)
            
                return o;
            

            0 > i ? a = o + 1 : NumberOfNames = o;

         while (a < NumberOfNames);
    

    return MAXULONG;


PVOID getWowProcs(PCUNICODE_STRING DllName, PCSTR Name);

PVOID getWowProcs(PVOID ImageBase, PVOID BaseAddress, PCSTR Name)

    ULONG exportSize, exportRVA;

    PIMAGE_EXPORT_DIRECTORY pied = (PIMAGE_EXPORT_DIRECTORY)
        RtlImageDirectoryEntryToData(BaseAddress, TRUE, IMAGE_DIRECTORY_ENTRY_EXPORT, &exportSize);

    if (!pied) return 0;

    exportRVA = RtlPointerToOffset(BaseAddress, pied);

    ULONG NumberOfFunctions = pied->NumberOfFunctions;

    if (!NumberOfFunctions) return 0;

    ULONG NumberOfNames = pied->NumberOfNames;
    ULONG OrdinalBase = pied->Base;

    PULONG AddressOfFunctions = (PULONG)RtlOffsetToPointer(BaseAddress, pied->AddressOfFunctions);
    PULONG AddressOfNames = (PULONG)RtlOffsetToPointer(BaseAddress, pied->AddressOfNames);
    PWORD AddressOfNameOrdinals = (PWORD)RtlOffsetToPointer(BaseAddress, pied->AddressOfNameOrdinals);

    ULONG o;

    if (*Name == '#')
    
        if ((o = strtoul(Name + 1, const_cast<char**>(&Name), 10)) < OrdinalBase || *Name)
        
            return 0;
        
        o -= OrdinalBase;
    
    else
    
        o = GetNameOrdinal(BaseAddress, AddressOfNames, NumberOfNames, Name);
        if (o < NumberOfNames)
        
            o = AddressOfNameOrdinals[o];
        
    

    if (o >= NumberOfFunctions)
    
        return 0;
    

    DWORD Rva = AddressOfFunctions[o];

    if ((ULONG_PTR)Rva - (ULONG_PTR)exportRVA >= exportSize)
    
        return RtlOffsetToPointer(ImageBase, Rva);
    

    // forward export
    PCSTR pfn = RtlOffsetToPointer(BaseAddress, Rva);

    if (!(Name = strrchr(pfn, '.')))
    
        return 0;
    

    static const WCHAR DLL[] = L"DLL";

    ULONG BytesInUnicodeString, BytesInMultiByteString = RtlPointerToOffset(pfn, ++Name);

    if (0 > RtlMultiByteToUnicodeSize(&BytesInUnicodeString, pfn, BytesInMultiByteString) || 
        (BytesInUnicodeString += sizeof(DLL)) >= MAXUSHORT )
    
        return 0;
    

    UNICODE_STRING DllName = 
        0, (USHORT)BytesInUnicodeString, (PWSTR)alloca(BytesInUnicodeString)
    ;

    if (0 > RtlMultiByteToUnicodeN(DllName.Buffer, DllName.MaximumLength, 
        &BytesInUnicodeString, pfn, BytesInMultiByteString))
    
        return 0;
    

    DllName.Length = (USHORT)BytesInUnicodeString;

    if (0 > RtlAppendUnicodeToString(&DllName, DLL))
    
        return 0;
    

    static const UNICODE_STRING API_ = RTL_CONSTANT_STRING(L"API-");
    static const UNICODE_STRING EXT_ = RTL_CONSTANT_STRING(L"EXT-");

    if (!RtlPrefixUnicodeString(&API_, &DllName, TRUE) &&
        !RtlPrefixUnicodeString(&EXT_, &DllName, TRUE))
    
        return getWowProcs(&DllName, Name);
    

    HMODULE hmod;

    if (0 <= LdrLoadDll(0, 0, &DllName, &hmod))
    
        ANSI_STRING as, *pas;

        if (*Name == '#')
        
            pas = 0;
            o = strtoul(Name + 1, const_cast<char**>(&Name), 10);
            if (*Name)
            
                o = 0;
            
        
        else
        
            o = 0;
            RtlInitAnsiString(pas = &as, Name);
        

        PVOID pv, pvfn = 0;
        if (0 <= LdrGetProcedureAddress(hmod, pas, o, &pv))
        
            PLDR_DATA_TABLE_ENTRY ldte;
            if (0 <= LdrFindEntryForAddress(pv, &ldte))
            
                pvfn = getWowProcs(&ldte->BaseDllName, Name);
            
        
        LdrUnloadDll(hmod);

        return pvfn;
    

    return 0;


PVOID getWowProcs(PCUNICODE_STRING DllName, PCSTR Name)

    static const WCHAR KnownDlls32[] = L"\\KnownDlls32\\";

    UNICODE_STRING ObjectName =  
        0, 
        (USHORT)(sizeof(KnownDlls32) + DllName->Length), 
        (PWSTR)alloca(ObjectName.MaximumLength)
    ;

    if (0 > RtlAppendUnicodeToString(&ObjectName, KnownDlls32) || 
        0 > RtlAppendUnicodeStringToString(&ObjectName, DllName))
    
        return 0;
    

    OBJECT_ATTRIBUTES oa =  sizeof(oa), 0, &ObjectName, OBJ_CASE_INSENSITIVE ;

    HANDLE hSection;

    PVOID pv = 0;

    if (0 <= ZwOpenSection(&hSection, SECTION_QUERY|SECTION_MAP_READ, &oa))
    
        SECTION_IMAGE_INFORMATION sii;
        if (0 <= ZwQuerySection(hSection, SectionImageInformation, &sii, sizeof(sii), 0))
        
            PVOID BaseAddress = 0;
            SIZE_T ViewSize = 0;

            if (0 <= ZwMapViewOfSection(hSection, NtCurrentProcess(), &BaseAddress, 0, 0, 0, &ViewSize, ViewUnmap, 0, PAGE_READONLY))
            
                __try
                
                    if (PIMAGE_NT_HEADERS32 pinth = (PIMAGE_NT_HEADERS32)RtlImageNtHeader(BaseAddress))
                    
                        pv = getWowProcs((PBYTE)sii.TransferAddress - pinth->OptionalHeader.AddressOfEntryPoint, BaseAddress, Name);
                    
                
                __except(EXCEPTION_EXECUTE_HANDLER)
                
                

                ZwUnmapViewOfSection(NtCurrentProcess(), BaseAddress);
            
        
        NtClose(hSection);
    

    return pv;


PVOID GetCtrlRoutine(HANDLE hProcess)

    BOOL bWow;
    if (IsWow64Process(hProcess, &bWow))
    
        static const UNICODE_STRING kernel32 = RTL_CONSTANT_STRING(L"kernel32.dll");

        if (bWow)
        
            static PVOID pvCtrlRoutine = 0;
            if (!pvCtrlRoutine)
            
                // GetOverlappedResultEx
                // just for some extreme case, for better understand code in getWowProcs
                // it not need here
                // pvCtrlRoutine = getWowProcs(&kernel32, "GetOverlappedResultEx");
                pvCtrlRoutine = getWowProcs(&kernel32, "CtrlRoutine");
            
            return pvCtrlRoutine;
        

        static PVOID pvCtrlRoutine = 0;
        if (!pvCtrlRoutine)
        
            if (HMODULE hmod = GetModuleHandle(kernel32.Buffer))
            
                pvCtrlRoutine = GetProcAddress(hmod, "CtrlRoutine");
            
        
        return pvCtrlRoutine;
    

    return 0;


void SendCtrlEvent(HANDLE hProcess)

    if (PVOID pv = GetCtrlRoutine(hProcess))
    
        if (HANDLE hThread = CreateRemoteThread(hProcess, 0, 0, (PTHREAD_START_ROUTINE)pv, CTRL_C_EVENT, 0, 0))
        
            CloseHandle(hThread);
            return ;
        
    

    TerminateProcess(hProcess, STATUS_CONTROL_C_EXIT);


void DemoUseCtrlC()

    WCHAR AppName[MAX_PATH];

    if (ExpandEnvironmentStringsW(L"%SystemRoot%\\syswow64\\ping.exe", AppName, _countof(AppName)))
    
        STARTUPINFO si =  sizeof(si);
        PROCESS_INFORMATION pi;
        if (CreateProcessW(AppName, const_cast<PWSTR>(L"* -n 999 8.8.8.8"), 0, 0, 0, 0, 0, 0, &si, &pi))
        
            CloseHandle(pi.hThread);
            MessageBoxW(HWND_DESKTOP, L"Break..", L"CTRL+C", MB_ICONINFORMATION);
            SendCtrlEvent(pi.hProcess);
            CloseHandle(pi.hProcess);
        
    

【讨论】:

谢谢!我还没有详细研究它来投票,但这看起来很有希望。我的 ATM 有点晚了,所以我明天再看看 :) @CássioRenan - 这里 99% 的代码 - 获取CtrlRoutine 的地址。如果仅适用于 64/64 或 32/32 进程 - 所有你需要的 - 声明 EXTERN_C WINBASEAPI ULONG WINAPI CtrlRoutine(_In_ DWORD dwCtrlEvent); 并与 kernel32.lib 链接(无论如何你都这样做)。或通过GetProcAddress(GetModuleHandle(L"kernel32"), "CtrlRoutine") 获取地址并在SendCtrlEvent 中使用它,但如果 64->32 需要所有这些代码。真的甚至 32->64 可能,但为此需要更多代码和更复杂的代码 @CássioRenan 我之前使用此代码优雅地停止从我的 64 位服务启动的 java.exe 进程(自己的 java.exe 是 32 位)。并且这项工作是正确的 我无法构建这个。我绝不是 Windows 专家,这似乎需要一些我研究过的任何 Microsoft SDK 都没有提供的标头。您能否概述一下我需要做什么才能使用它?例如:我需要包含什么、链接到、要添加的定义...如果这不是 Qt 特定的也没关系,我可以使用它。 @CássioRenan - 我使用 wdk 标头(#include ),但这需要一些技巧。但对你来说,你会更简单地从这个头文件中复制粘贴一些定义到自己的文件中,而不是尝试包含 ntifs.h。我在这里只添加 undocumented 结构和 api(SECTION_IMAGE_INFORMATIONLDR_DATA_TABLE_ENTRYLdrFindEntryForAddress) - 但是现在我尝试为你添加另一个(对我来说更简单的情况是因为我使用 ntifs.h)【参考方案2】:

首先,您没有运行事件循环。您对单次计时器所做的只是从事件循环中运行您的阻塞代码,但您永远不会将控制权返回给事件循环,除非让您的代码再次阻塞它,而且QProcess 不能保证很有帮助当你这样做时给你。

运行事件循环意味着您的代码执行“少量”工作,从不阻塞,并在完成后立即返回。 如果你想让事件循环运行,你不能使用std::cin - 无论如何,如果没有特定于平台的诡计。

在一个简单的演示中,您可以在进程开始后发送 Ctrl-C,而不是使其交互,以响应 QProcess 发送的信号 - 然后添加一些额外的时间(比如 500 毫秒) .

对于交互使用,不要使用控制台,而是制作一个最小的图形程序,例如使用带有几个按钮的小部件。对于 MCVE,使用单个 main.cpp 文件,不使用头文件,以 #include &lt;QtWidgets&gt; 开头并以 #include "main.moc" 结尾(仅当您有任何 Q_OBJECT 宏或您需要将 moc 输出编译到您的代码),除非您需要,否则不要在堆上分配 - 这只是视觉噪音(例如,main 中的进程持有者 std::unique_ptr 是不必要的,您在其他地方进行的几乎每一个动态分配都是如此)。

【讨论】:

我理解并欣赏这种情绪,是的,我的 MCVE 并不是有史以来最好的,但这对我没有帮助 - 例如:它没有回答问题。话虽如此,我会尝试按照你的提示,用一个小的 GUI 制作一个 mcve。

以上是关于在 Windows 上向 QProcess 发送 Ctrl+C的主要内容,如果未能解决你的问题,请参考以下文章

Socket.io 无法在 Chrome 和 Firefox 上向 Node 服务器发送消息

在 Spring Websocket 上向特定用户发送消息

c#在linux上向本地syslog服务发送消息

将 QProcess 输出发送到 textedit 的最佳方法

如何在 websocket 服务器上向特定用户发送消息

如何在 iOS 设备上向 Web 应用程序发送推送通知?