问题来源
论坛反馈:https://forum.smart-teach.cn/d/1924-zhi-hui-jiao-inkeysde-pptxiao-zu-jian-you-shi-bu-xian-shi
用户反馈 PPT 小组件有时不显示,重启后可缓解。当前已知环境和症状:
- 系统:Windows 10
- Office:Microsoft PowerPoint 2019
- 机器曾安装过 WPS
- 现象:开机数小时后使用或切换 PPT 课件时,小组件不显示
- 后续补充:开机后约 2 小时内没有进入过 PPT 放映模式,再进入放映时就不显示
- 用户称上一版本没有类似情况,可以正常弹出 PPT 组件
初步排查范围
对比范围:20260206a -> 20260502a
重点相关文件:
PptCOM/PptCOM.cs
智绘教/IdtPlug-in.cpp
智绘教/PptCOM.dll
初步判断
当前更倾向于 C# 侧 PPTCom3 的 COM/ROT 绑定状态问题,而不是组件 UI 绘制或点击交互问题。
1. COM busy-retry 超时逻辑可能导致状态被置为未播放
86b1fac 引入了 PowerPoint COM busy-retry 逻辑。超时后会写入:
*pptTotalPage = -1
*pptCurrentPage = -1
而 C++ 侧小组件显示依赖 PptInfoState.TotalPage != -1。因此如果长时间空转后首次进入 PPT 放映时 PowerPoint/WPS/ROT 短暂 busy,诊断逻辑可能把状态打回 -1,最终表现为小组件不显示。
2. ProgID-based ROT 扫描在安装过 WPS 的环境中可能选错或提前去重
5854797 引入了 ProgID-based application moniker lookup:
PowerPoint.Application
KWPP.Application
Wpp.Application
WPP.Application
安装过 WPS 的机器上,ROT 中可能同时存在 Office/WPS 相关 moniker。新版会把 application moniker 直接作为候选对象,并在计算优先级前参与去重。若某个候选对象暂时无法取得 ActivePresentation / SlideShowWindow,后续同实例的 presentation moniker 可能被视作重复跳过,需要进一步验证。
建议的下一步调查方案
由于用户不方便进行多轮实时调试,建议制作一次性诊断版,启用本地日志后让用户正常使用到复现,再回传日志。
建议拆成:
opt/pptcom_trace_session.json
opt/pptcom_trace_cs.jsonl
opt/pptcom_trace_cpp.jsonl
opt/pptcom_trace_enable.json
C# 侧重点记录 COM/ROT 绑定、busy retry、cleanup、break/continue 原因;C++ 侧重点记录 PptComService() 返回值、PptInfoState 状态边沿变化、UI 是否触发展开/关闭。
预期验证目标
诊断日志应能判断问题属于以下哪一类:
- COM busy 超时导致 PPT 状态被置为
-1
- ROT 扫描在 Office/WPS 混装环境中选错对象或去重过早
- 权限隔离导致 ROT 无法取得目标 PowerPoint 实例
- C++ 侧已收到有效页码但 UI 没展开
- 其他 PowerPoint/WPS COM 异常路径
已按 `20260206a -> 20260502a` 摸排,没改代码,工作树仍是干净的。
结论倾向:最可疑的是 `86b1fac` 引入的 PowerPoint COM busy-retry 逻辑,也就是你记得的“防止 pptcom 死掉”那块。旧版遇到 `0x8001010A` 这类 PowerPoint 忙只打印并继续轮询;新版在 [PptCOM.cs](D:/Project/Inkeys/Repo/Inkeys2/PptCOM/PptCOM.cs:221) 增加了 10 秒超时,超时后会把 `TotalPage/CurrentPage` 写成 `-1`,部分路径会 `break` 或 `FullCleanup()`。而 UI 是否出现正是由 [IdtPlug-in.cpp](D:/Project/Inkeys/Repo/Inkeys2/智绘教/IdtPlug-in.cpp:2264) 的 `PptInfoState.TotalPage != -1` 驱动,所以“开机很久后第一次进入放映不显示”很可能是 COM 首次绑定时 PowerPoint/WPS/ROT 短暂忙或返回异常,被新版当成断联处理了。
第二个可疑点是 `5854797` 的 ProgID-based ROT 扫描。新版会解析 `PowerPoint.Application`、`KWPP.Application`、`Wpp.Application`、`WPP.Application`,并直接把 application moniker 当作候选对象,见 [PptCOM.cs](D:/Project/Inkeys/Repo/Inkeys2/PptCOM/PptCOM.cs:384) 和 [PptCOM.cs](D:/Project/Inkeys/Repo/Inkeys2/PptCOM/PptCOM.cs:605)。论坛里用户机器装过 WPS,这点和现象吻合。这里还有一个细节:候选 COM 对象在计算优先级前就进入去重列表,见 [PptCOM.cs](D:/Project/Inkeys/Repo/Inkeys2/PptCOM/PptCOM.cs:637),如果某个 WPS/Office application moniker 先被扫到但 `ActivePresentation` 暂时不可用,后续同实例的 presentation moniker 可能被当重复跳过。
`IdtPlug-in.cpp` 这轮变更主要是线程状态守卫、触摸/鼠标捕获和 UI 信号原子化;这些更像是“组件显示后交互”的问题源,不像“组件根本不显示”的主因。C++ 侧 [GetPptState](D:/Project/Inkeys/Repo/Inkeys2/智绘教/IdtPlug-in.cpp:457) 仍会在 `PptComService()` 返回后约 2 秒重试,所以它不是完全死掉,而是更像反复得不到有效 `TotalPage`。
外部帖子我读到的关键症状是:Win10、PowerPoint 2019、装过 WPS;开机数小时后使用或切换 PPT 小组件不显示,重启缓解;补充为“开机后 2 小时内没进入过 PPT 放映模式,再进入就不显示”。来源:[论坛帖](https://forum.smart-teach.cn/d/1924-zhi-hui-jiao-inkeysde-pptxiao-zu-jian-you-shi-bu-xian-shi),代码范围参考:[GitHub compare](https://github.com/Alan-CRL/Inkeys/compare/20260206a...20260502a)。
可以,下一步我会倾向做一个“一次性诊断版”,不要让用户反复远程配合。核心思路是把 PPTCom3 做成黑盒飞行记录器:用户只需要替换一次程序或 DLL,正常使用到复现,然后把日志文件发回来。
我建议记录成 `JSONL` 或简单文本,放到例如 `opt/pptcom_trace.log`,滚动限额 2 到 5 MB,避免无限写盘。重点记录这些点:
1. `PptCOM.cs` 里的 COM 绑定全过程
记录 `PptComService` 每轮关键状态:`bestPriority/targetPriority`、是否 `needRebind`、`pptApplication == null`、`pptActivePresentation == null`、`bindingEvents`、`forcePolling`、`TotalPage/CurrentPage`、每次 `break/continue/FullCleanup` 的原因。
2. ROT 扫描细节
在 `GetAnyActivePowerPoint` 中记录每个命中的 moniker:`displayName` 类型、是否 presentation file、是否 application moniker、是否重复、是否取到 `ActivePresentation`、是否取到 `SlideShowWindow`、最终优先级。
这个能直接判断是不是 WPS/Office moniker 抢占、重复去重过早、或者 ROT 里根本没目标。
3. COM 异常和 busy-retry 细节
在 `HandleBusyException` 里记录 `stage`、HRESULT、连续 busy 起点、距离超时还剩多久、最终是否触发“发送结束信号”。
这能验证我前面怀疑的 `10s busy timeout` 是否真把状态打成 `-1`。
4. C++ 侧状态变化
在 `IdtPlug-in.cpp` 记录 `CheckPptCom`、`PptComService()` 返回值、`PptInfoState.TotalPage/CurrentPage` 从 `-1` 到有效或有效到 `-1` 的边沿变化。这里不用每 500ms 写一次,只写变化,日志会清爽很多。
5. 环境快照
启动时写一次:Inkeys 版本、PptCOM 版本、系统版本、是否管理员、PowerPoint/WPS 进程列表、PowerPoint 路径、WPS 路径、相关 ProgID 解析出来的 CLSID。文件名和课件完整路径建议默认打码,只保留扩展名和 hash,避免用户隐私问题。
比“只写本地文件过程细节”更好的形式是:做一个可开关的诊断模式。比如检测 `opt/pptcom_trace_enable.flag` 或配置项存在才启用详细日志。这样可以发给用户一个诊断包,说明三步:放入文件、正常使用、复现后发回 `opt/pptcom_trace*.log`。
我会优先避免让用户装调试器或实时连线。这个问题最需要的是复现当刻的 COM/ROT 状态,而不是截图;一份好的 `PptCOM.cs + IdtPlug-in.cpp` 双侧日志基本就能判断是 busy 超时、ROT 选错对象、权限隔离,还是 UI 层没有响应。
对,用两个日志文件更稳。不要让 C# 和 C++ 争同一个 JSON 文件锁,尤其 PPTCom 是 COM/.NET 环境,C++ 侧又可能在高频线程里跑,单文件追加很容易出现锁等待、半行写入、编码差异或异常时 flush 不完整。
我建议这样设计:
- C# 侧:`opt/pptcom_trace_cs.jsonl`
- C++ 侧:`opt/pptcom_trace_cpp.jsonl`
- 公共会话文件:`opt/pptcom_trace_session.json`
日志本体用 `jsonl`,不是一个完整 JSON 数组。每行一条事件,追加写入,坏一行也不影响后面分析。
关键是两个日志要能对齐。`pptcom_trace_session.json` 里写一个 `sessionId`,例如 `20260530-143012-<pid>`,C++ 启动 PPTLinkageMain 时生成;C# 第一次 `Initialization()` 时读取同一个 session 文件。之后两边每条日志都带:
```json
{
"sessionId": "...",
"side": "cs",
"ts": "2026-05-30T14:30:12.345+08:00",
"pid": 1234,
"tid": 5678,
"event": "busy_retry",
"stage": "检查放映窗口",
"hr": "0x8001010A",
"totalPage": -1,
"currentPage": -1
}
C++ 侧不要高频每 500ms 都写,只写“边沿”和“决策点”:CheckPptCom 成功/失败、PptComService 返回、PptInfoState 从 -1 变有效或从有效变 -1、PPT UI 展开/关闭。C# 侧则写 COM/ROT 的细节:moniker 命中、优先级、busy retry、cleanup、break/continue 原因。
更进一步,建议加一个开关文件:opt/pptcom_trace_enable.json。不存在就完全不写,存在时才写诊断日志。这样诊断包可以让用户只做一次操作:放入启用文件,正常用到复现,把 pptcom_trace_*.jsonl 发回。
命名上我会避免叫两个普通 json,直接叫 jsonl 更准确;session.json 才是普通 JSON。这样既不会冲突,也方便后续写一个小脚本把 C# / C++ 两边按时间合并成一条时间线。
问题来源
论坛反馈:https://forum.smart-teach.cn/d/1924-zhi-hui-jiao-inkeysde-pptxiao-zu-jian-you-shi-bu-xian-shi
用户反馈 PPT 小组件有时不显示,重启后可缓解。当前已知环境和症状:
初步排查范围
对比范围:
20260206a -> 20260502a重点相关文件:
PptCOM/PptCOM.cs智绘教/IdtPlug-in.cpp智绘教/PptCOM.dll初步判断
当前更倾向于 C# 侧 PPTCom3 的 COM/ROT 绑定状态问题,而不是组件 UI 绘制或点击交互问题。
1. COM busy-retry 超时逻辑可能导致状态被置为未播放
86b1fac引入了 PowerPoint COM busy-retry 逻辑。超时后会写入:*pptTotalPage = -1*pptCurrentPage = -1而 C++ 侧小组件显示依赖
PptInfoState.TotalPage != -1。因此如果长时间空转后首次进入 PPT 放映时 PowerPoint/WPS/ROT 短暂 busy,诊断逻辑可能把状态打回-1,最终表现为小组件不显示。2. ProgID-based ROT 扫描在安装过 WPS 的环境中可能选错或提前去重
5854797引入了 ProgID-based application moniker lookup:PowerPoint.ApplicationKWPP.ApplicationWpp.ApplicationWPP.Application安装过 WPS 的机器上,ROT 中可能同时存在 Office/WPS 相关 moniker。新版会把 application moniker 直接作为候选对象,并在计算优先级前参与去重。若某个候选对象暂时无法取得
ActivePresentation/SlideShowWindow,后续同实例的 presentation moniker 可能被视作重复跳过,需要进一步验证。建议的下一步调查方案
由于用户不方便进行多轮实时调试,建议制作一次性诊断版,启用本地日志后让用户正常使用到复现,再回传日志。
建议拆成:
opt/pptcom_trace_session.jsonopt/pptcom_trace_cs.jsonlopt/pptcom_trace_cpp.jsonlopt/pptcom_trace_enable.jsonC# 侧重点记录 COM/ROT 绑定、busy retry、cleanup、break/continue 原因;C++ 侧重点记录
PptComService()返回值、PptInfoState状态边沿变化、UI 是否触发展开/关闭。预期验证目标
诊断日志应能判断问题属于以下哪一类:
-1已按 `20260206a -> 20260502a` 摸排,没改代码,工作树仍是干净的。 结论倾向:最可疑的是 `86b1fac` 引入的 PowerPoint COM busy-retry 逻辑,也就是你记得的“防止 pptcom 死掉”那块。旧版遇到 `0x8001010A` 这类 PowerPoint 忙只打印并继续轮询;新版在 [PptCOM.cs](D:/Project/Inkeys/Repo/Inkeys2/PptCOM/PptCOM.cs:221) 增加了 10 秒超时,超时后会把 `TotalPage/CurrentPage` 写成 `-1`,部分路径会 `break` 或 `FullCleanup()`。而 UI 是否出现正是由 [IdtPlug-in.cpp](D:/Project/Inkeys/Repo/Inkeys2/智绘教/IdtPlug-in.cpp:2264) 的 `PptInfoState.TotalPage != -1` 驱动,所以“开机很久后第一次进入放映不显示”很可能是 COM 首次绑定时 PowerPoint/WPS/ROT 短暂忙或返回异常,被新版当成断联处理了。 第二个可疑点是 `5854797` 的 ProgID-based ROT 扫描。新版会解析 `PowerPoint.Application`、`KWPP.Application`、`Wpp.Application`、`WPP.Application`,并直接把 application moniker 当作候选对象,见 [PptCOM.cs](D:/Project/Inkeys/Repo/Inkeys2/PptCOM/PptCOM.cs:384) 和 [PptCOM.cs](D:/Project/Inkeys/Repo/Inkeys2/PptCOM/PptCOM.cs:605)。论坛里用户机器装过 WPS,这点和现象吻合。这里还有一个细节:候选 COM 对象在计算优先级前就进入去重列表,见 [PptCOM.cs](D:/Project/Inkeys/Repo/Inkeys2/PptCOM/PptCOM.cs:637),如果某个 WPS/Office application moniker 先被扫到但 `ActivePresentation` 暂时不可用,后续同实例的 presentation moniker 可能被当重复跳过。 `IdtPlug-in.cpp` 这轮变更主要是线程状态守卫、触摸/鼠标捕获和 UI 信号原子化;这些更像是“组件显示后交互”的问题源,不像“组件根本不显示”的主因。C++ 侧 [GetPptState](D:/Project/Inkeys/Repo/Inkeys2/智绘教/IdtPlug-in.cpp:457) 仍会在 `PptComService()` 返回后约 2 秒重试,所以它不是完全死掉,而是更像反复得不到有效 `TotalPage`。 外部帖子我读到的关键症状是:Win10、PowerPoint 2019、装过 WPS;开机数小时后使用或切换 PPT 小组件不显示,重启缓解;补充为“开机后 2 小时内没进入过 PPT 放映模式,再进入就不显示”。来源:[论坛帖](https://forum.smart-teach.cn/d/1924-zhi-hui-jiao-inkeysde-pptxiao-zu-jian-you-shi-bu-xian-shi),代码范围参考:[GitHub compare](https://github.com/Alan-CRL/Inkeys/compare/20260206a...20260502a)。 可以,下一步我会倾向做一个“一次性诊断版”,不要让用户反复远程配合。核心思路是把 PPTCom3 做成黑盒飞行记录器:用户只需要替换一次程序或 DLL,正常使用到复现,然后把日志文件发回来。 我建议记录成 `JSONL` 或简单文本,放到例如 `opt/pptcom_trace.log`,滚动限额 2 到 5 MB,避免无限写盘。重点记录这些点: 1. `PptCOM.cs` 里的 COM 绑定全过程 记录 `PptComService` 每轮关键状态:`bestPriority/targetPriority`、是否 `needRebind`、`pptApplication == null`、`pptActivePresentation == null`、`bindingEvents`、`forcePolling`、`TotalPage/CurrentPage`、每次 `break/continue/FullCleanup` 的原因。 2. ROT 扫描细节 在 `GetAnyActivePowerPoint` 中记录每个命中的 moniker:`displayName` 类型、是否 presentation file、是否 application moniker、是否重复、是否取到 `ActivePresentation`、是否取到 `SlideShowWindow`、最终优先级。 这个能直接判断是不是 WPS/Office moniker 抢占、重复去重过早、或者 ROT 里根本没目标。 3. COM 异常和 busy-retry 细节 在 `HandleBusyException` 里记录 `stage`、HRESULT、连续 busy 起点、距离超时还剩多久、最终是否触发“发送结束信号”。 这能验证我前面怀疑的 `10s busy timeout` 是否真把状态打成 `-1`。 4. C++ 侧状态变化 在 `IdtPlug-in.cpp` 记录 `CheckPptCom`、`PptComService()` 返回值、`PptInfoState.TotalPage/CurrentPage` 从 `-1` 到有效或有效到 `-1` 的边沿变化。这里不用每 500ms 写一次,只写变化,日志会清爽很多。 5. 环境快照 启动时写一次:Inkeys 版本、PptCOM 版本、系统版本、是否管理员、PowerPoint/WPS 进程列表、PowerPoint 路径、WPS 路径、相关 ProgID 解析出来的 CLSID。文件名和课件完整路径建议默认打码,只保留扩展名和 hash,避免用户隐私问题。 比“只写本地文件过程细节”更好的形式是:做一个可开关的诊断模式。比如检测 `opt/pptcom_trace_enable.flag` 或配置项存在才启用详细日志。这样可以发给用户一个诊断包,说明三步:放入文件、正常使用、复现后发回 `opt/pptcom_trace*.log`。 我会优先避免让用户装调试器或实时连线。这个问题最需要的是复现当刻的 COM/ROT 状态,而不是截图;一份好的 `PptCOM.cs + IdtPlug-in.cpp` 双侧日志基本就能判断是 busy 超时、ROT 选错对象、权限隔离,还是 UI 层没有响应。 对,用两个日志文件更稳。不要让 C# 和 C++ 争同一个 JSON 文件锁,尤其 PPTCom 是 COM/.NET 环境,C++ 侧又可能在高频线程里跑,单文件追加很容易出现锁等待、半行写入、编码差异或异常时 flush 不完整。 我建议这样设计: - C# 侧:`opt/pptcom_trace_cs.jsonl` - C++ 侧:`opt/pptcom_trace_cpp.jsonl` - 公共会话文件:`opt/pptcom_trace_session.json` 日志本体用 `jsonl`,不是一个完整 JSON 数组。每行一条事件,追加写入,坏一行也不影响后面分析。 关键是两个日志要能对齐。`pptcom_trace_session.json` 里写一个 `sessionId`,例如 `20260530-143012-<pid>`,C++ 启动 PPTLinkageMain 时生成;C# 第一次 `Initialization()` 时读取同一个 session 文件。之后两边每条日志都带: ```json { "sessionId": "...", "side": "cs", "ts": "2026-05-30T14:30:12.345+08:00", "pid": 1234, "tid": 5678, "event": "busy_retry", "stage": "检查放映窗口", "hr": "0x8001010A", "totalPage": -1, "currentPage": -1 }C++ 侧不要高频每 500ms 都写,只写“边沿”和“决策点”:
CheckPptCom成功/失败、PptComService返回、PptInfoState从-1变有效或从有效变-1、PPT UI 展开/关闭。C# 侧则写 COM/ROT 的细节:moniker 命中、优先级、busy retry、cleanup、break/continue 原因。更进一步,建议加一个开关文件:
opt/pptcom_trace_enable.json。不存在就完全不写,存在时才写诊断日志。这样诊断包可以让用户只做一次操作:放入启用文件,正常用到复现,把pptcom_trace_*.jsonl发回。命名上我会避免叫两个普通
json,直接叫jsonl更准确;session.json才是普通 JSON。这样既不会冲突,也方便后续写一个小脚本把 C# / C++ 两边按时间合并成一条时间线。