传统CreateProcess传递StdIN StdOUT 管道方式存在进程输出不能及时刷管道问题导致ReadFile阻塞,传统解决方法是子进程强制进行fflush(stdout), 如果不能改变子进程,就没办法了
从win10开始 windows支持伪终端,可以完全模拟终端管道的行为,
传统管道crt会使用GetFileType检测STDHANDLE ,对于控制台调用检测到的是字符设备,而对于改变后的STDHANDLE 检测到的是管道设备,对发现管道设备时,缓冲方式就变成了块缓冲方式,遇到换行符也不会及时flush,这就导致预期输出不能直接被ReadFile读取到,
Win10上的伪终端方式接管进程输入输出代码
cpp
//Kernel32.dll
#ifndef PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE
#define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE 0x20016
#endif
typedef HRESULT(WINAPI* PFN_CreatePseudoConsole)(
_In_ COORD size,
_In_ HANDLE hInput,
_In_ HANDLE hOutput,
_In_ DWORD dwFlags,
_Out_ HPCON* phPC
);
HRESULT MyCreatePseudoConsole(
_In_ COORD size,
_In_ HANDLE hInput,
_In_ HANDLE hOutput,
_In_ DWORD dwFlags,
_Out_ HPCON* phPC
)
{
static PFN_CreatePseudoConsole fn = (PFN_CreatePseudoConsole)::GetProcAddress(GetModuleHandle(L"Kernel32.dll"), "CreatePseudoConsole");
if (fn)
return fn(size, hInput, hOutput, dwFlags, phPC);
return E_FAIL;
}
typedef BOOL(WINAPI* PFN_InitializeProcThreadAttributeList)(
LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList,
DWORD dwAttributeCount,
DWORD dwFlags,
PSIZE_T lpSize
);
BOOL MyInitializeProcThreadAttributeList(
LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList,
DWORD dwAttributeCount,
DWORD dwFlags,
PSIZE_T lpSize
)
{
static PFN_InitializeProcThreadAttributeList fn = (PFN_InitializeProcThreadAttributeList)::GetProcAddress(GetModuleHandle(L"Kernel32.dll"), "InitializeProcThreadAttributeList");
if (fn)
return fn(lpAttributeList, dwAttributeCount, dwFlags, lpSize);
return 0;
}
typedef BOOL(WINAPI* PFN_UpdateProcThreadAttribute)(
LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList,
DWORD dwFlags,
DWORD_PTR Attribute,
PVOID lpValue,
SIZE_T cbSize,
PVOID lpPreviousValue,
PSIZE_T lpReturnSize
);
BOOL MyUpdateProcThreadAttribute(
LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList,
DWORD dwFlags,
DWORD_PTR Attribute,
PVOID lpValue,
SIZE_T cbSize,
PVOID lpPreviousValue,
PSIZE_T lpReturnSize
)
{
static PFN_UpdateProcThreadAttribute fn = (PFN_UpdateProcThreadAttribute)::GetProcAddress(GetModuleHandle(L"Kernel32.dll"), "UpdateProcThreadAttribute");
if (fn)
return fn(lpAttributeList, dwFlags, Attribute, lpValue, cbSize, lpPreviousValue, lpReturnSize);
return 0;
}
typedef void(WINAPI* PFN_ClosePseudoConsole)(_In_ HPCON hPC);
void MyClosePseudoConsole(_In_ HPCON hPC)
{
static PFN_ClosePseudoConsole fn = (PFN_ClosePseudoConsole)::GetProcAddress(GetModuleHandle(L"Kernel32.dll"), "ClosePseudoConsole");
if (fn)
fn(hPC);
}
typedef VOID(WINAPI*PFN_DeleteProcThreadAttributeList)(
LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList
);
VOID MyDeleteProcThreadAttributeList(
LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList
)
{
static PFN_DeleteProcThreadAttributeList fn = (PFN_DeleteProcThreadAttributeList)::GetProcAddress(GetModuleHandle(L"Kernel32.dll"), "DeleteProcThreadAttributeList");
if (fn)
fn(lpAttributeList);
}
typedef struct _MySTARTUPINFOEXW
{
STARTUPINFOW StartupInfo;
LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList;
} MySTARTUPINFOEXW;
BidirectionalPipe::~BidirectionalPipe() {
Cleanup();
}
bool BidirectionalPipe::StartProcess(bool bPsConsole, const std::wstring & command)
{
SECURITY_ATTRIBUTES sa = { 0 };
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
// 创建输入管道(父进程写,子进程读)
if (!CreatePipe(&m_hChildStdinRead, &m_hParentWrite, &sa, 0)) {
Cleanup();
return false;
}
// 创建输出管道(父进程读,子进程写)
if (!CreatePipe(&m_hParentRead, &m_hChildStdoutWrite, &sa, 0)) {
Cleanup();
return false;
}
if (bPsConsole)
{
// 2. 创建伪控制台 (ConPTY)
COORD consoleSize = { 80, 25 }; // 初始控制台大小
HRESULT hr = MyCreatePseudoConsole(consoleSize, m_hChildStdinRead, m_hChildStdoutWrite, 0, &m_hPC);
if (FAILED(hr)) {
Cleanup();
return false;
}
}
// 3. 准备 STARTUPINFOEXW 结构体
MySTARTUPINFOEXW si = { 0 };
if (bPsConsole) {
si.StartupInfo.cb = sizeof(MySTARTUPINFOEXW);
}
else {
si.StartupInfo.cb = sizeof(STARTUPINFOW);
}
si.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
if (bPsConsole)
{
// 关键:将 ConPTY 句柄附加到 StartupInfo
SIZE_T size = 0;
MyInitializeProcThreadAttributeList(NULL, 1, 0, &size);
m_lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, size);
MyInitializeProcThreadAttributeList(m_lpAttributeList, 1, 0, &size);
MyUpdateProcThreadAttribute(m_lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, m_hPC, sizeof(HPCON), NULL, NULL);
si.lpAttributeList = m_lpAttributeList;
si.StartupInfo.wShowWindow = SW_HIDE;
si.StartupInfo.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
}
else
{
si.StartupInfo.wShowWindow = SW_HIDE;
si.StartupInfo.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
si.StartupInfo.hStdError = m_hChildStdoutWrite;
si.StartupInfo.hStdOutput = m_hChildStdoutWrite;
si.StartupInfo.hStdInput = m_hChildStdinRead;
}
// 启动进程
if (!CreateProcessW(NULL, const_cast<wchar_t*>(command.c_str()),
NULL, NULL,
bPsConsole ? FALSE : TRUE,
bPsConsole ? EXTENDED_STARTUPINFO_PRESENT : 0,
NULL, NULL, &si.StartupInfo, &m_pi)) {
Cleanup();
return false;
}
// 【至关重要】:CreateProcess 成功后,父进程必须关闭它不需要的管道句柄!
// 否则 ReadFile 永远不会收到 EOF,导致卡死。
if (m_hChildStdinRead) {
CloseHandle(m_hChildStdinRead);
m_hChildStdinRead = 0;
}
if (m_hChildStdoutWrite) {
CloseHandle(m_hChildStdoutWrite);
m_hChildStdoutWrite = 0;
}
return true;
}
void BidirectionalPipe::WriteInput(const std::string & input)
{
if (m_hParentWrite == NULL)
return;
DWORD bytesWritten;
WriteFile(m_hParentWrite, input.c_str(), (DWORD)input.size(), &bytesWritten, NULL);
FlushFileBuffers(m_hParentWrite);
}
__declspec(noinline) CStringA BidirectionalPipe::ReadOutput()
{
CStringA strRet;
const int bufMaxChars = 4095;
char buffer[bufMaxChars + 10] = { 0 };
DWORD bytesRead;
//FlushFileBuffers(m_hChildStdoutWrite);
// 第一次读取
if (!ReadFile(m_hParentRead, buffer, bufMaxChars, &bytesRead, NULL) || bytesRead == 0) {
return strRet; // 读取失败或无数据
}
buffer[bytesRead] = '\0';
strRet = buffer;
//// 检查是否读取完所有数据
//if (bytesRead < bufMaxChars) {
// return strRet; // 缓冲区未满,说明数据已读完
//}
// 缓冲区已满,检查管道内是否还有剩余数据
while (true) {
DWORD bytesAvailable = 0;
BOOL peekResult = PeekNamedPipe(
m_hParentRead,
NULL, // 不读取数据,只查询状态
0, // 缓冲区大小为0
NULL, // 读取的字节数
&bytesAvailable, // 可用字节数
NULL // 消息剩余字节数(用于消息模式)
);
if (!peekResult || bytesAvailable == 0) {
break; // 查询失败或无剩余数据
}
// 动态分配缓冲区读取剩余数据(最多读取bufMaxChars字节)
DWORD readSize = min(bytesAvailable, (DWORD)bufMaxChars);
if (ReadFile(m_hParentRead, buffer, readSize, &bytesRead, NULL) && bytesRead > 0) {
buffer[bytesRead] = '\0';
strRet += buffer;
// 如果读取的字节数等于缓冲区大小,说明可能还有更多数据
if (bytesRead < readSize) {
break; // 数据已读完
}
}
else {
break; // 读取失败
}
}
return strRet;
}
void BidirectionalPipe::Terminate()
{
if (m_pi.hProcess)
{
::TerminateProcess(m_pi.hProcess, 0);
WaitForSingleObject(m_pi.hProcess, 10000);
}
}
void BidirectionalPipe::Cleanup() {
if (m_hChildStdinRead) {
CloseHandle(m_hChildStdinRead);
m_hChildStdinRead = 0;
}
if (m_hParentWrite) {
CloseHandle(m_hParentWrite);
m_hParentWrite = 0;
}
if (m_hParentRead) {
CloseHandle(m_hParentRead);
m_hParentRead = 0;
}
if (m_hChildStdoutWrite) {
CloseHandle(m_hChildStdoutWrite);
m_hChildStdoutWrite = 0;
}
if (m_pi.hProcess) {
CloseHandle(m_pi.hProcess);
m_pi.hProcess = 0;
}
if (m_pi.hThread) {
CloseHandle(m_pi.hThread);
m_pi.hThread = 0;
}
if (m_lpAttributeList)
{
MyDeleteProcThreadAttributeList(m_lpAttributeList);
HeapFree(GetProcessHeap(), 0, m_lpAttributeList);
m_lpAttributeList = 0;
}
if (m_hPC)
{
MyClosePseudoConsole(m_hPC);
m_hPC = 0;
}
}
///////////////////////////////////Header/////////////////////////////
#pragma once
typedef void* HPCON;
class BidirectionalPipe
{
private:
HANDLE m_hChildStdinRead = NULL;
HANDLE m_hParentWrite = NULL;
HANDLE m_hParentRead = NULL;
HANDLE m_hChildStdoutWrite = NULL;
PROCESS_INFORMATION m_pi = { 0 };
LPPROC_THREAD_ATTRIBUTE_LIST m_lpAttributeList = 0;
HPCON m_hPC = 0;
public:
~BidirectionalPipe();
bool StartProcess(bool bPsConsole, const std::wstring& command);
void WriteInput(const std::string& input);
CStringA ReadOutput();
void Terminate();
private:
void Cleanup();
};