You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

向子进程发信息需每次新建管道?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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 21:45:38