Snippet para obtener handle de procesos

Iniciado por Lekim, 21 Noviembre 2015, 06:18 AM

0 Miembros y 1 Visitante están viendo este tema.

Lekim

Hola

Iré al grano, utilizando la función Process.GetProcesses() puedo obtener información del los procesos del sistema así como el Handle o el MainWindowHandle de la ventana principal de un proceso.

El problema es que en algunos casos  MainWindowHandle  devuelve 0

Recordé un viejo código de VB que tenía por ahí. Y tras un buen rato probando lo conseguí pasar a VB.NET, ya que usa APIs y si no se usa bien devuelve error o valores nulos.

Se necesita el ID de un proceso para obtener el handle principal, y dado que la clase  Process no devuelve valor nulo con los IDs obtengo todos los Handles en la lista.


Ahí va el code  ;):

////OBTENER HANDLE PROCESO////

Código (vbnet) [Seleccionar]
#Region "Obtener Handle Proceso"
Public Module modProcessHandle
   <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
   Private Function FindWindow(ByVal lpClassName As String, _
   ByVal lpWindowName As String) As IntPtr
   End Function
   <DllImport("user32.dll", ExactSpelling:=True, CharSet:=CharSet.Auto)> _
   Public Function GetParent(ByVal hWnd As IntPtr) As IntPtr
   End Function
   <DllImport("user32.dll", ExactSpelling:=True, CharSet:=CharSet.Auto)> _
   Private Function GetWindow(ByVal hWnd As IntPtr, _
   ByVal wCmd As Integer) As IntPtr
   End Function
   <DllImport("user32.dll", ExactSpelling:=True, CharSet:=CharSet.Auto)> _
   Private Function GetWindowThreadProcessId(ByVal hWnd As IntPtr, _
   ByRef lpdwProcessId As Integer) As IntPtr
   End Function


   Public Function GetProcessHandle(ByVal ProcessId As Integer) As IntPtr
       Dim HwndTemporal As IntPtr
       Dim idProc As Integer = 0
       Dim HWND As IntPtr = IntPtr.Zero
       Const GW_HWNDNEXT = 2
       HwndTemporal = FindWindow(Nothing, Nothing)
       ' Loop hasta que encuentra una coincidencia o no hay más identificadores de ventana:
       Do Until HwndTemporal = IntPtr.Zero
           ' Comprueba que no tenga ninguna ventana 'Parent'
           If GetParent(HwndTemporal) = IntPtr.Zero Then
               GetWindowThreadProcessId(HwndTemporal, idProc)
               If ProcessId.Equals(idProc) Then
                   HWND = HwndTemporal 'Devuelve el handle encontrado
                   Return HWND
                   Exit Do ' Salir de bucle
               End If
           End If
           idProc = 0
           'Siguiente identificador de ventana
           HwndTemporal = GetWindow(HwndTemporal, GW_HWNDNEXT)
       Loop
       Return HWND
   End Function

End Module
#End Region



Aquí un ejemplo de como usarlo para obtener una lista con los procesos en ejecución sus handles y su Class Name.


Código (vbnet) [Seleccionar]
Option Strict On
Option Explicit On
Imports System.Runtime.InteropServices
Imports System.Text

Public Class Form1
   <DllImport("user32.dll", CharSet:=CharSet.Auto)>
   Private Shared Function GetClassName(ByVal hWnd As IntPtr, _
  ByVal lpClassName As System.Text.StringBuilder, _
  ByVal nMaxCount As Integer) As Integer
   End Function
   Dim ClassName As StringBuilder = New StringBuilder(256)
   Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
       ListView1.Columns.Add("Name", 100)
       ListView1.Columns.Add("MainHandle", 100)
       ListView1.Columns.Add("ClassName", 200)
       ListView1.Columns.Add("MainHandle", 100)
       ListView1.Columns.Add("ClassName", 200)
       ListView1.View = View.Details
       ListView1.FullRowSelect = True
       'Recorre los procesos y agrega la información al ListView
       For Each Proceso In Process.GetProcesses()
           Dim Handle As IntPtr = modProcessHandle.GetProcessHandle(Proceso.Id)
           If Handle <> IntPtr.Zero Then
               'Nombre Proceso
               ListView1.Items.Add(Proceso.ProcessName)

               'MainWindowHandle
               ListView1.Items(ListView1.Items.Count - 1).SubItems.Add _
                   (Conversion.Hex(Proceso.MainWindowHandle.ToInt32))
               'ClassName
               ClassName.Clear()
               GetClassName(CType(Proceso.MainWindowHandle.ToInt32, IntPtr), ClassName, ClassName.Capacity)
               ListView1.Items(ListView1.Items.Count - 1).SubItems.Add(ClassName.ToString)

               'MainWindowHandle
               ListView1.Items(ListView1.Items.Count - 1).SubItems.Add(Conversion.Hex(CInt(Handle)))
               'ClassName
               ClassName.Clear()
               GetClassName(Handle, ClassName, ClassName.Capacity)
               ListView1.Items(ListView1.Items.Count - 1).SubItems.Add(ClassName.ToString)

           End If
       Next
   End Sub

   Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

   End Sub
End Class
#Region "Obtener Handle Proceso"
Public Module modProcessHandle
   <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
   Private Function FindWindow(ByVal lpClassName As String, _
   ByVal lpWindowName As String) As IntPtr
   End Function
   <DllImport("user32.dll", ExactSpelling:=True, CharSet:=CharSet.Auto)> _
   Public Function GetParent(ByVal hWnd As IntPtr) As IntPtr
   End Function
   <DllImport("user32.dll", ExactSpelling:=True, CharSet:=CharSet.Auto)> _
   Private Function GetWindow(ByVal hWnd As IntPtr, _
   ByVal wCmd As Integer) As IntPtr
   End Function
   <DllImport("user32.dll", ExactSpelling:=True, CharSet:=CharSet.Auto)> _
   Private Function GetWindowThreadProcessId(ByVal hWnd As IntPtr, _
   ByRef lpdwProcessId As Integer) As IntPtr
   End Function


   Public Function GetProcessHandle(ByVal ProcessId As Integer) As IntPtr
       Dim HwndTemporal As IntPtr
       Dim idProc As Integer = 0
       Dim HWND As IntPtr = IntPtr.Zero
       Const GW_HWNDNEXT = 2
       HwndTemporal = FindWindow(Nothing, Nothing)
       ' Loop hasta que encuentra una coincidencia o no hay más identificadores de ventana:
       Do Until HwndTemporal = IntPtr.Zero
           ' Comprueba que no tenga ninguna ventana 'Parent'
           If GetParent(HwndTemporal) = IntPtr.Zero Then
               GetWindowThreadProcessId(HwndTemporal, idProc)
               If ProcessId.Equals(idProc) Then
                   HWND = HwndTemporal 'Devuelve el handle encontrado
                   Return HWND
                   Exit Do ' Salir de bucle
               End If
           End If
           idProc = 0
           'Siguiente identificador de ventana
           HwndTemporal = GetWindow(HwndTemporal, GW_HWNDNEXT)
       Loop
       Return HWND
   End Function

End Module
#End Region



Como demostración he hecho que se muestre en una columna los handles obtenidos con la clase Process y otra columna con los handles obtenidos con el snippet. Se puede ver que con algunos procesos ocultos, es decir no son visibles, se devuelve valor 0, pero con el snippet se obtiene el handle sin problemas. Además se obtienen los nombres de clase (ClassName) que sin los Handles de las ventanas no es posible obtenerlos. Por lo menos que yo sepa.

La primera columna ClassName se obtiene con MainWindowHandle del Procces. La segunda columna ClassName se obtiene con los handles obtenidos con el snippet.


Si hay otra forma más sencilla, decídmelo  :-*


El código original VB6 está en el siguiente enlace:
CodeVB

Espero que os sirva...

Saludos

Eleкtro

#1
Es útil, funciona muy bien hasta donde lo he testeado.

Yo uso la parte importante del código que has mostrado (función GetProcessHandle) para obtener el hwnd de la ventana principal de un proceso para cambiar la visibilidad de dicha ventana mediante la función Win32 ShowWindow.

saludos








Lekim

#2
Cita de: Eleкtro en 25 Noviembre 2015, 16:03 PM
Es útil, funciona muy bien hasta donde lo he testeado.

Yo uso la parte importante del código que has mostrado (función GetProcessHandle) para obtener el hwnd de la ventana principal de un proceso para cambiar la visibilidad de dicha ventana mediante la función Win32 ShowWindow.

saludos

Hola
Si te soy sincero,  como conoces Net mucho más que yo pensé mostrarías una de tus perlas usando código Net para hacer lo mismo. Por eso dejé la coletilla:

Si hay otra forma más sencilla, decídmelo

Y si no tu otro/a. XD

Claro que no me refiero a reducir el código,  sino al uso de alguna otra clase distinta a Process, u otra función.

No entiendo porqué con la clase Process sólo se obtengan los handles de unos pero de otros no.

sl2s

Eleкtro

#3
Cita de: Lekim en 25 Noviembre 2015, 17:18 PMSi te soy sincero,  como conoces Net mucho más que yo pensé mostrarías una de tus perlas usando código Net para hacer lo mismo. Por eso dejé la coletilla:

Si hay otra forma más sencilla, decídmelo

Claro que no me refiero a reducir el código,  sino al uso de alguna otra clase distinta a Process, u otra función.

Hombre, con las classes de Windows UI Automation se puede reemplazar todo ese p/invoking de tu código para hacer practicamente cualquier tipo de Spy y automación en las ventanas, pero hay un gran problema, UI Automation descarta las ventanas ocultas así que la única manera posible que se me ocurre para obtener el hwnd de una ventana OCULTA sería mediante código no administrado, como ya has mostrado. Para todo lo demás puedes usar Windows UI Automation.

Edito:
Te muestro un ejemplo que simularia la función Process.GetProcessById para obtener una representación del proceso (o mejor dicho de la ventana) y así el hwnd de la ventana:

Código (vbnet) [Seleccionar]
Dim p As New Process

p.StartInfo = New ProcessStartInfo() With {
    .WindowStyle = ProcessWindowStyle.Normal,
    .FileName = "notepad"
}

p.Start()
p.WaitForInputIdle()

Dim window As AutomationElement =
   AutomationElement.RootElement.FindFirst(TreeScope.Descendants,
                                           New PropertyCondition(AutomationElement.ProcessIdProperty, p.Id))

Dim hwnd As IntPtr = New IntPtr(window.Current.NativeWindowHandle)


Yo según que cosas prefiero utilizar la API de Windows, ya que el manejo de UI Automation se puede volver demasiado tedioso.




Cita de: Lekim en 25 Noviembre 2015, 17:18 PMNo entiendo porqué con la clase Process sólo se obtengan los handles de unos pero de otros no.

Si vieras detenidamente el código fuente de la class process de .Net framework lo entenderías (la referencia online u offline del código fuente), simplemente se devuelve un handle nulo (IntPtr.Zero) a cualquier ventana que esté oculta, ahora, el "por qué" ...¿quien sabe?, probablemente serán cuestiones de diseño de Microsoft, al igual que UI Automation no trabaja con ventanas ocultas quien sabe por qué (cuando perfectamente podría, ya que la API de Windows puede), algún motivo tendrán, digo yo.

Saludos