Tutorial 14: Procesos

Aprenderemos qué es un proceso, cómo crearlo y cómo terminarlo.

Bajar el ejemplo aquí.

Preliminares:

¿Qué es un proceso? He extrañido esta definición del referencia de la API de Win32:

"Un proceso es una aplicación en ejecución que consiste en un espacio de direcciones privado, código, datos, y otros recursos del sistema operativo, tales como archivos, tuberías, y objetos de sincronización que son visibles al proceso."

Cómo puedes ver, un proceso "se apropia" de algunos objetos: el espacio de direcciones, el módulo ejecutante o los módulos, y cualquier cosa que el módulo ejecutante pueda crear o abrir. Al menos, un proceso debe consistir en un módulo ejecutable, un espacio de direcciones privado y un hilo. Todo proceso debe tener por lo menos un hilo.

¿Qué es un hilo? Un hilo es realmente una cola o flujo de ejecución. Cuando Windows crea un proceso, crea sólo un hilo para el proceso. Este hilo generalmente inicia la ejecución desde la primera instrucción en el módulo. Si el proceso luego necesita más hilos, puede crearlos explícitamente.

Cuando Windows recibe la orden de crear un proceso, crea un espacio de direcciones privado para el proceso y luego proyecta el archivo ejecutable en la memoria de ese proceso. Después de eso crea el hilo primario del proceso.

Bajo Win32, puedes crear procesos desde tus programas llamando a la función CreateProcess. CreateProcess tiene la siguiente sintaxis:

CreateProcess proto lpApplicationName:DWORD,\
                                 lpCommandLine:DWORD,\
                                 lpProcessAttributes:DWORD,\

                                 lpThreadAttributes:DWORD,\
                                 bInheritHandles:DWORD,\
                                 dwCreationFlags:DWORD,\
                                 lpEnvironment:DWORD,\
                                 lpCurrentDirectory:DWORD,\
                                 lpStartupInfo:DWORD,\
                                 lpProcessInformation:DWORD

No te alarmes por el número de parámetros. Podemos ignorar muchos de ellos.

lpApplicationName --> El nombre del archivo ejecutable, con o sin ubicación, que quieres ejecutar. Si este parámetro es nulo, debes proveer el nombre del archivo ejecutable en el parámetro lpCommandLine

lpCommandLine   --> Los argumentos en la línea de órdenes del programa que quieres ejecutar. Nota que si lpApplicationName es NULL, este parámetro debe contener también el nombre del archivo ejecutable. Como este: "notepad.exe readme.txt"

lpProcessAttributes ylpthreadAttributes --> Especifican los atributos de seguridad para el proceso y el hilo primario. Si son NULLs, son usados los atributos de seguridad por defecto.

bInheritHandles --> Una bandera que especifica si quieres que el nuevo proceso herede todos los manejadores abiertos de tu proceso.

dwCreationFlags --> Algunas banderas que determinan la conducta del proceso que quieres crear, tales como, ¿quieres que el proceso sea cerrado pero inmediatamente suspendido para que puedas examinarlo o modificarlo antes de que corra? También puedes indicar la clase de prioridad del(os ) hilo(s) en el nuevo proceso. Esta clase de prioridad es usada para determinar el plan de prioridades de los hilos dentro del proceso. Normalmente usamos la bandera NORMAL_PRIORITY_CLASS.

lpEnvironment --> Puntero a un bloque del entorno que contiene algunas cadenas del entorno para el nuevo proceso. Si este parámetro es NULL, el nuevo proceso hereda el bloque de entorno del proceso padre.

lpCurrentDirectory --> Puntero que especifica el volumen o disco duro y el directorio para el proceso hijo. NULL si quieres que el proceso hijo herede el del padre.

lpStartupInfo --> Apunta a una estructura STARTUPINFO que especifica como la ventana principal del nuevo proceso debería aparecer. la estructura STARTUPINFO contienen muchos miembros que especifican la apariencia de la ventana principal del proceso hijo. Si no quieres nada especial, puedes llenar la estructura STARTUPINFO con los valores del proceso padre llamando a la función GetStartupInfo.

lpProcessInformation --> Apunta a la estructura PROCESS_INFORMATION que recibe información sobre la identificación del nuevo proceso. La estructura PROCESS_INFORMATION tiene los siguientes miembros:

PROCESS_INFORMATION STRUCT
    hProcess          HANDLE ?             ; handle to the child process
    hThread            HANDLE ?             ; handle to the primary thread of the child process
    dwProcessId     DWORD ?             ; ID of the child process
    dwThreadId      DWORD ?            ; ID of the primary thread of the child process
PROCESS_INFORMATION ENDS

El manejador del proceso y su ID son dos cosas diferentes. Un ID de proceso es un identificador único para el proceso en el sistema. Un manejador de proceso es un valor que regresa Windows para usar en otras funciones API relacionadas con el proceso. Un manejador de proceso no puede ser usado para identificar un proceso, ya que no es único.

Después de llamar a CreateProcess, se crea un nuevo proceso y la llamada a CreateProcess regresa de inmediato. Puedes chequear si el nuevo proceso todavía está activo llamando a la función GetExitCodeProcess que tiene la siguiente sintaxis:

GetExitCodeProcess proto hProcess:DWORD, lpExitCode:DWORD

Si esta llamada tiene éxito, lpExitCode contiene el status de terminación del proceso en cuestión. Si el valor en lpExitCode es igual a STILL_ACTIVE, entonces ese proceso todavía está corriendo.

Puedes forzar la terminación de un proceso llamando a la función TerminateProcess. Tiene la siguiente sintaxis:

TerminateProcess proto hProcess:DWORD, uExitCode:DWORD

Puedes especificar el código de salida que desees para el proceso, cualquier valor que te guste. TerminateProcess no es una manera limpia de terminar un proceso ya que ninguna dll enganchada al proceso será notificada que el proceso ha terminado.
 

Ejemplo:

El siguiente ejemplo creará un nuevo proceso cuando el usuario seleccione el elemento de menú "create process". Intentará ejecutar "msgbox.exe". Si el usuario quiere terminar el nuevo proceso, puede seleccionar el elemento de menú "terminate process". El programa chequeará primero si el nuevo proceso ya está destruido, si este no es el caso, el programa llamará a la función TerminateProcess para destruir el nuevo proceso.

.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

.const
IDM_CREATE_PROCESS equ 1
IDM_TERMINATE equ 2
IDM_EXIT equ 3

.data
ClassName db "Win32ASMProcessClass",0
AppName  db "Win32 ASM Process Example",0
MenuName db "FirstMenu",0
processInfo PROCESS_INFORMATION <>
programname db "msgbox.exe",0

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hMenu HANDLE ?
ExitCode DWORD ?   ; contiene el estatus del código de salida de la llamada a
                                  ; GetExitCodeProcessl.

.code
start:
        invoke GetModuleHandle, NULL
        mov    hInstance,eax
        invoke GetCommandLine
        mov CommandLine,eax
        invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
        invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
    LOCAL wc:WNDCLASSEX
    LOCAL msg:MSG
    LOCAL hwnd:HWND
    mov   wc.cbSize,SIZEOF WNDCLASSEX
    mov   wc.style, CS_HREDRAW or CS_VREDRAW
    mov   wc.lpfnWndProc, OFFSET WndProc
    mov   wc.cbClsExtra,NULL
    mov   wc.cbWndExtra,NULL
    push  hInst
    pop   wc.hInstance
    mov   wc.hbrBackground,COLOR_WINDOW+1
    mov   wc.lpszMenuName,OFFSET MenuName
    mov   wc.lpszClassName,OFFSET ClassName
    invoke LoadIcon,NULL,IDI_APPLICATION
    mov   wc.hIcon,eax
    mov   wc.hIconSm,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov   wc.hCursor,eax
    invoke RegisterClassEx, addr wc
    invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
           WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
           CW_USEDEFAULT,300,200,NULL,NULL,\
           hInst,NULL
    mov   hwnd,eax
    invoke ShowWindow, hwnd,SW_SHOWNORMAL
    invoke UpdateWindow, hwnd
    invoke GetMenu,hwnd
    mov  hMenu,eax
    .WHILE TRUE
                invoke GetMessage, ADDR msg,NULL,0,0
                .BREAK .IF (!eax)
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
    .ENDW
    mov     eax,msg.wParam
    ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    LOCAL startInfo:STARTUPINFO
    .IF uMsg==WM_DESTROY
        invoke PostQuitMessage,NULL
    .ELSEIF uMsg==WM_INITMENUPOPUP
        invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
        .if eax==TRUE
            .if ExitCode==STILL_ACTIVE
                invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_GRAYED
                invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_ENABLED
            .else
                invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
                invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
            .endif
        .else
            invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
            invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
        .endif
    .ELSEIF uMsg==WM_COMMAND
        mov eax,wParam
        .if lParam==0
            .if ax==IDM_CREATE_PROCESS
                .if processInfo.hProcess!=0
                    invoke CloseHandle,processInfo.hProcess
                    mov processInfo.hProcess,0
                .endif
                invoke GetStartupInfo,ADDR startInfo
                invoke CreateProcess,ADDR programname,NULL,NULL,NULL,FALSE,\
                                        NORMAL_PRIORITY_CLASS,\
                                        NULL,NULL,ADDR startInfo,ADDR processInfo
                invoke CloseHandle,processInfo.hThread
            .elseif ax==IDM_TERMINATE
                invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
                .if ExitCode==STILL_ACTIVE
                    invoke TerminateProcess,processInfo.hProcess,0
                .endif
                invoke CloseHandle,processInfo.hProcess
                mov processInfo.hProcess,0
            .else
                invoke DestroyWindow,hWnd
            .endif
        .endif
    .ELSE
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
    .ENDIF
    xor    eax,eax
    ret
WndProc endp
end start

Análisis:

El programa crea la ventana principal y regresa el manejador del menú para usarlo en el futuro. Luego espera a que el usuario seleccione una orden o comando en el menú. Cuando el usuario selecciona el elemento de menú "Process" en el menú principal, procesamos el mensaje WM_INITMENUPOPUP para modificar los elementos de menú dentro de el menú emergente antes de que sea desplegado.

    .ELSEIF uMsg==WM_INITMENUPOPUP
        invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
        .if eax==TRUE
            .if ExitCode==STILL_ACTIVE
                invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_GRAYED
                invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_ENABLED
            .else
                invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
                invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
            .endif
        .else
            invoke EnableMenuItem,hMenu,IDM_CREATE_PROCESS,MF_ENABLED
            invoke EnableMenuItem,hMenu,IDM_TERMINATE,MF_GRAYED
        .endif

¿Por qué queremos procesar este mensaje? porque queremos preparar los elementos en el menú emergente antes de que el usuario pueda verlos. En nuestro ejemplo, si el nuevo proceso aún no ha comenzado, queremos habilitar el elemento "start process" y difuminar [gray out] el elemento "terminate process". Hacemos la inversión si el nuevo proceso ya está activo.

Primero chequeamos si el nuevo proceso todavía está activo llamando a la función GetExitCodeProcess con el manejador de proceso llenado por la función CreateProcess. Si GetExitCodeProcess regresa FALSE, significa que el proceso no ha comenzado todavía así que difuminamos el elemento de menú "terminate process". Si GetExitCodeProcess regresa TRUE, sabemos que ha sido iniciado un nuevo proceso, pero tenemos que chequear luego si todavía está corriendo. Así que comparamos el valor en ExitCode al valor STILL_ACTIVE, si son iguales, el proceso todavía está corriendo: debemos difuminar el elemento de menú "start process" ya que no queremos iniciar varios procesos concurrentes.

            .if ax==IDM_CREATE_PROCESS
                .if processInfo.hProcess!=0
                    invoke CloseHandle,processInfo.hProcess
                    mov processInfo.hProcess,0
                .endif
                invoke GetStartupInfo,ADDR startInfo
                invoke CreateProcess,ADDR programname,NULL,NULL,NULL,FALSE,\
                                        NORMAL_PRIORITY_CLASS,\
                                        NULL,NULL,ADDR startInfo,ADDR processInfo
                invoke CloseHandle,processInfo.hThread
 
Cuando el usuario selecciona el elemento de menú "start process", primero chequeamos si el miembro hProcess de la estrcutura PROCESS_INFORMATION ya está cerrado. Si es la primera vez, el valor de hProcess siempre será cero ya que definimos la estructura PROCESS_INFORMATION en la sección .data. Si el valor del miembro hProcess no es 0, significa que el proceso hijo ha terminado pero no hemos cerrado su manejador de proceso todavía. Así que este es el momento de hacerlo.

Si llamamos a la función GetStartupInfo llenaremos la estructura startupinfo que pasaremos a la función CreateProcess. Después de que llamamos a la función CreateProcess para comenzar el nuevo proceso. Nota que no hemos chequeado el valor regersado por CreateProcess ya que haría más complejo el ejemplo. En la vida real, deberías chequear el valor regresado por CreateProcess. Inmediatamente después de CreateProcess, cerramos el manejador de hilo primario regresado en la estructura processInfo. Cerrar el manejador no significa que hemos terminado el hilo, sólo siginifica que no queremos usar el manejador para referirse al hilo de nuestro programa. Si no lo cerramos, causará carencia de recursos.

            .elseif ax==IDM_TERMINATE
                invoke GetExitCodeProcess,processInfo.hProcess,ADDR ExitCode
                .if ExitCode==STILL_ACTIVE
                    invoke TerminateProcess,processInfo.hProcess,0
                .endif
                invoke CloseHandle,processInfo.hProcess
                mov processInfo.hProcess,0

Cuando el usuario selecciona el elemento de menú "terminate process", chequeamos si el nuevo proceso ya está activo llamando a la función GetExitCodeProcess. Si todavía está activo, llamamos la función TerminateProcess para matar el proceso. También cerramos el manejador del proceso hijo ya que no lo necesitamos más.


Índice

Next

[Iczelion's Win32 Assembly HomePage]

n u M I T_o r's   Programming Page

Este tutorial, original de Iczelion, ha sido traducido por:   n u M I T_o r

www.000webhost.com