进程管道通讯-伪终端方式
传统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(); };

相关新闻

STM32CubeMX串口配置实战:从HAL库到DMA与空闲中断应用

STM32CubeMX串口配置实战:从HAL库到DMA与空闲中断应用

1. 项目概述:为什么STM32CubeMX是串口配置的“瑞士军刀”如果你刚开始接触STM32,面对一堆寄存器、时钟树和引脚复用,想快速让串口“吐”出第一句“Hello World”,STM32CubeMX几乎是绕不开的起点。它不是什么高深莫测的黑科技&…

2026/7/30 4:09:35阅读更多 →
五层电梯PLC控制系统设计与实现详解

五层电梯PLC控制系统设计与实现详解

1. 项目概述:五层电梯PLC控制系统的核心价值这个项目本质上是用PLC(可编程逻辑控制器)实现一套完整的五层电梯控制系统。作为工业自动化领域的经典案例,电梯控制能充分体现PLC在顺序控制、逻辑判断和安全保护方面的优势。我选择西…

2026/7/30 4:09:35阅读更多 →
STM32定时器时钟源深度解析:从内部时钟到外部同步的工程实践

STM32定时器时钟源深度解析:从内部时钟到外部同步的工程实践

1. 从“心跳”说起:为什么定时器是STM32的基石 如果你玩过STM32,或者任何一款单片机,那你一定绕不开一个东西——定时器。它就像单片机的心脏,负责提供精准的“心跳”,让一切有序运行。没有它,你的延时函数…

2026/7/30 4:09:35阅读更多 →
AI教育系统如何优化教学课题设计与实施

AI教育系统如何优化教学课题设计与实施

1. 项目概述:AI如何重塑教育课题设计流程去年帮某师范院校评审研究生开题报告时,我发现一个惊人现象:超过60%的选题要么脱离教学实际,要么缺乏可操作性。直到接触了百考通AI系统,才意识到传统选题模式的根本问题在于缺…

2026/7/30 5:23:52阅读更多 →
游戏引擎技术升级挑战:从AI行为树到服务器网格化的开发实践

游戏引擎技术升级挑战:从AI行为树到服务器网格化的开发实践

在游戏开发领域,大型多人在线游戏的引擎升级和内容发布延期是常态,但像《星际公民》Alpha 4.10版本“奥利森围城”技术预览这样备受关注的更新,其延期背后的技术原因值得深入探讨。这次更新原计划引入新的行星技术、AI行为树重构、物理引擎优…

2026/7/30 5:23:52阅读更多 →
STM32 GPIO上拉下拉配置详解:从原理到实战避坑指南

STM32 GPIO上拉下拉配置详解:从原理到实战避坑指南

1. 项目概述:从“悬空”到“确定”的引脚状态控制在嵌入式开发,尤其是STM32这类MCU的应用中,GPIO(通用输入输出)引脚的状态控制是基础中的基础。很多新手,甚至一些有经验的开发者,常常会忽略一个…

2026/7/30 5:23:52阅读更多 →
OpenClaw汉化版完整安装与配置指南

OpenClaw汉化版完整安装与配置指南

1. OpenClaw汉化版安装指南:从零开始完整部署作为一名长期从事开源工具部署的技术博主,我最近在多个项目中使用了OpenClaw汉化版,发现它在数据处理和自动化工作流方面确实能带来不少便利。但初次安装时也踩过不少坑,特别是汉化版的…

2026/7/30 5:23:52阅读更多 →
Unity ToggleGroup默认选中首项问题:原理剖析与根治方案

Unity ToggleGroup默认选中首项问题:原理剖析与根治方案

1. 项目概述:一个看似简单却暗藏玄机的交互问题在Unity UI开发中,ToggleGroup组件是构建单选按钮组、标签页切换等功能的基石。它确保了同一组内的Toggle只能有一个处于选中状态,逻辑清晰,使用方便。然而,很多开发者&a…

2026/7/30 5:23:52阅读更多 →
TLS协议深度解析:从握手流程到证书验证与安全配置实战

TLS协议深度解析:从握手流程到证书验证与安全配置实战

1. 从“裸奔”到“加密隧道”:为什么我们需要TLS?如果你在浏览器里输入一个网址,看到地址栏左边出现一把小锁,或者看到“https”开头的链接,那么恭喜你,你和服务器之间的通信正在受到TLS(传输层…

2026/7/30 5:21:51阅读更多 →
覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

🔹 工具基础介绍 OpenClaw 是开源生态中一款实用性较强的本地智能工具,凭借本地离线运行、可视化图形操作和任务自动化三大核心特性,赢得了众多用户的青睐。与普通在线对话AI工具不同,它属于能够直接操控本机软硬件的智能数字员工…

2026/7/29 9:47:45阅读更多 →
伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

所谓液压伺服阀体的精密激光焊接,是用激光束对阀座壳体(通常为不锈钢或铝合金)进行密封焊接,使阀体在21-35MPa的高压液压油或压缩气体中长期运行而不发生介质泄漏。液压伺服阀是高端液压系统的"大脑"。从航空航天飞行控…

2026/7/29 7:00:19阅读更多 →
D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南 【免费下载链接】d2dx D2DX is a complete solution to make Diablo II run well on modern PCs, with high fps and better resolutions. 项目地址: https://gitcode.com/gh_mirrors/d2/d2dx 你是否还在…

2026/7/29 7:58:51阅读更多 →
3分钟解锁iOS应用自由:TrollInstallerX让你的iPhone摆脱安装限制 [特殊字符]

3分钟解锁iOS应用自由:TrollInstallerX让你的iPhone摆脱安装限制 [特殊字符]

3分钟解锁iOS应用自由:TrollInstallerX让你的iPhone摆脱安装限制 🚀 【免费下载链接】TrollInstallerX A TrollStore installer for iOS 14.0 - 16.6.1 项目地址: https://gitcode.com/gh_mirrors/tr/TrollInstallerX 你是否曾经因为iOS系统的严格…

2026/7/30 0:00:58阅读更多 →
[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:00:58阅读更多 →
Windows驱动存储终极清理工具:DriverStoreExplorer完全指南

Windows驱动存储终极清理工具:DriverStoreExplorer完全指南

Windows驱动存储终极清理工具:DriverStoreExplorer完全指南 【免费下载链接】DriverStoreExplorer Driver Store Explorer 项目地址: https://gitcode.com/gh_mirrors/dr/DriverStoreExplorer 您是否曾因Windows系统盘空间不足而烦恼?是否遇到过设…

2026/7/30 0:00:58阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/30 0:27:26阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/30 4:47:18阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/29 14:26:42阅读更多 →