我想编写一个简单的Windows shell扩展来添加到上下文菜单中,而C#是我最近使用的语言。它是shell扩展的一个不错的选择吗?接口是否易于使用?是否有额外的开销导致菜单弹出更慢?
任何人都有很好的入门指南?
答案 0 :(得分:25)
雷蒙德的帖子:Do not write in-process shell extensions in managed code。
底线是,不,它不合适:
Guidance for implementing in-process extensions已经过修订,并继续建议不要在托管代码中编写shell扩展和Internet Explorer扩展(以及其他类型的进程内扩展),即使您使用的是版本4或更高版本。
答案 1 :(得分:14)
冒着看起来像一个先令的风险,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));
}
}
答案 2 :(得分:11)
版本冲突
版本冲突可能通过不支持在单个进程中加载多个运行时版本的运行时产生。版本4.0之前的CLR版本属于此类别。如果加载一个版本的运行时阻止加载同一运行时的其他版本,则如果主机应用程序或另一个进程内扩展使用冲突版本,则会产生冲突。如果版本与另一个进程内扩展冲突,则冲突可能难以重现,因为失败需要正确的冲突扩展,而失败模式取决于加载冲突扩展的顺序。
考虑使用版本4.0之前的CLR版本编写的进程内扩展。计算机上使用文件“打开”对话框的每个应用程序都可能将对话框的托管代码及其附带的CLR依赖项加载到应用程序的进程中。首先将4.1版之前的CLR加载到应用程序进程中的应用程序或扩展程序限制了该进程随后可以使用哪个版本的CLR。如果具有“打开”对话框的托管应用程序构建在CLR的冲突版本上,则扩展可能无法正确运行并可能导致应用程序出现故障。相反,如果扩展是第一个在进程中加载并且托管代码的冲突版本在此之后尝试启动(可能是托管应用程序或正在运行的应用程序按需加载CLR),则操作将失败。对于用户来说,应用程序的某些功能似乎随机停止工作,或者应用程序神秘地崩溃。
请注意,CLR的版本等于或高于4.0版本通常不会受到版本控制问题的影响,因为它们旨在彼此共存并与大多数4.0之前版本的CLR共存(1.0版除外) ,不能与其他版本共存)。但是,如本主题的其余部分所述,可能会出现版本冲突以外的问题。
效果问题
运行时会出现性能问题,这些问题会在加载到进程中时造成严重的性能损失。性能损失可以是内存使用,CPU使用,已用时间甚至地址空间消耗的形式。众所周知,CLR,JavaScript / ECMAScript和Java是高影响力的运行时。由于进程内扩展可以加载到许多进程中,并且通常在性能敏感时刻(例如准备用户显示菜单时)这样做,因此影响较大的运行时会对整体响应能力产生负面影响。
消耗大量资源的高影响运行时可能会导致主机进程或其他进程内扩展失败。例如,为其堆消耗数百兆字节地址空间的高影响运行时可能导致主机应用程序无法加载大型数据集。此外,由于进程内扩展可以加载到多个进程中,因此单个扩展中的高资源消耗可以快速地扩展到整个系统的高资源消耗。
如果运行时仍然加载或以其他方式继续消耗资源,即使使用该运行时的扩展已卸载,那么该运行时也不适合在扩展中使用。
特定于.NET Framework的问题
以下各节讨论使用托管代码进行扩展时发现的问题示例。它们不是您可能遇到的所有可能问题的完整列表。这里讨论的问题既是扩展中不支持托管代码的原因,也是评估其他运行时使用时要考虑的要点。
<强>重入强>
当CLR阻塞单线程单元(STA)线程时(例如,由于Monitor.Enter,WaitHandle.WaitOne或竞争锁定语句),CLR在其标准配置中在等待时进入嵌套消息循环。禁止许多扩展方法处理消息,这种不可预测和意外的重入可能导致异常行为,难以重现和诊断。多线程公寓 CLR为组件对象模型(COM)对象创建运行时可调用包装器。这些相同的Runtime Callable Wrappers后来被CLR的终结器销毁,它是多线程单元(MTA)的一部分。将代理从STA移动到MTA需要编组,但并非所有扩展使用的接口都可以编组。
非确定性对象生命周期
CLR具有比本机代码更弱的对象生存期保证。许多扩展对对象和接口都有引用计数要求,CLR使用的垃圾收集模型无法满足这些要求。
- 如果CLR对象获得对COM对象的引用,则在运行时可调用包装器被垃圾收集之前,不会释放由运行时可调用包装器保存的COM对象引用。非确定性释放行为可能与某些接口契约冲突。例如,IPersistPropertyBag :: Load方法要求在Load方法返回时,对象不保留对属性包的引用。
- 如果将CLR对象引用返回到本机代码,则运行时可调用包装器在运行时可调用包装器对Release的最终调用时放弃对CLR对象的引用,但直到最后才确定基础CLR对象它是垃圾收集的。非确定性最终确定可能与某些接口契约冲突。例如,缩略图处理程序需要在引用计数降至零时立即释放所有资源。
托管代码和其他运行时的可接受使用
使用托管代码和其他运行时来实现进程外扩展是可以接受的。进程外Shell扩展的示例包括:
- 预览处理程序
- 基于命令行的操作,例如在shell \ verb \ command子键下注册的操作。
- 在本地服务器中实现的COM对象,用于允许进程外激活的Shell扩展点。
某些扩展可以作为进程内或进程外扩展来实现。如果这些扩展不满足进程内扩展的这些要求,则可以将这些扩展实现为进程外扩展。以下列表显示了可以作为进程内或进程外扩展实现的扩展示例:
- 与在shell \ verb \ command子项下注册的DelegateExecute条目关联的IExecuteCommand。
- 与在shell \ verb \ DropTarget子项下注册的CLSID关联的IDropTarget。
- 与在shell \ verb子项下注册的CommandStateHandler条目关联的IExplorerCommandState。
SharpShell使用.NET Framework轻松创建Windows Shell Extensions。
源代码托管在https://github.com/dwmkerr/sharpshell上 - 您可以在此处或那里发布问题和功能请求。 支持的扩展
您可以使用SharpShell构建以下任何扩展程序:
使用SharpShell的项目
1. Trello Context Menu
2. REAL Shuffle Player 2.0
CodeProject的文章系列