標籤:windows 顯示器
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***************EnumDisplayDevicesDevice 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: ''EnumDisplayMonitorsDisplayDevice: \\.\DISPLAY1DisplayDevice: \\.\DISPLAY2DisplayDevice: \\.\DISPLAY3GetSystemMetrics: 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
(轉載請註明)
Windows 下編程檢測顯示器資訊及插拔