Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Начальство по разработке модулей растяжений на C# для Visual Studio 2005-2012 и Atmel Studio

Anna | 17.06.2014 | нет комментариев

Аннотация

Около года назад мы опубликовали в блоге цикл статей о разработке плагинов для Visual Studio на языке C#. Теперь мы переработали эти материалы, добавили новые разделы и предлагаем вашему вниманию новейший вариант начальства.

Создание модулей растяжения (либо плагинов) для среды разработки Microsoft Visual Studio вначале может показаться дюже простым. чай есть очаровательная документация в MSDN, статьи, примеры и много дополнительных материалов. Но может показаться и трудным, когда некоторые действия будут давать не тот итог, тот, что ожидается. И правда такое поведение часто оказывается свойственно для всякий программистской задачи, все-таки тема разработки плагинов не всецело раскрыта.

Мы занимаемся разработкой статического анализатора кода PVS-Studio. И правда сам инструмент предуготовлен для программистов на C , немалая его часть написана на C#. Когда мы начинали разрабатывать наш плагин, в мире Visual Studio самой новой и нынешней считалась версия Visual Studio 2005. И правда теперь, когда теснее вышла Visual Studio 2012, некоторые могут сказать, что Visual Studio 2005 не вовсе востребована, мы до сих пор поддерживаем эту версию в своем инструменте. За то время что мы поддерживали различные версии Visual Studio и применяли различные вероятности среды, у нас накопилось огромное число фактических познаний о том, как же верно (а исключительно ненормально!) создавать плагины. Удерживать в себе эти познания огромнее не было никаких сил. Следственно мы решили оформить их и опубликовать. чай некоторые решения, которые теперь кажутся явственными, были обнаружены только несколько лет через. А те, задачи, которые теснее давным-давно решены, до сих пор могут мучать некоторых разработчиков плагинов.

Будут рассмотрены следующие вопросы:

  • базовая информация по созданию и отладке MSVS плагинов, а также помощь данных планов растяжения в цельной кодовой базе для нескольких версий Visual Studio;
  • обзор объектной модели автоматизации и классов MPF (Managed Package Framework);
  • растяжения интерфейса среды разработки с применением API объектной модели автоматизации (EnvDTE) и классов MPF (Managed Package Framework) пользовательскими меню, панелями инструментов, инструментальными окнами и диалогами настроек;
  • обзор проектной модели Visual Studio, взаимодействие с пользовательскими проектными моделями на примере реализованной в виде Visual Studio Isolated Shell среды Atmel Studio
  • сбор всех нужных данных, таких как параметры и настройки компиляции различных конфигураций и платформ, с поддержкой проектной модели Visual C для работы с внешним препроцессором/компилятором;

Больше детальное и полное изложение затронутых в статье тем доступно по приведённым в конце всякого раздела ссылкам на формальные материалы библиотеки MSDN и другие сторонние источники.

Рассматриваться будет только разработка подключаемых модулей для Visual Studio 2005 и выше. Это лимитация обусловлено тем, что PVS-Studio поддерживает только системы с VS2005 и выше. Такое лимитация при разработке PVS-Studio вызвано возникновением в среде Visual Studio 2005 новой модели API, которая не совместима с предыдущими версиями API растяжения среды.

Создание, отладка и развертывание пакетов растяжения сред Microsoft Visual Studio 2005/2008/2010/2012

В данном разделе будет произведён обзор разных способов растяжения функциональности среды Visual Studio. Детально будет рассмотрено создание модулей растяжения вида Visual Studio Extension Package (пакет растяжения Visual Studio), их отладка, регистрация и развёртывание на машине финального пользователя.

Создание и отладка VSPackage модулей растяжения Visual Studio и Visual Studio Isolated Shell

Существует уйма методов для растяжения функционала Microsoft Visual Studio. На самом базовом ярусе дозволено автоматизировать примитивные рутинные действия с поддержкой макросов. Для программной автоматизации примитивных действий с UI объектами среды, манипуляций пунктами в меню и т.п. дозволено применять подключаемый модуль (Add-In).

Растяжение встроенных редакторов среды допустимо через MEF (Managed Extensibility Framework) компоненты (начиная с версии MSVS 2010). Для интеграции в Visual Studio больших самостоятельных компонентов отменнее каждого подходят растяжения вида Extension Package (пакеты расширrqvmk!Регистрация и развёртывание пакетов растяжения Visual Studio
Регистрация пакета растяжения требует регистрации непринужденно самого пакета, а также всех интегрируемых им в 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, которая механически зарегистрирует все пеабатываемых только под версии Visual Studio не поменьше 10-ой (т.е. начиная от Visual Studio 2010), т.к. аналогичные значения сейчас задаются в файле манифеста VSIX;

Рекомендуемые ссылки

 

  1. MSDN. Experimental Build.
  2. MSDN. How to: Register a VSPackage.
  3. MSDN. VSIX Deployment.
  4. MSDN. How to: Obtain a PLK for a VSPackage.
  5. MZ-Tools. Resources about Visual Studio .NET extensibility.
  6. MSDN. Creating Add-ins and Wizards.
  7. MSDN. Using a Custom Registration Attribute to Register an Extension.
  8. MSDN. Shell (Integrated or Isolated).

 

Объектная модель автоматизации Visual Studio, интерфейсы EnvDTE и Visual Studio Shell Interop

В данном разделе проводится обзор объектной модели автоматизации Visual Studio. Рассматривается всеобщая конструкция модели, описывается приобретение доступа к её интерфейсам с поддержкой объектов верхнего яруса DTE/DTE2, приводятся примеры применения некоторых её элементов. Также рассматривается вопрос применения интерфейсов модели в многопоточных приложениях и приводится реализация механизмов многопоточного взаимодействия с COM интерфейсами в managed коде.

Вступление

Среда разработки Visual Studio построена на тезисах автоматизации и расширяемости, дозволяя разработчикам интегрировать в себя фактически всякие новые элементы и взаимодействовать как со стандартными, так и с пользовательскими компонентами. Для реализации данных задач пользователям Visual Studio предоставлено несколько взаимно-дополняющих друг друга инструментов. Самым базовым и универсальным из которых является объектная модель автоматизации Visual Studio.

Объектная модель автоматизации представляет собой ряд библиотек, содержащих обширный отлично структурированный комплект API, покрывающих все аспекты автоматизации IDE и множество аспектов её расширяемости. Невзирая на то, что данная модель по сопоставлению с другими инструментами растяжения IDE не предоставляет вероятностей для взаимодействия с некоторыми областями Visual Studio (а в основном это касается растяжения некоторого функционала IDE), она является особенно эластичным и универсальным таким средством.

Множество интерфейсов модели автоматизации доступны для всякого вида IDE растяжений, в том числе дозволяя взаимодействовать с ней и из внешнего само­стоятельного процесса. Больше того, сама модель может быть расширена за счёт пользовательского растяжения самой Visual Studio, делая тем самым доступными новые пользовательские компоненты для других разработчиков.

Конструкция объектной модели автоматизации

Объектная модель Visual Studio состоит из взаимосвязанных функциональных групп объектов, охватывающих все основные аспекты среды разработки, и предоставляет вероятности для их управления и растяжения. Доступ к всякий из этих групп допустим через всеобщий интерфейс верхнего яруса DTE (Development Tools Environment). На рисунке 1 приведена всеобщая конструкция объектной модели автоматизации с распределением на функциональные группы.

Рисунок 1 — Visual Studio Automation Object Model (нажмите на рисунок для увеличения)

Рисунок 1 — Visual Studio Automation Object Model (нажмите на рисунок для увеличения)

Модель может быть расширена пользователем в следующих функциональных группах:

  • проектные модели (реализация новых типов планов, помощь новых языков);
  • документные модели (реализация новых типов документов и редакторов документов);
  • модели яруса редактора кода(реализация помощь специфичных конструкций для новых языков);
  • модели сборочного процесса планов.

Растяжение модели автоматизации доступно только для модулей VSPackage.

Все интерфейсы модели автоматизации дозволено условно поделить на 2 крупные группы. 1 группа – интерфейсы пространств имён EnvDTE и Visual Studio Interop, которые затрагивают взаимодействие с всеобщими базовыми компонентами непринужденно самой среды Visual Studio, такими, как редакторы, инструментальные окна, службы обработки событий и т.п. 2-ая группа – это интерфейсы определенной проектной модели. На рисунке выше эта группа интерфейсов обозначена через свойства позднего связывания (late-bound properties), т.е. эти интерфейсы реализованы в отдельной динамически-подгружаемой библиотеке. Всякая стандартная (т.е. включённая в дистрибутив Visual Studio по умолчанию) проектная модель, как скажем Visual C либо Visual Basic, имеет свою реализацию данных интерфейсов. Сторонние разработчики также могут расширять модель автоматизации, добавляя поддержку собственных проектных моделей и предоставляя свою реализацию интерфейсов автоматизации.

Подметим, что интерфейсы из выделенной нами 1-ой группы довольно универсальны и в большинстве случаев могут быть использованы при работе с всякий проектной моделью либо редакцией Visual Studio, в том числе и в изолированныхинтегрированных оболочках (Visual Studio IsolatedIntegrated Shell). В данном же разделе мы больше детально остановимся как раз на этой группе.

Тем не менее, невзирая на универсальность модели автоматизации, не все из представленных тут групп интерфейсов могут быть идентично использованы в растяжениях разного типа. В частности, некоторые вероятности модели будут недостижимы для внешних процессов, т.к. они завязаны на такие специфичные типы модулей-растяжений, как Add-In либо VSPackage. Следственно выбирая тип грядущего разрабатываемого модуля, следует ориентироваться раньше каждого на требуемый от него функционал.

Пространство имён Microsoft.VisualStudio.Shell.Interop также предоставляет ряд COM интерфейсов, разрешающих расширять и автоматизировать работу со средой Visual Studio из managed кода. Классы MPF (Managed Package Framework), которые мы применяли ранее, в частности, для создания VSPackage плагина, в своей основе также применяют эти интерфейсы. Правда такие интерфейсы автоматизации и не являются частью рассмотренной выше модели EnvDTE, для VS Package плагинов они дополняют эту модель дополнительной функциональностью, недостижимой плагинам других типов.

Приобретение ссылок на объекты DTE/DTE2

Для создания приложения автоматизации Visual Studio раньше каждого нужно получить доступ непринужденно к самим объектам автоматизации. Для этого нужно, во-первых, подключить положительные версии библиотек, содержащих нужные managed обёртки для API среды в пространстве имён EnvDTE. Во-вторых, необходимо получить ссылку на основной объект верхнего яруса модели автоматизации — интерфейс DTE2.

В процессе становления среды Visual Studio некоторые из объектов автоматизации претерпевали метаморфозы и получали дополнительную функциональность. Для сохранения обратной совместимости с теснее существовавшими модулями-растяжениями, взамен обновления ветхих интерфейсов EnvDTE были сделаны новые пространства имён EnvDTE80, EnvDTE90, EnvDTE100 и т.п. Множество сходственных новых интерфейсов имеют такие же имена, что и в EnvDTE, но с добавлением на конце порядкового номера, скажем, Solution и Solution2. Рекомендуется применять новые версии интерфейсов при создании нового плана, от того что именно они содержат особенно полную функциональность. Стоит подметить, что поля и способы интерфейса DTE2 возвращают объекты, типы которых соответствуют интерфейсу DTE, т.е. при обращении к dte2.Solution возвращается Solution, а не Solution2, как может показаться.

Невзирая на то, что новые пространства имён EnvDTE80, EnvDTE90, EnvDTE100 содержат некоторую новую функциональность, именно в EnvDTE всё еще находится основная часть объектов автоматизации. Следственно, Дабы иметь доступ ко каждому присутствующем интерфейсам, нужно подключить к плану все версии managed библиотек-обёрток COM интерфейсов, а также получить ссылки как на DTE, так и на DTE2.

Метод приобретения ссылки на верхний объект EnvDTE зависит от типа разрабатываемого растяжения Visual Studio. Дальше разглядим 3 вида растяжений: Add-In, VSPackage и самостоятельный от MSVS внешний процесс.

Add-In растяжение

В случае разработки модуля вида Add-In, доступ к DTE интерфейсу дозволено получить в способе OnConnection, тот, что должен быть реализован для интерфейса IDTExtensibility, предоставляющего доступ к событиям взаимодействия среды и Add-In модулей. Способ OnConncetion вызываnce(t, true); // Cast the instance to DTE2 and assign to variable dte. EnvDTE80.DTE2 dte = (EnvDTE80.DTE2)obj; // Show IDE Main Window dte.MainWindow.Activate();
В поведённом примере мы реально создаём новейший объект DTE, запуская процесс devenv.exe способом CreateInstance. При этом GUI окно среды будет отображено только позже вызова способа Activate. Примитивный же метод получить ссылку на DTE из теснее запущенного экземпляра Visual Studio будет выглядеть так:

EnvDTE80.DTE2 dte2;
dte2 = (EnvDTE80.DTE2)
  System.Runtime.InteropServices.Marshal.GetActiveObject(
    "VisualStudio.DTE.10.0");

Впрочем если было запущено несколько экземпляров IDE, то способ GetActiveObject вернёт ссылку только на интерфейс самого первого из запущенных экземпляра среды. Разглядим один из допустимых вариантов приобретения DTE интерфейса у запущенного экземпляра Visual Studio по PID его процесса.

using EnvDTE80;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;

[DllImport("ole32.dll")]
private static extern void CreateBindCtx(int reserved, 
                                         out IBindCtx ppbc);
[DllImport("ole32.dll")]
private static extern void GetRunningObjectTable(int reserved, 
  out IRunningObjectTable prot);

public static DTE2 GetByID(int ID)
{
  //rot entry for visual studio running under current process.
  string rotEntry = String.Format("!VisualStudio.DTE.10.0:{0}", ID);
  IRunningObjectTable rot;
  GetRunningObjectTable(0, out rot);
  IEnumMoniker enumMoniker;
  rot.EnumRunning(out enumMoniker);
  enumMoniker.Reset();
  IntPtr fetched = IntPtr.Zero;
  IMoniker[] moniker = new IMoniker[1];
  while (enumMoniker.Next(1, moniker, fetched) == 0)
  {
    IBindCtx bindCtx;
    CreateBindCtx(0, out bindCtx);
    string displayName;
    moniker[0].GetDisplayName(bindCtx, null, out displayName);
    if (displayName == rotEntry)
    {
      object comObject;
      rot.GetObject(moniker[0], out comObject);
      return (EnvDTE80.DTE2)comObject;
    }
  }
  return null;
}

Тут мы получили интерфейс DTE, идентифицировав необходимый нам экземпляр IDE в таблице запущенных COM объектов (ROT, Running Object Table) по его идентификатору. Сейчас мы можем получить DTE для всякого из запущенных экземпляров Visual Studio, скажем:

Process Devenv;
...
//Get DTE by Process ID
EnvDTE80.DTE2 dte2 = GetByID(Devenv.Id);

Добавочно, для приобретения проектно-специфичных интерфейсов (в том числе и пользовательских растяжений модели), скажем для проектной модели CSharpProjects, через имеющийся DTE объект, дозволено воспользоваться способом GetObject:

Projects projects = (Projects)dte.GetObject("CSharpProjects");

Способ GetObject вернёт нам уйма Projects стандартных Project объектов, всякий из которых будет, помимо всеобщих в модели автоматизации свойств, содержать ссылку и на специфичный для данной модели тип.

Документы текстового редактора Visual Studio

Модель автоматизации определяет текстовые документы Visual Studio через интерфейс текстового документаTextDocument. Начальные C/C файлы открываются средой как текстовые документы. TextDocument основан на всеобщем интерфейсе документов модели автоматизации (интерфейс Document), описывающем всякий открытый в редакторе либо дизайнере Visual Studio файл. Ссылку на объект текстового документа дозволено получить через поле Object объекта Document. Получим, скажем, текстовый документ для энергичного (т.е. открытого и имеющего фокус) документа из текстового редактора IDE.

EnvDTE.TextDocument objTextDoc =
(TextDocument)PVSStudio.DTE.ActiveDocument.Object("TextDocument");

 

Редактирование документов

Интерфейс TextSelection разрешает модифицировать текст и руководить выделениями. Способы данного интерфейса отражают функционал текстового редактора Visual Studio, т.е. разрешают трудиться непринужденно с видимым в UI текстом.

EnvDTE.TextDocument Doc =
  (TextDocument)PVSStudio.DTE.ActiveDocument.Object(string.Empty);
Doc.Selection.SelectLine();
TextSelection Sel = Doc.Selection;
int CurLine = Sel.TopPoint.Line;
String Text = Sel.Text;
Sel.Insert("testrn");

В данном примере мы выделяем строку текста под курсором, считываем выделенные текст и заменяем его на строку ‘test’.

Также интерфейс TextDocument разрешает редактировать текст документа с поддержкой интерфейсаEditPoint. Данный интерфейс схож с интерфейсом TextSelection, но в различие от него разрешает манипулировать данными текстового буфера, а не текстом, отображённым непосредствеs_permark! new _dispVCProjectEngineEvents_ItemRemovedEventHandler( m_ProjectItemsEvents_ItemRemoved);

События MDI окон

Для обработки стандартных событий MDI окна среды дозволено воспользоваться интерфейсом Events.WindowEvents. Данный интерфейс разрешает назначить как обособленный обработчик для окна (определённого через интерфейс EnvDTE.Window), так и всеобщий обработчик для всех окон среды.

Предназначение всеобщего обработчика для среды на переключение между окнами:

WindowEvents WE = PVSStudio.DTE.Events.WindowEvents;
WE.WindowActivated  = 
  new _dispWindowEvents_WindowActivatedEventHandler(
    Package.WE_WindowActivated);

Предназначение обработчика на переключение для энергичного в данный момент окна допустимо через индексатор свойства WindowEvents:

WindowEvents WE = m_dte.Events.WindowEvents[MyPackage.DTE.ActiveWindow];
WE.WindowActivated  = new
  _dispWindowEvents_WindowActivatedEventHandler(
    MyPackage.WE_WindowActivated);

 

События IDE команд

Непринужденно работа с командами и их растяжение через модель автоматизации рассматриваются в отдельном разделе. Тут мы затронем вопрос обработки событий команд (но не самого выполнения команд). Предназначение обработчиков на события допустимо с поддержкой интерфейса Events.CommandEvents. Качество CommandEvents, по аналогии с обработкой событий MDI окон, также разрешает назначить обработчик как для всех команд IDE, так и для определенной команды с поддержкой индексатора.

Предназначение обработчика команд среды на событие заключения выполнения команды:

CommandEvents CEvents = DTE.Events.CommandEvents;
CEvents.AfterExecute  = new
  _dispCommandEvents_AfterExecuteEventHandler(C_AfterExecute);

Для назначения обработчика определенной команды нужно вначале идентифицировать эту команду. Всякая команда среды идентифицируется парой GUID:ID, причём для пользовательских команд эти значения задаются непринужденно при интеграции команды разработчиком, скажем через таблицы команд (VSCT). Visual Studio имеет особый отладочный режим работы, разрешающий узнать идентификаторы всякий команды. Для активизации этого режима необходимо добавить дальнейший ключ в системный реестр (пример для Visual Studio 2010):

[HKEY_CURRENT_USERSoftwareMicrosoftVisualStudio10.0General]
"EnableVSIPLogging"=dword:00000001

Позже перезапуска среды с этим ключом, наведение курсора мыши (изредка не срабатывает сразу, если не кликнуть) с зажатыми клавишами CTRL SHIFT вызывает диалоговое окно, в котором приведены все внутренние идентификаторы команды. Из них нас волнуют значения Guid и CmdID. Пример назначения обработчика для команды File.NewFile:

CommandEvents CEvents = DTE.Events.CommandEvents[
  "{5EFC7975-14BC-11CF-9B2B-00AA00573819}", 221];
CEvents.AfterExecute  = new  
  _dispCommandEvents_AfterExecuteEventHandler(C_AfterExecute);

Обработчик, определенный сходственным образом, получает управление позже выполнения команды.

void C_AfterExecute(string Guid, int ID, object CustomIn, 
  object CustomOut)
{
  ...
}

Не стоит путать данный обработчик (позже заключению выполнения) с непосредственным обработчиком выполнения самой команды, тот, что может быть назначен при её инициализации (из модуля-растяжения в случае, если команда является пользовательской). Детально работа с командами среды будет описана в особом посвященном IDE командам разделе.

В завершение данного подраздела подметим, что при разработке пакета-растяжения (VSPackage) PVS-Studio мы столкнулись с необходимостью беречь ссылки на объекты интерфейса, содержащие в свою очередь наши делегаты-обработчики (такие, как CommandEvents, WindowEvents и прочие), в качестве полей нашего основного подкласса Package. При назначении же обработчика через локальную переменную, определённую внутри самой функции, данный обработчик терялся сразу позже выхода из неё. Схоже, что это происходит в итоге действий сборщика мусора .NET. Причём происходит это невзирая на то, что мы получаем ссылку на такой объект из интерфейса DTE, однозначно присутствующего в течение каждого времени жизни нашего модуля-растяжения.

Обработка событий планов и решений (для VSPackage модулей)

Разглядим несколько интерфейсов из пространства имён Microsoft.VisualStudio.Shell.Interop, разрешающих обрабатывать события непринужденно планов и решений в среде Visual Studio. Правда эти интерфейсы и не относятся непринужденно к модели автоматизации EnvDTE, они могут быть реализованы основным классом модуля растяжения VS Package (т.е. классом, наследуемым от Package из Managed Package Framework). Следственно, в случае разр! Если же вы не имеете по какой-то причине доступа к объекту IVsUIShell2 (скажем, при разработке плагина не VSPackage типа), но вам все же нужно поддержать цветовую схему Visual Studio, то вы можете взять значения цветов отдельных UI компонентов в обход интерфейсов оптимизации, напрямую из системного реестра. В данной статье мы не будем останавливаться на этом способе детально, но вы можете скачать отсель бесплатную и открытую утилиту для редактирования цветовых тем Visual Studio. Эта утилита написана на C# и содержит каждый нужный код для чтения и модификации цветовых тем Visual Studio 2012 из managed приложений.

Взаимодействие с COM интерфейсами в многопоточном приложении

Первоначально пакет растяжения PVS-Studio не содержал в себе каких-то особенных механизмов для обеспечения потоковой безопасности при работе с Visual Studio API. При этом мы усердствовали ограничить всё взаимодействие с данными интерфейсами в рамках одного, создаваемого нашим плагином, фонового потока. Данный подход работал без видимых задач длинное время. Впрочем сообщения о аналогичных ComException ошибках от нескольких наших пользователей побудили нас больше подробно разобраться в данном вопросе и реализовать личный механизм для обеспечения потоковой безопасности в COM Interop.

Правда объектная модель автоматизации Visual Studio не является потоково-безвредной, она предоставляет вероятности для взаимодействия с многопоточными приложениями. Приложение Visual Studio является COM (Component Object Mode) сервером. Для обращения COM-заказчиков (в данном случае это наш модуль-растяжение) к потоково-небезопасным серверам спецтехнология COM предоставляет механизм, знаменитый как STA (single-threaded apartment) модель. В терминах COM Apartment представляет собой логичный контейнер внутри процесса, в котором объекты и потоки имеют всеобщие права межпотокового доступа. STA разрешает содержать в таком контейнере только один поток, но неограниченное число объектов. Обращения же из других потоков к таким потоково-небезопасным объектам в STA преобразуются в сообщения и помещаются в очередь сообщений. После этого сообщения поочерёдно достаются из этой очереди и преобразуются в вызовы способов потоком, находящимся в STA, что делает допустимым обращение к этим небезопасным объектам на сервере только из одного потока.

Применение Apartment механизма в managed коде

Непринужденно .NET Framework не использует Apartment механизм COM. Следственно, когда в обстановки COM взаимодействия managed приложение обращается к COM объекту, CLR (Common Language Runtime) должен сделать и инициализировать apartment контейнер. Managed поток может сделать и войти как в MTA (multi-threaded apartment, контейнер, тот, что, в противовес STA, может содержать несколько потоков единовременно), так и в STA, причём, по умолчанию, поток будет запущен именно в MTA. Задать apartment дозволено с поддержкой способа Thread.SetApartmentState перед непосредственным запуском потока:

Thread t = new Thread(ThreadProc);
t.SetApartmentState(ApartmentState.STA);
...
t.Start();

Т.к. apartment не может быть изменён для теснее запущенного потока, для его установки в основном потоке в managed приложения в STA нужно воспользоваться признаком STAThread:

[STAThread]
static void Main(string[] args)
{...}

 

Реализация фильтра ошибок доступа к COM интерфейсам в managed среде

Так как в STA все обращения к COM серверу сериализуются, один из вызывающих заказчиков может быть блокирован либо отклонён в моменты, когда сервер занят, обрабатывает другие обращения либо иной поток теснее находится в apartment контейнере. В случае, когда COM сервер отклоняет обращение заказчика, .NET COM Interop генерирует исключения вида System.Runtime.InteropServices.COMException («The message filter indicated that the application is busy»).

В случае разработки встраиваемого в Visual Studio модуля (VSPackage, Add-In) либо макроса, управление в него передаётся обыкновенно из основного STA UI потока среды (перехват event’ов, обработка изменений состояний и т.п.). Обращение к COM интерфейсам автоматизации из этого основного потока является безвредным. Впрочем если планируется создание других фоновых потоков и обращение к интерфейсам EnvDTE из них (скажем, долгие вычисления, которые могут привести к зависанию интерфейса среды), то желанно реализовать механизм для обработки отклонённых сервером вызовов.

Особенно Зачастую с сходственными COM Exception ошибками мы сталкивались при работе с PVS-Studio в окружении других установленных в Visual Studio плагинов при одновременном взаимодействием самого пользователя с интерфейсом IDE. Абсолютно правомерно, что сходственная обстановка приводила к одновременным параллельным запросам объектов, находящихся в STA, и соответственно, отклонению частииз этих запросов.

Для выборочной обработки входящих и исходящих сообщений COM предоставляет интерфейс IMessageFilter. Если сервер реализует его, то все запросы поступают в способ HandleIncomingCall, а заказчик информируется об отклонённом запросе через способ RetryRejectedCall. При этом возникает вероятность либо повторить запрос, либо правильно отработать отказ на него (скажем, показав пользователю соответствующий диалог о занятости сервера). Дальше приведём пример реализации обработки отклонённого запроса в managed приложении.

[ComImport()]
[Guid("00000016-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IMessageFilter
{
  [PreserveSig]
  int HandleInComingCall(
    int dwCallType,
    IntPtr hTaskCaller,
    int dwTickCount,
    IntPtr lpInterfaceInfo);

  [PreserveSig]
  int RetryRejectedCall(
    IntPtr hTaskCallee,
    int dwTickCount,
    int dwRejectType);

  [PreserveSig]
  int MessagePending(
    IntPtr hTaskCallee,
    int dwTickCount,
    int dwPendingType);
}

class MessageFilter : MarshalByRefObject, IDisposable, IMessageFilter
{

  [DllImport("ole32.dll")]
  [PreserveSig]
  private static extern int CoRegisterMessageFilter(
    IMessageFilter lpMessageFilter, 
    out IMessageFilter lplpMessageFilter);

  private IMessageFilter oldFilter;
  private const int SERVERCALL_ISHANDLED = 0;
  private const int PENDINGMSG_WAITNOPROCESS = 2;
  private const int SERVERCALL_RETRYLATER = 2;

  public MessageFilter()
  {
    //Starting IMessageFilter for COM objects
    int hr =
      MessageFilter.CoRegisterMessageFilter(
        (IMessageFilter)this, 
         out this.oldFilter);
    System.Diagnostics.Debug.Assert(hr >= 0, 
      "Registering COM IMessageFilter failed!");
  }

  public void Dispose()
  {
    //disabling IMessageFilter
    IMessageFilter dummy;
    int hr = MessageFilter.CoRegisterMessageFilter(this.oldFilter, 
                                                   out dummy);
    System.Diagnostics.Debug.Assert(hr >= 0, 
      "De-Registering COM IMessageFilter failed!")
    System.GC.SuppressFinalize(this);
  }

  int IMessageFilter.HandleInComingCall(int dwCallType, 
    IntPtr threadIdCaller, int dwTickCount, IntPtr lpInterfaceInfo)
  {
    // Return the ole default (don't let the call through).
    return MessageFilter.SERVERCALL_ISHANDLED;
  }

  int IMessageFilter.RetryRejectedCall(IntPtr threadIDCallee, 
    int dwTickCount, int dwRejectType)
  {
    if (dwRejectType == MessageFilter.SERVERCALL_RETRYLATER)
    {
      // Retry the thread call immediately if return >=0 & 
      // <100.
      return 150; //waiting 150 mseconds until retry
    }
    // Too busy; cancel call. SERVERCALL_REJECTED
    return -1;
    //Call was rejected by callee. 
    //(Exception from HRESULT: 0x80010001 (RPC_E_CALL_REJECTED))
  }

  int IMessageFilter.MessagePending(
      IntPtr threadIDCallee, int dwTickCount, int dwPendingType)
  {
    // Perform default processing.
    return MessageFilter.PENDINGMSG_WAITNOPROCESS;
  }
}

Сейчас мы сумеем применять MessageFilter при работе с COM интерфейсами из фонового потока:

using (new MessageFilter())
{
  //COM-interface dependent code
  ...
}

 

Рекомендуемые ссылки

 

  1. MSDN. Referencing Automation Assemblies and the DTE2 Object.
  2. MSDN. Functional Automation Groups.
  3. Форумы Microsoft Development Network. FAQ — Растяжение Visual Studio.
  4. MZ-Tools. HOWTO: Use correctly the OnConnection method of a Visual Studio add-in.
  5. The Code Project. Understanding The COM Single-Threaded Apartment.
  6. MZ-Tools. HOWTO: Add an event handler from a Visual Studio add-in.
  7. Dr. eX’s Blog. Using EnableVSIPLogging to identify menus and commands with VS 2005 SP1.
  8. MSDN. Visual Studio Interop Assemblies.

 

Команды Visual Studio

В данном разделе рассматриваются способы программного создания, применения и обработки команд Visual Studio в модулях-растяжениях данной среды с поддержкой интерфейсов объектной модели автоматизации и IDE служб. Показана связь между IDE командами и UI элементами среды через пользовательские меню и панели инструментов.

Вступление

Команды Visual Studio разрешают напрямую взаимодействовать со средой разработки с поддержкой клавиатуры. Множество функционала разных диалоговых и инструментальных окон среды и инструментальных панелей представлено командами. Пункты основного меню приложения и кнопки панелей инструментов реально являются командами. Команды могут и не иметь непосредственного представления в UI среды разработки, они не являются непринужденно элементами интерфейса IDE, но могут быть ими представлены, как, скажем, в случае с пунктами основного меню.

Модуль-растяжение PVS-Studio для IDE в качестве одного их своих основных UI компонентов (иным таким компонентом является инструментальное окно) интегрирует в основное меню Visual Studio несколько собственных подгрупп команд, разрешая пользователю контролировать все аспекты применения статического обзора как из самой среды, так и через прямой вызов команд из командной строки.

Применение команд

Любая IDE команда, самостоятельно от формы её представления (либо отсутствия такового) в интерфейсе IDE, может быть выполнена напрямую через окна Command Window и Immediate Window, а также с поддержкой довода командной строки devenv.exe /command при запуске приложения из консоли.

Полное имя команды формируется в соответствии с её принадлежностью к какой-либо функциональной группе, скажем команды пункта основного меню среды File. Её полное имя дозволено посмотреть в диалоге Keyboard, Environment страницы настроек Options. Диалог Tools -> Customize -> Commands разрешает посмотреть зарегистрированные в среде команды, отсортированные по группам и методам отображения в интерфейсе (меню, панели инструментов), а также редактировать их, удалять либо добавлять новые команды.

Команды могут принимать добавочные доводы, передаваемые через пробел. Приведём пример вызова стандартной системной команды меню File -> New -> File, с передачей ей дополнительных параметров, через окно среды Command Window:

>File.NewFile Mytext /t:"GeneralText File" 
  /e:"Source Code (text) Editor"

Синтаксис написания команд подчиняется дальнейшим правилам:

  • Имя команды и её доводы разделяются пробелом
  • Содержащие пробелы значения доводов оборачиваются в кавычки
  • Знак вставки (^) применяется для экранирования символов
  • Односимвольные сокращения имён доводов дозволено сочетать, скажем /case (/c) и /word (/w) могут быть записаны как /cw

При применении параметра запуска command, имя команды совместно со всеми её доводами следует обернуть в двойные кавычки:

devenv.exe /command "MyGroup.MyCommandName arg1 arg2"

Для стремительного вызова команде может быть назначен псевдоним с поддержкой команды alias:

>alias MyAlias File.NewFile MyFile

Команды, добавляемые в основное меню IDE модулем-растяжением PVS-Studio, могут также быть использованы и через вызов /command, в частности, скажем, для решения задачи интеграции статического обзора в автоматизированный сборочный процесс плана. Тут подметим, что непринужденно сам статический анализатор PVS-Studio.exe представляет собой как раз консольное приложение, работающее по схожему с компилятором тезису, т.е. он получает на вход путь до файла с начальным кодом и параметры компиляции этого файла, выдавая итоги своего обзора в потоки stdout/strerr. ?сно и то, что анализатор может быть касательно легко интегрирован непринужденно в сборочную систему (скажем, основанную на том же MSBuild, NMake либо даже GNU Make) на одном ярусе с вызовом C/C компилятора. Сходственная система теснее сама по определению будет реализовывать обход всех начальных файлов с предоставлением параметров компиляции для всякого из них, что реально разрешает подменять (либо скорее дополнять) вызов компилятора вызовом анализатора. И правда такой режим работы всецело поддерживается анализатором PVS-Studio.exe, сходственная интеграция обзора в сборочный процесс требует довольно близкого знакомства непринужденно со сборочной системой, ровно как и собственно вероятности для такой её модификации, что может быть как затруднительно, так и совсем не доступно.

Следственно для интеграции статического обзора PVS-Studio в автоматизированный сборочный процесс, дозволено на больше высоком ярусе (т.е. теснее непринужденно на ярусе сервера постоянной интеграции) применять вызов команд модуля-растяжения в Visual Studio через /command, скажем, команды проверки PVS-Studio.CheckSolution. Безусловно, такой вариант допустим только при применении для сборки нативных проектных решений Visual C (vcproj/vcxproj).

В случае запуска Visual Studio из командной строки с доводом /command, команда будет исполнена сразу позже загрузки среды. При этом среда разработки будет запущена как обыкновенное UI приложение, соответственно, и без перенаправления в запускающую её консоль эталонных потоков ввода/вывода. Стоит подметить, что в всеобщем случае Visual Studio является именно UI средой разработки и не предуготовлена для работы в режиме командной строки. Так, скажем, для компиляции планов в системах автоматизации сборок рекомендуется напрямую вызывать сборочную утилиту Microsoft MSBuild, поддерживающую все типовые типы планов Visual Studio.

С осторожностью следует использовать вызов команд Visual Studio через /command при работе в неинтерактивном режиме рабочего стола (скажем, при запуске из службы Windows). Скажем, проверяя вероятность интегрировать статический обзор PVS-Studio в сборочные процессы Microsoft Team Foundation, мы столкнулись с несколькими увлекательными моментами, т.к. по умолчанию Team Foundation работает именно как Windows служба. Сам наш плагин был не готов трудиться в неинтерактивном режиме рабочего стола, ненормально управляя своими дочерними окнами и диалогами, что в свою очередь приводило к аварийному падению. У Visual Studio таких задач не обнаружилось, а вернее фактически не обнаружилось. Позже первого запуска среды, для всякого пользователя Visual Studio выдаёт диалог, предлагающий предпочесть одну из стандартных конфигураций интерфейса. Данный же диалог она выдала и для пользователя LocalSystem, которому принадлежал сервис Team Foundation. Оказалось, что данный диалог Visual Studio генерирует и в неинтерактивном режиме при вызове /command, блокируя всё последующее исполнение. А так как пользователь не имеет интерактивного рабочего стола, то и закрыть данный диалог оказалось затруднительно. В выводе мы всё же сумели это сделать, запустив Visual Studio для LocalSystem в интерактивном режиме с поддержкой утилиты psexec из комплекта PSTools.

Создание и обработка команд в VSPackage, Vsct файлы

Для создания и управления интегрируемыми IDE командами в VSPackage применяются таблицы команд (Visual Studio Command Table, vsct файлы). Таблицы команд — это текстовые конфигурационные файлы в формате XML, компилируемые VSCT-компилятором в бинарные cto-файлы (command table output). CTO файлы включаются после этого в качестве источника в финальную сборку модуля-растяжения IDE. С поддержкой VCST команды могут быть назначены на пункты основного меню IDE либо кнопки панелей инструментов. Помощь VSCT файлов доступна начиная с Visual Studio 2005, в предыдущих версиях IDE для изложения команд применялись CTC (command table compiler) файлы, в рамках данной статьи они рассматриваться не будут.

Всякой команде в vsct файле присваивается неповторимый идентификатор — CommandID, имя и группа, определяется сочетание для стремительного вызова. С поддержкой разных флагов задаётся её внешний вид в интерфейсе (в меню либо на панели инструментов), определяются параметры её видимости и т.д.

Разглядим базовую конструкцию VSCT файла. Корневой элемент таблицы команд CommandTable должен содержать под-узел Commands, в котором будут определены все пользовательские команды, группы, меню, панели инструментов и т.д. Узел Commands должен также иметь признак Package со значением, соответствующим идентификатору разрабатываемого пакета растяжения. Под-узел корневого узла Symbols должен содержать определения для всех используемых в VSCT файле идентификаторов. Под-узел корневого узла KeyBindings содержит задаваемые по умолчания сочетания для стремительного вызова команд.

<CommandTable"http://schemas.microsoft.com/VisualStudio/2005-10-
18/CommandTable">

    <Extern href="stdidcmd.h"/>
    <Extern href="vsshlids.h"/>
  <Commands>
    <Groups>
    ...
    </Groups>
    <Bitmaps>
    ...
    </Bitmaps>
  </Commands>
  <Commands package="guidMyPackage">
    <Menus>
    ...
    </Menus>
    <Buttons>
    ...
    </Buttons>
  </Commands>

  <KeyBindings>
    <KeyBinding guid="guidMyPackage" id="cmdidMyCommand1"
 editor="guidVSStd97" key1="221" mod1="Alt" />
  </KeyBindings>
  <Symbols>
    <GuidSymbol name="guidMyPackage" value="{B837A59E-5BF0-4190-B8FC-
FDC35BE5C342}" />
    <GuidSymbol name="guidMyPackageCmdSet" value="{CC8B1E36-FE6B-48C1-
B9A9-2CC0EAB4E71F}">
      <IDSymbol name="cmdidMyCommand1" value="0x0101" />
    </GuidSymbol>
  </Symbols>
</CommandTable>

Элемент Buttons задаёт непринужденно сами IDE команды, задавая их внешний вид и привязывая их к группам команд.

<Button guid="guidMyPackageCmdSet" id="cmdidMyCommand1"
priority="0x0102" type="Button">
  <Parent guid="guidMyPackageCmdSet" id="MyTopLevelMenuGroup" />
  <Icon guid="guidMyPackageCmdSet" id="bmpMyCommand1" />
  <CommandFlag>Pict</CommandFlag>
  <CommandFlag>TextOnly</CommandFlag>
  <CommandFlag>IconAndText</CommandFlag>
  <CommandFlag>DefaultDisabled</CommandFlag>
  <Strings>
    <ButtonText>My &amp;Command 1</ButtonText>
  </Strings>
</Button>

Элемент Menus описывает конструкцию UI элементов основного меню и панели инструментов, и объединяет их с группами команд элемента Groups. Группа команд, связанная с элементом Menu, будет отображена на заданном тут меню либо панели инструментов.

<Menu guid=" guidMyPackageCmdSet" id="SubMenu1" priority="0x0000"
type="Menu">
  <Parent guid="guidMyPackageCmdSet" id="MyTopLevelMenuGroup"/>
  <Strings>
    <ButtonText>Sub Menu 1</ButtonText>
  </Strings>
</Menu>
<Menu guid="guidMyPackageCmdSet" id="MyToolBar1" priority="0x0010"
type="Toolbar">
</Menu>

Элемент Groups формирует группы пользовательских команд среды.

<Group guid="guidMyPackageCmdSet" id="MySubGroup1" priority="0x0020">
  <Parent guid="guidMyPackageCmdSet" id="MyGroup1" />
</Group>

Для добавления vsct файла в MSBuild план VSPackage нужно вначале вставить дальнейший узел для вызова VSCT компилятора в csproj файл (в автогенерируемом плане SDK образца VSPackage vsct файл будет теснее добавлен в план):

<ItemGroup>
  <VSCTCompile Include="TopLevelMenu.vsct">
    <ResourceName>Menus.ctmenu</ResourceName>
  </VSCTCompile>
</ItemGroup>

Для интеграции пользовательской команды либо группы команд в одну из стандартных групп Visual Studio, нужно указать для вашей группы идентификатор целевой стандартной группы в узле parent. Скажем, для интеграции вашей группы команд в контекстное меню плана окна Solution Explorer:

<Group guid="guidMyCmdSet" id="ProjectNodeContextMenuGroup" priority="0x07A0">
    <Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_PROJNODE" />
</Group>

Как видим, тут применяется типовой идентификатор IDM_VS_CTXT_PROJNODE. Пройдя по этой ссылке, вы сумеете посмотреть список стандартных идентификаторов групп команд Visual Studio.

И после этого указать на него с поддержкой признака ProvideMenuResource у вашего преемника класса Package:

[ProvideMenuResource("Menus.ctmenu", 1)]
...
public sealed class MyPackage : Package

Предназначение обработчиков для команд, определённых в VSCT файле, допустимо с поддержкой службы, доступной через интерфейс IMenuCommandService. Получить ссылку на него дозволено с поддержкой способа GetService класса Package:

OleMenuCommandService MCS = GetService(typeof(IMenuCommandService)) as
  OleMenuCommandService;

Приведём пример назначения обработчика на команду меню (команда должна быть определена в vsct файле предварительно):

EventHandler eh = new EventHandler(CMDHandler);
CommandID menuCommandID = new CommandID(guidCommand1CmdSet, id); 
//ID and GUID should be the same as in the VCST file
OleMenuCommand menuItem = new OleMenuCommand(eh, menuCommandID);
menuItem.ParametersDescription = "$";
MCS.AddCommand(menuItem);

Для приобретения доводов команды во время обработки её вызова дозволено преобразовать получаемое обработчиком значение EventArgs в OleMenuCmdEventArgs:

void CMDHandler(object sender, EventArgs e)
{                 
  OleMenuCmdEventArgs eventArgs = (OleMenuCmdEventArgs)e;
                   if (eventArgs.InValue != null)
                       param = eventArgs.InValue.ToString();
  ...
}

 

Работа с командами через интерфейс EnvDTE.DTE

Объект автоматизации EnvDTE.DTE также предоставляет вероятности для прямой программной манипуляции (создание, модификация, исполнение) команд через интерфейс dte.Commands и способ dte.ExecuteCommand.

Применение объектной модели автоматизации для вызова, модификации и создания IDE команд, в различие от механизма VSCT, доступного только для VSPackage, разрешает взаимодействовать с командами из модулей-растяжений Visual Studio типа Add-In.

Объект автоматизации DTE разрешает напрямую создавать, модифицировать и вызывать команды через интерфейс DTE.Commands. Способ Commands.AddNamedCommand разрешает добавить команду в IDE (только для Add-In модуля):

dte.Commands.AddNamedCommand(add_in, "MyCommand", "My Command", 
  "My Tooltip", true);

При этом добавленная сходственным образом команда будет сохранена в IDE и появится в меню позже перезапуска среды, даже если сделавшее её растяжение не будет загружено. Следственно вызывать данный способ стоит только при самом первом подключении Add-In модуля позже его установки (детально данный момент описан в разделе, посвященном объектной модели автоматизации Visual Studio). Для способа OnConnection Add-In модулей доступен вариант первичной загрузки (при самой первой инициализации), вызываемый только один раз за всю жизнь модуля, тот, что также дозволено применять для интеграции UI элементов в IDE.

public void OnConnection(object application, 
                         ext_ConnectMode connectMode, 
                         object addInInst, ref Array custom)
{
  switch(connectMode)
  {
      case ext_ConnectMode.ext_cm_UISetup:
          ...
          break;

      ...
  }

}

Интерфейс EnvDTE.Command абстрагирует в себе отдельную команду IDE. Его дозволено применять для модификации связанной с ним команды. Данный интерфейс разрешает трудиться с командами среды как из VSPackage, так и из Add-In модулей. Получим ссылку на объект автоматизации EnvDTE.Command для нашей пользовательской команды MyCommand1 и используем данный интерфейс для назначения ей «жгучей клавиши» стремительного вызова:

EnvDTE.Command MyCommand1 = 
MyPackage.DTE.Commands.Item("MyGroup.MyCommand1", -1);
MyCommand1.Bindings = new object[1] { "Global::Alt 1" };

Назначенное для команды MyGroup.MyCommand1 сочетание стремительного вызова сейчас будет видно в настройках среды в диалоге Keyboard, Environment.

Стоит помнить, что команда Visual Studio не является по умолчанию элементом интерфейса IDE. Способ интерфейса Commands.AddCommandBar разрешает создавать такие элементы UI среды, как пункты основного меню, инструментальные панели, контекстные меню, и назначать на них пользовательские команды.

CommandBar MyToolbar = dte.Commands.AddCommandBar("MyToolbar1", 
  vsCommandBarType.vsCommandBarTypeToolbar) as CommandBar;
CommandBar MyMenu = dte.Commands.AddCommandBar("MyMenu1", 
  vsCommandBarType.vsCommandBarTypeMenu) as CommandBar;
CommandBarButton MyButton1 = MyCommand1.AddControl(MyToolbar) as 
  CommandBarButton;
MyButton1.Caption = "My Command 1";

Для удаления команды либо командной панели из IDE дозволено воспользоваться способом Delete интерфейсов Command/ CommandBar:

MyCommand1.Delete();

В всеобщем случае не рекомендуется создавать команды всякий раз при загрузке Add-In модуля и удалять их при его выгрузке, т.к. это может замедлить загрузку самой IDE, а в случае некорректно отработавшего способа OnDisconnect пользовательские команды не будут всецело очищены до дальнейшего запуска модуля. Следственно, добавление и удаление команд среды рекомендуется осуществлять в процессе инсталляции/деинсталляции модуля, применяя, скажем, приобретение ссылки на интерфейс DTE из стороннего приложения, в данном случае инсталлятора. Детально инициализация Add-Inn модулей и приобретение доступа к объекту DTE из внешних приложений описаны в разделе, посвящённом модели объектной автоматизации EnvDTE.

Любая команда среды (как пользовательская, так и встроенная) может быть вызвана с поддержкой с поддержкой способа ExecuteCommand. Приведём пример вызова пользовательской команды MyCommand1:

MyPackage.DTE.ExecuteCommand("MyGroup.MyCommand1", args);

Для обработки команды стержневой класс Add-In модуля должен наследоваться от интерфейса IDTCommandTarget и реализовывать способ обработки Exec:

public void Exec(string commandName, 
  vsCommandExecOption executeOption, ref object varIn, 
  ref object varOut, ref bool handled)
{
  handled = false;
  if(executeOption == 
    vsCommandExecOption.vsCommandExecOptionDoDefault)
  {
    if(commandName == "MyAddin1.Connect.MyCommand1")
    {
      ...
      handled = true;
      return;
    }
  }
}

 

Рекомендуемые ссылки

 

  1. MSDN. Visual Studio Commands and Switches.
  2. MSDN. Visual Studio Command Table (.Vsct) Files.
  3. MSDN. Designing XML Command Table (.Vsct) Files.
  4. MSDN. Walkthrough: Adding a Toolbar to the IDE.
  5. MSDN. How VSPackages Add User Interface Elements to the IDE.
  6. MZ-Tools. HOWTO: Adding buttons, commandbars and toolbars to Visual Studio .NET from an add-in.
  7. MSDN. How to: Create Toolbars for Tool Windows.

 

Инструментальные окна Visual Studio

В данном разделе рассматривается растяжение Visual Studio через интеграцию в среду пользовательского инструментального окна. Будут затронуты вопросы регистрации и инициализации пользовательских окон в модулях вида VSPackage и Add-In, отображения в окне пользовательских компонентов, обработки событий и контроля состояния окна.

Вступление

Инструментальные окна (tool window) — дочерние окна MDI (Multiple Document Interface) интерфейса Visual Studio предуготовленные для итога информации. Solution Explorer и Error List являются инструментальными окнами. Обыкновенно содержимое инструментального окна не связывается с файлом и не содержит редакторов, для этого существуют особые окна документов.

Модуль-растяжение PVS-Studio интегрирует в IDE несколько инструментальных окон, основным из которых является окно итога итогов обзора (PVS-Studio Output Window). Из него теснее дозволено открыть другие окна, скажем окно поиска по списку. Окно итога итогов PVS-Studio доступно из основного меню Visual Studio (PVS-Studio -> Show PVS-Studio Output Window) и механически открывается при запуске обзора.

В большинстве случаев IDE создаёт и использует только один экземпляр всякого инструментального окна (single instance window), причём экземпляр этого окна остаётся открытым до конца сеанса работы среды. При нажатии на кнопку закрытия такое окно скрывается, а при дальнейшем обращении к нему оно опять становится видимым, причём все отражённые в нём данные сохраняются. Впрочем создание и интеграция в IDE Multi-Instance пользовательских окон (т.е. окон, которые дозволено открывать по нескольку раз) также допустимы. Инструментальное окно может быть закреплено за определённым контекстом интерфейса IDE (т.н. динамические окна). Такое окно будет механически показано пользователю при его попадании в данный контекст.

Интеграция инструментальных окон в IDE поддерживается в VSPackage и Add-In модулях растяжений (причём реализации для этих видов модулей различаются), и требует задания их изначальных параметров и регистрации в системном реестре.

Регистрация и инициализация инструментальных окон

Устанавливаемый совместно с Visual Studio SDK образец плана VSPackage разрешает сгенерировать пользовательское инструментальное окно для создаваемого им плана пакета модуля-растяжения. Данный план теснее должен содержать все рассмотренные ниже элементы, следственно его комфортно применять как пример при знакомстве с процессом интеграции пользовательских окон в Visual Studio.

Регистрация, инициализация и вызов окна в VSPackage

Регистрация пользовательского окна в среде требует добавления информации о нём в особый раздел системного реестра ветки Visual Studio. Данный процесс может быть автоматизирован с поддержкой генерации pkgdef файла, тот, что будет содержать в себе всю нужную регистрационную информацию об окне. Содержимое pkgdef файла задаётся с поддержкой особых регистрационных признаков подкласса Package.

За непосредственную регистрацию пользовательского инструментального окна в модуль VSPackage отвечает признак ProvideToolWindow класса Package:

[ProvideToolWindow(typeof(MyWindowPane), Orientation = 
ToolWindowOrientation.Right, Style = VsDockStyle.Tabbed, Window = 
Microsoft.VisualStudio.Shell.Interop.ToolWindowGuids.Outputwindow, 
MultiInstances = false, Transient = true, Width = 500, Height = 250, 
PositionX = 300, PositionY = 300)]

Разглядим некоторые из параметров данного признака. Typeof указывает на пользовательскую реализацию клиентской области окна (ToolWindowPane). Параметр MultiInstances разрешает применять окно в Multi-Instance режиме, т.е. с вероятностью открывать несколько экземпляров окна единовременно. Параметры Orientaton, Size и Style разрешают задать изначальное расположение окна при первом открытии. Стоит помнить, ч// control that you want to host in the new tool window, as well as // its caption and a unique GUID. string assemblypath = “C:\MyToolwindow\MyToolWindowControl.dll”; string classname = ” MyToolWindowControl.MyUserControl”; string guidpos = “{E87F0FC8-5330-442C-AF56-4F42B5F1AD11}”; string caption = “My Window”; // Creates the new tool window and inserts the user control into it. myWindow = window.CreateToolWindow2(add_in, assemblypath, classname, caption, guidpos, ref ctlobj); myWindow.Visible = true; }
В приведённом примере создаётся пользовательское инструментальное окно, использующее класс MyToolWindowControl.MyUserControl в качестве клиентской области. Класс MyToolWindowControl.MyUserControl может находиться либо в той же assembly, что и инициализирующий его add-in, либо в отдельной библиотеке, имеющей полную COM видимость (скажем, через опцию Register for COM interop в настройках плана). В качестве MyUserControl может быть использован типовой композитный пользовательский подклас UserControl.

Реализация пользовательского инструментального окна в модуле VSPackage

Инструментальные окна состоят из каркаса-рамки клиентской области. При этом каркас окна предоставляется средой и отвечает за стыковку с другими компонентами интерфейса (docking), размер и расположение окна. Клиентская область (pane) отображает содержимое окна, контролируемое пользователем. Инструментальные окна могут содержать пользовательские WinForms и WPF компоненты и предоставляют вероятность обрабатывать такие типовые события, как скажем OnShow, OnMove и т.п.

Пользовательское инструментальное окно, а вернее его клиентская область, реализуется путём наследования от класса, реализующего стандартное пустое окно IDE — ToolWindowPane:

[Guid("870ab1d8-b434-4e86-a479-e49b3c6797f0")]
public class MyToolWindow : ToolWindowPane
{

  public MyToolWindow():base(null)
  {
    this.Caption = Resources.ToolWindowTitle;
    this.BitmapResourceID = 301;
    this.BitmapIndex = 1;
    ...

  }

}

Признак Guid уникально идентифицирует всякое пользовательское окно. В случае если модуль создаёт несколько окон различного типа, всякое из них должно иметь свой неповторимый Guid. Подкласс ToolWindowPane может в последующем быть модифицирован для отображения в нём пользовательских компонентов и управления его состоянием.

Хостинг пользовательских компонентов

Базовый класс ToolWindowPane реализует пустое инструментальное окно среды. Наследование от данного класса разрешает отобразить в этом окне пользовательские WinForms либо WPF компоненты.

До версии Visual Studio 2008 инструментальные окна нативно поддерживали отображение WinForms пользовательских компонентов, а также могли отображать WPF компоненты с поддержкой WPF Interoperability компонента ElementHost. Начиная с Visual Studio 2010, инструментальные окна стали базироваться на спецтехнологии WPF, но всё еще поддерживают загрузку и отображение WinForms компонентов в режиме совместимости.

Для отображения в окне пользовательского WinForms компонента дозволено воспользоваться переопределённым свойством Window у ToolWindowPane:

public MyUserControl control;

public MyToolWindow():base(null)
{
  this.Caption = Resources.ToolWindowTitle;
  this.BitmapResourceID = 301;
  this.BitmapIndex = 1;
  this.control = new MyUserControl();
}

public override IWin32Window Window
{
  get { return (IWin32Window)control; }
}

При этом MyUserControl представляет собой обыкновенный комбинированный компонент типа System.Windows.Forms.UserControl, разрешающий отобразить всякие другие пользовательские компоненты. Для отображения WPF компонентов в нём дозволено воспользоваться стандартным компонентом WPF ElementHost.

Начиная с версии Visual Studio 2010 возникла вероятность нативно отображать WPF компоненты. Для этого необходимо передать ссылку на ваш WPF компонент полю Content базового класса:

public MyToolWindow():base(null)
{
  this.Caption = Resources.ToolWindowTitle;
  this.BitmapResourceID = 301;
  this.BitmapIndex = 1;
  base.Content = new MyWPFUserControl();
}

Подметьте, что одновременное применение этих 2-х способов не поддерживается. При назначении WPF компонента в base.Content переопределённое качество Window будет проигнорировано.

В основном окне Output Window модуля-растяжения PVS-Studio расположена виртуальная таблица, основанная на open-source плане SourceGrid, предуготовленная для работы с итогами статического обзора. При этом таблица применяется для отображения стандартной таблицы ADO.NET System.Data.Datatable, которая и применяется для непосредственного хранения итогов работы анализатора. До версии 4.00 PVS-Studio применял для итога итогов обзора стандартное IDE окно Error List, впрочем, по мере становления вероятностей анализатора, его функционала стало неудовлетворительно. Помимо неосуществимости быть расширенным такими специфичными для статического анализатора элементами управления, как скажем механизмы для фильтрации и средства подавления неверных срабатываний, Error List, являясь обыкновенным real grid элементом, дозволял адекватно оперировать лишь 1-2 тысячами сообщений. Большее число сообщений начинало приводить теснее к невидимым лагам каждого интерфейса IDE. Практика же применения статического обзора показала, что для больших планов, как скажем Chromium либо LLVM, всеобщее число диагностических сообщений (с учётом теснее размеченных неверных срабатываний, пользовательских низкоуровневых и оптимизационных диагностик) абсолютно может добиваться значений в несколько десятков тысяч.

Реализация собственного окна итога итогов PVS-Studio на основе виртуального грида, связанного с таблицей БД, разрешает отображать и комфортно трудиться с сотнями тысяч сообщений единовременно. Дюже значимым аспектом при работе с итогами статического обзора является также вероятность их эластичной фильтрации, так как ручной просмотр для поиска реальных ошибок даже такого «небольшого» число диагностических сообщений как 1-2 тысячи, фактически немыслим. При хранении же итогов в таблице Datatable сходственная фильтрация легко доступна с поддержкой примитивных SQL запросов, причём итоги наложения фильтров становятся видны фактически молниеносно в отражении таблицы на виртуальном гриде.

Обработка событий в инструментальных окнах

Клиентская область инструментального окна (представленная нашим преемником от класса ToolWindowPane) может обрабатывать события взаимодействия пользователя с окном IDE. Для того Дабы подписаться на обработку этих событий дозволено воспользоваться интерфейсом IVsWindowFrameNotify3. Приведём пример реализации данного интерфейса:

public sealed class WindowStatus: IVsWindowFrameNotify3
{
  // Private fields to keep track of the last known state
  private int x = 0;
  private int y = 0;
  private int width = 0;
  private int height = 0;
  private bool dockable = false;

  #region Public properties

  // Return the current horizontal position of the window
  public int X
  {
    get { return x; }
  }

  // Return the current vertical position of the window
  public int Y
  {
    get { return y; }
  }

  // Return the current width of the window
  public int Width
  {
    get { return width; }
  }

  // Return the current height of the window
  public int Height
  {
    get { return height; }
  }

  // Is the window dockable
  public bool IsDockable
  {
    get { return dockable; }
  }

  #endregion

  public WindowStatus()
  {}

  #region IVsWindowFrameNotify3 Members
  // This is called when the window is being closed
  public int OnClose(ref uint pgrfSaveOptions)
  {
    return Microsoft.VisualStudio.VSConstants.S_OK;
  }

  // This is called when a window "dock state" changes. 
  public int OnDockableChange(int fDockable, int x, int y, int w, 
  int h)
  {
    this.x = x;
    this.y = y;
    this.width = w;
    this.height = h;
    this.dockable = (fDockable != 0);

    return Microsoft.VisualStudio.VSConstants.S_OK;
  }

  // This is called when the window is moved
  public int OnMove(int x, int y, int w, int h)
  {
    this.x = x;
    this.y = y;
    this.width = w;
    this.height = h;

    return Microsoft.VisualStudio.VSConstants.S_OK;
  }

  // This is called when the window is shown or hidden
  public int OnShow(int fShow)
  {
    return Microsoft.VisualStudio.VSConstants.S_OK;
  }

  /// This is called when the window is resized
  public int OnSize(int x, int y, int w, int h)
  {
    this.x = x;
    this.y = y;
    this.width = w;
    this.height = h;
    return Microsoft.VisualStudio.VSConstants.S_OK;
  }

  #endregion

}

Как видно из приведённого кода, реализующий интерфейс класс WindowStatus может обрабатывать такие метаморфозы в состоянии пользовательского окна, как метаморфоза размера, расположения на экране, видимости и т.п. Сейчас подпишем наше окно на обработку данных событий. Для этого переопределим способ OnToolWindowCreated у нашего класса-преемника ToolWindowPane:

public class MyToolWindow: ToolWindowPane
{
  public override void OnToolWindowCreated()
  {
    base.OnToolWindowCreated();

    // Register to the window events
    WindowStatus windowFrameEventsHandler = new WindowStatus();

ErrorHandler.ThrowOnFailure(((IVsWindowFrame)this.Frame).SetProperty(
  (int)__VSFPROPID.VSFPROPID_ViewHelper,
  (IVsWindowFrameNotify3)windowFrameEventsHandler));
  }

  ...
}

 

Контроль состояния окна

Контроль состояния окна дозволено осуществлять с поддержкой обработчиков событий нашей реализации интерфейса IVsWindowFrameNotify3.

Способ OnShow информирует модулю-растяжению об изменении ранга видимости инструментального окна, дозволяя определить появление/скрытие окна для пользователя, скажем, когда пользователь переключает вкладку с одного окна на другое. Нынешнее состояние видимости дозволено узнать с поддержкой параметра fShow, соответствующего списку __FRAMESHOW.

Способ OnClose информирует о закрытии каркаса окна, разрешая задать нужное поведение IDE с поддержкой параметра pgrfSaveOptions, руководящего отображением диалога о сохранении открытого в окне документа (__FRAMECLOSE).

Способ OnDockableChange сообщает модуль об изменении docking ранга окна. Параметр fDockable показывает, сцеплено ли окно с каким-либо иным, а остальные параметры указывают новейший размер и координаты окна до либо позже сцепления.

Параметры способов OnMove и OnSize информируют новые координаты и/или размер окна при его перетаскивании и маштабировании.

Рекомендуемые ссылки

 

  1. MSDN. Kinds of Windows.
  2. MSDN. Tool Windows.
  3. MSDN. Tool Window Essentials.
  4. MSDN. Tool Window Walkthroughs.
  5. MSDN. Arranging and Using Windows in Visual Studio.
  6. MZ-Tools. HOWTO: Understanding toolwindow states in Visual Studio.

 

Интеграция в настройки Visual Studio

В данном разделе рассматривается растяжение среды Visual Studio путём интеграции в её настройки пользовательских групп и страниц настроек. Показана регистрация и интеграция в IDE пользовательских страниц настроек с поддержкой разных видов подключаемых модулей растяжений, методы отображения стандартных и пользовательских компонентов в них. Рассмотрены вероятности программного доступа к настройкам среды через объектную модель автоматизации Visual Studio, а также механизм сохранения состояния настроек.

Вступление

Visual Studio использует цельное диалоговое окно для доступа к настройкам разных компонентов среды разработки. Это окно доступно через пункт основного меню IDE Tools -> Options. Базовым элементом настроек Visual Studio является страница настроек. Диалоговое окно Options располагает страницы настроек в древовидной структуре в соответствии с их принадлежностью к разным функциональным группам. Причём всякая из страниц может быть уникально идентифицирована по имени её группы и персональному имени, скажем страница настроек редактора Visual Basic кода: «Text Editor, Basic».

Модули-растяжения имеют вероятность читать и модифицировать значения отдельных настроек у зарегистрированных в IDE страниц. Также модули могут создавать и регистрировать личные пользовательские страницы в среде с применением объектной модели автоматизации и классов MPF (Managed Package Framework, доступно только для VSPackage модулей). Visual Studio имеет встроенный механизм для сохранения состояния объекта-страницы, тот, что применяется по умолчанию, а также может быть переопределён либо отключен.

Создание и регистрация пользовательских страниц настроек

При разработке модуля-растяжения Visual Studio может оказаться пригодным сопоставить его с одной либо несколькими пользовательскими страницами настроек в меню Tools -> Options. Сходственный инструмент для конфигурации и управления растяжением будет соответствовать UI парадигме среды разработки и комфортен при работе с растяжением непринужденно из IDE. Способы реализации пользовательской страницы настроек, её интеграции в IDE и регистрации будут различаться в зависимости от типа разрабатываемого модуля-растяжения и применяемой спецтехнологии (модель автоматизации либо MPF).

Интеграция с применением MPF классов

Managed Package Framework разрешает создавать пользовательские страниц настроек путём наследования от класса DialogPage. В связи с тем, что среда самостоятельно подгружает страницу настроек при открытии соответствующего раздела в диалоге Tools -> Options, всякая пользовательская страница должна быть реализована в виде отдельного самостоятельного объекта.

Объект, реализующий таким образом пользовательскую страницу настроек, должен быть связан с пакетом растяжения Visual Studio (VSPackage) через признак ProvideOptionPage подкласса Package.

[ProvideOptionPageAttribute(typeof(OptionsPageRegistration),
"MyPackage", "MyOptionsPage", 113, 114, true)]

Признак задаёт группу и имя страницы в диалоге настроек IDE и должен быть определён для всякой интегрируемой пользовательской страницы. Реально признак обеспечивает регистрацию страницы в системном реестре через pkgdef файл, и никак сам по себе не влияет на исполняемый код. Для правильной работы страница настроек должна быть зарегистрирована в дальнейшем разделе системного реестра:

HKEY_LOCAL_MACHINESOFTWAREMicrosoftVisualStudio<VsVersion>
ToolsOptionsPages

Тут <VsVersion> — номер версии Visual Studio, скажем 10.0. Данная запись будет механически сделана при применении признака ProvideOptionPage. Стоит также помнить, что для удаления пакета-растяжения из Visual Studio понадобится очистить все сделанные им записи в системном реестре, в том числе и записи, относящиеся к пользовательским страницам настроек. Начиная с версии Visual Studio 2010 VSPackage модули могут применять VSIX пакеты для своего развёртывания и деинсталляции, при этом инсталлятор VSIX механически записывает либо удаляет записи о модуле в реестре. Для больше ветхих версий IDE чистку реестра понадобится провести вручную, скажем в standalone инсталляторе.

Шестой bool довод конструктора признака разрешает зарегистрировать пользовательскую страницу как объект автоматизации. Это дозволит получить доступ к настройкам, определённым в данной странице через интерфейсы EnvDTE, из сторонних plug-in модулей. Регистрация объекта автоматизации требует создания записей в системном реестре (что происходит механически при применении данных признаков) в следующих ветках:

HKEY_LOCAL_MACHINESOFTWAREMicrosoftVisualStudio<VersionPackages
<PackageGUID>Automation 

HKEY_LOCAL_MACHINESOFTWAREMicrosoftVisualStudio<Version>
AutomationProperties

Признак ProvideProfile разрешает зарегистрировать страницу настроек либо всякий иной само­стоятельный объект, реализующий интерфейс IProfileManager, для применения встроенного IDE механизм сохранения настроек.

Реализация наследования от MPF класса DialogPage

Минимальным требованием к подклассу DialogPage для реализации пользовательской страницы настроек является присутствие в классе-преемнике открытых свойств (public properties). Сходственная базовая реализация будет выглядеть дальнейшим образом:

namespace MyPackage
{
  class MyOptionsPage : DialogPage
  {
    bool myOption = true;
    public bool MyOption
    {
        get { return this. myOption; }
        set { this. myOption = value; }
    }
  }
}

Данная базовая реализация подкласса DialogPage будет применять для отображения клиентской области пользовательского окна настроек типовой PropertyGrid, в котором будут показаны все открытые свойства класса-преемника. Это может оказаться пригодным, когда конфигурационные параметры плагина довольно примитивны для их отображения и редактирования через типовые PropertyGrid редакторы. Применение родного средства отображения среды дозволит также избежать и распространённых задач с правильным отображением и масштабированием пользовательских интерфейсных компонентов (скажем, на разных системных DPI) непринужденно в самом диалоговом окне Visual Studio.

Для отображения в окне настроек пользовательского интерфейса нужно переопределить качество Windowкласса DialogPage:

[BrowsableAttribute(false)]
protected override IWin32Window Window
{
  get
  {
    return MyUserControl;
  }
}

В него нужно передать ссылку на пользовательский объект IWin32Window, реализующий интерфейс клиентской области окна. Также стоит помнить, что Visual Studio требует от таких окон быть непрерывными, т.е. они не обязаны пересоздаваться в последующих вызовах. Т.к. такие объекты, как Windows Forms, могут в течение своего существования самосильно пересоздавать свой оконный handle, желанно применять тут ссылку, полученную через объект типа UserControl.

Через качество AutomationObject пользовательской страницы настроек, унаследованной от класса DialogPage, определяются те открытые свойсds_andmk!lt;/Category> </ToolsOptionsPage> </Extensibility>
Изложение пользовательской страницы настроек расположено в узле <ToolsOptionsPage>. Тут узел <Assembly> указывает на библиотеку, содержащую пользовательский компонент, тот, что будет использован для отображения клиентской области окна натроек. Узел <FullClassName> указывает на полное имя пользовательского компонента в формате Namespace.ClassName. Узлы <Category> и <SubCategory> определяют расположение пользовательской страницы в древовидной структуре настроек диалога Tools -> Options, задавая группу и персональное имя страницы. В качестве <Category> может быть указана как теснее присутствующая, так и новая группа. Как видно из примера, в данном случае пользовательский компонент MyAddin1.UserControl1 находится в одной библиотеке с самим модулем, но это не является непременным требованием.

Visual Studio подгружает страницу настроек позже первого обращения к ней через диалог Options. В различие от интеграции страницы настроек через Managed Package Framework, изложение страницы содержится только в xml файле изложении addin, и, соответственно, страница будет загружена только при выявлении средой такого файла. Visual Studio читает доступные ей addin файлы непринужденно позже своей загрузки. Поиск addin файлов производится в директориях, задаваемых на странице настроек Environment -> Add-In/Macross Security. В различие от страниц настроек, реализованных через MPF, сходственный высокоуровневый способ интеграции не регистрирует страницу, как объект автоматизации, и соответственно, не предоставляет вероятностей для применения механизмов доступа к её содержимому через объектную модель автоматизации и встроенных IDE механизмов сохранения состояния страницы.

Доступ к окнам настроек с поддержкой модели автоматизации

Объектная модель автоматизации Visual Studio предоставляет вероятность получить доступ ко каждому системным настройкам среды диалога Tools -> Options, помимо страниц Dynamic Help и Fonts and Colors (для них существуют отдельные API). Пользовательские страницы настроек будут доступны через модель автоматизации в случае, если они были зарегистрированы как объекты автоматизации, как было описано в предыдущей главе.

Для приобретения настроек дозволено воспользоваться способом get_Properties интерфейса EnvDTE.DTE:

Properties propertiesList = PVSStudio.DTE.get_Properties("MyPackage", 
"MyOptionsPage");

Страница настроек тут идентифицируется по её имени и имени её группы. Для приобретения определенного значения свойства:

Property MyProp1 = propertiesList.Item("MyOption1");

Сейчас значение свойства дозволено получить и модифицировать через поле MyProp1.Value.

Для открытия страницы пользовательской настроек в диалоге Options дозволено воспользоваться способом ShowOptionPage MPF класса Package:

MyPackage.ShowOptionPage(typeof(MyOptionsPage));

Данные способ принимает тип (итог typeof) пользовательского класса преемника от DialogPage. Если нужно открыть страницу, определённую вне разрабатываемого VSPackage (скажем, стандартную страницу IDE либо страницу, определённую в ином модуле), то дозволено обнаружить её по GUID идентификатору, тот, что задан в дальнейшей ветке реестра:

HKEY_LOCAL_MACHINESOFTWAREMicrosoftVisualStudio9.0
ToolsOptionsPages<OptionsPageNme>

Где <OptionsPageName> — имя страницы в диалоге Tools -> Options. Приведём пример открытия стандартной страницы IDE TextEditor -> General с поддержкой всеобщей службы IMenuCommandService:

string targetGUID = "734A5DE2-DEBA-11d0-A6D0-00C04FB67F6A";
var command = new CommandID(VSConstants.GUID_VSStandardCommandSet97,
  VSConstants.cmdidToolsOptions);
var mcs = GetService(typeof(IMenuCommandService)) 
  as MenuCommandService;
mcs.GlobalInvoke(command, targetGUID);

На самом деле, данный код реально равнозначен выполнению команды среды Tools.Options. Её дозволено также вызвать с поддержкой способа ExecuteCommand объекта EnvDTE.DTE:

dte.ExecuteCommand("Tools.Options", 
"734A5DE2-DEBA-11d0-A6D0-00C04FB67F6A").

 

Рекомендуемые ссылки

 

  1. MSDN. Options Pages.
  2. MSDN. State Persistence and the Visual Studio IDE.
  3. MSDN. User Settings and Options.
  4. MSDN. Registering Custom Options Pages.
  5. MSDN. Providing Automation for VSPackages.

 

Проектная модель Visual Studio

В данном разделе будет рассмотрена конструкция проектной модели Visual Studio и её реализация на примере стандартной модели Visual C (VCProject). Приведены примеры применения проектной модели для приобретения списков проектных элементов и их компиляционных свойств через соответствующие конфигурации. Также будет рассмотрена сторонняя (пользовательская) реализация проектной модели на примере само­стоятельной изолированной оболочки (Visual Studio Isolated Shell) Atmel Studio

Вступление

Проектная модель Visual Studio представляет собой группу интерфейсов, описывающих функционал компилятора, линковщика и других сборочных инструментов, а также конструкцию MSVS-совместимых планов. Она связана с объектной моделью автоматизации через late-bound качество VCProjects. Проектная модель Visual C является растяжением стандартной проектной модели Visual Studio, обеспечивая вероятность доступа к специфичному для Visual C (vcrpoj/vcxproj) планов функционалу. Проектная модель Visual C является независимым COM компонентом, доступным через файл VCProjectEngine.dll, тот, что также может быть использован самостоятельно вне среды Visual Studio. Сторонние разработчики могут создавать свои реализации проектных моделей, добавляя таким образом в Visual Studio поддержку новых компиляторов и языков программирования.

Конструкция проектной модели

Visual Studio предоставляет расширяемую проектно-само­стоятельную объектную модель, в которой представлены решения, планы, объекты кода, документы и т.п. Всякий тип MSVS планов представлен соответствующим ему интерфейсом автоматизации. Всякий инструмент в среде, имеющий сопоставленные с ним планы, также сопоставлен и с объектом типа Project. Стандартная модель Visual C также следует данной всеобщей схеме проектной автоматизации:

Projects
  |- Project -- Object(unique for the project type)
      |- ProjectItems (a collection of ProjectItem)
          |- ProjectItem (single object) -- ProjectItems (another
                                                          collection)
              |- Object(unique for the project type)

Интерфейс Projects предоставляет общность абстрактных планов типа Project. Интерфейс Project описывает отвлеченный план, т.е. может указывать на план всякий проектной модели, придерживающейся стандартной схемы. При этом все уникальные свойства определенной модели обязаны быть описаны через особый, неповторимый для данной модели, интерфейс. Ссылку на такой объект дозволено получить через поле Project.Object. Скажем, для проектной модели Visual C такой неповторимый объект будет иметь тип VCProject, а для проектной модели Atmel Studio это будет, скажем, AvrGCCNode:

Project proj;
...
VCProject vcproj = proj.Object as VCProject;
AvrGCCNode AvrGccProject = proj.Object as AvrGCCNode;

Общность Projects дозволено получить для всех загруженных в IDE планов solution файла через поле dte.Solution.Projects либо только для одной проектной модели через способ DTE.GetObject. Скажем, для планов Visual C :

Projects vcprojs = m_dte.GetObject("VCProjects") as Projects;

Для приобретения планов всех типов дозволено воспользоваться дальнейшим кодом:

Projects AllProjs = PVSStudio.DTE.Solution.Projects;

Интерфейс ProjectItems представляет общность абстрактных элементов дерева плана типа ProjectItem. По аналогии с Project, ProjectItem может описывать элемент всякого типа, содержащий в том числе и такую же вложенную общность ProjectItems (через поле ProjectItem.ProjectItems) либо быть планом Project. Объект неповторимый для определенной проектной модели дозволено по аналогии получить через ProjectItem.Object. Скажем, у проектной модели Visual C файл с начальным кодом представлен типом VCFile, а у проектной модели Atmel Studio — AvrGccFileNode:

ProjectItem projectItem;
...
VCFile file = projectItem.Object as VCFile;
AvrGccFileNode file = projectItem.Object as AvrGccFileNode;

Подобно выглядит и приобретение вложенного плана, в случае, когда данный элемент иерархии описывает план:

Project proj = projectItem.Object as Project;

 

Рекурсивный обход всех элементов ветви Solution дерева

Для обхода ветви Solution дерева дозволено воспользоваться интерфейсом управленk! … public void EnumHierarchyItemsFlat(uint itemid, IVsHierarchy hierarchy, int recursionLevel, bool visibleNodesOnly) { if (hierarchy == null) return; int hr; object pVar; hr = hierarchy.GetProperty(itemid, (int)__VSHPROPID.VSHPROPID_ExtObject, out pVar); ProjectItem projectItem = pVar as ProjectItem; if (projectItem != null) { … } recursionLevel ; //Get the first child node of the current hierarchy being walked hr = hierarchy.GetProperty(itemid, (visibleNodesOnly ? (int)__VSHPROPID.VSHPROPID_FirstVisibleChild :( int)__VSHPROPID.VSHPROPID_FirstChild), out pVar); Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(hr); if (VSConstants.S_OK == hr) { //We are using Depth first search so at each level we recurse //to check if the node has any children // and then look for siblings. uint childId = GetItemId(pVar); while (childId != VSConstants.VSITEMID_NIL) { EnumHierarchyItemsFlat(childId, hierarchy, recursionLevel, visibleNodesOnly); hr = hierarchy.GetProperty(childId, (visibleNodesOnly ? (int)__VSHPROPID.VSHPROPID_NextVisibleSibling : (int)__VSHPROPID.VSHPROPID_NextSibling), out pVar); if (VSConstants.S_OK == hr) { childId = GetItemId(pVar); } else { Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(hr); break; } } } } private uint GetItemId(object pvar) { if (pvar == null) return VSConstants.VSITEMID_NIL; if (pvar is int) return (uint)(int)pvar; if (pvar is uint) return (uint)pvar; if (pvar is short) return (uint)(short)pvar; if (pvar is ushort) return (uint)(ushort)pvar; if (pvar is long) return (uint)(long)pvar; return VSConstants.VSITEMID_NIL; }
Полученный в данном примере объект типа ProjectItem для всякого узла дерева дозволит нам получить соответствующий ему объект проектной модели Visual С (как однако и всякий иной модели) через его поле Object, как было описано выше.

Обход всех планов Solution дерева

Для обхода всех планов дерева дозволено воспользоваться интерфейсом DTE.Solution.Projects:

if (m_DTE.Solution.Projects != null)
  {
  try
    {
      foreach (object prj in m_DTE.Solution.Projects)
      {
        EnvDTE.Project proj = prj as EnvDTE.Project;
        if (proj != null)
          WalkSolutionFolders(proj);
      } 
    }
  }

Помимо непринужденно планов, Solution дерево также может содержать узлы-папки (Solution Folders). Их необходимо рассматривать при обходе всякого Project элемента:

public void WalkSolutionFolders(Project prj)
{
  VCProject vcprj = prj.Object as VCProject;
  if (vcprj != null && prj.Kind.Equals(VCCProjectTypeGUID))
  {
    if (!ProjectExcludedFromBuild(prj))
    {
      IVsHierarchy projectHierarchy = ToHierarchy(prj);
      EnumHierarchyItemsFlat(VSConstants.VSITEMID_ROOT, 
      projectHierarchy, 0, false);
    }
  }
  else if (prj.ProjectItems != null)
  {
    foreach (ProjectItem item in prj.ProjectItems)
    {
      Project nextlevelprj = item.Object as Project;
      if (nextlevelprj != null && !ProjectUnloaded(nextlevelprj))
      WalkSolutionFolders(nextlevelprj);
    }
  }
} 

Отдельную проверку стоит добавить для планов, исключённых из сборки, т.к. их элементы не будут доступны через модель автоматизации позже их выгрузки:

public bool ProjectExcludedFromBuild(Project project)
{
  if (project.UniqueName.Equals("<MiscFiles>", 
    StringComparison.InvariantCultureIgnoreCase))
  return true;
  Solution2 solution = m_DTE.Solution as Solution2;
  SolutionBuild2 solutionBuild = 
    (SolutionBuild2)solution.SolutionBuild;
    SolutionContexts projectContexts = 
    solutionBuild.ActiveConfiguration.SolutionContexts;
    //Skip this  project if it is excluded from build.
    bool shouldbuild = 
      projectContexts.Item(project.UniqueName).ShouldBuild;
    return !shouldbuild;
}

 

Обход выделенных элементов

Для обхода элементов, выделенных пользователем в интерфейсе окна Solution Explorer, дозволено воспользоваться интерфейсом DTE.SelectedItems.

foreach (SelectedItem item in items)
{
  VCProject vcproj = null;
  if (item.Project != null)
  {
    vcproj = item.Project.Object as VCProject;

    if (vcproj != null && item.Project.Kind.Equals("{"   
      VSProjectTypes.VCpp   "}"))
      {
        IVsHierarchy projectHierarchy = ToHierarchy(item.Project);
        PatternsForActiveConfigurations.Clear();
        EnumHierarchyItemsFlat(VSConstants.VSITEMID_ROOT, 
        projectHierarchy, 0, false, files, showProgressDialog);
      }
      else if (item.Project.ProjectItems != null)
      {
        //solution folder
        if (!ProjectUnloaded(item.Project))
          WalkSolutionFolders(item.Project);
      }
    }
    else if (item.ProjectItem != null)
    {
      //walking files
      ...
      else if (item.ProjectItem.ProjectItems != null)
      if (item.ProjectItem.ProjectItems.Count > 0)
        WalkProjectItemTree(item.ProjectItem);
    }
  }

private void WalkProjectItemTree(object CurrentItem)
{
  Project CurProject = null;
  CurProject = CurrentItem as Project;
  if (CurProject != null)
  {
    IVsHierarchy projectHierarchy = ToHierarchy(CurProject);
    PatternsForActiveConfigurations.Clear();
    EnumHierarchyItemsFlat(VSConstants.VSITEMID_ROOT, 
      projectHierarchy, 0, false);

    return;
  }
    ProjectItem item = null;
    item = CurrentItem as ProjectItem;
    if (item != null)
    {
        ...
        if (item.ProjectItems != null)
            if (item.ProjectItems.Count > 0)
            {
                foreach (object NextItem in item.ProjectItems)
                    WalkProjectItemTree(NextItem);
            }
    }
}

 

Проектная модель Visual C . Конфигурации и свойства планов и файлов

Ранее мы рассматривали обобщённую, внешнюю часть проектной модели Visual Studio, интерфейсы которой определены в EnvDTE и доступны из всякий реализации модели автоматизации. Остановимся сейчас на одной из базовых реализаций проектной модели — Microsoft Visual C , которая определена в пространстве имён Microsoft.VisualStudio.VCProjectEngine.

Проектная модель Visual C реализует стандартную проектную модель Visual Studio, следственно интерфейсы, описанные в первом подразделе могут применяться в том числе и для работы с планами этой модели. Интерфейсы, специфичные для модели Visual C , определены в файле Microsoft.VisualStudio.VCProjectEngine.dll, тот, что нужно добавить в список используемых сборок разрабатываемого плана растяжения.

Модель Visual C физически хранит сборочные параметры (параметры компиляции, линковки, пред и позже-сборочные шаги, параметры запуска сторонних утилит и т.п.) С/С файлов начального кода в своих проектных xml файлах (vcproj/vcxproj). Данные параметры доступны пользователю Visual Studio через интерфейс диалоговых окон страниц свойств (Property Pages).

Комплекты свойств определены для всякого сочетания сборочной конфигурации плана (скажем, Debug и Release) и сборочной платформы (Win32, x64, IA64 и т.п.). При этом такие комплекты свойств определены на ярусе каждого плана, а отдельные свойства могут быть переопределены для всякого определенного файла (по умолчанию свойства файла наследуются от плана). Какие именно свойства могут быть переопределены зависит от типа файла, скажем для заголовочных файлов доступно переопределение только свойства ExcludedFromBuild, тогда как для cpp файла допустимо переопределение всякого свойства компиляции.

Приобретение конфигураций

В проектной модели Visual C страницы свойств представлены через интерфейсы VCConfiguration (для плана) иVCFileConfiguration (для файла). Для приобретения данных объектов будем отталкиваться от объекта ProjectItem, представляющего собой отвлеченный элемент Solution дерева среды.

ProjectItem item;
VCFile vcfile = item.Object as VCFile;
Project project = item.ContainingProject;
String pattern = "Release|x64";
if (String.IsNullOrEmpty(pattern))
  return null;

VCFileConfiguration fileconfig = null;
IVCCollection fileCfgs = (IVCCollection)vcfile.FileConfigurations;
fileconfig = fileCfgs.Item(pattern) as VCFileConfiguration;
if (fileconfig == null)
  if (fileCfgs.Count == 1)
    fileconfig = (VCFileConfiguration)fileCfgs.Item(0);
        

В данном примере мы получили файловую конфигурацию для объекта VCFile (С/C заголовочный либо начальный файл) с поддержкой способа Item(), передав в него pattern (имя конфигурации и имя платформы) требуемой нам конфигурации. Pattern сборочной конфигурации определён на ярусе плана. Приведём пример приобретения энергичной (выбранной в интерфейсе IDE) конфигурации плана.

ConfigurationManager cm = project.ConfigurationManager;
Configuration conf = cm.ActiveConfiguration;
String platformName = conf.PlatformName;
String configName = conf.ConfigurationName;
String pattern = configName   "|"   platformName;
return pattern;

Качество ActiveConfiguration следует применять с осторожностью, т.к. довольно Зачастую мы сталкивались с исключениями при частом обращении к нему из IDE модуля-растяжения PVS-Studio. В частности, данное поле оказывается недостижимым через объектную модель автоматизации в моменты, когда пользователь Visual Studio осуществляет действия по сборке планов либо легко взаимодействует с интерфейсом среды. Так как немыслимо однозначно предсказать сходственные действия со стороны пользователя, рекомендуется добавочно обрабатывать сходственные обстановки в механизмах доступа к свойствам объектов модели автоматизации. Подметим, что данная определенная обстановка не является подвидом COM исключений, обработка которых рассматривалась в разделе, посвящённом EnvDTE интерфейсам, и скорее каждого связана с недоработками в самой модели автоматизации.

Получим сейчас конфигурацию плана, содержащего данный файл:

VCConfiguration cfg=(VCConfiguration)fileconfig.ProjectConfiguration;

Непринужденно сами интерфейсы конфигураций содержат только всеобщие свойства вкладки General, а свойства всякого определенного сборочного инструмента определены в объектах, ссылки на которые доступны через поле VCConfiguration.Tools либо VCFileConfiguration.Tool (одному файлу соответствует только один сборочный инструмент).

Разглядим, скажем, интерфейс, описывающий параметры C компилятора VCCLCompilerTool:

ct = ((IVCCollection)cfg.Tools).Item("VCCLCompilerTool") as 
  VCCLCompilerTool;
ctf = fileconfig.Tool as VCCLCompilerTool;

Получим для примера содержимое поля AdditionalOptions настроек компилятора, применяя для вычисления значений макросов в этом поле способ Evaluate.

String ct_add = fileconfig.Evaluate(ct.AdditionalOptions);
String ctf_add = fileconfig.Evaluate(ctf.AdditionalOptions);

 

Property Sheets

Файлы свойств (property sheets) представляют собой XML файлы с растяжением props, разрешающие самостоятельно определять сборочные свойства плана (т.е. параметры запуска разных сборочных инструментов, таких, как компилятор либо линковщик). Property sheets поддерживают наследование и могут быть использованы для определения сборочных конфигураций в нескольких планах единовременно, т.е. конфигурация, определённая в файле плана (vcproj/vcxproj), может наследовать часть своих свойств из одного либо нескольких props файлов.

Для работы с файлами свойств (Property sheets) проектная модель Visual C предоставляет интерфейсVCPropertySheet. Получить доступ к общности объектов VCPropertySheet плана дозволено через поле VCConfiguration. PropertySheets:

IVCCollection PSheets_all = fileconfig.PropertySheets;

Подобно поле PropertySheets интерфейса VCPropertySheet дозволит получить ссылку на все дочерние файлы настроек для заданного объекта. Разглядим пример рекурсивного обхода всех файлов настроек плана:

private void ProcessAllPropertySheets(VCConfiguration cfg, IVCCollection PSheets)
{
  foreach (VCPropertySheet propertySheet in PSheets)
  {
    VCCLCompilerTool ctPS = 
      (VCCLCompilerTool)((IVCCollection)propertySheet.Tools).Item(
      "VCCLCompilerTool");

  if (ctPS != null)
  {
    ...

    IVCCollection InherPSS = propertySheet.PropertySheets;
    if (InherPSS != null)
      if (InherPSS.Count != 0)
        ProcessAllPropertySheets(cfg, InherPSS);
      }
    }
}

В приведённом примере мы получаем объект типа VCCLCompilerTool (свойства компилятора) для PropertySheet всякого яруса. Таким образом, мы сумеем собрать все параметры компиляции, определённые во всех файлах свойств плана, в том числе и во вложенных.

Интерфейс VCPropertySheet не содержит способов для вычисления макросов в своих полях, следственно для этого доводится применять тот же способ Evaluate конфигурации плана. Такая практика, впрочем, может приводить к неправильному поведению в случае, если значение вычисляемого макроса связано непринужденно с props файлом. Скажем, ряд макросов MSBuild, появившихся в 4 версии, могут также быть использованы внутри новых планов vcxproj из Visual Studio 2010. Макрос MSBuildThisFileDirectory, к примеру, раскрывается в путь до папки нынешнего файла, и следственно вычисление его через cfg.Evaluate раскроет его до пути к vcxproj файла, а не к props файлу, в котором он применяется.

Все страницы свойств плана Visual C дозволено поделить на пользовательские и системные файлы. При этом под пользовательскими мы подразумеваем props файлы, непринужденно сделанные и включённые в план самим пользователем. Впрочем как дозволено подметить, даже пустой MSVC план дюже Зачастую включает несколько props страниц по умолчанию. Такие системные props файлы применяются средой для фактического определения ряда компиляционных параметров, задаваемых в странице настроек самого плана. Скажем, задание параметра CharacterSet через юникод приведёт к происхождению в списке Property Sheets данного плана системного props файла, определяющего несколько символов препроцессора (Unicode, _Unicode). Следственно, при обработке свойств, хранящихся в props файлах, следует помнить, что заданные в системных файлах символы компиляции также определены и через соответствующее им качество в конфигурации теснее самого плана, доступной через API модели автоматизации. Видимо, что приобретение настроек через эти два механизма приведёт к дублированию таких доводов.

Проектная модель Atmel Studio, настройки компиляции в сборочном инструментарии плана

Ранее мы разглядели реализацию проектной модели Visual Studio для C/C планов из Microsoft Visual C , по умолчанию включённую в дистрибутиве Visual Studio. Впрочем модель автоматизации Visual Studio расширяема, и может быть дополнена интерфейсами для взаимодействия с проектными моделями сторонних разработчиков (такая сторонняя модель может быть реализована в модуле-растяжении VSPackage). Следственно, если разработчик сторонней проектной модели предоставляет для неё интерфейсы, мы сумеем взаимодействоватьс ней так же, как мы взаимодействовали и со стандартными моделями, скажем с моделью Visual C , как было описано выше.

Для примера разглядим интерфейсы проектной модели, предоставляемой средой для разработки embedded решений Atmel Studio. Допустимо, вы спросите – при чём тут Atmel Studio, когда мы рассматриваем работу со средой Visual Studio? Впрочем сама Atmel Studio является изолированной оболочкой Visual Studio (Isolated Shell). Теперь мы не будем останавливаться детально на том, что из себя представляют изолированные оболочки среды, скажем лишь, что для них также допустима разработка всех тех же стандартных видов плагинов и растяжений Visual Studio, что и для обыкновенных версий данной среды. Вы можете больше детально ознакомиться с особенностями разработки плагинов под Visual Studio, включая и isolated shell редакции, в предыдущих разделах данного цикла.

Проектная модель Atmel является реализацией стандартной проектной модели Visual Studio. Так же, как и для планов Visual C , всеобщие интерфейсы, могут использоваться с планами Atmel Studio. Интерфейсы, специфичные для данной модели, определены в файлах AvrGCC.dll, AvrProjectManagement.dll и Atmel.Studio.Toolchain.Interfaces.dll, которые дозволено получить, скачав особый пакет Atmel Studio Extension Developer’s Kit (XDK).

Физически проектная модель Atmel Studio хранит сборочные параметры в файлах планов cproj, которые, являются проектными файлами сборочной платформы MSBuild (как, однако, и все типовые типы планов в Visual Studio). Планы Atmel Studio поддерживают языки программирования C/C и применяют для сборки особую версию компиляторов GCC.

Типы планов и сборочных инструментариев

Atmel Studio предоставляет 2 типа планов: C и C планы. Обратите внимание, что данные планы имеют различный GUID идентификатор, что необходимо рассматривать при обходе проектного дерева.

Проектная модель Atmel также предоставляет и 2 комплекта сборочных инструментариев – GNU C compiler и GNU C Compiler, всякий со своими обособленными настройками. Стоит впрочем подметить, что тогда как C планы могут содержать только настройки для C компилятора, C планы содержат в своём инструментарии как C так и C настройки. А при компиляции будет выбран тот комплект настроек, тот, что соответствует собираемому файлу, т.е. в случае «смешанного» плана настройки будут браться из 2-х комплектов!

Проверить доступные комплекты настроек для всякого определенного плана дозволено с поддержкой интерфейса ProjectToolchainOptions.

ProjectItem item;
...
AvrGccFileNode file = item.Object as AvrGccFileNode;
AvrGCCNode project = file.ProjectMgr as AvrGCCNode;
AvrProjectConfigProperties ActiveProps = project.ConfigurationManager.GetActiveConfigProperties();
ProjectToolchainOptions ToolChainOptions = ActiveProps.ToolchainOptions;
if (ToolChainOptions.CppCompiler != null)
    //Toolchain compiler options for C   compiler
if (ToolChainOptions.CCompiler != null)
    //Toolchain compiler options for C Compiler

 

Приобретение настроек компиляции

Для приобретения непринужденно самих настроек компиляции дозволено воспользоваться полученным нами ранее объектом типа CompilerOptions (базовый тип для CppCompilerOptions и CCompilerOptions). Ряд настроек дозволено напрямую взять из объекта, скажем Include пути плана:

CompilerOptions options;
...
List<String> Includes = options. IncludePaths;

Подметьте, что часть настроек являются всеобщими для все комплектов (т.е. для C и C настроек). Скажем, это всеобщие системные Include пути:

List<String> SystemIncludes = options. DefaultIncludePaths;

Но огромная часть настроек доступна через Dictionary<String, IList<String>> качество OtherProperties. Как видно, всякому свойству (ключу) может соответствовать список из одного либо больше значений.

Если же вам требуется полная строка, передаваемая из плана в MSBuild (и, соответственно, в компилятор), а не отдельные настройки сборки, то такую строку дозволено сразу получить с поддержкой свойства CommandLine (гораздо проще, чем в VCProjectEngine!):

String RawCommandLine = this.compilerOptions.CommandLine;

Впрочем полученная сходственным методом командная строка всё равно не будет содержать General настроек, таких, как системные Include пути.

Стоит, впрочем, помнить, что как отдельные настройки, полученные данным методом, так и полная командная строка, могут содержать нераскрытые MSBuild макросы. Для их раскрытия воспользуемся способом GetAllProjectProperties интерфейса AvrGCCNode:

AvrGCCNode project;
...
Dictionary<string, string> MSBuildProps = new Dictionary<string, string>();
project.GetAllProjectProperties().ForEach(x =>MSBuildProps.Add(x.Key, x.Value));

 

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB