c# - tutorial - Automatizando Visual Studio con EnvDTE
pruebas automatizadas visual studio 2015 (3)
Como solución a este problema, puede registrarse en un evento que notifique cuando se realiza la carga de la solución.
Esta es una muestra de clase que le permite escuchar eventos en la carga de soluciones:
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
}
Ejemplo de uso:
DTE2 applicationObject = dte;
var serviceProvider = new ServiceProvider(applicationObject as IServiceProvider);
solutionEventsListener = new SolutionEventsListener(serviceProvider);
solutionEventsListener.AfterSolutionLoaded += () => /* logic here */ ;
Estoy instanciando / automatizando exitosamente Visual Studio usando el siguiente código:
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
Observe la llamada Thread.Sleep(1000)
? Si no lo incluyo, el código intenta generar errores en la instancia antes de que esté listo y obtengo una excepción:
the message filter indicated that the application is busy.
En lugar de esperar exactamente n segundos, ¿hay alguna forma de sondear este objeto para que esté listo?
No he tenido mucha suerte con la técnica IVSSolutionEvents (aunque no probé el código exactamente como se muestra arriba). En cambio, creé una pequeña función para ayudarme a reintentar la llamada. Sé que no es particularmente hermoso, ¡pero es fácil de llamar y funciona!
Aquí hay un enlace a mi respuesta a otra pregunta similar: https://.com/a/8565990/1106459
(También ayuda con los errores de "servidor ocupado" al llamar a otras funciones de EnvDTE, así como a abrir y cerrar la solución).
Si bien las soluciones aquí son creativas, no solucionarán el problema por completo o son muy complicadas de usar. Solo debes registrar un filtro de mensajes como recomienda Microsoft .
Código copiado aquí para mayor comodidad (reemplace VisualStudio.DTE.10.0
con cualquier versión de VS que desee abrir), solo preste atención para decorar el método Main
con el atributo STAThread
, el filtrado de mensajes no funcionará sin él y se omite en MSDN original solución.
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);
}
}