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////
#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.
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 (http://www.mvp-access.es/buho/ficheros/apiprogramacargado.txt)
Espero que os sirva...
Saludos
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
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
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:
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