Windows下提示显示器信息主要通过两个函数实现。一个是EnumDisplayDevices(), 另一个是EnumDisplayMonitors(). EnumDisplayDevices()枚举所有显示设备,而EnumDisplayMonitors枚举的是所有显示器。显示设备和显示器不一样,比如显卡算显示设备,但是不是显示器。具体差别后面会分析。EnumDisplayMonitors()还会枚举出不可见的伪显示器,如果只是想得到实际的显示器数目的话可以用GetSystemMetrics(SM_CMONITORS),
该函数不包括虚拟显示器。
下面一段代码展示了这三个函数的用法和差别(参考自https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/668e3cf9-4e00-4b40-a6f8-c7d2fc1afd39/how-can-i-retrieve-monitor-information?forum=windowsgeneraldevelopmentissues)
// MonitorSerialCtrlApp.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include <windows.h> using namespace std; BOOL CALLBACK MyInfoEnumProc( HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData ) { MONITORINFOEX mi; ZeroMemory(&mi, sizeof(mi)); mi.cbSize = sizeof(mi); GetMonitorInfo(hMonitor, &mi); wprintf(L"DisplayDevice: %s\n", mi.szDevice); return TRUE; } int _tmain(int argc, _TCHAR* argv[]) { int numMonitor; int run=0; while(1) { printf("*********************%d****************\n",run); run++; printf("\n\n\EnumDisplayDevices\n\n\n"); DISPLAY_DEVICE dd; ZeroMemory(&dd, sizeof(dd)); dd.cb = sizeof(dd); for(int i=0; EnumDisplayDevices(NULL, i, &dd, 0); i++) { //EnumDisplayDevices(NULL, i, &dd, 0); wprintf(L"\n\nDevice %d:", i); wprintf(L"\n DeviceName: '%s'", dd.DeviceName); wprintf(L"\n DeviceString: '%s'", dd.DeviceString); wprintf(L"\n StateFlags: %s%s%s%s", ((dd.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) ? L"desktop " : L""), ((dd.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE ) ? L"primary " : L""), ((dd.StateFlags & DISPLAY_DEVICE_VGA_COMPATIBLE) ? L"vga " : L""), ((dd.StateFlags & DISPLAY_DEVICE_MULTI_DRIVER ) ? L"multi " : L""), ((dd.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER ) ? L"mirror " : L"")); // Get more info about the device DISPLAY_DEVICE dd2; ZeroMemory(&dd2, sizeof(dd2)); dd2.cb = sizeof(dd2); EnumDisplayDevices(dd.DeviceName, 0, &dd2, 0); wprintf(L"\n DeviceID: '%s'", dd2.DeviceID); wprintf(L"\n Monitor Name: '%s'", dd2.DeviceString); } printf("\n\n\nEnumDisplayMonitors\n\n\n"); EnumDisplayMonitors(NULL, NULL, MyInfoEnumProc, 0); numMonitor = GetSystemMetrics(SM_CMONITORS); printf("GetSystemMetrics: %d\n", numMonitor); Sleep(5000); } while(1); return 0; }
这段代码每5秒钟刷新一次显示器显示设备信息。输出结果如下:
*********************3*************** EnumDisplayDevices Device 0: DeviceName: '\\.\DISPLAY1' DeviceString: 'NVIDIA GeForce GTX 760' StateFlags: desktop primary DeviceID: 'MONITOR\ACI23F7\{4d36e96e-e325-11ce-bfc1-08002be10318}\0002' Monitor Name: 'Generic PnP Monitor' Device 1: DeviceName: '\\.\DISPLAY2' DeviceString: 'NVIDIA GeForce GTX 760' StateFlags: desktop DeviceID: 'MONITOR\ACI23F7\{4d36e96e-e325-11ce-bfc1-08002be10318}\0001' Monitor Name: 'Generic PnP Monitor' Device 2: DeviceName: '\\.\DISPLAY3' DeviceString: 'NVIDIA GeForce GTX 760' StateFlags: desktop DeviceID: 'MONITOR\GSM0001\{4d36e96e-e325-11ce-bfc1-08002be10318}\0003' Monitor Name: 'Generic PnP Monitor' Device 3: DeviceName: '\\.\DISPLAY4' DeviceString: 'NVIDIA GeForce GTX 760' StateFlags: DeviceID: '' Monitor Name: '' Device 4: DeviceName: '\\.\DISPLAYV1' DeviceString: 'RDPDD Chained DD' StateFlags: DeviceID: '' Monitor Name: '' Device 5: DeviceName: '\\.\DISPLAYV2' DeviceString: 'RDP Encoder Mirror Driver' StateFlags: DeviceID: '' Monitor Name: '' Device 6: DeviceName: '\\.\DISPLAYV3' DeviceString: 'RDP Reflector Display Driver' StateFlags: DeviceID: '' Monitor Name: '' EnumDisplayMonitors DisplayDevice: \\.\DISPLAY1 DisplayDevice: \\.\DISPLAY2 DisplayDevice: \\.\DISPLAY3 GetSystemMetrics: 3
可以看出EnumDisplayDevices()列出的都是显示设备。前四个都是我的显卡 GeforceGTX 760. 有4个设备因为我的显卡有4个接口。其中前三个接口接了显示器,所以下面显示了显示器信息。显示器名都是”通用即插即用显示器”这和windows设备管理器里显示的名字是一样的。唯一能有区分度的信息是DeviceID里MONITOR\后面的7个字符,这7个字符是和生产厂商信号相关的。ACI23F7代表的我的ASUS显示器,GSM001指的是LG显示器。值得注意的是Windows7显示属性里或控制面板硬件里能显示出显示器可识别的显示器型号和厂商,这个信息想通过编程方法获得是不可能的,这点已经在该网页留言里由微软工作人员验证了。原话是“Thereis
not a supported way to figour out the IDs that you referred toprogrammatically. It was never a design goal to provide a way for applicationsto label monitors with the same IDs that the screen resolution control paneluses.”
EnumDisplayMonitors()只会列出显示器信息。如上,显示的是
DisplayDevice: \\.\DISPLAY1
DisplayDevice: \\.\DISPLAY2
DisplayDevice: \\.\DISPLAY3
GetSystemMetrics (SM_CMONITORS) 只会得到显示器个数:3
值得一提的是,实验发现,当所有显示器都拔掉后,Nvidia会自己虚拟一个显示器NVD0000,所以没有显示器时,使用GetSystemMetrics(SM_CMONITORS)得到的显示器个数是1
如上函数只能轮询获得当前显示器信息,如何能检测显示器插拔呢?
有设备变化时Windows会发出WM_DEVICECHANGE的信息。但是默认情况下Windows发出WM_DEVICECHANGE有两个条件:
1. 程序必须有个主窗口
2. 得是端口和磁盘变化才行
要检测别的硬件插拔,或者该程序没有主窗口,则必须使用RegisterDeviceNotification() 函数注册所需监视的硬件。微软官方给了个使用该函数的范例:
https://msdn.microsoft.com/en-us/library/windows/desktop/aa363432(v=vs.85).aspx
该范例中,所需监测的设备类型是通过GUID的函数参数传递给系统的。
// This GUID is for all USB serial host PnP drivers, but you can replace it // with any valid device class guid. GUID WceusbshGUID = { 0x25dbce51, 0x6c8f, 0x4a72, 0x8a,0x6d,0xb5,0x4c,0x2b,0x4f,0xc8,0x35 };
需要注意的是改GUID必须是个设备接口GUID。有两种GUID,一个是设备类型GUID(device class GUID),另一个是设备接口GUID(device interfaceGUID)。设备类型GUID决定了在设备管理器里设备是哪一种类型。设备接口GUID是与设备与系统的接口相关的,这才是我们需要传递的参数。设备接口GUID可以在微软官方上查询得到。这里(https://msdn.microsoft.com/en-us/library/windows/hardware/ff545901(v=vs.85).aspx)查询到显示器的接口GUID是{E6F07B5F-EE97-4a90-B076-33F57BF4EAA7},替换至范例代码里就能检测到显示器插拔了。。。运行结果如下:
DBT_DEVICEREMOVECOMPLETE 代表硬件移除
DBT_DEVNODES_CHANGED 代表硬件变化,插入移除都会有该消息
DBT_DEVICEARRIVAL代表硬件插入
之后再在DBT_DEVICEARRIVAL信息后面查询DEV_BROADCAST_DEVICEINTERFACE结构体里的dbcc_name成员就可以得到新插入的显示器信息。
PDEV_BROADCAST_DEVICEINTERFACE b = (PDEV_BROADCAST_DEVICEINTERFACE) lParam; TCHAR strBuff[256]; TCHAR deviceID[8]; TCHAR *ptr; // Output some messages to the window. switch (wParam) { case DBT_DEVICEARRIVAL: msgCount++; StringCchPrintf( strBuff, 256, TEXT("Message %d: DBT_DEVICEARRIVAL\n"), msgCount); wcscat_s(strBuff, b->dbcc_name); wcscat_s(strBuff, TEXT("\n")); break; case DBT_DEVICEREMOVECOMPLETE: . . . .
运行结果如下
GSM0001代表新插入的是LG显示器。
参考:
http://www.codeproject.com/Articles/14500/Detecting-Hardware-Insertion-and-or-Removal
(转载请注明)