Skip to content

PPTCom3:开机长时间后首次进入或切换 PPT 时小组件不显示 #188

Description

@Alan-CRL

问题来源

论坛反馈: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++ 两边按时间合并成一条时间线。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions