C++ Tutorial Multi-Threaded Programming II Native Thread for Win32 (C) (Creating Processes) - 2020
To create a new process, we need to call CreateProcess().
Syntax:
BOOL WINAPI CreateProcess( __in_opt LPCTSTR lpApplicationName, __inout_opt LPTSTR lpCommandLine, __in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes, __in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes, __in BOOL bInheritHandles, __in DWORD dwCreationFlags, __in_opt LPVOID lpEnvironment, __in_opt LPCTSTR lpCurrentDirectory, __in LPSTARTUPINFO lpStartupInfo, __out LPPROCESS_INFORMATION lpProcessInformation );
Parameters:
- lpApplicationName [in, optional]
The name of the module to be executed.
This module can be a Windows-based application.
It can be some other type of module (for example, MS-DOS or OS/2) if the appropriate subsystem is available on the local computer.
The string can specify the full path and file name of the module to execute or it can specify a partial name. In the case of a partial name, the function uses the current drive and current directory to complete the specification. The function will not use the search path. This parameter must include the file name extension; no default extension is assumed.
The lpApplicationName parameter can be NULL.
In that case, the module name must be the first white space delimited token in the lpCommandLine string.
- lpCommandLine [in, out, optional]
The command line to be executed. The maximum length of this string is 32,768 characters, including the Unicode terminating null character. If lpApplicationName is NULL, the module name portion of lpCommandLine is limited to MAX_PATH characters.
The lpCommandLine parameter can be NULL. In that case, the function uses the string pointed to by lpApplicationName as the command line.
If both lpApplicationName and lpCommandLine are non-NULL, the null-terminated string pointed to by lpApplicationName specifies the module to execute, and the null-terminated string pointed to by lpCommandLine specifies the command line. The new process can use GetCommandLine to retrieve the entire command line. Console processes written in C can use the argc and argv arguments to parse the command line. Because argv[0] is the module name, C programmers generally repeat the module name as the first token in the command line. - lpProcessAttributes [in, optional]
A pointer to a SECURITY_ATTRIBUTES structure that determines whether the returned handle to the new process object can be inherited by child processes. If lpProcessAttributes is NULL, the handle cannot be inherited.
- lpThreadAttributes [in, optional]
A pointer to a SECURITY_ATTRIBUTES structure that determines whether the returned handle to the new thread object can be inherited by child processes. If lpThreadAttributes is NULL, the handle cannot be inherited.
The lpSecurityDescriptor member of the structure specifies a security descriptor for the main thread. If lpThreadAttributes is NULL o rlpSecurityDescriptor is NULL, the thread gets a default security descriptor. The ACLs in the default security descriptor for a thread come from the process token. - bInheritHandles [in]
If this parameter TRUE, each inheritable handle in the calling process is inherited by the new process. If the parameter is FALSE, the handles are not inherited. Note that inherited handles have the same value and access rights as the original handles. - dwCreationFlags [in]
The flags that control the priority class and the creation of the process.
This parameter also controls the new process's priority class, which is used to determine the scheduling priorities of the process's threads.
If none of the priority class flags is specified, the priority class defaults to NORMAL_PRIORITY_CLASS unless the priority class of the creating process is IDLE_PRIORITY_CLASSBELOW_NORMAL_PRIORITY_CLASS. In this case, the child process receives the default priority class of the calling process. - lpEnvironment [in, optional]
A pointer to the environment block for the new process. If this parameter is NULL, the new process uses the environment of the calling process.
An environment block consists of a null-terminated block of null-terminated strings. Each string is in the following form:name=value\0
Because the equal sign is used as a separator, it must not be used in the name of an environment variable.
An environment block can contain either Unicode or ANSI characters.
If the environment block pointed to by lpEnvironment contains Unicode characters, be sure that dwCreationFlags includes CREATE_UNICODE_ENVIRONMENT.
If this parameter is NULL and the environment block of the parent process contains Unicode characters, you must also ensure that dwCreationFlags includes CREATE_UNICODE_ENVIRONMENT. - lpCurrentDirectory [in, optional]
The full path to the current directory for the process. The string can also specify a UNC path.
If this parameter is NULL, the new process will have the same current drive and directory as the calling process. (This feature is provided primarily for shells that need to start an application and specify its initial drive and working directory.) - lpStartupInfo [in]
A pointer to a STARTUPINFO or STARTUPINFOEX structure.
To set extended attributes, use a STARTUPINFOEX structure and specify EXTENDED_STARTUPINFO_PRESENT in the dwCreationFlags parameter. Handles in STARTUPINFO must be closed with CloseHandle when they are no longer needed. - lpProcessInformation [out]
A pointer to a PROCESS_INFORMATION structure that receives identification information about the new process.
Handles in PROCESS_INFORMATION must be closed with CloseHandle when they are no longer needed.
The following example shows the steps necessary to create a new process. If the application is started with a command-line parameter, the process will print this out and then create a child process without any parameters. If the process is started without any parameters, it prints out a message indicating this and exits.
#include <Windows.h> #include <stdio.h> int main(int argc, char* argv[]) { STARTUPINFO startupInfo; PROCESS_INFORMATION processInfo; if(argc > 1) { printf("Argument %s\n", argv[1]); printf("Starting child process\n"); memset(&startupInfo, 0, sizeof(startupInfo)); memset(&processInfo, 0, sizeof(processInfo)); startupInfo.cb = sizeof(startupInfo); if(CreateProcess(argv[0], 0, 0, 0, 0, 0, 0, 0, &startupInfo, &processInfo) == 0) { printf(" Error %d\n", GetLastError()); } WaitForSingleObject(processInfo.hProcess, INFINITE); } else { printf("No argument\n"); } getchar(); return 0; }
Suppose the executable of the above code is MyProc.exe. If we type "MyProc.exe myArgument" into command line, the output we get is:
Argument myArgument Starting child process No argument
The handle of the created process is returned in processInfo.hProcess. This handle is used to call WaitForSingleObject(). This call returns when the child process exits. The child process did not have any argument in the example, so the "No argument" was the output from the child process while the "Argument myArgument" was from the main process.
The following example is passing arguments to a child process. We need to repeat the application name as the first command-line parameter, "MyProc.exe myArgument". The entire command line gets passed to the child process.
#include <Windows.h> #include <stdio.h> int main(int argc, char* argv[]) { STARTUPINFO startupInfo; PROCESS_INFORMATION processInfo; if(argc == 1) { char Args[256]; sprintf_s(Args, "dummyArgs %s", argv[0]); memset(&startupInfo, 0, sizeof(startupInfo)); memset(&processInfo, 0, sizeof(processInfo)); startupInfo.cb = sizeof(startupInfo); if(CreateProcess(argv[0], Args, 0, 0, 0, 0, 0, 0, &startupInfo, &processInfo) == 0) { printf(" Error %d\n", GetLastError()); } WaitForSingleObject(processInfo.hProcess, INFINITE); } else { printf("Argument %s\n", argv[1]); } getchar(); return 0; }
Output is:
Argument myArgument
Processes can share memory between them. Once one process has set up a region of memory with sharing attributes, another can open that region of memory and map it into its address space.
The file mapping function CreateFileMapping() is used by the shared memory. The parameter INVALID_HANDLE_VALUE is for creating a handle to a region of shared memory. This can then be mapped into the process by calling MapViewOfFile(). Except the fact that the function OpenFileMapping() is used to obtain the handle, the steps of attaching to an existing region of shared memory are similar.
An object can be shared between processes either by sharing the object's handle or by using a common name. The name can contain any character except a backslash and must start with either the local namespace identifier Local\ or the global namespace identifier Global\. The local namespace is private to each user while the global namespace is shared by all users.
The OpenFileMapping() call which opens an existing file mapping object takes three parameters:
HANDLE WINAPI OpenFileMapping( __in DWORD dwDesiredAccess, __in BOOL bInheritHandle, __in LPCTSTR lpName );
- The first parameter gives the security attributes for the mapping object, which usually will be FILE_MAP_ALL_ACCESS to allow both reading and writing to the share memory.
- The second parameter is a boolean that determines whether the handle can be inherited by child processes.
- The third parameter is the name of the mapping object.
The CreateFileMapping() call creates the mapping object in the kernel, however, it does not actually map the object into user space. The call to MapViewOfFile() causes the shared object to be mapped into memory. The return value of this call is a pointer to the base address of the memory. This call takes file parameters:
LPVOID WINAPI MapViewOfFile( __in HANDLE hFileMappingObject, __in DWORD dwDesiredAccess, __in DWORD dwFileOffsetHigh, __in DWORD dwFileOffsetLow, __in SIZE_T dwNumberOfBytesToMap );
Parameters:
- hFileMappingObject [in]
A handle to a file mapping object. The CreateFileMapping and OpenFileMapping functions return this handle. - dwDesiredAccess [in]
The type of access to a file mapping object, which determines the protection of the pages. This parameter can be one of the following values. - dwFileOffsetHigh [in]
A high-order DWORD of the file offset where the view begins. - dwFileOffsetLow [in]
A low-order DWORD of the file offset where the view is to begin. The combination of the high and low offsets must specify an offset within the file mapping. They must also match the memory allocation granularity of the system. That is, the offset must be a multiple of the allocation granularity. To obtain the memory allocation granularity of the system, use the GetSystemInfo function, which fills in the members of a SYSTEM_INFO structure. - dwNumberOfBytesToMap [in]
The number of bytes of a file mapping to map to the view. All bytes must be within the maximum size specified by CreateFileMapping . If this parameter is 0 (zero), the mapping extends from the specified offset to the end of the file mapping.
Once the process has finished with the shared memory, it needs to be unmapped by calling UnMapViewOfFile(), which takes the base address of the shared memory as a parameter:
BOOL WINAPI UnmapViewOfFile( __in LPCVOID lpBaseAddress );
Then, the handle can be closed by calling CloseHandle().
The following example demonstrates how a region of memory can be created and then shared between two processes. If the application is started without any parameters, it will create a child process. The parent process will also create a region of shared memory and store a string into the shared memory. The shared memory is given the name sharedmemory:
char ID[] = TEXT("Local\\sharedmemory");
It is created in the Local\ namespace. So, it is visible to all the processes owned by the user.
The child process attaches to the shared memory and can print out the value of the string stored there by the parent process. Once the child process has printed the string, it unmaps the memory and closes the file handle before exiting. Once the child process has exited, the parent process is free to unmap the memory, close the file handle, and then exit.
#include <Windows.h> #include <stdio.h> int main(int argc, char* argv[]) { STARTUPINFO startupInfo; PROCESS_INFORMATION processInfo; HANDLE fileHandle; char ID[] = TEXT("Local\\sharedmemory"); char *memory; if(argc == 1) { fileHandle = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 1024, ID); memory = (char*)MapViewOfFile(fileHandle, FILE_MAP_ALL_ACCESS, 0, 0, 0); sprintf_s(memory, 1024, "%s", "Data from first process"); printf("First process: %s\n", memory); memset(&startupInfo, 0, sizeof(startupInfo)); memset(&processInfo, 0, sizeof(processInfo)); startupInfo.cb = sizeof(startupInfo); char Args[256]; sprintf_s(Args, "dummyArgs %s", argv[0]); CreateProcess(argv[0], Args, 0, 0, 0, 0, 0, 0, &startupInfo, &processInfo); WaitForSingleObject(processInfo.hProcess, INFINITE); UnmapViewOfFile(memory); CloseHandle(fileHandle); } else { fileHandle = OpenFileMapping(FILE_MAP_ALL_ACCESS, 0, ID); memory = (char*)MapViewOfFile(fileHandle, FILE_MAP_ALL_ACCESS, 0, 0, 0); printf("Second process %s\n", memory); UnmapViewOfFile(memory); CloseHandle(fileHandle); } getchar(); return 0; }
The output we get:
First process: Data from first process Second process Data from first process
To share a mutex between processes, we need to create mutex with a name. Then, other process can use the OpenMutex() or CreateMutex() to get a handle to the mutex. Here are the complexities involved in this:
- Only one process can create the mutex, and the others just open the existing mutex.
- The name should be unique.
- The name of the mutex should be passed to the other processes.
In the example below, the mutex is created by CteateMutex(). Here, we create two copies of the same processes and enables them to share a mutex.
#include <Windows.h> #include <stdio.h> int main(int argc, char* argv[]) { STARTUPINFO startupInfo; PROCESS_INFORMATION processInfo; HANDLE sharedmutex; ZeroMemory(&startupInfo,sizeof(startupInfo)); ZeroMemory(&processInfo,sizeof(processInfo)); startupInfo.cb = sizeof(startupInfo); sharedmutex = CreateMutex(0,0,"MyMutex"); if(GetLastError() != ERROR_ALREADY_EXISTS) { if(CreateProcess(argv[0],0,0,0,0,0,0,0, &startupInfo, &processInfo) == 0) { printf("ERROR %d : Mutex already exists\n", GetLastError()); } WaitForInputIdle(processInfo.hProcess, INFINITE); } WaitForSingleObject(processInfo.hProcess, INFINITE); for(int i = 0; i <20; i++) { printf("Process %d Count %d\n", GetCurrentProcessId(),i); } ReleaseMutex(sharedmutex); CloseHandle(sharedmutex); getchar(); return 0; }
Output is;
ERROR 2 : Mutex already exists Process 6200 Count 0 Process 6200 Count 1 Process 6200 Count 2 Process 6200 Count 3 Process 6200 Count 4 Process 6200 Count 5 Process 6200 Count 6 Process 6200 Count 7 Process 6200 Count 8 Process 6200 Count 9 Process 6200 Count 10 Process 6200 Count 11 Process 6200 Count 12 Process 6200 Count 13 Process 6200 Count 14 Process 6200 Count 15 Process 6200 Count 16 Process 6200 Count 17 Process 6200 Count 18 Process 6200 Count 19
If the mutex already exists, then a handle to the existing mutex is returned, and the error condition is set to ERROR_ALREADY_EXISTS. If this is not the error condition, then it is assumed by the code that this means that the mutex was created by the calling process, and the calling process needs to start a second copy. Note that the call to CreateMutex() takes the name of the mutex. The shared mutex is used to ensure that only one of the two processes counts up to 20 at a time. If there was no mutex, then both processes could be active simultaneously, and the console output would be a mix of the output from both processes. By using the mutex, the output is from just one of the processes at a time.
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization