我想编写一个简单的 Windows shell 扩展来添加到上下文菜单中,而 C# 是我这些天最常用的语言。它是 shell 扩展的一个不错的选择吗?界面是否易于使用?是否有额外的开销导致菜单弹出速度较慢?
任何人都有很好的入门指南?
我想编写一个简单的 Windows shell 扩展来添加到上下文菜单中,而 C# 是我这些天最常用的语言。它是 shell 扩展的一个不错的选择吗?界面是否易于使用?是否有额外的开销导致菜单弹出速度较慢?
任何人都有很好的入门指南?
Raymond 的帖子:不要在托管代码中编写进程内 shell 扩展。
最近的后续行动:既然 .NET Framework 版本 4 支持进程内并行运行时,那么现在可以在托管代码中编写 shell 扩展了吗?
底线是,不,这不行:
实现进程内扩展的指南已经过修订,它继续建议不要在托管代码中编写 shell 扩展和 Internet Explorer 扩展(以及其他类型的进程内扩展),即使您使用的是版本 4 或更高版本。
版本冲突
不支持在单个进程中加载多个运行时版本的运行时可能会出现版本冲突。CLR 4.0 之前的版本属于这一类。如果一个运行时版本的加载排除了同一运行时其他版本的加载,如果宿主应用程序或另一个进程内扩展使用冲突版本,这可能会产生冲突。在版本与另一个进程内扩展冲突的情况下,冲突可能难以重现,因为失败需要正确的冲突扩展,并且失败模式取决于加载冲突扩展的顺序。
考虑使用 4.0 之前的 CLR 版本编写的进程内扩展。计算机上使用文件打开对话框的每个应用程序都可能将对话框的托管代码及其附带的 CLR 依赖项加载到应用程序的进程中。首先将 CLR 的 4.0 之前版本加载到应用程序进程中的应用程序或扩展限制了该进程随后可以使用的 CLR 版本。如果带有“打开”对话框的托管应用程序是基于 CLR 的冲突版本构建的,则扩展可能无法正确运行,并可能导致应用程序出现故障。反过来,如果扩展是第一个加载到进程中的,并且托管代码的冲突版本在此之后尝试启动(可能托管应用程序或正在运行的应用程序按需加载 CLR),则操作失败。对用户来说,应用程序的某些功能似乎随机停止工作,或者应用程序神秘地崩溃了。
请注意,等于或高于 4.0 版的 CLR 版本通常不会受到版本控制问题的影响,因为它们被设计为相互共存并与大多数 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 进行垃圾回收之前,不会释放 Runtime Callable Wrapper 持有的 COM 对象引用。非确定性发布行为可能与某些接口契约发生冲突。例如,IPersistPropertyBag::Load 方法要求在 Load 方法返回时对象不保留对属性包的引用。
- 如果 CLR 对象引用返回到本机代码,则在 Runtime Callable Wrapper 对 Release 的最终调用时,Runtime Callable Wrapper 放弃其对 CLR 对象的引用,但底层 CLR 对象在被垃圾回收之前不会最终确定。非确定性最终确定可能与某些接口契约发生冲突。例如,缩略图处理程序需要在引用计数降至零时立即释放所有资源。
托管代码和其他运行时的可接受用途
使用托管代码和其他运行时来实现进程外扩展是可以接受的。进程外 Shell 扩展的示例包括:
- 预览处理程序
- 基于命令行的操作,例如在 shell\verb\command 子键下注册的操作。
- 在本地服务器中实现的 COM 对象,用于允许进程外激活的 Shell 扩展点。
一些扩展可以实现为进程内或进程外扩展。如果这些扩展不满足进程内扩展的这些要求,您可以将它们实现为进程外扩展。以下列表显示了可以作为进程内或进程外扩展实现的扩展示例:
- IExecuteCommand 与在 shell\verb\command 子项下注册的 DelegateExecute 条目相关联。
- IDropTarget 与在 shell\verb\DropTarget 子项下注册的 CLSID 关联。
- IExplorerCommandState 与在 shell\verb 子项下注册的 CommandStateHandler 条目相关联。
SharpShell 使使用 .NET 框架创建 Windows Shell 扩展变得容易。
源代码托管在https://github.com/dwmkerr/sharpshell - 您可以在这里或那里发布问题和功能请求。支持的扩展
您可以使用 SharpShell 构建以下任何扩展:
使用 SharpShell 的项目
1. Trello 上下文菜单
2. REAL Shuffle Player 2.0
CodeProject 上的文章系列
冒着看起来像个骗子的风险,EZShellExtensions是一个很棒的(但不是免费的)框架,用于在 C# 中进行 shell 扩展开发。你可以用大约 20 行代码编写一个简单的上下文菜单扩展,而且最重要的是,永远不必弄乱 COM 接口。我的公司将它(和他们的命名空间扩展框架)用于目前由数以万计的客户使用的一组扩展,而且,就其价值而言,我们从未遇到过上述 CLR 冲突的问题。
这是一个快速示例,以显示它是多么容易:
[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));
}
}