windows - 寻找 Forms.Screen.DeviceName 的可靠映射以监控 EDID 信息

标签 windows winforms winapi monitor edid

我正在开发一个应用程序,它将在相应显示器的对话框中显示从 EDID block (显示器型号、ID、S/N 等)派生的信息。

This code用于查找显示器的 EDID 信息。它通过枚举HKLM\SYSTEM\CurrentControlSet\Enum\DISPLAY\[Monitor]\[PnPID]\Device Parameters\EDID下的DISPLAY键来提取EDID信息。

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

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

注意:在下面的代码块中,DisplayDetails.GetMonitorDetails() 现在已使用 SetupAPI 替换为更强大的注册表枚举代码,但返回的数据是相同的。

例如

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

这与 Screen:\\.\DISPLAY1 相关吗?

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

这与 Screen:\\.\DISPLAY2 相关吗?


回答:否

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

我的问题: 有没有办法可靠地获取给定 Forms.Screen 的 EDID 信息?我可以获取 EDID block ,但找不到将其关联到 UI 的路径顶级表格。提示用户是不可取的,因为在我的用例中,两个(或更多)显示器可能具有相同的型号和分辨率,并且在 S/N 中仅相差几位数字。

我一直在寻找遵循 Forms.Screen API、Win32 EnumDisplay、其他注册表 GUID(PnP 和驱动程序相关)的路径,但没有找到任何有希望的路径。

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

我怀疑是否有办法做到这一点,它是通过 SetupAPI,但我还没有找到它。

最佳答案

将 GDI 解析为 SetupAPI 的方法在 EnumDisplayDevices 中可用。应用程序接口(interface)。如果您为 dwFlags 传入 EDD_GET_DEVICE_INTERFACE_NAME,监视器枚举将返回以下形式的 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}

DeviceID 字段现在与 didd.DevicePath 的结果相匹配,如以下 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 block :

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;
}

最后,可以针对 VESA 描述符 block 进行解析,如 DisplayDetails.GetMonitorDetails() method in this code. 中所示

关于windows - 寻找 Forms.Screen.DeviceName 的可靠映射以监控 EDID 信息,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10237937/

相关文章:

windows - 'jmeter' 不是内部或外部命令,也不是可运行的程序或批处理文件

windows - 我可以从 elisp 中读取 Windows 注册表吗?如何?

c# - 在 C# 中模拟雷达的最佳方法是什么?

c++ - 具有 VirtualProtect 的 PAGE_GUARD 在执行访问时不会引发异常

c++ - 如何从加载程序 DLL 获取函数地址?

c++ - 将 int 转换为 wchar_t 的正确方法是什么?

windows - 如果 OpenProcess() 因 ACCESS_DENIED 而失败,如何从 pid 获取进程文件名?

c# - 如何从 savefiledialog 获取完整路径并在 "startInfo.Arguments"中使用?

c# - 将项目添加到任务栏应用程序菜单

javascript - 从 Electron 代码更改外部应用程序窗口大小