diff --git a/pyproject.toml b/pyproject.toml index 2e42ed6..d0d5904 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "jiangchang-platform-kit" -version = "1.2.3" +version = "1.2.4" description = "匠厂平台共享组件:Skill 实体 SDK + 桌面应用自动化 SDK + 桌面 UIA 暖树" readme = "README.md" requires-python = ">=3.10" diff --git a/src/jiangchang_desktop_wake/ACCEPTANCE.md b/src/jiangchang_desktop_wake/ACCEPTANCE.md index 63dee37..0149b6d 100644 --- a/src/jiangchang_desktop_wake/ACCEPTANCE.md +++ b/src/jiangchang_desktop_wake/ACCEPTANCE.md @@ -1,6 +1,7 @@ -# jiangchang_desktop_wake 验收(阶段 A:ensure 主路径) +# jiangchang_desktop_wake 验收(ensure 主路径,1.2.4+) 正式对外路径是 **`ensure`**(内部:`activate` → `desktop-wake --mode n2-wake` → `probe`)。 +`n2-wake` **不再**在企微进程内跑 UIAutomation FindAll(防 `uiautomationcore.dll` 崩溃);判暖只看进程外 probe。 `c2` / `inject` 仅诊断用。 ## 0. 编译(企微先完全退出,含托盘) @@ -56,8 +57,9 @@ 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` `lr_client≠0` | +| provider | `done=1` `subclass=1` `dui=1` `no_inproc_uia=1`;`lr_client≠0` 更佳 | | subclass | `method=SetWindowLongPtr`,`ok=1`,`userdata≠0` | +| 企微进程 | **不崩**、无「错误报告」对话框 | ### 失败时 diff --git a/src/jiangchang_desktop_wake/README.md b/src/jiangchang_desktop_wake/README.md index 13a49b8..ee8f910 100644 --- a/src/jiangchang_desktop_wake/README.md +++ b/src/jiangchang_desktop_wake/README.md @@ -3,7 +3,7 @@ 桌面 UIA Provider **暖树**(与 `jiangchang_skill_core` / `jiangchang_desktop_sdk` 平级)。 企微主路径:注入 `wecom_wake.dll`,对主窗 `SetWindowLongPtr` 后走 DuiLib -`GetAccessible` / `OnGetObject`(已验收冷树 findall 2→300+)。 +`GetAccessible` / `OnGetObject`;**不在进程内 FindAll**(1.2.4+,避免 uiautomationcore 崩溃)。 可整目录删除:`src/jiangchang_desktop_wake` + `tests/desktop_wake`。 diff --git a/src/jiangchang_desktop_wake/native/desktop-wake.exe b/src/jiangchang_desktop_wake/native/desktop-wake.exe index c3b8007..81a4f6b 100644 Binary files a/src/jiangchang_desktop_wake/native/desktop-wake.exe and b/src/jiangchang_desktop_wake/native/desktop-wake.exe differ diff --git a/src/jiangchang_desktop_wake/native/wecom_wake.dll b/src/jiangchang_desktop_wake/native/wecom_wake.dll index 2afe17a..63bc320 100644 Binary files a/src/jiangchang_desktop_wake/native/wecom_wake.dll and b/src/jiangchang_desktop_wake/native/wecom_wake.dll differ diff --git a/src/jiangchang_desktop_wake/native_src/N2/payload/wecom_wake.cpp b/src/jiangchang_desktop_wake/native_src/N2/payload/wecom_wake.cpp index c27f5e4..94389c5 100644 --- a/src/jiangchang_desktop_wake/native_src/N2/payload/wecom_wake.cpp +++ b/src/jiangchang_desktop_wake/native_src/N2/payload/wecom_wake.cpp @@ -6,23 +6,20 @@ * WakeTarget — 旧子类化(危险,仅 n2-hook) * JcCallWndProc — 旧钩子(危险,仅 n2-hook) * - * C2+ 主路径: - * 1) STA 工作线程 + 进程内 UIA(保持客户端) - * 2) SetWindowSubclass:WM_GETOBJECT 时走 DuiLib 已导出的 - * GetAccessible / OnGetObject(不挂钩、不用 CreateStdAccessibleObject) + * 1.2.4 主路径(防 uiautomationcore 崩): + * - SetWindowLongPtr 子类化,WM_GETOBJECT → DuiLib GetAccessible/OnGetObject + * - 重入守卫:处理 GETOBJECT 时不再二次进 DuiLib + * - 不做进程内 UIAutomation FindAll / AccessibleObjectFromWindow(判暖交给进程外 probe) */ #define WIN32_LEAN_AND_MEAN #include #include #include -#include #include #include #pragma comment(lib, "oleacc.lib") #pragma comment(lib, "ole32.lib") -#pragma comment(lib, "oleaut32.lib") -#pragma comment(lib, "uuid.lib") #pragma comment(lib, "user32.lib") #pragma comment(lib, "comctl32.lib") @@ -35,9 +32,6 @@ #ifndef CHILDID_SELF #define CHILDID_SELF 0 #endif -#ifndef ANR_ON -#define ANR_ON 0x00000001 -#endif #ifndef UiaRootObjectId #define UiaRootObjectId ((LONG)0xFFFFFFE7) #endif @@ -49,23 +43,21 @@ static HMODULE g_self = NULL; static HWND g_hwnd = NULL; static WNDPROC g_oldProc = NULL; +static WNDPROC g_duiOldProc = NULL; static volatile LONG g_getobjectHits = 0; static volatile LONG g_hookHits = 0; static volatile LONG g_duiHits = 0; +static volatile LONG g_reenterSkip = 0; +static volatile LONG g_subclassInstalled = 0; static HANDLE g_staThread = NULL; static HANDLE g_staReady = NULL; static HWND g_msgHwnd = NULL; static volatile LONG g_staStarted = 0; -static IUIAutomation* g_auto = NULL; -static IUIAutomationElement* g_root = NULL; -static IUIAutomationElementArray* g_arr = NULL; -static IAccessible* g_acc = NULL; +/* per-thread reentrancy guard for WM_GETOBJECT */ +static __declspec(thread) int t_inGetObject = 0; -static volatile LONG g_subclassInstalled = 0; - -/* DuiLib thiscall helpers (x86) */ typedef IAccessible*(__thiscall* FnGetAccessible)(void* self); typedef LRESULT(__thiscall* FnOnGetObject)( void* self, UINT uMsg, WPARAM wParam, LPARAM lParam, bool* bHandled); @@ -138,25 +130,6 @@ static void NudgeWinEvents(HWND hwnd) PostMessageW(hwnd, WM_NULL, 0, 0); } -static void TryAccUtilityState(HWND hwnd) -{ - HMODULE oleacc = GetModuleHandleW(L"oleacc.dll"); - if (!oleacc) oleacc = LoadLibraryW(L"oleacc.dll"); - if (!oleacc) return; - typedef HRESULT(WINAPI* PFN)(HWND, DWORD, DWORD); - PFN fn = (PFN)GetProcAddress(oleacc, "AccSetRunningUtilityState"); - if (!fn) return; - fn(hwnd, ANR_ON, ANR_ON); -} - -static void ReleaseKeepAlive() -{ - if (g_arr) { g_arr->Release(); g_arr = NULL; } - if (g_root) { g_root->Release(); g_root = NULL; } - if (g_auto) { g_auto->Release(); g_auto = NULL; } - if (g_acc) { g_acc->Release(); g_acc = NULL; } -} - static bool ResolveDuiLib() { if (g_fnGetAccessible && g_fnOnGetObject) return true; @@ -171,7 +144,6 @@ static bool ResolveDuiLib() return g_fnGetAccessible != NULL || g_fnOnGetObject != NULL; } -/* Force DuiLib MSAA for WM_GETOBJECT — no CreateStdAccessibleObject (avoids UI hang). */ static LRESULT TryDuiLibGetObject(HWND hwnd, WPARAM wParam, LPARAM lParam) { if (!ResolveDuiLib()) @@ -194,7 +166,6 @@ static LRESULT TryDuiLibGetObject(HWND hwnd, WPARAM wParam, LPARAM lParam) IAccessible* acc = g_fnGetAccessible(self); if (acc) { LRESULT lr = LresultFromObject(IID_IAccessible, wParam, (LPUNKNOWN)acc); - /* LresultFromObject AddRef's; release our ref from GetAccessible */ acc->Release(); return lr; } @@ -202,7 +173,33 @@ static LRESULT TryDuiLibGetObject(HWND hwnd, WPARAM wParam, LPARAM lParam) return 0; } -static WNDPROC g_duiOldProc = NULL; +static LRESULT HandleGetObject( + HWND hwnd, WPARAM wParam, LPARAM lParam, WNDPROC oldProc, bool useDefSubclass) +{ + InterlockedIncrement(&g_getobjectHits); + + if (t_inGetObject) { + InterlockedIncrement(&g_reenterSkip); + if (useDefSubclass) + return DefSubclassProc(hwnd, WM_GETOBJECT, wParam, lParam); + if (oldProc) + return CallWindowProcW(oldProc, hwnd, WM_GETOBJECT, wParam, lParam); + return DefWindowProcW(hwnd, WM_GETOBJECT, wParam, lParam); + } + + t_inGetObject = 1; + LRESULT r = 0; + if (useDefSubclass) + r = DefSubclassProc(hwnd, WM_GETOBJECT, wParam, lParam); + else if (oldProc) + r = CallWindowProcW(oldProc, hwnd, WM_GETOBJECT, wParam, lParam); + + if (r == 0) + r = TryDuiLibGetObject(hwnd, wParam, lParam); + + t_inGetObject = 0; + return r; +} static LRESULT CALLBACK DuiSubclassProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, @@ -210,34 +207,15 @@ static LRESULT CALLBACK DuiSubclassProc( { (void)uIdSubclass; (void)dwRefData; - if (uMsg == WM_GETOBJECT) { - InterlockedIncrement(&g_getobjectHits); - LRESULT r = DefSubclassProc(hWnd, uMsg, wParam, lParam); - if (r != 0) - return r; - r = TryDuiLibGetObject(hWnd, wParam, lParam); - if (r != 0) - return r; - return 0; - } + if (uMsg == WM_GETOBJECT) + return HandleGetObject(hWnd, wParam, lParam, NULL, true); return DefSubclassProc(hWnd, uMsg, wParam, lParam); } -/* Fallback when comctl SetWindowSubclass unavailable in host */ static LRESULT CALLBACK DuiWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { - if (msg == WM_GETOBJECT) { - InterlockedIncrement(&g_getobjectHits); - LRESULT r = 0; - if (g_duiOldProc) - r = CallWindowProcW(g_duiOldProc, hwnd, msg, wParam, lParam); - if (r != 0) - return r; - r = TryDuiLibGetObject(hwnd, wParam, lParam); - if (r != 0) - return r; - return 0; - } + if (msg == WM_GETOBJECT) + return HandleGetObject(hwnd, wParam, lParam, g_duiOldProc, false); if (g_duiOldProc) return CallWindowProcW(g_duiOldProc, hwnd, msg, wParam, lParam); return DefWindowProcW(hwnd, msg, wParam, lParam); @@ -251,21 +229,7 @@ static bool InstallDuiSubclass(HWND hwnd) ResolveDuiLib(); void* userdata = (void*)GetWindowLongPtrW(hwnd, GWLP_USERDATA); - LONG_PTR accPtr = 0; - LONG_PTR ongetLr = 0; - if (userdata && g_fnGetAccessible) { - IAccessible* acc = g_fnGetAccessible(userdata); - accPtr = (LONG_PTR)acc; - if (acc) { - long kids = -1; - acc->get_accChildCount(&kids); - ongetLr = kids; - /* keep one ref for diagnostics; Release extra if we AddRef'd — GetAccessible owns */ - acc->Release(); - } - } - /* 企微进程里 SetWindowSubclass 常失败;主用 SetWindowLongPtr(只转发 DuiLib GetAccessible,不挂钩) */ SetLastError(0); g_duiOldProc = (WNDPROC)SetWindowLongPtrW( hwnd, GWLP_WNDPROC, (LONG_PTR)DuiWndProc); @@ -288,10 +252,9 @@ static bool InstallDuiSubclass(HWND hwnd) char line[512]; sprintf_s(line, "hwnd=%p ok=%d gle=%lu method=%s userdata=%p " - "getacc_ret=%p getacc_kids=%ld duilib=%p getacc=%p onget=%p\r\n", + "duilib=%p getacc=%p onget=%p\r\n", hwnd, (int)ok, gle, method, userdata, - (void*)accPtr, (long)ongetLr, g_duilib, - g_fnGetAccessible, g_fnOnGetObject); + g_duilib, g_fnGetAccessible, g_fnOnGetObject); WriteMarker("jiangchang_desktop_wake_subclass.txt", line); if (!ok) { InterlockedExchange(&g_subclassInstalled, 0); @@ -300,78 +263,35 @@ static bool InstallDuiSubclass(HWND hwnd) return true; } -static void DoInProcessUiaWake(HWND hwnd) +/* 只装子类化 + 异步 nudge;可选一次短超时 GETOBJECT 看 lr(无进程内 UIA) */ +static void DoSafeWake(HWND hwnd) { - char line[1024]; - LONG findall = -1; - LONG msaaKids = -1; - HRESULT hrAuto = E_FAIL; - HRESULT hrEl = E_FAIL; - HRESULT hrFind = E_FAIL; - HRESULT hrMsaa = E_FAIL; + char line[512]; LRESULT lrClient = 0; - LRESULT lrUia = 0; int subclass_ok = 0; int dui_ok = 0; - TryAccUtilityState(hwnd); ResolveDuiLib(); dui_ok = (g_fnGetAccessible || g_fnOnGetObject) ? 1 : 0; subclass_ok = InstallDuiSubclass(hwnd) ? 1 : 0; NudgeWinEvents(hwnd); - /* Sync probe WM_GETOBJECT after subclass — goes through our handler */ - lrClient = SendMessageW(hwnd, WM_GETOBJECT, 0, (LPARAM)OBJID_CLIENT); - lrUia = SendMessageW(hwnd, WM_GETOBJECT, 0, (LPARAM)UiaRootObjectId); - - ReleaseKeepAlive(); - - hrAuto = CoCreateInstance( - CLSID_CUIAutomation, NULL, CLSCTX_INPROC_SERVER, - IID_IUIAutomation, (void**)&g_auto); - if (SUCCEEDED(hrAuto) && g_auto) { - hrEl = g_auto->ElementFromHandle(hwnd, &g_root); - if (SUCCEEDED(hrEl) && g_root) { - IUIAutomationCondition* cond = NULL; - if (SUCCEEDED(g_auto->CreateTrueCondition(&cond)) && cond) { - hrFind = g_root->FindAll(TreeScope_Subtree, cond, &g_arr); - cond->Release(); - if (SUCCEEDED(hrFind) && g_arr) { - int len = 0; - if (SUCCEEDED(g_arr->get_Length(&len))) - findall = len; - } - } - } - } - - { - IAccessible* acc = NULL; - hrMsaa = AccessibleObjectFromWindow( - hwnd, OBJID_CLIENT, IID_IAccessible, (void**)&acc); - if (SUCCEEDED(hrMsaa) && acc) { - g_acc = acc; - long kids = 0; - if (SUCCEEDED(acc->get_accChildCount(&kids))) - msaaKids = kids; - } + 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 findall=%ld msaa_kids=%ld " - "lr_client=%ld lr_uia=%ld subclass=%d dui=%d " - "dui_hits=%ld getobject_hits=%ld " - "hr_auto=0x%08lx hr_el=0x%08lx hr_find=0x%08lx hr_msaa=0x%08lx " - "keep_auto=%d keep_root=%d keep_arr=%d keep_acc=%d done=1\r\n", - hwnd, GetCurrentThreadId(), (long)findall, (long)msaaKids, - (long)lrClient, (long)lrUia, subclass_ok, dui_ok, - (long)g_duiHits, (long)g_getobjectHits, - (unsigned long)hrAuto, (unsigned long)hrEl, - (unsigned long)hrFind, (unsigned long)hrMsaa, - g_auto ? 1 : 0, g_root ? 1 : 0, g_arr ? 1 : 0, g_acc ? 1 : 0); + "hwnd=%p sta_tid=%lu lr_client=%ld 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, + (long)g_duiHits, (long)g_getobjectHits, (long)g_reenterSkip); WriteMarker("jiangchang_desktop_wake_provider.txt", line); } @@ -380,7 +300,7 @@ static LRESULT CALLBACK MsgWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lP if (msg == WM_JC_WAKE) { HWND target = (HWND)wParam; if (target && IsWindow(target)) - DoInProcessUiaWake(target); + DoSafeWake(target); else WriteMarker("jiangchang_desktop_wake_provider.txt", "err=bad_hwnd done=1\r\n"); return 0; @@ -394,9 +314,6 @@ static LRESULT CALLBACK MsgWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lP static DWORD WINAPI StaThreadProc(LPVOID) { - HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); - bool needUninit = SUCCEEDED(hr) || hr == S_FALSE; - WNDCLASSW wc; ZeroMemory(&wc, sizeof(wc)); wc.lpfnWndProc = MsgWndProc; @@ -413,7 +330,6 @@ static DWORD WINAPI StaThreadProc(LPVOID) if (!g_msgHwnd) { WriteMarker("jiangchang_desktop_wake_provider.txt", "err=no_msg_hwnd done=1\r\n"); - if (needUninit) CoUninitialize(); return 1; } @@ -423,12 +339,10 @@ static DWORD WINAPI StaThreadProc(LPVOID) DispatchMessageW(&msg); } - ReleaseKeepAlive(); if (g_msgHwnd) { DestroyWindow(g_msgHwnd); g_msgHwnd = NULL; } - if (needUninit) CoUninitialize(); return 0; } @@ -475,7 +389,7 @@ extern "C" __declspec(dllexport) DWORD WINAPI WakeProvider(LPVOID param) DWORD_PTR result = 0; LRESULT ok = SendMessageTimeoutW( g_msgHwnd, WM_JC_WAKE, (WPARAM)hwnd, 0, - SMTO_ABORTIFHUNG, 15000, &result); + SMTO_ABORTIFHUNG, 8000, &result); if (ok == 0) { char line[256]; @@ -519,13 +433,7 @@ static LRESULT TryStdAccessible(HWND hwnd, WPARAM wParam, LONG objId) static LRESULT CALLBACK SubclassProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { if (msg == WM_GETOBJECT) { - InterlockedIncrement(&g_getobjectHits); - LRESULT r = 0; - if (g_oldProc) - r = CallWindowProcW(g_oldProc, hwnd, msg, wParam, lParam); - if (r != 0) - return r; - r = TryDuiLibGetObject(hwnd, wParam, lParam); + LRESULT r = HandleGetObject(hwnd, wParam, lParam, g_oldProc, false); if (r != 0) return r; LONG objId = (LONG)lParam; diff --git a/src/jiangchang_desktop_wake/native_src/build.ps1 b/src/jiangchang_desktop_wake/native_src/build.ps1 index 80811db..c0dbbdd 100644 --- a/src/jiangchang_desktop_wake/native_src/build.ps1 +++ b/src/jiangchang_desktop_wake/native_src/build.ps1 @@ -43,7 +43,7 @@ $bat = Join-Path $env:TEMP "jiangchang_build_wecom_wake.bat" @" @echo off call "$vcvars32" || exit /b 1 -cl /nologo /LD /O2 /EHsc /DWIN32 /D_WINDOWS /D_USRDLL /utf-8 "$srcCpp" /Fe"$dllTarget" /Fo"$objPath" /link /DEF:"$srcDef" user32.lib ole32.lib oleacc.lib oleaut32.lib uuid.lib comctl32.lib +cl /nologo /LD /O2 /EHsc /DWIN32 /D_WINDOWS /D_USRDLL /utf-8 "$srcCpp" /Fe"$dllTarget" /Fo"$objPath" /link /DEF:"$srcDef" user32.lib ole32.lib oleacc.lib comctl32.lib exit /b %ERRORLEVEL% "@ | Set-Content -Path $bat -Encoding ASCII