news 2026/3/8 10:06:12

通过命名管道实现C#与C++进程间通信

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
通过命名管道实现C#与C++进程间通信

概述

可能是出于C++效率更高、写硬件驱动更方便、或是反编译难度更高的原因,现在有些项目喜欢使用C#与C++混合编程,C#/WPF写界面与一些界面逻辑,C++写一些驱动或是业务逻辑。那么要实现这一点,就无法避免C#与C++的交互问题。

之间使用过C++封装成DLL然后C#通过P/Invoke调用,也搞过直接通过命令行调用。今天介绍的是通过命名管道实现C#与C++进程间通信。

命名管道介绍

命名管道(Named Pipe)是一种进程间通信(IPC)机制,它允许在同一台计算机上的不同进程或网络中不同计算机上的进程之间进行数据交换。与匿名管道不同,命名管道具有明确的名称,可以在不相关的进程之间建立通信通道,支持双向数据传输,并且可以同时被多个客户端进程连接。命名管道采用文件系统风格的命名方式,在Windows系统中通常以".\pipe\管道名"的形式存在,提供了可靠、有序的数据传输服务,广泛应用于需要进程间数据共享和通信的场景,如客户端-服务器应用程序、系统服务与用户程序之间的数据交换等。

实践

今天举的这个例子是C#程序充当命名管道客户端,C++程序充当命名管道服务端。

效果:

模拟的是在C#程序中点击打开仪器按钮,通过命名管道发送一个指令,服务端根据这个指令进行实际的操作,然后返回操作的结果。

本文不详细解释每一个步骤,只会对关键步骤进行方便自己回顾的备忘录,目前只要知道有这种方式就行了,如果想通过完整例子学习,可以叫AI实现一个完整的例子进行学习就好了。

首选需要创建一个命名管道:

// 创建命名管道 HANDLE hPipe = CreateNamedPipe( PIPE_NAME, // 管道名称 PIPE_ACCESS_DUPLEX, // 双向管道 PIPE_TYPE_MESSAGE | // 消息类型管道 PIPE_READMODE_MESSAGE | // 消息读取模式 PIPE_WAIT, // 阻塞模式 PIPE_UNLIMITED_INSTANCES, // 无限制实例数 BUFFER_SIZE, // 输出缓冲区大小 BUFFER_SIZE, // 输入缓冲区大小 0, // 默认超时 NULL); // 默认安全属性

PIPE_NAME表示管道名称,可以在开头这样写来定义:#define PIPE_NAME TEXT("\\\\.\\pipe\\InstrumentControlPipe")\\.\表示本地计算机,pipe\表示命名管道设备,InstrumentControlPipe是自定义的管道名称。

管道方向可选值为:PIPE_ACCESS_INBOUNDPIPE_ACCESS_OUTBOUNDPIPE_ACCESS_DUPLEX

这三个常量定义了命名管道的访问方向,控制数据在管道中的流动方向。

常量名称

含义

PIPE_ACCESS_INBOUND

0x00000001

管道仅用于数据输入(客户端到服务器)

PIPE_ACCESS_OUTBOUND

0x00000002

管道仅用于数据输出(服务器到客户端)

PIPE_ACCESS_DUPLEX

0x00000003

管道支持双向数据传输

接下来的PIPE_TYPE_MESSAGE |PIPE_READMODE_MESSAGE | PIPE_WAIT都是dwPipeMode 参数的一部分,通过按位或运算组合在一起。

PIPE_TYPE_MESSAGE:将管道设置为消息模式,数据以离散消息形式传输,保持消息边界。

PIPE_READMODE_MESSAGE:以消息为单位读取数据,确保读取完整的消息而不是任意字节。

PIPE_WAIT:阻塞模式操作,当没有数据可读时调用线程会阻塞等待。

PIPE_UNLIMITED_INSTANCES虽然看过去像是无限制实例数,但是查看它的定义#define PIPE_UNLIMITED_INSTANCES 255发现最多只能存在255个实例。

BUFFER_SIZE可以在前面这样定义:#define BUFFER_SIZE 512

0:使用默认的50毫秒超时。

NULL:使用默认安全描述符,句柄不可被子进程继承。

命名管道的类型为HANDLEHANDLE是 Windows 操作系统中用于标识系统资源(如文件、管道、进程、线程等)的抽象句柄。它本质上是一个不透明的指针或整数,用于唯一标识操作系统管理的各种对象。

现在需要先等待客户端连接:

// 等待客户端连接 BOOL fConnected = ConnectNamedPipe(hPipe, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);

ConnectNamedPipe()会阻塞等待,直到客户端连接。

现在再来看下C#程序中是如何连接这个C++服务端。

C#中创建一个命名管道客户端,可以使用NamedPipeClientStream这个类,NamedPipeClientStream是 .NET 中用于实现命名管道客户端的类,位于System.IO.Pipes命名空间中。它提供了一种进程间通信(IPC)机制,允许在同一台计算机上的不同进程之间或网络上的不同计算机之间进行数据交换。

查看创建客户端的代码:

private NamedPipeClientStream _client; _client = new NamedPipeClientStream(".", "InstrumentControlPipe", PipeDirection.InOut, PipeOptions.Asynchronous);

查看对应的构造函数:

public NamedPipeClientStream(string serverName, string pipeName, PipeDirection direction, PipeOptions options) : this(serverName, pipeName, direction, options, TokenImpersonationLevel.None, HandleInheritability.None) { }

参数名

参数值

说明

serverName

"."

服务器名称,"."表示本地计算机

pipeName

"InstrumentControlPipe"

管道名称,客户端和服务器必须使用相同的名称

direction

PipeDirection.InOut

管道方向,InOut表示双向通信(既能读取也能写入)

options

PipeOptions.Asynchronous

管道选项,Asynchronous表示启用异步操作,避免UI阻塞

然后await _client.ConnectAsync(5000);即可连接到这个命名管道。

现在C#程序向这个管道发送数据,这样写即可:

private StreamWriter _writer; _writer.Write(command + "\n"); _writer.Flush();

使用StreamWriter类向这个管道写入数据。

现在再来看下C++程序中,如何接受这个数据。

// 读取客户端发送的数据 BOOL fSuccess = ReadFile( hPipe, // 管道句柄 buffer, // 接收数据的缓冲区 BUFFER_SIZE - 1, // 缓冲区大小,保留一个位置给null终止符 &dwRead, // 实际读取的字节数 NULL); // 不使用重叠I/O

然后就是获取buffer的内容,这里做这些处理,就是确保buffer内容可以正确提取出来。

// 确保字符串以null结尾 buffer[dwRead] = '\0'; // 将新读取的数据添加到命令缓冲区 commandBuffer += buffer; // 检查是否接收到完整的命令(以换行符或回车符结尾) size_t pos = commandBuffer.find_first_of("\r\n"); if (pos == std::string::npos) { // 命令不完整,继续读取 continue; } // 提取完整命令 std::string rawCommand = commandBuffer.substr(0, pos); commandBuffer = commandBuffer.substr(pos + 1); // 去除可能的BOM标记和空白字符 std::string command = rawCommand; // 去除字符串前后的空白字符 size_t start = command.find_first_not_of(" \t\r\n"); if (start != std::string::npos) { size_t end = command.find_last_not_of(" \t\r\n"); command = command.substr(start, end - start + 1); } else { command.clear(); } // 去除可能的UTF-8 BOM if (command.length() >= 3 && (unsignedchar)command[0] == 0xEF && (unsignedchar)command[1] == 0xBB && (unsignedchar)command[2] == 0xBF) { command = command.substr(3); }

然后根据指令执行不同的操作:

if (command == "Open") { std::cout << "收到命令: Open - 正在打开仪器..." << std::endl; // 这里可以添加实际的仪器控制代码 Sleep(3000); // 模拟操作时间 // 90%概率成功,10%概率失败 std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution<> dis(1, 100); int random = dis(gen); if (random <= 90) { success = true; response = "true\n"; // 添加换行符以便客户端读取 std::cout << "仪器已打开 - 成功" << std::endl; } else { success = false; response = "false\n"; // 添加换行符以便客户端读取 std::cout << "仪器打开失败" << std::endl; } } elseif (command == "Close") { std::cout << "收到命令: Close - 正在关闭仪器..." << std::endl; // 这里可以添加实际的仪器控制代码 Sleep(3000); // 模拟操作时间 // 90%概率成功,10%概率失败 std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution<> dis(1, 100); int random = dis(gen); if (random <= 90) { success = true; response = "true\n"; // 添加换行符以便客户端读取 std::cout << "仪器已关闭 - 成功" << std::endl; } else { success = false; response = "false\n"; // 添加换行符以便客户端读取 std::cout << "仪器关闭失败" << std::endl; } }

C++服务端向客户端发送数据,可以这样写:

// 向客户端发送响应 DWORD dwWritten; BOOL writeSuccess = WriteFile( hPipe, // 管道句柄 response.c_str(), // 要发送的数据 static_cast<DWORD>(response.length()), // 数据长度 &dwWritten, // 实际写入的字节数 NULL); // 不使用重叠I/O

然后现在还需要C#程序中读取C++程序的返回值。

private StreamReader _reader; string response = await reader.ReadLineAsync();

使用StreamReader类进行读取即可。StreamWriterStreamReader的初始化可以这样写:

_writer = new StreamWriter(_client, Encoding.UTF8) { AutoFlush = true }; _reader = new StreamReader(_client, Encoding.UTF8);

本文只是简单介绍一下可以通过命名管道实现C#与C++的进程间通信,感兴趣的同学也可以通过AI写一个Demo进行学习。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/27 6:33:03

Java毕设项目推荐-基于SpringBoot的校园失物招领系统的设计与实现基于springboot的学院失物招领平台的设计与实现【附源码+文档,调试定制服务】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/3/5 0:36:03

MacOS远程桌面报错0x207解决方法

用Windows App远程桌面至Ubuntu 24.04&#xff0c;报错0x207的解决办法。1. 首先确保Ubuntu端的设置没有问题。用其他设备&#xff08;windows等&#xff09;可以正常连接。且用户名密码均无误。 2. 将远程桌面连接导出为RDP文件。用文本编辑器打开&#xff0c;找到“use redir…

作者头像 李华
网站建设 2026/3/3 18:20:50

Dify可视化编排中的条件分支逻辑设置方法

Dify可视化编排中的条件分支逻辑设置方法 在构建AI驱动的应用时&#xff0c;一个常见的挑战是&#xff1a;如何让大语言模型&#xff08;LLM&#xff09;不只是“回答问题”&#xff0c;而是真正具备“判断能力”&#xff1f;比如&#xff0c;当用户说“我衣服尺码不对想退货”…

作者头像 李华
网站建设 2026/3/3 13:55:08

具身机械主义框架下的智能制造L3系统架构核心要素

一、 具身机械主义在 L3 的落地定义L3 的具身智能源自一个机制化的运营认知体&#xff1a;用事件驱动的状态估计与意图对齐&#xff0c;在护栏约束下生成可验证的段级行动&#xff0c;并用证据链闭环塑形世界模型与策略。二、 核心要素&#xff08;按“具身—机制—治理”组织&…

作者头像 李华
网站建设 2026/3/2 20:04:19

Dify镜像与Kubernetes集群的整合部署方案

Dify镜像与Kubernetes集群的整合部署方案 在企业加速拥抱大模型的今天&#xff0c;如何高效构建、稳定运行并快速迭代AI应用&#xff0c;已成为技术团队的核心命题。传统的开发模式往往陷入“一人一环境”的泥潭&#xff1a;算法工程师忙于调参&#xff0c;后端疲于部署&#x…

作者头像 李华