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

传统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();
};
相关推荐
程序喵大人26 分钟前
【C++入门】值类别与表达式 - 03 引用绑定:为什么有些参数能接住临时对象
开发语言·c++·引用绑定
handler011 小时前
【Linux】信号:内核的“敲门声”
linux·运维·服务器·c++·c·信号·signal
欧特克_Glodon1 小时前
OpenCV计算机视觉开发入门与实践<三十五>:开发视频播放器
c++·opencv·计算机视觉·音视频
hansang_IR2 小时前
【题解】P9753 [CSP-S 2023] 消消乐
数据结构·c++·算法
竞赛考级题库2 小时前
202606 青少年等级考试C/C++真题一级 建议答题时长:60min
java·c语言·c++
佳&弥2 小时前
后渗透痕迹清理
服务器·windows·web安全·网络安全
linx2952 小时前
阶段五讲义:STL 全貌——扔掉手写循环,学“动词“
c++
handler013 小时前
【Linux】动态库、静态库使用与原理
linux·运维·服务器·c++·c·动态库·静态库
MSTcheng.3 小时前
【Linux学习】Linux学习第四弹——Linux基本指令3
linux·windows·学习·操作系统·指令
raindayinrain3 小时前
c++并发--同步
c++·同步