透過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);




留言
張貼留言