【问题标题】:Visual Studio Extension: Access VS Options from arbitrary DLLVisual Studio 扩展:从任意 DLL 访问 VS 选项
【发布时间】:2015-08-24 09:00:45
【问题描述】:

我目前正在开发我的第一个 VS 扩展,它需要为用户提供一些选项。在https://msdn.microsoft.com/en-us/library/bb166195.aspx 之后,很容易想出我自己的选项页面。但是,我还没有找到如何阅读我的选项。

我的扩展的解决方案结构如下:

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

按照上面引用的教程,我在我的 VSIX 项目中添加了一个 VS Package 并按照描述对其进行了配置。结果,带有我的选项的选项页面显示在工具/选项下。好的!这是我的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 接口,并通过从Package 的构造函数调用静态方法Options.Register(IOptions) 让它自己注册。这有效(即,Register() 中的断点被命中),但是当我尝试读取选项时,静态 IOptions 实例仍然为空。我的假设是这是因为代码是从不同的进程执行的(这超出了我的控制范围)。

在谷歌搜索后,我尝试获取DTE 对象的实例(如果我理解正确,这将允许我阅读我的选项),但没有成功。我尝试了几种变体,包括https://msdn.microsoft.com/en-us/library/ee834473.aspx

中描述的变体
DTE Dte = Package.GetGlobalService(typeof(DTE)) as DTE;

我总是得到一个空引用。

最后,由于教程建议通过Package 的实例访问选项,我试图弄清楚如何通过某种注册表(我可以将其转换为IOptions),但还是没有运气。

谁能指出我正确的方向?或者甚至无法从非 VSIX 项目访问 VS 选项?

更新:我做了更多研究,但缺少一条信息:我的扩展是一个单元测试适配器。这似乎意味着测试发现代码和测试执行代码是从不同的进程运行的,也就是说,我的假设是正确的。

与此同时,我设法访问了我正在运行的 VS 实例的 DTE 对象(一旦我的问题得到解决,我将用我的完整解决方案发布该对象),但仍然无法访问选项.事实上,下面的代码(从这里复制: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();

PackageInitialize() 方法(正如 Matze 建议的那样),可能是因为我以前没有看过那里:-))。

那么如何读取我的属性呢?

【问题讨论】:

    标签: visual-studio visual-studio-extensions vsix envdte vspackage


    【解决方案1】:

    如果你想从你的包中读取选项,你可以通过VSPackageGetDialogPage 方法请求一个OptionPageGrid 实例。例如:

    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 组件类型的全名。每个键都包含属性值;在您的情况下,您应该找到一个名为 MyOptionREG_SZ 值,其数据值是 TrueFalse

    【讨论】:

    • 我们希望有一天会弃用注册表持久性,因此如果可以的话,最好通过各种 API 进行调用。
    • 我考虑过直接访问注册表,但想将其作为最后一个选项...在我引用的 MS 文章中也提到了通过其 Package 访问我的 PropertyPage 的方式 - 问题是如何从我的自定义代码中访问该包。事实证明,我确实需要处理几个过程 - 请参阅我更新的问题......
    • 请注意,最近的 VS 版本将设置存储在私有注册表配置单元中,该配置单元只能在 VS 进程中访问。
    • @DarkDaskin 有趣;我会检查的。
    【解决方案2】:

    我的假设是这是因为代码是从不同的进程执行的

    除非您非常明确地编写其他内容,否则所有 VS 可扩展性代码都在 devenv.exe 下的单个 AppDomain 中运行。如果您看到它显示为 null,则表示您的注册码没有运行,或者没有按照您的想法执行。

    你有几个选择:

    1. 完全避免该问题:尝试重构您的代码,以便非 VSIX 项目中的代码不必从环境中读取内容,而是将选项传递给某个方法调用,而 VSIX 项目就是这样做的。这通常是最好的结果,因为它使单元测试更容易。 (“全局状态”总是不利于测试。)
    2. 让您的非 VSIX 项目只有一个用于设置选项的静态类,并且您的 VSIX 项目引用非 VSIX 项目并在设置发生更改时对其进行设置。
    3. 通过 ProvideService 属性注册一个服务,并且在你的非 VSIX 项目中仍然为你自己的服务类型调用Package.GetGlobalService。这有一个主要的警告,即 GetGlobalService 有很多陷阱;由于涉及的挑战,我一般会避免使用这种方法。
    4. 使用 MEF。如果您熟悉 Import/Export 属性,则可以让您的 VSIX 项目导出在非 VSIX 项目中定义的接口,然后将其导入。如果您熟悉依赖注入的概念,这非常好,但学习曲线确实是一个学习悬崖。

    【讨论】:

    • 感谢您的出色建议,但我忘记了我现在添加的一点信息 - 看起来我的假设是正确的。我来自 Java 世界,所以我花了一段时间才明白发生了什么 - 抱歉没有问得更准确......尽管如此,我很可能会出于兴趣而研究 MEF,
    • 哦,您在不同的进程中运行这一事实显着改变了这一点。一般来说,我描述的所有读取属性的方法都只能在同一个过程中工作,所以你需要有一些方法来传递信息。我不熟悉测试适配器的可扩展性,不知道他们是否有这样的设计。请注意,您可能会发现使用 DTE 从其他进程读取您的选项的代码或建议,这是可以做到的。我不建议这样做,因为您可能会面临死锁的风险。
    • 好的,谢谢你告诉我。看起来我坚持从注册表中读取选项。但是,由于我只需要阅读它们(而且我目前只计划对 VS2015 及更高版本的支持),这应该不会太糟糕。单元测试适配器框架没有类似的 API 仍然很奇怪......
    • 我明天会在内部发送一封电子邮件,看看是否能找到人,但考虑到团队位于不同的时区,这需要几天时间......
    • 那太棒了,再次感谢!请注意,我最感兴趣的是测试适配器框架的一些通用文档——我没有找到任何官方 API 文档,只有一些博客条目......
    【解决方案3】:

    这个答案是我第一个答案的补充。当然,从间谍注册表项中读取选项可能是一种肮脏的方法,因为专有 API 的实现将来可能会发生变化,这可能会破坏您的扩展。正如杰森所说,你可以尝试“完全避免这个问题”;例如,通过削减从注册表读取/写入选项的功能。

    DialogPage 类提供了方法LoadSettingsFromStorageSaveSettingsToStorage,它们都是虚拟的,因此您可以覆盖它们并调用您的自定义持久性功能。

    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&lt;T&gt; 接口的实现可以放入共享程序集中,供 VSIX 项目和您的独立应用程序(您的测试适配器程序集)引用。设置服务还可以使用 Windows 注册表或任何其他合适的存储...

    public class WindowsRegistrySettingsService<T> : ISettingsService<T> 
    {
        ...
    }
    
    public interface ISettingsService<T>
    {
        T LoadSettingsFromStorage();
        void SaveSettingsToStorage();
    }
    

    【讨论】:

      【解决方案4】:

      VS 测试适配器框架恰好有用于跨进程共享设置的 API。由于根本没有任何文档记录,因此需要一段时间才能弄清楚如何使用该 API。有关工作示例,请参阅 this GitHub project

      更新:vstest 框架最近已由 MS 开源。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-11-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多