Rozdział XIV - Proces


Nauczymy się czym jest proces oraz jak go utworzyć i zatrzymać.

 

 

Pobierz plik z przykładem {z tego archiwum}.

 

Wstęp

Czym jest proces? Cytuję poniższą definicję z pliku pomocy Win32 API:


"Proces jest wykonującą się aplikacją złożoną z prywatnej, wirtualnej przestrzeni adresowej, kodu, danych oraz innych zasobów systemowych takich jak pliki, podprocesy oraz obiekty synchronizacji widoczne dla tego procesu."

Z powyższego tekstu można wyciągnąć wniosek, iż proces "posiada" kilka obiektów: przestrzeń adresową, wykonujący(e) się moduł(y) oraz wszystko, co te moduły utworzą lub otworzą. Proces co najmniej musi się składać z wykonującego się modułu, prywatnej przestrzeni adresowej oraz wątku. Każdy proces musi posiadać przynajmniej jeden watek. Co to jest wątek? W rzeczywistości wątek jest kolejką wykonań. Gdy system Windows po raz pierwszy tworzy pewien proces, przydziela mu tylko jeden watek, który zwykle rozpoczyna wykonanie od pierwszej instrukcji modułu. Jeśli później proces ten będzie potrzebował więcej wątków, to może je sobie jawnie stworzyć.

Gdy system Windows otrzyma polecenie utworzenia procesu, tworzy dla niego prywatną przestrzeń adresową w pamięci, a następnie rozmieszcza w niej plik wykonywalny. Po wykonaniu tego zadania tworzy główny wątek dla procesu.

W bibliotece Win32 istnieje również możliwość tworzenia procesów z wnętrza swoich własnych programów poprzez wywołanie funkcji CreateProcess o następującej składni:


CreateProcess PROTO lpApplicationName:    DWORD,\ 
                    lpCommandLine:        DWORD,\
                    lpProcessAttributes:  DWORD,\ 
                    lpThreadAttributes:   DWORD,\ 
                    bInheritHandles:      DWORD,\ 
                    dwCreationFlags:      DWORD,\ 
                    lpEnvironment:        DWORD,\ 
                    lpCurrentDirectory:   DWORD,\ 
                    lpStartupInfo:        DWORD,\ 
                    lpProcessInformation: DWORD 


Niech cię nie przeraża liczba parametrów. Większość z nich można zignorować.

PROCESS_INFORMATION STRUCT
    hProcess    HANDLE ? ; uchwyt procesu potomnego
    hThread     HANDLE ? ; uchwyt wątku głównego w procesie potomnym
    dwProcessId DWORD  ? ; numer ID procesu potomnego
    dwThreadId  DWORD  ? ; numer ID głównego wątku procesu potomnego
PROCESS_INFORMATION ENDS


Uchwyt do procesu i numer ID procesu to dwie różne rzeczy. Numer ID procesu jest unikalnym identyfikatorem dla procesu w systemie. Uchwyt procesu jest pewną wartością zwracaną przez system Windows do użycia przez inne funkcje API powiązane z procesami. Uchwytu procesu nie można użyć do identyfikacji ponieważ nie jest on unikalny.

Po wywołaniu CreateProcess zostaje utworzony nowy proces i następuje natychmiastowy powrót z wywołania. Można sprawdzić, czy nowy proces jest wciąż aktywny wywołując funkcję GetExitCodeProcess o następującej składni:


GetExitCodeProcess PROTO hProcess:DWORD, lpExitCode:DWORD


Jeśli wywołanie to się powiedzie, to pod adresem lpExitCode umieszczony zostanie stan zakończenia badanego procesu. Jeśli wartość pod adresem lpExitCode jest równa stałej STILL_ACTIVE, to proces się wciąż wykonuje.

Możesz siłowo wymusić zakończenie procesu poprzez wywołanie funkcji TerminateProcess o następującej składni:


TerminateProcess PROTO hProcess:DWORD, uExitCode:DWORD


Można określić dowolny kod wyjścia dla procesu. TerminateProcess nie jest czystym sposobem zakończenia procesu, ponieważ biblioteki dynamiczne DLL dołączone do procesu nie zostaną poinformowane, iż ich proces zakończył się.

Przykład

 

Poniższy przykład utworzy nowy proces, gdy użytkownik wybierze z menu opcję Twórz Proces. Będzie on próbował wykonać program "msgbox.exe", który utworzyliśmy na początku naszego kursu (plik programu jest dołączony do archiwum zip z kodem źródłowym). Gdy użytkownik zechce zakończyć ten nowy proces, może wybrać z menu opcję Zakończ Proces. Program sprawdzi najpierw, czy nowy proces został już usunięty, a jeśli nie, to program wywoła funkcję TerminateProcess w celu usunięcia go.

Plik PROCESS.ASM

 

.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 "Przykład Procesu w Asemblerze", 0
MenuName    DB "FirstMenu", 0
ProcessInfo PROCESS_INFORMATION <>
ProgramName DB "msgbox.exe", 0

.DATA?

hInstance   HINSTANCE ?
CommandLine LPSTR     ?
hMenu       HANDLE    ?
ExitCode    DWORD     ?

.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

 

Plik PROCESS.RC

 

// Stałe dla menu

#define IDM_CREATE_PROCESS 1
#define IDM_TERMINATE      2
#define IDM_EXIT           3

FirstMenu MENU
{
POPUP "&Proces"
    {
         MENUITEM "&Twórz Proces", IDM_CREATE_PROCESS
         MENUITEM "&Zakończ Proces", IDM_TERMINATE
         MENUITEM SEPARATOR
         MENUITEM "&Koniec", IDM_EXIT
    }
}

Analiza

Program ten tworzy główne okno i pobiera uchwyt menu do przyszłego użytku. Następnie czeka, aż użytkownik wybierze polecenie z menu. Gdy użytkownik wybierze element menu Proces w głównym menu, obsłużymy wiadomość WM_INITMENUPOPUP, aby zmodyfikować elementy menu wewnątrz listy zanim zostaną wyświetlone.


    .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


Dlaczego chcemy obsługiwać tę wiadomość? Ponieważ chcemy przygotować elementy menu na rozwijanej liście zanim użytkownik będzie mógł je zobaczyć. W naszym przykładzie jeżeli nowy proces nie został jeszcze uruchomiony, chcemy uaktywnić element menu Twórz Proces i wyłączyć Zakończ Proces. Jeśli proces jest już aktywny, to wykonujemy operacje odwrotne.

Sprawdzenia, czy proces wciąż wykonuje się, dokonujemy wywołując funkcję GetExitCodeProcess z parametrem uchwytu tego procesu, który zwróciła funkcja CreateProcess. Jeśli GetExitCodeProcess zwraca wartość FALSE, to oznacza to, iż proces nie został jeszcze uruchomiony, zatem wyłączamy element menu Zakończ Proces. Jeśli GetExitCodeProcess zwraca TRUE, to wiemy, iż nowy proces został uruchomiony, lecz musimy sprawdzić dalej, czy wciąż wykonuje się. Zatem porównujemy wartość w ExitCode z wartością stałej STILL_ACTIVE, a jeśli są równe, to proces wciąż wykonuje się: musimy wyłączyć opcję menu Twórz Proces, ponieważ nie chcemy uruchamiać kilku równolegle wykonujących się procesów.


    .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


Gdy użytkownik wybierze element menu Twórz Proces, najpierw sprawdzamy, czy pole hProcess struktury PROCESS_INFORMATION jest już zamknięte. Jeśli jest to pierwszy raz, to wartość hProcess zawsze będzie wynosiła zero, ponieważ strukturę PROCESS_INFORMATION definiujemy w sekcji danych zainicjowanych .DATA. Jeśli wartość pola hProcess nie wynosi 0, oznacza to, iż proces potomny zakończył się, lecz nie zamknęliśmy jeszcze jego uchwytu. Zatem dokonujemy zamknięcia uchwytu procesu.

Wywołujemy funkcję GetStartupInfo w celu wypełnienia struktury startupinfo, którą przekażemy do funkcji CreateProcess. W następnej kolejności wywołujemy funkcję CreateProcess, aby rozpocząć nowy proces. Zwróć uwagę, iż nie sprawdzam wartości zwrotnej z CreateProcess, ponieważ skomplikowało by to nasz przykład. W rzeczywistości powinieneś to zrobić. Bezpośrednio za CreateProcess zamykamy uchwyt głównego wątku zwrócony w strukturze ProcessInfo. Zamknięcie uchwytu nie oznacza zakończenia wątku, a jedynie to, iż nie chcemy używać tego uchwytu do odwoływania się do wątku z naszego programu. Jeśli go nie zamkniemy, to spowodujemy wyciek zasobów.


    .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


Gdy użytkownik wybierze opcję menu Zakończ Proces, sprawdzamy, czy nowy proces jest wciąż aktywny wywołując funkcję GetExitCodeProcess. Jeśli tak, to wywołujemy funkcję TerminateProcess w celu zakończenia działania tego procesu. Również zamykamy uchwyt procesu potomnego, ponieważ nie jest nam on już do niczego potrzebny.

Dodatek w Pascalu

Ta sama aplikacja w Pascalu:

Uwaga: do katalogu projektowego przekopiuj z archiwum tut14.zip program msbox.exe. Plik zasobów zapisz pod nazwą RSRC.RC.

 

{********************************
**  I Liceum Ogólnokształcące  **
**           w Tarnowie        **
**       mgr Jerzy Wałaszek    **
********************************}

program Proces;

uses Windows;

const
  IDM_CREATE_PROCESS = 1;
  IDM_TERMINATE      = 2;
  IDM_EXIT           = 3;
  ClassName          = 'Win32ASMProcessClass';
  AppName            = 'Przykład Procesu w Asemblerze';
  MenuName           = 'FirstMenu';
  ProgramName        = 'msgbox.exe';

var
  ProcessInfo : PROCESS_INFORMATION;
  hInstance   : HINST;
  hMenu       : HANDLE;
  ExitCode    : longword;

function WndProc(hWnd:HWND;uMsg:UINT;wParam:WPARAM;lParam:LPARAM) : longint;
var
  startInfo : STARTUPINFO;
begin
  Result := 0;
  case uMsg of
    WM_DESTROY: PostQuitMessage(0);
    WM_INITMENUPOPUP:
    begin
      if GetExitCodeProcess(ProcessInfo.hProcess,ExitCode) then
      begin
        if ExitCode = STILL_ACTIVE then
        begin
         
          EnableMenuItem(hMenu,IDM_CREATE_PROCESS,MF_GRAYED);
          EnableMenuItem(hMenu,IDM_TERMINATE,MF_ENABLED);
        end
        else
        begin
          EnableMenuItem(hMenu,IDM_CREATE_PROCESS,MF_ENABLED);
          EnableMenuItem(hMenu,IDM_TERMINATE,MF_GRAYED);
         end
      end
      else
      begin
        EnableMenuItem(hMenu,IDM_CREATE_PROCESS,MF_ENABLED);
        EnableMenuItem(hMenu,IDM_TERMINATE,MF_GRAYED);
      end
    end;
    WM_COMMAND:
    if lParam = 0 then
      case (wParam and $ffff) of
        IDM_CREATE_PROCESS:
        begin
          if ProcessInfo.hProcess <> 0 then
          begin
            CloseHandle(ProcessInfo.hProcess);
            ProcessInfo.hProcess := 0;
          end;
          GetStartupInfo(@startInfo);
          CreateProcess(ProgramName,0,0,0,false,NORMAL_PRIORITY_CLASS,
                        0,0,startInfo,ProcessInfo);
          CloseHandle(ProcessInfo.hThread);
        end;
        IDM_TERMINATE:
        begin
          GetExitCodeProcess(ProcessInfo.hProcess,ExitCode);
          if ExitCode = STILL_ACTIVE then
            TerminateProcess(ProcessInfo.hProcess,0);
          CloseHandle(ProcessInfo.hProcess);
          ProcessInfo.hProcess := 0;
        end
        else DestroyWindow(hWnd);
      end;
    else Result := DefWindowProc(hWnd,uMsg,wParam,lParam);
  end;
end;

function WinMain(hInst,hPrevInst:HINST;CmdLine:LPSTR;CmdShow:longword)
         : longint;
var
  wc   : WNDCLASSEX;
  msg : MSG;
  hwnd: HWND;
begin
  with wc do
  begin
    cbSize        := sizeof(WNDCLASSEX);
    style         := CS_HREDRAW or CS_VREDRAW;
    lpfnWndProc   := @WndProc;
    cbClsExtra    := 0;
    cbWndExtra    := 0;
    hInstance     := hInst;
    hbrBackground := COLOR_WINDOW + 1;
    lpszMenuName  := MenuName;
    lpszClassName := ClassName;
    hIcon         := LoadIcon(0,IDI_APPLICATION);
    hIconSm       := hIcon;
    hCursor       := LoadCursor(0,IDC_ARROW);
  end;
  RegisterClassEx(wc);
  hwnd := CreateWindowEx(WS_EX_CLIENTEDGE,ClassName,AppName,
                         WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,
                         300,200,0,0,hInst,0);
  ShowWindow(hwnd,SW_SHOWNORMAL);
  UpdateWindow(hwnd);
  hMenu := GetMenu(hwnd);
  while GetMessage(msg,0,0,0) do
  begin
    TranslateMessage(msg);
    DispatchMessage(msg);
  end;
  Result := msg.wParam;
end;

begin
  hInstance := GetModuleHandle(0);
  ExitProcess(WinMain(hInstance,0,GetCommandLine,SW_SHOWDEFAULT));
end.

 

Autorem kursu jest Iczelion. Kurs programowania Windows znalazł się na serwerze I LO w Tarnowie za pisemną zgodą autora.
Tłumaczenie z języka angielskiego, opracowanie HTML i konwersję przykładów programów wykonał mgr Jerzy Wałaszek.



List do administratora Serwisu Edukacyjnego Nauczycieli I LO

Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:
Uwaga: ← tutaj wpisz wyraz  ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048

 

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień szeroko opisywanych w podręcznikach.



   I Liceum Ogólnokształcące   
im. Kazimierza Brodzińskiego
w Tarnowie

©2017 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.