Skip to content

feat(tsf): 激活链路诊断日志——COM 入口与线程激活标志 - #114

Merged
huanfeng merged 2 commits into
huanfeng:mainfrom
sage-z-cn:feat/tsf-activation-diag
Sep 5, 2026
Merged

feat(tsf): 激活链路诊断日志——COM 入口与线程激活标志#114
huanfeng merged 2 commits into
huanfeng:mainfrom
sage-z-cn:feat/tsf-activation-diag

Conversation

@sage-z-cn

Copy link
Copy Markdown
Contributor

背景

「游戏等特殊宿主中输入法被静默筛除」类问题定位困难——TIP 未被激活时连 ActivateEx 都不会被调用,现有日志无从下手。本 PR 在四个关键层补充埋点,正是它们把流放之路(国际服)案例中「COM 激活前被筛除」这一层钉死的。

改动

  • DllGetClassObject 入口:日志输出 rclsid/riid 与 CLSID 匹配结果(COM 激活第一入口)
  • CClassFactory::CreateInstance 入口:日志输出 riid 与聚合参数
  • CTextService::ActivateEx:经 ITfThreadMgrEx::GetActiveFlags 打印线程激活标志(activeFlags,TF_TMF_* 位),可区分 SECUREMODE/UIELEMENTENABLEDONLY/COMLESS/IMMERSIVE 等约束
  • CTextService::QueryInterface:未匹配接口(E_NOINTERFACE)时记录被探测的 riid

用法

tsf_log_configmode=file level=debug 后,在 %LOCALAPPDATA%\WindInput[Dev]\logs\tsf_log\ 按「有无 DllGetClassObject 日志行」即可判定拒绝发生在 COM 之前(注册面/能力筛选)还是之后(TIP 内部)。

已在本机流放之路(国际服)案例中实测:游戏正式会话期间选清风连 DllGetClassObject 都无记录,直接证明筛选发生在 COM 激活之前。

- DllGetClassObject / ClassFactory::CreateInstance 入口日志(GUID 串 + CLSID 匹配结果)
- ActivateEx 打印 ITfThreadMgrEx::GetActiveFlags(线程激活标志位)
- QueryInterface 未匹配接口(E_NOINTERFACE)时记录被探测的 riid

定位「游戏等特殊宿主中输入法被静默筛除」类问题时,可在
%LOCALAPPDATA%\WindInput[Dev]\logs\tsf_log\ 观察 COM 激活被拒发生在哪一层。
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@sage-z-cn

Copy link
Copy Markdown
Contributor Author

I have read the CLA Document and I hereby sign the CLA

@sage-z-cn

Copy link
Copy Markdown
Contributor Author

recheck

@huanfeng

huanfeng commented Sep 5, 2026

Copy link
Copy Markdown
Owner

审过了,代码没有问题,可以合。

核对无误的部分

  • 日志器初始化早于 COM 入口CFileLogger::Instance().Init()DllMain(DLL_PROCESS_ATTACH)dllmain.cpp:13),先于 DllGetClassObject。这条是承重墙——[讨论] 反作弊(ACE)环境下未签名输入法的兼容性调研——流放之路国服/穿越火线不可用,英雄联盟正常 #115 里「有 PROCESS_ATTACH 行、无 DllGetClassObject 行 ⇒ 拒绝发生在 COM 之前」这个推断的成立与否全看它,所以专门验了。
  • 无引用泄漏ITfThreadMgrEx 的 QI 与 Release() 配对,只在成功路径释放;TextService.h:416 只存 _pThreadMgr、没有现成的 Ex 指针,所以这不是重复 QI。
  • 缓冲区WCHAR[64] 对 39 字符(含花括号与 NUL)的 GUID 串够用,且零初始化 ⇒ StringFromGUID2 失败时打空串而非脏数据。
  • 房风一致IID_ITfThreadMgrEx + C 风格 cast 与 KeyEventSink.cpp:1781LangBarItemButton.cpp:1243 等处同形。
  • 换行正确:新日志不带 \n,而 writer 自己补 \r\nFileLogger.cpp:150)——比某些带 \n 的老调用点更对。
  • 隐私:只输出 GUID,符合仓内「INFO 及以下不得含敏感信息」的约定。
  • 三处埋点都在它们所观察的分支之后,不改任何返回值与控制流;ActivateEx 的探测是纯只读,失败只记日志。

一条不阻塞的建议

TextService.cppE_NOINTERFACE 分支里,StringFromGUID2 在实参位置——日志宏只能延后格式化,挡不住实参位置的函数调用(C++ 求值顺序要求先做)。CTextService::QueryInterface 有十几个接口分支、落空是常态,所以这行在出厂默认 mode=none 下也会跑。

仓里为此备了闸门,定义与用法各有注释:Globals.h:32-38Globals.cpp:303-308。包一层即可:

if (WindLog::IsEnabled(4)) {
    WCHAR szIid[64] = {};
    StringFromGUID2(riid, szIid, ARRAYSIZE(szIid));
    WIND_LOG_DEBUG_FMT(L"TextService::QueryInterface E_NOINTERFACE riid=%ls", szIid);
}

说清楚分量:StringFromGUID2 只是把 16 字节格式化成 38 个字符的纯内存操作,和那条闸门原本要挡的 OpenProcess + 令牌查询 + GetWindowTextW 不是一个量级,实测开销可以忽略。加它的理由是房风一致与「不必再去论证这条路到底有多热」,不是性能。另两处(DllGetClassObject / CreateInstance)每进程至多被调几次,不加闸门无妨。愿意就顺手加,不加也不影响合并。

一处你没提到的价值

Activate() 委托给 ActivateEx(pThreadMgr, tfClientId, 0)TextService.cpp:1123-1125),所以日志里「宿主走的是旧式 Activate」与「真的以 dwFlags=0 激活」本来分不清。新加的 activeFlags 恰好能把这两种情况区分开——值得在用法说明里写一句。

CI

当前那次红的与你无关:失败项是 charset_def::tests::dangerous_keys_are_rejected_not_sanitized,main 上 wind-config 的一个跨平台守卫缺陷(用了 std::path::is_separator,Linux 下不认 \),已在 295c507f 修掉。你那次 CI 跑在修复之前。

我这就 update-branch 把新 main 并进来重跑(rerun 会重放旧的 merge SHA,拿不到新 base)。绿了就合。

@huanfeng
huanfeng merged commit 2d14d54 into huanfeng:main Sep 5, 2026
4 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 5, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants