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***************

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

(转载请注明)

时间: 2024-10-23 22:11:01

Windows 下编程检测显示器信息及插拔的相关文章

Windows下编程2----- C语言常用函数举例

几个小函数 1.????//MessageBoxA(0,"网络故障,重新登录","qq error",3); //弹出对话框 2.????//ShellExecuteA(0,"open","notepad",0,0,6);????//执行指令 notepad可以指定网址 ? 3.????//malloc(100000);//吃内存,铲食 ????//Sleep(100); 4.获取当前时间并打印 方法一: ????SYSTEM

windows下脚本检测tomcat是否启动,没有启动则启动

最近有个服务需要部署到windows server2003上面,机房没有windows ser的机器,没办法搞了个阿里云服务,购买的配置比较低, 不知道什么原因,tomcat启动后总是容易自动退出,搞了个脚本检测tomcat是否启动状态,没有启动则启动.然后放入定时任务每隔5分钟跑一次, 脚本如下: @echo offtitle Tomcat重啟脚本 ping -n 6 127.1 >nulwmic process where name="java.exe" get proces

windows下的socket网络编程(入门级)

windows下的socket网络编程 clinet.c 客户端 server.c 服务器端 UDP通信的实现 代码如下 已经很久没有在windows下编程了,这次因为需要做一个跨平台的网络程序,就先写了个简单的winSocket网路通信的例子,以便以后用到的时候有个参考. windows下使用winsock编程与linux/unix的区别在于windows下需要先有一个初始化的操作,结束的时候需要一个清理的操作.还有windows下编译的时候需要连接ws32_lib库. 大致过程如下 1.初始

windows下的socket网络编程

windows下的socket网络编程 windows下的socket网络编程 clinet.c 客户端 server.c 服务器端 UDP通信的实现 代码如下 已经很久没有在windows下编程了,这次因为需要做一个跨平台的网络程序,就先写了个简单的winSocket网路通信的例子,以便以后用到的时候有个参考. windows下使用winsock编程与linux/unix的区别在于windows下需要先有一个初始化的操作,结束的时候需要一个清理的操作.还有windows下编译的时候需要连接ws

【cocos2d-x学习笔记】Windows下创建项目&amp;Linux环境搭建&amp;安卓环境搭建

一.windows下创建新项目 打开CMD将路径设置到cocos软件下的tools下的project-creator下,执行命令:create_project.py -project HelloWorld -package com.zsc.HelloWorld -language cpp 二.Linux环境搭建(QT配置) 1. 安装linux系统,ubuntu 14.04 64位 2. 安装支持软件 第1步:sudo apt-get update(运行此命令) 第2步:sudo apt-get

Windows游戏编程之从零开始d

I'm back~~恩,几个月不见,大家还好吗? 这段时间真的好多童鞋在博客里留言说或者发邮件说浅墨你回来继续更新博客吧. woxiangnifrr童鞋说每天都在来浅墨的博客逛一下看有没有更新,"每天都来就像看女神那般不依不舍",弄得我再不更新都不好意思了,哈哈~怎么说呢,前段时间忙毕设,回国,暑假,间隔年旅行休整,然后是适应新的生活,各种事情,也真正没有心境来更新博客了,最近正好心境安定下来,就继续开始写博.额,关于思想汇报改天我专门写一篇文章和大家交流交流,现在先打住说正事吧~ 首

初探WINDOWS下IME编程

初探WINDOWS下IME编程作者:广东南海市昭信科技有限公司-李建国 大家知道,DELPHI许多控件有IME属性.这么好用的东西VC可没自带,怎么办呢?其实,可通过注册表,用API实现.下面说一下本人对IME的研究结果,并提供示例工程供大家参考: 下载示例工程 10.6K 本文示例程序运行结果如上图1.将用到的API RegOpenKey:打开注册表一键RegQueryValue:查询一键值RegQueryValueEx:同上RegCloseKey:关闭打开的键 LoadKeyboardLay

Windows下多线程编程(一)

前言 熟练掌握Windows下的多线程编程,能够让我们编写出更规范多线程代码,避免不要的异常.Windows下的多线程编程非常复杂,但是了解一些常用的特性,已经能够满足我们普通多线程对性能及其他要求. 进程与线程 1. 进程的概念 进程就是正在运行的程序.主要包括两部分: • 一个是操作系统用来管理进程的内核对象.内核对象也是系统用来存放关于进程的统计信息的地方. • 另一个是地址空间,它包含所有可执行模块或 D L L模块的代码和数据.它还包含动态内 2. 线程的概念 线程就是描述进程的一条执

Windows下Hadoop编程环境配置指南

刘勇    Email: [email protected] 本博客记录作者在工作与研究中所经历的点滴,一方面给自己的工作与生活留下印记,另一方面若是能对大家有所帮助,则幸甚至哉矣! 简介 鉴于最近在研究Hadoop编程时,为考虑编程的方便,在Windows本地编译源程序,然后直接访问Hadoop集群,这样给广大编程人员提供了极大的便利.在这个过程中积累了一些实际经验,并针对在该过程中(初级阶段)可能会遇到的问题,提供一些解决方案,希望对大家有所帮助. 环境介绍 Hadoop 集群:hadoop