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