进程管道通讯-伪终端方式
传统CreateProcess传递StdIN StdOUT 管道方式存在进程输出不能及时刷管道问题导致ReadFile阻塞传统解决方法是子进程强制进行fflush(stdout), 如果不能改变子进程就没办法了从win10开始 windows支持伪终端可以完全模拟终端管道的行为传统管道crt会使用GetFileType检测STDHANDLE 对于控制台调用检测到的是字符设备而对于改变后的STDHANDLE 检测到的是管道设备对发现管道设备时缓冲方式就变成了块缓冲方式遇到换行符也不会及时flush这就导致预期输出不能直接被ReadFile读取到Win10上的伪终端方式接管进程输入输出代码//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(LKernel32.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(LKernel32.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(LKernel32.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(LKernel32.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(LKernel32.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_castwchar_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(); };

相关新闻

计算机自学路线:从零基础到进阶的完整指南

计算机自学路线:从零基础到进阶的完整指南

1. 计算机课程自学路线规划作为一名在IT行业摸爬滚打十年的老程序员,我经常被问到"如何系统学习计算机知识"。市面上课程琳琅满目,但缺乏体系化的学习路径往往让初学者陷入"东一榔头西一棒子"的困境。这里分享我总结的计算机课程学习…

2026/7/30 4:11:47 阅读更多
锡膏管理企业居然能决定良品率?

锡膏管理企业居然能决定良品率?

在电子制造行业,有一句老话叫“七分锡膏、三分焊接”。尽管说法稍显夸张,但锡膏管理的质量与规范性,确实直接关系到SMT产线的良品率。很多工程师和管理者都遇到过这样的场景:明明钢网开孔、印刷参数都没问题,但上线的板…

2026/7/30 4:11:47 阅读更多
国密SM2性能认证:缓存侧信道与恒定时间编程实战

国密SM2性能认证:缓存侧信道与恒定时间编程实战

1. 项目概述:国密性能认证的“隐形门槛”最近和几个做国密产品认证的老朋友聊天,大家不约而同地提到了一个现象:很多基于C语言开发的国密算法库,在功能测试上都能顺利过关,但一到商用密码检测中心的性能认证环节&#…

2026/7/30 4:11:47 阅读更多
PLC系统调试全流程:从硬件核查到压力测试的工程实践

PLC系统调试全流程:从硬件核查到压力测试的工程实践

1. 从“能跑”到“跑得稳”:PLC系统调试的本质与价值干了这么多年自动化,我见过太多项目现场,PLC程序下载进去,设备能动,甲方就催着验收。结果呢?生产线三天两头出点小毛病,不是传感器偶尔误触发…

2026/7/30 13:12:14 阅读更多
Java RSA加密实战:从原理到混合加密与密钥管理

Java RSA加密实战:从原理到混合加密与密钥管理

1. 项目概述:为什么RSA在Java中依然至关重要 最近在整理一个老项目的安全模块,发现里面还在用简单的对称加密处理一些敏感信息传输,这让我心里一惊。在当今这个数据安全被提到前所未有高度的环境下,作为开发者,如果对非…

2026/7/30 13:12:14 阅读更多
针对直放站拉远的场景,远端拉远后出现接入断续的情况,是否需要调整NCS指标?

针对直放站拉远的场景,远端拉远后出现接入断续的情况,是否需要调整NCS指标?

针对直放站拉远的场景,远端拉远后出现接入断续的情况,是否需要调整NCS指标? 导语 在日常的网络优化和基站维护中,“直放站/RRU光纤拉远” 是解决盲区覆盖的常用手段。但不少工程师在拉远后常遇到一个头疼的问题:远端用户接入断续、时好时坏,甚至随机接入成功率暴跌。 遇…

2026/7/30 13:12:14 阅读更多
[GESP202606 四级] 扫雷

[GESP202606 四级] 扫雷

B4557 [GESP202606 四级] 扫雷 https://www.luogu.com.cn/problem/B4557 中国计算机学会(CCF)2026年6月C四级讲解——扫雷 https://www.bilibili.com/video/BV1MCMg6AEXR/ B4557 [GESP202606 四级] 扫雷 https://www.bilibili.com/video/BV1ZKTj6ZEVh/ 2…

2026/7/30 0:01:06 阅读更多