记录.Net中使用WMI的一些坑,触摸失效和发布增加 PublishTrimmed裁剪异常

1. 在没有报异常的情况下触控屏获取触摸失效。

在做触摸屏笔书写的时候,我们WPF可以获取到笔的压感值,今天测试的时候突然发现压感失效了,笔迹的书写压感效果没有了。然后一致怀疑书写组件配置的问题或是实现的问题。测试了好久 。

最后通过控制变量一步步注释代码,使用最简单Demo测试。最后发现在调用Wmi方法会触发InvalidCastException导致获取压感触摸失效。最后改为通过Cim获取Sn就没有问题了。此问题只有在.Net Core和.net版本有,在 .NET Framework 框架是没问题的。

System.InvalidCastException: 没有注册接口

   at MS.Win32.Penimc.UnsafeNativeMethods.CoCreateInstance(Guid& clsid, Object punkOuter, Int32 context, Guid& iid)
   at MS.Win32.Penimc.UnsafeNativeMethods.CreatePimcManager()

下面是测试Demo

using System.Diagnostics;
using System.Management;
using System.Text;
using System.Windows;
using System.Windows.Annotations;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using H3C.Board.Core;
using Microsoft.Management.Infrastructure;

namespace WpfApp1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {

        public MainWindow()
        {
            AppDomain.CurrentDomain.FirstChanceException += (sender, args) =>
            {
                Debug.WriteLine(args.Exception);
            };
            InitializeComponent();
            //WmiGetSerialNumberInfo();//立即Window初始化的时候调用Wmi会抛出FirstChanceException,Grid_StylusDown就不会触发了

            Loaded += MainWindow_Loaded;

        }

        private void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            WmiGetSerialNumberInfo(); //Loaded之后调用不会有问题,
        }

        private void Grid_StylusDown(object sender, StylusDownEventArgs e)
        {

            var pressure = GetPressureValue(e);
            var point = e.GetPosition(this);

            Console.WriteLine($"开始记录压感 - 位置: {point}, 压感: {pressure:F4}");
        }

        private void Grid_StylusMove(object sender, StylusEventArgs e)
        {
            var pressure = GetPressureValue(e);
            var point = e.GetPosition(this);

            // 实时显示压感值
            DisplayPressureInfo(point, pressure);
        }

        private void Grid_StylusUp(object sender, StylusEventArgs e)
        {

            var pressure = GetPressureValue(e);
            var point = e.GetPosition(this);
         
        }

        private double GetPressureValue(StylusEventArgs e)
        {
            try
            {
                var stylusPoints = e.GetStylusPoints(this);
                if (stylusPoints.Count > 0)
                {
                    return stylusPoints[0].PressureFactor;
                }

            }
            catch (Exception ex)
            {
                Console.WriteLine($"获取压感值失败: {ex.Message}");
            }

            return 0.0;
        }

        private void DisplayPressureInfo(Point point, double pressure)
        {
            // 在UI上显示压感信息
            Console.WriteLine($"实时压感 - X: {point.X:F1}, Y: {point.Y:F1}, Pressure: {pressure:F4}");

            // 可以更新UI控件显示压感值

            // 更新文本显示
            PressureDisplay.Text = $"压感: {pressure:F4}";

            // 更新可视化指示器
            PressureIndicator.Visibility = Visibility.Visible;
            Canvas.SetLeft(PressureIndicator, point.X - 10);
            Canvas.SetTop(PressureIndicator, point.Y - 10);

            // 根据压感调整指示器大小和颜色
            var size = 10 + (pressure * 30); // 压感越大,圆圈越大
            PressureIndicator.Width = size;
            PressureIndicator.Height = size;

            // 根据压感调整颜色
            var colorIntensity = (byte)(pressure * 255);
            PressureIndicator.Fill = new SolidColorBrush(Color.FromRgb(255, (byte)(255 - colorIntensity), (byte)(255 - colorIntensity)));
        }

        /// <summary>
        /// 使用Cim不会触摸失效
        /// </summary>
        /// <returns></returns>
        public static string? GetComputerSn()
        {
            try
            {
                // 创建本地 CIM 会话
                using var session = CimSession.Create(null);
                // 查询 Win32_BIOS 类
                var instances = session.QueryInstances(@"root\cimv2", "WQL", "SELECT SerialNumber FROM Win32_BIOS");

                foreach (var instance in instances)
                {
                    var snProperty = instance.CimInstanceProperties["SerialNumber"];
                    if (snProperty?.Value == null) continue;
                    string? sn = snProperty.Value.ToString()?.Trim();
                    if (string.IsNullOrEmpty(sn)) continue;
                    // 检查是否为无效的默认值(常见于一些 OEM 厂商)
                    if (sn.Contains("O.E.M", StringComparison.OrdinalIgnoreCase) ||
                        sn.Contains("OEM", StringComparison.OrdinalIgnoreCase) ||
                        sn.Equals("Default", StringComparison.OrdinalIgnoreCase) ||
                        sn.Equals("Default string", StringComparison.OrdinalIgnoreCase) ||
                        sn.Equals("To be filled by O.E.M.", StringComparison.OrdinalIgnoreCase))
                    {
                        continue; // 跳过无效的序列号
                    }
                    return sn;
                }
            }

            catch
            {
                //
            }
            return string.Empty;
        }

        private void WmiGetSerialNumberInfo()
        {
            try
            {
                // 创建 WMI 查询
                ManagementObjectSearcher searcher = new(
                    "SELECT SerialNumber FROM Win32_BIOS");

                // 遍历查询结果
                foreach (ManagementObject obj in searcher.Get())
                {
                    string serialNumber = obj["SerialNumber"]?.ToString();

                    if (!string.IsNullOrEmpty(serialNumber))
                    {
                        Console.WriteLine($"BIOS 序列号: {serialNumber}");
                        return;
                    }
                }

                Console.WriteLine("未找到 BIOS 序列号信息");
            }
            catch (ManagementException ex)
            {
                Console.WriteLine($"WMI 查询错误: {ex.Message}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"WMI 查询异常错误: {ex.Message}");
            }
        }

        private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
        {
            WmiGetSerialNumberInfo();
        }
    }

 
}
<Window
    x:Class="WpfApp1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:core="clr-namespace:H3C.Board.Core;assembly=H3C.Board.Core"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:WpfApp1"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="1000"
    Height="800"
    Background="Transparent"
    ShowInTaskbar="False"
    Stylus.IsPressAndHoldEnabled="False"
    Stylus.IsTapFeedbackEnabled="False"
    WindowChrome.ResizeGripDirection="None"
    WindowStartupLocation="CenterScreen"
    WindowStyle="None"
    mc:Ignorable="d">
    <Grid
        Background="LightGray"
        StylusDown="Grid_StylusDown"
        StylusMove="Grid_StylusMove"
        StylusUp="Grid_StylusUp">

        <!--  压感值显示  -->
        <TextBlock
            x:Name="PressureDisplay"
            Margin="30"
            HorizontalAlignment="Left"
            VerticalAlignment="Top"
            FontSize="16"
            FontWeight="Bold"
            Foreground="Blue" />

        <!--  压感可视化指示器  -->
        <Ellipse
            x:Name="PressureIndicator"
            Width="20"
            Height="20"
            HorizontalAlignment="Left"
            VerticalAlignment="Top"
            Fill="Red"
            Visibility="Collapsed" />
        <Button
            Width="30"
            Height="30"
            Click="ButtonBase_OnClick" />
    </Grid>
</Window>

.net8使用WMI可能还有一些意想不到的坑,建议都替换成CIMI。

此问题lindexi已经报告给 WPF 官方,请看 https://github.com/dotnet/wpf/issues/9752

2. 在.Net8发布增加 PublishTrimmed 裁剪选项,调用WMI 的ManagementObject 异常

下面是转自wutangyuan的博客:https://www.cnblogs.com/wuty/p/18931865

最近在做OTA的功能,需要获取到sn做一些业务的逻辑。我们自己实现的库里边的,大部分都是调用 System.Management 的 ManagementObjectSearcher 获取 Bios 的序列号

如下所示:

 private void BtnWmi_OnClick(object sender, RoutedEventArgs e)
 {try
     {// 创建 WMI 查询
         ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT SerialNumber FROM Win32_BIOS");
// 遍历查询结果foreach (ManagementObject obj in searcher.Get())
         {string serialNumber = obj["SerialNumber"]?.ToString();
if (!string.IsNullOrEmpty(serialNumber))
             {
                 Console.WriteLine($"BIOS 序列号: {serialNumber}");return;
             }
         }
         Console.WriteLine("未找到 BIOS 序列号信息");
     }catch (ManagementException ex)
     {
         Console.WriteLine($"WMI 查询错误: {ex.Message}");
     }catch (Exception ex)
     {
         Console.WriteLine($"WMI 查询异常错误: {ex.Message}");
     }
 }

由于我们项目现在新建的项目都是基于.Net 8 开发,而且为了兼容多种设备和系统,我们目前的打包方式都是以发布独立部署的方式

同时为了能减少输出文件的大小,我们会启用裁剪的方式 <PublishTrimmed>true</PublishTrimmed>

  <PublishTrimmed>true</PublishTrimmed>
  <_SuppressWpfTrimError>true</_SuppressWpfTrimError>
  <BuiltInComInteropSupport>true</BuiltInComInteropSupport>
  <TrimMode>partial</TrimMode>

以上准备工作做好,发布以上的程序,运行发现如下的错误:

查询了官网的资料,有如下的说明:剪裁选项 - .NET | Microsoft Learn

就是启用裁剪会禁用掉某些框架的功能。

解决方法:

参考:使用 C# 远程连接到 WMI - Win32 apps | Microsoft Learn

使用 Microsoft.Management.InfrastructureCimSession 替换WMI 早期的版本

如下所示:

 using Microsoft.Management.Infrastructure;

private void BtnCim_OnClick(object sender, RoutedEventArgs e)
 {try
     {// 创建本地CIM会话using (var session = CimSession.Create(null))
         {// 查询Win32_BIOS类var instances = session.QueryInstances(@"root\cimv2", "WQL", "SELECT SerialNumber FROM Win32_BIOS");foreach (var instance in instances)
             {var serialNumber = instance.CimInstanceProperties["SerialNumber"].Value?.ToString();
                 Console.WriteLine($"BIOS 序列号: {serialNumber}");
             }
         }
     }catch (Exception ex)
     {
         Console.WriteLine($"错误: {ex.Message}");
     }
 }

不修改发布选项的情况下,运行如下:是可以获取得到Bios的sn的

总结:

1、推荐使用 Microsoft.Management.Infrastructure 的 CimSession 替换 WMI 旧版的 ManagementObject

2、裁剪的选项 PublishTrimmed 如果不介意应用程序的一点体积,是可以忽略不加

代码Demo 链接:wutangyuan/wutyDemo: Demo代码备份

posted @ 2025-09-10 19:38  拚忘  阅读(45)  评论(2)    收藏  举报