Iczelion - 14 - Prozesse

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