| Windows Process & Threads Programming 3 | Main | Windows Process & Threads Programming 5 | Site Index | Download |


 

 

 

MODULE T3

PROCESSES AND THREADS: Win32/WINDOWS APIs

Part 4: Story And Program Examples

 

 

 

What do we have in this Module?

  1. Terminating a Thread

  2. Thread Security and Access Rights

  3. Child Processes

  4. Creating Processes

  5. Setting Window Properties Using STARTUPINFO

  6. Process Handles and Identifiers

  7. Process Enumeration

  8. Obtaining Additional Process Information

  9. Inheritance

  10. Inheriting Handles

  11. Inheriting Environment Variables

  12. Inheriting the Current Directory

  13. Environment Variables

  14. Terminating a Process

 

 

 

 

 

 

 

 

 

 

 

 

 

 

My Training Period: yy hours. Before you begin, read some instruction here.

 

 

 

The expected abilities:

  • Able to understand from zero ground about a process and thread implementation in Windows OSes.

  • Able to understand various terms used in the process and thread discussion.

  • Able to understand process scheduling and scheduler.

  • Able to use Windows Task Manager to view and control processes.

  • Able to understand Synchronization.

  • Able to understand various object used to synchronize multiple threads such as Mutex, Semaphore and Timer.

  • Able to understand and differentiate between multitasking and multithreading.

  • Able to understand various Inter-process communications among processes mechanisms such as Remote Procedure Call (RPC), Pipes and Windows Sockets.

Terminating a Thread

 

A thread executes until one of the following events occurs:

  1. The thread calls the ExitThread() function.

  2. Any thread of the process calls the ExitProcess() function.

  3. The thread function returns.

  4. Any thread calls the TerminateThread() function with a handle to the thread.

  5. Any thread calls the TerminateProcess() function with a handle to the process.

 

The GetExitCodeThread() function returns the termination status of a thread. While a thread is executing, its termination status is STILL_ACTIVE. When a thread terminates, its termination status changes from STILL_ACTIVE to the exit code of the thread. The exit code is either the value specified in the call to ExitThread(), ExitProcess(), TerminateThread(), or TerminateProcess(), or the value returned by the thread function.

When a thread terminates, the state of the thread object changes to signaled, releasing any other threads that had been waiting for the thread to terminate.  If a thread is terminated by ExitThread(), the system calls the entry-point function of each attached DLL with a value indicating that the thread is detaching from the DLL (unless you call the DisableThreadLibraryCalls() function). If a thread is terminated by ExitProcess(), the DLL entry-point functions are invoked once, to indicate that the process is detaching. DLLs are not notified when a thread is terminated by TerminateThread() or TerminateProcess().  The TerminateThread() and TerminateProcess() functions should be used only in extreme circumstances, since they do not allow threads to clean up, do not notify attached DLLs, and do not free the initial stack. The following steps provide a better solution:

 

  1. Create an event object using the CreateEvent() function.

  2. Create the threads.

  3. Each thread monitors the event state by calling the WaitForSingleObject() function. Use a wait time-out interval of zero.

  4. Each thread terminates its own execution when the event is set to the signaled state (WaitForSingleObject() returns WAIT_OBJECT_0).

 

Thread Security and Access Rights

 

Windows enables you to control access to thread objects.  You can specify a security descriptor for a thread when you call the CreateProcess(), CreateProcessAsUser(), CreateProcessWithLogonW(), CreateThread(), or CreateRemoteThread() function. If you specify NULL, the thread gets a default security descriptor. The ACLs in the default security descriptor for a thread come from the primary or impersonation token of the creator. To retrieve a thread's security descriptor, call the GetSecurityInfo() function. To change a thread's security descriptor, call the SetSecurityInfo() function. The handle returned by the CreateThread() function has THREAD_ALL_ACCESS access to the thread object. When you call the GetCurrentThread() function, the system returns a pseudohandle with the maximum access that the thread's security descriptor allows the caller. The valid access rights for thread objects include the DELETE, READ_CONTROL, SYNCHRONIZE, WRITE_DAC, and WRITE_OWNER standard access rights, in addition to the following thread-specific access rights.

 

Value

Meaning

SYNCHRONIZE

Enables the use of the thread handle in any of the wait functions.

THREAD_ALL_ACCESS

All possible access rights for a thread object.

THREAD_DIRECT_IMPERSONATION

Required for a server thread that impersonates a client.

THREAD_GET_CONTEXT

Required to read the context of a thread using GetThreadContext().

THREAD_IMPERSONATE

Required to use a thread's security information directly without calling it by using a communication mechanism that provides impersonation services.

THREAD_QUERY_INFORMATION

Required to read certain information from the thread object, such as the exit code, GetExitCodeThread().

THREAD_SET_CONTEXT

Required to write the context of a thread using SetThreadContext().

THREAD_SET_INFORMATION

Required to set certain information in the thread object.

THREAD_SET_THREAD_TOKEN

Required to set the impersonation token for a thread using SetTokenInformation().

THREAD_SUSPEND_RESUME

Required to suspend or resume a thread (for example SuspendThread() and ResumeThread()).

THREAD_TERMINATE

Required to terminate a thread using TerminateThread().

 

Table 9

 

You can request the ACCESS_SYSTEM_SECURITY access right to a thread object if you want to read or write the object's SACL.

 

Child Processes

 

A child process is a process that is created by another process, called the parent process.

 

Creating Processes

 

The CreateProcess() function creates a new process, which runs independently of the creating process. However, for simplicity, the relationship is referred to as a parent-child relationship. The following code fragment demonstrates how to create a process.

 

// For WinXp

#define _WIN32_WINNT 0x0501

#include <windows.h>

#include <stdio.h>

 

void main(void)

{

    STARTUPINFO si;

    PROCESS_INFORMATION pi;

    ZeroMemory(&si, sizeof(si));

    si.cb = sizeof(si);

    ZeroMemory(&pi, sizeof(pi));

    // Start the child process.

    if (!CreateProcess("C:\\WINDOWS\\system32\\cmd.exe", // module name.

        NULL,    // Command line.

        NULL,    // Process handle not inheritable.

        NULL,    // Thread handle not inheritable.

        FALSE,   // Set handle inheritance to FALSE.

        0,       // No creation flags.

        NULL,    // Use parent’s environment block.

        NULL,    // Use parent’s starting directory.

        &si,     // Pointer to STARTUPINFO structure.

        &pi)     // Pointer to PROCESS_INFORMATION structure.

    )

              printf("\nSorry! CreateProcess() failed.\n\n");

       else

              printf("\nWell, CreateProcess() looks OK.\n\n");

    // Wait until child process exits (in milliseconds). If INFINITE,

    // the function’s time-out interval never elapses except with user or other intervention.

    WaitForSingleObject(pi.hProcess, INFINITE);

    printf("\n");

    // Close process and thread handles.

    CloseHandle(pi.hProcess);

    CloseHandle(pi.hThread);

}

 

A sample output:

 

Creating a process: Program output

 

The following figure shows how to end a process using Task Manager.

 

Verifying a process through Task manager

 

Launching the cmd.exe. To terminate, type exit.

 

Creating a process: Program output

 

Creating a process: Program output

 

Figure

 

If CreateProcess() succeeds, it returns a PROCESS_INFORMATION structure containing handles and identifiers for the new process and its primary thread. The thread and process handles are created with full access rights, although access can be restricted if you specify security descriptors. When you no longer need these handles, close them by using the CloseHandle() function. You can also create a process using the CreateProcessAsUser() or CreateProcessWithLogonW() function. This allows you to specify the security context of the user account in which the process will execute.

 

Setting Window Properties Using STARTUPINFO

 

A parent process can specify properties associated with the main window of its child process. The CreateProcess() function takes a pointer to a STARTUPINFO structure as one of its parameters. Use the members of this structure to specify characteristics of the child process's main window. The dwFlags member contains a bit field that determines which other members of the structure are used. This allows you to specify values for any subset of the window properties. The system uses default values for the properties you do not specify. The dwFlags member can also force a feedback cursor to be displayed during the initialization of the new process. For GUI processes, the STARTUPINFO structure specifies the default values to be used the first time the new process calls the CreateWindow() and ShowWindow() functions to create and display an overlapped window. The following default values can be specified:

  1. The width and height, in pixels, of the window created by CreateWindow().
  2. The location, in screen coordinates of the window created by CreateWindow().
  3. The nCmdShow parameter of ShowWindow().

 

For console processes, use the STARTUPINFO structure to specify window properties only when creating a new console (either using CreateProcess() with CREATE_NEW_CONSOLE or with the AllocConsole() function). The STARTUPINFO structure can be used to specify the following console window properties:

  1. The size of the new console window, in character cells.

  2. The location of the new console window, in screen coordinates.

  3. The size, in character cells, of the new console's screen buffer.

  4. The text and background color attributes of the new console's screen buffer.

  5. The title of the new console's window.

 

Process Handles and Identifiers

 

When a new process is created by the CreateProcess() function, handles of the new process and its primary thread are returned. These handles are created with full access rights, and, subject to security access checking, can be used in any of the functions that accept thread or process handles. These handles can be inherited by child processes, depending on the inheritance flag specified when they are created. The handles are valid until closed, even after the process or thread they represent has been terminated. The CreateProcess() function also returns an identifier that uniquely identifies the process throughout the system. A process can use the GetCurrentProcessId() function to get its own process identifier (also known as the process ID or PID). The identifier is valid from the time the process is created until the process has been terminated. A process can use the Process32First() function to obtain the process identifier of its parent process. If you have a process identifier, you can get the process handle by calling the OpenProcess() function. OpenProcess() enables you to specify the handle's access rights and whether it can be inherited. A process can use the GetCurrentProcess() function to retrieve a pseudo handle to its own process object. This pseudo handle is valid only for the calling process; it cannot be inherited or duplicated for use by other processes. To get the real handle to the process, call the DuplicateHandle() function.

 

Process Enumeration

 

All users have read access to the list of processes in the system and there are a number of different functions that enumerate the active processes. The function you should use will depend on factors such as desired platform support.  The following functions are used to enumerate processes.

 

Function

Description

EnumProcesses()

Retrieves the process identifier for each process object in the system.

Process32First()

Retrieves information about the first process encountered in a system snapshot.

Process32Next()

Retrieves information about the next process recorded in a system snapshot.

WTSEnumerateProcesses()

Retrieves information about the active processes on the specified terminal server.

 

Table 10

 

The toolhelp() functions and EnumProcesses() enumerate all process. To list the processes that are running in a specific user account, use WTSEnumerateProcesses() and filter on the user SID. You can filter on the session ID to hide processes running in other terminal server sessions. You can also filter processes by user account, regardless of the enumeration function, by calling OpenProcess(), OpenProcessToken(), and GetTokenInformation() with TokenUser. However, you cannot open a process that is protected by a security descriptor unless you have been granted access.

 

Obtaining Additional Process Information

 

There are a variety of functions for obtaining information about processes. Some of these functions can be used only for the calling process, because they do not take a process handle as a parameter. You can use functions that take a process handle to obtain information about other processes.

  1. To obtain the command-line string for the current process, use the GetCommandLine() function.

  2. To parse a Unicode command-line string obtained from the Unicode version of GetCommandLine(), use the CommandLineToArgvW() function.

  3. To retrieve the STARTUPINFO structure specified when the current process was created, use the GetStartupInfo() function.

  4. To obtain the version information from the executable header, use the GetProcessVersion() function.

  5. To obtain the full path and file name for the executable file containing the process code, use the GetModuleFileName() function.

  6. To obtain the count of handles to graphical user interface (GUI) objects in use, use the GetGuiResources() function.

  7. To determine whether a process is being debugged, use the IsDebuggerPresent() function.

  8. To retrieve accounting information for all I/O operations performed by the process, use the GetProcessIoCounters() function.

 

Inheritance

 

A child process can inherit several properties and resources from its parent process. You can also prevent a child process from inheriting properties from its parent process. The following can be inherited:

  1. Open handles returned by the CreateFile() function. This includes handles to files, console input buffers, console screen buffers, named pipes, serial communication devices, and mailslots.

  2. Open handles to process, thread, mutex, event, semaphore, named-pipe, anonymous-pipe, and file-mapping objects. These are returned by the CreateProcess(), CreateThread(), CreateMutex(), CreateEvent(), CreateSemaphore(), CreateNamedPipe(), CreatePipe(), and CreateFileMapping() functions, respectively.

  3. Environment variables.

  4. The current directory.

  5. The console, unless the process is detached or a new console is created. A child console process can also inherits the parent's standard handles, as well as access to the input buffer and the active screen buffer.

 

The child process does not inherit the following:

  1. Priority class.

  2. Handles returned by LocalAlloc(), GlobalAlloc(), HeapCreate(), and HeapAlloc().

  3. Pseudo handles, as in the handles returned by the GetCurrentProcess() or GetCurrentThread() function.  These handles are valid only for the calling process.

  4. DLL module handles returned by the LoadLibrary() function.

  5. GDI or USER handles, such as HBITMAP or HMENU.

 

Inheriting Handles

 

A child process to inherit some of its parent's handles, but not inherit others. To cause a handle to be inherited, you must do two things:

  1. Specify that the handle is to be inherited when you create, open, or duplicate the handle. Creation functions typically use the bInheritHandle member of a SECURITY_ATTRIBUTES structure for this purpose. DuplicateHandle() uses the bInheritHandles parameter.

  2. Specify that inheritable handles are to be inherited by setting the bInheritHandles parameter to TRUE when calling the CreateProcess() function. Additionally, to inherit the standard input, standard output, and standard error handles, the dwFlags member of the STARTUPINFO structure must include STARTF_USERSTDHANDLES.

 

An inherited handle refers to the same object in the child process as it does in the parent process. It also has the same value and access privileges. Therefore, when one process changes the state of the object, the change affects both processes. To use a handle, the child process must retrieve the handle value and "know" the object to which it refers. Usually, the parent process communicates this information to the child process through its command line, environment block, or some form of interprocess communication. The DuplicateHandle() function is useful if a process has an inheritable open handle that you do not want to be inherited by the child process. In this case, use DuplicateHandle() to open a duplicate of the handle that cannot be inherited, then use the CloseHandle() function to close the inheritable handle. You can also use the DuplicateHandle() function to open an inheritable duplicate of a handle that cannot be inherited.

 

Inheriting Environment Variables

 

A child process inherits the environment variables of its parent process by default. However, CreateProcess() enables the parent process to specify a different block of environment variables.

 

Inheriting the Current Directory

 

The GetCurrentDirectory() function retrieves the current directory of the calling process. A child process inherits the current directory of its parent process by default. However, CreateProcess() enables the parent process to specify a different current directory for the child process. To change the current directory of the calling process, use the SetCurrentDirectory() function.

 

Environment Variables

 

Every process has an environment block that contains a set of environment variables and their values. The command processor provides the SET command to display its environment block or to create new environment variables as shown in the following figure.  For more information, type set /? at the command prompt.

 

set commmand: Viewing the environment variables

 

Programs started by the command processor inherit the command processor's environment variables. By default, a child process inherits the environment variables of its parent process. However, you can specify a different environment for the child process by creating a new environment block and passing a pointer to it as a parameter to the CreateProcess() function. The GetEnvironmentStrings() function returns a pointer to the environment block of the calling process. This should be treated as a read-only block; do not modify it directly. Instead, use the SetEnvironmentVariable() function to change an environment variable. When you are finished with the environment block obtained from GetEnvironmentStrings(), call the FreeEnvironmentStrings() function to free the block. Calling SetEnvironmentVariable() has no effect on the system environment variables. The user can add or modify system environment variables using the Control Panel. As an example, for Windows Xp Pro:

Control Panel Performance and Maintenance System Advanced tab click the Environment Variables button.

You can add new, edit existing and delete the entry for a user and/or system environment variables.

 

Environment variables

 

To programmatically add or modify system environment variables, add them to the:

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment

registry key then broadcast a WM_SETTINGCHANGE message. This allows applications, such as the shell, to pick up your updates. Note that environment variables listed in this key are limited to 1024 characters.

The GetEnvironmentVariable() function determines whether a specified variable is defined in the environment of the calling process, and, if so, what its value is.

 

Terminating a Process

 

A process executes until one of the following events occurs:

  1. Any thread of the process calls the ExitProcess() function. This terminates all threads of the process.

  2. The primary thread of the process returns. The primary thread can avoid terminating other threads by explicitly calling ExitThread() before it returns. One of the remaining threads can still call ExitProcess() to ensure that all threads are terminated.

  3. The last thread of the process terminates.

  4. Any thread calls the TerminateProcess() function with a handle to the process. This terminates all threads of the process, without allowing them to clean up or save data.

  5. For console processes, the default handler function calls ExitProcess() when the console receives a CTRL+C or CTRL+BREAK signal. All console processes attached to the console receive these signals. Detached processes and GUI processes are not affected by CTRL+C or CTRL+BREAK signals.

  6. The user shuts down the system or logs off. Use the SetProcessShutdownParameters() function to specify shutdown parameters, such as when a process should terminate relative to the other processes in the system. The GetProcessShutdownParameters() function retrieves the current shutdown priority of the process and other shutdown flags.

 

When a process is terminated, all threads of the process are terminated immediately with no chance to run additional code. This means that the process does not execute code in termination handler blocks. The GetExitCodeProcess() function returns the termination status of a process. While a process is executing, its termination status is STILL_ACTIVE. When a process terminates, its termination status changes from STILL_ACTIVE to the exit code of the process. The exit code is either the value specified in the call to ExitProcess() or TerminateProcess(), or the value returned by the main or WinMain() function of the process. If a process is terminated due to a fatal exception, the exit code is the value of the exception that caused the termination. In addition, this value is used as the exit code for all the threads that were executing when the exception occurred. When a process terminates, the state of the process object becomes signaled, releasing any threads that had been waiting for the process to terminate.

Open handles to files or other resources are closed automatically when a process terminates. However, the objects themselves exist until all open handles to them are closed. This means that an object remains valid after a process closes, if another process has a handle to it. If a process is terminated by ExitProcess(), the system calls the entry-point function of each attached DLL with a value indicating that the process is detaching from the DLL. DLLs are not notified when a process is terminated by TerminateProcess(). The execution of the ExitProcess(), ExitThread(), CreateThread(), CreateRemoteThread(), and CreateProcess() functions is serialized within an address space. The following restrictions apply:

  1. During process startup and DLL initialization routines, new threads can be created, but they do not begin execution until DLL initialization is finished for the process.

  2. Only one thread at a time can be in a DLL initialization or detach routine.

  3. The ExitProcess() function does not return until there are no threads are in their DLL initialization or detach routines.

 

The TerminateProcess() function should be used only in extreme circumstances, since it does not allow threads to clean up or save data and does not notify attached DLLs. If you need to have one process terminate another process, the following steps provide a better solution: Have both processes call the RegisterWindowMessage() function to create a private message.  One process can terminate the other process by broadcasting the private message using the BroadcastSystemMessage() function as follows:

BroadcastSystemMessage(

    BSF_IGNORECURRENTTASK, // do not send message to this process

    BSM_APPLICATIONS,                // broadcast only to applications

    PM_MYMSG,                                 // registered private message

    wParam,                                         // message-specific value

    lParam);                                         // message-specific value

 

The process receiving the private message calls ExitProcess() to terminate its execution. When the system is terminating a process, it does not terminate any child processes that the process has created.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Further reading and digging:

 

  1. Microsoft Visual C++, online MSDN.

  2. For Multibytes, Unicode characters and Localization please refer to Locale, wide characters & Unicode (Story) and Windows users & groups programming tutorials (Implementation).

  3. Structure, enum, union and typedef story can be found C/C++ struct, enum, union & typedef.

  4. Check the best selling C / C++ and Windows books at Amazon.com.

 

 

 

 

 

 

 

 | Windows Process & Threads Programming 3 | Main | Windows Process & Threads Programming 5 | Site Index | Download |