鱼C论坛

 找回密码
 立即注册
查看: 260|回复: 4

获取一个窗口的截图

[复制链接]
发表于 2024-5-12 22:46:32 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
我想要通过窗口句柄获取一个窗口的截图,这个窗口是一个外部程序的,只知道进程号,然后回显到当前正在运行的程序窗口上。
这些代码是我从https://learn.microsoft.com/en-us/windows/win32/gdi/capturing-an-image复制下来的,在147行GetDC(NULL)获取桌面窗口句柄,这样可以成功显示。
但是当我把GetDC()参数换成上面这种通过进程ID获取的窗口句柄时,显示都是黑屏的。我不知道是什么原因,求大佬解答
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
{
        EnumWindowsArg* pArg = (EnumWindowsArg*)lParam;
        DWORD  dwProcessID = 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[MAX_LOADSTRING];                  // The title bar text
WCHAR szWindowClass[MAX_LOADSTRING];            // 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;
}
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 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 小助理,如未能正确解答您的问题,请继续追问。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2024-5-12 22:50:23 | 显示全部楼层

回帖奖励 +100 鱼币

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2024-5-12 22:50:55 | 显示全部楼层
厉害啊
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 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等),以防止资源泄漏。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-9-8 10:22

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表