【内存测试】06-WPF 读取 SMBIOS 实现内存规格自动检测

【内存测试】06-WPF 读取 SMBIOS 实现内存规格自动检测

系列:《WPF 产线功能测试实战》第六篇
关键词WPF C# SMBIOS P/Invoke 内存检测 产线测试 .NET Framework Windows API 硬件信息读取 DIMM


一、前言与应用场景

在产线测试中,内存(RAM)的规格验证是不可或缺的一环。一台出厂设备的内存大小是否符合订单规格(8GB/16GB/32GB)?内存频率是否达到设计标准(4800MHz/5600MHz)?每个 DIMM 插槽是否都能被系统识别?这些问题如果靠人工目测,既费时又容易出错。

本文介绍如何在 WPF 中通过 Windows SMBIOS 固件接口自动读取内存规格,并与预设的配置参数进行对比,实现全自动的内存规格验证。Demo 中用本地 JSON 文件替代生产线 MES 系统,任何人都可以直接跑起来调试。


二、基础概念科普

什么是 SMBIOS?

SMBIOS(System Management BIOS)是 BIOS 向操作系统暴露硬件信息的标准接口,由 DMTF 组织制定。你可以把它理解成一张"硬件出生证明"------主板上的 BIOS 在系统启动时,把 CPU、内存、主板、硬盘等硬件的规格信息写入一块内存区域,操作系统和应用程序可以随时来读取。

Windows 提供了 GetSystemFirmwareTable 这个 API,让普通应用程序也能读到这块数据。在 C# 中,通过 P/Invoke(让托管代码调用 Windows 原生 C API 的桥梁机制)来调用它。

Type 17:内存设备信息结构

SMBIOS 数据由若干个"结构"组成,每种结构有固定的类型编号(Type)。Type 17 = Memory Device,就是我们需要的内存信息,每个 DIMM 插槽对应一条 Type 17 记录,哪怕该槽是空的也会有一条。

Type 17 中与我们相关的字段:

字段偏移 字段名 说明
0Ch Size 内存大小,值 < 32768 时单位为 MB,位 15=1 时单位为 KB
15h Speed 内存速度(MHz),0xFFFF 表示未知
10h DeviceLocator 插槽名称的字符串索引(如 "ChannelA-DIMM0")

内存大小字段的位运算小技巧

SMBIOS Type 17 的 Size 字段是一个 ushort(2字节无符号整数),其中最高位(bit 15)决定单位:

复制代码
bit15 = 0 → 低15位 = 内存大小(MB)
bit15 = 1 → 低15位 = 内存大小(KB),需 & 0x7FFF 清除高位

这种节省字节的设计是上世纪 BIOS 规范的遗产,用一个字段同时表达单位和数值。

为什么用本地 JSON 替代 MES?

生产线工具通常从 MES(制造执行系统)获取当前产品应有的内存规格,这样可以自动适配不同 SKU。但在 Demo 和开发调试阶段,不需要依赖 MES 服务器,因此改用一个 JSON 配置文件,让开发者自行填写期望的内存大小和频率,方便本地调试和演示。


三、方案设计与架构

测试流程

MVVM 职责分层

文件 职责
View MainWindow.xaml 显示测试状态和结果,按钮触发
ViewModel MainVM.cs 编排三步测试,更新绑定属性
工具类 SmbiosReader.cs 封装 P/Invoke,解析 Type 17
配置 MemoryConfig.cs 读写本地 JSON 配置文件

核心依赖

本 Demo 只需 Newtonsoft.Json(NuGet 安装)用于解析配置文件,不依赖任何其他第三方库。SMBIOS 读取完全通过 Windows 原生 API 实现。


四、核心代码详解

4.1 P/Invoke 声明与 SMBIOS 数据结构

csharp 复制代码
// GetSystemFirmwareTable:向 Windows 请求固件表数据
// FirmwareTableProviderSignature = 0x52534D42 对应 ASCII "RSMB"
[DllImport("kernel32.dll", SetLastError = true)]
private static extern uint GetSystemFirmwareTable(
    uint FirmwareTableProviderSignature,
    uint FirmwareTableID,
    IntPtr pFirmwareTableBuffer,
    uint BufferSize);

// SMBIOS 数据块的头部(固定5字节)
[StructLayout(LayoutKind.Sequential, Pack = 1)]
private struct RawSMBIOSData
{
    public byte Used20CallingMethod;
    public byte SMBIOSMajorVersion;
    public byte SMBIOSMinorVersion;
    public byte DmiRevision;
    public uint Length;             // 后续 SMBIOS 表的字节长度
}

// Type 17 Memory Device 结构(精简,只映射到我们需要的字段)
[StructLayout(LayoutKind.Sequential, Pack = 1)]
private struct MemoryDeviceInfo
{
    public byte Type;               // 固定为 17
    public byte Length;             // 本结构的格式化区域长度
    public ushort Handle;
    public ushort PhysicalArrayHandle;
    public ushort ErrorInfoHandle;
    public ushort TotalWidth;
    public ushort DataWidth;
    public ushort Size;             // 0Ch:内存大小,见位运算说明
    public byte FormFactor;
    public byte DeviceSet;
    public byte DeviceLocator;      // 10h:插槽名称字符串的索引
    public byte BankLocator;
    public byte MemoryType;
    public ushort TypeDetail;
    public ushort Speed;            // 15h:内存频率(MHz)
}

Pack = 1 告诉运行时按 1 字节对齐,与 SMBIOS 规范一致,不能省略,否则字段偏移会错位。

4.2 获取内存大小(累加所有插槽)

csharp 复制代码
public double GetTotalMemorySizeGB()
{
    const uint RSMB = 0x52534D42;
    uint bufferSize = GetSystemFirmwareTable(RSMB, 0, IntPtr.Zero, 0); // 先查大小
    if (bufferSize == 0) return 0;

    IntPtr buffer = Marshal.AllocHGlobal((int)bufferSize);
    try
    {
        GetSystemFirmwareTable(RSMB, 0, buffer, bufferSize);

        RawSMBIOSData header = Marshal.PtrToStructure<RawSMBIOSData>(buffer);
        IntPtr current = IntPtr.Add(buffer, Marshal.SizeOf<RawSMBIOSData>());
        IntPtr end = IntPtr.Add(current, (int)header.Length);

        double totalGB = 0;
        while (current.ToInt64() < end.ToInt64())
        {
            var info = Marshal.PtrToStructure<MemoryDeviceInfo>(current);
            if (info.Type == 17 && info.Size != 0 && info.Size != 0xFFFF)
            {
                // bit15=0 → MB;bit15=1 → KB(低15位才是数值)
                double mb = (info.Size < 32768)
                    ? info.Size
                    : (info.Size & 0x7FFF) / 1024.0;
                totalGB += mb / 1024.0;
            }
            current = SkipToNext(current, end, info.Length);
            if (current == IntPtr.Zero) break;
        }
        return Math.Round(totalGB, 0); // 四舍五入到整数 GB
    }
    finally { Marshal.FreeHGlobal(buffer); }
}

为什么要累加? 一台 16GB 内存的机器可能由两条 8GB 组成(双通道),SMBIOS 里会有两条 Type 17 记录,必须全部加起来才是总量。

4.3 获取内存频率

频率的读取逻辑比大小简单------取第一个有效的 Type 17 记录的 Speed 字段即可(同一台机器的内存条通常频率相同):

csharp 复制代码
public double GetMemorySpeedMHz()
{
    // ... 同上,获取 buffer 并解析头部 ...
    while (current.ToInt64() < end.ToInt64())
    {
        var info = Marshal.PtrToStructure<MemoryDeviceInfo>(current);
        if (info.Type == 17 && info.Speed != 0 && info.Speed != 0xFFFF)
            return info.Speed; // 直接返回第一个有效值(MHz)
        current = SkipToNext(current, end, info.Length);
        if (current == IntPtr.Zero) break;
    }
    return 0;
}

4.4 遍历 DIMM 插槽,读取 Device Locator 字符串

为什么要测这一项?

前两步(大小 + 频率)验证的是内存的总量规格 ,但它们存在一个盲区:只要总量对上,单个插槽出问题不会被发现

举个真实场景:一台设计为双通道 16GB 的笔记本(两个槽各插 8GB),如果其中一个槽的焊接虚焊或接触不良,BIOS 可能仍然能凑出 16GB 的总量(有时会 fallback 到单槽),但内存控制器其实只认出了一个槽。这种情况下,大小测试通过,性能却只有单通道,出厂后用户会明显感受到内存带宽不足。

Device Locator 是 BIOS 在初始化时为每个已识别插槽写入的物理位置标签(例如 ChannelA-DIMM0ChannelB-DIMM0)。BIOS 能写出完整的 Device Locator,意味着该槽完成了初始化握手------内存颗粒被控制器正确识别,SPD 信息(内存条自带的参数 EEPROM)也被成功读取。若某个槽的 Device Locator 为空或不可读,则说明该槽在 BIOS POST 阶段就出了问题,属于硬件级故障。

简而言之:大小 + 频率 验证规格是否符合订单,Device Locator 验证每个物理插槽是否被硬件层面真正识别------两者缺一不可,覆盖的失效模式不同。


Type 17 的 DeviceLocator 字段存的是一个字符串索引 ,不是字符串本身。真正的字符串在结构的格式化区域之后,按 index 顺序排列,以 \0 分隔,整个字符串段以双 \0 结束:

csharp 复制代码
private string GetSMBIOSString(IntPtr structPtr, byte structLength, byte index)
{
    if (index == 0) return string.Empty;
    IntPtr strPtr = IntPtr.Add(structPtr, structLength); // 跳过格式化区域
    int cur = 1;
    while (Marshal.ReadByte(strPtr) != 0) // 未到双'\0'结尾
    {
        if (cur == index) return ReadNullTermString(strPtr);
        while (Marshal.ReadByte(strPtr) != 0) strPtr = IntPtr.Add(strPtr, 1);
        strPtr = IntPtr.Add(strPtr, 1); // 跳过当前字符串的'\0'
        cur++;
    }
    return string.Empty;
}

4.5 跳转到下一个 SMBIOS 结构

每个 SMBIOS 结构由"格式化区域(固定长度)+ 字符串区域(变长)"组成,遍历时需要同时跳过这两部分:

csharp 复制代码
private IntPtr SkipToNext(IntPtr ptr, IntPtr end, byte structLength)
{
    ptr = IntPtr.Add(ptr, structLength); // 跳过格式化区域
    // 跳过字符串区域,直到出现连续两个 '\0'
    while (ptr.ToInt64() < end.ToInt64() - 1)
    {
        if (Marshal.ReadByte(ptr) == 0 && Marshal.ReadByte(IntPtr.Add(ptr, 1)) == 0)
            return IntPtr.Add(ptr, 2);
        ptr = IntPtr.Add(ptr, 1);
    }
    return IntPtr.Zero;
}

五、完整可运行 Demo

Demo 项目结构

复制代码
MemoryTestDemo/
├── App.xaml
├── MainWindow.xaml          ← 测试结果 UI
├── MainWindow.xaml.cs       ← 代码后置(DataContext 绑定)
├── MainVM.cs                ← 测试主逻辑(ViewModel)
├── SmbiosReader.cs          ← SMBIOS 封装工具类
├── MemoryConfig.cs          ← JSON 配置文件读写
└── memory_config.json       ← 用户可编辑的期望规格(自动生成)

NuGet 依赖Newtonsoft.JsonInstall-Package Newtonsoft.Json


memory_config.json(首次运行自动生成,可手动修改)

json 复制代码
{
  "ExpectedMemorySizeGB": 16,
  "ExpectedMemorySpeedMHz": 4800
}

修改 ExpectedMemorySizeGBExpectedMemorySpeedMHz 两个值即可适配不同机型规格。


MemoryConfig.cs

csharp 复制代码
using System.IO;
using System.Text;
using Newtonsoft.Json;

namespace MemoryTestDemo
{
    public class MemoryConfig
    {
        public double ExpectedMemorySizeGB { get; set; } = 16;
        public double ExpectedMemorySpeedMHz { get; set; } = 4800;

        private static readonly string ConfigPath =
            Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "memory_config.json");

        public static MemoryConfig Load()
        {
            if (!File.Exists(ConfigPath))
            {
                var def = new MemoryConfig();
                def.Save(); // 首次运行:写出默认配置供用户参考
                return def;
            }
            string json = File.ReadAllText(ConfigPath, Encoding.UTF8);
            return JsonConvert.DeserializeObject<MemoryConfig>(json) ?? new MemoryConfig();
        }

        public void Save()
        {
            string json = JsonConvert.SerializeObject(this, Formatting.Indented);
            File.WriteAllText(ConfigPath, json, Encoding.UTF8);
        }
    }
}

SmbiosReader.cs

csharp 复制代码
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;

namespace MemoryTestDemo
{
    public class SmbiosReader
    {
        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern uint GetSystemFirmwareTable(
            uint FirmwareTableProviderSignature, uint FirmwareTableID,
            IntPtr pFirmwareTableBuffer, uint BufferSize);

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        private struct RawSMBIOSData
        {
            public byte Used20CallingMethod, SMBIOSMajorVersion, SMBIOSMinorVersion, DmiRevision;
            public uint Length;
        }

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        private struct MemoryDeviceInfo
        {
            public byte Type, Length;
            public ushort Handle, PhysicalArrayHandle, ErrorInfoHandle,
                          TotalWidth, DataWidth, Size;
            public byte FormFactor, DeviceSet, DeviceLocator, BankLocator, MemoryType;
            public ushort TypeDetail, Speed;
        }

        private const uint RSMB = 0x52534D42;

        private IntPtr _buffer;
        private IntPtr _tableStart;
        private IntPtr _tableEnd;

        public bool Init()
        {
            uint size = GetSystemFirmwareTable(RSMB, 0, IntPtr.Zero, 0);
            if (size == 0) return false;
            _buffer = Marshal.AllocHGlobal((int)size);
            GetSystemFirmwareTable(RSMB, 0, _buffer, size);
            var hdr = Marshal.PtrToStructure<RawSMBIOSData>(_buffer);
            _tableStart = IntPtr.Add(_buffer, Marshal.SizeOf<RawSMBIOSData>());
            _tableEnd = IntPtr.Add(_tableStart, (int)hdr.Length);
            return true;
        }

        public void Free() { if (_buffer != IntPtr.Zero) Marshal.FreeHGlobal(_buffer); }

        public double GetTotalMemorySizeGB()
        {
            double total = 0;
            ForEachType17(info =>
            {
                if (info.Size == 0 || info.Size == 0xFFFF) return;
                double mb = (info.Size < 32768) ? info.Size : (info.Size & 0x7FFF) / 1024.0;
                total += mb / 1024.0;
            });
            return Math.Round(total, 0);
        }

        public double GetMemorySpeedMHz()
        {
            double speed = 0;
            ForEachType17(info =>
            {
                if (speed > 0) return; // 只取第一个有效值
                if (info.Speed != 0 && info.Speed != 0xFFFF) speed = info.Speed;
            });
            return speed;
        }

        public Dictionary<int, string> GetDimmDeviceLocators()
        {
            var result = new Dictionary<int, string>();
            int idx = 0;
            IntPtr current = _tableStart;
            while (current.ToInt64() < _tableEnd.ToInt64())
            {
                byte type = Marshal.ReadByte(current);
                byte len = Marshal.ReadByte(IntPtr.Add(current, 1));
                if (type == 17)
                {
                    var info = Marshal.PtrToStructure<MemoryDeviceInfo>(current);
                    string loc = GetSMBIOSString(current, len, info.DeviceLocator);
                    result[++idx] = loc;
                }
                current = SkipToNext(current, _tableEnd, len);
                if (current == IntPtr.Zero) break;
            }
            return result;
        }

        private void ForEachType17(Action<MemoryDeviceInfo> action)
        {
            IntPtr current = _tableStart;
            while (current.ToInt64() < _tableEnd.ToInt64())
            {
                byte type = Marshal.ReadByte(current);
                byte len = Marshal.ReadByte(IntPtr.Add(current, 1));
                if (type == 17) action(Marshal.PtrToStructure<MemoryDeviceInfo>(current));
                current = SkipToNext(current, _tableEnd, len);
                if (current == IntPtr.Zero) break;
            }
        }

        private IntPtr SkipToNext(IntPtr ptr, IntPtr end, byte structLength)
        {
            ptr = IntPtr.Add(ptr, structLength);
            while (ptr.ToInt64() < end.ToInt64() - 1)
            {
                if (Marshal.ReadByte(ptr) == 0 && Marshal.ReadByte(IntPtr.Add(ptr, 1)) == 0)
                    return IntPtr.Add(ptr, 2);
                ptr = IntPtr.Add(ptr, 1);
            }
            return IntPtr.Zero;
        }

        private string GetSMBIOSString(IntPtr structPtr, byte structLength, byte index)
        {
            if (index == 0) return string.Empty;
            IntPtr p = IntPtr.Add(structPtr, structLength);
            int cur = 1;
            while (Marshal.ReadByte(p) != 0)
            {
                if (cur == index) return ReadNullTermStr(p);
                while (Marshal.ReadByte(p) != 0) p = IntPtr.Add(p, 1);
                p = IntPtr.Add(p, 1);
                cur++;
            }
            return string.Empty;
        }

        private string ReadNullTermStr(IntPtr ptr)
        {
            int len = 0;
            while (Marshal.ReadByte(ptr, len) != 0) len++;
            if (len == 0) return string.Empty;
            byte[] bytes = new byte[len];
            Marshal.Copy(ptr, bytes, 0, len);
            return Encoding.ASCII.GetString(bytes);
        }
    }
}

MainVM.cs

csharp 复制代码
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
using System.Windows.Media;

namespace MemoryTestDemo
{
    public class MainVM : INotifyPropertyChanged
    {
        private string _statusText = "点击"开始测试"";
        private Brush _statusColor = Brushes.Gray;
        private string _logText = string.Empty;

        public string StatusText { get => _statusText; set { _statusText = value; OnChanged(); } }
        public Brush StatusColor { get => _statusColor; set { _statusColor = value; OnChanged(); } }
        public string LogText { get => _logText; set { _logText = value; OnChanged(); } }

        private readonly SmbiosReader _reader = new SmbiosReader();
        private readonly StringBuilder _log = new StringBuilder();

        public async Task StartTestAsync()
        {
            _log.Clear();
            LogText = string.Empty;

            if (!_reader.Init())
            {
                SetStatus("无法读取 SMBIOS 数据", Brushes.Red);
                return;
            }

            try
            {
                // 加载本地 JSON 配置(期望规格)
                MemoryConfig config = MemoryConfig.Load();
                Log($"期望内存大小:{config.ExpectedMemorySizeGB} GB");
                Log($"期望内存频率:{config.ExpectedMemorySpeedMHz} MHz");
                Log("---");

                // Step 1:内存大小检测
                SetStatus("正在检测内存大小...", Brushes.Yellow);
                double actualSize = _reader.GetTotalMemorySizeGB();
                Log($"SMBIOS 读取内存大小:{actualSize} GB");
                if (actualSize <= 0) { SetStatus("无法从 SMBIOS 读取内存大小", Brushes.Red); return; }
                if (actualSize != config.ExpectedMemorySizeGB)
                {
                    SetStatus($"内存大小不符!期望 {config.ExpectedMemorySizeGB}GB,实际 {actualSize}GB", Brushes.Red);
                    return;
                }
                Log("✔ 内存大小检测通过");

                // Step 2:内存频率检测
                SetStatus("正在检测内存频率...", Brushes.Yellow);
                double actualSpeed = _reader.GetMemorySpeedMHz();
                Log($"SMBIOS 读取内存频率:{actualSpeed} MHz");
                if (actualSpeed <= 0) { SetStatus("无法从 SMBIOS 读取内存频率", Brushes.Red); return; }
                if (actualSpeed != config.ExpectedMemorySpeedMHz)
                {
                    SetStatus($"内存频率不符!期望 {config.ExpectedMemorySpeedMHz}MHz,实际 {actualSpeed}MHz", Brushes.Red);
                    return;
                }
                Log("✔ 内存频率检测通过");

                // Step 3:DIMM 插槽 Device Locator 检测
                SetStatus("正在检测 DIMM 插槽...", Brushes.Yellow);
                var slots = _reader.GetDimmDeviceLocators();
                if (slots.Count == 0) { SetStatus("未检测到任何内存插槽信息", Brushes.Red); return; }
                bool allValid = true;
                foreach (var kv in slots)
                {
                    if (string.IsNullOrWhiteSpace(kv.Value))
                    {
                        Log($"插槽 {kv.Key}:Device Locator 读取失败");
                        allValid = false;
                    }
                    else
                    {
                        Log($"插槽 {kv.Key}:{kv.Value}");
                    }
                }
                if (!allValid) { SetStatus("部分插槽 Device Locator 无法读取", Brushes.Red); return; }
                Log("✔ 所有 DIMM 插槽检测通过");

                SetStatus($"全部通过!内存 {actualSize}GB @ {actualSpeed}MHz", Brushes.YellowGreen);
            }
            finally
            {
                _reader.Free();
            }
        }

        private void SetStatus(string text, Brush color)
        {
            StatusText = text;
            StatusColor = color;
            Log(text);
        }

        private void Log(string msg)
        {
            _log.AppendLine(msg);
            LogText = _log.ToString();
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void OnChanged([CallerMemberName] string name = "") =>
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    }
}

MainWindow.xaml

xml 复制代码
<Window x:Class="MemoryTestDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="内存规格检测 Demo" Height="420" Width="520"
        Background="#1E1E2E">
    <Grid Margin="20">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <!-- 测试状态 -->
        <TextBlock Grid.Row="0" Text="{Binding StatusText}"
                   Foreground="{Binding StatusColor}"
                   FontSize="18" FontWeight="Bold"
                   TextWrapping="Wrap" Margin="0,0,0,12"/>

        <!-- 提示:JSON 配置路径 -->
        <TextBlock Grid.Row="1"
                   Text="📄 期望规格配置文件:memory_config.json(与 exe 同目录,可手动编辑)"
                   Foreground="#888" FontSize="11" Margin="0,0,0,8"/>

        <!-- 日志输出 -->
        <ScrollViewer Grid.Row="2" VerticalScrollBarVisibility="Auto">
            <TextBox Text="{Binding LogText, Mode=OneWay}"
                     Background="#12121C" Foreground="#CDD6F4"
                     FontFamily="Consolas" FontSize="12"
                     IsReadOnly="True" TextWrapping="Wrap"
                     BorderThickness="0"/>
        </ScrollViewer>

        <!-- 开始按钮 -->
        <Button Grid.Row="3" Content="开始测试" Margin="0,12,0,0"
                Height="36" FontSize="14"
                Background="#89B4FA" Foreground="#1E1E2E"
                BorderThickness="0"
                Click="StartButton_Click"/>
    </Grid>
</Window>

MainWindow.xaml.cs

csharp 复制代码
using System.Windows;

namespace MemoryTestDemo
{
    public partial class MainWindow : Window
    {
        private readonly MainVM _vm = new MainVM();

        public MainWindow()
        {
            InitializeComponent();
            DataContext = _vm;
        }

        private async void StartButton_Click(object sender, RoutedEventArgs e)
        {
            await _vm.StartTestAsync();
        }
    }
}

运行效果说明

  1. 首次运行 :程序在 exe 同目录自动生成 memory_config.json,默认值为 16GB / 4800MHz

  2. 修改配置 :用记事本打开 memory_config.json,将数值改为你机器的实际规格

  3. 点击"开始测试":程序依次检测大小 → 频率 → 插槽,全部通过显示绿色,任一不符显示红色并说明原因

  4. 典型输出(通过)

    期望内存大小:16 GB
    期望内存频率:4800 MHz

    SMBIOS 读取内存大小:16 GB
    ✔ 内存大小检测通过
    SMBIOS 读取内存频率:4800 MHz
    ✔ 内存频率检测通过
    插槽 1:ChannelA-DIMM0
    ✔ 所有 DIMM 插槽检测通过
    全部通过!内存 16GB @ 4800MHz


六、总结与扩展

本文核心要点

  • SMBIOS 是读取硬件规格的最可靠途径,无需额外安装驱动,通过 GetSystemFirmwareTable P/Invoke 调用
  • Type 17Size 字段的单位由 bit15 决定,0xFFFF 表示未知------解析时必须处理这两种边界情况
  • Device Locator 字段存的是字符串索引而非字符串本身,需要从结构末尾的字符串区域按索引逐个读取
  • 本地 JSON 配置 替代 MES 让 Demo 完全独立可运行,同时也可以作为离线测试场景的解决方案

可以扩展的方向

  • 内存类型识别 :解析 Type 17 的 MemoryType 字段(0x1A = DDR5,0x18 = DDR4),加入类型校验
  • 制造商/SN 读取 :解析 ManufacturerSerialNumber 字符串字段,用于追溯物料来源
  • WMI 方案对比Win32_PhysicalMemory 也能获取类似信息,但 SMBIOS 直读更底层、速度更快
  • 多 SKU 支持:JSON 配置扩展为数组,工具启动时让用户选择当前机型对应的配置项

如有问题欢迎评论区交流。觉得有用的话点个赞和关注下,系列会持续更新 😃

相关推荐
Bofu-4 小时前
【Storage存储测试】07-WPF 通过 WMI + NVMe SMART 实现 SSD 规格自动验证
wpf·nvme·wmi·smart·ssd检测
Bofu-9 小时前
【键盘测试】05-WPF 可视化键盘布局配置 + 全局钩子按键检测实战
wpf·键盘测试·全局键盘钩子·scancode·组合键检测
bugcome_com9 小时前
WPF 路径动画完全指南:自绘制控件实战
wpf
不会编程的懒洋洋2 天前
WPF 性能优化+异步+渲染
开发语言·笔记·性能优化·c#·wpf·图形渲染·线程
求学中--3 天前
状态管理一文通:@State、@Prop、@Link、@Provide/Consume全解析
人工智能·小程序·uni-app·wpf·harmonyos
雨浓YN4 天前
GKTGD 工业监控系统-00设计文档
wpf
秋の本名5 天前
第一章 鸿蒙生态架构与开发理念
华为·wpf·harmonyos