向子进程发信息需每次新建管道?WinAPI管道复用问题
管道进程间通信问题:如何实现多次消息发送
我正在开发一个带两个控制台窗口的程序,通过启动子进程并生成独立控制台窗口实现。需求是父进程能多次向子进程发送消息,在子控制台打印。采用管道实现进程间通信,但遇到问题:
- 按照微软示例实现后,
ReadFile仅在写端句柄关闭时才读取缓冲区,关闭句柄后无法再发送后续消息 - 尝试每次发消息新建管道,效率太低,想找更优方案
实际运行现象:
- 第一次
WriteFile成功,子进程能接收;第二次WriteFile失败,因为写句柄已关闭 - 子进程会结束,因为写句柄关闭后,
ReadFile不再阻塞
父进程原代码
#define UNICODE #include <windows.h> #include <stdio.h> #include <iostream> #include <string> #include <cstring> int main(int argc, char* argv[]) { HANDLE ChildStd_IN_Rd = NULL; // Handle for reading from pipe (to be sent to child) HANDLE ChildStd_IN_Wr = NULL; // Handle for writing to the pipe (used by parent) SECURITY_ATTRIBUTES saAttr; // Used as input for the create pipe function to tell it to give handles saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); // Set size saAttr.bInheritHandle = TRUE; // Tell child process to inherit handles saAttr.lpSecurityDescriptor = NULL; // Create a pipe for the child process's STDIN. if (! CreatePipe(&ChildStd_IN_Rd, &ChildStd_IN_Wr, &saAttr, 0)) std::cout << "Failed to create pipe" << std::endl; // Ensure the write handle to the pipe for STDIN is not inherited. if ( ! SetHandleInformation(ChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0) ) std::cout << "Failed to set Handle information" << std::endl; STARTUPINFO si; // This stores info for the main console when a new process console is made PROCESS_INFORMATION pi; // Stores the information for the new process being created // This sets the memeory of the location of these to zero and then fills them with the correct info // I am not sure why they set this to zero but it was done on the example I am following ZeroMemory(&pi, sizeof(pi)); ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); si.hStdInput = ChildStd_IN_Rd; // Set the standard input of the child to the handle we made for the pipe si.dwFlags |= STARTF_USESTDHANDLES; // Tells the child to use the above handle for the std input // Now this starts the actual child process if ( !CreateProcess(L"test1Child.exe", NULL, // This is a command line to run NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi)) { std::cout << "Failed to create Child" << std::endl; return 0; } else { CloseHandle(ChildStd_IN_Rd); // This will close the handle to the read pipe which is now already duplicated to the Child process // Just a note, it took way longer than I think it should to find out that each process has a duplicate handle. So closing the handle here only closes it for the parent and not the child. } std::string toSend; // String to send toSend = "Hello There"; int bytesToSend = toSend.length() + 1; // Get the length of the string to know how much to send char* toSend_char = new char [toSend.length()+1]; // Make the char* array to store the string to send std::strcpy(toSend_char, toSend.c_str()); // Put the string into the char* array DWORD bytesWritten; // Number of bytes actually written BOOL writeSuccess; // Bool to see if the write was succesful writeSuccess = WriteFile(ChildStd_IN_Wr, toSend_char, bytesToSend, &bytesWritten, NULL); // Write it BABY! CloseHandle(ChildStd_IN_Wr); // Trying to send it again toSend = "Hello There2"; bytesToSend = toSend.length() + 1; // Get the length of the string to know how much to send toSend_char = new char [toSend.length()+1]; // Make the char* array to store the string to send std::strcpy(toSend_char, toSend.c_str()); // Put the string into the char* array writeSuccess = WriteFile(ChildStd_IN_Wr, toSend_char, bytesToSend, &bytesWritten, NULL); // Write it BABY! CloseHandle(ChildStd_IN_Wr); std::cout << "If this is a zero the second write failed: " << writeSuccess << std::endl; system("pause"); // Cose the process and thread handles of the child CloseHandle(pi.hProcess); CloseHandle(pi.hThread); return 0; }
子进程原代码
#include <iostream> #include <windows.h> #include <stdio.h> #include <string> #define BUFSIZE 4096 int main(int argc, char* argv[]) { CHAR readBuf[BUFSIZE]; // Buffer to read to DWORD numBytesRead; // To store the number of Bytes that were read HANDLE Stdin; // Handle for the standard input from the other function Stdin = GetStdHandle(STD_INPUT_HANDLE); // Set the input handle to the standard input handle. This should be the one given to the process by the parent BOOL readSuccess; // Bool to check if the read was successful std::string readString; // String Read in for (;;) { readSuccess = ReadFile(Stdin, readBuf, BUFSIZE, &numBytesRead, NULL); // Read the Pipe if (!readSuccess || numBytesRead == 0) { break; } readString = readBuf; std::cout << readString << std::endl; } for (;;) { readSuccess = ReadFile(Stdin, readBuf, BUFSIZE, &numBytesRead, NULL); if (!readSuccess || numBytesRead == 0) { break; } readString = readBuf; std::cout << readString << std::endl; } std::cout << "If this is a 0 then it failed: " << readSuccess << std::endl; Sleep(1000); return 0; }
问题分析与解决方案
核心问题
管道并非只能一次性使用,你的代码错误在于第一次发送后就关闭了写句柄,导致后续无法写入。匿名管道是流式通道,只要写句柄保持打开,就能多次写入数据。
修正方案
1. 父进程端修改
- 仅在所有消息发送完成后关闭
ChildStd_IN_Wr句柄 - 修复内存泄漏:释放
new分配的字符数组
2. 子进程端修改
- 合并重复的读取循环,用一个循环持续监听管道
- 手动添加字符串终止符,避免读取后出现乱码(
ReadFile不会自动添加\0)
修正后的代码
父进程代码
#define UNICODE #include <windows.h> #include <stdio.h> #include <iostream> #include <string> #include <cstring> int main(int argc, char* argv[]) { HANDLE ChildStd_IN_Rd = NULL; HANDLE ChildStd_IN_Wr = NULL; SECURITY_ATTRIBUTES saAttr; saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); saAttr.bInheritHandle = TRUE; saAttr.lpSecurityDescriptor = NULL; if (!CreatePipe(&ChildStd_IN_Rd, &ChildStd_IN_Wr, &saAttr, 0)) std::cout << "Failed to create pipe" << std::endl; if (!SetHandleInformation(ChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0)) std::cout << "Failed to set Handle information" << std::endl; STARTUPINFO si; PROCESS_INFORMATION pi; ZeroMemory(&pi, sizeof(pi)); ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); si.hStdInput = ChildStd_IN_Rd; si.dwFlags |= STARTF_USESTDHANDLES; if (!CreateProcess(L"test1Child.exe", NULL, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi)) { std::cout << "Failed to create Child" << std::endl; return 0; } else { CloseHandle(ChildStd_IN_Rd); } std::string toSend; DWORD bytesWritten; BOOL writeSuccess; // 第一次发送 toSend = "Hello There"; int bytesToSend = toSend.length() + 1; char* toSend_char = new char[bytesToSend]; std::strcpy(toSend_char, toSend.c_str()); writeSuccess = WriteFile(ChildStd_IN_Wr, toSend_char, bytesToSend, &bytesWritten, NULL); delete[] toSend_char; // 释放内存 // 第二次发送 toSend = "Hello There2"; bytesToSend = toSend.length() + 1; toSend_char = new char[bytesToSend]; std::strcpy(toSend_char, toSend.c_str()); writeSuccess = WriteFile(ChildStd_IN_Wr, toSend_char, bytesToSend, &bytesWritten, NULL); delete[] toSend_char; // 释放内存 // 所有消息发送完成后,关闭写句柄 CloseHandle(ChildStd_IN_Wr); std::cout << "If this is a zero the second write failed: " << writeSuccess << std::endl; system("pause"); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); return 0; }
子进程代码
#include <iostream> #include <windows.h> #include <stdio.h> #include <string> #define BUFSIZE 4096 int main(int argc, char* argv[]) { CHAR readBuf[BUFSIZE]; DWORD numBytesRead; HANDLE Stdin = GetStdHandle(STD_INPUT_HANDLE); BOOL readSuccess; std::string readString; // 一个循环持续读取,直到管道关闭 while (true) { readSuccess = ReadFile(Stdin, readBuf, BUFSIZE - 1, &numBytesRead, NULL); // 留一个位置给\0 if (!readSuccess || numBytesRead == 0) { break; } readBuf[numBytesRead] = '\0'; // 手动添加字符串终止符 readString = readBuf; std::cout << readString << std::endl; } std::cout << "If this is a 0 then it failed: " << readSuccess << std::endl; Sleep(1000); return 0; }
额外说明
- 匿名管道是流式的,没有消息边界,若需要区分不同消息,需自定义协议(比如每个消息前添加长度前缀)
- 如需双向通信,可再创建一对管道用于子进程向父进程发送消息
- 确保所有不再使用的句柄及时关闭,避免资源泄漏
内容的提问来源于stack exchange,提问作者Slippery_Chickenz
相关产品推荐
相关产品推荐

