【Visual Studio】在VS2012中使用VSXtra


最近工作中需要把之前為VS 2010寫的擴展遷移到VS 2012上。在將工程版本改為VS2012之后,代碼沒有修改,直接編譯通過,也可以安裝到VS2012上。

不過,在實際使用的時候,卻報錯,提示“The framework has not been sited!”。調試后發現,這個錯誤是我們在IDE開發中用到的VSXtra報出的。錯誤的報錯位置是在SiteManager的GetGlobalService方法中:

        // --------------------------------------------------------------------------------------------
        /// <summary>
        /// Gets a global service with the specified address and behavior type.
        /// </summary>
        /// <typeparam name="SInterface">Address type of the service</typeparam>
        /// <typeparam name="TInterface">Behavior (endpoint) type of the service</typeparam>
        /// <returns>The service instance obtained.</returns>
        // --------------------------------------------------------------------------------------------
        public static TInterface GetGlobalService<SInterface, TInterface>()
            where TInterface : class
            where SInterface : class
        {
            if (!HasGlobalServiceProvider)
                throw new InvalidOperationException("The framework has not been sited!");
            var ti = GlobalServiceProvider.GetService<SInterface, TInterface>();
            if (ti == null)
                throw new NotSupportedException(typeof(SInterface).FullName);
            return ti;
        }

從代碼可以看到,異常拋出的條件是在GlobalServiceProvider不存在的時候。那么這個HasGlobalServiceProvider屬性是什么樣的呢:

        // --------------------------------------------------------------------------------------------
        /// <summary>
        /// Gets the flag indicating if the SiteManager has already a global service provider or not.
        /// </summary>
        // --------------------------------------------------------------------------------------------
        public static bool HasGlobalServiceProvider
        {
            get { return (GlobalServiceProvider != null); }
        }

看來是判斷GlobalServiceProvider是否為null。那么,是誰負責設置這個GlobalServiceProvider的呢?我們通過Find All References可以發現這個屬性是在SuggestGlobalServiceProvider方法中被設置的,這個方法有多個重載,跟蹤一下,需要調用到的是這個:

        // --------------------------------------------------------------------------------------------
        /// <summary>
        /// Suggests a DTE2 object as the global service provider for SiteManager.
        /// </summary>
        /// <param name="dte2">DTE2 object as a global service provider candidate.</param>
        // --------------------------------------------------------------------------------------------
        public static void SuggestGlobalServiceProvider(DTE2 dte2)
        {
            SuggestGlobalServiceProvider(dte2 as IOleServiceProvider);
        }

而這個SuggestGlobalServiceProvider則是在一個叫做TryToGetServiceProviderFromCurrentProcess的關鍵方法中被調用到的(部分代碼省略):

        private static void TryToGetServiceProviderFromCurrentProcess(string vsMoniker)
        {
            ......

            string ideMoniker = String.Format(vsMoniker, Process.GetCurrentProcess().Id);

            ......
            while (enumMoniker.Next(1, moniker, IntPtr.Zero) == 0)
            {
                string displayName;
                moniker[0].GetDisplayName(ctx, moniker[0], out displayName);
                if (displayName == ideMoniker)
                {
                    // --- Got the IDE Automation Object
                    Object oDTE;
                    rot.GetObject(moniker[0], out oDTE);
                    dte = oDTE as DTE2;
                    if (dte != null) break;
                }
            }

            SuggestGlobalServiceProvider(dte);
        }

其大概意思是從當前進程中獲取一些信息,跟通過傳入的vsMoniker格式化之后的一個字符串進行比對,如果名字是一樣的,則通過它獲取VS IDE的DTE實例。那么,這個vsMoniker是個什么東東?又是誰調用了這個方法呢?

    // --------------------------------------------------------------------------------------------
    /// <summary>
    /// Monikers of possible DTE objects. Right now VS 2008 and VS 2010 is handled.
    /// </summary>
    // --------------------------------------------------------------------------------------------
    private static readonly List<string> VSMonikers =
      new List<string>
        {
          "!VisualStudio.DTE.9.0:{0}",
          "!VisualStudio.DTE.10.0:{0}"
        };

    // --------------------------------------------------------------------------------------------
    /// <summary>
    /// The static constructor automatically tries to assign the SiteManager static class to a site
    /// that accesses VS IDE global services.
    /// </summary>
    // --------------------------------------------------------------------------------------------
    static SiteManager()
    {
      foreach (string moniker in VSMonikers)
      {
        TryToGetServiceProviderFromCurrentProcess(moniker);
        if (HasGlobalServiceProvider) return;
      }
    }

看到這兒,我們也明白了,敢情VSXtra這兒寫死了幾個字符串{ "!VisualStudio.DTE.9.0:{0}", "!VisualStudio.DTE.10.0:{0}" },跟VS版本相關,而VS2012的版本號是11,這里沒有,所以當然找不到DTE Instance了。

最簡單的修改版本,添加一個VS2012的版本號:"!VisualStudio.DTE.11.0:{0}",運行測試OK。

不過,對於這種hard-code的寫法筆者是看着很不爽,那么,有沒動態獲取已經安裝的VS版本的方法呢?答案是:有,通過注冊表。

於是最后的代碼修改如下:

        // --------------------------------------------------------------------------------------------
        /// <summary>
        /// Monikers of possible DTE objects. Right now VS 2008 and VS 2010 is handled.
        /// </summary>
        // --------------------------------------------------------------------------------------------
        private static readonly IEnumerable<string> VSMonikers;

        // --------------------------------------------------------------------------------------------
        /// <summary>
        /// The static constructor automatically tries to assign the SiteManager static class to a site
        /// that accesses VS IDE global services.
        /// </summary>
        // --------------------------------------------------------------------------------------------
        static SiteManager()
        {
            var vsRegKey = Registry.CurrentUser
                .OpenSubKey("Software")
                .OpenSubKey("Microsoft")
                .OpenSubKey("VisualStudio");
            var installedNames = vsRegKey.GetSubKeyNames().Where(x => !x.Contains("Exp") && !x.Contains("_Config"));
            VSMonikers = installedNames.Select(x => "!VisualStudio.DTE." + x + ":{0}").ToArray();

            foreach (string moniker in VSMonikers)
            {
                TryToGetServiceProviderFromCurrentProcess(moniker);
                if (HasGlobalServiceProvider) return;
            }
        }

附:修改后的VSXtra


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM