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

传统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();
};
相关推荐
颜x小2 小时前
[C#]泛型类与泛型方法
开发语言·c++·c#
Lhan.zzZ2 小时前
深入理解 windeployqt:混合 C++/Qt 项目的打包指南
开发语言·c++·qt
CodexDave2 小时前
Python 自动化接单实战(九):Windows 免环境交付如何打包与诊断
windows·python·自动化·python自动化·pyinstaller·软件交付·windows打包
zh路西法3 小时前
【Navigation2进阶】(十二):自主探索性能优化与 RIG 算法推导与 Nav2 插件实现
c++·dijkstra·ros2·rrt·navigation2·前沿探索
DB哥讲数据库3 小时前
【windows安装sql server教程】25步图文详解(附安装包)
windows
惠惠软件3 小时前
快速生成磁盘目录文件-双击运行a.bat ,可以看到磁盘目录下出现了 文件目录.txt-供大家学习研究参考
windows·学习·文件列表
星恒随风3 小时前
C++ STL 详解:set 与 multiset 的使用、区间查询和算法应用
开发语言·c++·笔记·学习·算法
再卷也是菜3 小时前
C++11支持并发库
开发语言·c++
Starmoon_dhw3 小时前
重塑你的Windows文件夹图标 Folder Theme Studio
windows