寻找可靠的Forms.Screen.DeviceName到显示器EDID信息的映射

7

我正在开发一款应用程序,它将在相应的显示器上的对话框中显示从EDID块(监视器型号、ID、S/N等)获得的信息。

这段代码 可以找到显示器的EDID信息。它通过枚举HKLM\SYSTEM\CurrentControlSet\Enum\DISPLAY\[Monitor]\[PnPID]\Device Parameters\EDID下的显示键提取EDID信息。

更新: 上面的代码依赖于PnP使用注册表的“副作用”。我现在使用SetupAPI 枚举显示器,可以正确处理连接/移除的显示器(不像上面链接中的代码)。

我正在尝试将Windows.Forms.Screen.AllScreens[]中的每个屏幕(\\.\DISPLAY1、\\.\DISPLAY2等)与上述注册表检查返回的条目进行关联。

注意:在下面的代码块中, DisplayDetails.GetMonitorDetails() 现已被更健壮的注册表枚举代码替换,但返回的数据是相同的。

例如:

private void Form1_Load(object sender, EventArgs e)
{
    Console.WriteLine("Polling displays on {0}:", System.Environment.MachineName);
    int i = 0;
    foreach ( DisplayDetails dd in DisplayDetails.GetMonitorDetails())
    {
        Console.WriteLine( "Info: Model: {0}, MonitorID: {1}, PnPID: {2}, Serial#:{3}", dd.Model, dd.MonitorID, dd.PnPID, dd.SerialNumber );
        Console.WriteLine( "Does this correlate to Screen: {0}?", Screen.AllScreens[i++].DeviceName );
    }
}

输出:

信息:型号:DELL P2411H,监视器ID:DELA06E,PnPID:5&2e2fefea&0&UID1078018,序列号:F8NDP0C...PU

这是否与屏幕相对应:\\.\DISPLAY1?

信息:型号:DELL P2411H,监视器ID:DELA06E,PnPID:5&2e2fefea&0&UID1078019,序列号:F8NDP0C...AU

这是否与屏幕相对应:\\.\DISPLAY2?


答案:不是

在测试中,我发现它们之间不能可靠地相互关联(我有一个系统,其中第一个显示枚举是 \\.\DISPLAY2)。

我的问题是:是否有一种可靠的方法来获取给定 Forms.Screen 的 EDID 信息? 我可以获取 EDID 块,但没有找到将其与 UI 顶级表单相关联的路径。提示用户是不可取的,因为在我的用例中,两个(或更多)监视器可能是相同的型号和分辨率,仅在 S/N 中有几个数字不同。

我已经寻找了遵循 Forms.Screen API、Win32 EnumDisplay、其他注册表 GUID(PnP 和驱动程序相关)的路径,但没有找到任何有前途的路径。

我还调查了 WMI Win32_DesktopMonitor API(Windows 7),但它似乎没有更多的信息可以帮助我将其与 Windows.Forms.Screen.AllScreens[] 条目相关联。

我怀疑如果有一种方法来做到这一点,那就是通过 SetupAPI,但我还没有找到它。


3
不应直接使用注册表键。适当的方法是通过SetupDiGetClassDevices(GUID_DEVINTERFACE_MONITOR)+ SetupDiEnumDeviceInterfaces + SetupDiOpenDevRegKey来实现。这样做有可能更好地响应PnP事件,但不确定是否真的如此。 - Hans Passant
注意:赏金现在正在寻找明确绑定这两个解决方案的答案。抱歉,我无法更新赏金文本(http://meta.stackexchange.com/questions/16065/how-does-the-bounty-system-work)。 - holtavolt
1
找到了“缺失的链接”。如果您在dwFlags中指定EDD_GET_DEVICE_INTERFACE_NAME,则DIDD路径将匹配EnumDisplayDevices()查找的结果!感谢帮助,汉斯(如果您想要赏金,请发布答案)。 - holtavolt
1
请问您可不可以发整个解决方案? - m0sa
1
是的,最终的解决方案会非常方便,你能发帖分享一下吗? - Roni Tovi
显示剩余5条评论
2个回答

8
EnumDisplayDevices API中提供了一种解决GDI到SetupAPI的方法。如果您将EDD_GET_DEVICE_INTERFACE_NAME传递给dwFlags,监视器枚举将返回以下形式的DeviceID信息:
Monitor 0 info:
DeviceName: \\.\DISPLAY1
MonitorInfo: Dell P2411H(Digital)
DeviceID: \\?\DISPLAY#DELA06E#5&2e2fefea&0&UID1078018#{e6f07b5f-ee97-4a90-b076-3
3f57bf4eaa7}
Monitor 1 info:
DeviceName: \\.\DISPLAY2
MonitorInfo: Dell P2411H(Digital)
DeviceID: \\?\DISPLAY#DELA06E#5&2e2fefea&0&UID1078019#{e6f07b5f-ee97-4a90-b076-3
3f57bf4eaa7}

设备ID字段现在与以下C#片段中检索的结果匹配:
    Guid MonitorGUID = new Guid(Win32.GUID_DEVINTERFACE_MONITOR);

    // We start at the "root" of the device tree and look for all
    // devices that match the interface GUID of a monitor
    IntPtr h = Win32.SetupDiGetClassDevs(ref MonitorGUID, IntPtr.Zero, IntPtr.Zero, (uint)(Win32.DIGCF_PRESENT | Win32.DIGCF_DEVICEINTERFACE));
    if (h.ToInt64() != Win32.INVALID_HANDLE_VALUE)
    {
        bool Success = true;
        uint i = 0;
        while (Success)
        {
            // create a Device Interface Data structure
            Win32.SP_DEVICE_INTERFACE_DATA dia = new Win32.SP_DEVICE_INTERFACE_DATA();
            dia.cbSize = (uint)Marshal.SizeOf(dia);

            // start the enumeration 
            Success = Win32.SetupDiEnumDeviceInterfaces(h, IntPtr.Zero, ref MonitorGUID, i, ref dia);
            if (Success)
            {
                // build a DevInfo Data structure
                Win32.SP_DEVINFO_DATA da = new Win32.SP_DEVINFO_DATA();
                da.cbSize = (uint)Marshal.SizeOf(da);

                // build a Device Interface Detail Data structure
                Win32.SP_DEVICE_INTERFACE_DETAIL_DATA didd = new Win32.SP_DEVICE_INTERFACE_DETAIL_DATA();
                didd.cbSize = (uint)(4 + Marshal.SystemDefaultCharSize); // trust me :)

                // now we can get some more detailed information
                uint nRequiredSize = 0;
                uint nBytes = Win32.BUFFER_SIZE;
                if (Win32.SetupDiGetDeviceInterfaceDetail(h, ref dia, ref didd, nBytes, out nRequiredSize, ref da))
                {
                    // Now we get the InstanceID
                    IntPtr ptrInstanceBuf = Marshal.AllocHGlobal((int)nBytes);
                    Win32.CM_Get_Device_ID(da.DevInst, ptrInstanceBuf, (int)nBytes, 0);
                    string InstanceID = Marshal.PtrToStringAuto(ptrInstanceBuf);
                    Console.WriteLine("InstanceID: {0}", InstanceID );
                    Marshal.FreeHGlobal(ptrInstanceBuf);
                   
                    Console.WriteLine("DevicePath: {0}", didd.DevicePath );
                }
                i++;
            }
        }
    }
    Win32.SetupDiDestroyDeviceInfoList(h);
}

示例输出:

InstanceID: DISPLAY\DELA06E\5&2E2FEFEA&0&UID1078018
DevicePath: \\?\display#dela06e#5&2e2fefea&0&uid1078018#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}

原始的EnumDisplayDevices中的DeviceName与Forms.Screen.DeviceName属性匹配。

有了这两个信息,现在可以使用以下片段在SetupDIEnumDeviceInterface遍历期间读取EDID块:

private static byte[] GetMonitorEDID(IntPtr pDevInfoSet, SP_DEVINFO_DATA deviceInfoData)
{
    IntPtr hDeviceRegistryKey = SetupDiOpenDevRegKey(pDevInfoSet, ref deviceInfoData,
        DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_QUERY_VALUE);
    if (hDeviceRegistryKey == IntPtr.Zero)
    {
        throw new Exception("Failed to open a registry key for device-specific configuration information");
    }

    IntPtr ptrBuff = Marshal.AllocHGlobal((int)256);
    try
    {
        RegistryValueKind lpRegKeyType = RegistryValueKind.Binary;
        int length = 256;
        uint result = RegQueryValueEx(hDeviceRegistryKey, "EDID", 0, ref lpRegKeyType, ptrBuff, ref length);
        if (result != 0)
        {
            throw new Exception("Can not read registry value EDID for device " + deviceInfoData.ClassGuid);
        }
    }
    finally
    {
        RegCloseKey(hDeviceRegistryKey);
    }
    byte[] edidBlock = new byte[256];
    Marshal.Copy(ptrBuff, edidBlock, 0, 256);
    Marshal.FreeHGlobal(ptrBuff);
    return edidBlock;
}

最终,此代码中的DisplayDetails.GetMonitorDetails()方法可以用于解析VESA描述符块。

你是怎么得到 "MonitorInfo: Dell P2411H(Digital)" 这个字符串的? - m0sa
@m0sa - 从EnumDisplayDevices API中获取DeviceString字段。有关如何PInvoke的信息可以在此处找到:http://www.pinvoke.net/search.aspx?search=EnumDisplayDevices&namespace=[All] - holtavolt
请您提交Win32类? - Roni Tovi
Win32类也来自于pinvoke.net - 链接在上面。 - holtavolt
这个不起作用了--也许事情已经改变了。但是DeviceID不再匹配DevicePathInstanceID - Andy

0
我找到了一个正好能满足你需求的脚本。然而,我不知道为什么 outVar.DeviceID 变量是空的,请问有人可以告诉我原因吗?没有设备ID信息,就无法映射EDID信息。
谢谢,Mathew
using System; 
using System.Runtime.InteropServices; 
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace Resolution 
{ 
    [StructLayout(LayoutKind.Sequential)] 
    public struct DEVMODE1 
    { 
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] 
        public string dmDeviceName; 
        public short dmSpecVersion; 
        public short dmDriverVersion; 
        public short dmSize; 
        public short dmDriverExtra; 
        public int dmFields; 
        public short dmOrientation; 
        public short dmPaperSize; 
        public short dmPaperLength; 
        public short dmPaperWidth; 
        public short dmScale; 
        public short dmCopies; 
        public short dmDefaultSource; 
        public short dmPrintQuality; 
        public short dmColor; 
        public short dmDuplex; 
        public short dmYResolution; 
        public short dmTTOption; 
        public short dmCollate; 
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] 
        public string dmFormName; 
        public short dmLogPixels; 
        public short dmBitsPerPel; 
        public int dmPelsWidth; 
        public int dmPelsHeight; 
        public int dmDisplayFlags; 
        public int dmDisplayFrequency; 
        public int dmICMMethod; 
        public int dmICMIntent; 
        public int dmMediaType; 
        public int dmDitherType; 
        public int dmReserved1; 
        public int dmReserved2; 
        public int dmPanningWidth; 
        public int dmPanningHeight; 
    }; 
    
    [Flags()]
    public enum DisplayDeviceStateFlags : int
    {
        /// <summary>The device is part of the desktop.</summary>
        AttachedToDesktop = 0x1,
        MultiDriver = 0x2,
        /// <summary>The device is part of the desktop.</summary>
        PrimaryDevice = 0x4,
        /// <summary>Represents a pseudo device used to mirror application drawing for remoting or other purposes.</summary>
        MirroringDriver = 0x8,
        /// <summary>The device is VGA compatible.</summary>
        VGACompatible = 0x10,
        /// <summary>The device is removable; it cannot be the primary display.</summary>
        Removable = 0x20,
        /// <summary>The device has more display modes than its output devices support.</summary>
        ModesPruned = 0x8000000,
        Remote = 0x4000000,
        Disconnect = 0x2000000
    }

    [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
    public struct DISPLAY_DEVICE 
    {
            [MarshalAs(UnmanagedType.U4)]
            public int cb;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst=32)]
            public string DeviceName;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)]
            public string DeviceString;
            [MarshalAs(UnmanagedType.U4)]
            public DisplayDeviceStateFlags StateFlags;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)]
            public string DeviceID;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)]
            public string DeviceKey;
    }
    class User_32 
    { 
        [DllImport("user32.dll")] 
        public static extern int EnumDisplaySettings(string deviceName, int modeNum, ref DEVMODE1 devMode); 
        [DllImport("user32.dll")] 
        public static extern int ChangeDisplaySettings(ref DEVMODE1 devMode, int flags); 
        [DllImport("user32.dll")]
        public static extern bool EnumDisplayDevices(string lpDevice, uint iDevNum, ref DISPLAY_DEVICE lpDisplayDevice, uint dwFlags);
        public const int ENUM_CURRENT_SETTINGS = -1; 
        public const int CDS_UPDATEREGISTRY = 0x01; 
        public const int CDS_TEST = 0x02; 
        public const int DISP_CHANGE_SUCCESSFUL = 0; 
        public const int DISP_CHANGE_RESTART = 1; 
        public const int DISP_CHANGE_FAILED = -1; 
    } 
    public class Displays
    {
        public static IList<string> GetDisplayNames()
        {
            var returnVals = new List<string>();
            for(var x=0U; x<1024; ++x)
            {
                DISPLAY_DEVICE outVar = new DISPLAY_DEVICE();
                outVar.cb = (short)Marshal.SizeOf(outVar);

                if(User_32.EnumDisplayDevices(null, x, ref outVar, 1U))
                {
                    Regex regex = new Regex(DisplayDeviceStateFlags.AttachedToDesktop.ToString());
                    Match match = regex.Match(outVar.StateFlags.ToString());

                    if (match.Success)
                    {
                        returnVals.Add(outVar.DeviceName + ":" + outVar.DeviceID);
                    }
                }
            }
            return returnVals;
        }
        
        public static string GetCurrentResolution(string deviceName)
        {
            string returnValue = null;
            DEVMODE1 dm = GetDevMode1();
            if (0 != User_32.EnumDisplaySettings(deviceName, User_32.ENUM_CURRENT_SETTINGS, ref dm))
            {
                returnValue = dm.dmPelsWidth + "x" + dm.dmPelsHeight;
            }
            return returnValue;
        }
        
        public static IList<string> GetResolutions()
        {
            var displays = GetDisplayNames();
            var returnValue = new List<string>();
            foreach(var display in displays)
            {
                string[] ds = display.Split(':');
                returnValue.Add(ds[0] + ";" + GetCurrentResolution(ds[0]) + ";" + ds[1]);
            }
            return returnValue;
        }
        
        private static DEVMODE1 GetDevMode1() 
        { 
            DEVMODE1 dm = new DEVMODE1(); 
            dm.dmDeviceName = new String(new char[32]); 
            dm.dmFormName = new String(new char[32]); 
            dm.dmSize = (short)Marshal.SizeOf(dm); 
            return dm; 
        }

    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接