结论先放这儿:不是菜单没注册,不是命令 ID 撞了,是外壳扩展里一句多余的判空,把菜单的第 0 项直接判了死刑。这事到 0.34-15 才补上。
现象是这样的:用户装了七亿压缩(7eZip)之后,右键菜单里「压缩到压缩包…」这一项,怎么点都没反应;旁边「立即压缩为.zip」点着却好好的。报上来的原话就一句「点了没反应」,没人说得出其中的规律。
我一开始怀疑的是老毛病——菜单项没注册上,或者命令 ID 跟别人撞了。0.34-12/13 才刚处理过一次多重选择下菜单重复的问题(那次是注册表里静态菜单项的 MultiSelectModel=Single 护栏,在版本回退时被带丢了),所以我第一反应就是注册表又出问题。reg query 一通查,菜单项在,护栏也在,排除。
后来又怀疑 handler 根本没被调起来。那阵子的测法是 Start-Process 直接拉进程看行为——结果怎么都复现不出来,菜单正常响应。现在回头看,这一步是白走的:外壳扩展跑在 explorer 进程里,在自己的测试进程里调 COM,跟真实右键走的路不是一回事,得走真 COM 的 InvokeCommand 才能命中。
在真机上一项一项走 InvokeCommand,才看出规律:失灵的就是第 0 项,后面每一项都正常。只有第 0 项坏,这个特征太有辨识度了,反过来一想就明白问题出在「0」上。
代码里 IContextMenu::InvokeCommand 当时是这么写的:
if (!pici->lpVerb) return E_FAIL;写这行的人(别问,也是我)当时想的是「lpVerb 为空就别处理」。问题是,在整数动词这条路上,第 0 条菜单项的 lpVerb 指针数值上就是 NULL——它不是一个字符串,是一个索引,第 0 项就是 0,指针本身就是空。于是第 0 项永远被判成「空」,直接 return,后面几行根本走不到。
修法不复杂,把判据换成能区分动词类型的写法:HIWORD(lpVerb) != 0——高位不为 0 说明是字符串动词,为 0 就是整数索引。不能拿指针判空,这个结论的代价是一个版本的 bug。
改完那一项就活了。另外有个测试上的教训也跟着记住了:测外壳扩展必须走真的 COM 的 InvokeCommand,用别的进程间接去调,第 0 项这类坑永远复现不出来——后面再动菜单相关的东西,我们测试里都把这条加上了。
之前写过一篇讲那个压缩软件怎么从零做起来的,右键菜单这一摊子只是其中一段。软件本身在七亿压缩的站点上,装完不弹广告、不改主页。
为一行判空搭进去一个版本,说出去不太好听。
评论