【问题标题】:Get the reference of the DTE2 object in Visual C# 2010在 Visual C# 2010 中获取 DTE2 对象的引用
【发布时间】:2011-06-11 02:19:11
【问题描述】:

我想获得对当前解决方案的引用,在 Visual Studio 2010 中使用 DTE2 对象和 C#。

我首先尝试了以下代码:

var dte = Marshal.GetActiveObject("VisualStudio.DTE.10.0") as EnvDTE80.DTE2;

但是当我打开 2 个解决方案并且此代码在第一个解决方案中时,我得到的不是对当前解决方案的引用,而是对我加载的最后一个解决方案的引用。我需要当前的解决方案...

在网上搜索,在How do you get the current solution directory from a VSPackage?找到如下解决方案:

// Get an instance of the currently running Visual Studio IDE
DTE dte = (DTE)GetService(typeof(DTE));

但是当我使用它时,我的 dte 对象总是 NULL。

那么我如何在 .net framework 4.0 上使用 C# 在 VS2010 中获取我当前的解决方案对象?

【问题讨论】:

    标签: c# visual-studio-2010 add-in envdte


    【解决方案1】:

    经过大量搜索和尝试,我终于使用添加到 MSDN 页面的评论得到了答案:http://msdn.microsoft.com/en-us/library/ms228755.aspx

    我在我的 c# 项目中添加了一个静态类:

    using System;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    using System.Runtime.InteropServices.ComTypes;
    using EnvDTE80;
    
    [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);
    
      internal static DTE2 GetCurrent()
      {
    
         //rot entry for visual studio running under current process.
         string rotEntry = String.Format("!VisualStudio.DTE.10.0:{0}", Process.GetCurrentProcess().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;
      }
    

    在我想访问当前 IDE 时:

    var dte = CurrentIde.GetCurrent();
    var sol = dte.Solution;
    

    但请记住....此代码在调试期间不起作用!!!以字符串 rotEntry... 开头的代码行调用了 Process.GetCurrentProcess 来获取当前进程的 ID。

    在调试插件中的某些功能时(使用 MME http://mme.codeplex.com/),我调用了一个需要当前 IDE 的方法。我使用调用插件方法的 ConsoleApp 对此进行了测试。在获取当前 IDE 时,当前进程不是 IDE,而是 ConsoleApp.vshost.exe。所以我的代码在调试过程中没有工作,但是在构建插件并安装这个插件之后 DID 工作。

    【讨论】:

    • 感谢您的回答!它帮助了我很多。事实上,我是如此坚持这一点,以至于我在一个类似的问题上开了一个赏金。如果您去那里并发布指向此答案的链接,那么我很有可能会将赏金奖励给您。 stackoverflow.com/questions/10864595/…
    • 你不知道你今天为我节省了多少时间!!希望我能做的不仅仅是+1!谢谢
    • 这是一个较旧的线程,但它仍然解决了我们从代码生成 T4 时长期存在的问题。非常感谢。
    【解决方案2】:

    我觉得以下几点令人不安,因此我已经解决了这些问题并找到了适合我的解决方案:

    • GetActiveObject("VisualStudio.DTE.10.0") 仅适用于第一次打开的(我想)Visual Studio
    • Dennis 回答的internal static DTE2 GetCurrent() 方法需要Visual Studio 进程ID。如果您从加载项运行代码(我认为),那很好,但不起作用,例如在单元测试中。
    • 调试模式中的问题

    我也开始使用 GetCurrent 方法,取自 here。问题是,我不知道如何获得正确 VisualStudio 进程的 ProcessId(通常多个实例正在运行)。所以我采用的方法是获取所有 VisualStudio ROT 条目及其 DTE2,然后将 DTE2.Solution.FullName 与正在执行的程序集位置进行比较(您看到更好的选择吗?)。虽然我很容易承认这不是非常精确的科学,但如果您没有相当特殊的输出路径配置,它应该可以工作。 然后我发现在调试模式下运行我的代码并访问 DTE2 COM 对象会引发以下异常:System.Runtime.InteropServices.COMException: Call was rejected by callee. (Exception from HRESULT: 0x80010001 (RPC_E_CALL_REJECTED))。不过有一个补救办法,叫做MessageFilter。为了完整起见,我在底部包含了代码。

    测试类包含一个测试方法(使用示例)、调整后的GetCurrent方法和一个用于字符串比较的辅助方法:

    using System;
    using System.Collections.Generic;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using EnvDTE80;
    using EnvDTE;
    using System.Runtime.InteropServices;
    using System.Runtime.InteropServices.ComTypes;
    
    [TestClass]
    public class ProjectSettingsTest
    {
        /// <summary>
        /// Tests that the platform for Mixed Platforms and Any CPU configurations 
        /// is Any CPU for all projects of this solution
        /// </summary>
        [TestMethod]
        public void TestReleaseBuildIsAnyCPU()
        {
            MessageFilter.Register();
    
            DTE2 dte2 = GetCurrent();
            Assert.IsNotNull(dte2);
    
            foreach (SolutionConfiguration2 config in dte2.Solution.SolutionBuild.SolutionConfigurations)
            {
                if (config.PlatformName.Contains("Mixed Platforms") || config.PlatformName.Contains("Any CPU"))
                {
                    foreach (SolutionContext context in config.SolutionContexts)
                        Assert.AreEqual("Any CPU", context.PlatformName, string.Format("{0} is configured {1} in {2}/{3}", context.ProjectName, context.PlatformName, config.PlatformName, config.Name));
                }
            }
    
            MessageFilter.Revoke();
        }
    
    
        [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);
    
        /// <summary>
        /// Gets the current visual studio's solution DTE2
        /// </summary>
        public static DTE2 GetCurrent()
        {
            List<DTE2> dte2s = new List<DTE2>();
    
            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);
                // add all VisualStudio ROT entries to list
                if (displayName.StartsWith("!VisualStudio"))
                {
                    object comObject;
                    rot.GetObject(moniker[0], out comObject);
                    dte2s.Add((DTE2)comObject);
                }
            }
    
            // get path of the executing assembly (assembly that holds this code) - you may need to adapt that to your setup
            string thisPath = System.Reflection.Assembly.GetExecutingAssembly().Location;
    
            // compare dte solution paths to find best match
            KeyValuePair<DTE2, int> maxMatch = new KeyValuePair<DTE2, int>(null, 0);
            foreach (DTE2 dte2 in dte2s)
            {
                int matching = GetMatchingCharsFromStart(thisPath, dte2.Solution.FullName);
                if (matching > maxMatch.Value)
                    maxMatch = new KeyValuePair<DTE2, int>(dte2, matching);
            }
    
            return (DTE2)maxMatch.Key;
        }
    
        /// <summary>
        /// Gets index of first non-equal char for two strings
        /// Not case sensitive.
        /// </summary>
        private static int GetMatchingCharsFromStart(string a, string b)
        {
            a = (a ?? string.Empty).ToLower();
            b = (b ?? string.Empty).ToLower();
            int matching = 0;
            for (int i = 0; i < Math.Min(a.Length, b.Length); i++)
            {
                if (!char.Equals(a[i], b[i]))
                    break;
    
                matching++;
            }
            return matching;
        }
    }
    

    MessageFilter 类:

    /// <summary>
    /// Class containing the IOleMessageFilter
    /// thread error-handling functions.
    /// </summary>
    public class MessageFilter : IOleMessageFilter
    {
        // Start the filter.
        public static void Register()
        {
            IOleMessageFilter newFilter = new MessageFilter();
            IOleMessageFilter oldFilter = null;
            CoRegisterMessageFilter(newFilter, out oldFilter);
        }
    
        // Done with the filter, close it.
        public static void Revoke()
        {
            IOleMessageFilter oldFilter = null;
            CoRegisterMessageFilter(null, out oldFilter);
        }
    
        //
        // IOleMessageFilter functions.
        // Handle incoming thread requests.
        int IOleMessageFilter.HandleInComingCall(int dwCallType, System.IntPtr hTaskCaller, int dwTickCount, System.IntPtr lpInterfaceInfo)
        {
            return 0; //Return the flag SERVERCALL_ISHANDLED.
        }
    
        // Thread call was rejected, so try again.
        int IOleMessageFilter.RetryRejectedCall(System.IntPtr hTaskCallee, int dwTickCount, int dwRejectType)
        {
            if (dwRejectType == 2)
            // flag = SERVERCALL_RETRYLATER.
            {
                return 99; // Retry the thread call immediately if return >=0 & <100.
            }
            return -1; // Too busy; cancel call.
        }
    
        int IOleMessageFilter.MessagePending(System.IntPtr hTaskCallee, int dwTickCount, int dwPendingType)
        {
            //Return the flag PENDINGMSG_WAITDEFPROCESS.
            return 2;
        }
    
        // Implement the IOleMessageFilter interface.
        [DllImport("Ole32.dll")]
        private static extern int CoRegisterMessageFilter(IOleMessageFilter newFilter, out IOleMessageFilter oldFilter);
    }
    
    [ComImport(), Guid("00000016-0000-0000-C000-000000000046"),
    InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
    interface IOleMessageFilter
    {
        [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);
    }
    

    【讨论】:

      【解决方案3】:

      我知道这是一个旧线程,但我们需要将此代码用于多个 Visual Studio 版本。我们对代码进行了如下调整:

      string processID = Process.GetCurrentProcess().Id.ToString();
      if (displayName.StartsWith("!VisualStudio.DTE.", StringComparison.OrdinalIgnoreCase) &&
              displayName.EndsWith(processID))
      

      【讨论】:

        【解决方案4】:

        对于任何有兴趣使用 F# 执行此操作的人,这里是最完整的转换(当前设置为在 linqpad 中运行):

        open System;
        open System.Runtime.InteropServices;
        open System.Runtime.InteropServices.ComTypes;
        open EnvDTE;
        open System.Diagnostics;
        //http://stackoverflow.com/questions/10864595/getting-the-current-envdte-or-iserviceprovider-when-not-coding-an-addin
        
        //http://stackoverflow.com/questions/6558789/how-to-convert-out-ref-extern-parameters-to-f
        //http://stackoverflow.com/questions/1689460/f-syntax-for-p-invoke-signature-using-marshalas
        
        [<System.Runtime.InteropServices.DllImport("ole32.dll")>] 
        extern int CreateBindCtx(System.IntPtr inRef, IBindCtx& outParentRef);
        [<System.Runtime.InteropServices.DllImport("ole32.dll")>]
        extern int GetRunningObjectTable(System.IntPtr inRef, IRunningObjectTable& outParentRef);
        //let dte = System.Runtime.InteropServices.Marshal.GetActiveObject("VisualStudio.DTE.12.0") :?> EnvDTE80.DTE2
        let comName="VisualStudio.DTE.12.0"
        let rotEntry = "!"+comName
        //let mutable rot:IRunningObjectTable =null
        
        let rot=
            let mutable result:IRunningObjectTable = null
            GetRunningObjectTable(nativeint 0, &result) |> ignore
            result
        
        
        let mutable enumMoniker:IEnumMoniker = null
        rot.EnumRunning (&enumMoniker) 
        enumMoniker.Reset() |> ignore
        let mutable fetched = IntPtr.Zero
        let mutable moniker:IMoniker[] = Array.zeroCreate 1 //http://msdn.microsoft.com/en-us/library/dd233214.aspx
        
        let matches = seq {
            while enumMoniker.Next(1, moniker, fetched) = 0 do
                "looping" |> Dump
                let mutable bindCtx:IBindCtx = null
                CreateBindCtx(nativeint 0, &bindCtx) |> ignore
                let mutable displayName:string = null
                moniker.[0].GetDisplayName(bindCtx,null, &displayName)
                displayName |> Dump
                if displayName.StartsWith(rotEntry) then
                    let mutable comObject = null
                    rot.GetObject(moniker.[0], &comObject) |> ignore
                    let dte =  comObject:?>EnvDTE80.DTE2
                    yield displayName,bindCtx,comObject,dte.FullName, dte
        }
        matches |> Dump
        

        【讨论】:

          【解决方案5】:

          我在下面做了一个更舒适的完美解决方案(没有 Rocket Science)。这个工作从 Visual Studio 20 降到 10 以找到独立于 VS 版本的 DTE。

          using System;
          using System.Diagnostics;
          using System.Runtime.InteropServices;
          using System.Runtime.InteropServices.ComTypes;
          using EnvDTE80;
          
          namespace Fortrus.Metadata
          {
              /// <summary>
              /// This class takes care of fetching the correct DTE instance for the current process
              /// The current implementation works it way down from Visual Studio version 20 to 10 so
              /// it should be farely version independent
              /// </summary>
              public static class Processes
              {
                  [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);
          
                  private const int m_MaxVersion = 20;
                  private const int m_MinVersion = 10;
          
                  internal static DTE2 GetDTE()
                  {
                      DTE2 dte = null;
          
                      for (int version = m_MaxVersion; version >= m_MinVersion; version--)
                      {
                          string versionString = string.Format("VisualStudio.DTE.{0}.0", version);
          
                          dte = Processes.GetCurrent(versionString);
          
                          if (dte != null)
                          {
                              return dte;
                          }
                      }
          
                      throw new Exception(string.Format("Can not get DTE object tried versions {0} through {1}", m_MaxVersion, m_MinVersion));
                  }
          
                  /// <summary>
                  /// When multiple instances of Visual Studio are running there also multiple DTE available
                  /// The method below takes care of selecting the right DTE for the current process
                  /// </summary>
                  /// <remarks>
                  /// Found this at: http://stackoverflow.com/questions/4724381/get-the-reference-of-the-dte2-object-in-visual-c-sharp-2010/27057854#27057854
                  /// </remarks>
                  private static DTE2 GetCurrent(string versionString)
                  {
                      //rot entry for visual studio running under current process.
                      string rotEntry = String.Format("!{0}:{1}", versionString, Process.GetCurrentProcess().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;
                  }
              }
          }
          

          【讨论】:

          • Paul,我一直在尝试获取 VS2012 的 EnvDTE,当我看到常量都为 11 时,找不到它。抛出你的异常。它永远不会在 ROT 中找到当前 VS 版本的进程 ID。对可能出了什么问题有任何想法吗?
          猜你喜欢
          • 2011-04-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-12-13
          • 2011-04-22
          • 1970-01-01
          相关资源
          最近更新 更多