用于在不切换用户前台桌面的情况下,启动 GUI 程序、枚举窗口,再通过 HWND 进行后台自动化。纯 aardio 封装 Win32 API,不依赖 .NET。
这不是 Windows 任务视图的虚拟桌面,也不是安全沙箱。dotNet.desktop 管理任务视图;本库管理 HDESK 桌面对象。
下面示例会在私有桌面启动指定程序,枚举其窗口,然后强制终止本次启动的程序。请替换成专用于自动化测试的 EXE,不要用来处理尚未保存的工作。
import process.desktop;
import winex;
var desk,err = process.desktop();
if(!desk){ print(err); return; }
var p,err = desk.start("C:\path\your-app.exe");
if(!p){ desk.close(); print(err); return; }
// 类名、标题均可省略;传入 p 可限定进程,并在进程退出时提前结束等待。
var hwnd,tidOrErr = desk.wait(,,p,8000);
if(hwnd){
for h,title,tid,pid in desk.each(,,p){
print(h,title,tid,pid);
}
// 根据目标程序继续复用 winex:
// var edit = winex.findEx(hwnd,,"Edit",,1011);
// print(winex.getText(edit));
}
else print(tidOrErr);
// 业务完成或失败都应明确收尾;不需要强制终止时改用 desk.close()。
var cleaned,cleanupError = desk.dispose();
p.free();
if(!cleaned) print(cleanupError); // 失败时桌面仍可重试 dispose。
| 接口 | 用途与返回值 |
|---|---|
process.desktop() |
创建自动命名的私有桌面;失败返回 null,错误信息,Win32错误码 |
desk.start(exe,args,startInfo) |
在本桌面启动进程,返回现有 process 对象 |
desk.list() |
顶层 HWND 快照数组;正常空结果是 [] |
desk.each(cls,title,processOrPid) |
快照迭代器,每次返回 hwnd,title,threadId,processId |
desk.find(cls,title,processOrPid) |
首个匹配窗口:hwnd,threadId,processId;未找到返回 null |
desk.wait(cls,title,processOrPid,timeout=8000) |
有限时等待;成功返回值同 find |
desk.close() |
关闭本库持有的桌面句柄和内部跟踪句柄,不终止进程 |
desk.dispose(timeout=2000) |
强制终止 start 直接启动的进程,总限时等待后调用 close |
desk.name 是自动生成的名称,desk.path 是 窗口站名\桌面名,desk.handle 是 HDESK 指针。这三个字段只读使用,不要修改或自行释放句柄。
WinSta0。args、startInfo 沿用 process 的规则,可指定参数数组、工作目录、环境变量等。startInfo.desktop;不会修改调用方传入的参数表。waitInputTimeout 默认 0,避免启动阶段无限等待 GUI 就绪;showWindow 默认 1,但窗口只显示在私有桌面。显式传入的 0 等值会保留。cls 和 title 使用 aardio 模式匹配;省略不筛选,title="" 仅匹配空标题。process 对象或数值 PID。传入对象时,wait 能在进程退出或对象释放时提前结束;单独的数值 PID 只用于筛选。winex.findEx、winex.enum 查找。each 都枚举一次桌面、创建快照;不会持续跟踪后续新窗口。窗口可能随时销毁,不应长期缓存 HWND。GetWindowTextW 读取外部窗口的系统缓存标题,不发送 WM_GETTEXT,可避免无响应进程拖住标题筛选。自绘内容不等于系统标题;控件正文仍用 winex 等方法读取。wait 默认等待 8000 毫秒,约每 50 毫秒查询一次;0 或负数只检查一次。超时返回 null,"等待窗口超时",258。wait 同步阻塞调用线程。GUI 程序应在工作线程中创建并使用桌面对象;不要把这个对象直接传到其他线程。wait 只等待窗口,不监控整个自动化任务的最长执行时间,也不会在超时时自动杀进程。list/each/find/wait 出错时返回 null,错误信息[,Win32错误码]。直接用于 for ... in 时,迭代器创建失败会表现为没有迭代;需要区分错误时先取得迭代器:
var iterator,err = desk.each("目标窗口类名",,p);
if(!iterator){ print(err); return; }
for hwnd,title,tid,pid in iterator {
print(hwnd,title);
}
close 与 dispose 都可重复调用,成功返回 true,失败返回 null,错误信息[,错误码]。close 就放弃了本库对此前进程的清理权;之后再 dispose 不会补杀进程。要终止进程,应直接调用 dispose。dispose 使用独立持有的原生进程句柄,不根据 EXE 名称或重新打开 PID 来杀进程。即使调用方先 p.free(),清理仍然有效。dispose 只清理本对象 start 直接创建的进程,不追踪孙进程,也不清理通过 desk.path 从外部启动的进程。需要进程树约束时应另行采用 Job Object 等机制。process 对象独立存在,dispose 不会调用它的 free;仍可查询退出状态,然后自行 p.free()。dispose。若不再重试而直接 close,剩余进程不会被终止。close,不会自动强杀进程。请显式收尾;业务代码可能抛错时,可用一次 call 包住业务,将清理放在其后。raw.closeHandle 代替 CloseDesktop。EnumDesktopWindows 按整个桌面枚举。已知目标 TID 时 EnumThreadWindows 也可能找到对应窗口,但只覆盖一个线程,不能代替桌面枚举。