最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c# - Uniquely tracking monitors in Windows across reconnections - Stack Overflow

programmeradmin1浏览0评论

I am writing a software in c# which must be able to uniquely track monitors in windows across reconnections. It must be able to rediscover the monitors display number (e.g. DISPLAY1, DISPLAY2) upon reconnection, in order to pass it into ChangeDisplaySettingsEx, which unfortunately only works with display numbers retrieved by using EnumDisplayDevices. My first thought was to query Windows Management Object (at: root\WMI", "SELECT * FROM WmiMonitorID), and retrieve the monitors serial number plus a piece of information that is equivalent in EnumDisplayDevices, then search EnumDisplayDevices with this key information in order to find the display number that corresponds to the saved display serial number, however this is proving very difficult as WMI and EnumDisplayDevices appear to have no matchable information in common. I interrogated chatgpt on data they may have in common which I can match however every suggestion has failed. I am assuming there is a way to correlate the two, as Windows display settings has all of this information (serial number, display number etc) contained within its UI. In short, I would like to save a display permanently to my software, so that it can be recognized and changed the next time a user connects.

Other Considerations

  • The data in EnumDisplayDevices unfortunately does not contain anything that is unique to a specific display, so it cannot be used to identify them.
  • Much of the data in WindowManagementObject(root\WMI", "SELECT * FROM WmiMonitorID) is also not static and changes, with the exception of the displays serial number which appears to be the only piece of information that can uniquely identify a connected display, even if you have multiple identical displays.

The main thing I have been trying to compare thus far is InstanceName from ManagementObject (which is connected to the serial number), to DeviceID from EnumDisplayDevice, which are supposed to have data in common, but never appear to.

Heres the code I have been using to test this functionality, it is able to retreive all of the data, however never finds a match as InstanceName and DeviceID are consistently different, meaning the serial number and display number cannot be linked.

public class DisplayTracker
{
    private static readonly Guid GUID_DEVCLASS_MONITOR = new Guid("4d36e96e-e325-11ce-bfc1-08002be10318");

    static void Main(string[] args)
    {
        PrintDisplayNumbersAndSerials();
    }

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

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    private struct DISPLAY_DEVICE
    {
        public int cb;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string DeviceName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string DeviceString;
        public int StateFlags;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string DeviceID;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string DeviceKey;
    }

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

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

    [DllImport("SetupAPI.dll", SetLastError = true, CharSet = CharSet.Auto)]
    private static extern bool SetupDiGetDeviceInstanceId(
        IntPtr DeviceInfoSet,
        ref SP_DEVINFO_DATA DeviceInfoData,
        StringBuilder DeviceInstanceId,
        int DeviceInstanceIdSize,
        out int RequiredSize
    );

    [DllImport("SetupAPI.dll", SetLastError = true)]
    private static extern bool SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet);

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    static extern bool EnumDisplayDevices(string lpDevice, uint iDevNum, ref DISPLAY_DEVICE lpDisplayDevice, uint dwFlags);

    private const uint DIGCF_PRESENT = 0x00000002;

    public static void PrintDisplayNumbersAndSerials()
    {
        var serials = GetMonitorSerialsFromWMI();
        var displays = GetCurrentDisplays();
        var connectedDevices = GetConnectedDeviceInstanceIDs();

        foreach (var kvp in serials)
        {
            string instanceName = kvp.Key;
            string serial = kvp.Value;

            string matchedDeviceInstance = connectedDevices.FirstOrDefault(dev => dev.Contains(instanceName, StringComparison.OrdinalIgnoreCase));

            if (matchedDeviceInstance != null)
            {
                var match = displays.FirstOrDefault(d => matchedDeviceInstance.Contains(d.Key, StringComparison.OrdinalIgnoreCase));
                if (!string.IsNullOrEmpty(match.Value))
                {
                    Console.WriteLine($"Display Serial: {serial} -> Display Number: {match.Value}");
                }
                else
                {
                    Console.WriteLine($"Display Serial: {serial} -> Display Number: (Not Found)");
                }
            }
            else
            {
                Console.WriteLine($"Display Serial: {serial} -> Display Number: (Not Connected)");
            }
        }
    }

    public static Dictionary<string, string> GetCurrentDisplays()
    {
        var displays = new Dictionary<string, string>();

        DISPLAY_DEVICE displayDevice = new DISPLAY_DEVICE();
        displayDevice.cb = Marshal.SizeOf(displayDevice);
        uint deviceIndex = 0;

        while (EnumDisplayDevices(null, deviceIndex, ref displayDevice, 0))
        {
            displays[displayDevice.DeviceID] = displayDevice.DeviceName;
            deviceIndex++;
        }

        return displays;
    }

    public static Dictionary<string, string> GetMonitorSerialsFromWMI()
    {
        var monitorSerials = new Dictionary<string, string>();

        try
        {
            ManagementObjectSearcher searcher = new ManagementObjectSearcher(@"root\WMI", "SELECT * FROM WmiMonitorID");

            foreach (ManagementObject mo in searcher.Get())
            {
                string instanceName = mo["InstanceName"] as string;
                string serial = GetStringFromUShortArray((ushort[])mo["SerialNumberID"]);

                if (!string.IsNullOrEmpty(instanceName) && !string.IsNullOrEmpty(serial))
                {
                    monitorSerials[instanceName] = serial;
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error querying WMI: {ex.Message}");
        }

        return monitorSerials;
    }

    private static string GetStringFromUShortArray(ushort[] data)
    {
        if (data == null) return null;
        return Encoding.ASCII.GetString(Array.ConvertAll(data, Convert.ToByte)).TrimEnd('\0');
    }

    public static List<string> GetConnectedDeviceInstanceIDs()
    {
        var deviceInstanceIDs = new List<string>();

        Guid monitorGuid = GUID_DEVCLASS_MONITOR;
        IntPtr deviceInfoSet = SetupDiGetClassDevs(ref monitorGuid, IntPtr.Zero, IntPtr.Zero, DIGCF_PRESENT);
        if (deviceInfoSet == IntPtr.Zero) return deviceInstanceIDs;

        try
        {
            SP_DEVINFO_DATA deviceInfoData = new SP_DEVINFO_DATA();
            deviceInfoData.cbSize = Marshal.SizeOf(deviceInfoData);

            uint index = 0;
            while (SetupDiEnumDeviceInfo(deviceInfoSet, index, ref deviceInfoData))
            {
                StringBuilder deviceInstanceId = new StringBuilder(256);
                int requiredSize;

                if (SetupDiGetDeviceInstanceId(deviceInfoSet, ref deviceInfoData, deviceInstanceId, deviceInstanceId.Capacity, out requiredSize))
                {
                    deviceInstanceIDs.Add(deviceInstanceId.ToString());
                }

                index++;
            }
        }
        finally
        {
            SetupDiDestroyDeviceInfoList(deviceInfoSet);
        }

        return deviceInstanceIDs;
    }
}

Any assistance is greatly appreciated.

I am writing a software in c# which must be able to uniquely track monitors in windows across reconnections. It must be able to rediscover the monitors display number (e.g. DISPLAY1, DISPLAY2) upon reconnection, in order to pass it into ChangeDisplaySettingsEx, which unfortunately only works with display numbers retrieved by using EnumDisplayDevices. My first thought was to query Windows Management Object (at: root\WMI", "SELECT * FROM WmiMonitorID), and retrieve the monitors serial number plus a piece of information that is equivalent in EnumDisplayDevices, then search EnumDisplayDevices with this key information in order to find the display number that corresponds to the saved display serial number, however this is proving very difficult as WMI and EnumDisplayDevices appear to have no matchable information in common. I interrogated chatgpt on data they may have in common which I can match however every suggestion has failed. I am assuming there is a way to correlate the two, as Windows display settings has all of this information (serial number, display number etc) contained within its UI. In short, I would like to save a display permanently to my software, so that it can be recognized and changed the next time a user connects.

Other Considerations

  • The data in EnumDisplayDevices unfortunately does not contain anything that is unique to a specific display, so it cannot be used to identify them.
  • Much of the data in WindowManagementObject(root\WMI", "SELECT * FROM WmiMonitorID) is also not static and changes, with the exception of the displays serial number which appears to be the only piece of information that can uniquely identify a connected display, even if you have multiple identical displays.

The main thing I have been trying to compare thus far is InstanceName from ManagementObject (which is connected to the serial number), to DeviceID from EnumDisplayDevice, which are supposed to have data in common, but never appear to.

Heres the code I have been using to test this functionality, it is able to retreive all of the data, however never finds a match as InstanceName and DeviceID are consistently different, meaning the serial number and display number cannot be linked.

public class DisplayTracker
{
    private static readonly Guid GUID_DEVCLASS_MONITOR = new Guid("4d36e96e-e325-11ce-bfc1-08002be10318");

    static void Main(string[] args)
    {
        PrintDisplayNumbersAndSerials();
    }

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

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    private struct DISPLAY_DEVICE
    {
        public int cb;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string DeviceName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string DeviceString;
        public int StateFlags;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string DeviceID;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string DeviceKey;
    }

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

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

    [DllImport("SetupAPI.dll", SetLastError = true, CharSet = CharSet.Auto)]
    private static extern bool SetupDiGetDeviceInstanceId(
        IntPtr DeviceInfoSet,
        ref SP_DEVINFO_DATA DeviceInfoData,
        StringBuilder DeviceInstanceId,
        int DeviceInstanceIdSize,
        out int RequiredSize
    );

    [DllImport("SetupAPI.dll", SetLastError = true)]
    private static extern bool SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet);

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    static extern bool EnumDisplayDevices(string lpDevice, uint iDevNum, ref DISPLAY_DEVICE lpDisplayDevice, uint dwFlags);

    private const uint DIGCF_PRESENT = 0x00000002;

    public static void PrintDisplayNumbersAndSerials()
    {
        var serials = GetMonitorSerialsFromWMI();
        var displays = GetCurrentDisplays();
        var connectedDevices = GetConnectedDeviceInstanceIDs();

        foreach (var kvp in serials)
        {
            string instanceName = kvp.Key;
            string serial = kvp.Value;

            string matchedDeviceInstance = connectedDevices.FirstOrDefault(dev => dev.Contains(instanceName, StringComparison.OrdinalIgnoreCase));

            if (matchedDeviceInstance != null)
            {
                var match = displays.FirstOrDefault(d => matchedDeviceInstance.Contains(d.Key, StringComparison.OrdinalIgnoreCase));
                if (!string.IsNullOrEmpty(match.Value))
                {
                    Console.WriteLine($"Display Serial: {serial} -> Display Number: {match.Value}");
                }
                else
                {
                    Console.WriteLine($"Display Serial: {serial} -> Display Number: (Not Found)");
                }
            }
            else
            {
                Console.WriteLine($"Display Serial: {serial} -> Display Number: (Not Connected)");
            }
        }
    }

    public static Dictionary<string, string> GetCurrentDisplays()
    {
        var displays = new Dictionary<string, string>();

        DISPLAY_DEVICE displayDevice = new DISPLAY_DEVICE();
        displayDevice.cb = Marshal.SizeOf(displayDevice);
        uint deviceIndex = 0;

        while (EnumDisplayDevices(null, deviceIndex, ref displayDevice, 0))
        {
            displays[displayDevice.DeviceID] = displayDevice.DeviceName;
            deviceIndex++;
        }

        return displays;
    }

    public static Dictionary<string, string> GetMonitorSerialsFromWMI()
    {
        var monitorSerials = new Dictionary<string, string>();

        try
        {
            ManagementObjectSearcher searcher = new ManagementObjectSearcher(@"root\WMI", "SELECT * FROM WmiMonitorID");

            foreach (ManagementObject mo in searcher.Get())
            {
                string instanceName = mo["InstanceName"] as string;
                string serial = GetStringFromUShortArray((ushort[])mo["SerialNumberID"]);

                if (!string.IsNullOrEmpty(instanceName) && !string.IsNullOrEmpty(serial))
                {
                    monitorSerials[instanceName] = serial;
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error querying WMI: {ex.Message}");
        }

        return monitorSerials;
    }

    private static string GetStringFromUShortArray(ushort[] data)
    {
        if (data == null) return null;
        return Encoding.ASCII.GetString(Array.ConvertAll(data, Convert.ToByte)).TrimEnd('\0');
    }

    public static List<string> GetConnectedDeviceInstanceIDs()
    {
        var deviceInstanceIDs = new List<string>();

        Guid monitorGuid = GUID_DEVCLASS_MONITOR;
        IntPtr deviceInfoSet = SetupDiGetClassDevs(ref monitorGuid, IntPtr.Zero, IntPtr.Zero, DIGCF_PRESENT);
        if (deviceInfoSet == IntPtr.Zero) return deviceInstanceIDs;

        try
        {
            SP_DEVINFO_DATA deviceInfoData = new SP_DEVINFO_DATA();
            deviceInfoData.cbSize = Marshal.SizeOf(deviceInfoData);

            uint index = 0;
            while (SetupDiEnumDeviceInfo(deviceInfoSet, index, ref deviceInfoData))
            {
                StringBuilder deviceInstanceId = new StringBuilder(256);
                int requiredSize;

                if (SetupDiGetDeviceInstanceId(deviceInfoSet, ref deviceInfoData, deviceInstanceId, deviceInstanceId.Capacity, out requiredSize))
                {
                    deviceInstanceIDs.Add(deviceInstanceId.ToString());
                }

                index++;
            }
        }
        finally
        {
            SetupDiDestroyDeviceInfoList(deviceInfoSet);
        }

        return deviceInstanceIDs;
    }
}

Any assistance is greatly appreciated.

Share Improve this question edited Mar 3 at 19:23 stuartd 73.4k16 gold badges138 silver badges168 bronze badges asked Mar 3 at 19:08 epice500epice500 92 bronze badges 0
Add a comment  | 

1 Answer 1

Reset to default 1

Both WmiMonitorID.InstanceName and DISPLAY_DEVICE.DeviceID include hardware specific identifier and instance specific identifier. So you can extract these identifiers and correlate them to identify a specific monitor.

The format of WmiMonitorID.InstanceName is

DISPLAY\<hardware id>\<instance id>_0

In fact, it is almost same as the format of Device Instance ID

DISPLAY\<hardware id>\<instance id>

The format of DISPLAY_DEVICE.DeviceID is

\\?\DISPLAY#<hardware id>#<instance id>#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}
发布评论

评论列表(0)

  1. 暂无评论