有关实现In-Process扩展的指南

进程内扩展将加载到触发它们的任何进程中。 例如,Shell 命名空间扩展可以加载到直接或间接访问 Shell 命名空间的任何进程中。 Shell 命名空间由许多 Shell 操作使用,例如显示通用文件对话框、通过其关联的应用程序启动文档,或获取用于表示文件的图标。 由于进程内扩展可以加载到任意进程中,因此必须注意它们不会对主机应用程序或其他进程内扩展产生负面影响。

特别需要注意的一个运行时是公共语言运行时 (CLR) ,也称为托管代码.NET FrameworkMicrosoft 建议不要将托管进程内扩展写入 Windows 资源管理器或 Windows Internet Explorer,并且不将其视为受支持的方案。

本主题讨论在确定 CLR 以外的任何运行时是否适合进程内扩展使用时要考虑的因素。 其他运行时的示例包括 Java、Visual Basic、JavaScript/ECMAScript、Delphi 和 C/C++ 运行时库。 本主题还提供了进程内扩展中不支持托管代码的一些原因。

版本冲突

版本冲突可能由不支持在单个进程中加载多个运行时版本的运行时引起。 4.0 版之前的 CLR 版本属于此类别。 如果加载某个版本的运行时排除了加载同一运行时的其他版本,则如果主机应用程序或其他进程内扩展使用冲突版本,则可能导致冲突。 如果版本与其他进程内扩展发生冲突,则冲突可能难以重现,因为故障需要正确的冲突扩展,故障模式取决于冲突扩展的加载顺序。

请考虑使用版本 4.0 之前的 CLR 版本编写的进程内扩展。 计算机上使用文件 “打开 ”对话框的每个应用程序都可能将对话框的托管代码及其助理 CLR 依赖项加载到应用程序的进程中。 首先将 4.0 之前的 CLR 版本加载到应用程序的进程中的应用程序或扩展会限制该进程随后可以使用哪个版本的 CLR。 如果具有“ 打开 ”对话框的托管应用程序是在 CLR 的冲突版本上构建的,则扩展可能无法正确运行,并可能导致应用程序中的故障。 相反,如果扩展是进程中第一个加载的扩展,并且有冲突的托管代码版本在 (可能为托管应用程序或正在运行的应用程序按需加载 CLR) 之后尝试启动,则操作将失败。 对用户来说,应用程序的某些功能似乎随机停止工作,或者应用程序神秘地崩溃。

请注意,等于或高于版本 4.0 的 CLR 版本通常不会受到版本控制问题的影响,因为它们旨在彼此共存,并且与大多数 4.0 之前的 CLR ((版本 1.0 除外)共存,而版本 1.0 不能与其他版本) 共存。 但是,除了版本冲突之外,可能会出现其他问题,如本主题的其余部分所述。

性能问题

运行时在加载到进程中时,可能会出现性能问题,从而造成严重的性能损失。 性能损失可能以内存使用率、CPU 使用率、已用时间甚至地址空间消耗的形式出现。 众所周知,CLR、JavaScript/ECMAScript 和 Java 是影响最大的运行时。 由于进程内扩展可以加载到许多进程中,并且通常在性能敏感 ((例如,在准备要显示用户) 菜单时)执行此操作,因此影响较高的运行时可能会对整体响应能力产生负面影响。

消耗大量资源的高影响运行时可能会导致主机进程或其他进程内扩展失败。 例如,高影响运行时在其堆中消耗数百兆字节的地址空间可能会导致主机应用程序无法加载大型数据集。 此外,由于进程内扩展可以加载到多个进程中,因此单个扩展中的高资源消耗可能在整个系统中迅速成倍增加的资源消耗量。

如果运行时保持加载状态,或者即使使用该运行时的扩展已卸载,该运行时仍继续消耗资源,则该运行时不适合在扩展中使用。

特定于.NET Framework的问题

以下部分讨论使用托管代码进行扩展时发现的问题的示例。 它们不是你可能遇到的所有可能问题的完整列表。 此处讨论的问题都是扩展中不支持托管代码的原因,也是评估其他运行时使用时要考虑的问题。

重新进入

当 CLR 阻止单线程单元 (STA) 线程时,例如,由于 Monitor.Enter、WaitHandle.WaitOne 或争用 lock 语句,CLR 在其标准配置中,在等待时进入嵌套消息循环。 许多扩展方法被禁止处理消息,这种不可预知和意外的重新进入可能会导致异常行为,难以重现和诊断。

多线程公寓

CLR 为组件对象模型 (COM) 对象创建 运行时可调用包装器 。 这些相同的运行时可调用包装器稍后会被 CLR 的终结器销毁,该终结器是 MTA) 多线程单元 (的一部分。 将代理从 STA 移动到 MTA 需要封送处理,但扩展使用的所有接口都无法封送。

非确定性对象生存期

与本机代码相比,CLR 的对象生存期保证较弱。 许多扩展对对象和接口都有引用计数要求,CLR 采用的垃圾回收模型无法满足这些要求。

  • 如果 CLR 对象获取对 COM 对象的引用,则在对运行时可调用包装进行垃圾回收之前,不会释放运行时可调用包装器持有的 COM 对象引用。 不确定的发布行为可能会与某些接口协定冲突。 例如, IPersistPropertyBag::Load 方法要求对象在 Load 方法返回时不保留对属性包的引用。
  • 如果 CLR 对象引用返回到本机代码,则运行时可调用包装器在进行运行时可调用包装器对 Release 的最后一次调用时放弃对 CLR 对象的引用,但在进行垃圾回收之前,基础 CLR 对象不会最终确定。 不确定的终结可能会与某些接口协定冲突。 例如,缩略图处理程序需要当资源引用计数降至零时立即释放所有资源。

托管代码和其他运行时的可接受使用

可以使用托管代码和其他运行时来实现进程外扩展。 进程外 Shell 扩展的示例包括:

  • 预览处理程序
  • 基于命令行的操作,例如在 shell\谓词\命令 子项下注册的操作。
  • 在本地服务器中实现的 COM 对象,用于允许进程外激活的 Shell 扩展点。

某些扩展可以作为进程内扩展或进程外扩展实现。 如果这些扩展不符合进程内扩展的这些要求,则可以将这些扩展实现为进程外扩展。 以下列表显示了可作为进程内扩展或进程外扩展实现的扩展示例:

  • 在 shell\谓词\命令子项下注册的 DelegateExecute 条目关联的 IExecuteCommand
  • 与在 shell\谓词\DropTarget 子项下注册的 CLSID 关联的 IDropTarget
  • 在 shell\谓词子项下注册的 CommandStateHandler 条目关联的 IExplorerCommandState