2011-01-18 4 views
10

Voglio ottenere un riferimento alla soluzione corrente, utilizzando l'oggetto DTE2 con C# in Visual Studio 2010.Prendi il riferimento dell'oggetto DTE2 in Visual C# 2010

ho provato il seguente codice:

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

Ma quando apro 2 soluzioni e questo codice è nella prima soluzione, NON ottengo un riferimento alla soluzione corrente, ma un riferimento all'ultima soluzione che ho caricato. Ho bisogno della soluzione attuale ...

ricerca su internet, ho trovato la seguente soluzione in 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)); 

Ma quando io uso questo, il mio oggetto DTE è sempre nullo.

Quindi, come si arriva al mio oggetto soluzione corrente in VS2010 utilizzando C# su .net framework 4.0?

risposta

12

Dopo una certa vasta ricerca e provare finalmente ho avuto la risposta utilizzando il commento che è stato aggiunto alla pagina di MSDN: http://msdn.microsoft.com/en-us/library/ms228755.aspx

ho aggiunto una classe statica al mio progetto 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; 
    } 

E a il punto che voglio accedere all'ID corrente:

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

Ma ricorda .... Questo codice NON funzionerà durante il debugging !!! La riga di codice che inizia con la stringa rotEntry ... ha una chiamata a Process.GetCurrentProcess per ottenere l'ID del processo corrente.

Durante il debug di alcune funzionalità nel mio componente aggiuntivo (utilizzando MME http://mme.codeplex.com/) chiamo un metodo che richiede l'IDE corrente. Lo collaudo con una ConsoleApp che chiama il metodo addin. Al momento di ottenere l'IDE corrente, il processo corrente NON è l'IDE, ma ConsoleApp.vshost.exe. Quindi il mio codice non ha funzionato durante il debug, ma il lavoro DID ha funzionato dopo aver compilato l'addin e installato questo addin.

+0

Grazie per questa risposta! Mi ha aiutato molto. In effetti ero così bloccato su questo che ho aperto una taglia su una domanda proprio come quella. Se vai lì e inserisci un link a questa risposta, ci sono buone probabilità che ti assegnerò la taglia. http://stackoverflow.com/questions/10864595/getting-the-current-envdte-or-iserviceprovider-when-not-coding-an-addin – Vaccano

+0

Non hai idea di quanto tempo mi hai salvato oggi !! Vorrei poter fare più di un +1! Grazie –

+0

Questo è un thread più vecchio, ma ha comunque risolto un problema di vecchia data avvenuto con la generazione T4 dal nostro codice. Grazie mille. –

8

mi dicono che i seguenti punti sono snervante, così li ho affrontati e trovato una soluzione che funziona per me:

  • GetActiveObject("VisualStudio.DTE.10.0") funziona solo per il primo aperto (suppongo) di Visual Studio
  • Il internal static DTE2 GetCurrent() Il metodo della risposta di Dennis richiede l'ID del processo di Visual Studio. Questo va bene se si esegue il codice da componenti aggiuntivi (credo), ma non funziona ad es. in unit test.
  • Problemi in modalità debug

Ho anche iniziato con il metodo GetCurrent, tratto da here. Il problema era, non sapevo come arrivare al ProcessId del giusto processo VisualStudio (di solito sono in esecuzione più istanze). Quindi l'approccio che ho preso è stato ottenere tutte le voci ROT di VisualStudio e il loro DTE2, quindi confrontare DTE2.Solution.FullName con l'assembly assembly in esecuzione (vedi una scelta migliore?). Mentre ammetto prontamente che questa non è una scienza molto esatta, dovrebbe funzionare se non si dispone di configurazioni del percorso di output piuttosto speciali. Quindi ho scoperto che l'esecuzione del mio codice in modalità di debug e l'accesso agli oggetti COM DTE2 ha generato la seguente eccezione: System.Runtime.InteropServices.COMException: Call was rejected by callee. (Exception from HRESULT: 0x80010001 (RPC_E_CALL_REJECTED)). Però c'è un rimedio, chiamato MessageFilter.Ho incluso il codice in fondo per completezza.

classe

di prova contenente un metodo di prova (esempio di utilizzo), il metodo rettificato GetCurrent e un metodo di supporto per il confronto stringa: classe

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

So che questo è un vecchio thread, ma avevamo bisogno di usare questo codice con più versioni di Visual Studio. Abbiamo modificato il codice come di seguito:

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

per chiunque sia interessato a fare questo con F # una conversione quasi completo è qui (attualmente impostato per l'esecuzione 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

Ho fatto il perfetto soluzione sotto un po 'più comoda (no Rocket Science). Questo funziona da Visual Studio 20 a 10 per trovare il DTE indipendente dalle versioni 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, ho cercato di ottenere l'EnvDTE per VS2012, e quando vedo le costanti a 11, non riesce a trovarlo. Genera la tua eccezione. Non trova mai l'ID processo della versione VS corrente nel ROT. Qualche idea su cosa potrebbe essere sbagliato? – CoderForHire