C++单头文件实现windows进程间通信(基于命名管道)

goodtrailer/win-pipe: Single-file C++ library for Windows named pipes IPC.

参考Github开源项目,并补充了双向管道的实现。

本文介绍了一个名为win-pipe的Windows专用命名管道库,主要包含接收器(receiver)、发送器(sender)和**双向管道(DuplexPipe)**三个核心组件。接收器通过异步线程处理消息接收并调用回调函数,发送器提供简单的消息发送功能,双向管道则封装了二者实现双向通信。该库提供了线程安全的消息处理机制,支持管道连接管理、错误处理和资源自动释放。适合需要进程间通信的Windows应用程序开发。

win-pipe.h 需要进行进程间通信的进程,包含此头文件即可

cpp 复制代码
/*
 * ISC License
 *
 * Copyright (c) 2021 Alden Wu
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#pragma once

#ifndef _WIN32
#error "win-pipe is a Windows only library."
#endif

#include <algorithm>
#include <functional>
#include <memory>
#include <mutex>
#include <stdexcept>
#include <string>
#include <vector>

#include <iostream>

#include <Windows.h>

namespace win_pipe {

  namespace details {
    static inline std::string format_name(const std::string& name)
    {
      std::string formatted = R"(\\.\pipe\)";
      formatted += name;
      return formatted;
    }

    struct handle_deleter {
      void operator()(HANDLE handle)
      {
        if (handle != NULL && handle != INVALID_HANDLE_VALUE)
          CloseHandle(handle);
      }
    };

    using unique_handle = std::unique_ptr<void, handle_deleter>;
  }

  using callback_t = std::function<void(uint8_t*, size_t)>;

  // -------------------------------------------------------------------[ receiver

  class receiver {
  public:
    /// <summary>
    /// Default constructor. Does nothing. No pipe is opened/created, and no
    /// read thread is started.
    /// <para/>
    /// Note: remember that move constructor exists. This constructor is mainly
    /// meant for use with containers which require a default constructor.
    /// </summary>
    receiver() = default;

    receiver(const std::string& name, callback_t callback)
    {
      m_param = std::make_unique<thread_param>();

      std::string pipe_name{ details::format_name(name) };
      m_param->pipe.reset(CreateNamedPipeA(
        pipe_name.c_str(),
        PIPE_ACCESS_INBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE,
        PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
        1, 1024, 1024, NMPWAIT_USE_DEFAULT_WAIT, NULL));
      if (m_param->pipe.get() == INVALID_HANDLE_VALUE) {
        std::string msg{ "Pipe creation failed: " };
        msg += std::to_string(GetLastError());
        throw std::runtime_error(msg);
      }

      m_param->callback = callback;
      m_param->event.reset(CreateEventA(NULL, TRUE, FALSE, NULL));

      m_thread.reset(CreateThread(NULL, NULL, thread, m_param.get(), 0, NULL));
    }

    receiver(receiver&&) noexcept = default;

    ~receiver()
    {
      if (m_param)
        SetEvent(m_param->event.get());

      CancelSynchronousIo(m_thread.get());
      WaitForSingleObject(m_thread.get(), INFINITE);
    }

    receiver& operator=(receiver&&) noexcept = default;

    void set_callback(callback_t callback)
    {
      if (!m_param)
        return;

      std::lock_guard<std::mutex> lock{ m_param->callback_mutex };
      m_param->callback = callback;
    }

  private:
    static DWORD WINAPI thread(LPVOID lp)
    {
      auto* param = reinterpret_cast<thread_param*>(lp);
      auto pipe = param->pipe.get();
      auto event = param->event.get();
      auto& callback = param->callback;
      auto& callback_mutex = param->callback_mutex;

      std::vector<uint8_t> buffer(1024);

      while (WaitForSingleObject(event, 1) == WAIT_TIMEOUT) {
        ConnectNamedPipe(pipe, NULL);

        while (WaitForSingleObject(event, 1) == WAIT_TIMEOUT) {
          DWORD bytes_read = 0;
          if (!ReadFile(pipe, buffer.data(), (DWORD)buffer.size(), &bytes_read, NULL)) {
            if (GetLastError() != ERROR_MORE_DATA)
              break;

            DWORD leftover = 0;
            PeekNamedPipe(pipe, NULL, NULL, NULL, NULL, &leftover);
            buffer.resize(bytes_read + leftover);

            DWORD more_bytes_read = 0;
            ReadFile(pipe, buffer.data() + bytes_read, leftover, &more_bytes_read, NULL);
            bytes_read += more_bytes_read;
          }
          std::lock_guard<std::mutex> lock{ callback_mutex };
          callback(buffer.data(), (size_t)bytes_read);
        }

        DisconnectNamedPipe(pipe);
      }

      return TRUE;
    }

  private:
    struct thread_param {
      details::unique_handle pipe;
      details::unique_handle event;
      std::mutex callback_mutex;
      callback_t callback;
    };

  private:
    std::unique_ptr<thread_param> m_param;
    details::unique_handle m_thread;
  };

  // ---------------------------------------------------------------------[ sender

  class sender {
  public:
    /// <summary>
    /// Default constructor. Does nothing. Cannot actually write to a pipe.
    /// <para />
    /// Note: remember that move constructor exists. This constructor is mainly
    /// meant for use with containers which require a default constructor.
    /// </summary>
    sender() = default;

    sender(const std::string& name)
      : m_name{ details::format_name(name) }
    {
    }

    sender(sender&&) noexcept = default;

    sender& operator=(sender&&) noexcept = default;

    bool send(const void* buffer, DWORD size)
    {
      if (WriteFile(m_pipe.get(), buffer, size, NULL, NULL) == FALSE) {
        DWORD error = GetLastError();
        switch (error) {
        case ERROR_INVALID_HANDLE:
        case ERROR_PIPE_NOT_CONNECTED:
          connect();
          break;
        default:
          return false;
        }

        if (WriteFile(m_pipe.get(), buffer, size, NULL, NULL) == FALSE)
          return false;
      }

      FlushFileBuffers(m_pipe.get());
      return true;
    }

  private:
    void connect()
    {
      // In order to CloseHandle before CreateFile, you need to destroy
      // what's inside the unique_ptr by either calling reset() or assigning
      // it nullptr.
      m_pipe = nullptr;
      m_pipe.reset(CreateFileA(m_name.c_str(), GENERIC_WRITE,
        FILE_SHARE_READ, NULL, OPEN_ALWAYS, NULL,
        NULL));
    }

  private:
    details::unique_handle m_pipe;
    std::string m_name;
  };

  class DuplexPipe {
  public:
    using Callback = std::function<void(uint8_t*, size_t)>;

    DuplexPipe(const std::string& self_name, const std::string& peer_name, Callback recv_callback)
      : self_name_(self_name),
      peer_name_(peer_name),
      recv_callback_(recv_callback),
      running_(false)
    {
    }

    void start() {
      // 启动receiver线程
      receiver_ = std::make_unique<win_pipe::receiver>(self_name_.c_str(), recv_callback_);
      running_ = true;
      recv_thread_ = std::thread([this]() {
        while (running_) {
          // 让receiver持续工作(如需阻塞可略过此循环)
          std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }
        });
      // sender可以随时使用,不需要线程
      sender_ = std::make_unique<win_pipe::sender>(peer_name_.c_str());
    }

    void stop() {
      running_ = false;
      if (recv_thread_.joinable()) recv_thread_.join();
      receiver_.reset();
      sender_.reset();
    }

    void send(const uint8_t* data, size_t size) {
      if (sender_) sender_->send(data, size);
    }

    void send(const std::string& str) {
      if (sender_)
        sender_->send((uint8_t*)str.data(), str.size());
    }

    ~DuplexPipe() {
      stop();
    }

  private:
    std::string self_name_;
    std::string peer_name_;
    Callback recv_callback_;
    std::unique_ptr<win_pipe::receiver> receiver_;
    std::unique_ptr<win_pipe::sender> sender_;
    std::thread recv_thread_;
    std::atomic<bool> running_;
  };

}

进程A main.cpp

cpp 复制代码
#include "win_pipe.h"
#include <iostream>
#include <thread>
#include <chrono>
#include <string>

int main() {
    std::cout << "进程A启动..." << std::endl;

    try {
        // 进程A:监听管道 "pipe_a",向管道 "pipe_b" 发送数据
        win_pipe::DuplexPipe pipe(
            "pipe_a",  // 自身监听的管道名
            "pipe_b",  // 对端管道名
            [](uint8_t* data, size_t size) {
                // 接收数据的回调函数
                std::string message(reinterpret_cast<char*>(data), size);
                std::cout << "[进程A 收到]: " << message << std::endl;
            }
        );

        pipe.start();
        std::cout << "进程A管道已启动" << std::endl;

        // 发送一些测试消息
        for (int i = 1; i <= 5; ++i) {
            std::string message = "Hello from Process A, message #" + std::to_string(i);
            pipe.send(message);
            std::cout << "[进程A 发送]: " << message << std::endl;
            std::this_thread::sleep_for(std::chrono::seconds(2));
        }

        // 等待一段时间让进程B有机会回复
        std::this_thread::sleep_for(std::chrono::seconds(10));

        pipe.stop();
        std::cout << "进程A结束" << std::endl;

    } catch (const std::exception& e) {
        std::cerr << "进程A错误: " << e.what() << std::endl;
        return 1;
    }

    return 0;
}

进程B main.cpp

cpp 复制代码
#include "win_pipe.h"
#include <iostream>
#include <thread>
#include <chrono>
#include <string>

int main() {
    std::cout << "进程B启动..." << std::endl;

    try {
        // 进程B:监听管道 "pipe_b",向管道 "pipe_a" 发送数据
        win_pipe::DuplexPipe pipe(
            "pipe_b",  // 自身监听的管道名
            "pipe_a",  // 对端管道名  
            [](uint8_t* data, size_t size) {
                // 接收数据的回调函数
                std::string message(reinterpret_cast<char*>(data), size);
                std::cout << "[进程B 收到]: " << message << std::endl;
                
                // 可以在这里添加回复逻辑
            }
        );

        pipe.start();
        std::cout << "进程B管道已启动" << std::endl;

        // 发送回复消息
        for (int i = 1; i <= 3; ++i) {
            std::string message = "Reply from Process B, reply #" + std::to_string(i);
            pipe.send(message);
            std::cout << "[进程B 发送]: " << message << std::endl;
            std::this_thread::sleep_for(std::chrono::seconds(3));
        }

        // 保持运行以接收更多消息
        std::this_thread::sleep_for(std::chrono::seconds(15));

        pipe.stop();
        std::cout << "进程B结束" << std::endl;

    } catch (const std::exception& e) {
        std::cerr << "进程B错误: " << e.what() << std::endl;
        return 1;
    }

    return 0;
}
相关推荐
平凡灵感码头2 小时前
STM32 程序内存分布详解
stm32·单片机·嵌入式硬件
沉木渡香3 小时前
VSCode中Java开发环境配置的三个层级(Windows版)1-3
java·windows·vscode
btzhy3 小时前
STM32单片机:基本定时器应用:精确定时(STM32L4xx)
stm32·单片机·嵌入式硬件·基本定时器应用:精确定时
磨十三9 小时前
C++ 标准库排序算法 std::sort 使用详解
开发语言·c++·排序算法
云山工作室9 小时前
基于单片机智能水产养殖系统设计(论文+源码)
单片机·嵌入式硬件·毕业设计·毕设
xiaomin201710 小时前
【STM32 HAL库】高级定时器TIM8输出PWM
stm32·单片机·嵌入式硬件
湫兮之风11 小时前
C++: Lambda表达式详解(从入门到深入)
开发语言·c++
奔跑吧邓邓子11 小时前
【C++实战(54)】C++11新特性实战:解锁原子操作与异步编程的奥秘
c++·实战·c++11新特性·原子操作·异步编程
Mr_WangAndy11 小时前
C++设计模式_结构型模式_适配器模式Adapter
c++·设计模式·适配器模式·c++设计模式