在计算机上枚举显示器

52

我找到了7种不同的方法来枚举连接到计算机上的监视器。但是所有的解决方案都给出了不同的结果(监视器的数量和每个监视器的信息)。

这些解决方案包括:

  1. 使用著名的EnumDisplayDevices

  2. 使用EnumDisplayMonitors

  3. 使用Windows Management Instrumentation (WMI)
    使用以下查询:SELECT * FROM WmiMonitorIDroot\\WMI命名空间中。

  4. 再次使用WMI
    使用新查询:SELECT * FROM Win32_DesktopMonitorroot\\CIMV2命名空间中。

  5. 使用Setup API
    首先调用SetupDiGetClassDevs来检索设备信息集,然后使用SetupDiEnumDeviceInfo进行迭代。

  6. 使用DirectX图形基础设施(DXGI),首先使用IDXGIFactory::EnumAdapters,然后使用IDXGIAdapter::EnumOutput

  7. 使用连接和配置显示器 (CCD) API:
    QueryDisplayConfig(QDC_ALL_PATHS, &numPathArrayElements, pathInfoArray, &numModeInfoArrayElements, modeInfoArray, nullptr);

我试图通过MSDN文档理解这些方法之间的区别,但徒劳无功。

观察结果

从我的观察中得出以下结论:

  • WmiMonitorIDSetup API查询返回已连接的(但不一定是活动的)显示器列表。
  • Win32_DesktopMonitor WMI查询返回错误(至少是意外的)结果(即使在另一个显示器上不活动和桌面上,仍只枚举了1个监视器)。
  • EnumDisplayDevices返回活动设备列表(除非只有1个监视器处于活动状态并连接了其他监视器)
  • EnumDisplayMonitorsDXGI查询返回活动监视器列表。
  • CCD 似乎是最可靠的方法(提供目标和源之间所有可能的路径)。

问题

使用这些方法时,我真正应该期望什么结果(连接的显示器列表、安装的显示器列表、活动显示器列表)?如果我使用镜像显示或扩展显示会怎样?如果计算机有多个图形卡但没有多个输出,会怎样?

额外奖励:某些方法(DXGIEnumDisplayDevicesCCD)使用一种层次结构,其中包含 Adapter-Monitor 的概念,但不会给出相同的适配器和监视器链接。那么,DXGICCDEnumDisplayDevices 中适配器的定义是什么?


我想这里的诀窍将在于知道它们是否都调用相同的最低级别API并使用它。 - paulm
3
我认为正确的做法是提出一个更精确的问题,例如“在执行Y时如何列举X?”其中X可以是监视器、物理设备、逻辑设备等,Y是你的目标。掌握你的目标将有助于你排除一些可能性。正如你(深入而准确地)研究所示,事情并不像你之前想的那么简单,使用“监视器”和“计算机”等词语是没有可能得到答案的。 - user948581
@Cedric Bignon 我不使用Windows,但你为什么不编写一些测试代码,使用所有这些方法,可能在单独的文件中,然后反汇编二进制文件,看它们是否进行了相同的系统调用? - Charles D Pantoga
1
我支持@tibo的观点。你的问题太过笼统。此外,不同版本的Windows之间也存在差异(例如某些DXGI仅适用于Windows 8等)。一个有用的观察结果是:.NET框架(可以视为Windows上的抽象层)定义了Screen类(System.Windows.Forms.Screen),该类完全基于EnumDisplayMonitors/GetMonitorInfo(后者告诉设备名称)。 - Simon Mourier
另外还有一个函数:GetSystemMetrics(SM_CMONITORS) 仅计算可见的显示器。这与 EnumDisplayMonitors 不同,后者枚举了可见的显示器和与镜像驱动程序相关联的不可见伪监视器。不可见的伪监视器与用于远程或其他目的的应用程序绘图镜像的伪设备相关联。 - mikew
我的观点与上面的海报不同。我发现那个问题非常有帮助,因为我目前也在苦恼于完全相同的事情。尤其是镜像和扩展方面让我感到很困惑。我测试了大部分与原帖一样的东西,结果非常令人困惑。所以我很好奇在这里下面能找到什么答案。但还是要向原帖者致以极大的赞赏!也许不是最精确的问题,但是非常完整和准确。 - MBODM
2个回答

12

我并不了解所有这些API,但我记得其中一些(糟糕的回忆),因此以下是我能够想起并在MSDN中找到的内容,并通过wbemtest进行测试,我很惊讶自己甚至还记得。我知道这个答案可能并不完全符合你的期望。

以下的插图(所有这些插图都在我用来给你打字的Dell Latitude笔记本电脑上,而我通过插座连接它的两个逻辑显示器)。但是,笔记本电脑已关闭,因此笔记本电脑屏幕不可见。

如果我进入显示属性,我只看到一个屏幕。

enter image description here

连接到CIMv2。

select * from Win32_DesktopMonitor;

返回两个实例。

enter image description here

DesktopMonitor1是外部显示器(GenericPNPDisplay),而DesktopMonitor1是默认的监视器(屏幕)。

连接到root\WMI

select * from WMIMonitorID;

它只给我一个实例,而这个实例是外部显示器(我知道这是因为制造商名称是HP)。(HWP26CE是HP w2408的标识符,请参见此处

输入图像描述

然后,显示适配器和显示器之间存在差异。EnumDisplayDevices 显示适配器,EnumDisplayMonitors 显示显示器。前者主要用于枚举适配器,而后者允许您提供裁剪矩形并确定该裁剪矩形所在的显示器。当您有多个活动监视器并且某人决定执行将跨越多个监视器的绘图时,这变得有用。您可以指定回调函数以调用 EnumDisplayMonitors,并且该回调函数将使用一些参数调用(如果我记得正确,则其中一个参数是落在指定监视器上的指定裁剪矩形的子集)。

我隐约记得 SetupDiEnumDeviceInfo,我认为它会给您每个接口的HDEVINFO,因此在我的配置中它只会(我相信)给您一个条目,因为我只有一个适配器。然后,您必须执行某些操作以获取SP_DEVINFO_DATA

我从未使用过DirectX和其他API,所以我对这两个API不做评论。希望其他人能就这两个API发表意见,您可能会得到完整的答案!


1
如果与您的情况相关,使用Qt 5.x时,您可以使用QGuiApplication::screens()方法 (http://qt-project.org/doc/qt-5.1/qtgui/qguiapplication.html#screens)枚举所有显示器。
或者如果不相关,您可以随时查看它们的源代码,查看如何枚举显示器并获取所有相关属性(还涉及镜像、扩展桌面等)。

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