fix(desktop-wake): only OBJID_CLIENT DuiLib path to stop UIA core crash (1.2.5)
All checks were successful
Publish Python Package to Gitea / publish (push) Successful in 25s

Skip UiaRoot MSAA spoofing, sync GETOBJECT, and SPI screen-reader for n2-wake.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-02 09:28:36 +08:00
parent 9451bef753
commit 6d3c353967
7 changed files with 29 additions and 29 deletions

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "jiangchang-platform-kit"
version = "1.2.4"
version = "1.2.5"
description = "匠厂平台共享组件:Skill 实体 SDK + 桌面应用自动化 SDK + 桌面 UIA 暖树"
readme = "README.md"
requires-python = ">=3.10"

View File

@@ -1,7 +1,7 @@
# jiangchang_desktop_wake 验收(ensure 主路径,1.2.4+)
# jiangchang_desktop_wake 验收(ensure 主路径,1.2.5+)
正式对外路径是 **`ensure`**(内部:`activate` → `desktop-wake --mode n2-wake` → `probe`)。
`n2-wake` **不再**在企微进程内跑 UIAutomation FindAll(防 `uiautomationcore.dll` 崩溃);判暖只看进程外 probe。
`n2-wake`:**仅 OBJID_CLIENT** 走 DuiLib;不做进程内 FindAll、不同步 GETOBJECT、不 SPI 读屏;判暖只看进程外 probe。
`c2` / `inject` 仅诊断用。
## 0. 编译(企微先完全退出,含托盘)
@@ -57,7 +57,7 @@ python -m jiangchang_desktop_wake probe --profile wecom
| `probe_after.findall_count` | **≥ 80**(常见 300+) |
| markers | 含 `con_op`、`searchedit` |
| 企微 UI | 可点,无「未响应」 |
| provider | `done=1` `subclass=1` `dui=1` `no_inproc_uia=1`;`lr_client≠0` 更佳 |
| provider | `done=1` `subclass=1` `dui=1` `client_only=1` `no_inproc_uia=1` |
| subclass | `method=SetWindowLongPtr`,`ok=1`,`userdata≠0` |
| 企微进程 | **不崩**、无「错误报告」对话框 |

View File

@@ -2,8 +2,8 @@
桌面 UIA Provider **暖树**(与 `jiangchang_skill_core` / `jiangchang_desktop_sdk` 平级)。
企微主路径:注入 `wecom_wake.dll`,对主窗 `SetWindowLongPtr` 后走 DuiLib
`GetAccessible` / `OnGetObject`;**不在进程内 FindAll**(1.2.4+,避免 uiautomationcore 崩溃)。
企微主路径:注入 `wecom_wake.dll`,对主窗 `SetWindowLongPtr` 后仅对
`OBJID_CLIENT` 走 DuiLib `GetAccessible`(1.2.5+;避免 UiaRoot/MSAA 混用崩 uiautomationcore)。
可整目录删除:`src/jiangchang_desktop_wake` + `tests/desktop_wake`。

View File

@@ -152,8 +152,6 @@ namespace JiangchangDesktopWake
try
{
Activate(hwnd);
EnableScreenReaderFlag(); // 进程外 SPI:部分 App 见“读屏器”会挂树
// n1/auto:进程外 UIA
// n2:仅注入探针
// n2-wake:注入 + 远程 WakeProvider(C2,无钩子)
@@ -163,6 +161,9 @@ namespace JiangchangDesktopWake
bool doWakeProvider = (mode == "n2-wake") && profile == "wecom";
// n2-wake:不在 exe 内做 UIA 深扫(易跟着目标进程一起挂);暖树由 Python probe 判定
bool doN1 = (mode == "n1" || mode == "auto" || mode == "n2-hook");
// n2-wake 不再 SPI_SETSCREENREADER:会触发 UiaRoot 查询,测试机曾崩 uiautomationcore
if (!doWakeProvider)
EnableScreenReaderFlag();
if (doN2)
{

View File

@@ -6,10 +6,11 @@
* WakeTarget — 旧子类化(危险,仅 n2-hook)
* JcCallWndProc — 旧钩子(危险,仅 n2-hook)
*
* 1.2.4 主路径(防 uiautomationcore 崩):
* - SetWindowLongPtr 子类化,WM_GETOBJECT → DuiLib GetAccessible/OnGetObject
* - 重入守卫:处理 GETOBJECT 时不再二次进 DuiLib
* - 不做进程内 UIAutomation FindAll / AccessibleObjectFromWindow(判暖交给进程外 probe)
* 1.2.5 主路径(防 uiautomationcore 崩):
* - SetWindowLongPtr 子类化;仅 OBJID_CLIENT 走 DuiLib GetAccessible
* - UiaRootObjectId 等原样交给旧 WndProc(禁止用 MSAA Lresult 冒充 UIA root)
* - 不做进程内 UIAutomation FindAll;不做同步 SendMessage(GETOBJECT)
* - 重入守卫;判暖交给进程外 probe
*/
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
@@ -122,11 +123,9 @@ static HWND ReadTargetHwnd()
static void NudgeWinEvents(HWND hwnd)
{
/* 只异步点 CLIENT,绝不 Post UiaRootObjectId(易进 uiautomationcore 崩) */
NotifyWinEvent(EVENT_OBJECT_FOCUS, hwnd, OBJID_CLIENT, CHILDID_SELF);
NotifyWinEvent(EVENT_SYSTEM_ALERT, hwnd, OBJID_CLIENT, CHILDID_SELF);
PostMessageW(hwnd, WM_GETOBJECT, 0, (LPARAM)OBJID_CLIENT);
PostMessageW(hwnd, WM_GETOBJECT, 0, (LPARAM)OBJID_WINDOW);
PostMessageW(hwnd, WM_GETOBJECT, 0, (LPARAM)UiaRootObjectId);
PostMessageW(hwnd, WM_NULL, 0, 0);
}
@@ -144,8 +143,16 @@ static bool ResolveDuiLib()
return g_fnGetAccessible != NULL || g_fnOnGetObject != NULL;
}
static bool IsClientObjectId(LPARAM lParam)
{
return (LONG)lParam == (LONG)OBJID_CLIENT;
}
static LRESULT TryDuiLibGetObject(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
/* 只服务 MSAA CLIENT。UiaRootObjectId 用 LresultFromObject(IAccessible) 会搞崩 UIA core */
if (!IsClientObjectId(lParam))
return 0;
if (!ResolveDuiLib())
return 0;
@@ -194,7 +201,8 @@ static LRESULT HandleGetObject(
else if (oldProc)
r = CallWindowProcW(oldProc, hwnd, WM_GETOBJECT, wParam, lParam);
if (r == 0)
/* 非 CLIENT(含 UiaRootObjectId):绝不伪造返回值 */
if (r == 0 && IsClientObjectId(lParam))
r = TryDuiLibGetObject(hwnd, wParam, lParam);
t_inGetObject = 0;
@@ -263,11 +271,10 @@ static bool InstallDuiSubclass(HWND hwnd)
return true;
}
/* 只装子类化 + 异步 nudge;可选一次短超时 GETOBJECT 看 lr(无进程内 UIA) */
/* 只装子类化 + 异步 nudge;禁止同步 SendMessage(GETOBJECT) */
static void DoSafeWake(HWND hwnd)
{
char line[512];
LRESULT lrClient = 0;
int subclass_ok = 0;
int dui_ok = 0;
@@ -276,21 +283,13 @@ static void DoSafeWake(HWND hwnd)
subclass_ok = InstallDuiSubclass(hwnd) ? 1 : 0;
NudgeWinEvents(hwnd);
DWORD_PTR smResult = 0;
if (SendMessageTimeoutW(
hwnd, WM_GETOBJECT, 0, (LPARAM)OBJID_CLIENT,
SMTO_ABORTIFHUNG, 2000, &smResult)) {
lrClient = (LRESULT)smResult;
}
NudgeWinEvents(hwnd);
sprintf_s(line,
"hwnd=%p sta_tid=%lu lr_client=%ld subclass=%d dui=%d "
"hwnd=%p sta_tid=%lu subclass=%d dui=%d "
"dui_hits=%ld getobject_hits=%ld reenter_skip=%ld "
"no_inproc_uia=1 done=1\r\n",
hwnd, GetCurrentThreadId(), (long)lrClient, subclass_ok, dui_ok,
"client_only=1 no_sync_getobject=1 no_inproc_uia=1 done=1\r\n",
hwnd, GetCurrentThreadId(), subclass_ok, dui_ok,
(long)g_duiHits, (long)g_getobjectHits, (long)g_reenterSkip);
WriteMarker("jiangchang_desktop_wake_provider.txt", line);
}