loverinmydream 发表于 2024-5-12 22:46:32

获取一个窗口的截图

我想要通过窗口句柄获取一个窗口的截图,这个窗口是一个外部程序的,只知道进程号,然后回显到当前正在运行的程序窗口上。
这些代码是我从https://learn.microsoft.com/en-us/windows/win32/gdi/capturing-an-image复制下来的,在147行GetDC(NULL)获取桌面窗口句柄,这样可以成功显示。
但是当我把GetDC()参数换成上面这种通过进程ID获取的窗口句柄时,显示都是黑屏的。我不知道是什么原因,求大佬解答{:10_266:}BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
{
        EnumWindowsArg* pArg = (EnumWindowsArg*)lParam;
        DWORDdwProcessID = 0;
        // 通过窗口句柄取得进程ID
        ::GetWindowThreadProcessId(hwnd, &dwProcessID);
        if (dwProcessID == pArg->dwProcessID)
        {
                pArg->hwndWindow = hwnd;
                // 找到了返回FALSE
                return FALSE;
        }
        // 没找到,继续找,返回TRUE
        return TRUE;
}

///< 通过进程ID获取窗口句柄
HWND GetWindowHwndByPID(DWORD dwProcessID)
{
        HWND hwndRet = NULL;
        EnumWindowsArg ewa;
        ewa.dwProcessID = dwProcessID;
        ewa.hwndWindow = NULL;
        EnumWindows(EnumWindowsProc, (LPARAM)&ewa);
        if (ewa.hwndWindow)
        {
                hwndRet = ewa.hwndWindow;
        }
        return hwndRet;
}


// GDI_CapturingAnImage.cpp : Defines the entry point for the application.
//

#include "framework.h"
#include "GDI_CapturingAnImage.h"
#include "GetHWND.h"

#define MAX_LOADSTRING 100

// Global Variables:
HINSTANCE hInst;                              // current instance
WCHAR szTitle;                  // The title bar text
WCHAR szWindowClass;            // the main window class name

// Forward declarations of functions included in this code module:
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR    lpCmdLine,
_In_ int       nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);

// TODO: Place code here.

// Initialize global strings
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_GDICAPTURINGANIMAGE, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);

// Perform application initialization:
if (!InitInstance(hInstance, nCmdShow))
{
    return FALSE;
}

HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_GDICAPTURINGANIMAGE));

MSG msg;

// Main message loop:
while (GetMessage(&msg, nullptr, 0, 0))
{
    if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
    {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
}

return (int)msg.wParam;
}

//
//FUNCTION: MyRegisterClass()
//
//PURPOSE: Registers the window class.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEXW wcex;

wcex.cbSize = sizeof(WNDCLASSEX);

wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_GDICAPTURINGANIMAGE));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_GDICAPTURINGANIMAGE);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

return RegisterClassExW(&wcex);
}

//
//   FUNCTION: InitInstance(HINSTANCE, int)
//
//   PURPOSE: Saves instance handle and creates main window
//
//   COMMENTS:
//
//      In this function, we save the instance handle in a global variable and
//      create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // Store instance handle in our global variable

HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
    CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

if (!hWnd)
{
    return FALSE;
}

ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);

return TRUE;
}

// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved

//
//   FUNCTION: CaptureAnImage(HWND hWnd)
//
//   PURPOSE: Captures a screenshot into a window ,and then saves it in a .bmp file.
//
//   COMMENTS:
//
//      Note: This function attempts to create a file called captureqwsx.bmp
//      

int CaptureAnImage(HWND hWnd)
{
HDC hdcScreen;
HDC hdcWindow;
HDC hdcMemDC = NULL;
HBITMAP hbmScreen = NULL;
BITMAP bmpScreen;
DWORD dwBytesWritten = 0;
DWORD dwSizeofDIB = 0;
HANDLE hFile = NULL;
char* lpbitmap = NULL;
HANDLE hDIB = NULL;
DWORD dwBmpSize = 0;

// Retrieve the handle to a display device context for the client
// area of the window.
// HWND nhdcScreen = GetWindowHwndByPID(27380);
hdcScreen = GetDC(NULL); // source

hdcWindow = GetDC(hWnd);// destination

// Create a compatible DC, which is used in a BitBlt from the window DC.
hdcMemDC = CreateCompatibleDC(hdcWindow);

if (!hdcMemDC)
{
    MessageBox(hWnd, L"CreateCompatibleDC has failed", L"Failed", MB_OK);
    goto done;
}

// Get the client area for size calculation.
RECT rcClient;
GetClientRect(hWnd, &rcClient);

// This is the best stretch mode.
SetStretchBltMode(hdcWindow, HALFTONE);

// The source DC is the entire screen, and the destination DC is the current window (HWND).
if (!StretchBlt(hdcWindow,
    0, 0,
    rcClient.right, rcClient.bottom,
    hdcScreen,
    0, 0,
    GetSystemMetrics(SM_CXSCREEN),
    GetSystemMetrics(SM_CYSCREEN),
    SRCCOPY))
{
    MessageBox(hWnd, L"StretchBlt has failed", L"Failed", MB_OK);
    goto done;
}

// Create a compatible bitmap from the Window DC.
hbmScreen = CreateCompatibleBitmap(hdcWindow, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top);

if (!hbmScreen)
{
    MessageBox(hWnd, L"CreateCompatibleBitmap Failed", L"Failed", MB_OK);
    goto done;
}

// Select the compatible bitmap into the compatible memory DC.
SelectObject(hdcMemDC, hbmScreen);

// Bit block transfer into our compatible memory DC.
if (!BitBlt(hdcMemDC,
    0, 0,
    rcClient.right - rcClient.left, rcClient.bottom - rcClient.top,
    hdcWindow,
    0, 0,
    SRCCOPY))
{
    MessageBox(hWnd, L"BitBlt has failed", L"Failed", MB_OK);
    goto done;
}

// Get the BITMAP from the HBITMAP.
GetObject(hbmScreen, sizeof(BITMAP), &bmpScreen);

BITMAPFILEHEADER   bmfHeader;
BITMAPINFOHEADER   bi;

bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = bmpScreen.bmWidth;
bi.biHeight = bmpScreen.bmHeight;
bi.biPlanes = 1;
bi.biBitCount = 32;
bi.biCompression = BI_RGB;
bi.biSizeImage = 0;
bi.biXPelsPerMeter = 0;
bi.biYPelsPerMeter = 0;
bi.biClrUsed = 0;
bi.biClrImportant = 0;

dwBmpSize = ((bmpScreen.bmWidth * bi.biBitCount + 31) / 32) * 4 * bmpScreen.bmHeight;

// Starting with 32-bit Windows, GlobalAlloc and LocalAlloc are implemented as wrapper functions that
// call HeapAlloc using a handle to the process's default heap. Therefore, GlobalAlloc and LocalAlloc
// have greater overhead than HeapAlloc.
hDIB = GlobalAlloc(GHND, dwBmpSize);
lpbitmap = (char*)GlobalLock(hDIB);

// Gets the "bits" from the bitmap, and copies them into a buffer
// that's pointed to by lpbitmap.
GetDIBits(hdcWindow, hbmScreen, 0,
    (UINT)bmpScreen.bmHeight,
    lpbitmap,
    (BITMAPINFO*)&bi, DIB_RGB_COLORS);

// A file is created, this is where we will save the screen capture.
hFile = CreateFile(L"captureqwsx.bmp",
    GENERIC_WRITE,
    0,
    NULL,
    CREATE_ALWAYS,
    FILE_ATTRIBUTE_NORMAL, NULL);

// Add the size of the headers to the size of the bitmap to get the total file size.
dwSizeofDIB = dwBmpSize + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

// Offset to where the actual bitmap bits start.
bmfHeader.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER);

// Size of the file.
bmfHeader.bfSize = dwSizeofDIB;

// bfType must always be BM for Bitmaps.
bmfHeader.bfType = 0x4D42; // BM.

WriteFile(hFile, (LPSTR)&bmfHeader, sizeof(BITMAPFILEHEADER), &dwBytesWritten, NULL);
WriteFile(hFile, (LPSTR)&bi, sizeof(BITMAPINFOHEADER), &dwBytesWritten, NULL);
WriteFile(hFile, (LPSTR)lpbitmap, dwBmpSize, &dwBytesWritten, NULL);

// Unlock and Free the DIB from the heap.
GlobalUnlock(hDIB);
GlobalFree(hDIB);

// Close the handle for the file that was created.
CloseHandle(hFile);

// Clean up.
done:
DeleteObject(hbmScreen);
DeleteObject(hdcMemDC);
ReleaseDC(NULL, hdcScreen);
ReleaseDC(hWnd, hdcWindow);

return 0;
}

//
//FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//PURPOSE: Processes messages for the main window.
//
//WM_COMMAND- process the application menu
//WM_PAINT    - Paint the main window
//WM_DESTROY- post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_COMMAND:
{
    int wmId = LOWORD(wParam);
    // Parse the menu selections:
    switch (wmId)
    {
    case IDM_ABOUT:
      DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
      break;
    case IDM_EXIT:
      DestroyWindow(hWnd);
      break;
    default:
      return DefWindowProc(hWnd, message, wParam, lParam);
    }
}
break;
case WM_PAINT:
{
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hWnd, &ps);
    CaptureAnImage(hWnd);
    EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
    PostQuitMessage(0);
    break;
default:
    return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}

// Message handler for about box.
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
    return (INT_PTR)TRUE;

case WM_COMMAND:
    if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
    {
      EndDialog(hDlg, LOWORD(wParam));
      return (INT_PTR)TRUE;
    }
    break;
}
return (INT_PTR)FALSE;
}

FishC 发表于 2024-5-12 22:47:01

你遇到的问题是在将代码中的获取窗口句柄的函数`GetWindowHwndByPID`替换进程号获取窗口句柄代码时出现黑屏的情况。这是因为你使用的是外部程序的进程ID,而不是窗口句柄。

想要通过进程ID获取窗口句柄,你可以使用以下函数来实现:

HWND GetWindowHwndByPID(DWORD dwProcessID)
{
    HWND hwndRet = NULL;
    EnumWindows([&](HWND hWnd, LPARAM lParam) {
      DWORD pid = 0;
      GetWindowThreadProcessId(hWnd, &pid);
      if (pid == dwProcessID) {
            hwndRet = hWnd;
            return FALSE; // 找到了就返回FALSE停止枚举
      }
      return TRUE; // 没找到继续
    }, 0);

    return hwndRet;
}

请注意这里我们使用了C++11中的lambda表达式来实现回调函数,以便在回调函数内部使用局部变量。

然后,你需要在`WndProc`中进行截图并回显到当前窗口。你可以修改`WM_PAINT`消息的处理部分,如下所示:

case WM_PAINT:
{
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hWnd, &ps);
    HWND targetHwnd = GetWindowHwndByPID(外部程序的进程ID);
    if (targetHwnd != NULL) {
      CaptureAnImage(targetHwnd);
      // 将截图显示在当前窗口
      HBITMAP hBitmap = (HBITMAP)LoadImage(NULL, L"captureqwsx.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
      HDC hdcMem = CreateCompatibleDC(hdc);
      SelectObject(hdcMem, hBitmap);
      BITMAP bitmap;
      GetObject(hBitmap, sizeof(bitmap), &bitmap);
      BitBlt(hdc, 0, 0, bitmap.bmWidth, bitmap.bmHeight, hdcMem, 0, 0, SRCCOPY);
      DeleteDC(hdcMem);
      DeleteObject(hBitmap);
    }
    EndPaint(hWnd, &ps);
    break;
}

这样,当`WM_PAINT`消息被触发时,会获取外部程序窗口的截图,并将截图显示在当前程序窗口上。

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。

某一个“天” 发表于 2024-5-12 22:50:23

{:10_256:}

某一个“天” 发表于 2024-5-12 22:50:55

厉害啊

某一个“天” 发表于 2024-5-12 22:55:48

当你在使用 GetDC() 函数来捕获一个特定窗口的屏幕图像时,需要注意几个关键点。GetDC(NULL) 实际上获取的是整个屏幕的DC(设备上下文),而当你尝试对一个特定的窗口句柄调用 GetDC(hwnd) 时,你期望得到的是这个窗口的DC。

然而,仅仅使用 GetDC(hwnd) 可能不足以捕获一个窗口的内容,尤其是当窗口有自己的绘图逻辑(如使用DirectX、OpenGL或其他图形API)时。此外,如果窗口是透明的或者绘制在其上的内容是部分或全部透明的,你可能无法看到预期的内容。

下面是一些步骤和建议,帮助你成功捕获一个窗口的图像:

确保窗口存在并可见:
确保你捕获的窗口确实存在并且是可见的。你可以通过调用 IsWindowVisible() 来检查这一点。
使用 PrintWindow 函数:
PrintWindow 函数是一个更高级别的API,它专门用于捕获窗口的图像。这个函数会发送一个 WM_PRINT 消息到目标窗口,这通常会导致窗口按照打印机的方式渲染其内容。这通常比直接使用 GetDC 和位图操作更有效。
cpp
HWND hwnd = GetWindowHwndByPID(dwProcessID);
if (hwnd && IsWindowVisible(hwnd)) {
    RECT rc;
    GetClientRect(hwnd, &rc);
    HDC hdcScreen = GetDC(NULL);
    HBITMAP hBitmap = CreateCompatibleBitmap(hdcScreen, rc.right - rc.left, rc.bottom - rc.top);
    HDC hdcMem = CreateCompatibleDC(hdcScreen);
    SelectObject(hdcMem, hBitmap);
    PrintWindow(hwnd, hdcMem, PW_CLIENTONLY);
    // 现在 hBitmap 包含了你想要的窗口图像
    // ... 在这里将 hBitmap 绘制到另一个窗口或保存为文件 ...
    // 清理资源
    DeleteDC(hdcMem);
    DeleteObject(hBitmap);
    ReleaseDC(NULL, hdcScreen);
}
检查窗口的绘制逻辑:
如果 PrintWindow 仍然不能捕获你想要的图像,可能是因为窗口使用了特定的绘制逻辑,这些逻辑在接收到 WM_PRINT 消息时不会按预期工作。在这种情况下,你可能需要更深入地了解窗口的绘制机制,并考虑使用其他方法(如钩子、API劫持等)来捕获图像。
权限问题:
在某些情况下,尝试捕获其他进程的窗口图像可能会受到操作系统安全性的限制。确保你的应用程序有足够的权限来执行此操作。
错误处理:
确保你的代码中有适当的错误处理机制,以处理任何可能发生的失败情况(如窗口不存在、无法获取DC等)。
资源清理:
在捕获图像后,确保清理所有分配的资源(如HDC、HBITMAP等),以防止资源泄漏。
页: [1]
查看完整版本: 获取一个窗口的截图