1. 程式人生 > >Windows 下程式設計檢測顯示器資訊及插拔

Windows 下程式設計檢測顯示器資訊及插拔

Windows下提示顯示器資訊主要通過兩個函式實現。一個是EnumDisplayDevices(), 另一個是EnumDisplayMonitors(). EnumDisplayDevices()列舉所有顯示裝置,而EnumDisplayMonitors列舉的是所有顯示器。顯示裝置和顯示器不一樣,比如顯示卡算顯示裝置,但是不是顯示器。具體差別後面會分析。EnumDisplayMonitors()還會枚舉出不可見的偽顯示器,如果只是想得到實際的顯示器數目的話可以用GetSystemMetrics(SM_CMONITORS), 該函式不包括虛擬顯示器。

// 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() 函式註冊所需監視的硬體。微軟官方給了個使用該函式的範例:

該範例中,所需監測的裝置型別是通過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顯示器。

參考:


(轉載請註明)