首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 取得硬體識別碼完整指南:CPU ID、硬碟序號、MAC 位址

     編輯:關於C#

軟體授權、裝置指紋、資產管理常需取得硬體唯一識別碼。本文整理 C# 透過 WMI (`System.Management`) 與 Windows API 取得 CPU ID、硬碟序號、MAC 位址、主機名稱的完整實作,含權限需求、效能優化、跨平台考量。

核心類別設計

using System;
using System.Management;
using System.Net;
using System.Net.NetworkInformation;
using System.Runtime.InteropServices;

public static class HardwareId
{
    // 取得主機名稱
    public static string GetHostName() => Dns.GetHostName();

    // 取得 CPU ID (處理器 ID)
    public static string GetCpuId()
    {
        try
        {
            using var searcher = new ManagementObjectSearcher("SELECT ProcessorId FROM Win32_Processor");
            foreach (ManagementObject obj in searcher.Get())
            {
                return obj["ProcessorId"]?.ToString()?.Trim() ?? string.Empty;
            }
        }
        catch { /* 忽略 WMI 錯誤 */ }
        return string.Empty;
    }

    // 取得第一顆實體硬碟序號
    public static string GetDiskSerialNumber()
    {
        try
        {
            using var searcher = new ManagementObjectSearcher("SELECT SerialNumber FROM Win32_PhysicalMedia");
            foreach (ManagementObject obj in searcher.Get())
            {
                string serial = obj["SerialNumber"]?.ToString()?.Trim();
                if (!string.IsNullOrEmpty(serial))
                    return serial;
            }
        }
        catch { }
        return string.Empty;
    }

    // 取得主要網卡 MAC 位址 (現代 .NET 推薦方式)
    public static string GetMacAddress()
    {
        try
        {
            var interfaces = NetworkInterface.GetAllNetworkInterfaces()
                .Where(nic => nic.OperationalStatus == OperationalStatus.Up
                           && nic.NetworkInterfaceType != NetworkInterfaceType.Loopback
                           && !nic.Description.Contains("Virtual", StringComparison.OrdinalIgnoreCase)
                           && !nic.Description.Contains("Hyper-V", StringComparison.OrdinalIgnoreCase))
                .OrderByDescending(nic => nic.Speed); // 優先高速網卡

            foreach (var nic in interfaces)
            {
                var mac = nic.GetPhysicalAddress();
                if (mac != null && mac.Length == 6)
                {
                    return string.Join(":", mac.GetAddressBytes().Select(b => b.ToString("X2")));
                }
            }
        }
        catch { }
        return string.Empty;
    }

    // 綜合硬體指紋 (可自訂組合)
    public static string GetHardwareFingerprint()
    {
        var parts = new[]
        {
            GetCpuId(),
            GetDiskSerialNumber(),
            GetMacAddress()
        };
        return string.Join("|", parts.Where(p => !string.IsNullOrEmpty(p)));
    }
}

WMI 查詢詳細說明

| 查詢 | 類別 | 關鍵屬性 | 備註 | |------|------|----------|------| | CPU ID | `Win32_Processor` | `ProcessorId` | 多核心回傳相同 ID,單一字串 | | 硬碟序號 | `Win32_PhysicalMedia` | `SerialNumber` | 實體裝置,非邏輯分區 | | 邏輯磁碟 | `Win32_LogicalDisk` | `VolumeSerialNumber` | 格式化產生,重新格式化會變 | | 主機板 | `Win32_BaseBoard` | `SerialNumber` | 部分廠商未填寫 | | BIOS | `Win32_BIOS` | `SerialNumber` | 相對穩定 | | 網路卡 | `Win32_NetworkAdapter` | `MACAddress` | 需過濾虛擬/停用網卡 |

Windows API 版本 (MAC 位址備選)


// 僅在 NetworkInformation 不可用時使用 (極舊系統)
public static class LegacyMacAddress
{
    private const int MAX_ADAPTER_ADDRESS_LENGTH = 8;
    
    [DllImport("iphlpapi.dll", CharSet = CharSet.Ansi)]
    private static extern int GetAdaptersInfo(IntPtr pAdapterInfo, ref int pOutBufLen);

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    private struct IP_ADAPTER_INFO
    {
        public IntPtr Next;
        public int ComboIndex;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public string AdapterName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 132)]
        public string Description;
        public uint AddressLength;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_ADAPTER_ADDRESS_LENGTH)]
        public byte[] Address;
        public int Index;
        public uint Type;
        public uint DhcpEnabled;
        public IntPtr CurrentIpAddress;
        public IP_ADAPTER_INFO_LIST IpAddressList;
        public IP_ADAPTER_INFO_LIST GatewayList;
        public IP_ADAPTER_INFO_LIST DhcpServerList;
        public bool HaveWins;
        public IP_ADAPTER_INFO_LIST PrimaryWinsServer;
        public IP_ADAPTER_INFO_LIST SecondaryWinsServer;
        public int LeaseObtained;
        public int LeaseExpires;
    }

    public static string GetMacAddress()
    {
        int bufferSize = 0;
        GetAdaptersInfo(IntPtr.Zero, ref bufferSize);
        if (bufferSize == 0) return string.Empty;

        IntPtr buffer = Marshal.AllocHGlobal(bufferSize);
        try
        {
            int result = GetAdaptersInfo(buffer, ref bufferSize);
            if (result != 0) return string.Empty;

            IntPtr current = buffer;
            while (current != IntPtr.Zero)
            {
                var adapter = Marshal.PtrToStructure(current);
                if (adapter.AddressLength == 6)
                {
                    return string.Join(":", adapter.Address.Take(6).Select(b => b.ToString("X2")));
                }
                current = adapter.Next;
            }
        }
        finally
        {
            Marshal.FreeHGlobal(buffer);
        }
        return string.Empty;
    }
}

權限與部署需求

| 需求 | 說明 | |------|------| | **參考組件** | `System.Management.dll`、`.NET 5+` 需 `System.Management` NuGet 套件 | | **執行權限** | 讀取 WMI 需管理員權限或 WMI 命名空間權限 (`root\cimv2`) | | **目標平台** | x64 / Any CPU (x86 可能無法查詢 64 位元 WMI) | | **Linux/macOS** | WMI 僅限 Windows,跨平台需改用 `/sys`、SMBIOS、`dmidecode`、`ioreg` 等 |

效能優化建議

  • ✅ **快取結果**:硬體 ID 不會頻繁變更,應用程式啟動時查詢一次快取
  • ✅ **非同步查詢**:`Task.Run(() => HardwareId.GetCpuId())` 避免阻塞 UI
  • ✅ **精準查詢**:`SELECT ProcessorId FROM Win32_Processor` 而非 `SELECT *`
  • ✅ **超時處理**:WMI 查詢可能卡住,加上 `CancellationToken` 超時機制
// 快取範例
public static class HardwareIdCache
{
    private static readonly Lazy _cpuId = new(HardwareId.GetCpuId, true);
    private static readonly Lazy _diskSerial = new(HardwareId.GetDiskSerialNumber, true);
    private static readonly Lazy _mac = new(HardwareId.GetMacAddress, true);

    public static string CpuId => _cpuId.Value;
    public static string DiskSerial => _diskSerial.Value;
    public static string MacAddress => _mac.Value;
}

常見問題排查

| 問題 | 原因 | 解決方案 | |------|------|----------| | 回傳空字串 | WMI 服務停用、權限不足、虛擬機器未暴露硬體資訊 | 檢查 `winmgmt` 服務、以管理員執行、虛擬機設定啟用硬體傳遞 | | CPU ID 全 0 或相同 | 某些虛擬化環境、舊版處理器 | 組合多個硬體 ID、使用 BIOS/主機板序號 | | MAC 位址頻繁變更 | 虛擬網卡、藍牙、VPN、Docker 虛擬介面 | 過濾 `NetworkInterfaceType`、描述關鍵字、優先實體乙太網路 | | `ManagementObjectSearcher` 慢 | WMI 存儲庫損毀、查詢範圍過大 | `winmgmt /verifyrepository`、`winmgmt /salvagerepository` 修復 | | `System.Management` 找不到 | .NET Core/.NET 5+ 未安裝 NuGet | `dotnet add package System.Management` |

授權綁定最佳實踐

public class LicenseValidator
{
    public bool Validate(string licenseKey, string expectedFingerprint)
    {
        // 1. 解析授權碼中的指紋 (或線上驗證)
        // 2. 比對當前硬體指紋
        string current = HardwareIdCache.GetHardwareFingerprint();
        
        // 允許部分硬體變更 (如更換網卡)
        return CalculateSimilarity(current, expectedFingerprint) > 0.8;
    }

    private double CalculateSimilarity(string current, string expected)
    {
        var currentParts = current.Split('|');
        var expectedParts = expected.Split('|');
        int match = currentParts.Intersect(expectedParts).Count();
        return (double)match / Math.Max(currentParts.Length, expectedParts.Length);
    }
}

硬體識別碼取得看似簡單,但涉及權限、虛擬化、跨平台、效能等實務考量。建立可靠的硬體指紋需組合多來源、容忍部分變更、妥善處理異常情境。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved