Atachar procesos automáticamente con macros de Visual Studio

16 07 2008

La manera de atachar un proceso a Visual Studio es clasicamente mediante ‘Debug – Attach to process’ o el más productivo Ctrl + Alt + P, y luego seleccionar el o los procesos que querramos depurar.

En desarrollos medianamente importantes es muy probable que debamos realizar este procedimiento repetidas veces para depurar un proyecto fuera de la interface de usuario, expuesto mediante una capa de servicios, llámese WS, remoting, WCF, etc. Ni hablar del worker process de ASP.NET,  ‘aspnet_wp.exe’. También es común que corramos un proyecto desde fuera del Visual Studio para evitar la compilación, y luego lo atachemos al IDE.

Entonces, si los procesos que atachamos son siempre los mismos, ¿por que no automatizar la tarea?, bueno, la solución que propongo es una macro de Visual Studio que haga este trabajo, atachando con un atajo de teclado todos los procesos que sean necesarios.

Para incluirla en VS, vamos al editor de macros (Alt + F11), agregamos un nuevo módulo y reemplazamos el contenido por el siguiente:

Imports System.Collections.Generic
Imports EnvDTE
Imports EnvDTE80
Imports System.Text

Public Module DebugMacros

    Private Enum EnumAction
        ResultOK
        ReRunWithDisatach
        Abort
    End Enum

    Public Sub AttachProcs()
        Dim ProcessList As New List(Of String)
        ProcessList.Add("InterfaceUsuario.exe")
        ProcessList.Add("MiServiceLayer.exe")
        AttachProcs(False, ProcessList)
    End Sub

    Private Sub AttachProcs(ByVal detachAll As Boolean, ByVal ProcessList As List(Of String))

        If detachAll Then DTE.Debugger.DetachAll()

        Dim isOK As EnumAction = EnumAction.ResultOK
        Dim sbErrorResult As New StringBuilder

        For Each proc As String In ProcessList
            Dim match As Boolean = False
            For i As Integer = 1 To DTE.Debugger.LocalProcesses.Count
                If DTE.Debugger.LocalProcesses.Item(i).Name.EndsWith(proc) Then
                    match = True
                    Exit For
                End If
            Next
            If Not match Then
                sbErrorResult.Append("El proceso '" + proc + "' no se está ejecutando." + vbCrLf)
                isOK = EnumAction.Abort
            End If
        Next

        If isOK = EnumAction.ResultOK Then
            'Desatacho todos los procesos
            Dim dbg2 As EnvDTE80.Debugger2 = DTE.Debugger
            Dim trans As EnvDTE80.Transport = dbg2.Transports.Item("Default")
            Dim dbgeng(1) As EnvDTE80.Engine
            dbgeng(0) = trans.Engines.Item("Managed")
            Dim proc2 As EnvDTE80.Process2
            For Each processName As String In ProcessList
                Try
                    proc2 = dbg2.GetProcesses(trans, ".").Item(processName)
                    proc2.Attach2(dbgeng)
                Catch ex As Exception
                    If ex.Message.EndsWith("8971001E") Then
                        sbErrorResult.Append("Error atachando " + processName + ". " + "El proceso ya se encuentra atachado" + vbCrLf)
                        isOK = EnumAction.ReRunWithDisatach
                    Else
                        sbErrorResult.Append("Error inesperado atachando " + processName + ". " + ex.Message + vbCrLf)
                        isOK = EnumAction.Abort
                    End If
                End Try
            Next
        Else
        End If

        Select Case isOK
            Case EnumAction.Abort
                DTE.Debugger.DetachAll()
                MsgBox(sbErrorResult.ToString + vbCrLf + "No se ha atachado ningún proceso", MsgBoxStyle.OkOnly, "Error al atachar proceso")
            Case EnumAction.ReRunWithDisatach
                If MsgBox("Hay procesos previamente atachados. ¿Desea reatacharlos?", _
                                    MsgBoxStyle.YesNo, "Atachar Proceso") = MsgBoxResult.Yes Then
                    AttachProcs(True, ProcessList)
                    Exit Sub
                End If
            Case EnumAction.ResultOK
                Dim Resultado As New StringBuilder
                For Each s As String In ProcessList
                    Resultado.Append(s + vbCrLf)
                Next
                MsgBox("Se han atachado los siguientes procesos: " + vbCrLf + Resultado.ToString, MsgBoxStyle.OkOnly, "Proceso atachados")
        End Select
    End Sub

End Module

Observar que en AttachProcs() hay una colección con los nombres de los procesos la cual hay que editar de acuerdo a nuestras necesidades.

Ahora solo falta asignarle una combinación de teclas, desde Tools – Options – Keyboard, filtramos por “Macro” o “AtachProcs” y una vez ubicada la macro le asignamos una combinación de teclas.

Ahora solo basta correr los procesos por fuera de Visual Studio y atacharlos con la combinación de teclas.

Si el código encuentra alguno de los procesos atachado, desatacha todos los procesos e intenta nuevamente.

Nota: Este simple código está hecho a partir de la grabadora de macros (Tools – Macros – Record Temporary Macro), recordemos que todo lo que hacemos en VS queda registrado en la grabadora y que casi todo en el entorno es un objeto que se puede manipular.

Anuncios




Depurar sin complicarnos la vida utilizando Debugging Attributes

29 05 2008

Cuando depuramos con Visual Studio es un verdadero fastidio el hecho de tener que entrar a todos los métodos o propiedades que se encuentran dentro de una llamada, por ejemplo:

void MiMétodo(DameUnString())
{
   //algo acá que si quiero depurar
}
String DameUnString()
{
   //¡No quiero depurar esto!
   return "Este es tu String!";
}

DebuggerStepThrough y DebuggerHidden

Cuando se trabaja en cualquier sistema medianamente complejo es muy molesto depurar código como el anterior ya que no hay manera en VS de indicar que queremos ir directamente al cuerpo del método que estamos por depurar, sin entrar a cada parámetro.

Lo mismo ocurre cuando presionamos por error F11 en vez de F10 y entramos a un interminable método que nos obliga a poner un break al final (y luego a quitarlo para no parar nuevamente)

Una solución parcial pero util son los atributos DebuggerStepThrough y DebuggerHidden.

Poniendo DebuggerHidden a un método logramos que Visual Studio no pase nunca por el método:

[System.Diagnostics.DebuggerHidden]
String DameUnString(){...}

hará que nunca depuremos el método DameUnString.

En cambio con el más util DebuggerStepThrough, el entorno se detiene en el método si colocamos un break, pero no de otra manera.

Realmente sería más util una tecla para “Avanzar StepThrough”, incluso por ahí he visto una solicitada para su inclusión en VS…pero esta es la solución parcial que tenemos disponible.

DebuggerBrowsable y DebuggerDisplay

Por defecto VS nos permite ver los miembros privados de las clases y estructuras que escribimos al momento de depurarlas. Si bien esto puede ser util, cuando la clase es un contenedor de datos, no es práctico disponer de esta información que está expuesta nuevamente en las propiedades públicas. Ni hablar si nuestra clase va a ser expuesta a terceros.

Por ejemplo si depuramos una clase “Persona” con tres propiedades vemos en la ventana de Watch una molesta duplicación de datos:

El atributo DebuggerBrowsable nos permite especificar que no se deben mostrar en el depurador los métodos, miembros o propiedades decorados con el mismo.

DebuggerDisplay, a su vez, nos permite especificar el texto a mostrar en la columna value de las ventanas de depuración. En la cadena que nos pide podemos especificar entre llaves “{}” código ejecutable, pero la mejor manera de entender esto es ver un ejemplo. Si la clase de la imagen anterior estuviera declarada de la siguiente manera:

[DebuggerDisplay("{this.GetType().ToString(),nq} ({_nombre,nq} {_apellido,nq}, {_edad} años)")]
public struct Persona {...}

(nq significa “No Quotes”, para quitar las comillas de las cadenas)

y los miembros privados estuvieran decorados así:

[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private Int32 _edad;

Entonces lo que veríamos en la ventana de Watch sería:

Mucho más claro si tenemos en cuenta que el tipo persona lo escribiremos una sola vez pero lo depurarán por miles de años.

Hay otros atributos de debugging que permiten desde wrappear en otra clase lo que se va a mostrar al depurar, hasta implementar un viewer customizado para un tipo, como el que Visual Studio trae para el DataSet.

Nota: No olvidar que estos atributos pertenecen al namespace System.Diagnostics, por lo que hay que agregar el correspondiente using/Imports.