在 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::terminate
和QProcess::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
,入口点位于CtrlRoutine
和CTRL_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 SECTION_IMAGE_INFORMATION
、LDR_DATA_TABLE_ENTRY
、LdrFindEntryForAddress
) - 但是现在我尝试为你添加另一个(对我来说更简单的情况是因为我使用 ntifs.h)【参考方案2】:
首先,您没有运行事件循环。您对单次计时器所做的只是从事件循环中运行您的阻塞代码,但您永远不会将控制权返回给事件循环,除非让您的代码再次阻塞它,而且QProcess
不能保证很有帮助当你这样做时给你。
运行事件循环意味着您的代码执行“少量”工作,从不阻塞,并在完成后立即返回。 如果你想让事件循环运行,你不能使用std::cin
- 无论如何,如果没有特定于平台的诡计。
在一个简单的演示中,您可以在进程开始后发送 Ctrl-C,而不是使其交互,以响应 QProcess
发送的信号 - 然后添加一些额外的时间(比如 500 毫秒) .
对于交互使用,不要使用控制台,而是制作一个最小的图形程序,例如使用带有几个按钮的小部件。对于 MCVE,使用单个 main.cpp
文件,不使用头文件,以 #include <QtWidgets>
开头并以 #include "main.moc"
结尾(仅当您有任何 Q_OBJECT
宏或您需要将 moc 输出编译到您的代码),除非您需要,否则不要在堆上分配 - 这只是视觉噪音(例如,main
中的进程持有者 std::unique_ptr
是不必要的,您在其他地方进行的几乎每一个动态分配都是如此)。
【讨论】:
我理解并欣赏这种情绪,是的,我的 MCVE 并不是有史以来最好的,但这对我没有帮助 - 例如:它没有回答问题。话虽如此,我会尝试按照你的提示,用一个小的 GUI 制作一个 mcve。以上是关于在 Windows 上向 QProcess 发送 Ctrl+C的主要内容,如果未能解决你的问题,请参考以下文章
Socket.io 无法在 Chrome 和 Firefox 上向 Node 服务器发送消息