非切换状态下CreateDesktop创建的桌面截图返回黑位图问题求助
嘿,这个跨桌面截图的问题我太熟了!你遇到的黑图问题本质是Windows的桌面渲染机制导致的——非当前活动的桌面(也就是你用CreateDesktop创建但没切换过去的那个),它上面的窗口其实并没有被GPU实时渲染,所以直接用BitBlt抓屏幕DC肯定只能拿到黑块。我给你几个靠谱的解决方案,不管用C++、C#还是Python都能搞定:
方案1:临时切换桌面(最直接但有视觉闪烁)
这个方法原理很简单:先切换到目标桌面,完成截图后切回原桌面。虽然会有短暂的桌面切换闪烁,但胜在逻辑简单,对所有窗口都有效。
C++示例代码:
// 假设hNewDesktop是你CreateDesktop得到的新桌面句柄 HDESK hOldDesktop = GetThreadDesktop(GetCurrentThreadId()); // 保存当前桌面 if (SwitchDesktop(hNewDesktop)) { // 执行截图逻辑(针对目标窗口的客户区) RECT rc; GetClientRect(hwnd, &rc); const int width = rc.right - rc.left; const int height = rc.bottom - rc.top; const HDC hScreenDC = GetDC(nullptr); const HDC hMemoryDC = CreateCompatibleDC(hScreenDC); const HBITMAP hBitmap = CreateCompatibleBitmap(hScreenDC, width, height); HGDIOBJ hOldBmp = SelectObject(hMemoryDC, hBitmap); BitBlt(hMemoryDC, 0, 0, width, height, hScreenDC, rc.left, rc.top, SRCCOPY); // 复制到剪贴板 OpenClipboard(nullptr); EmptyClipboard(); SetClipboardData(CF_BITMAP, hBitmap); CloseClipboard(); // 清理资源 SelectObject(hMemoryDC, hOldBmp); DeleteDC(hMemoryDC); DeleteDC(hScreenDC); SwitchDesktop(hOldDesktop); // 切回原桌面 }
方案2:用WM_PRINT让窗口自绘(推荐,无视觉干扰)
这是更优雅的方式:直接给目标窗口发送WM_PRINT消息,让窗口自己把内容绘制到内存DC里,完全不需要依赖桌面是否激活。大部分标准Windows窗口(比如WinRAR、Explorer)都支持这个消息。
C++示例代码:
RECT rc; GetClientRect(hwnd, &rc); int width = rc.right - rc.left; int height = rc.bottom - rc.top; HDC hWindowDC = GetWindowDC(hwnd); HDC hMemoryDC = CreateCompatibleDC(hWindowDC); HBITMAP hBitmap = CreateCompatibleBitmap(hWindowDC, width, height); HGDIOBJ hOldBitmap = SelectObject(hMemoryDC, hBitmap); // 发送WM_PRINT消息,让窗口绘制自身(包含客户区、子窗口、非客户区) SendMessage(hwnd, WM_PRINT, (WPARAM)hMemoryDC, PRF_CLIENT | PRF_CHILDREN | PRF_NONCLIENT); // 复制到剪贴板 OpenClipboard(nullptr); EmptyClipboard(); SetClipboardData(CF_BITMAP, hBitmap); CloseClipboard(); // 清理资源 SelectObject(hMemoryDC, hOldBitmap); DeleteObject(hBitmap); DeleteDC(hMemoryDC); ReleaseDC(hwnd, hWindowDC);
C#示例代码:
using System; using System.Drawing; using System.Windows.Forms; using System.Runtime.InteropServices; public class WindowCapture { [DllImport("user32.dll")] private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, uint lParam); [DllImport("user32.dll")] private static extern IntPtr GetWindowDC(IntPtr hWnd); [DllImport("user32.dll")] private static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC); private const uint WM_PRINT = 0x0317; private const uint PRF_CLIENT = 0x00000004; private const uint PRF_CHILDREN = 0x00000010; private const uint PRF_NONCLIENT = 0x00000002; public static Bitmap CaptureWindow(IntPtr hwnd) { Rectangle rect = Control.FromHandle(hwnd).ClientRectangle; Bitmap bmp = new Bitmap(rect.Width, rect.Height); using (Graphics g = Graphics.FromImage(bmp)) { IntPtr hdc = g.GetHdc(); SendMessage(hwnd, WM_PRINT, hdc, PRF_CLIENT | PRF_CHILDREN | PRF_NONCLIENT); g.ReleaseHdc(hdc); } // 复制到剪贴板 Clipboard.SetImage(bmp); return bmp; } }
方案3:DWM合成表面抓取(适合Vista及以上系统)
如果你的系统开启了DWM(桌面窗口管理器),可以直接从DWM的合成表面中获取窗口画面,即使窗口在非活动桌面也能拿到。这个方法实现稍复杂,但没有闪烁,也不依赖窗口是否支持WM_PRINT。
C++核心逻辑:
#include <dwmapi.h> #pragma comment(lib, "dwmapi.lib") // 假设已经初始化了Direct3D设备 IDirect3DSurface9* pSurface = nullptr; BOOL dwmEnabled = FALSE; DwmGetWindowAttribute(hwnd, DWMWA_DWM_RENDERING_ENABLED, &dwmEnabled, sizeof(dwmEnabled)); if (dwmEnabled) { HRESULT hr = DwmGetWindowAttribute(hwnd, DWMWA_SOURCE_BITS, &pSurface, sizeof(pSurface)); if (SUCCEEDED(hr)) { // 将Direct3D表面复制到GDI位图 HDC hSurfaceDC = nullptr; pSurface->GetDC(&hSurfaceDC); BitBlt(hMemoryDC, 0, 0, width, height, hSurfaceDC, 0, 0, SRCCOPY); pSurface->ReleaseDC(hSurfaceDC); pSurface->Release(); } }
注意:需要提前初始化Direct3D设备,具体实现可以参考MSDN的DWM相关文档。
Python版本实现(快速验证)
用pywin32库快速实现WM_PRINT方案,适合快速测试:
import win32gui import win32api import win32con import win32ui def capture_window_to_clipboard(hwnd): # 获取窗口客户区尺寸 left, top, right, bottom = win32gui.GetClientRect(hwnd) width = right - left height = bottom - top # 创建设备上下文 hdc_window = win32gui.GetWindowDC(hwnd) hdc_memory = win32ui.CreateDCFromHandle(hdc_window) bitmap = win32ui.CreateBitmap() bitmap.CreateCompatibleBitmap(hdc_memory, width, height) hdc_memory.SelectObject(bitmap) # 发送WM_PRINT消息让窗口自绘 win32gui.SendMessage( hwnd, win32con.WM_PRINT, hdc_memory.GetSafeHdc(), win32con.PRF_CLIENT | win32con.PRF_CHILDREN | win32con.PRF_NONCLIENT ) # 复制到剪贴板 win32api.OpenClipboard() win32api.EmptyClipboard() win32api.SetClipboardData(win32con.CF_BITMAP, bitmap.GetHandle()) win32api.CloseClipboard() # 清理资源 hdc_memory.DeleteDC() win32gui.ReleaseDC(hwnd, hdc_window) bitmap.DeleteObject() # 使用示例:传入EnumDesktopWindow获取的窗口句柄 # capture_window_to_clipboard(target_hwnd)
总结建议
- 优先尝试方案2(WM_PRINT):无视觉干扰,代码简单,对绝大多数标准窗口有效;
- 如果窗口不支持WM_PRINT(比如某些游戏或DirectX程序),再用方案1(临时切换桌面);
- 追求极致体验且系统支持DWM的话,可以尝试方案3。
内容的提问来源于stack exchange,提问作者0_o
相关产品推荐
相关产品推荐

