Tutorial 14: Prozesse
Wir werden lernen was ein Prozess ist und wie man ihn erzeugt und wieder terminiert.
Laden Sie hier das Beispiel herunter.
Einleitung:
Was ist ein Prozess? Ich zitiere die Definition aus der Win32 API Referenz:
"Ein Prozess ist eine ausführbare Applikation die aus einem privaten virtuellen Adressraum, Code, Daten und anderen Betriebssystem Ressourcen besteht, so wie Dateien, Pipes und Synchronisationobjekten, die der Prozess sehen kann."
Wie Sie anhand der obigen Definition sehen können, "besitzt" ein Prozess verschiedene Objekte: den Adressraum, das auszuführende Modul(e) und alles was das ausführende Modul erzeugt oder öffnet. Ein Prozess besteht mindestens aus einem ausführbaren Modul, einem privaten Adressbereich und einem Thread. Jeder Prozess muss mindestens einen Thread haben. Was ist ein Thread? Ein Thread ist eigentlich eine Befehls-Warteschlange. Wenn Windows einen Prozess erzeugt, erzeugt es nur einen Thread pro Prozess. Dieser Thread startet seine Ausführung normalerweise vom ersten Befehl des Moduls an. Wenn der Prozess später mehrere Threads braucht, kann er sie explizit erstellen.
Wenn Windows ein Befehl zum erzeugen eines Prozesses bekommt, erzeugt es den privaten Adressraum für den Prozess und dann mappt (kopiert) er die ausführbare Datei in diesen Raum. Danach erzeugt es den Haupt-Thread für den Prozess.
Unter Win32 können Sie auch Prozesse erzeugen, indem Sie ihr Programm die Funktion CreateProcess aufrufen lassen. CreateProcess hat folgende Syntax:
CreateProcess proto lpApplicationName:DWORD,\ 
                                 lpCommandLine:DWORD,\
                                 lpProcessAttributes:DWORD,\ 
                                 lpThreadAttributes:DWORD,\ 
                                 bInheritHandles:DWORD,\ 
                                 dwCreationFlags:DWORD,\ 
                                 lpEnvironment:DWORD,\ 
                                 lpCurrentDirectory:DWORD,\ 
                                 lpStartupInfo:DWORD,\ 
                                 lpProcessInformation:DWORD 
Seien Sie nicht verunsichert bei der Anzahl der Parameter. Die meisten davon können wir ignorieren.
lpApplicationName -- Der Name der ausführbaren Datei, mit oder ohne Pfad, die Sie ausführen wollen. Wenn der Parameter null ist, müssen Sie den Namen der ausführbaren Datei im lpCommandLine Parameter übergeben.
lpCommandLine   -- Die Kommandozeile des Programms, das Sie ausführen wollen. Beachten Sie, dass wenn lpApplicationName NULL ist, dieser Parameter den Namen des ausführbaren Datei ebenfalls enthalten muss. So wie hier: "notepad.exe readme.txt"
lpProcessAttributes und lpthreadAttributes -- Spezifiziert die Sicherheits-Attribute für den Prozess und den Haupt-Thread. Wenn sie NULL sind, werden die Standard-Sicherheits-Attribute benutzt.
bInheritHandles -- Ein Flag, das spezifiziert, ob Sie dem neuen Prozess alle offenen Handles ihres Prozesses vererben wollen. 
dwCreationFlags -- Verschiedene Flags die das Verhalten des Prozesses bestimmen, den Sie erzeugen, wie zum Beispiel, ob der Prozess sofort nach dem erzeugen angehalten werden soll, so das Sie ihn unter die Lupe nehmen können oder modifizieren können, bevor er läuft. Sie können auch die Prioritäten-Klasse des/der Threads spezifizieren. Diese Prioritäteten-Klasse wird benutzt um die Priorität eines Threads in einem Prozess zu bestimmen. Normalerweise benutzen wir das NORMAL_PRIORITY_CLASS Flag.
lpEnvironment -- Ein Zeiger auf den Umgebungs-Block, der verschieden Umgebungs-Strings für den neuen Prozess enthält. Wenn dieser Parameter NULL ist, erbt der neue Prozess den Umgebungs-Block vom Eltern-Process.
lpCurrentDirectory -- Ein Zeiger auf den String, der das aktuelle Laufwerk und Verzeichnis des Child-Prozesses enthält. NULL, wenn Sie den Child-Prozess vom Eltern-Prozess erben lassen wollen.
lpStartupInfo -- Zeigt auf eine STARTUPINFO-Struktur die spezifziert wie das Hauptfenster des neuen Prozesses erscheinen soll. Die STARTUPINFO-Struktur enthält viele Elemente, die das Aussehen der Hauptfensters des Child-Prozesses spezifizieren. Wenn Sie nichts spezielles wollen, können Sie die STARTUPINFO-Struktur mit den Werten des Eltern-Prozesses füllen, indem Sie die GetStartupInfo-Funktion aufrufen.
lpProcessInformation -- Zeigt auf eine PROCESS_INFORMATION Struktur, die die Identifizierungs-Informationen über den neuen Prozess erhält. Die PROCESS_INFORMATION-Struktur hat folgende Elemente:
PROCESS_INFORMATION STRUCT 
    hProcess          HANDLE ?             ; Handle des Child-Prozesses 
    hThread            HANDLE ?             ; Handle des Hauptthreads des Child-Prozesses 
    dwProcessId     DWORD ?             ; ID des Child-Prozesses 
    dwThreadId      DWORD ?            ; ID des Hauptthreads des Child-Prozesses 
PROCESS_INFORMATION ENDS
Prozess Handle Und Process ID sind zwei verschiedene Dinge. Eine Prozess ID ist ein einmaliger Identifizierer des Prozesses im System. Ein Prozess Handle ist ein Wert, der von Windows zurückgegeben wird, um ihn mit anderen Prozess-verwandten API-Funktionen zu benutzen. Ein Prozess-Handle kann nicht benutzt werden, einen Prozess zu identifizieren, da er nicht einmalig ist.
Nachdem CreateProcess aufgerufen wurde, wird ein neuer Prozess erzeugt und CreateProcess kehrt sofort zurück. Sie können überprüfen, ob ein neuer Prozess noch immer aktiv ist, in dem Sie die GetExitCodeProcess-Funktion aufrufen, welche folgende Syntax hat:
GetExitCodeProcess proto hProcess:DWORD, lpExitCode:DWORD
Wenn dieser Aufruf erfolgreich ist, enthält lpExitCode den Terminierungs-Status des Prozesses. Wenn der Wert in lpExitCode gleich STILL_ACTIVE ist, dann läuft der Prozess noch.
Sie können eine Terminierung des Prozesses erzwingen, indem Sie die TerminateProcess-Funktion aufrufen. Sie hat folgende Syntax:
TerminateProcess proto hProcess:DWORD, uExitCode:DWORD
Sie können jeden gewünschten Wert als Rückgabewert nehmen. TerminateProcess ist keine saubere Methode einen Prozess zu terminieren, da jede DLL, die von dem Prozess benutzt wurde, die Terminierung des Prozesses nicht bemerken wird.
Beispiel:
Das folgende Beispiel wird einen neuen Prozess erzeugen, wenn der Benutzer das "create process" Menü-Element auswählt. Es wir versuchen "msgbox.exe" auszuführen. Wenn der Benutzer den neuen Prozess terminieren möchte, kann er das Menü-Element "terminate process" auswählen. Das Programm wird dann zuerst überprüfen, ob der neue Prozess schon zerstört wurde, wenn er es noch nicht wurde, ruft das Programm die TerminateProcess-Funktion auf, um den neuen Prozess zu zerstören.
.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 ?                    ; enthält den Prozess Rückgabewert Status des GetExitCodeProcess Aufrufs. 
.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 
Analyse:
Das Programm erzeugt das Haupt-Fenster und ermittelt die Menü-Handle für den späteren Gebrauch. Dann wartet es auf die Auswahl eines Befehls aus dem Menü durch den Benutzer. Wenn der Benutzer das "Process"-Menüelement in dem Hauptfenster auswählt, bearbeiten wir die WM_INITMENUPOPUP-Nachricht, um die Menüelemente in dem Popup-Menü zu modifizieren, bevor es angezeigt wird.
    .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 
Warum wollen wir diese Nachricht bearbeiten? Weil wir die Menüelemente in dem Pop-Menü vorbereiten wollen, bevor der Benutzer sie sehen kann. In unsrem Beispiel wollen wir, wenn der neue Prozess noch nicht gestartet ist, die Menü-Elemente"start process" aktivieren und "terminate process" ausgrauen. Wenn der neue Prozess schon aktiv ist, machen wir das Selbe anders herum.
Als erstes prüfen wir, ob der neue Prozess immer noch läuft, indem wir die GetExitCodeProcess-Funktion mit dem Prozess-Handle aufrufen, welches von der CreateProcess-Funktion übergeben wurde. Wenn GetExitCodeProcess TRUE zurückliefert, wissen wir, dass der neue Prozess schon gestartet wurde, aber wir müssen noch überprüfen, ob er immer noch läuft. Deswegen vergleichen wir den Wert in ExitCode mit dem Wert STILL_ACTIVE, wenn sie gleich sind, läuft der Prozess noch: wir müssen also das Menü-Element "start process" ausgrauen, da wir nicht noch einmal den selben Prozess starten wollen.
            .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 
Wenn der Benutzer das Menü-Element "start process" auswählt, überprüfen wir als erstes, ob das hProcess Element der PROCESS_INFORMATION-Struktur schon geschlossen ist. Das erste Mal wird der Wert von hProcess immer Null sein, da wir die PROCESS_INFORMATION-Struktur in der .data Sektion definieren. Wenn der Wert des hProcess Elements nicht 0 ist, bedeutet das, dass der Child-Prozess beendet wurde aber wir das Prozess-Handle noch nicht geschlossen haben. Deswegen ist es Zeit, dies zu tun.
Wir rufen die GetStartupInfo-Funktion auf, um die Startupinfo-Struktur zu füllen, die wir der CreateProcess-Funktion übergeben. Danach rufen wir die CreatProcess-Funktion auf, um den neuen Prozess zu starten. Beachten Sie, dass ich den Rückgabewert von CreateProcess nicht überprüft habe, da es das Beispiel komplizierter machen würde. Normalerweise sollten Sie den Rückgabewert von CreateProcess überprüfen. Unmittelbar nach CreateProcess, schließen wir den ersten Thread-Handle der in der processInfo-Struktur zurückgegeben wird. Das Schließen des Handles bedeutet nicht, dass wir den Thread terminieren, nur das wir das Handle nicht benutzen wollen, um den Thread aus unserem Programm anzusprechen. Wenn wir es nicht schließen, würde es ein Ressource-Loch verursachen.
            .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 
Wenn der Benutzer das Menü-Element "terminate process" auswählt, überprüfen wir, ob der neue Prozess immer noch aktiv ist, indem wir die GetExitCodeProcess-Funktion aufrufen. Wenn es immer noch aktiv ist, rufen wir die TerminateProcess-Funktion auf, um den Prozess zu killen. Auch schließen wir die Child-Prozess-Handle, da wir für sie keinen Nutzen mehr haben.
Deutsche Übersetzung: Joachim Rohde
Die original Win32Asm-Tutorials stammen von Iczelion's Win32 Assembly HomePage