根据进程名称查找窗口并实现完整与局部窗口截图的技术方案咨询
我来帮你搞定用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
相关产品推荐
相关产品推荐

