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