You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

根据进程名称查找窗口并实现完整与局部窗口截图的技术方案咨询

我来帮你搞定用lxn/win库实现窗口截图的需求,毕竟你不是Windows用户,对WINAPI不太熟,咱们一步步拆解每个功能的实现逻辑:

核心思路概述

lxn/win库是对Windows API的Go语言封装,咱们要用到窗口枚举、进程信息获取、GDI图形操作这几类接口,来完成「找窗口→完整截图→局部截取」的流程。和你之前用的screenshot库不同,它能直接针对单个窗口操作,不用截取整个屏幕。

1. 根据进程名称查找目标窗口

要找到对应进程的窗口,得两步走:枚举所有顶层窗口,然后逐个匹配窗口所属的进程名称。

具体步骤:

  • 用win.EnumWindows遍历所有顶层窗口,这个函数需要传入一个回调函数,每个窗口都会触发一次回调。
  • 在回调里,用win.GetWindowThreadProcessId获取当前窗口对应的进程ID(PID)。
  • 通过PID用win.OpenProcess打开进程,获取进程句柄。
  • 用win.GetModuleBaseName获取该进程的名称,和你的目标进程名(比如app.exe)做匹配。
  • 匹配成功后,记录下窗口句柄(HWND),终止枚举。

给你一段示例代码框架:

import (
    "syscall"
    "unsafe"
    "github.com/lxn/win"
)

var targetHWND win.HWND
var targetProcessName = "app.exe"

func findWindowByProcessName() {
    win.EnumWindows(func(hwnd win.HWND, lParam uintptr) uintptr {
        var pid uint32
        win.GetWindowThreadProcessId(hwnd, &pid)
        hProcess := win.OpenProcess(win.PROCESS_QUERY_INFORMATION|win.PROCESS_VM_READ, false, pid)
        if hProcess == 0 {
            return 1 // 继续枚举
        }
        defer win.CloseHandle(hProcess)

        var processName [256]uint16
        win.GetModuleBaseName(hProcess, 0, &processName[0], uint32(len(processName)))
        processNameStr := syscall.UTF16ToString(processName[:])

        if processNameStr == targetProcessName {
            targetHWND = hwnd
            return 0 // 找到目标,终止枚举
        }
        return 1
    }, 0)
}

注意:要确保进程名匹配时大小写一致,Windows进程名通常是小写(比如notepad.exe),你可以根据实际情况调整。

2. 对目标窗口完整截图

拿到窗口句柄后,就可以用GDI接口捕获窗口内容了:

步骤:

  • 用win.GetWindowRect获取窗口的屏幕坐标和尺寸(包含标题栏、边框的整个窗口);如果只想截客户区(窗口内部内容),可以用win.GetClientRect,再用win.ClientToScreen转成屏幕坐标。
  • 获取屏幕DC(win.GetDC(0)),创建兼容DC(win.CreateCompatibleDC)和兼容位图(win.CreateCompatibleBitmap),把位图选进兼容DC。
  • 用win.BitBlt把窗口的内容从屏幕DC复制到兼容DC的位图上。
  • 把位图转换成Go标准库的image.Image,或者直接保存成图片文件。

示例代码:

import (
    "image"
    "image/color"
    "unsafe"
    "github.com/lxn/win"
)

func captureFullWindow(hwnd win.HWND) (image.Image, error) {
    var rect win.RECT
    win.GetWindowRect(hwnd, &rect)
    width := rect.Right - rect.Left
    height := rect.Bottom - rect.Top

    hScreenDC := win.GetDC(0)
    defer win.ReleaseDC(0, hScreenDC)

    hMemDC := win.CreateCompatibleDC(hScreenDC)
    defer win.DeleteDC(hMemDC)

    hBitmap := win.CreateCompatibleBitmap(hScreenDC, int32(width), int32(height))
    defer win.DeleteObject(win.HGDIOBJ(hBitmap))

    oldBitmap := win.SelectObject(hMemDC, win.HGDIOBJ(hBitmap))
    defer win.SelectObject(hMemDC, oldBitmap)

    // BitBlt捕获窗口内容
    win.BitBlt(hMemDC, 0, 0, int32(width), int32(height), hScreenDC, rect.Left, rect.Top, win.SRCCOPY)

    return hBitmapToImage(hBitmap, width, height)
}

// 辅助函数:HBITMAP转image.Image
func hBitmapToImage(hBitmap win.HBITMAP, width, height int32) (image.Image, error) {
    var bmp win.BITMAP
    win.GetObject(win.HGDIOBJ(hBitmap), int32(unsafe.Sizeof(bmp)), unsafe.Pointer(&bmp))
    
    pixelBuffer := make([]byte, bmp.BmWidthBytes*bmp.BmHeight)
    hDC := win.CreateCompatibleDC(0)
    defer win.DeleteDC(hDC)
    oldBmp := win.SelectObject(hDC, win.HGDIOBJ(hBitmap))
    defer win.SelectObject(hDC, oldBmp)
    
    bi := win.BITMAPINFO{
        BmiHeader: win.BITMAPINFOHEADER{
            BiSize:        uint32(unsafe.Sizeof(win.BITMAPINFOHEADER{})),
            BiWidth:       bmp.BmWidth,
            BiHeight:      -bmp.BmHeight, // 负号表示像素顺序为从上到下
            BiPlanes:      1,
            BiBitCount:    32,
            BiCompression: win.BI_RGB,
        },
    }
    
    win.GetDIBits(hDC, hBitmap, 0, uint32(bmp.BmHeight), unsafe.Pointer(&pixelBuffer[0]), &bi, win.DIB_RGB_COLORS)
    
    rgba := image.NewRGBA(image.Rect(0, 0, int(width), int(height)))
    copy(rgba.Pix, pixelBuffer)
    return rgba, nil
}
3. 截取窗口指定局部区域(比如右上角固定宽高)

局部截取其实是在BitBlt的时候指定源区域的坐标,比如要截右上角宽200、高150的区域:

步骤:

  • 先获取窗口的完整矩形(GetWindowRect)。
  • 计算局部区域的左上角坐标:x = rect.Right - targetWidth,y = rect.Top。
  • 调用BitBlt时,指定源DC的起始坐标为(x, y),宽高为目标局部区域的尺寸,目标DC的起始坐标为(0,0),宽高同样是局部尺寸。

示例代码片段:

func capturePartialWindow(hwnd win.HWND, targetWidth, targetHeight int32) (image.Image, error) {
    var rect win.RECT
    win.GetWindowRect(hwnd, &rect)
    
    // 计算右上角区域的起始坐标
    srcX := rect.Right - targetWidth
    srcY := rect.Top

    hScreenDC := win.GetDC(0)
    defer win.ReleaseDC(0, hScreenDC)

    hMemDC := win.CreateCompatibleDC(hScreenDC)
    defer win.DeleteDC(hMemDC)

    hBitmap := win.CreateCompatibleBitmap(hScreenDC, targetWidth, targetHeight)
    defer win.DeleteObject(win.HGDIOBJ(hBitmap))

    oldBitmap := win.SelectObject(hMemDC, win.HGDIOBJ(hBitmap))
    defer win.SelectObject(hMemDC, oldBitmap)

    // 只复制指定区域
    win.BitBlt(hMemDC, 0, 0, targetWidth, targetHeight, hScreenDC, srcX, srcY, win.SRCCOPY)

    return hBitmapToImage(hBitmap, targetWidth, targetHeight)
}
补充说明
  • 如果你需要处理全屏窗口,GetWindowRect会返回整个屏幕的尺寸,和窗口化的处理逻辑一致,不用额外调整。
  • 多显示器场景下,GetWindowRect返回的坐标是相对于虚拟桌面的,BitBlt会自动对应到正确的屏幕,不用单独处理屏幕索引。
  • GDI对象(DC、Bitmap等)一定要记得释放(用DeleteDC、DeleteObject、ReleaseDC),否则会造成Windows资源泄漏。

内容的提问来源于stack exchange,提问作者Revenant

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 17:33:13