Get the reference of the DTE2 object in Visual C# 2010

后端 未结 5 1714
北海茫月
北海茫月 2020-12-09 05:32

I want to get a reference to the current solution, using the DTE2 object with C# in Visual Studio 2010.

I first tried the following code:

var dte = M         


        
相关标签:
5条回答
  • 2020-12-09 06:02

    for anyone interested in doing this with F# a mostly complete conversion is here ( currently set to run in 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
    
    0 讨论(0)
  • 2020-12-09 06:06

    I know this is an old thread, but we needed to use this code with multiple Visual Studio versions. We tweaked the code as below:

    string processID = Process.GetCurrentProcess().Id.ToString();
    if (displayName.StartsWith("!VisualStudio.DTE.", StringComparison.OrdinalIgnoreCase) &&
            displayName.EndsWith(processID))
    
    0 讨论(0)
  • 2020-12-09 06:12

    I've made the perfect solution below a bit more comfortabel (no Rocket Science). This one works it's way down from Visual Studio 20 to 10 to find the DTE independent of VS versions.

    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;
            }
        }
    }
    
    0 讨论(0)
  • I felt the following points are unnerving, so I have addressed them and found a solution that works for me:

    • GetActiveObject("VisualStudio.DTE.10.0") only works for the first opened (I suppose) Visual Studio
    • The internal static DTE2 GetCurrent() method of Dennis' answer needs the Visual Studio process Id. That is fine if you run the code from add-ins (I think), but does not work e.g. in unit tests.
    • Problems in debug mode

    I also started out with the GetCurrent method, taken from here. Problem was, I didn't know how to get to the ProcessId of the right VisualStudio process (usually multiple instances are running). So the approach I took was getting all VisualStudio ROT entries and their DTE2, then comparing DTE2.Solution.FullName to the executing assembly location (do you see a better choice?). While I readily admit this is not very exact science, it should work if you do not have rather special Output Path configurations. Then I found that running my code in debug mode and accessing the DTE2 COM objects threw the following exception: System.Runtime.InteropServices.COMException: Call was rejected by callee. (Exception from HRESULT: 0x80010001 (RPC_E_CALL_REJECTED)). There is a remedy for that though, called MessageFilter. I have included the code at the bottom for completeness sake.

    Test class containing a test method (usage example), the adjusted GetCurrent method and a helper method for string comparison:

    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 class:

    /// <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);
    }
    
    0 讨论(0)
  • 2020-12-09 06:25

    After some extensive searching and trying i finally got the answer using the comment that was added to the MSDN page: http://msdn.microsoft.com/en-us/library/ms228755.aspx

    I added a static class to my c# project:

    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;
      }
    

    And at the point that I want to access the current IDE:

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

    But remember.... This code will NOT work during debugging!!! The line of code starting with string rotEntry... has a call to the Process.GetCurrentProcess to get the ID of the current process.

    While debugging some functionality in my addin (using MME http://mme.codeplex.com/) I call a method that needs the current IDE. I test this with a ConsoleApp that calls the addin method. At the point of getting the current IDE, the currentprocess is NOT the IDE, but the ConsoleApp.vshost.exe. So my code did not work during debugging, but DID work after building the addin and installing this addin.

    0 讨论(0)
提交回复
热议问题