2026/1/1 6:17:17
网站建设
项目流程
白云区网站建设,湖南自驾旅游与房车协会,wordpress著名博客,怎样上百度做广告概述可能是出于C效率更高、写硬件驱动更方便、或是反编译难度更高的原因#xff0c;现在有些项目喜欢使用C#与C混合编程#xff0c;C#/WPF写界面与一些界面逻辑#xff0c;C写一些驱动或是业务逻辑。那么要实现这一点#xff0c;就无法避免C#与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_INBOUND、PIPE_ACCESS_OUTBOUND与PIPE_ACCESS_DUPLEX。这三个常量定义了命名管道的访问方向控制数据在管道中的流动方向。常量名称值含义PIPE_ACCESS_INBOUND0x00000001管道仅用于数据输入客户端到服务器PIPE_ACCESS_OUTBOUND0x00000002管道仅用于数据输出服务器到客户端PIPE_ACCESS_DUPLEX0x00000003管道支持双向数据传输接下来的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.服务器名称.表示本地计算机pipeNameInstrumentControlPipe管道名称客户端和服务器必须使用相同的名称directionPipeDirection.InOut管道方向InOut表示双向通信既能读取也能写入optionsPipeOptions.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_castDWORD(response.length()), // 数据长度 dwWritten, // 实际写入的字节数 NULL); // 不使用重叠I/O然后现在还需要C#程序中读取C程序的返回值。private StreamReader _reader; string response await reader.ReadLineAsync();使用StreamReader类进行读取即可。StreamWriter与StreamReader的初始化可以这样写_writer new StreamWriter(_client, Encoding.UTF8) { AutoFlush true }; _reader new StreamReader(_client, Encoding.UTF8);本文只是简单介绍一下可以通过命名管道实现C#与C的进程间通信感兴趣的同学也可以通过AI写一个Demo进行学习。