Расширение оболочки Windows с C#
Я хотел написать простое расширение оболочки Windows для добавления в контекстное меню, и C# - это язык, которым я больше всего пользуюсь в наши дни. Это достойный выбор для расширения оболочки? Легко ли получить доступ к интерфейсам? Существуют ли дополнительные накладные расходы, из-за которых меню медленнее всплывает?
У кого-нибудь есть хорошие указания для начала?
3 ответа
Пост Раймонда: не пишите внутрипроцессные расширения оболочки в управляемом коде.
Недавнее продолжение: теперь, когда версия.NET Framework 4 поддерживает параллельные среды выполнения в процессе, можно ли писать расширения оболочки в управляемом коде?
Суть в том, нет, это не хорошо:
Руководство по реализации внутрипроцессных расширений было пересмотрено, и в нем продолжаются рекомендации по написанию расширений оболочки и расширений Internet Explorer (и других типов внутрипроцессных расширений) в управляемом коде, даже если вы используете версию 4 или выше.
Руководство по внедрению расширений в процессе
Конфликты версий
Конфликт версий может возникнуть из-за среды выполнения, которая не поддерживает загрузку нескольких версий среды выполнения в рамках одного процесса. Версии CLR до версии 4.0 попадают в эту категорию. Если загрузка одной версии среды выполнения исключает загрузку других версий той же среды выполнения, это может создать конфликт, если приложение хоста или другое внутреннее расширение использует конфликтующую версию. В случае конфликта версий с другим внутрипроцессным расширением конфликт может быть трудно воспроизвести, поскольку для сбоя требуются правильные конфликтующие расширения, а режим сбоя зависит от порядка загрузки конфликтующих расширений.
Рассмотрим внутреннее расширение, написанное с использованием версии CLR до версии 4.0. Каждое приложение на компьютере, использующее диалоговое окно "Открыть файл", потенциально может иметь управляемый код диалогового окна и его зависимую CLR-зависимость, загруженную в процесс приложения. Приложение или расширение, которое первым загружает версию CLR до 4.0 в процесс приложения, ограничивает, какие версии CLR могут впоследствии использоваться этим процессом. Если управляемое приложение с диалоговым окном "Открыть" построено на конфликтующей версии CLR, расширение может работать некорректно и вызывать сбои в приложении. И наоборот, если расширение загружается первым в процессе, и конфликтующая версия управляемого кода пытается запустить его после этого (возможно, управляемое приложение или работающее приложение загружает CLR по требованию), операция завершается неудачей. Пользователю кажется, что некоторые функции приложения случайно перестают работать или приложение таинственным образом вылетает.
Обратите внимание, что версии CLR, равные или более поздние, чем версия 4.0, как правило, не подвержены проблеме управления версиями, поскольку они предназначены для сосуществования друг с другом и с большинством версий CLR, выпущенных до 4.0, (за исключением версии 1.0, которая не может сосуществовать с другими версиями). Однако могут возникнуть проблемы, отличные от конфликтов версий, как обсуждалось в оставшейся части этого раздела.
Проблемы с производительностью
Проблемы с производительностью могут возникнуть во время выполнения, которое приводит к значительному снижению производительности при загрузке в процесс. Нарушение производительности может быть в форме использования памяти, использования процессора, истекшего времени или даже использования адресного пространства. CLR, JavaScript/ECMAScript и Java, как известно, являются эффективными средами исполнения. Поскольку внутрипроцессные расширения могут загружаться во многие процессы и часто выполняются в моменты, чувствительные к производительности (например, при подготовке меню для отображения пользователю), время выполнения с высокой степенью воздействия может негативно повлиять на общую отзывчивость.
Мощная среда выполнения, которая потребляет значительные ресурсы, может вызвать сбой в хост-процессе или другом внутрипроцессном расширении. Например, динамическая среда выполнения, которая потребляет сотни мегабайт адресного пространства для своей кучи, может привести к тому, что приложение хоста не сможет загрузить большой набор данных. Кроме того, поскольку внутрипроцессные расширения могут загружаться в несколько процессов, высокое потребление ресурсов в одном расширении может быстро перерасти в высокое потребление ресурсов во всей системе.
Если среда выполнения остается загруженной или иным образом продолжает потреблять ресурсы, даже если расширение, которое использует эту среду выполнения, выгружено, то эта среда выполнения не подходит для использования в расширении.
Проблемы, специфичные для.NET Framework
В следующих разделах обсуждаются примеры проблем, обнаруженных при использовании управляемого кода для расширений. Они не являются полным списком всех возможных проблем, с которыми вы можете столкнуться. Обсуждаемые здесь проблемы являются как причинами того, что управляемый код не поддерживается в расширениях, так и моментами, которые следует учитывать при оценке использования других сред выполнения.
Re-entrancy
Когда CLR блокирует однопотоковый поток (STA), например, из-за оператора Monitor.Enter, WaitHandle.WaitOne или утверждения о блокировке, CLR в своей стандартной конфигурации входит во вложенный цикл обработки сообщений, ожидая его. Многим методам расширения запрещено обрабатывать сообщения, и это непредсказуемое и неожиданное повторное появление может привести к аномальному поведению, которое трудно воспроизвести и диагностировать.Многопоточная квартира CLR создает вызываемые оболочки во время выполнения для объектов объектной модели компонентов (COM). Эти те же самые Обозреваемые Обертки Runtime уничтожаются позже финализатором CLR, который является частью многопоточной квартиры (MTA). Перемещение прокси с STA на MTA требует маршалинга, но не все интерфейсы, используемые расширениями, могут быть маршалированы.
Недетерминированное время жизни объекта
CLR имеет более слабые гарантии времени жизни объекта, чем собственный код. Многие расширения предъявляют требования к количеству ссылок на объекты и интерфейсы, и модель сборки мусора, используемая CLR, не может удовлетворить эти требования.
- Если объект CLR получает ссылку на объект COM, ссылка на объект COM, хранящаяся в Callable Wrapper времени выполнения, не освобождается до тех пор, пока функция Callable Wrapper времени выполнения не будет собрана сборщиком мусора. Недетерминированное поведение при выпуске может конфликтовать с некоторыми интерфейсными контрактами. Например, метод IPersistPropertyBag::Load требует, чтобы объект не сохранял ссылки на пакет свойств при возврате метода Load.
- Если ссылка на объект CLR возвращается к собственному коду, Runtime Callable Wrapper освобождает свою ссылку на объект CLR, когда выполняется последний вызов Release для Runtime Callable Wrapper, но базовый объект CLR не завершается до тех пор, пока не будет собран мусор. Недетерминированная финализация может конфликтовать с некоторыми интерфейсными контрактами. Например, обработчики миниатюр требуются для немедленного освобождения всех ресурсов, когда их счетчик ссылок падает до нуля.
Допустимое использование управляемого кода и других сред выполнения
Для реализации внепроцессных расширений допустимо использовать управляемый код и другие среды выполнения. Примеры внешних расширений оболочки включают следующее:
- Предварительные обработчики
- Действия на основе командной строки, такие как те, которые зарегистрированы в подразделах shell\verb\command.
- COM-объекты, реализованные на локальном сервере, для точек расширения Shell, которые допускают внепроцессную активацию.
Некоторые расширения могут быть реализованы как внутрипроцессные или как внепроцессные. Вы можете реализовать эти расширения как внепроцессные, если они не соответствуют этим требованиям для внутренних расширений. В следующем списке приведены примеры расширений, которые могут быть реализованы как расширения в процессе или вне процесса:
- IExecuteCommand, связанный с записью DelegateExecute, зарегистрированной в подразделе shell\verb\command.
- IDropTarget, связанный с CLSID, зарегистрированным в подразделе shell\verb\DropTarget.
- IExplorerCommandState, связанный с записью CommandStateHandler, зарегистрированной в подразделе shell \ verb.
SharpShell
SharpShell позволяет легко создавать расширения оболочки Windows с помощью.NET Framework.
Исходный код размещен на https://github.com/dwmkerr/sharpshell - вы можете размещать вопросы и запрашивать функции здесь или там. Поддерживаемые расширения
Вы можете использовать SharpShell для создания любого из следующих расширений:
- Контекстные меню оболочки
- Обработчики значков
- Обработчики подсказок
- Drop Handlers
- Обработчики предварительного просмотра
- Обработчики наложения значков
- Миниатюра Hanlders
- Расширения листа недвижимости
Проекты, использующие SharpShell
1. Контекстное меню Trello
2. РЕАЛЬНЫЙ Shuffle Player 2.0
Серия статей в CodeProject
- .NET Shell Extensions - контекстные меню оболочки
- .NET Shell Extensions - Обработчики значков оболочки
- .NET Shell Extensions - Обработчики подсказок для Shell
- .NET Shell Extensions - Обработчики удаления оболочки
- .NET Shell Extensions - Обработчики предварительного просмотра оболочки
- .NET Shell Extensions - Обработчики наложения значков оболочки
- .NET Shell Extensions - Обработчики миниатюр оболочки
- .NET Shell Extensions - Листы свойств Shell
С риском быть похожим на шилла, EZShellExtensions является прекрасной (но не бесплатной) средой для разработки расширений оболочки в C#. Вы можете написать простое расширение контекстного меню с примерно 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));
}
}