Получить ссылку на объект DTE2 в Visual C# 2010


Я хочу получить ссылку на текущее решение, используя объект DTE2 с C# в Visual Studio 2010.

Я сначала попробовал следующий код:

var dte = Marshal.GetActiveObject("VisualStudio.DTE.10.0") as EnvDTE80.DTE2;
Но когда я открываю 2 решения, и этот код находится в первом решении, я получаю не ссылку на текущее решение, а ссылку на последнее загруженное решение. Мне нужно текущее решение...

Поискав в Интернете, я нашел следующее решение в Как получить текущий каталог решений из а "тесновато"?:

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

Но когда я использую это, мой объект dte всегда равен NULL.

Итак, как мне добраться до моего текущего объекта решения в VS2010 с помощью C# на .net framework 4.0?

5 10

5 ответов:

После некоторого тщательного поиска и попыток я, наконец, получил ответ, используя комментарий, который был добавлен на страницу 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... имеет вызов к процессу.GetCurrentProcess для получения Идентификатор текущего процесса.

При отладке некоторых функций в моем аддине (используя MME http://mme.codeplex.com/) я вызываю метод, который нуждается в текущей IDE. Я проверяю это с помощью ConsoleApp, который вызывает метод addin. В точке получения текущей IDE currentprocess - это не IDE, а ConsoleApp.vshost.exe. Таким образом, мой код не работал во время отладки, но работал после сборки надстройки и установки этой надстройки.

Я чувствовал, что следующие моменты нервируют, поэтому я обратился к ним и нашел решение, которое работает для меня:

  • GetActiveObject("VisualStudio.DTE.10.0") работает только для первой открытой (я полагаю) Visual Studio
  • в internal static DTE2 GetCurrent() Метод ответа Деннис нуждается в визуальной студии id процесса. Это нормально, если вы запускаете код из надстроек (я думаю), но не работает, например, в модульных тестах.
  • проблемы в режиме отладки

Я также начал с метода GetCurrent, взятого из здесь . Проблема была в том, что я не знал, как добраться до ProcessId правильного процесса VisualStudio (обычно запущено несколько экземпляров). Таким образом, подход, который я использовал, состоял в том, чтобы получить все записи VisualStudio ROT и их DTE2, а затем сравнить DTE2.Решение.Полное имя для расположения исполняемой сборки (вы видите лучший выбор?). Хотя я с готовностью признаю, что это не очень точная наука, она должна работать, если у вас нет довольно специальных конфигураций выходного пути. Затем я обнаружил, что запускаю свой код в режиме отладки и при обращении к COM-объектам DTE2 возникло следующее исключение: 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;
    }
}

Класс фильтра сообщений:

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

Я знаю, что это старый поток, но нам нужно было использовать этот код с несколькими версиями Visual Studio. Мы изменили код, как показано ниже:

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

Для всех, кто заинтересован в этом с помощью 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

Я сделал идеальное решение ниже немного более удобной меткой (без ракетостроения). Это работает это путь вниз от Visual Studio 20 до 10, чтобы найти DTE независимо от версий VS.

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