| ||
In the previous chapter, you considered the possibilities of using timers in applied task. Having specified one or more timers, you force the system to call one or more procedures in an automatic mode. Thus, using timers, you can implement multitasking mode within a single process. Furthermore, it was shown that such subtasks could communicate with each other. This is natural, because they share the same address space; consequently, information can be passed through global variables from subtask to subtask. This problem is interesting and complicated. I cover it later in this book. For the moment, consider the multitasking environment in the Windows operating system from the beginning.
The process is the kernel object (see Chapter 13) created by the Windows operating system when loading an executable module. It has the following at its exclusive disposal:
Virtual memory allocated to it by the operating system.
Handles of the files opened by it.
The list of dynamic link libraries (DLLs) loaded by it into its own memory.
Subprocesses, known as threads, which it has created. Threads execute independently in the memory owned by the process.
I hope that this definition clearly reflects the idea of the process concept. However, for most problems considered in this chapter, an even simpler definition would be sufficient every executable module (EXE) started in the Windows environment becomes the process.
Now, consider subprocesses. This concept is also simple: Every process can generate other processes within the address space allocated to it. These processes execute independently of each other and of the process that generates them. However, the generating process, if needed, can forcibly terminate (kill) any process it has generated. Such processes are also called threads. Now, it is clear that in the previous chapter I used the timer to create an analogue of threads. However, in the Windows operating system, there are special tools for creating threads.
Now, it is necessary to describe the types of multitasking. In the old 16-bit Windows version, switching between tasks was done only when the task returned control to the operating system. This type of multitasking is called nonpreemptive. In a certain sense, this situation was even worse than that in MS-DOS. There, the elements of multitasking were implemented using so-called terminate-and-stay resident programs. Such programs intercepted interrupts from the timer, the keyboard, or any other device; from time to time, they could gain control in response to events related to those devices.
The situation that existed in the old Windows operating system required the programmer to observe a gentleman's agreement that consisted of avoiding capturing the processor for a long time. The use of timers provided a kind of solution for this problem (as you have seen already). Another solution was the use of the PeekMessage function instead of GetMessage . The PeekMessage function, in contrast to GetMessage , returns control immediately, even if there are no messages in the queue.
In 32-bit operating systems of the Windows family (Windows 9 x , Windows NT, Windows 2000, etc.), the preemptive multitasking is implemented. According to this method, switching between processes and threads is delegated to the operating system. If the process spends too much time on a certain operation, then the cursor over the process window will turn into an hourglass. In this case, other processes will continue to execute, and you'll be able to switch to them. However, access to the window of the current process can be difficult. This problem can be solved using the previously mentioned approachnamely, replacing the GetMessage function with the PeekMessage function is the waiting loop. However, dividing the process into several threads would be a better solution.
The next few sections will be dedicated to the creation of threads. As relates to the remaining part of this section, it will concentrate on creation of processes. Your application can create processes by starting a specific executable program. These processes will run independently of the main application. At the same time, your application can delete the application that it has started. The process can be created using the CreateProcess function. Here is a brief description of parameters accepted by this function:
First parameterThis specifies the name of the program being started. The name can contain the full path to the program.
Second parameterIts value depends on whether or not the first parameter is set to NULL . If the first parameter points to a string, then this parameter is interpreted as a startup command line (without the program name). If the first parameter is NULL , then this parameter is interpreted as a command line whose first element represents the program name. If the path to the program is not specified, then the CreateProcess function searches for the program according to a certain algorithm:
Search within the directory, from which the program was started.
Search within the current directory.
Search in the system directory (which can be obtained by calling the GetSystemDirectory function). As a rule, the system directory's name is C:\WINDOWS\SYSTEM or C:\WINDOWS\SYSTEM32.
Search in the Windows directory (the actual name can be obtained by calling the GetWindowsDirectory function). Usually, this is the C:\WINDOWS directory.
Search in directories listed by the PATH environment variable.
Third and fourth parametersThese parameters are used for specifying access attributes of the process being generated. Usually, they are set to zero.
Fifth parameterIf this parameter is zero, then the process being generated doesn't inherit descriptors of the generating process. If the parameter has a nonzero value, the process being generated inherits descriptors.
Sixth parameterThis parameter allows you to change the properties of the process being generated. If this parameter is set to zero, then the properties are specified by default. Because this parameter can take a range of values, I won't provide them here. You can find this information in appropriate manuals.
Seventh parameterThis is a pointer to the buffer that contains environment parameters. If this parameter is set to zero, then the process being generated inherits environment parameters of the generating process.
Eighth parameterSpecifies the current drive and directory for the process being generated. If this parameter is set to null , then the process being generated inherits the current drive and directory of the generating process.
Ninth parameterThis is a pointer to the structure that contains information about the window of the process being created. The fields of this structure will be considered later.
Tenth parameterThis points to the structure filled when the application is started for execution. Here is this structure:
PROCINF STRUC HProcess DD ? ; Descriptor of the created process HThread DD ? ; Descriptor of the primary thread ; of the new process Idproc DD ? ; Identifier of the new process IdThr DD ? ; Identifier of the primary thread of the ; new process PROCINF.ENDS
The main difference between the descriptor (handle) and the identifier lies in that the descriptor is unique only within the limits of a given process and the identifier is the global value. The data area of the current process can be found using an identifier. I assume that you immediately ask the following question: What is the difference between the application descriptor that I get using the GetModulHandle function and the values you have just mentioned? Well, the application descriptor and the handle that you obtain using the GetModulHandle function are the same value. An application descriptor or module descriptor is a local value, which means that it is in force within the limits of a given process. As a rule, it is equal to the address, by which the module is loaded into the virtual address space. Every module loaded into the memory has a module handle, including DLLs.
Now, consider the structure pointed to by the ninth parameter of the CreateProcess function. Here is the structure:
STARTUP STRUC cb DD 0 lpReserved DD 0 lpDesktop DD 0 lpTitle DD 0 dwX DD 0 dwY DD 0 dwXSize DD 0 dwYSize DD 0 dwXCountChars DD 0 dwYCountChars DD 0 dwFillAttribute DD 0 dwFlags DD 0 wShowWindow DW 0 cbReserved2 DW 0 lpReserved2 DD 0 hStdInput DD 0 hStdOutput DD 0 hStdError DD 0 STARTUP ENDS
The fields of this structure have the following meanings:
cb The size of this structure in bytes; a mandatory field that must be filled
lpReserved Reserved; must be zero
lpDesktop Desktop (and workstation) name; has meaning only for the operating systems from the Windows NT family
lpTitle Window title for console applications that create their own console; must be zero for all other applications
dwX X-coordinate of the top left window corner
dwY Y-coordinate of the top left window corner
dwXSize Window width
dwYSize Window height
dwXCountChars Size of the console buffer (by the X-coordinate)
dwYCountChars Size of the console buffer (by the Y-coordinate)
dwFillAttribute Initial color of the text; makes sense only for console applications
dwFlags Flag of the field values; possible values are listed in Table 15.1
Macro value of the flag | Constant value | Explanation |
---|---|---|
STARTF_USESHOWWINDOW | 1h | Enable the dwShowWindow field |
STARTF_USESIZE | 2h | Enable dwXSize and dwYSize |
STARTF_USEPOSITION | 4h | Enable dwx and dwY |
STARTF_USECOUNTCHARS | 8h | Enable dwXCountChars and dwYCountChars |
STARTF_USEFILLATTRIBUTE | 10h | Enable dwFillAttribute |
STARTF_FORCEONFEEDBACK | 40h | Specify that the cursor is in the feedback mode |
STARTF_FORCEOFFFEEDBACK | 80h | Specify that the cursor feedback mode is off |
STARTF_USESTDHANDLES | 100h | Enable kStdInput |
wShowWindow Defines the window display mode
cbReserved2 Reserved; must be zero
hStdInput Input descriptor (for the console)
hStdOutput Output descriptor (for the console)
hStdError Output descriptor for displaying an error message (for the console)
The example shown in listing 15.1 demonstrates the procedure of creating a process. The WINWORD.EXE text editor was chosen as a program that generates a process. To check whether this example operates correctly, you'll have to specify the path to the WINWORD.EXE module on your computer (the PATH variable). Notice that the application appears on screen in a minimized form, and notice how this is achieved. As you can see, the CreateProcess function is much simpler than it seems to be at first glance.
// The PROCESS.RC file // Constant definitions #define WS_SYSMENU 0x00080000L #define WS_POPUP 0x80000000L #define DS_3DLOOK 0x0004L // The menu resource MENUP MENU { POPUP "&Start Word" { MENUITEM "& Start", 1 MENUITEM "&Close", 2 MENUITEM "E&xit the program", 3 } } // Dialog box definition DIAL1 DIALOG 0, 0, 240, 120 STYLE WS_POPUP WS_SYSMENU DS_3DLOOK CAPTION "Example of starting the process" FONT 8, "Arial" { } ; The PROCESS.INC file ; Constants STARTF_USESHOWWINDOW equ 1h SW_SHOWMINIMIZED equ 2 ; This message arrives when the window is closed NM_CLOSE equ 10h NM_INITDIALOG equ 110h NM_COMMAND equ 111h ; Prototypes of external procedures FDEF MASM ; For MASM EXTERN TerminateProcess@8:NEAR EXTERN CreateProcessA@40:NEAR EXTERN DialogBoxParamA@ 20:NEAR EXTERN EndDialog@8:NEAR EXTERN MessageBoxA@16:NEAR EXTERN ExitProcess@4:NEAR EXTERN GetModuleHandleA@4:NEAR EXTERN LoadMenuA@8:NEAR EXTERN SetMenu@8:NEAR EXTERN TranslateMessage@4:NEAR ELSE ; For TASM EXTERN TerminateProcess: NEAR EXTERN CreateProcessA:NEAR EXTERN DialogBoxParamA:NEAR EXTERN EndDialog:NEAR EXTERN MessageBoxA:NEAR EXTERN ExitProcess:NEAR EXTERN-GetModuleHandleA:NEAR EXTERN LoadMenuA:NEAR EXTERN SetMenu:NEAR EXTERN TranslateMessage:NEAR TerminateProcess@8 = TerminateProcess CreateProcessA@40 = CreateProcessA DialogBoxParamA@20 = DialogBoxParamA EndDialog@8 = EndDialog MessageBoxA@16 = MessageBoxA ExitProcess@4 = ExitProcess GetModuleHandleA@4 = GetModuleHandleA LoadMenuA@8 = LoadMenuA SetMenu@8 = SetMenu TranslateMessage@4 = TranslateMessage ENDIF ; Structures ; Message structure MSGSTRUCT STRUC MSHWND DD ? MSMESSAGE DD ? MSWPARAM DD ? MSLPARAM DD ? MSTIME DD ? MSPT DD ? MSGSTRUCT ENDS ; The structure for CreateProcess STARTUP STRUC cb DD 0 lpReserved DD 0 lpDesktop DD 0 lpTitle DD 0 dwX DD 0 dwY DD 0 dwXSize DD 0 dwYSize DD 0 dwXCountChars DD 0 dwYCountChars DD 0 dwFillAttribute DD 0 dwFlags DD 0 wShowWindow DW 0 cbReserved2 DW 0 lpReserved2 DD 0 hStdInput DD 0 hStdOutput DD 0 hStdError DD 0 STARTUP ENDS ; Structure containing information about the process PROCINF STRUC HProcess DD ? HThread DD ? Idproc DD ? IdThr DD ? PROCINF ENDS ; The PROCESS.ASM file .586P ; Flat memory model .MODEL FLAT, stdcall include process.inc ; INCLUDELIB directives for the linker to link libraries IFDEF MASM ; MASM directives includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ELSE ; TASM directives includelib c:\tasm32\lib\import32.lib ENDIF ; Data segment _DATA SEGMENT NEWHWND DD 0 MSG MSGSTRUCT <?> STRUP STARTUP <?> INF PROCINF <?> HINST DD 0 ; Application handle PA DB "DIAL1", 0 PMENU DB "MENUP", 0 PATH DB "C:\Program Files\Office\WINWORD.EXE", 0 _DATA ENDS ; Code segment _TEXT SEGMENT START: ; Get application descriptor PUSH 0 CALL GetModuleHandleA@4 MOV [HINST], EAX ; Create a modal dialog PUSH 0 PUSH OFFSET WNDPROC PUSH 0 PUSH OFFSET PA PUSH [HINST] CALL DialogBoxParamA@20 PUSH 0 CALL ExitProcess@4 ;-------------------------------- ; Window procedure ; Position of parameters in the stack ; [EBP+014H] ; LPARAM ; [EBP+10H] ; WAPARAM ; [EBP+0CH] ; MES ; [EBP+8] ; HWND WNDPROC PROC PUSH EBP MOV EBP, ESP PUSH EBX PUSH ESI PUSH EDI ; This message arrives when the window is closed CMP DWORD PTR [EBP+0CH], WM_CLOSE JNE L1 ; Close the dialog JMP L5 L1: ; The message for window initialization CMP DWORD PTR [EBP+0CH], WM_INITDIALOG JNE L3 ; Load the menu PUSH OFFSET PMENU PUSH [HINST] CALL LoadMenuA@8 ; Set the menu PUSH EAX PUSH DWORD PTR [EBP+08H] CALL SetMenu@8 JMP FINISH ; Check whether some events have happened to the ; menu items in the dialog L3: CMP DWORD PTR [EBP+0CH], WM_COMMAND JNE FINISH CMP WORD PTR [EBP+10H], 3 JE L5 CMP WORD PTR [EBP+10H], 2 JE L7 CMP WORD PTR [EBP+10H], 1 JE L6 JMP FINISH ; Close the dialog L5: PUSH 0 PUSH DWORD PTR [EBP+08H] CALL EndDialog@8 JMP FINISH ; Start Word L6: ; Fill the startup structure ; The window must appear in a minimized form' MOV STRUP.cb, 68 MOV STRUP.lpReserved, 0 MOV STRUP.lpDesktop, 0 MOV STRUP.lpTitle, 0 MOV STRUP.dwFlags, STARTF_USESHOWWINDOW MOV STRUP.cbReserved2, 0 MOV STRUP.lpReserved2, 0 MOV STRUP.wShowWindow, SW_SHOWMINIMIZED ; Starting the Winword application PUSH OFFSET INF PUSH OFFSET STRUP PUSH 0 PUSH 0 PUSH 0 PUSH 0 PUSH 0 PUSH 0 PUSH OFFSET PATH PUSH 0 CALL CreateProcessA@40 JMP FINISH ; Remove the process from memory L7: PUSH 0 ; Exit code PUSH INF.hProcess CALL TerminateProcess@8 FINISH: MOV EAX, 0 POP EDI POP ESI POP EBX POP EBP RET 16 WNDPROC ENDP _TEXT ENDS END START
To translate the PROCESS.ASM program presented in Listing 15.1, issue the following commands for MASM32:
ML /c /coff /DMASM process.asm RC process.re LINK /SUBSYSTEM:WINDOWS process.obj proces.res
To translate the PROCESS.ASM program presented in Listing 15.1, issue the following commands for TASM32:
TASM32 /ml process.asm BRCC32 process.rc TLINK32 -aa process.obj,,,,,process.res
I think that the program presented in Listing 15.1 doesn't require any other comment. The window displayed by this program, with the menu, is shown in Fig. 15.1.
It is necessary to emphasize how the started process can be unloaded from the memory. Naturally, if you start a module written by someone else, you can hope that the author of that module provided a natural method of exiting that program. In this case, the author of the third-party module bears all the responsibility related to application termination. On the other hand, if you use the TerminateProcess function, as was shown in Listing 15.1, then the program may not necessarily terminate correctly. Naturally, the system releases all resources allocated to the process. However, it cannot carry out another component's job. For example, if that program was working with a file, then part of the data might be lost. When using the TerminateProcess function, the system doesn't send any notification to the application. As relates to the ExitProcess function that I widely use in this book, this is a correct way of terminating a process when writing a program in Assembly language. However, avoid using this function when writing programs in high-level languages. Your application might contain objects, and they will be incorrectly removed from the memory (without executing destructors).
| ||