使用 WinApi 使用 C++ 实现多显示器屏幕截图,仅显示 2 个显示器

2024-04-17

我有一个使用 WinApi 和 C++ 在 Windows 平台上截取屏幕截图的功能。它与一台和两台显示器完美配合,但当我在具有 3 个或更多显示器的计算机上运行它时,它只拍摄两个显示器的照片。

我认为我的问题是“主”监视器左侧的监视器内容被切断。可悲的是我不知道如何解决它或我做错了什么。

我读过有关 BitBlt 和 StretchBlt 的内容,所以我尝试了它们,但没有成功。

这就是我正在做的:

// Get the system metrics
const int width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
const int height = GetSystemMetrics(SM_CYVIRTUALSCREEN);

所有显示器的宽度和高度都得到完美计算。

// Create a normal DC and a memory DC for the entire screen. The normal DC provides a "snapshot" of the screen contents.
// The memory DC keeps a copy of this "snapshot" in the associated bitmap.
const HDC hdcScr = CreateDCW(TEXT("DISPLAY"), NULL, NULL, NULL);
const HDC hdcMem = CreateCompatibleDC(hdcScr);



if (!StretchBlt(hdcMem, 0, 0, width, height, hdcScr, 0, 0, width, height, SRCCOPY)) {
    return;
}


// Create a compatible bitmap for hdcScreen.
const HBITMAP hbmScr = CreateCompatibleBitmap(hdcScr, width, height);
if (hbmScr == 0) {
    return;
}

if (!BitBlt(hdcMem, 0, 0, width, height, hdcScr, 0, 0, SRCCOPY)) {
    return;
}



// Select the bitmaps into the compatible DC.
if (!SelectObject(hdcMem, hbmScr)) {
    DeleteDC(hdcScr);
    DeleteDC(hdcMem);
    DeleteObject(hbmScr);
    return;
}

// Copy color data for the entire display into a bitmap that is selected into a compatible DC.
if (!StretchBlt(hdcMem, 0, 0, width, height, hdcScr, 0, 0, width, height, SRCCOPY)) {
    DeleteDC(hdcScr);
    DeleteDC(hdcMem);
    DeleteObject(hbmScr);
    return;
}

BITMAP bmp;

// Retrieve the bitmap's color format, width, and height.
if (!GetObject(hbmScr, sizeof(BITMAP), reinterpret_cast<LPSTR>(&bmp))) {
    DeleteDC(hdcScr);
    DeleteDC(hdcMem);
    DeleteObject(hbmScr);
    return;
}


// Convert the color format to a count of bits.
unsigned short cClrBits = bmp.bmPlanes * bmp.bmBitsPixel;
if (cClrBits == 1) {
    cClrBits = 1;
}
else if (cClrBits <= 4) {
    cClrBits = 4;
}
else if (cClrBits <= 8) {
    cClrBits = 8;
}
else if (cClrBits <= 16) {
    cClrBits = 16;
}
else if (cClrBits <= 24) {
    cClrBits = 24;
}
else {
    cClrBits = 32;
}


PBITMAPINFO pbmi;

// Allocate memory for the BITMAPINFO structure. (This structure contains a BITMAPINFOHEADER structure and an array of RGBQUAD data structures.)
if (cClrBits != 24) {
    pbmi = static_cast<PBITMAPINFO>(LocalAlloc(LPTR, sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * (1 << cClrBits)));
}
else { // There is no RGBQUAD array for the 24-bit-per-pixel format.
    pbmi = static_cast<PBITMAPINFO>(LocalAlloc(LPTR, sizeof(BITMAPINFOHEADER)));
}

// Initialize the fields in the BITMAPINFO structure.
pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
pbmi->bmiHeader.biWidth = bmp.bmWidth;
pbmi->bmiHeader.biHeight = bmp.bmHeight;
pbmi->bmiHeader.biPlanes = bmp.bmPlanes;
pbmi->bmiHeader.biBitCount = bmp.bmBitsPixel;

if (cClrBits < 24) {
    pbmi->bmiHeader.biClrUsed = (1 << cClrBits);
}

// If the bitmap is not compressed, set the BI_RGB flag.
pbmi->bmiHeader.biCompression = BI_RGB;

// Compute the number of bytes in the array of color indices and store the result in biSizeImage.
pbmi->bmiHeader.biSizeImage = (pbmi->bmiHeader.biWidth + 7) / 8 * pbmi->bmiHeader.biHeight * cClrBits;

// Set biClrImportant to 0, indicating that all of the device colors are important.
pbmi->bmiHeader.biClrImportant = 0;


const PBITMAPINFOHEADER pbih = reinterpret_cast<PBITMAPINFOHEADER>(pbmi);              // bitmap info-header
const LPBYTE lpBits = static_cast<LPBYTE>(GlobalAlloc(GMEM_FIXED, pbih->biSizeImage)); // memory pointer

if (!lpBits) {
    DeleteDC(hdcScr);
    DeleteDC(hdcMem);
    LocalFree(pbmi);
    GlobalFree(lpBits);
    DeleteObject(hbmScr);
    return;
}

// Retrieve the color table (RGBQUAD array) and the bits (array of palette indices) from the DIB.
if (!GetDIBits(hdcMem, hbmScr, 0, pbih->biHeight, lpBits, pbmi, DIB_RGB_COLORS)) {
    DeleteDC(hdcScr);
    DeleteDC(hdcMem);
    LocalFree(pbmi);
    GlobalFree(lpBits);
    DeleteObject(hbmScr);
    return;
}

BITMAPFILEHEADER hdr; // bitmap file-header

hdr.bfType = 0x4d42; // ('M' << 8) + 'B';

// Calculate the size of the entire file.
hdr.bfSize = sizeof(BITMAPFILEHEADER) + pbih->biSize + pbih->biClrUsed * sizeof(RGBQUAD) + pbih->biSizeImage;
hdr.bfReserved1 = NULL;
hdr.bfReserved2 = NULL;

// Calculate the offset to the array of color indices.
hdr.bfOffBits = sizeof(BITMAPFILEHEADER) + pbih->biSize + pbih->biClrUsed * sizeof(RGBQUAD);


const DWORD cb = pbih->biSizeImage; // incremental count of bytes


std::stringstream outputBitmap;

// Write the BITMAPFILEHEADER into the .BMP file.
outputBitmap.write( reinterpret_cast<LPSTR>(&hdr), sizeof(BITMAPFILEHEADER));

// Write the BITMAPINFOHEADER and RGBQUAD array into the file.
outputBitmap.write(reinterpret_cast<LPSTR>(pbih), sizeof(BITMAPINFOHEADER) + pbih->biClrUsed * sizeof(RGBQUAD));

// Write the array of color indices
outputBitmap.write(reinterpret_cast<LPSTR>(lpBits), cb);




// To test the whole thing
std::ofstream out("test.bmp", std::ios::out | std::ios::binary);
out << outputBitmap.str();
out.close();


// Cleanup
DeleteDC(hdcScr);
DeleteDC(hdcMem);
LocalFree(pbmi);
GlobalFree(lpBits);
DeleteObject(hbmScr);

Monitors can have negative coordinates, so it's dangerous to assume that (0, 0) is the monitor's top left corner. The real origin (x, y) is given by the system metrics SM_XVIRTUALSCREEN and SM_YVIRTUALSCREEN. Then all of your BLTs have to be updated to refer to the correct source location. This makes the first bit of the function:

int x = GetSystemMetrics(SM_XVIRTUALSCREEN);
int y = GetSystemMetrics(SM_YVIRTUALSCREEN);
int w = GetSystemMetrics(SM_CXVIRTUALSCREEN);
int h = GetSystemMetrics(SM_CYVIRTUALSCREEN);

BOOL ok = StretchBlt(hdcMem, 0, 0, w, h, hdcScr, x, y, w, h, SRCCOPY);
// And So On...

或者,由于此类问题被问得很多,您可以使用类似于以下一段不完全生产质量的代码来获取各个监视器:

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <iostream>
using std::wcout;
using std::endl;

typedef struct tagMonData
{
    int current;
    MONITORINFOEXW* info;
} MonData;

BOOL EnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData)
{
    MonData* data = (MonData*)dwData;
    data->info[data->current].cbSize = sizeof(MONITORINFOEXW);
    return GetMonitorInfoW(hMonitor, &(data->info[data->current++]));
}

BOOL GetAllMonitorInfo(MonData* data)
{
    return EnumDisplayMonitors(NULL, NULL, (MONITORENUMPROC)(&EnumProc), (LPARAM)(data));
}

int main()
{
    int cMonitors = GetSystemMetrics(SM_CMONITORS);
    MonData data;
    data.current = 0;
    data.info = (MONITORINFOEXW*)calloc(cMonitors, sizeof(MONITORINFOEXW));

    if (!GetAllMonitorInfo(&data)) return 1;

    for (int i = 0; i < cMonitors; i++)
    {
        wcout << data.info[i].szDevice << "X: " << data.info[i].rcMonitor.left << " Y: " << data.info[i].rcMonitor.top << endl;
    }

    free(data.info);
    return 0;
}

请注意,如果GetMonitorInfoW由于某种原因呼叫失败。


Raymond Chen has written several articles about the complexities of the Windows coordinate system over at The Old New Thing https://blogs.msdn.microsoft.com/oldnewthing/

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 WinApi 使用 C++ 实现多显示器屏幕截图,仅显示 2 个显示器 的相关文章

随机推荐

  • C#/WPF:如何单独显示 ListView 的最后一行?

    我有一个 ListView 其中包含大约 10 个 GridViewColumn 和大约 100 行 行 我想在 ListView 的底部显示 总计 或摘要行 有谁知道如何做到这一点 保持 ColumnWidth 等像其他一样并将其作为一个
  • 在 UITextview 中加载巨大的文本文件会崩溃

    我想更新 UITextView 中的一个巨大的文本文件 但设备有时会挂起或崩溃 文本文件大小为 4MB UITextView 是从 Interface Builder 添加的 我正在从文档目录加载文件 以下是加载文本文件的代码 NSErro
  • 如何从函数返回值 - React Native

    如何从反应本机函数返回布尔值 它可以这样完成 export function isJson str try JSON parse str catch e return false return true 该函数检查提供的值是否有效JSON
  • 检查标准输入缓冲区是否为空

    我正在尝试用字符读取数字字符 但我不知道标准输入缓冲区是否为空 我的第一个解决方案是寻找 n标准输入缓冲区中的字符 但是如果我要输入由分隔符分隔的多个数字 这就没用了 我如何知道标准输入缓冲区中是否有字符 我需要用 C 语言来完成它并且是可
  • 编译后的第一次执行非常慢,除非“明显”所有循环都会停止

    我这个标题的意思是 在某些情况下 构建整个程序后 它的第一次执行将需要大约 25 秒才能开始 直到第一个 printf 在控制台上显示 接下来的执行几乎立即开始 正如它们应该的那样 添加 删除一个空格并再次编译 之后的第一次执行再次变得极其
  • 如何在同一图上显示条形图和折线图

    我无法在同一绘图上显示条形图和折线图 示例代码 import pandas as pd import numpy as np import matplotlib pyplot as plt Df pd DataFrame data np r
  • Python-Selenium 查找不可点击的可点击元素

    我在用python selenium运行自动化测试 在复杂的非公共环境中运行这些测试时 我发现了一些我将标记为 selenium 中的错误的东西 基本上我想做的是在 DOM 中找到一些元素 当它变得可点击时 然后点击它 代码如下 what
  • 处理程序如何/何时被垃圾收集?

    在我的一个类中 我有以下代码 mHandler createHandler private Handler createHandler return new Handler public void handleMessage Message
  • 声明按位运算的掩码

    我是这样的低级操作的新手 我希望有人能指出我在这里犯的明显错误 Input value 00111100 I want to get the value of the bits at indexes 1 3 i e 0111 byte ma
  • SqlConnection 并避免升级到 MSDTC

    当我们需要在应用程序中进行数据库访问时 我们使用以下模式 为了查询 我们有一个带有方法的静态工厂类CreateOpenConnection其作用无非是new SqlConnection myConnectionString 并打电话Open
  • 在 Android 中使用自定义字体

    我想为我正在创建的 Android 应用程序使用自定义字体 我可以从代码中单独更改每个对象的字体 但我有数百个对象 So 有没有办法从 XML 中做到这一点 设置自定义字体 有没有一种方法可以从一个地方的代码中做到这一点 即整个应用程序和所
  • Excel VBA MySql 参数化更新“无效参数类型”

    我正在创建一个界面 用户可以在其中使用 Excel 无缝更改 SQL 数据库 我可以很好地检索数据 但是在更新记录时我得到 无效的参数类型 只需将值连接到查询中就可以正常工作 但是为了防止 SQL 注入 我需要参数化查询 我尝试用该值替换
  • 对于具有单个 Google 帐户的设备,Google 登录不显示帐户选择器

    我正在尝试为 Android 应用程序实现 google plus 登录 我按照谷歌开发者页面上的指南进行操作 https developers google com mobile android getting started https
  • Sublime Text 2 中的“关闭其他”命令快捷方式

    我正在尝试添加 关闭其他 选项卡的快捷方式 但似乎找不到该命令 这就是我正在尝试的 keys super alt w command close others Cmd Option W sort of like Cmd Option H i
  • SOLR 中的子字符串匹配

    我似乎无法弄清楚如何使用 SOLR 查找子字符串匹配 我已经根据前缀找出了匹配 这样我就可以让火腿与汉堡包匹配 我如何搜索 汉堡 来匹配汉堡包 我试过burger但这引发了错误 或 不允许作为 WildcardQuery 中的第一个字符 如
  • python - Atom IDE 如何启用自动完成代码以查看模块中的所有函数

    我正在为我的 python 项目使用atom IDE 在某些情况下有自动完成建议 但我想知道是否可以列出导入模块具有的所有可能功能 例如 如果我导入import urllib当我打字时urlib 并按 ctrl tab 想要查看包含可能使用
  • 如何在 C# 中声明大整数

    下面的代码 C 中 是我尝试转换为 C 的代码 DWORD Func X 4 DWORD arg1 DWORD arg2 DWORD arg3 LARGE INTEGER result 1 0 LARGE INTEGER temp1 0 L
  • 通过 HTTP Post-Commit Hook 将 Github 连接到安全的 Jenkins

    我已经在我的测试服务器上使用 Github 插件设置了 Jenkins 我通过仅允许经过身份验证的用户 匿名用户没有任何权限 和安全连接来保护 Jenkins 不幸的是 Github 提供的提交后挂钩似乎不适用于我的情况 我尝试访问以下网址
  • 在 Jersey 中是否可以访问注入的 HttpServletRequest,而不是代理

    注射时HttpServletRequest在 Jersey JAX RS 资源中 注入的值是代理 例如 Path myResource class MyResource Inject HttpServletRequest request 会
  • 使用 WinApi 使用 C++ 实现多显示器屏幕截图,仅显示 2 个显示器

    我有一个使用 WinApi 和 C 在 Windows 平台上截取屏幕截图的功能 它与一台和两台显示器完美配合 但当我在具有 3 个或更多显示器的计算机上运行它时 它只拍摄两个显示器的照片 我认为我的问题是 主 监视器左侧的监视器内容被切断