win32 Threads

A thread is fairly simple: it is created, it executes code, and then it exits.

Threads are created as a result of another thread calling the CreateThread() function, unless the thread is the primary thread for the process.

Usually, the primary thread for the process starts immediately after the thread has been created. If the process is created by calling the CreateProcess() function, however, the primary thread can be created in the suspended state by specifying the CREATE_SUSPENDED flag.

The various thread creation functions take a pointer to a function as a place to begin executing the thread; this function is known as the thread entry procedure. The function pointer must be a pointer to a function of type THREAD_START_ROUTINE.

CreateThread() is the basic win32 thread creation function. This function creates a new thread within the process of the caller. The new thread has its own stack, its own copy of the machine’s registers, and will be scheduled independently by the OS.

The CreateThread() function takes six parameters. The first parameter can be specified as NULL; it is supposed to be a pointer to SECURITY_ATTRIBUTES structure, which describes the security and inheritance properties of the new thread. Putting NULL here will cause the thread to be created with default security and the thread handle will not be inheritable by child processes of the current process.

The second parameter to CreateThread() is a DWORD value indicating the size of the new thread’s stack. The system will automatically round this value to the nearest page. If the parameter is 0, it will be the default size for the process. The third parameter is the address of the user function that the new thread will call when it begins executing. The fourth argument is a pointer to a parameter value to be passed into the function. The fifth parameter is a DWORD value representing creation flags for the thread. The final parameter is a pointer to a DWORD variable that receives the thread ID.

#include "stdafx.h"
#include <Windows.h>

DWORD WINAPI threadCreate(LPVOID threadParam);
DWORD WINAPI threadCreateWithParam(LPVOID threadParam);

int _tmain(int argc, _TCHAR* argv[])
{
    DWORD dwThreadIDOne, dwThreadIDTwo;
    int iValue = 73;
    int iCounter = 0;

    CreateThread(NULL, NULL, &threadCreate, NULL, 0, &dwThreadIDOne);
    printf("Just created thread %d\n", dwThreadIDOne);

    Sleep(2000);

    CreateThread(NULL, NULL, &threadCreateWithParam, &iValue, 0, &dwThreadIDTwo);
    printf("Just created thread %d\n", dwThreadIDTwo);

    for (iCounter; iCounter < 4; iCounter++){
        printf("In main thread.\n");
        Sleep(500);
    }

    Sleep(3000);

    return 0;
}


DWORD WINAPI threadCreate(LPVOID threadParam){
    int i = 10;
    while (i-- > 0){
        printf("In the new thread.\n");
        Sleep(500);
    }

    //oddly named ERROR_SUCCESS
    //indicates everything is copacetic 
    return ERROR_SUCCESS;
}

DWORD WINAPI threadCreateWithParam(LPVOID threadParam){
    int *iParam = (int*)threadParam;
    int i = 5;
    while (i-- > 0){
        printf("In the thread that received %d.\n", *iParam);
        Sleep(500);
    }

    //aka return 0
    return ERROR_SUCCESS;
}

When threads are initially created, they are unaware of their identifier. From within the thread, we can acquire the unique numeric value assigned to the thread when it is created by calling the GetCurrentThreadId() function.

#include "stdafx.h"
#include <Windows.h>

DWORD WINAPI makeThread(LPVOID lpThreadParam);

int _tmain(int argc, _TCHAR* argv[])
{
    DWORD dwThreadID;

    CreateThread(NULL, NULL, &makeThread, NULL, NULL, &dwThreadID);

    printf("Created thread %d from main thread %d.\n", dwThreadID, GetCurrentThreadId());

    Sleep(1000);
    
    return 0;
}


DWORD WINAPI makeThread(LPVOID lpThreadParam){
    printf("In thread %d.\n", GetCurrentThreadId());

    return ERROR_SUCCESS;
}

We can also acquire a HANDLE to the current thread by calling the GetCurrentThread() function. Note that this HANDLE is actually a pseudo-handle.

Advertisements

Introduction to Volume Management with the win32 API

The GetLogicalDrives() function returns a DWORD bitmask that represents the currently available disk drives. If the function fails, the return value is zero.

#include "stdafx.h"
#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
	int iCounter = 0;
	int iASCIILetter = (int)'a';

	DWORD dwDrivesMask = GetLogicalDrives();

	if (dwDrivesMask == 0){
		printf("Failed to acquire mask of drives.\n");
		exit(EXIT_FAILURE);
	} 

	printf("Drives available:\n");
        //extract the drives from the mask using 
        //logical and-ing with & and bitshift operator <<
	while (iCounter < 24){
		if (dwDrivesMask & (1 << iCounter)){
			printf("%c:\\ \n", iASCIILetter + iCounter);
		}
		iCounter++;
	}

	return 0;
}

The GetLogicalDriveStrings() function returns the valid drives in a system by filling a buffer with a series of null-terminated strings, one for each drive on the system, plus an additional null character at the end of the list. The GetLogicalDriveStrings() function takes two arguments, the first is the length of the buffer in TCHARS, and the second is a pointer to the buffer. Note that the size does not include the final, terminating null character.

#include "stdafx.h"
#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
    const int buffer_size = 512;
    TCHAR buffer[buffer_size];
    //we will use the pointer 
    //to iterate through the buffer
    TCHAR *tchPtr = buffer;

    if (GetLogicalDriveStrings(buffer_size - 1, buffer) == 0){
        printf("Unable to get drive strings.\n");
        exit(EXIT_FAILURE);
    }

    //go through buffer until we reach
    //the final double null char that indicates
    //end of the info
    while (*tchPtr != ''){
        printf("%s:\n", tchPtr);
        //_tcslen is the equiv. of strlen
        //does not include the null char, so we 
        //must add one
        tchPtr += _tcslen(tchPtr) + 1;
    }

    return 0;
}

The GetDriveType() function determines whether a disk drive is removable, fixed, CD-ROM, etc. The GetDriveType() function takes a single parameter, a string indicating the root directory for the drive; note that a trailing backslash is required. If the parameter is NULL, then the function uses the root of the current directory.

#include "stdafx.h"
#include <Windows.h>

void DisplayDriveType(int iParam);

int _tmain(int argc, _TCHAR* argv[])
{
    //null param (this drive)
    DisplayDriveType(GetDriveType(NULL));

    //nonexistant drive
    DisplayDriveType(GetDriveType(_T("x:\\")));

    //for all drives
    TCHAR szBuffer[256];
    TCHAR *tchPtr = szBuffer;
    GetLogicalDriveStrings(255, szBuffer);

    //print types for all drives on system
    for (tchPtr; *tchPtr != ''; tchPtr += _tcslen(tchPtr) +1)
    {
        printf("%s:\\ \t", tchPtr);
        DisplayDriveType(GetDriveType(tchPtr));
        printf("\n");
    }

    return 0;
}

void DisplayDriveType(int iParam){
    switch (iParam){
    case DRIVE_UNKNOWN:
        printf("Drive type unknown.\n");
        break;

    case DRIVE_NO_ROOT_DIR:
        printf("No drive for that root path.\n");
        break;

    case DRIVE_REMOVABLE:
        printf("Removable drive.\n");
        break;

    case DRIVE_FIXED:
        printf("Fixed drive.\n");
        break;

    case DRIVE_REMOTE:
        printf("Network drive.\n");
        break;

    case DRIVE_CDROM:
        printf("CD ROM drive.\n");
        break;

    }
}

The GetVolumeInformation() function retrieves file system metadata on a given volume. The GetVolumeInformation() function takes eight parameters. The first parameter is a string that contains the root directory of the volume for which we want information. The second argument is a pointer to a buffer that stores the name of the specified volume. The third argument is the length of the volume name buffer in TCHARS; the maximum buffer size is MAX_PATH + 1. The fourth argument is a pointer to a DWORD variable that receives the volume serial number. The fifth argument is a pointer to a DWORD variable that receives the maximum length, in TCHARs, of a file name component, where a file name component is a section of a filename between backslashes.

#include "stdafx.h"
#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
    TCHAR tchVolumeNameBuff[MAX_PATH + 1];
    DWORD dwSerialNumber, dwMaxCompLength;

    GetVolumeInformation(_T("C:\\"), tchVolumeNameBuff, MAX_PATH + 1, &dwSerialNumber, &dwMaxCompLength, NULL, NULL, NULL);

    printf("Name: %s Serial Number: %d\n", tchVolumeNameBuff, dwSerialNumber);

    return 0;
}

Diving into File Handling with win32

I’d like to review creating and managing files using win32, and maybe go a bit more in depth as well.

The second to last parameter for CreateFile() specifies a range of attributes for the file in the form of flags. These flags are logically organized into three groups; we will cover the first two groups here. The first group represents a set of file attributes. FILE_ATTRIBUTE_HIDDEN keeps the file from being included in an ordinary directory listing. FILE_ATTRIBUTE_ENCRYPTED will cause the file or directory to be encrypted; in the case of a directory, this means that all newly created files and subdirectories will be, by default, encrypted. FILE_ATTRIBUTE_NORMAL is the default; note that this flag is only valid when used by itself. FILE_ATTRIBUTE_READONLY makes the file available for reading, but not writing or deletion. FILE_ATTRIBUTE_TEMPORARY means that the file is being used for temporary storage.

#include "stdafx.h"
#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
    
    printf("FILE_ATTRIBUTE_READONLY \t %d 0x%x\n", FILE_ATTRIBUTE_READONLY, FILE_ATTRIBUTE_READONLY);
    printf("FILE_ATTRIBUTE_HIDDEN \t %d 0x%x\n", FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_HIDDEN);
    printf("FILE_ATTRIBUTE_NORMAL \t %d 0x%x\n", FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_NORMAL);
    printf("FILE_ATTRIBUTE_TEMPORARY \t %d 0x%x\n", FILE_ATTRIBUTE_TEMPORARY, FILE_ATTRIBUTE_TEMPORARY);
    printf("FILE_ATTRIBUTE_ENCRYPTED \t %d 0x%x\n", FILE_ATTRIBUTE_ENCRYPTED, FILE_ATTRIBUTE_ENCRYPTED);

    return 0;

}

The FILE_FLAG_BACKUP_SEMANTICS flag indicates that the file is being opened or created for a backup or restore operation. Note that we must specify this flag if we want to work with a directory. The FILE_FLAG_DELETE_ON_CLOSE flag indicates that the file should be deleted as soon as all of its handles are closed. The FILE_FLAG_OVERLAPPED flag indicates that the file or device is to be opened or created for asynchronous I/O. If this flag is specified, the file can be used for simultaneous read and write operations. The FILE_FLAG_RANDOM_ACCESS is pretty self-explanatory, the file will be accessed randomly. The FILE_FLAG_NO_BUFFERING flag indicates that the file or device is to be opened with no system caching for data reads and writes.

#include "stdafx.h"
#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
    
    printf("FILE_FLAG_DELETE_ON_CLOSE %d.\n", FILE_FLAG_DELETE_ON_CLOSE);
    printf("FILE_FLAG_OVERLAPPED %d.\n", FILE_FLAG_OVERLAPPED);
    printf("FILE_FLAG_BACKUP_SEMANTICS %d.\n", FILE_FLAG_BACKUP_SEMANTICS);
    printf("FILE_FLAG_RANDOM_ACCESS %d.\n", FILE_FLAG_RANDOM_ACCESS);
    printf("FILE_FLAG_NO_BUFFERING %d.\n", FILE_FLAG_NO_BUFFERING);

    return 0;

}

The OPEN_ALWAYS creation disposition will always give us a file handle to the file, unless there has been a grievous error of some sort. If the file doesn’t exist, it will be created, and if it does exist, the function will still succeed but the last-error code will be set to ERROR_ALREADY_EXISTS.

#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
    char *szFileName = "kilroy.txt";
    HANDLE hFile;
    //create the file!
    //if file already exists, then we will open it
    //otherwise it will be created and then opened
    hFile = CreateFile(
        szFileName,
        GENERIC_WRITE,
        FILE_SHARE_READ,
        NULL,
        OPEN_ALWAYS,
        FILE_ATTRIBUTE_NORMAL,
        NULL
        );

    if (hFile == INVALID_HANDLE_VALUE){
        printf("Failed to create/open file %s.\n", szFileName);
    }
    else {
        if (GetLastError() == ERROR_ALREADY_EXISTS){
            printf("File already exists, opening...\n");
        }
        else {
            printf("File created, opening...\n");
        }
    }

    //close the handle
    CloseHandle(hFile);

    return 0;

}

The WriteFile() function takes five arguments.The first argument or parameter is a handle to the file or I/O device. The second parameter is a pointer to the buffer that holds the data to be written. The third argument controls the number of bytes to be written. The fourth argument is a pointer to a DWORD value that stores the number of bytes written during a synchronous write operation. The final parameter is a pointer to an OVERLAPPED structure; this parameter is necessary if we are to perform asynchronous I/O.

#include "stdafx.h"
#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
    const int Buffer_Size = 256;

    HANDLE hFile;
    char *szFileName = "kilroy.txt";
    char szBuffer[Buffer_Size];
    DWORD dwBytesWritten;
    BOOLEAN boolRVal;

    hFile = CreateFile(szFileName, GENERIC_WRITE, NULL, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

    if (hFile == INVALID_HANDLE_VALUE){
        exit(EXIT_FAILURE);
    }

    strncpy_s(szBuffer, "What strength! But don't forget there are many guys like you all over the World.", Buffer_Size);

    //let's write!
    boolRVal = WriteFile(hFile, szBuffer, strlen(szBuffer), &dwBytesWritten, NULL);

    if (boolRVal){
        printf("%s written to successfully!\n", szFileName);
        printf("%d bytes written.\n", dwBytesWritten);
    }

    //close the handle afterwards
    CloseHandle(hFile);

    return 0;

}

The WriteFile() function is designed for both synchronous and asynchronous operations. For asynchronous write operations, the handle must have been opened with CreateFile() using the FILE_FLAG_OVERLAP flag.

The ReadFile() function is a block-reading function. As with WriteFile(), we pass it in a buffer, only this time we indicate the number of bytes to be read, and the function retrieves the specified number of bytes from the file starting at the current offset.

#include "stdafx.h"
#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
    const int Buffer_Size = 256;
    char *szFileName = "kilroy.txt";
    char szInBuffer[Buffer_Size];
    HANDLE hFile;
    DWORD dwBytesRead;

    hFile = CreateFile(szFileName, GENERIC_READ, NULL, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

    if (hFile == INVALID_HANDLE_VALUE){
        exit(EXIT_FAILURE);
    }

    if (ReadFile(hFile, szInBuffer, Buffer_Size, &dwBytesRead, NULL)){
        printf("%d bytes read.\n", dwBytesRead);
        //use dwBytesRead to place a string termination character
        //at the correct location in the buffer.
        szInBuffer[dwBytesRead] = '';
        printf("%s\n", szInBuffer);
    }

    CloseHandle(hFile);

    return 0;

}

The win32 API has several functions that are useful for retrieving file information. Many of these functions require an open file handle rather than the file’s name.

The GetFileTime() function fetches three different pieces of time information concerning a file: the creation time, the last access time, and the last write time. Each of these times is stored in a FILETIME structure. Data from the FILETIME structure should be extracted via win32 function calls, namely FileTimeToSystemTime() , which converts a FILETIME structure to a SYSTEMTIME structure, which is easy to work with.

#include "stdafx.h"
#include <Windows.h>

void DisplayTime(FILETIME structFT);

int _tmain(int argc, _TCHAR* argv[])
{
    TCHAR *szFilename = _T("kilroy.txt");
    FILETIME structFTCreate, structFTLastWrite, structFTLastAccess;
    BOOLEAN boolSuccess;

    HANDLE hFile = CreateFile(szFilename, GENERIC_READ, FILE_SHARE_WRITE, 0, OPEN_ALWAYS, 0, 0);

    if (hFile == INVALID_HANDLE_VALUE){
        printf("Error no. %d\n", GetLastError());
        exit(EXIT_FAILURE);
    }
    else {
        printf("Acquiring file times...\n");
    }

    boolSuccess = GetFileTime(hFile, &structFTCreate, &structFTLastAccess, &structFTLastWrite);

    printf("Creation time: \n");
    DisplayTime(structFTCreate);
    printf("\n");
    printf("Last write time: \n");
    DisplayTime(structFTLastWrite);

    return 0;
}

void DisplayTime(FILETIME structFT){
    SYSTEMTIME structST;
    FileTimeToSystemTime(&structFT, &structST);
    printf("Month: %d Day: %d Year: %d ", structST.wMonth, structST.wDay, structST.wYear);
    printf("%d:%d\n", structST.wHour, structST.wMinute);
}

The FILETIME structure contains two 32-bit values. The FileTimeToSystemTime() function convert this amalgamated 64 bit value into a local time suitable for output.

The GetFileSize() function returns the size of the file in bytes; however, a more suitable variant for contemporary programming is GetFileSizeEx(). The GetFileSizeEx() function accepts two arguments, the first being a handle to the file, and the second is a pointer to a LARGE_INTEGER union.

#include "stdafx.h"
#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
    HANDLE hFile;
    TCHAR *szFilename = _TEXT("kilroy.txt");
    LARGE_INTEGER structLargeInt;

    hFile = CreateFile(szFilename, GENERIC_READ, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, NULL, NULL);

    if (hFile == INVALID_HANDLE_VALUE){
        printf("Error no. %d\n", GetLastError());
        exit(EXIT_FAILURE);
    }
    else {
        GetFileSizeEx(hFile, &structLargeInt);
        //only the QuadPart member of the LARGE_INTEGER structure concerns us
        printf("Size: %d bytes.\n", structLargeInt.QuadPart);
    }

    return 0;
}

Diving into win32 Processes

A process from the Windows perspective is a kernel object that characterizes a certain address space within which threads execute. As with any other kernel object, a process has a reference count indicating how many open handles there are to itself. A process is created via a call to the CreateProcess() function, and remains in existence until all open handles to the process have been closed, at which point the reference count for the process object is zero, and there are no more active threads in the process.

The GetCurrentProcess() function returns a pseudo handle to the current process. A handle is typically an integer value, although technically speaking we ought to consider a handle as opaque, meaning that we only really need to focus on how to us it, not what it is, as such. A pseudo handle is a special integral value, which again we should consider opaque, which is why we acquire it via the GetCurrentProcess() function, rather than hard-coding it as a constant value.

Once we have a handle to the process, there are a number of functions we can call to provide us with information about the process. The GetProcessId() function takes the handle to the process and returns the ID of the process.

#include "stdafx.h"
#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
    HANDLE hCurrProc;

    hCurrProc = GetCurrentProcess();

    //pseudo handle will be (HANDLE)-1
    _tprintf(_T("Handle literal value %d\n"), (int)hCurrProc);

    //process ID
    _tprintf(_T("Process ID of current process %d\n"), GetCurrentProcessId());
    //returns the same data, but this call can be used with any process 
    _tprintf(_T("Ditto %d\n"), GetProcessId(hCurrProc));


    return 0;
}

An IO_COUNTERS structure is used to store I/O information for a process or a job object. The win32 GetProcessIoCounters() function enables us to get a pointer to an IO_COUNTERS structure that contains the I/O accounting information for the process.

#include "stdafx.h"
#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
    IO_COUNTERS structIOCount;
    HANDLE hCurrProcess = GetCurrentProcess();
    //second argument needs to be the address of an IO_COUNTERS
    //structure
    if (GetProcessIoCounters(hCurrProcess, &structIOCount)){
        _tprintf(_T("Got I/O Information for Process %d\n"), GetProcessId(hCurrProcess));
        _tprintf(_T("The number of read operations performed: %d\n"), structIOCount.ReadOperationCount);
        _tprintf(_T("The number of write operations performed: %d\n"), structIOCount.WriteOperationCount);
        _tprintf(_T("The number of bytes read: %d\n"), structIOCount.ReadTransferCount);
        _tprintf(_T("THe number of bytes written: %d\n"), structIOCount.WriteTransferCount);
    }


    return 0;
}

The CreateProcess() function returns a handle to a new process object that will manage the lifetime of the indicated running program. The CreateProcess() function takes ten arguments

The first parameter for CreateProcess() is a pointer to the null-terminated name of the program to run; if this parameter is NULL, the program indicated in the second argument will be executed. This second argument is likewise a null-terminated string; it indicates the program to execute, along with any command-line arguments. The third argument is a pointer to a SECURITY_ATTRIBUTES structure; if this argument is NULL, the default security attributes are used, and the process handle returned by the function is not inheritable. The fourth argument is likewise a pointer to a SECURITY_ATTRIBUTES structure; if NULL, the default attributes are used, and the thread handle is not inheritable. The fifth argument is a Boolean value indicating whether or not the new process should inherit the inheritable handles of the calling process. The sixth argument is a DWORD value that indicates the creation flags, which can be OR-ed together to form combinations. This argument can also be used to control the priority of the new process. The seventh argument is a pointer to an environment block for the new process. If we put NULL here than the process will be passed a copy of the current environment. The eight argument is a string that specifies the initial default drive and directory of the process. The ninth argument is a pointer to a STARTUPINFO structure, and the tenth argument is a pointer to a PROCESS_INFORMATION structure that will be filled in with details about the newly created process.

#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
    STARTUPINFO structSI;
    PROCESS_INFORMATION structPI;
    BOOL bRValue;
    ZeroMemory(&structSI, sizeof(structSI));
    //important!
    structSI.cb = sizeof(STARTUPINFO);

    bRValue = CreateProcess(
        NULL, //lpApplicationName
        "notepad.exe", //lpCommandLine
        NULL, //lpProcessAttributes
        NULL, //lpThreadAttributes
        TRUE, //bInheritHandles
        0, //dwCreationFlags
        NULL, //lpEnvironment
        NULL, //lpCurrentDirectory
        &structSI, //lpStartupInfo
        &structPI //lpProcessInformation
        );

    if (bRValue){
        printf("Process created.\n");
    }

    return 0;
}

The PROCESS_INFORMATION structure has four members. The hProcess member is the handle to the newly created process. The calling program should use the CloseHandle() function to release its reference to the newly created process kernel object. The hThread member is the handle to the primary thread of the new process. The dwProcessId member stores the system-wide unique identifier to the new process. The dwThreadId member stores the system-wide unique identifier to the primary thread of the new process.

#include "stdafx.h"
#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
    STARTUPINFO structSI;
    PROCESS_INFORMATION structPI;

    ZeroMemory(&structSI, sizeof(structSI));
    structSI.cb = sizeof(structSI);

    //createprocess returns Boolean value
    if (CreateProcess(NULL, "notepad.exe", NULL, NULL, FALSE,
        0, NULL, NULL, &structSI, &structPI)){
        //display the information stored in structPI
        printf("Notepad.exe created as process %d and thread %d\n",
            structPI.dwProcessId, structPI.dwThreadId);
    }

    //let's close the newly created process and thread handles
    CloseHandle(structPI.hProcess);
    CloseHandle(structPI.hThread);

}

Each process has a handle and a system-wide unique identifier. From the calling program, we can access these values via the PROCESS_INFORMATION data structure. From within the process itself, an application can determine its own pseudo-handle via the GetCurrentProcess() call. Note that a pseudo-handle is only valid in the context of the calling process, as it is not a real handle.

The TerminateProcess() function can be used to terminate another process. Generally speaking, this function should not be called unless in an emergency.

Any process can get the exit status of a process it has a handle to by using the GetExitCodeProcess() function. The GetExitCodeProcess() function returns a Boolean value and accepts two parameters, the first being the handle to the process and the second being a pointer to a DWORD value to store the exit code.

#include "stdafx.h"
#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
    STARTUPINFO structSInfo;
    PROCESS_INFORMATION structPInfo;
    DWORD dwExitCode;
    BOOLEAN boolRValue;

    ZeroMemory(&structSInfo, sizeof(structSInfo));

    //start notepad
    boolRValue = CreateProcess(
        NULL, //application name
        "notepad.exe", //command line
        NULL, //Inherit security attributes
        NULL, //process attributes
        TRUE, //inherit handles
        0, //no creation flags
        NULL, //environment
        NULL, //current directory
        &structSInfo,
        &structPInfo
        );

    if (boolRValue){
        printf("Notepad.exe created as process %d, thread %d.\n", structPInfo.dwProcessId, structPInfo.dwThreadId);
    }
    else {
        printf("Error creating new process.");
        exit(EXIT_FAILURE);
    }

    //wait for a few seconds
    Sleep(3000);

    //terminate notepad.exe
    TerminateProcess(structPInfo.hProcess, 0);

    //get exit code
    boolRValue = GetExitCodeProcess(structPInfo.hProcess, &dwExitCode);

    if (boolRValue){
        printf("notepad.exe's exit code is %d\n", dwExitCode);
    }

    CloseHandle(structPInfo.hProcess);
    CloseHandle(structPInfo.hThread);

    return 0;

}

One last item to cover is waiting for a process to exit – this is the simplest, and perhaps most limiting, way to synchronize processes. To wait for a process to exit, we simply call  WaitForSingleObject(). The WaitForSingleObject() function takes two arguments, the first is the handle to the process, and the second specifies how long we want the process to wait for the other process to end.

#include "stdafx.h"
#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[])
{
    STARTUPINFO structSI;
    PROCESS_INFORMATION structPI;
    DWORD dwWaitRValue, dwExitCode;

    ZeroMemory(&structSI, sizeof(structSI));
    structSI.cb = sizeof(structSI);

    CreateProcess(
        NULL,
        "powershell.exe",
        NULL,
        NULL,
        FALSE,
        CREATE_NEW_CONSOLE, //creation flag
        NULL,
        NULL,
        &structSI,
        &structPI
        );

    //print some information
    printf("Powershell.exe was created as process ID %d.\n", structPI.dwProcessId);

    //wait for process to exit
    while (true){
        dwWaitRValue = WaitForSingleObject(structPI.hProcess, 1000);
        if (dwWaitRValue == WAIT_OBJECT_0){
            printf("\nProcess ID %d exited.\n", structPI.dwProcessId);
            break;
        }
        if (dwWaitRValue == WAIT_TIMEOUT){
            printf("Powershell.exe (Process %d) has not exited yet.\n", structPI.dwProcessId);
        }
    }

    //get exit code
    if (GetExitCodeProcess(structPI.hProcess, &dwExitCode)){
        printf("Powershell.exe exited with code %d.\n", dwExitCode);
    }

    CloseHandle(structPI.hProcess);
    CloseHandle(structPI.hThread);

    return 0;

}

This covers most of what we need to know about the basics of creating and managing processes in Windows.