2011-01-18 14 views
10

Quiero obtener una referencia a la solución actual, utilizando el objeto DTE2 con C# en Visual Studio 2010.obtener la referencia del objeto DTE2 en Visual C# 2010

primera vez que trató el siguiente código:

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

Pero cuando abro 2 soluciones, y este código está en la primera solución, NO obtengo una referencia a la solución actual, sino una referencia a la última solución que cargué. Necesito la solución actual ...

la búsqueda en Internet, he encontrado la siguiente solución en 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)); 

Pero cuando se utiliza esto, mi objeto DTE es siempre NULL.

Entonces, ¿cómo llego a mi objeto de solución actual en VS2010 usando C# en .NET Framework 4.0?

Respuesta

12

Después de una cierta amplitud de la búsqueda y tratando de que finalmente dio la respuesta con el comentario de que se ha añadido a la página de MSDN: http://msdn.microsoft.com/en-us/library/ms228755.aspx

que añade una clase estática a mi proyecto de 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; 
    } 

Y en el punto que quiero acceder al IDE actual:

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

Pero recuerde ... ¡Este código NO funcionará durante la depuración! La línea de código que comienza con la cadena rotEntry ... tiene una llamada al Process.GetCurrentProcess para obtener el ID del proceso actual.

Al depurar algunas funciones en mi complemento (usando MME http://mme.codeplex.com/) llamo a un método que necesita el IDE actual. Pruebo esto con una ConsoleApp que llama al método addin. En el momento de obtener el IDE actual, el proceso actual NO es el IDE, sino el ConsoleApp.vshost.exe. Así que mi código no funcionó durante la depuración, pero DID funcionó después de compilar el complemento e instalar este complemento.

+0

Gracias por esta respuesta! Me ayudo mucho. De hecho, estaba tan atrapado en esto que abrí una recompensa por una pregunta así. Si vas allí y publicas un enlace a esta respuesta, hay una buena posibilidad de que te otorgue la recompensa. http://stackoverflow.com/questions/10864595/getting-the-current-envdte-or-iserviceprovider-when-not-coding-an-addin – Vaccano

+0

¡No tienes idea de cuánto tiempo me salvaste hoy! ¡Ojalá pudiera hacer más que un +1! Gracias –

+0

Este es un hilo más antiguo, pero todavía resolvió un problema de larga data que teníamos con la generación T4 de nuestro código. Muchas gracias. –

8

me sentí los siguientes puntos son desconcertante, así que les han abordado y ha encontrado una solución que funciona para mí:

  • GetActiveObject("VisualStudio.DTE.10.0") sólo funciona para el primer abierto (supongo) de Visual Studio
  • El internal static DTE2 GetCurrent() El método de respuesta de Dennis necesita el Id del proceso de Visual Studio. Eso está bien si ejecuta el código desde complementos (creo), pero no funciona, p. en pruebas unitarias
  • Los problemas en el modo de depuración

También comenzaron con el método GetCurrent, tomada de here. El problema era que no sabía cómo acceder al ProcessId del proceso VisualStudio correcto (generalmente se están ejecutando varias instancias). Así que el enfoque que tomé fue obtener todas las entradas ROT de VisualStudio y su DTE2, y luego comparar DTE2.Solution.FullName con la ubicación del ensamblaje de ejecución (¿ves una mejor opción?). Aunque admito que esta ciencia no es muy exacta, debería funcionar si no tienes configuraciones de ruta de salida bastante especiales. Luego descubrí que al ejecutar mi código en modo de depuración y al acceder a los objetos COM DTE2 lancé la siguiente excepción: System.Runtime.InteropServices.COMException: Call was rejected by callee. (Exception from HRESULT: 0x80010001 (RPC_E_CALL_REJECTED)). Sin embargo, hay un remedio para eso, llamado MessageFilter.He incluido el código en la parte inferior para completarlo.

clase

de ensayo que contiene un método de prueba (ejemplo de uso), la ajustada GetCurrent método y un método de ayuda para la comparación de cadenas: Clase

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

Sé que esto es una vieja hilo, pero necesitábamos usar este código con múltiples versiones de Visual Studio. Ajustamos el código de la siguiente manera:

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

para cualquier persona interesada en hacer esto con F # una conversión casi completa está aquí (actualmente ajustado para funcionar en 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

He hecho el perfecto solución debajo de un poco más comfortabel (no Rocket Science). Esta funciona muy por debajo de Visual Studio 20 a 10 para encontrar el DTE independiente de las versiones 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; 
     } 
    } 
} 
+0

Paul, he intentado obtener el EnvDTE para VS2012, y cuando veo las constantes en 11, no lo encuentro. Lanza su excepción. Nunca encuentra el ID de proceso de la versión VS actual en el ROT. ¿Alguna idea de lo que podría estar mal? – CoderForHire

Cuestiones relacionadas