C# 用SetupAPI讀取EDID

透過SetupAPI指定Monitor的GUID 4d36e96e-e325-11ce-bfc1-08002be10318 取得裝置路徑,避免錯配。

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\DISPLAY\

從下圖可以看到Registry註冊表內螢幕的GUID都一樣


EXE執行檔載點

步驟概述

1. 取得顯示裝置集合 (device info set)

  • 使用 SetupDiGetClassDevs,指定 DISPLAY_DEVICE 的 GUID。

2. 列舉裝置

  • 使用 SetupDiEnumDeviceInfo 遍歷裝置。

3. 取得裝置屬性

  • 主要是 DEVPKEY_Device_HardwareIds 或者 EDID 從 registry。

4. 讀取 EDID

  • EDID 存在 registry:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\DISPLAY\<VendorID>\<DeviceID>\Device Parameters\EDID

程式碼

// --- P/Invoke Definitions ---
const int DIGCF_PRESENT = 0x2;

[DllImport("setupapi.dll", SetLastError = true)]
static extern IntPtr SetupDiGetClassDevs(ref Guid ClassGuid, string Enumerator, IntPtr hwndParent, uint Flags);

[DllImport("setupapi.dll", SetLastError = true)]
static extern bool SetupDiEnumDeviceInfo(IntPtr DeviceInfoSet, int MemberIndex, ref SP_DEVINFO_DATA DeviceInfoData);

[DllImport("cfgmgr32.dll", CharSet = CharSet.Auto)]
static extern int CM_Get_Device_ID(uint dnDevInst, StringBuilder Buffer, int BufferLen, int ulFlags);

[StructLayout(LayoutKind.Sequential)]
struct SP_DEVINFO_DATA
{
    public int cbSize;
    public Guid ClassGuid;
    public uint DevInst;
    public IntPtr Reserved;
}

private void button1_Click(object sender, EventArgs e)
{
    Guid GUID_DEVCLASS_MONITOR = new Guid("4d36e96e-e325-11ce-bfc1-08002be10318");
    IntPtr hDevInfo = SetupDiGetClassDevs(ref GUID_DEVCLASS_MONITOR, null, IntPtr.Zero, DIGCF_PRESENT);

    if (hDevInfo == IntPtr.Zero)
    {
        Console.WriteLine("Cannot get device info set.");
        return;
    }

    SP_DEVINFO_DATA devInfo = new SP_DEVINFO_DATA();
    devInfo.cbSize = Marshal.SizeOf(devInfo);

    for (int i = 0; SetupDiEnumDeviceInfo(hDevInfo, i, ref devInfo); i++)
    {
        // 取得裝置 Instance ID
        StringBuilder sb = new StringBuilder(256);
        if (CM_Get_Device_ID(devInfo.DevInst, sb, sb.Capacity, 0) == 0)
        {
            string deviceId = sb.ToString();
            Console.WriteLine("Device ID: " + deviceId);

            // 嘗試從 Registry 讀 EDID
            string edidRegPath = @"SYSTEM\CurrentControlSet\Enum\" + deviceId + @"\Device Parameters";
            using (RegistryKey key = Registry.LocalMachine.OpenSubKey(edidRegPath))
            {
                if (key != null)
                {
                    byte[] edid = key.GetValue("EDID") as byte[];
                    if (edid != null)
                    {                                
                        Console.WriteLine("EDID Length: " + edid.Length);
                        Console.WriteLine(BitConverter.ToString(edid).Replace("-", " "));

                        Console.WriteLine();
                    }
                }
            }
        }
    }
}

25行:

IntPtr hDevInfo = SetupDiGetClassDevs(ref GUID_DEVCLASS_MONITOR, null, IntPtr.Zero, DIGCF_PRESENT);

SetupDiGetClassDevs()用來取得系統中裝置(Device)的集合(Device Information Set),後續可用來列舉、

查詢或操作硬體裝置。

參數一:指向裝置類別 GUID

參數四:搜尋內容的旗標Flags

參數 說明
DIGCF_DEFAULT 0x01 只回傳與系統預設裝置相關的資訊
DIGCF_PRESENT 0x02 只取得目前存在的裝置
DIGCF_ALLCLASSES 0x04 包含所有裝置類別(ClassGuid 可為 NULL)
DIGCF_PROFILE 0x08 只取得目前硬體設定檔(hardware profile)的裝置
DIGCF_DEVICEINTERFACE 0x10 列舉裝置介面(Interface GUID)

36行:

for (int i = 0; SetupDiEnumDeviceInfo(hDevInfo, i, ref devInfo); i++)

SetupDiEnumDeviceInfo()用來 從 HDEVINFO 裝置集合中逐一列舉裝置的 API。

簡單說,它是 SetupDiGetClassDevs 之後,用來「一個一個取得裝置資訊」的函式。

  • 從 SetupDiGetClassDevs 回傳的 HDEVINFO 裝置集合中,依索引逐一取得裝置資訊
  • 回傳的資訊是 SP_DEVINFO_DATA 結構,裡面有裝置的 Class GUID、DevInst、大小 等基本資訊
  • 之後通常搭配:
    • SetupDiGetDeviceRegistryProperty(取得名稱、硬體 ID、描述)
    • SetupDiOpenDevRegKey(操作裝置登錄資訊)
    • SetupDiEnumDeviceInterfaces(取得裝置介面)
參數 說明
DeviceInfoSet 由 SetupDiGetClassDevs 回傳的 HDEVINFO
MemberIndex 0 開始的索引,逐一列舉
DeviceInfoData 指向 SP_DEVINFO_DATA 結構,接收裝置資訊,必須先設 cbSize

40行:

if (CM_Get_Device_ID(devInfo.DevInst, sb, sb.Capacity, 0) == 0)

CM_Get_Device_ID()是 Windows Configuration Manager(cfgmgr32)API 的函式,用來取得某個裝置的

Device Instance ID(裝置實例 ID)。

Device Instance ID 是 Windows 用來唯一識別一個「實際裝置實例」的字串,例如:

DISPLAY\LGD06F9\4&1545EAE2&0&UID8388688

讀 EDID 的正確流程

SetupDiGetClassDevs
    ↓
SetupDiEnumDeviceInfo
    ↓
CM_Get_Device_ID
    ↓
HKLM\SYSTEM\CCS\Enum\DISPLAY\...\Device Parameters\EDID


解析EDID

class EDIDParser
{
    public static void ParseEDID(byte[] edid)
    {
        if (edid == null || edid.Length < 128)
        {
            Console.WriteLine("EDID data too short.");
            return;
        }

        // Header
        Console.WriteLine("Header: " + BitConverter.ToString(edid, 0, 8));

        // 製造商 ID (2 bytes)
        ushort manufacturerCode = (ushort)((edid[8] << 8) | edid[9]);
        string manufacturer = DecodeManufacturerID(manufacturerCode);
        Console.WriteLine("Manufacturer: " + manufacturer);

        // 產品 ID (2 bytes)
        ushort productId = (ushort)((edid[10] << 8) | edid[11]);
        Console.WriteLine("Product ID: 0x" + productId.ToString("X4"));

        // 序號 (4 bytes)
        uint serial = BitConverter.ToUInt32(edid, 12);
        Console.WriteLine("Serial Number: " + serial);

        // 生產週 / 年
        byte week = edid[16];
        byte year = edid[17];
        Console.WriteLine($"Manufacture Date: Week {week}, Year {year + 1990}");

        // EDID Version
        byte version = edid[18];
        byte revision = edid[19];
        Console.WriteLine($"EDID Version: {version}.{revision}");

        // 基本顯示參數
        byte widthCm = edid[21];
        byte heightCm = edid[22];
        Console.WriteLine($"Display Size: {widthCm} cm x {heightCm} cm");

        // 顯示顏色特性 (簡單輸出)
        byte redGreenLow = edid[25];
        byte blueLow = edid[26];
        Console.WriteLine($"Color Info (raw): {redGreenLow:X2} {blueLow:X2}");

        // 詳細時序描述 (第一個)
        int dtdStart = 54; // 第一個 DTD offset
        if (edid[dtdStart] != 0)
        {
            int pixelClock = edid[dtdStart] + (edid[dtdStart + 1] << 8);
            Console.WriteLine($"Pixel Clock: {pixelClock} kHz (x10)");
        }
    }

    // 製造商 3 字母解碼
    private static string DecodeManufacturerID(ushort code)
    {
        char[] chars = new char[3];
        chars[0] = (char)(((code >> 10) & 0x1F) + 64);
        chars[1] = (char)(((code >> 5) & 0x1F) + 64);
        chars[2] = (char)((code & 0x1F) + 64);
        return new string(chars);
    }
}

使用方式

byte[] edid = ... // 從 registry 讀到的 EDID
EDIDParser.ParseEDID(edid);

留言