欢迎光临
我们一直在努力

跨平台部署:C#上位机基于.NET 8 + Avalonia实现Windows/Linux双系统兼容

在工业自动化领域,上位机软件长期被Windows生态垄断,WinForm、WPF是绝大多数开发者的默认选择。但随着智能制造的推进,Linux工控机、边缘计算网关、国产操作系统在产线的占比逐年提升,传统.NET桌面技术栈的平台局限性愈发明显。要么切换C++/Qt技术栈重写全部业务,要么忍受两套代码分开维护,几乎成了行业两难的选择。

Avalonia框架的成熟,给C#开发者提供了第三条路。它是一套开源、跨平台的XAML UI框架,基于.NET 8原生运行,支持Windows、Linux、macOS甚至ARM架构设备,语法与WPF高度相似,现有C#业务代码几乎可以零成本复用。对于工业上位机场景而言,这意味着一套代码可以同时覆盖Windows工位机与Linux边缘网关,大幅降低开发与维护成本。

本文将从架构设计出发,完整讲解基于.NET 8 + Avalonia搭建工业级跨平台上位机的全流程,包含平台差异抽象、工业控件实现、双端部署打包以及现场高频踩坑解决方案,所有内容均来自实际项目落地经验。

一、技术选型:为什么是Avalonia

工业上位机对UI框架的核心要求很明确:运行稳定、控件可控、硬件兼容好、开发效率高。在跨平台技术路线上,主流方案各有优劣:

技术方案技术栈平台覆盖工业适配性学习成本
WPF C# / XAML 仅Windows 极高
WinForm C# 仅Windows 极高 极低
Qt C++ 全平台
MAUI C# / XAML 移动为主
Avalonia C# / XAML 全桌面+ARM 低(WPF开发者可快速上手)

Avalonia的核心优势体现在三个方面:

  • 原生渲染:基于Skia图形库做跨平台绘制,不依赖系统原生控件,界面表现一致性高,不会出现Linux下控件样式错乱的问题
  • 生态兼容:完整支持.NET 8标准库,原有S7通信、Modbus、算法逻辑、数据库操作等代码可以直接复用
  • 部署灵活:支持自包含单文件发布,x86/x64/ARM64全架构覆盖,适配工控机、边缘网关甚至国产操作系统

对于已有C#开发能力的团队,Avalonia是跨平台改造投入产出比最高的方案。

二、整体架构设计:分层解耦保障最大复用

跨平台开发的核心原则是:平台相关代码越少越好,业务复用代码越多越好。我们采用经典的四层分层架构,将平台差异隔离在最底层,上层业务完全不感知运行环境。

#mermaid-svg-sNaxNTIu2RMAN1wY{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-sNaxNTIu2RMAN1wY .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-sNaxNTIu2RMAN1wY .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-sNaxNTIu2RMAN1wY .error-icon{fill:#552222;}#mermaid-svg-sNaxNTIu2RMAN1wY .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-sNaxNTIu2RMAN1wY .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-sNaxNTIu2RMAN1wY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-sNaxNTIu2RMAN1wY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-sNaxNTIu2RMAN1wY .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-sNaxNTIu2RMAN1wY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-sNaxNTIu2RMAN1wY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-sNaxNTIu2RMAN1wY .marker{fill:#333333;stroke:#333333;}#mermaid-svg-sNaxNTIu2RMAN1wY .marker.cross{stroke:#333333;}#mermaid-svg-sNaxNTIu2RMAN1wY svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-sNaxNTIu2RMAN1wY p{margin:0;}#mermaid-svg-sNaxNTIu2RMAN1wY .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-sNaxNTIu2RMAN1wY .cluster-label text{fill:#333;}#mermaid-svg-sNaxNTIu2RMAN1wY .cluster-label span{color:#333;}#mermaid-svg-sNaxNTIu2RMAN1wY .cluster-label span p{background-color:transparent;}#mermaid-svg-sNaxNTIu2RMAN1wY .label text,#mermaid-svg-sNaxNTIu2RMAN1wY span{fill:#333;color:#333;}#mermaid-svg-sNaxNTIu2RMAN1wY .node rect,#mermaid-svg-sNaxNTIu2RMAN1wY .node circle,#mermaid-svg-sNaxNTIu2RMAN1wY .node ellipse,#mermaid-svg-sNaxNTIu2RMAN1wY .node polygon,#mermaid-svg-sNaxNTIu2RMAN1wY .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-sNaxNTIu2RMAN1wY .rough-node .label text,#mermaid-svg-sNaxNTIu2RMAN1wY .node .label text,#mermaid-svg-sNaxNTIu2RMAN1wY .image-shape .label,#mermaid-svg-sNaxNTIu2RMAN1wY .icon-shape .label{text-anchor:middle;}#mermaid-svg-sNaxNTIu2RMAN1wY .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-sNaxNTIu2RMAN1wY .rough-node .label,#mermaid-svg-sNaxNTIu2RMAN1wY .node .label,#mermaid-svg-sNaxNTIu2RMAN1wY .image-shape .label,#mermaid-svg-sNaxNTIu2RMAN1wY .icon-shape .label{text-align:center;}#mermaid-svg-sNaxNTIu2RMAN1wY .node.clickable{cursor:pointer;}#mermaid-svg-sNaxNTIu2RMAN1wY .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-sNaxNTIu2RMAN1wY .arrowheadPath{fill:#333333;}#mermaid-svg-sNaxNTIu2RMAN1wY .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-sNaxNTIu2RMAN1wY .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-sNaxNTIu2RMAN1wY .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-sNaxNTIu2RMAN1wY .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-sNaxNTIu2RMAN1wY .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-sNaxNTIu2RMAN1wY .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-sNaxNTIu2RMAN1wY .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-sNaxNTIu2RMAN1wY .cluster text{fill:#333;}#mermaid-svg-sNaxNTIu2RMAN1wY .cluster span{color:#333;}#mermaid-svg-sNaxNTIu2RMAN1wY div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-sNaxNTIu2RMAN1wY .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-sNaxNTIu2RMAN1wY rect.text{fill:none;stroke-width:0;}#mermaid-svg-sNaxNTIu2RMAN1wY .icon-shape,#mermaid-svg-sNaxNTIu2RMAN1wY .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-sNaxNTIu2RMAN1wY .icon-shape p,#mermaid-svg-sNaxNTIu2RMAN1wY .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-sNaxNTIu2RMAN1wY .icon-shape .label rect,#mermaid-svg-sNaxNTIu2RMAN1wY .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-sNaxNTIu2RMAN1wY .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-sNaxNTIu2RMAN1wY .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-sNaxNTIu2RMAN1wY :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

平台适配层

UI呈现层

平台无关层

业务核心层

设备通信抽象层

数据模型与算法层

Avalonia UI界面

Windows 实现

Linux 实现

各层职责划分:

  • 设备通信抽象层:定义统一的PLC通信、串口通信、相机采集接口,屏蔽平台差异
  • 业务核心层:包含产线流程控制、数据处理、报警逻辑、参数管理等纯业务代码,完全不依赖UI和平台
  • UI呈现层:基于Avalonia实现监控界面、参数配置、趋势曲线、日志查询等交互界面
  • 平台适配层:针对Windows和Linux分别实现硬件操作、系统API、文件权限等平台相关逻辑
  • 这套架构的核心价值在于:90%以上的业务代码可以跨平台复用,真正需要分开编写的平台适配代码不足10%。

    三、开发环境搭建与项目初始化

    3.1 基础环境准备

    开发端推荐Windows环境,Linux端用于测试和部署:

    • 开发机:Windows 10/11 + Visual Studio 2022 / Rider,安装.NET 8 SDK
    • 测试环境:Ubuntu 22.04 Desktop 或对应工控机Linux发行版
    • 命令行安装Avalonia项目模板:

    dotnet new install Avalonia.Templates

    3.2 创建项目结构

    通过模板创建基础项目后,建议按模块化拆分项目结构,而不是全部代码塞在一个项目里:

    Industrial.App.sln
    ├── Industrial.Core # 业务核心类库(平台无关)
    ├── Industrial.Devices # 设备通信抽象与实现
    ├── Industrial.UI # Avalonia UI主项目
    └── Industrial.Platform # 平台特定实现
    ├── Industrial.Platform.Windows
    └── Industrial.Platform.Linux

    其中Industrial.Core和Industrial.Devices是.NET Standard类库,不引用任何UI框架和平台API,确保可以在任意环境运行。

    3.3 核心依赖包

    UI项目安装核心NuGet包:

    Avalonia.Desktop
    Avalonia.Diagnostics
    LiveChartsCore.SkiaSharpView.Avalonia
    Serilog.Sinks.File
    Microsoft.Extensions.DependencyInjection

    工业场景推荐引入依赖注入容器,统一管理服务生命周期,方便平台实现的动态替换。

    四、核心跨平台适配实战

    4.1 平台差异抽象与依赖注入

    跨平台开发最忌讳在业务代码里写if (OperatingSystem.IsWindows())这种判断。正确做法是定义接口,不同平台分别实现,运行时通过依赖注入注入对应实现。

    以串口服务为例,先定义统一接口:

    public interface ISerialPortService : IDisposable
    {
    bool IsOpen { get; }
    bool Open(string portName, int baudRate);
    void Close();
    int Read(byte[] buffer, int offset, int count);
    void Write(byte[] buffer, int offset, int count);
    event EventHandler<byte[]> DataReceived;
    }

    Windows和Linux端分别实现该接口,底层都可以使用System.IO.Ports,差异点主要在端口名称枚举和权限处理。

    在程序入口处根据运行平台注入对应实现:

    if (OperatingSystem.IsWindows())
    {
    services.AddSingleton<ISerialPortService, WindowsSerialPortService>();
    }
    else if (OperatingSystem.IsLinux())
    {
    services.AddSingleton<ISerialPortService, LinuxSerialPortService>();
    }

    业务层只依赖ISerialPortService接口,完全感知不到底层运行在哪个系统。

    4.2 工业常用控件的跨平台实现

    工业上位机有大量专属控件,Avalonia原生控件库没有提供,需要自行封装。得益于Skia统一渲染,自定义控件在两个平台下表现完全一致。

    以状态指示灯为例,实现一个支持颜色切换、闪烁效果的自定义控件:

    public class StatusLamp : TemplatedControl
    {
    public static readonly StyledProperty<bool> IsOnProperty =
    AvaloniaProperty.Register<StatusLamp, bool>(nameof(IsOn));

    public static readonly StyledProperty<Color> OnColorProperty =
    AvaloniaProperty.Register<StatusLamp, Color>(nameof(OnColor), Colors.Green);

    public bool IsOn
    {
    get => GetValue(IsOnProperty);
    set => SetValue(IsOnProperty, value);
    }

    public Color OnColor
    {
    get => GetValue(OnColorProperty);
    set => SetValue(OnColorProperty, value);
    }
    }

    搭配XAML模板,用Ellipse绘制灯体和光晕效果,全部使用Avalonia标准绘图API,无需任何平台特定代码。

    对于实时趋势曲线,推荐使用LiveCharts2的Avalonia版本,支持动态数据更新、缩放平移,跨平台渲染效果一致:

    <lvc:CartesianChart Series="{Binding TemperatureSeries}" />

    4.3 设备通信层的零修改复用

    以太网类的设备通信天然具备跨平台能力,基于原生Socket实现的S7协议、Modbus TCP、OPC UA客户端等代码,不需要做任何修改就可以在Linux下运行。

    这也是为什么建议工业通信层全部基于标准类库实现,尽量避免使用厂商提供的Windows专属SDK。对于必须调用厂商驱动的硬件(如部分工业相机),可以在适配层封装,上层业务保持统一。

    4.4 UI线程调度与实时刷新

    Avalonia的调度机制与WPF略有差异,后台线程更新UI必须通过Dispatcher,不能直接操作控件属性。工业场景数据刷新频率高,写法不当很容易出现界面卡顿或跨线程异常。

    标准的安全更新写法:

    Dispatcher.UIThread.Post(() =>
    {
    TemperatureValue = value;
    StatusMessage = status;
    }, DispatcherPriority.Background);

    高频数据更新建议使用批量合并机制,不要每来一个数据就刷新一次UI,按100ms~500ms的间隔批量更新,既能保证实时性,又不会占用过多UI线程资源。

    五、工程化加固:工业场景的细节处理

    5.1 统一主题与分辨率适配

    工业上位机普遍使用深色主题,降低长时间盯屏的视觉疲劳。Avalonia支持全局样式定义,统一设置配色、字体、控件圆角,确保双平台视觉效果完全一致。

    针对工控机常见的1080P、4K以及各种非标分辨率,采用自适应布局:

    • 外层使用Grid按比例划分区域,避免硬编码宽高
    • 字体使用相对单位,跟随系统DPI缩放
    • 针对Linux下DPI检测不准的问题,提供手动缩放配置项

    5.2 日志与全局异常处理

    工业软件必须具备完善的可排查性。使用Serilog作为日志框架,跨平台统一写入本地文件,按日期切割,保留30天日志。

    在App启动处注册全局异常捕获,兜底处理未预料的错误:

    AppDomain.CurrentDomain.UnhandledException += (s, e) =>
    {
    Log.Fatal((Exception)e.ExceptionObject, "程序发生未处理异常");
    Log.CloseAndFlush();
    };

    TaskScheduler.UnobservedTaskException += (s, e) =>
    {
    Log.Error(e.Exception, "未观察到的任务异常");
    e.SetObserved();
    };

    5.3 路径与配置文件兼容

    Linux文件系统区分大小写,且没有盘符概念,代码中绝对不能出现硬编码的C:\\\\路径。所有文件路径都要通过Environment.SpecialFolder获取标准目录,配置文件统一放在程序目录或用户目录下。

    string configPath = Path.Combine(AppContext.BaseDirectory, "config", "system.json");

    读取配置文件时注意JSON键名的大小写敏感问题,统一使用小写或配置读取器忽略大小写。

    六、双系统部署与打包方案

    6.1 Windows端部署

    Windows端推荐自包含单文件发布,用户不需要安装.NET运行时,解压即可运行。

    发布命令:

    dotnet publish Industrial.UI -c Release -r win-x64 \\
    –self-contained true \\
    /p:PublishSingleFile=true \\
    /p:EnableCompressionInSingleFile=true \\
    /p:IncludeNativeLibrariesForSelfExtract=true

    产出单个exe文件,配合Inno Setup制作安装包,添加桌面快捷方式、开机自启、卸载程序等功能。

    6.2 Linux端部署

    Linux端发布流程与Windows类似,替换运行时标识符即可:

    dotnet publish Industrial.UI -c Release -r linux-x64 \\
    –self-contained true \\
    /p:PublishSingleFile=true

    部署到Linux系统需要处理几个关键问题:

  • 执行权限:给发布文件添加可执行权限chmod +x Industrial.UI
  • 中文字体:安装中文字体解决中文显示方块问题sudo apt install fonts-wqy-microhei

    同时在Avalonia中配置字体回退,优先使用系统已安装字体。

  • 硬件权限:串口、USB设备需要将用户加入对应用户组sudo usermod -aG dialout $USER
  • 桌面快捷方式:创建.desktop文件放入应用程序目录,实现菜单启动
  • 对于需要长期运行的设备,可以用systemd托管程序,实现开机自启、崩溃自动重启:

    [Unit]
    Description=工业监控上位机
    After=network.target

    [Service]
    Type=simple
    User=operator
    ExecStart=/opt/industrial/Industrial.UI
    Restart=always
    RestartSec=5

    [Install]
    WantedBy=multi-user.target

    6.3 完整部署流程

    #mermaid-svg-c5x5r90DhmazrZXI{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-c5x5r90DhmazrZXI .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-c5x5r90DhmazrZXI .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-c5x5r90DhmazrZXI .error-icon{fill:#552222;}#mermaid-svg-c5x5r90DhmazrZXI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-c5x5r90DhmazrZXI .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-c5x5r90DhmazrZXI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-c5x5r90DhmazrZXI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-c5x5r90DhmazrZXI .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-c5x5r90DhmazrZXI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-c5x5r90DhmazrZXI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-c5x5r90DhmazrZXI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-c5x5r90DhmazrZXI .marker.cross{stroke:#333333;}#mermaid-svg-c5x5r90DhmazrZXI svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-c5x5r90DhmazrZXI p{margin:0;}#mermaid-svg-c5x5r90DhmazrZXI .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-c5x5r90DhmazrZXI .cluster-label text{fill:#333;}#mermaid-svg-c5x5r90DhmazrZXI .cluster-label span{color:#333;}#mermaid-svg-c5x5r90DhmazrZXI .cluster-label span p{background-color:transparent;}#mermaid-svg-c5x5r90DhmazrZXI .label text,#mermaid-svg-c5x5r90DhmazrZXI span{fill:#333;color:#333;}#mermaid-svg-c5x5r90DhmazrZXI .node rect,#mermaid-svg-c5x5r90DhmazrZXI .node circle,#mermaid-svg-c5x5r90DhmazrZXI .node ellipse,#mermaid-svg-c5x5r90DhmazrZXI .node polygon,#mermaid-svg-c5x5r90DhmazrZXI .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-c5x5r90DhmazrZXI .rough-node .label text,#mermaid-svg-c5x5r90DhmazrZXI .node .label text,#mermaid-svg-c5x5r90DhmazrZXI .image-shape .label,#mermaid-svg-c5x5r90DhmazrZXI .icon-shape .label{text-anchor:middle;}#mermaid-svg-c5x5r90DhmazrZXI .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-c5x5r90DhmazrZXI .rough-node .label,#mermaid-svg-c5x5r90DhmazrZXI .node .label,#mermaid-svg-c5x5r90DhmazrZXI .image-shape .label,#mermaid-svg-c5x5r90DhmazrZXI .icon-shape .label{text-align:center;}#mermaid-svg-c5x5r90DhmazrZXI .node.clickable{cursor:pointer;}#mermaid-svg-c5x5r90DhmazrZXI .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-c5x5r90DhmazrZXI .arrowheadPath{fill:#333333;}#mermaid-svg-c5x5r90DhmazrZXI .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-c5x5r90DhmazrZXI .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-c5x5r90DhmazrZXI .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-c5x5r90DhmazrZXI .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-c5x5r90DhmazrZXI .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-c5x5r90DhmazrZXI .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-c5x5r90DhmazrZXI .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-c5x5r90DhmazrZXI .cluster text{fill:#333;}#mermaid-svg-c5x5r90DhmazrZXI .cluster span{color:#333;}#mermaid-svg-c5x5r90DhmazrZXI div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-c5x5r90DhmazrZXI .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-c5x5r90DhmazrZXI rect.text{fill:none;stroke-width:0;}#mermaid-svg-c5x5r90DhmazrZXI .icon-shape,#mermaid-svg-c5x5r90DhmazrZXI .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-c5x5r90DhmazrZXI .icon-shape p,#mermaid-svg-c5x5r90DhmazrZXI .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-c5x5r90DhmazrZXI .icon-shape .label rect,#mermaid-svg-c5x5r90DhmazrZXI .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-c5x5r90DhmazrZXI .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-c5x5r90DhmazrZXI .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-c5x5r90DhmazrZXI :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    代码提交

    CI构建

    Windows x64构建包

    Linux x64构建包

    Linux ARM64构建包

    Inno Setup打包安装程序

    压缩包+systemd配置

    边缘网关部署包

    七、现场踩坑与排障指南

    跨平台开发的大部分问题都集中在Linux端,这里汇总了工业场景最高频的踩坑点与解决方案。

    7.1 串口打开提示权限不足

    • 现象:代码在Windows下正常,Linux下打开串口抛出UnauthorizedAccessException
    • 原因:当前用户没有串口设备的访问权限
    • 解决:将用户加入dialout用户组,注销后生效;永久方案是编写udev规则,指定设备的权限和所属组

    7.2 中文全部显示为方块

    • 现象:界面中文全部变成小方块,英文正常
    • 原因:Linux系统没有安装中文字体,Avalonia找不到可用字体
    • 解决:安装文泉驿或思源中文字体;在App.axaml中配置字体回退链,确保找不到指定字体时能降级到系统字体

    7.3 高DPI下界面大小不一致

    • 现象:同一分辨率下,Linux界面比Windows大一圈或小一圈
    • 原因:两个系统的DPI检测机制不同,缩放比例计算有差异
    • 解决:提供手动缩放配置项,允许用户根据实际显示器调整;关键控件使用固定像素尺寸,避免过度依赖自动缩放

    7.4 ARM工控机运行崩溃

    • 现象:x64环境正常,ARM64设备上启动即崩溃
    • 原因:发布时选错运行时,或引用了仅支持x86的原生库
    • 解决:使用linux-arm64运行时发布;检查所有依赖项是否支持ARM架构,避免引入Windows专属的C++库

    7.5 文件读写偶发异常

    • 现象:同一份配置文件,Windows正常,Linux偶发读取失败
    • 原因:Linux文件系统区分大小写,代码中路径大小写与实际文件不一致
    • 解决:统一文件命名规范,全部小写;文件读取时做大小写容错处理;禁止硬编码路径

    7.6 界面偶发花屏或渲染错误

    • 现象:Linux下快速切换界面时出现花屏、控件残影
    • 原因:显卡驱动对OpenGL支持不完善
    • 解决:启用软件渲染模式,牺牲少量性能换取极致兼容性;工控机核显环境下推荐默认开启

    八、总结与选型建议

    基于.NET 8 + Avalonia的跨平台方案,最核心的价值是保留C#技术栈的同时突破Windows生态限制。对于大多数工业自动化团队来说,不需要从零学习Qt,不需要维护两套代码,现有业务资产可以最大程度复用。

    适用场景

    • 需要同时支持Windows工位机与Linux边缘网关的项目
    • 有国产操作系统适配需求,希望一套代码覆盖多系统
    • 现有技术栈为C#/.NET,不想切换开发语言的团队
    • ARM架构工控机、嵌入式设备的轻量上位机开发

    不推荐场景

    • 重度依赖Windows专属硬件驱动且无Linux版本的项目
    • 对界面性能有极致要求的复杂3D可视化场景
    • 团队有成熟Qt技术栈且无.NET积累的情况

    从实际落地经验来看,Avalonia在工业场景的稳定性已经完全达标,配合完善的异常处理和自动重启机制,可以满足产线7×24小时运行的要求。随着国产操作系统的进一步普及,这套技术方案的优势会愈发明显。

    赞(0)
    未经允许不得转载:171主机测评 » 跨平台部署:C#上位机基于.NET 8 + Avalonia实现Windows/Linux双系统兼容
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址