2010-03-26 4 views
28

Я успешно инстанцировании/автоматизирующей Visual Studio, используя следующий код:Автоматизация Visual Studio с EnvDTE

System.Type t = System.Type.GetTypeFromProgID("VisualStudio.DTE.9.0"); 
object obj = Activator.CreateInstance(t, true); 
dte = (DTE)obj; 
Solution sln = dte.Solution; 
sln.Open(SolutionFile); 
System.Threading.Thread.Sleep(1000); 
//Do stuff with the solution 

Обратите внимание на Thread.Sleep(1000) вызов? Если я не включаю, что код пытается жук экземпляр, прежде чем он будет готов, и я получаю исключение:

the message filter indicated that the application is busy. 

Вместо того, чтобы ждать ровно п секунд, есть способ опрашивать этот объект для готовности?

ответ

31

В качестве решения этой проблемы вы можете зарегистрироваться на событие, которое уведомляет о завершении загрузки решения.

Это образец класса, который позволяет прослушивать события на погрузку раствора: например

public class SolutionEventsListener : IVsSolutionEvents, IDisposable 
{ 
    private IVsSolution solution; 
    private uint solutionEventsCookie; 

    public event Action AfterSolutionLoaded; 
    public event Action BeforeSolutionClosed; 

    public SolutionEventsListener(IServiceProvider serviceProvider) 
    { 
     InitNullEvents(); 

     solution = serviceProvider.GetService(typeof (SVsSolution)) as IVsSolution; 
     if (solution != null) 
     { 
      solution.AdviseSolutionEvents(this, out solutionEventsCookie); 
     } 
    } 

    private void InitNullEvents() 
    { 
     AfterSolutionLoaded +=() => { }; 
     BeforeSolutionClosed +=() => { }; 
    } 

    #region IVsSolutionEvents Members 

    int IVsSolutionEvents.OnAfterCloseSolution(object pUnkReserved) 
    { 
     return VSConstants.S_OK; 
    } 

    int IVsSolutionEvents.OnAfterLoadProject(IVsHierarchy pStubHierarchy, IVsHierarchy pRealHierarchy) 
    { 
     return VSConstants.S_OK; 
    } 

    int IVsSolutionEvents.OnAfterOpenProject(IVsHierarchy pHierarchy, int fAdded) 
    { 
     return VSConstants.S_OK; 
    } 

    int IVsSolutionEvents.OnAfterOpenSolution(object pUnkReserved, int fNewSolution) 
    { 
     AfterSolutionLoaded(); 
     return VSConstants.S_OK; 
    } 

    int IVsSolutionEvents.OnBeforeCloseProject(IVsHierarchy pHierarchy, int fRemoved) 
    { 
     return VSConstants.S_OK; 
    } 

    int IVsSolutionEvents.OnBeforeCloseSolution(object pUnkReserved) 
    { 
     BeforeSolutionClosed(); 
     return VSConstants.S_OK; 
    } 

    int IVsSolutionEvents.OnBeforeUnloadProject(IVsHierarchy pRealHierarchy, IVsHierarchy pStubHierarchy) 
    { 
     return VSConstants.S_OK; 
    } 

    int IVsSolutionEvents.OnQueryCloseProject(IVsHierarchy pHierarchy, int fRemoving, ref int pfCancel) 
    { 
     return VSConstants.S_OK; 
    } 

    int IVsSolutionEvents.OnQueryCloseSolution(object pUnkReserved, ref int pfCancel) 
    { 
     return VSConstants.S_OK; 
    } 

    int IVsSolutionEvents.OnQueryUnloadProject(IVsHierarchy pRealHierarchy, ref int pfCancel) 
    { 
     return VSConstants.S_OK; 
    } 

    #endregion 

    #region IDisposable Members 

    public void Dispose() 
    { 
     if (solution != null && solutionEventsCookie != 0) 
     { 
      GC.SuppressFinalize(this); 
      solution.UnadviseSolutionEvents(solutionEventsCookie); 
      AfterSolutionLoaded = null; 
      BeforeSolutionClosed = null; 
      solutionEventsCookie = 0; 
      solution = null; 
     } 
    } 

    #endregion 
} 

Использование:

DTE2 applicationObject = dte; 
var serviceProvider = new ServiceProvider(applicationObject as IServiceProvider); 
solutionEventsListener = new SolutionEventsListener(serviceProvider); 
solutionEventsListener.AfterSolutionLoaded +=() => /* logic here */ ; 
+7

Это отличный ответ и отлично работает. Кстати, я нашел чит, который работал так же, - проверить свойство Solution.IsOpen сразу после открытия решения с помощью Solution.Open. Это, по-видимому, блокирующий вызов, который заставляет ждать, пока экземпляр devenv.exe не будет готов. –

+2

@Dave Swersky, мне нравится Solution.IsOpen, он экономит много кода :) – Elisha

+0

Это также помогло мне в VS11, где SolutionEvents кажется сломанным! – riezebosch

0

у меня не было много удачи с техникой IVSSolutionEvents (хотя Я не пробовал код точно так же, как указано выше). Вместо этого я создал небольшую функцию, чтобы помочь мне повторить вызов. Я знаю, что это не особенно красиво, но просто позвонить, и это работает!

Вот ссылка на мой ответ на другой, аналогичный вопрос: https://stackoverflow.com/a/8565990/1106459

(. Это также помогает с «Сервер занят» ошибки при вызове других функций EnvDTE, а также открытие и закрытие раствора)

7

Хотя решения здесь являются творческими, они либо не будут полностью устранять проблему, либо очень громоздки в использовании. Вы должны просто register a message filter as Microsoft recommends.

Код скопирован здесь для удобства (заменить VisualStudio.DTE.10.0 с любой версией VS вы хотите открыть), просто обратите внимание, чтобы украсить метод Main с STAThread атрибутом, фильтрация сообщений не будет работать без него, и он будет пропущен в оригинальном MSDN решение.

using System; 
using System.Collections.Generic; 
using System.Text; 
using EnvDTE; 
using EnvDTE80; 
using System.Runtime.InteropServices; 
using System.Windows.Forms; 

namespace ConsoleApplication2 
{ 
    class Program 
    { 
     [STAThread] 
     static void Main(string[] args) 
     { 
      EnvDTE80.DTE2 dte; 
      object obj = null; 
      System.Type t = null; 

      // Get the ProgID for DTE 8.0. 
      t = System.Type.GetTypeFromProgID("VisualStudio.DTE.10.0", 
       true); 
      // Create a new instance of the IDE. 
      obj = System.Activator.CreateInstance(t, true); 
      // Cast the instance to DTE2 and assign to variable dte. 
      dte = (EnvDTE80.DTE2)obj; 

      // Register the IOleMessageFilter to handle any threading 
      // errors. 
      MessageFilter.Register(); 
      // Display the Visual Studio IDE. 
      dte.MainWindow.Activate(); 

      // ===================================== 
      // ==Insert your automation code here.== 
      // ===================================== 
      // For example, get a reference to the solution2 object 
      // and do what you like with it. 
      Solution2 soln = (Solution2)dte.Solution; 
      System.Windows.Forms.MessageBox.Show 
       ("Solution count: " + soln.Count); 
      // ===================================== 

      // All done, so shut down the IDE... 
      dte.Quit(); 
      // and turn off the IOleMessageFilter. 
      MessageFilter.Revoke(); 

     } 
    } 

    public class MessageFilter : IOleMessageFilter 
    { 
     // 
     // Class containing the IOleMessageFilter 
     // thread error-handling functions. 

     // Start the filter. 
     public static void Register() 
     { 
      IOleMessageFilter newFilter = new MessageFilter(); 
      IOleMessageFilter oldFilter = null; 
      int hr = CoRegisterMessageFilter(newFilter, out oldFilter); 
      if (hr != 0) 
       Marshal.ThrowExceptionForHR(hr); 
     } 

     // 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 the flag SERVERCALL_ISHANDLED. 
      return 0; 
     } 

     // Thread call was rejected, so try again. 
     int IOleMessageFilter.RetryRejectedCall(System.IntPtr 
      hTaskCallee, int dwTickCount, int dwRejectType) 
     { 
      if (dwRejectType == 2) 
      // flag = SERVERCALL_RETRYLATER. 
      { 
       // Retry the thread call immediately if return >=0 & 
       // <100. 
       return 99; 
      } 
      // Too busy; cancel call. 
      return -1; 
     } 

     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

Отличное решение, и это должен быть ОТВЕТ. Вот как это должно быть сделано. Никто из других не вел себя для меня достоверно. Просто подскажите, пожалуйста, обратите внимание на [STAThread] наверху, без него MessageFilter не будет регистрироваться, так как он не поддерживается для MTA. Благодаря! – mikus

+0

Ну, повторное решение в http://stackoverflow.com/questions/5330289/is-there-a-better-way-to-handle-rpc-e-call-rejected-exceptions-when-doing-visual/8565990 # 8565990 будет более или менее делать то же самое, но будет гораздо более громоздким в использовании. Это использует встроенную систему фильтрации сообщений в com для повторного вызова вызовов. – poizan42

+0

Я использовал аналогичный код с кодированным интерфейсом, где я не стал бы ждать загрузки страницы, просто попробуйте выполнить действие до тех пор, пока оно не будет работать. Но, как вы сказали, его менее прозрачный и раздражающий для использования в долгосрочной перспективе. Особенно те исключения COM могут произойти не только при первом вызове, но и позже. – mikus

Смежные вопросы