记录.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.Infrastructure 的 CimSession 替换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代码备份

浙公网安备 33010602011771号