首页 > 解决方案 > 无法通过winapi获取窗口截图

问题描述

使用Node.js ffi-napi 包,如果没有提供窗口句柄,我试图从给定窗口或桌面的屏幕截图中获取位图数据的缓冲区。为此,我正在尝试从microsoft 文档中移植 c++ 示例,并通过 ffi-napi 进行 api 调用。

即使所有的 api 调用都没有错误地返回,我最终只会得到一个填充 0 的缓冲区。我已经将它追溯到可能导致它的几个潜在部分,但我不知道哪个部分不正确以及如何修复它。

我的猜测是 BitBlt 实际上并没有将桌面 dc 复制到内存 dc,即使它返回 true,我也不知道为什么会这样。我也尝试过运行计算器应用程序并将 dc 传递给屏幕截图函数,即使它返回了适当的项目,我得到的缓冲区仍然全为 0。

任何帮助将不胜感激!

这是我的 node.js 代码

function screenshot(hWnd = null) {
    let hdcWindow = null;
    let hdcMemDC = null;
    let hbmScreen = null;
    let hDIB = null;

    try {
        if (!hWnd) hWnd = user32.GetDesktopWindow();
        console.log('hWnd', hWnd);

        // Retrieve the handle to a display device context for the client area of the window.
        hdcWindow = user32.GetDC(hWnd);
        console.log('hdcWindow', hdcWindow);
        const rcClient = new win32_structs.RECT();
        user32.GetClientRect(hWnd, rcClient);
        console.log('rcClient', rcClient);

        // Create a compatible DC and bitmap
        hdcMemDC = gdi32.CreateCompatibleDC(hdcWindow);
        console.log('hdcMemDC', hdcMemDC);
        hbmScreen = gdi32.CreateCompatibleBitmap(hdcWindow, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top);
        console.log('hbmScreen', hbmScreen);
        const hPrevDC = gdi32.SelectObject(hdcMemDC, hbmScreen);
        console.log('hPrevDC', hPrevDC);

        // Bit block transfer into our compatible memory DC.
        const bitBltRes = gdi32.BitBlt(hdcMemDC, 0, 0, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top, hdcWindow, 0, 0, apiConstants.SRCCOPY);
        const pixelWnd = gdi32.GetPixel(hdcWindow, 0, 0);
        const pixelMem = gdi32.GetPixel(hdcMemDC, 0, 0);
        console.log('pixelWnd', pixelWnd);
        console.log('pixelMem', pixelMem);
        console.log('bitBltRes', bitBltRes);

        // Get the BITMAP from the HBITMAP
        const bmpScreen = new win32_structs.BITMAP();
        const getObjectRes = gdi32.GetObjectA(hbmScreen, bmpScreen.length, bmpScreen);
        console.log('getObjectRes', getObjectRes);
        console.log('bmpScreen.length', bmpScreen.length);
        console.log('bmpScreen', bmpScreen);

        const bi = new win32_structs.BITMAPINFOHEADER();
        bi.biSize = bi.length;
        bi.biWidth = bmpScreen.bmWidth;
        bi.biHeight = bmpScreen.bmHeight;
        bi.biPlanes = 1;
        bi.biBitCount = 32;
        bi.biCompression = apiConstants.BI_RGB;
        bi.biSizeImage = 0;
        bi.biXPelsPerMeter = 0;
        bi.biYPelsPerMeter = 0;
        bi.biClrUsed = 0;
        bi.biClrImportant = 0;
        console.log('bi', bi);

        const dwBmpSize = ((bmpScreen.bmWidth * bi.biBitCount + 31) / 32) * 4 * bmpScreen.bmHeight;
        console.log('dwBmpSize', dwBmpSize);

        // 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 = kernel32.GlobalAlloc(apiConstants.GHND, dwBmpSize);
        // const lpBitmap = kernel32.GlobalLock(hDIB);
        const lpBitmap = new Buffer.alloc(dwBmpSize);

        // Gets the "bits" from the bitmap and copies them into buffer lpbitmap
        const getDIBitsRes = gdi32.GetDIBits(hdcWindow, hbmScreen, 0, bmpScreen.bmHeight, lpBitmap, bi, apiConstants.DIB_RGB_COLORS);
        console.log('getDIBitsRes', getDIBitsRes);
        console.log('lpBitmap', lpBitmap);

        for (const c of lpBitmap) {
            if (c > 0) {
                console.log(c);
                break;
            }
        }

        // clean up
        if (hDIB != null) {
            kernel32.GlobalUnlock(hDIB);
            kernel32.GlobalFree(hDIB);
        }

        if (hbmScreen != null) gdi32.DeleteObject(hbmScreen);
        if (hdcMemDC != null) gdi32.DeleteObject(hdcMemDC);
        if (hdcWindow != null) user32.ReleaseDC(hWnd, hdcWindow);


        return lpBitmap;

    } catch (err) {
        // clean up memory on errors
        if (hDIB != null) {
            kernel32.GlobalUnlock(hDIB);
            kernel32.GlobalFree(hDIB);
        }

        if (hbmScreen != null) gdi32.DeleteObject(hbmScreen);
        if (hdcMemDC != null) gdi32.DeleteObject(hdcMemDC);
        if (hdcWindow != null) user32.ReleaseDC(hWnd, hdcWindow);

        throw err;
    }
}

这是我的控制台日志:

hWnd 65552
hdcWindow 83954845

rcClient <Buffer@0x000001BE188E8670 00 00 00 00 00 00 00 00 80 07 00 00 38 04 00 00, _structProps: { left: { offset: 0, dataType: 'long' }, top: { offset: 4, dataType: 'long' }, right: { offset: 8, dataType: 'long' 
    }, bottom: { offset: 12, dataType: 'long' } }, left: 0, top: 0, right: 1920, bottom: 1080>
    
hdcMemDC 1375804638
hbmScreen 990189696
hPrevDC 8716303
pixelWnd { r: 231, g: 234, b: 237 }
pixelMem { r: 0, g: 0, b: 0 }
bitBltRes true
getObjectRes 32
bmpScreen.length 32

bmpScreen <Buffer@0x000001BE188C5CC0 00 00 00 00 80 07 00 00 38 04 00 00 00 1e 00 00 01 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00, 
    _structProps: { bmType: { offset: 0, dataType: 'long' }, bmWidth: { offset: 4, dataType: 'long' }, bmHeight: { offset: 8, dataType: 'long' }, bmWidthBytes: { offset: 12, dataType: 'long' }, bmPlanes: { offset: 16, dataType: 'uint' }, bmBitsPixel: { offset: 18, dataType: 'uint' }, pointerPadding: { offset: 20, dataType: 'long' }, bmBits: { offset: 24, dataType: 'ulonglong' } }, 
     bmType: 0, bmWidth: 1920, bmHeight: 1080, bmWidthBytes: 7680, bmPlanes: 1, bmBitsPixel: 32, pointerPadding: 0, bmBits: 0n>
     
bi <Buffer@0x000001BE188C5540 28 00 00 00 80 07 00 00 38 04 00 00 01 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00, 
    _structProps: { biSize: { offset: 0, dataType: 'ulong' }, biWidth: { offset: 4, dataType: 'long' }, biHeight: { offset: 8, dataType: 'long' }, biPlanes: { offset: 12, dataType: 'uint' }, biBitCount: { offset: 14, dataType: 'uint' }, biCompression: { offset: 16, dataType: 'ulong' }, biSizeImage: { offset: 20, dataType: 'ulong' }, biXPelsPerMeter: { offset: 24, dataType: 'long' }, biYPelsPerMeter: { offset: 28, dataType: 'long' }, biClrUsed: { offset: 32, dataType: 'ulong' }, biClrImportant: { offset: 36, dataType: 'ulong' } }, 
    biSize: 40, biWidth: 1920, biHeight: 1080, biPlanes: 1, biBitCount: 32, biCompression: 0, biSizeImage: 0, 
    biXPelsPerMeter: 0, biYPelsPerMeter: 0, biClrUsed: 0, biClrImportant: 0>
    
dwBmpSize 8298585
getDIBitsRes 1080

lpBitmap <Buffer@0x000001BE18AED040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ... 8298535 more bytes>      

标签: node.jswinapi

解决方案


OP 提到他们最终能够让他们的代码正常工作,只需对其中一个常量进行简单修复。但是,这对其他读者并没有太大帮助,因为他们没有完整的代码;所以我想我会尝试重现他们拥有的工作代码。(感谢@vincitego 的起点!)

经过大量的试验和错误,我能够让我的复制工作,并将解决方案作为一个组件发布在我的 Windows FFI 库中:https ://github.com/Venryx/windows-ffi

用法:

import {VRect, CaptureScreenshot, GetForegroundWindowHandle} from "windows-ffi";

// First capture a screenshot of a section of the screen.
const screenshot = CaptureScreenshot({
    windowHandle: GetForegroundWindowHandle(), // comment to screenshot all windows
    rectToCapture: new VRect(0, 0, 800, 600),
});

// The image-data is now stored in the `screenshot.buffer` Buffer object.
// Access it directly (and cheaply) using the helper functions on `screenshot`.
for (let x = 0; x < 800; x++) {
    console.log(`Pixel color at [${x}, 0] is:`, screenshot.GetPixel(x, 0).ToHex_RGB());
}

您可以使用打包的库,或者,如果您愿意,只需参考其源代码并提取您需要的部分。屏幕截图的主要模块可以在这里看到


推荐阅读