进程管道通讯-伪终端方式

传统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();
};
相关推荐
王老师青少年编程5 小时前
2026年9月GESP真题及题解(C++七级):必经之路
c++·题解·真题·gesp·七级·2026年9月·必经之路
硫酸锌015 小时前
在手机上运行Python增量备份手机数据到PC电脑
android·windows·python
码匠许师傅5 小时前
【C++三方组件】Asio 上篇:回调式 TCP 客户端与服务端
开发语言·c++·tcp/ip
沐欣工作室_lvyiyi5 小时前
家庭水资源节能系统设计(论文+源码)
c++·python·云平台·单片机毕业设计·单片机仿真·电子信息工程毕业设计·机械电子工程
Yyyyyy~5 小时前
[c++]数组
c++
知识分享小能手6 小时前
C++ 学习教程,从入门到精通,C++内存模型和名称空间 — 详细知识点总结(9)
开发语言·c++·学习
java_logo6 小时前
Docker 部署 dockurr/windows:轻松搭建浏览器可控 Windows 虚拟机
运维·windows·docker·容器·虚拟机·kvm·轩辕镜像
2601_962218616 小时前
c++中多重继承与虚继承的实现
开发语言·c++
沫璃染墨6 小时前
《Qt从零入门系列(十一):Qt事件机制详解——从QEvent到鼠标、键盘与定时器事件》
c++·qt·ui·硬件工程·交互·个人开发·qt5
2601_962218616 小时前
C++中分配器allocator的实现
开发语言·c++