使用C#编写的Windows Shell扩展程序

42

我希望编写一个简单的Windows shell扩展程序,将其添加到上下文菜单中,而C#是我最近使用最多的语言。它是否适合用于shell扩展程序?它是否易于访问接口?是否存在额外的开销导致菜单弹出速度变慢?

有没有好的入门指南可以提供?


10
时代在变化,这个问题的答案已经不再适用。通过4.0版本,你现在可以编写托管的Shell扩展程序。 - user1228
3
您可以使用.NET 4.0编写Shell扩展,但它目前还没有得到官方支持。 - Juozas Kontvainis
13
不理解为什么这个问题被关闭了,编写Windows的Shell扩展是非常常见的编程任务,我认为该问题非常合理。参考:http://www.codeproject.com/KB/shell/columnhandler.aspx (关于使用C#编写列处理程序)。 - Abel
6
今天是否支持使用.NET 4编写Windows Shell扩展? 微软尚未完全测试涉及托管Shell扩展的所有情况,并且尚未确定它是否会长期支持托管Shell扩展。因此,微软不会支持托管Shell扩展,并建议不要编写它们。 - Paolo Moretti
3个回答

26

3
可以做到,但这是个不好的想法。我知道有些微软开发团队在用C#编写Shell扩展后最后还是要改成C++本地代码重新编写。 - pm100
17
最新的.Net 4.0运行时支持与早期的.Net运行时同时进行进程内加载,包括所有未来的运行时。请参见以下摘录 http://msdn.microsoft.com/en-us/magazine/ee819091.aspx "由于可以在任何其他运行时中进程内加载多个运行时,因此我们现在可以提供编写托管Shell扩展的一般支持,即使这些扩展在计算机上与任意应用程序一起进程内运行。" - logicnp
作者是否意味着放弃支持在4.0之前的框架编写的应用程序是可以接受的? - GSerg
2
@logicnp Raymond Chen 终于对那个问题进行了跟进 - GSerg
没有规定禁止在.NET中编写进程外扩展,只要你能够弄清楚如何做并遵循引用计数合同即可。 - SamB

18

实施进程扩展的指导

版本冲突

版本冲突可能是由于运行时不支持在单个进程中加载多个运行时版本而引起的。 CLR 4.0 版本之前的版本属于此类。如果加载一个运行时版本会排除其他相同运行时版本的加载,当主机应用程序或另一个进程内扩展使用冲突版本时,这可能会创建冲突。在与另一个进程内扩展存在版本冲突的情况下,冲突可能难以重现,因为故障需要正确的冲突扩展,并且故障模式取决于加载冲突扩展的顺序。
考虑使用 CLR 4.0 版本之前的 CLR 版本编写的进程内扩展。计算机上使用文件打开对话框的每个应用程序都可能将对话框的托管代码及其附带的 CLR 依赖项加载到应用程序的进程中。首先加载 CLR 4.0 版本之前的版本的应用程序或扩展将限制该进程随后可以使用的 CLR 版本。如果使用与冲突 CLR 版本构建的托管应用程序,则扩展可能无法正确运行并可能导致应用程序失败。反之,如果扩展是第一个在进程中加载的,并且在此之后尝试启动冲突的托管代码版本(例如,托管应用程序或正在运行的应用程序按需加载 CLR),则操作将失败。对于用户而言,似乎应用程序的某些功能随机停止工作,或者应用程序神秘地崩溃。
请注意,CLR 4.0 版本或更高版本通常不容易出现版本问题,因为它们被设计为彼此共存,并且与大多数 CLR 4.0 版本之前的版本(除了版本 1.0 无法与其他版本共存)共存。但是,除版本冲突外,本主题中还可能出现其他问题。

性能问题

运行时的性能问题可能会在加载到进程中时导致显著的性能损失。性能损失可以表现为内存使用、CPU使用、经过时间甚至是地址空间消耗。已知CLR、JavaScript/ECMAScript和Java是高影响力的运行时。由于内部扩展可以加载到许多进程中,并且通常在性能敏感的时刻(例如准备将菜单显示给用户时)进行,高影响力的运行时可能会对整体响应性产生负面影响。

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

如果运行时即使在卸载使用该运行时的扩展后仍保持加载或以其他方式继续消耗资源,则该运行时不适合用于扩展。

特定于.NET Framework的问题

以下部分讨论了使用托管代码作为扩展时遇到的问题示例。这些并不是您可能遇到的所有可能问题的完整列表。这里讨论的问题既是托管代码不支持扩展的原因,也是在评估使用其他运行时时要考虑的问题。
- 重新进入 当CLR阻止单线程公寓(STA)线程,例如由于Monitor.Enter、WaitHandle.WaitOne或争用锁语句,CLR在其标准配置中等待时会进入嵌套消息循环。许多扩展方法禁止处理消息,这种不可预测和意外的重新进入可能导致难以重现和诊断的异常行为。
- 多线程公寓 CLR为组件对象模型(COM)对象创建运行时可调用包装器。这些相同的运行时可调用包装器稍后由CLR的终结器销毁,终结器是多线程公寓(MTA)的一部分。将代理从STA移动到MTA需要进行编组,但不是所有扩展使用的接口都可以进行编组。
- 非确定性对象生存期 CLR具有比本机代码更弱的对象生存期保证。许多扩展对对象和接口具有引用计数要求,CLR采用的垃圾回收模型无法满足这些要求。 如果CLR对象获取对COM对象的引用,则Runtime Callable Wrapper持有的COM对象引用直到垃圾回收器回收Runtime Callable Wrapper时才被释放。非确定性释放行为可能与某些接口合同冲突。例如,IPersistPropertyBag::Load方法要求在Load方法返回时不保留对属性包的引用。 如果将CLR对象引用返回给本机代码,则Runtime Callable Wrapper在其最后一次调用Release时放弃对CLR对象的引用,但底层CLR对象直到垃圾回收时才会被终结。非确定性终结行为可能与某些接口合同冲突。例如,缩略图处理程序需要在引用计数降至零时立即释放所有资源。

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

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

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

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

  • 与在 shell\verb\command 子键下注册的 DelegateExecute 条目相关联的 IExecuteCommand。
  • 与在 shell\verb\DropTarget 子键下注册的 CLSID 相关联的 IDropTarget。
  • 与在 shell\verb 子键下注册的 CommandStateHandler 条目相关联的 IExplorerCommandState。

SharpShell

SharpShell使用.NET Framework轻松创建Windows Shell扩展。

源代码托管在https://github.com/dwmkerr/sharpshell上,您可以在此处或那里发布问题和功能请求。 支持的扩展

您可以使用SharpShell构建以下任何扩展:

  • Shell上下文菜单
  • 图标处理程序
  • 信息提示处理程序
  • 拖放处理程序
  • 预览处理程序
  • 图标覆盖处理程序
  • 缩略图处理程序
  • 属性表扩展

使用SharpShell的项目
1. Trello上下文菜单
2. REAL Shuffle Player 2.0

CodeProject上的文章系列


16
虽然这可能看起来像是个广告,但EZShellExtensions是一款非常好用的(但不免费)C# shell扩展开发框架。您只需编写约20行代码即可创建一个简单的上下文菜单扩展程序,最重要的是,无需处理COM接口。我的公司使用它(以及他们的命名空间扩展框架)为数以万计的客户提供了一组扩展程序,并且据我所知,我们从未遇到过上述CLR冲突的问题。
以下是一个示例,演示了EZShellExtensions的易用性:
[Guid("00000000-0000-0000-0000-000000000000"), ComVisible(true)]
[TargetExtension(".txt", true)]
public class SampleExtension : ContextMenuExtension
{
   protected override void OnGetMenuItems(GetMenuitemsEventArgs e)
   {
      e.Menu.AddItem("Sample Extension", "sampleverb", "Status/help text");
   }

   protected override bool OnExecuteMenuItem(ExecuteItemEventArgs e)
   {
      if (e.MenuItem.Verb == "sampleverb")
         ; // logic
      return true;
   }

   [ComRegisterFunction]
   public static void Register(Type t)
   {
      ContextMenuExtension.RegisterExtension(typeof(SampleExtension));
   }

   [ComUnregisterFunction]
   public static void UnRegister(Type t)
   {
      ContextMenuExtension.UnRegisterExtension(typeof(SampleExtension));
   }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接