Расширение Visual Studio: доступ к параметрам VS из произвольной библиотеки DLL

В настоящее время я разрабатываю свое первое расширение VS, которое должно предоставить пользователю некоторые опции. После https://msdn.microsoft.com/en-us/library/bb166195.aspx было довольно легко придумать мою собственную страницу настроек. Тем не менее, я еще не узнал, как читать мои варианты.

Структура решения моего расширения выглядит следующим образом:

MySolution
  MyProject (generates a DLL from C# code)
  MyProjectVSIX

Следуя приведенному выше руководству, я добавил VS Package в мой проект VSIX и настроил его, как описано. В результате моя страница опций с моими опциями показывается в разделе Инструменты / Опции. Ницца! Вот мой DialogPage реализация:

public class OptionPageGrid : DialogPage
{
    private bool myOption = false;

    [Category(Options.CATEGORY_NAME)]
    [DisplayName("My option")]
    [Description("Description of my option.")]
    public bool MyOption
    {
        get { return myOption; }
        set { myOption = value; }
    }
}

И вот глава моего пакета класса:

[PackageRegistration(UseManagedResourcesOnly = true)]
[InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]     [Guid(MyOptionsPage.PackageGuidString)]
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "pkgdef, VS and vsixmanifest are valid VS terms")]
[ProvideOptionPage(typeof(OptionPageGrid), Options.CATEGORY_NAME, Options.PAGE_NAME, 0, 0, true)]
public sealed class MyOptionsPage : Package, IOptions
{
    ...

Тем не менее, теперь я хочу прочитать эти параметры, и я хочу сделать это из MyProject (который не зависит от MyProjectVSIX). И здесь я как бы потерялся. Моей первой попыткой было позволить моим Package реализовать IOptions интерфейс и позволить ему зарегистрироваться, вызвав статический метод Options.Register(IOptions) от Packageконструктор. Это работает (т.е. точка останова в Register() ударил), но когда я пытаюсь прочитать параметры, статический IOptions экземпляр по-прежнему нулевой. Я предполагаю, что это связано с тем, что код выполняется из разных процессов (что находится вне моего контроля).

После еще нескольких поисков, я попытался получить экземпляр DTE объект (который позволил бы мне прочитать мои варианты, если я правильно понял), но безуспешно. Я пробовал несколько вариантов, в том числе описанный на https://msdn.microsoft.com/en-us/library/ee834473.aspx и

DTE Dte = Package.GetGlobalService(typeof(DTE)) as DTE;

Я всегда получаю нулевую ссылку.

Наконец, так как учебник предложил доступ к опциям через экземпляр PackageЯ попытался выяснить, как получить такой экземпляр моего VS Package через какой-то реестр (который я мог бы потом бросить IOptions) но опять без везения.

Кто-нибудь может указать мне правильное направление? Или это даже невозможно получить доступ к параметрам VS из проекта не-VSIX?

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

Тем временем мне удалось получить доступ к DTE объект экземпляра VS, в котором я работаю (я опубликую это с моим полным решением, как только моя проблема будет решена), но все еще будут проблемы с доступом к опциям. На самом деле, следующий код (скопированный отсюда: https://msdn.microsoft.com/en-us/library/ms165641.aspx) работает хорошо:

Properties txtEdCS = DTEProvider.DTE.get_Properties("TextEditor", "CSharp");
Property prop = null;
string msg = null;
foreach (EnvDTE.Property temp in txtEdCS)
{
    prop = temp;
    msg += ("PROP NAME: " + prop.Name + "   VALUE: " + prop.Value) + "\n";
}
MessageBox.Show(msg);

Однако, если я изменю вышеизложенное следующим образом:

Properties txtEdCS = DTEProvider.DTE.get_Properties(CATEGORY_NAME, PAGE_NAME);

Теперь код вылетает. Как ни странно, я вижу свою категорию недвижимости и страницу в реестре под HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\14.0Exp_Config\AutomationProperties\My Test Adapter\General, Поиск моих свойств показывает их под HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\14.0Exp\ApplicationPrivateSettings\MyProjectVSIX\OptionPageGrid (возможно, потому что я добавил

OptionPageGrid Page = (OptionPageGrid)GetDialogPage(typeof(OptionPageGrid));
Page.SaveSettingsToStorage();

к Package"s Initialize() метод (как предложил Маце), возможно, потому что я не смотрел там раньше:-)).

Так как же читать мои свойства?

4 ответа

Решение

В платформе тестового адаптера VS есть API для обмена настройками между процессами. Поскольку ничего не задокументировано, потребовалось некоторое время, чтобы понять, как использовать этот API. Рабочий пример смотрите в этом проекте GitHub.

Обновление: фреймворк vstest недавно был открыт MS.

Если вы хотите прочитать варианты из вашего пакета, вы можете просто попросить OptionPageGrid экземпляр через GetDialogPage метод VSPackage, Например:

var options = (OptionGridPage)this.package.GetDialogPage(typeof(OptionGridPage));
bool b = options.MyOption;

Если вам нужен доступ к параметрам из другого приложения (или сборки, которую можно использовать без среды выполнения Visual Studio), вы можете попробовать прочитать эти параметры прямо из реестра Windows, но ключи и значения реестра могут не присутствовать, если варианты не были написаны IDE или ваш пакет. Вы можете принудительно сохранить параметры, вызвав SaveSettingsToStorage метод из вашего пакета, например, при первой загрузке:

options.SaveSettingsToStorage();

Настройки будут сохранены под следующим ключом:

HKEY_CURRENT_USER\SOFTWARE\Microsoft\VisualStudio\12.0\DialogPage

согласно которому 12.0 указывает версию Visual Studio. Под этим ключом вы найдете группу подключей, имена которых являются полными именами DialogPage типы компонентов. Каждый ключ содержит значения свойств; в вашем случае вы должны найти REG_SZ значение по имени MyOption имея либо True или же False как это значение данных.

Я предполагаю, что это связано с тем, что код выполняется из разных процессов

Если вы не пишете что-то явно, весь код расширяемости VS запускается в одном домене приложений под devenv.exe. Если вы видите, что он обнуляется, это означает, что либо ваш регистрационный код не запускался, либо не выполнял то, о чем вы думали.

У вас есть несколько вариантов здесь:

  1. Избегайте этой проблемы полностью: попробуйте рефакторинг вашего кода таким образом, чтобы код в проекте не-VSIX не должен был читать материал из среды, а передавал опции в вызов некоторого метода, и проект VSIX делает это. Зачастую это лучший результат, потому что он значительно упрощает модульное тестирование. ("Глобальное состояние" всегда плохо для тестирования.)
  2. Пусть ваш проект не-VSIX имеет статический класс для настройки параметров, а ваш проект VSIX ссылается на проект не-VSIX и устанавливает его при изменении настроек.
  3. Зарегистрируйте сервис с помощью атрибута ProvideService, и в вашем проекте, отличном от VSIX, по-прежнему вызывайте Package.GetGlobalService для вашего собственного типа обслуживания. Это главная оговорка, что GetGlobalService имеет кучу ошибок; Я избегаю этого метода в целом из-за проблем, связанных с ним.
  4. Используйте MEF. Если вы знакомы с атрибутами импорта / экспорта, ваш VSIX проект может экспортировать интерфейс, определенный в вашем не-VSIX проекте, и вы импортируете его. Если вы знакомы с идеей внедрения зависимостей, это очень хорошо, но кривая обучения - это действительно скала для обучения.

Этот ответ является дополнением к моему первому ответу. Конечно, чтение параметров из зашифрованного раздела реестра может быть грязным подходом, потому что реализация проприетарного API может измениться в будущем, что может сломать ваше расширение. Как упоминал Джейсон, вы можете попытаться "полностью избежать этой проблемы"; например, обрезая функциональность, которая читает / записывает параметры из / в реестр.

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

public class OptionPageGrid : DialogPage
{
    private readonly ISettingsService<OptionPageGridSettings> settingsService = ...

    protected override IWin32Window Window
    {
        get
        {
            return new OptionPageGridWindow(this.settingsService);
        }
    }

    public override void LoadSettingsFromStorage()
    {
        this.settingsService.LoadSettingsFromStorage();
    }

    public override void SaveSettingsToStorage()
    {
        this.settingsService.SaveSettingsToStorage();
    }
}

Реализация ISettingsService<T> Интерфейс может быть помещен в общую сборку, на которую ссылается проект VSIX и ваше автономное приложение (ваша сборка тестового адаптера). Служба настроек также может использовать реестр Windows или любое другое подходящее хранилище...

public class WindowsRegistrySettingsService<T> : ISettingsService<T> 
{
    ...
}

public interface ISettingsService<T>
{
    T LoadSettingsFromStorage();
    void SaveSettingsToStorage();
}
Другие вопросы по тегам