011. UG二次开发,UG管道通讯服务器与wpf客户端通讯

"管道通讯服务"= 专门用来传信息的通道服务​​,就像一根水管,一头进、另一头出,中间只负责把东西送过去。

举几个生活例子你就懂了:

  • ​两家用一根管子传纸条​:甲写纸条塞进去,乙在另一头拿出来。这"管子"就是管道,传纸条就是通讯,整套帮你管管子、保证不丢、按顺序到的,就叫"管道通讯服务"。
  • ​工厂传送带​:前一道工序把零件放上传送带,后一道工序在尽头拿。不管中间传多远,只管"稳稳送到",不关心零件怎么用。
  • ​打电话的线​:运营商给你拉根线,只负责把声音从你这头传到对方那头,不替你编内容------这也算"纯管道"通讯。

放到电脑/手机里也一个道理:

  • 一个软件想把数据发给另一个软件,不走硬盘存文件,直接走"管道"发过去,又快又不留垃圾。
  • 公司里多个系统互相发订单、发日志,中间用"消息管道"排队传,不会堵、不会丢,这就是现在常说的管道通讯服务。

​记住一句就行​​:谁产生信息 → 通过"管道" → 谁接收信息;管道只管运输,不管内容。

一.核心需求

1.UG作为管道服务器,异步监听

目的是想实现ug2512上挂载dll作为管道通讯服务器,实时异步监听连接我的客户端发来的信息,并将信息读取出来,后期还会根据客户端发来的信息指令建模等功能,另一点是,dll生命周期与ug一致,一旦ug软件关闭,必须马上卸载管道服务器,减少内存占用

2.WPF程序做客户端,连接服务器并通讯

外部模式的客户端连接管道通讯服务器,连接成功后,发送一条测试信息给服务器

二实现过程

1.服务器程序设计

UG挂载的dll管道服务器程序设计

服务器dll项目须添加程序集引用

完整代码

这段代码通过后台子线程监听管道消息 + 线程安全队列(ConcurrentQueue)存储数据 + WinForms 定时器切回 NX UI 主线程 的架构,完美解决了 NXOpen API 非线程安全的问题。

cs 复制代码
using System;
using System.Collections.Concurrent; // 提供并发线程安全的集合(ConcurrentQueue)
using System.IO;
using System.IO.Pipes;               // 提供命名管道 IPC(进程间通信)功能
using System.Text;
using System.Threading;
using System.Threading.Tasks;        // 提供 async/await 异步任务支持
using System.Windows.Forms;          // 提供 Win32 消息泵绑定的 System.Windows.Forms.Timer
using NXOpen;                        // NX 核心托管 API
using NXOpen.UF;                     // NX User Function (UF) C 语言 API 包装层

namespace Sever_Tool
{
    public class Program
    {
        // 声明 NXOpen 的核心会话句柄对象
        private static Session theSession;                      // NX 高级 API 会话对象
        private static UFSession theUFSession = null;          // NX UF 底层 API 会话对象
        private static UI theUI;                                // NX UI 界面管理器对象
        private static Program theProgram;                      // 当前类的静态实例指针
        private static bool isDisposeCalled;                    // 标记变量,防止 Dispose 被重复调用执行

        // 管道服务与后台线程控制变量
        private static NamedPipeServerStream pipeServer;        // Windows 命名管道服务端对象
        private static CancellationTokenSource cts;             // 用于通知和取消后台 Task 任务的令牌源
        private static Task listenTask;                         // 运行后台管道监听的异步任务对象

        // 【跨线程数据桥梁】线程安全队列:后台子线程负责向队尾存入指令 (Enqueue),NX 主线程负责从队头取出指令 (Dequeue)
        private static ConcurrentQueue<string> messageQueue = new ConcurrentQueue<string>();

        // 【跨线程调度器】WinForms 定时器:基于 Windows 消息队列(WM_TIMER),确保 Tick 事件严格在 NX 主 UI 线程触发
        private static System.Windows.Forms.Timer uiTimer;

        /// <summary>
        /// 类的构造函数:初始化 NXOpen 环境句柄
        /// </summary>
        public Program()
        {
            try
            {
                theSession = Session.GetSession();             // 获取当前 NX 会话的单例
                theUI = UI.GetUI();                             // 获取当前 NX UI 界面的单例
                theUFSession = UFSession.GetUFSession();       // 获取当前 NX UFSession 的单例
                isDisposeCalled = false;                        // 初始化释放标记为 false
            }
            catch (NXOpen.NXException)
            {
                // 捕获 NX 内部异常,防止构造失败崩溃
            }
        }

        /// <summary>
        /// DLL 被 NX 加载或通过 Ctrl+U 执行时的入口点函数(必定运行于 NX 主 UI 线程)
        /// </summary>
        /// <param name="args">传递给 DLL 的入口参数</param>
        /// <returns>返回状态标识(0 为正常退出)</returns>
        public static int Main(string[] args)
        {
            int retValue = 0;
            try
            {
                theProgram = new Program();                     // 实例化当前 Program 对象

                // 1. 初始化 WinForms 定时器(在主线程中创建,使其绑定到 NX 主 UI 线程的消息泵)
                uiTimer = new System.Windows.Forms.Timer();
                uiTimer.Interval = 100;                         // 设置轮询时间间隔为 100 毫秒
                uiTimer.Tick += OnUiTimerTick;                  // 绑定定时器触发时的回调方法
                uiTimer.Start();                                // 启动定时器开始轮询消息队列

                // 2. 启动后台异步管道监听服务
                StartServer();

                // 在 NX 状态栏/提示框中输出启动成功信息(在 Main 中执行是安全的,因为在主线程)
                theUFSession.Ui.DisplayMessage("管道服务端已启动,主线程安全调度器已注册!", 1);
            }
            catch (NXOpen.NXException ex)
            {
                // 捕获并忽略 NXOpen 执行异常
            }
            return retValue;                                    // 返回执行结果码
        }

        /// <summary>
        /// WinForms 定时器的 Tick 回调函数:由 NX 软件的 Win32 UI 主线程每 100ms 自动触发一次
        /// </summary>
        private static void OnUiTimerTick(object sender, EventArgs e)
        {
            // 循环轮询队列:只要队列中有未处理的客户端消息,就逐个取出
            while (messageQueue.TryDequeue(out string message))
            {
                try
                {
                    // 【绝对线程安全区域】因为处于 NX 主 Win32 UI 线程中,可以放心调用任何 NXOpen API
                    ProcessClientCommandInMainThread(message);
                }
                catch (Exception ex)
                {
                    // 防止单条消息引起的业务异常中断整个定时器循环
                }
            }
        }

        /// <summary>
        /// 启动后台管道监听线程的控制逻辑
        /// </summary>
        private static void StartServer()
        {
            // 防止重复启动:如果任务已存在且正在运行,则直接返回
            if (listenTask != null && !listenTask.IsCompleted) return;

            cts = new CancellationTokenSource();                // 重新实例化取消令牌源
            // 创建并启动一个全新的后台 Task 任务来运行管道监听方法 ListenForClientsAsync
            listenTask = Task.Run(() => ListenForClientsAsync(cts.Token));
        }

        /// <summary>
        /// 异步管道监听循环函数(运行于 .NET 线程池的后台子线程中)
        /// </summary>
        /// <param name="token">传递给异步任务的取消令牌,用于停止监听</param>
        private static async Task ListenForClientsAsync(CancellationToken token)
        {
            const string pipeName = "NX_Pipe_Server";          // 命名管道的唯一名称,客户端连接时需使用此名称

            // 循环监听:只要未收到取消请求,管道断开后会自动重新进入监听状态
            while (!token.IsCancellationRequested)
            {
                try
                {
                    // 实例化命名管道服务端(双向通信、1个允许实例、字节传输模式、支持异步操作)
                    using (pipeServer = new NamedPipeServerStream(
                        pipeName,
                        PipeDirection.InOut,
                        1,
                        PipeTransmissionMode.Byte,
                        PipeOptions.Asynchronous))
                    {
                        // 异步等待外部客户端建立连接,并支持 CancellationToken 取消
                        await pipeServer.WaitForConnectionAsync(token);

                        // 使用 UTF-8 编码包裹管道流,按行读取客户端发送的数据
                        using (StreamReader reader = new StreamReader(pipeServer, Encoding.UTF8))
                        {
                            // 异步读取客户端发送的一行字符串
                            string clientMessage = await reader.ReadLineAsync();
                            if (!string.IsNullOrEmpty(clientMessage))
                            {
                                // 【关键技术点】接收到数据后,只压入 ConcurrentQueue 队列,严禁在此子线程调用任何 NXOpen API!
                                messageQueue.Enqueue(clientMessage);
                            }
                        }
                    }
                }
                catch (OperationCanceledException)
                {
                    // 接收到 cancellationToken 终止信号时,正常退出循环
                    break;
                }
                catch (Exception)
                {
                    // 捕获异常(如客户端强制断开等),稍微等待 1 秒后再尝试下一次监听,防止无限死循环吃满 CPU
                    await Task.Delay(1000, token).ConfigureAwait(false);
                }
            }
        }

        /// <summary>
        /// 消费客户端指令的业务逻辑函数(100% 运行在 NX UI 主线程上)
        /// </summary>
        /// <param name="command">从消息队列中提取出的客户端文本命令</param>
        private static void ProcessClientCommandInMainThread(string command)
        {
            // 此时调用任何 NXOpen / UF API 均绝不会抛出 "未在主线程" 的警告或崩塌
            theUFSession.Ui.DisplayMessage($"主线程成功接收并处理指令: {command}", 1);

            // TODO: 在此处根据解析出的 command 内容,编写参数化建模、特征修改等复杂 NXOpen 逻辑
        }

        /// <summary>
        /// NX 在初始化启动此 DLL 时调用的可选入口(保留为空即可)
        /// </summary>
        public static int Startup()
        {
            return 0;
        }

        /// <summary>
        /// NX 询问该 DLL 的卸载机制(极重要!)
        /// </summary>
        /// <param name="arg">参数</param>
        /// <returns>返回卸载选项枚举对应的数字</returns>
        public static int GetUnloadOption(string arg)
        {
            // 必须返回 AtTermination(NX 退出时卸载):确保 DLL 保存在 NX 进程内,使管道监听持续保持运行
            return System.Convert.ToInt32(Session.LibraryUnloadOption.AtTermination);
        }

        /// <summary>
        /// 当 NX 软件退出卸载此 DLL 时被自动调用的资源清理函数
        /// </summary>
        public void Dispose()
        {
            try
            {
                if (!isDisposeCalled)                          // 防止重复调用资源释放
                {
                    // 1. 停止并销毁 UI 定时器,防止释放后定时器仍在主线程回调导致空指针异常
                    if (uiTimer != null)
                    {
                        uiTimer.Stop();                         // 停止定时器
                        uiTimer.Tick -= OnUiTimerTick;          // 解绑事件回调
                        uiTimer.Dispose();                     // 释放 Timer 占用的 Win32 资源
                        uiTimer = null;                         // 指针清空
                    }

                    // 2. 终止并清理后台管道线程
                    if (cts != null)
                    {
                        cts.Cancel();                           // 向后台 Task 发送取消命令
                    }

                    if (pipeServer != null)
                    {
                        pipeServer.Close();                     // 强行关闭管道服务端,解除 WaitForConnection 的阻塞
                        pipeServer.Dispose();                  // 释放管道资源
                    }

                    if (listenTask != null)
                    {
                        listenTask.Wait(1000);                  // 主线程阻塞等待后台监听任务最长 1 秒,确保优雅安全退出
                    }

                    isDisposeCalled = true;                     // 标记 Dispose 已完成
                }
            }
            catch (Exception)
            {
                // 忽略注销时的异常,确保 NX 退出时不报错
            }
        }
    }
}

💡 关键流程串联拆解

  1. 入口启动 (Main)

    • 运行在 NX 主线程 。创建 WinForms.Timer 并注册 AtTermination 卸载模式。

    • 启动后台 Task(运行在子线程 )开启命名管道服务端 NX_Pipe_Server

  2. 接收数据 (ListenForClientsAsync)

    • 后台子线程阻塞式等待客户端连接。当客户端发送数据时读取字符串。

    • 拿到字符串后只做一件事:messageQueue.Enqueue(clientMessage)(压入队列,绝对不触碰任何 NX API)。

  3. 主线程调度 (OnUiTimerTick)

    • 每隔 100 毫秒,WinForms.Timer 利用 Windows WM_TIMER 消息将任务切回 NX 主 UI 线程

    • 主线程从 messageQueue 中弹出消息,安全执行 theUFSession.Ui.DisplayMessage 或建模 API。

  4. 程序退出 (GetUnloadOption & Dispose)

    • AtTermination 保证 NX 运行期间后台管道永不中断;当用户关闭 NX 时,自动触发 Dispose() 关闭定时器和后台 Task,优雅释放系统资源。

2.客户端的代码逻辑

cs 复制代码
 public partial class MainWindow : Window
 {
     private const string pipeName = "NX_Pipe_Server";// 服务器的命名管道名称
     public MainWindow()
     {
         InitializeComponent();
     }

     private async void Button_Click(object sender, RoutedEventArgs e)
     {
         using (var client = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut))
         {
             client.Connect(3000); // 3秒超时
             using (var writer = new StreamWriter(client, Encoding.UTF8))
             {
                 writer.WriteLine("Hello from Client!");//向服务器写入信息
                 writer.Flush();
             }
         }
         MessageBox.Show("信息已经发送");
     }
 }

三.运行测试

1.ug软件中启动监听服务器dll等待客户端连接,

2.启动客户端,按下按键,连接服务器,并发送信息给服务器

3.ug服务器正确读取到客户端发来的信息

相关推荐
迪康coolmu1 小时前
企业文档防泄密五层防护实战:基于迪康端点安全一体化管理系统的设计与落地
运维·经验分享·安全
智能运维指南1 小时前
AI加持下的可观测平台与传统平台有什么本质区别?智能告警降噪和根因分析是否可靠?
运维·人工智能·可观测平台·嘉为蓝鲸
上官唐云1 小时前
2024届本科 | C++ Linux服务端开发求职:从嵌入式到底层服务端的技术之路(附开源项目+简历)
linux·c++·开源·求职招聘
就叫飞六吧1 小时前
SSH 本地端口转发:用 localhost 访问远程服务
运维·ssh
Zucker n1 小时前
Ubuntu24.04结合ollama本地使用Hermes
linux·ubuntu
邪修king1 小时前
Linux系统篇(二十三) 基础 IO:从“文件”到“文件描述符”,彻底理解重定向
android·java·linux
snow@li2 小时前
服务器运维:Redis 安装笔记
运维
JavaPub-rodert2 小时前
Docker 容器启动了,不代表服务真的能用:Healthcheck 实战
运维·docker·容器
正仪2 小时前
systemd 是什么?
linux·运维·windows