В данном разделе будет произведён обзор различных методов расширения среды Visual Studio. Подробно будет рассмотрено создание расширений вида Visual Studio Extension Package (пакет расширения Visual Studio), их отладка, регистрация и развёртывание на машине конечного пользователя.
Эта статья устарела. Обновленную версию этой статьи вы можете прочитать здесь.
Данный цикл статей посвящён разработке пакета-расширения для сред Visual Studio 2005/2008/2010/2012 на платформе .NET Framework с использованием языка C#. В статьях будут рассмотрены следующие вопросы:
Материал статей основан на опыте разработки плагина-расширения для статического анализатора PVS-Studio. Более детальное и полное описание затронутых в статьях тем доступно по приведённым в конце каждого раздела ссылкам на официальные материалы библиотеки MSDN и другие сторонние ресурсы.
Рассматривается будет только разработка подключаемых модулей для Visual Studio 2005 и выше. Это ограничение идет из-за того, что PVS-Studio работает в системах с VS2005 и выше. В свою очередь мы такое ограничение выбирали при разработке нашего продукта из-за того, что в VS2005 появилась новая модель API, которая не совместима с предыдущими версиями API среды.
Существует множество способов для расширения функционала Microsoft Visual Studio. На самом базовом уровне можно автоматизировать простые рутинные действия с помощью макросов. Для программной автоматизации простых действий с UI объектами среды, манипуляций пунктами в меню и т.п. можно использовать подключаемый модуль (Add-In).
Расширение встроенных редакторов среды возможно через MEF (Managed Extensibility Framework) компоненты (начиная с версии MSVS 2010). Для интеграции в Visual Studio крупных независимых компонентов лучше всего подходят расширения вида Extension Package (пакеты расширения, также известные как VSPackage). При этом VSPackage позволяют сочетать в себе автоматизацию управления компонентами IDE через объектную модель автоматизации с расширением среды через MEF (Managed Extensibility Framework) и Managed Package Framework классы (таких, как Package).
VSPackage модули также позволяют расширять саму модель автоматизации, предоставляя возможности для добавления в неё пользовательских объектов автоматизации. Такие объекты становятся доступны через модель автоматизации для других модулей-расширений, предоставляя им программный доступ к сторонним интегрированным пользовательским компонентам.
Первые версии плагина PVS-Studio (точнее 1.XX и 2.XX, когда наш продукт еще назывался Viva64), мы выпускали как Add-In. С версии PVS-Studio 3.00 мы переделали его на VSPackage. Причина перехода – нам стало "тесновато" в Add-In и было неудобно отлаживаться. Кроме того, хотелось иметь свой значок на экранной заставке Visual Studio!
В отличие от подключаемых модулей (Add-In), разработка пакета расширения среды потребует установки Microsoft Visual Studio SDK для целевой версии среды разработки. То есть для разработки пакета расширения под каждую версию Visual Studio потребует установки отдельного SDK.
В дальнейшем мы будем рассматривать версии 2005, 2008, 2010 и 2012. Установка Visual Studio SDK добавляет в стандартные шаблоны среды проект типа Visual Studio Package (пункт Other Project Types -> Extensibility). Данный шаблон сгенерирует простейший MSBuild проект для модуля расширения, позволяя задать язык разработки и заглушки для нескольких типовых компонентов (пункт меню, редактор, пользовательское окно).
Мы будет использовать C# проект (csproj) VSPackage, представляющий собой MSBuild проект динамически подключаемой библиотеки (dll). В нашем случае это managed assembly, содержащий также несколько специфичных для пакетов расширения Visual Studio сборочных XML узлов, таких VCST компилятор и IncludeInVSIX для последних версий Visual Studio.
Основным классом пакета-расширения Visual Studio должен быть класс-наследник от класса Microsoft.VisualStudio.Shell.Package. Этот базовый класс предоставляет managed-обёртки для интерфейсов взаимодействия с IDE, необходимых полноценному пакету расширения Visual Studio.
public sealed class MyPackage: Package
{
public MyPackage ()
{}
...
}
Класс Package предоставляет возможность переопределения базового метода Initialize. Метод Initialize получает управление в момент инициализации пакета расширения для текущей сессии IDE.
protected override void Initialize()
{
base.Initialize();
...
}
Инициализация модуля происходит при первом обращении к нему, а также может быть вызвана автоматически, например, при запуске IDE, при вхождении пользователя в заданный UI контекст (например, открытие проекта) и т.п.
Вообще очень важно понимать, как стартует и как завершается модуль расширения. Ведь может оказаться, что разработчик пытается использовать какой-то функционал Visual Studio, который в данный момент использовать нельзя. При разработке PVS-Studio у нас бывали ситуации, когда среда "била нас по рукам" за непонимание того, что при завершении Visual Studio нельзя "в лоб" показать message box с вопросом.
Задача отладки подключаемого модуля или расширения для среды разработки является не совсем типичной. Ведь сама эта среда используется и для разработки, и для отладки такого модуля. Подключение такого нестабильного нового модуля к IDE может привести к нестабильности работы самой среды разработки. Дополнительные неудобства создаст необходимость деинсталлировать каждый раз разрабатываемый модуль из среды разработки перед каждой отладкой его новой версии, что зачастую требует перезапуска самой среды (т.к. IDE может блокировать уже подключенный dll, который для отладки потребуется заменить новой версией).
Надо отметить, что отлаживать VSPackage значительно удобнее, чем Add-In. Это послужило одной из ключевых причин для смены используемой в PVS-Studio модели работы с Add-In на VSPackage.
Для решения перечисленных проблем при разработке и отладке пакетов VSPackage можно использовать экспериментальный экземпляр Visual Studio (experimental instance). Его можно запустить, добавив в строку аргументов запуска среды специальный параметр:
"C:\Program Files (x86)\Microsoft Visual Studio 10.0\
Common7\IDE\devenv.exe" /RootSuffix Exp
Экспериментальный экземпляр среды использует отдельную независимую ветку в системном реестре (experimental hive) для сохранения регистрационной информации установленных компонентов и настроек среды. Любые изменения в настройках IDE, регистрация или модификация новых компонентов в ветке Experimental Hive, никак не отразятся на том экземпляре среды, который используется непосредственно для разработки и отладки модуля (т.е. в основной базовой версии, запускаемой по умолчанию).
Visual Studio SDK предоставляет специальную утилиту для создания или очистки экспериментальных экземпляров — CreateExpInstance. Вызов CreateExpInstance для создания новой экспериментальной ветки выглядит следующим образом:
CreateExpInstance.exe /Reset /VSInstance=10.0 /RootSuffix=PVSExp
Выполнение этой команды создаст новую экспериментальную ветку реестра с суффиксом PVSExp в имени для 10 версии IDE (Visual Studio 2010) с предварительным сбросом всех настроек среды до значений по умолчанию. Путь до новой ветки в системном реестре будет выглядеть так:
HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\10.0PVSExp
Хотя по умолчанию при отладке в шаблонном проекте VSPackage используется суффикс Exp (и соответствующая ему ветка реестра), ничто не мешает создавать и другие экспериментальные ветки, соответственно с другими суффиксами имён. Для запуска экземпляра среды в созданной нами ранее новой экспериментальной ветке (содержащей PVSExp в имени) нужно выполнить:
"C:\Program Files (x86)\Microsoft Visual Studio 10.0\
Common7\IDE\devenv.exe" /RootSuffix PVSExp
Возможность создать на одной локальной машине несколько экспериментальных веток может быть полезна, например, для одновременной разработки нескольких пакетов расширения в изолированных друг от друга средах.
После установки SDK в меню программ также будет добавлена ссылка, позволяющая сбросить настройки экспериментального экземпляра IDE до значений по умолчанию (например, Reset the Microsoft Visual Studio 2010 Experimental Instance).
Чем быстрее вы разберетесь с тем, как работать с отладочным окружением, тем меньше у вас будет проблем с непониманием что, почему и как загружается при разработке плагина.
Регистрация пакета расширения требует регистрации непосредственно самого пакета, а также всех интегрируемых им в IDE компонентов (например, пункты меню, страницы настроек, пользовательские окна и т.п.). Регистрация осуществляется через создание соответствующих компонентам записей в основной ветке системного реестра Visual Studio.
Вся информация о необходимых для регистрации компонентах записывается в специальный файл pkgdef во время сборки VSPackage проекта на основании специальных атрибутов основного класса модуля (подкласс Package). Файл pkgdef также можно сгенерировать вручную с помощью утилиты CreatePkgDef. Данная утилита собирает регистрационную информацию о модуле методом .NET рефлексии через специальные атрибуты подкласса package. Рассмотрим эти атрибуты.
Атрибут PackageRegistration сообщает регистрационной утилите о том, что данный класс является модулем-расширением Visual Studio. После его обнаружения будет произведён поиск дополнительных регистрационных атрибутов.
[PackageRegistration(UseManagedResourcesOnly = true)]
Атрибут Guid задаёт уникальный идентификатор модуля-расширения, который затем будет использован для создания регистрационной под-ветки в системном реестре, в ветке Visual Studio.
[Guid("a0fcf0f3-577e-4c47-9847-5f152c16c02c")]
Атрибут InstalledProductRegistration позволяет добавить информацию в Help -> About диалог и на splash экран загрузки среды Visual Studio.
[InstalledProductRegistration("#110", "#112", "1.0",
IconResourceID = 400)]
Атрибут ProvideAutoLoad позволяет назначить автоматическую инициализацию модуля на активацию заданного UI контекста среды. При вхождении пользователя в данный контекст модуль будет подгружен и инициализирован автоматически. Приведём пример назначения инициализации модуля на открытие solution файла.
[ProvideAutoLoad("D2567162-F94F-4091-8798-A096E61B8B50")]
Значения GUID идентификаторов для различных контекстов IDE можно посмотреть в классе Microsoft.VisualStudio.VSConstants.UICONTEXT.
Атрибут ProvideMenuResource задаёт ID ресурсов пользовательских команд и меню для их регистрации в IDE.
[ProvideMenuResource("Menus.ctmenu", 1)]
Атрибут DefaultRegistryRoot задаёт путь для записи регистрационной информации в системном реестре. Начиная с Visual Studio 2010 данный атрибут можно не использовать, а соответствующая ему регистрационная информация должна быть записана в манифесте VSIX контейнера. Пример использования атрибута для регистрации модуля в Visual Studio 2008:
[DefaultRegistryRoot("Software\\Microsoft\\VisualStudio\\9.0")]
Регистрация других пользовательских компонентов, таких как инструментальные и документные окна, редакторы, страницы настроек и т.п., также требует добавления соответствующих им атрибутов для пользовательского подкласса Package. Мы будем рассматривать такие атрибуты по мере рассмотрения самих компонентов.
При необходимости добавления в системный реестр во время регистрации пакета расширения пользовательских ключей или необходимости добавления значений для уже существующих ключей, возможно создание пользовательских регистрационных атрибутов путём наследования от абстрактного класса RegistrationAttribute.
[AttributeUsage(AttributeTargets.Class, Inherited = true,
AllowMultiple = false)]
public class CustomRegistrationAttribute : RegistrationAttribute
{
}
Атрибут-наследник RegistrationAttribute обязан будет переопределить методы Register и Unregister, которые используются для модификации регистрационной информации в системном реестре.
Для добавления регистрационной информации в реестр можно воспользоваться утилитой RegPkg, которая автоматически зарегистрирует все перечисленные в переданном ей pkgdef файле компоненты в заданную через аргумент /root ветку реестра. Так, например, вызов RegPkg автоматически прописывается в проектах Visual Studio для регистрации разрабатываемого модуля в экспериментальной ветке реестра для удобства его отладки. После добавления всей регистрационной информации в реестр нужно запустить среду Visual Studio (devenv.exe) с параметром /setup для регистрации новых компонентов уже в самой IDE.
Прежде чем приступить к описанию процедуры развертывания, запомните важное правило:
При создании дистрибутива с разработанным вами плагином, каждый раз обязательно тестируйте его на машине без Visual Studio SDK, чтобы убедиться, что у обычного пользователя он корректно регистрируется в системе.
Сейчас, когда первые релизы PVS-Studio давно уже позади, у нас не бывает проблем с этим. Однако поначалу несколько неудачных версий попадали к пользователям.
Развёртывание плагина для сред Visual Studio 2005/2008 потребует запуска regpkg для pkgdef файла с указанием основной ветки реестра IDE либо добавления всех ключей из файла pkgdef в системный реестр вручную. Пример команды для автоматического добавления в реестр регистрационной информации из pkgdef файла (в одну строку):
RegPkg.exe /root:Software\Microsoft\VisualStudio\9.0Exp
"/pkgdeffile:obj\Debug\PVS-Studio-vs2008.pkgdef"
"C:\MyPackage\MyPackage.dll"
После добавления регистрационной информации в реестр необходимо запустить Visual Studio с параметром /setup, обычно это последний шаг процедуры инсталляции нового модуля.
Devenv.exe /setup
Запуск среды с данным ключом указывает Visual Studio на необходимость вычитывания метаданных о ресурсах пользовательских компонентов из всех доступных модулей-расширений для их корректного отображения в интерфейсе. Запуск devenv с данным ключом не открывает GUI окно IDE.
В PVS-Studio мы обходимся без запуска RegPkg, а вручную добавляем нужную информацию в реестр во время установки. Это делается из тех соображений, чтобы не зависеть от еще одной сторонней утилиты, а полностью самим контролировать процесс установки. Но мы всё же используем RegPkg при разработке плагина для удобства его отладки.
Начиная с версии Visual Studio 2010, появилась возможность существенно упростить развёртывание VSPackage модулей с помощью VSIX пакетов. VSIX пакет представляет из себя стандартный OPC (Open Packaging Conventions) архив, содержащий бинарные файлы плагина и все необходимые для их развёртывания вспомогательные файлы. Данный архив может быть передан стандартной утилите VSIXInstaller.exe, которая автоматически зарегистрирует содержащиеся в нём модули-расширения.
VSIXInstaller.exe MyPackage.vsix
С помощью инсталлятора VSIX можно также удалить установленный пакет командой /uninstall, с указанием уникального GUID идентификатора модуля.
VSIXInstaller.exe /uninstall: 009084B1-6271-4621-A893-6D72F2B67A4D
Cодержимое VSIX контейнера задаётся через специальный файл vsixmanifest, который должен быть добавлен в проект плагина. Vsixmanifest позволяет задать:
Для включения в контейнер дополнительных файлов из проекта необходимо задать для этих файлов в MSBuild проекте узел IncludeInVSIX (можно также отметить такие файлы в SolutionExplorer через окно Properties).
<Content Include="MyPackage.pdb">
<IncludeInVSIX>true</IncludeInVSIX>
</Content>
Фактически VSIX файл — это полноценный инсталлятор для расширений Visual Studio последних версий (20010 и 2012), позволяющий установить пакет методом "одного клика". Публикация VSIX пакета на официальном сайте IDE расширений Visual Studio Gallery позволит пользователю устанавливать такой модуль через диалог среды Tools -> Extension Manager.
Появившиеся в VS2010 инсталляции в виде VSIX существенно облегчили пользователю (да и разработчику) установку расширений. Причем на столько, что некоторые разработчики плагинов делают только инсталлятор для VS2010, лишь бы не связываться с разработкой плагина и инсталлятора для старых версий Visual Studio.
На практике, к сожалению, как это часто бывает в программистском мире возможны проблемы при использовании VSIX инсталлятора совместно с интерфейсом extension manager в Visual Studio 2010. В частности, в некоторых случаях бинарные файлы не всегда удаляются корректно, что блокирует работу как VSIX инсталлятора, так и студийного extension manager и вынуждает находить и удалять эти файлы вручную. Поэтому следует использовать VSIX с осторожностью, по возможности обеспечивая перед началом установки прямое удаление файлов от старой версии устанавливаемого плагина.
Каждый загружаемый в Visual Studio модуль VSPackage должен содержать уникальный PLK (Package Load Key) ключ. PLK ключ задаётся через атрибут ProvideLoadKey класса Package для версий IDE 2005 и 2008.
[ProvideLoadKey("Standard", "9.99", "MyPackage", "My Company", 100)]
Начиная с Visual Studio 2010, наличие PLK и, соответственно, атрибута ProvideLoadKey, не является обязательным, однако его можно использовать в случае, если разрабатываемый модуль нацелен на несколько версий среды MSVS. Для получения PLK ключа необходимо зарегистрироваться на портале Visual Studio Industry Partner, т.е. PLK ключ гарантирует, что в среде разработки загружены только пакеты, сертифицированные Microsoft. Однако для машин с установленным пакетом Visual Studio SDK делается исключение. Вместе с SDK устанавливается Developer License Key, позволяющий в дальнейшем загружать в соответствующей данной SDK среде Visual Studio любой модуль расширения, независимо от достоверности его PLK ключа.
И еще раз стоит напомнить о необходимости тестировать готовый дистрибутив на машине без установленного Visual Studio SDK. Дело в том, что если у вас задан неправильный ключ PLK, то на машине разработчика понять это будет непросто, так как модуль расширения будет работать.
По умолчанию шаблон VSPackage генерирует проект расширения для текущей, используемой при разработке, версии Visual Studio. Однако это не является необходимым требованием, т.е. возможна разработка расширения для одной версии среды с использованием другой версии. Стоит помнить, что при автоматическом обновлении файла проекта до более новой версии через devenv /Upgrade целевая версия IDE и, соответственно, подключенные managed API библиотеки-обёртки останутся неизменными, т.е. от предыдущей версии Visual Studio.
Для изменения целевой версии Visual Studio (а точнее для регистрации плагина именно в этой версии среды) необходимо поменять значения, передаваемые атрибуту DefaultRegistryRoot (для версий 2005 и 2008, начиная с версии Visual Studio 2010 данный атрибут не нужен), или поменять целевую версию в файле манифеста VSIX (для версий после 2008).
Поддержка VSIX появилась только в Visual Studio 2010, поэтому для сборки и отладки плагина для более ранних версий из Visual Studio 2010 (и более новых версий) необходимо будет обеспечить выполнение всех ранее описанных шагов регистрации вручную, без использования манифеста VSIX. При изменении целевой версии IDE стоит не забывать менять и версии используемых в плагине managed библиотек-обёрток для COM интерфейсов.
Изменение целевой версии IDE для плагина затрагивает следующие атрибуты класса Package:
0