Even More About Threads with win32 API

A Windows process doesn’t technically run; threads run, the process is simply a manager. Threads are the actual entities scheduled to execute code on calls. A thread by default runs under its parent process’s security context.

In our first program, we will create a thread to execute the classic “Hello World!” program. We will use the CreateThread() function, which takes six parameters. The first two parameters we will pass 0 to, the third parameter is the memory location of the function we wish to run, which we deliver in the form of the function’s name. The fourth parameter is the argument we wish to send to the function. The fifth argument will we specify 0 for, as we do not wish to pass in any special flags. The final argument is a pointer to a DWORD value to store the thread ID of the new thread.

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

//entry function for child thread
DWORD WINAPI ChildThreadFunc(LPVOID lpThreadParam) {
 //parameter is an integer value indicating
 //the number of seconds the thread should run for
 DWORD dwNumSeconds = (DWORD)lpThreadParam;
 DWORD dwThreadID = GetCurrentThreadId();
 while (dwNumSeconds-- > 0) {
 printf("Hello from thread ID %d\n", dwThreadID);
 Sleep(1000);
 }
 //lets return 0 
 return 0;
}

int main()
{
 HANDLE hChildThread;
 DWORD dwChildID, dwParentID, dwExitCode;

 //determine primary thread's ID number
 dwParentID = GetCurrentThreadId();

 printf("Application started; primary ID is %d\n", dwParentID);
 
 //launch child thread
 hChildThread = CreateThread(
 0,
 0,
 ChildThreadFunc,
 (LPVOID)5,
 0,
 &dwChildID
 );

 //check if the thread was created
 if (hChildThread == INVALID_HANDLE_VALUE) {
 printf("Error creating thread.\n");
 exit(EXIT_FAILURE);
 }
 else {
 printf("Thread %d created.\n", dwChildID);
 }

 Sleep(5000);

 //get the exit code 
 GetExitCodeThread(hChildThread, &dwExitCode);

 printf("Child thread exited with value %d\n", dwExitCode);
 printf("Exiting main thread ID %d\n", dwParentID);

 //close the handle
 CloseHandle(hChildThread);

 return 0;
}

Note that is possible to make the current thread stop executing for a specified period of time by calling the Sleep() function. In our second program, we will code a simple thread that beeps in the background.

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

int GetInput(TCHAR tszPtr[], int Buffer_Size);
int ConvertTCHARToInt(TCHAR *tszPtr);
DWORD WINAPI BeepThread(LPVOID ptrParam);

struct SBeep {
 int iIterations;
 int iFrequency;
 int iDuration;
};

int main()
{
 HANDLE hThread;
 DWORD dwThreadID;
 struct SBeep structBeep;
 const int Buffer_Size = 56;
 TCHAR tszBuffer[Buffer_Size];

 printf("Please enter the number of beeps you'd like us to produce!\n");
 GetInput(tszBuffer, Buffer_Size);
 structBeep.iIterations = ConvertTCHARToInt(tszBuffer);
 printf("Please enter the frequency you'd like us to produce!\n");
 GetInput(tszBuffer, Buffer_Size);
 structBeep.iFrequency = ConvertTCHARToInt(tszBuffer);
 printf("Please enter the duration of the beep!\n");
 GetInput(tszBuffer, Buffer_Size);
 structBeep.iDuration = ConvertTCHARToInt(tszBuffer);
 _tprintf(_T("We will beep %d times for you at %d frequency for %d duration.\n"), 
 structBeep.iIterations, structBeep.iFrequency, structBeep.iDuration);
 //create a thread to execute the BeepThread function
 hThread = CreateThread(0, 0, BeepThread, &structBeep, 0, &dwThreadID);

 //wait for the thread to finish
 while (WaitForSingleObject(hThread, 500) == WAIT_TIMEOUT) {
 printf("Waiting on thread %d.\n", dwThreadID);
 }

 printf("All done!\n");

 return 0;
}

//get the string input 
int GetInput(TCHAR tszPtr[], int Buffer_Size) {
 TCHAR tch;
 int iCount = 0;
 while ((iCount < Buffer_Size) && (tch = getchar())!='\n'){
 *(tszPtr + iCount++) = tch;
 }
 //null-terminate the string
 *(tszPtr + iCount) = '\0';
 return iCount;
}

int ConvertTCHARToInt(TCHAR *tszPtr){
 int iCount = 0;
 int iRVal = 0;
 int iPlace = 1;
 //get length of string
 while (*(tszPtr + iCount) != '\0') { iCount++; }
 //iterate backwards through string
 while (iCount-- > 0) {
 //to get numeric value of character, subtract character zero
 iRVal += (*(tszPtr + iCount) -'0') * iPlace;
 iPlace *= 10;
 }
 return iRVal;
}

//the function to run in the thread
DWORD WINAPI BeepThread(LPVOID ptrParam) {
 //cast LPVOID param to correct data type
 struct SBeep *structBeep = (SBeep*)ptrParam;
 while (structBeep->iIterations-- > 0) {
 Beep(structBeep->iFrequency, structBeep->iDuration);
 Sleep(500);
 }
 return 0;
}

This program makes use of the WaitForSingleObject() function. We pass in as timeout value 200 milliseconds. If the thread has not completed by the time at which the timeout value has been reached, it returns the value WAIT_TIMEOUT.

As we have seen, it is possible to pass in a parameter to the thread function in the form of a pointer to a structure.

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

int GetInput(TCHAR tszPtr[], int Buffer_Size);
int ConvertTCHARToInt(TCHAR *tszPtr);
DWORD WINAPI BeepThread(LPVOID ptrParam);
VOID BeepForUs(int iIter, int iFreq, int iDur, int iPause);

struct SBeep {
 int iIterations;
 int iFrequency;
 int iDuration;
 //new field, pause
 int iPause;
};

int main()
{
 const int Buffer_Size = 56;
 const int Num_Threads = 5;

 struct SBeep structBeep;

 TCHAR tszBuffer[Buffer_Size];
 //we will have an array of thread handles
 HANDLE hThread[Num_Threads];

 DWORD dwThreadID;
 int iNumThreads = 0;

 do {
 printf("Please enter the number of beeps you'd like us to produce!\n");
 GetInput(tszBuffer, Buffer_Size);
 structBeep.iIterations = ConvertTCHARToInt(tszBuffer);
 printf("Please enter the frequency you'd like us to produce!\n");
 GetInput(tszBuffer, Buffer_Size);
 structBeep.iFrequency = ConvertTCHARToInt(tszBuffer);
 printf("Please enter the duration of the beep!\n");
 GetInput(tszBuffer, Buffer_Size);
 structBeep.iDuration = ConvertTCHARToInt(tszBuffer);
 printf("Please enter pause between beeps!\n");
 GetInput(tszBuffer, Buffer_Size);
 structBeep.iPause = ConvertTCHARToInt(tszBuffer);
 _tprintf(_T("We will beep %d times for you at %d frequency for %d duration and %d pause length.\n\n"),
 structBeep.iIterations, structBeep.iFrequency, structBeep.iDuration, structBeep.iPause);

 //create a thread and add it to the array
 hThread[iNumThreads] = CreateThread(0, 0, BeepThread, &structBeep, 0, &dwThreadID);
 } while (++iNumThreads < Num_Threads);

 //wait for the thread to finish
 while (WaitForSingleObject(hThread, 500) == WAIT_TIMEOUT) {
 printf("Waiting on thread %d.\n", dwThreadID);
 }

 //now we wait for all threads to finish executing
 WaitForMultipleObjects(Num_Threads, hThread, TRUE, INFINITE);

 printf("All done!\n");

 return 0;
}

//get the string input 
int GetInput(TCHAR tszPtr[], int Buffer_Size) {
 TCHAR tch;
 int iCount = 0;
 while ((iCount < Buffer_Size) && (tch = getchar())!='\n'){
 *(tszPtr + iCount++) = tch;
 }
 //null-terminate the string
 *(tszPtr + iCount) = '\0';
 return iCount;
}

int ConvertTCHARToInt(TCHAR *tszPtr){
 int iCount = 0;
 int iRVal = 0;
 int iPlace = 1;
 //get length of string
 while (*(tszPtr + iCount) != '\0') { iCount++; }
 //iterate backwards through string
 while (iCount-- > 0) {
 //to get numeric value of character, subtract character zero
 iRVal += (*(tszPtr + iCount) -'0') * iPlace;
 iPlace *= 10;
 }
 return iRVal;
}

//the function to run in the thread
DWORD WINAPI BeepThread(LPVOID ptrParam) {
 //make local copies of info
 struct SBeep * sBeep = (SBeep*)ptrParam;
 BeepForUs(sBeep->iIterations, sBeep->iFrequency, sBeep->iDuration, sBeep->iPause);
 return 0;
}

VOID BeepForUs(int iIter, int iFreq, int iDur, int iPause) {
 while (iIter-- > 0) {
 Beep(iFreq, iDur);
 Sleep(iPause);
 }
}

Thread prioritization is how important a thread is relative to others within the process. After we create a thread, we can change its priority from the default of normal to something more appropriate for the application. The SetThreadPriority() function takes two arguments; the first is the handle to the thread, the second is the priority value for the thread.

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


int main()
{
 //thread priority named constants
 printf("THREAD_PRIORITY_TIME_CRITICAL = %d\n", THREAD_PRIORITY_TIME_CRITICAL);
 printf("THREAD_PRIORITY_HIGHEST = %d\n", THREAD_PRIORITY_HIGHEST);
 printf("THREAD_PRIORITY_ABOVE_NORMAL = %d\n", THREAD_PRIORITY_ABOVE_NORMAL);
 printf("THREAD_PRIORITY_NORMAL = %d\n", THREAD_PRIORITY_NORMAL);
 printf("THREAD_PRIORITY_BELOW_NORMAL = %d\n", THREAD_PRIORITY_BELOW_NORMAL);
 printf("THREAD_PRIORITY_LOWEST = %d\n", THREAD_PRIORITY_LOWEST);

 return 0;
}

In our next program, we will create five threads and assign them an arbitrary workload to accomplish. We will then randomly modify their priority and wait for all of them to complete. The threads should complete in order of priority, with the threads with a higher priority number finishing first.

#include "stdafx.h"
#include <Windows.h>
//for intializing srand()
#include <ctime>

DWORD WINAPI DoWork(LPVOID lpParam) {
 int i = INT_MAX;
 int j = 1;
 while (i-- > 0) { j = i - 1; }
 printf("Thread %d done.\n", GetCurrentThreadId());
 return 0;
}


int main()
{
 const int Num_Threads = 5;
 const int Num_Priorities = 5;
 HANDLE hThreads[Num_Threads];
 DWORD dwThreadIDs[Num_Threads];
 int iNamedCons[Num_Priorities] = { THREAD_PRIORITY_LOWEST, THREAD_PRIORITY_BELOW_NORMAL, THREAD_PRIORITY_NORMAL, THREAD_PRIORITY_ABOVE_NORMAL, THREAD_PRIORITY_HIGHEST };
 int iCounter;
 int iRandValue; 

 srand(time(NULL));

 for (iCounter = 0; iCounter < Num_Threads; iCounter++) {
 hThreads[iCounter] = CreateThread(0, 0, DoWork, 0, 0, &dwThreadIDs[iCounter]);
 }

 //alter thread priority
 for (iCounter = 0; iCounter < Num_Threads; iCounter++) {
 iRandValue = rand() % Num_Priorities;
 printf("Setting thread ID %d to priority %d\n", dwThreadIDs[iCounter], iNamedCons[iRandValue]);
 SetThreadPriority(hThreads[iCounter], iNamedCons[iRandValue]);
 }

 WaitForMultipleObjects(Num_Threads, hThreads, TRUE, INFINITE);

 return 0;
}

Well, that is enough for today. Take a look at my book, if you are interested: http://www.amazon.com/Big-Als-C-Standard-ebook/dp/B00A4JGE0M/

Advertisements

Threads and Processes with win32

We can create a separate process from within a running application using the CreateProcess() function. In our program, we will retrieve the current process’s startup information using the GetStartupInfo() function.

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


int main()
{
 TCHAR szBuffer[MAX_PATH];
 STARTUPINFO structStartupInfo;
 PROCESS_INFORMATION structProcInfo;
 BOOL bSuccess;
 DWORD dwCharsRead;


 //get the name of the executable to run in the 
 //new process
 printf("Enter the command to execute, please: ");
 ReadConsole(GetStdHandle(STD_INPUT_HANDLE), szBuffer, _tcslen(szBuffer) - 2, &dwCharsRead, NULL);
 szBuffer[dwCharsRead - 2] = '';

 GetStartupInfo(&structStartupInfo);


 bSuccess = CreateProcess(0, szBuffer, 0, 0, FALSE, CREATE_NEW_CONSOLE, 0, 0, &structStartupInfo, &structProcInfo);

 if (bSuccess) {
 printf("New process created successfully.\n");
 //wait for child process to complete
 if ((WaitForSingleObject(structProcInfo.hProcess, INFINITE)) == WAIT_OBJECT_0) {
 printf("Process %d ended.\n", structProcInfo.dwProcessId);
 }
 CloseHandle(structProcInfo.hProcess);
 CloseHandle(structProcInfo.hThread);
 }

 printf("Finished!\n");

 return 0;
}

For the CreateProcess() function, we can indicate the executable to run in either the first or second parameter. By and large, most people use the second parameter to define the executable and any command line arguments.

When a process spawns a child process, the child process is completely independent of the parent. It is possible, however, for the child process to inherit handles from the parent if the parent marks any of its handles as inheritable.

The ParentProcess program

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

//parent process
HANDLE hFile;
const int Max_Buffer = 2056;

int main()
{
 STARTUPINFO structSUpInfo;
 PROCESS_INFORMATION structProcInfo;
 BOOL bSuccess;
 TCHAR tszFileName[MAX_PATH];
 TCHAR tszBuffer[Max_Buffer];
 SECURITY_ATTRIBUTES structSAttrib;
 //we'll use dwFileAttributes to check 
 //for the existence of the child executable
 DWORD dwCharsRead, dwFileAttributes;
 //store file handle number as text
 TCHAR tszFileHandleString[256];
 LARGE_INTEGER uLI;
 
 //set up security attributes
 //so as to allow child process to inherit 
 //file handle
 structSAttrib.nLength = sizeof(SECURITY_ATTRIBUTES);
 structSAttrib.lpSecurityDescriptor = 0;
 structSAttrib.bInheritHandle = TRUE;

 //get file name
 printf("Please enter the name of the file: ");
 bSuccess = ReadConsole(GetStdHandle(STD_INPUT_HANDLE), tszFileName, MAX_PATH, &dwCharsRead, NULL);
 if (bSuccess) {
 printf("File name acquired.\n");
 }
 else {
 exit(EXIT_FAILURE);
 }

 //remove windows newline chars
 tszFileName[dwCharsRead - 2] = '';

 printf("Creating file.\n");

 hFile = CreateFile(tszFileName, GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE, &structSAttrib, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
 
 if (hFile != INVALID_HANDLE_VALUE) {
 printf("File created.\n");
 }
 else {
 printf("Error creating file. Error %d. \n", GetLastError());
 exit(EXIT_FAILURE);
 }
 
 //check to make sure executable for child process 
 //is available
 dwFileAttributes = GetFileAttributes(_T("ChildProcess.exe"));
 if (dwFileAttributes == INVALID_FILE_ATTRIBUTES) {
 printf("Could not find the child process executable.\n");
 exit(EXIT_FAILURE);
 } 

 //get startup info
 GetStartupInfo(&structSUpInfo);

 //convert file handle to string for passing to child process
 printf("Handle value is %d\n", hFile);
 swprintf(tszFileHandleString, 16, L"%d", hFile);


 bSuccess = CreateProcess(_T("ChildProcess.exe"), tszFileHandleString, 0, 0, TRUE, CREATE_NEW_CONSOLE, 0, 0, &structSUpInfo, &structProcInfo);

 if (!bSuccess) {
 printf("Error creating child process.\n");
 exit(EXIT_FAILURE);
 }
 else {
 printf("Child process created.\n");
 }

 WaitForSingleObject(structProcInfo.hProcess, INFINITE);

 CloseHandle(structProcInfo.hProcess);
 CloseHandle(structProcInfo.hThread);

 printf("Child process %d finished.\n", structProcInfo.dwProcessId);
 
 //now let's read from the file
 hFile = CreateFile(tszFileName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
 uLI.QuadPart = 0;
 SetFilePointerEx(hFile, uLI, 0, FILE_BEGIN);
 //don't forget - ReadFile counts number of bytes read, not chars!

 bSuccess = ReadFile(hFile, tszBuffer, Max_Buffer - 1, &dwCharsRead, NULL);
 if (bSuccess) {
 printf("File read successfully.\n");
 }

 tszBuffer[dwCharsRead / sizeof(TCHAR)] = '';

 _tprintf(_T("Read from file: %s\n"), tszBuffer);


 return 0;
}

The ChildProcess program

// ChildProcess.cpp : Defines the entry point for the console application.


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

HANDLE hFile;
const int Max_Buffer = 2056;

//child process
int main(int argc, char *argv[])
{
 TCHAR tszBuffer[Max_Buffer];
 int iHNum;
 DWORD dwCharsRead, dwCharsWritten;
 BOOL bSuccess;

 printf("In the child process...\n");

 printf("Received argument %s.\n", argv[0]);
 //convert string argument to handle
 iHNum = atoi(argv[0]);

 hFile = (HANDLE)iHNum;
 printf("Handle value is %d\n", hFile);

 printf("Please enter text: ");
 bSuccess = ReadConsole(GetStdHandle(STD_INPUT_HANDLE), tszBuffer, Max_Buffer, &dwCharsRead, NULL);

 if (bSuccess == FALSE) {
 printf("Error reading text.\n");
 exit(EXIT_FAILURE);
 }

 tszBuffer[dwCharsRead] = '';
 _tprintf(_T("Text to be written to file: %s"), tszBuffer);

 bSuccess = WriteFile(hFile, tszBuffer, _tcslen(tszBuffer)*sizeof(TCHAR), &dwCharsWritten, 0);

 if (bSuccess) {
 printf("File written to successfully.\n");
 }
 else {
 printf("File not written to successfully.\n");
 printf("Error %d\n", GetLastError());
 Sleep(2000);
 exit(EXIT_FAILURE);
 }
 
 CloseHandle(hFile);
 printf("Finishing...");
 Sleep(2000);

 return 0;
}

When any process starts in Windows, it contains by default one thread of execution. The thread is what actually executes the code; the process functions as a container for the heap owned by the application, the global variables, and the environment strings. All threads in one process share the global variable space of their parent process.

The CreateThread() function has six parameters. The first parameter is a pointer to a SECURITY_ATTRIBUTES structure. If NULL, the handle to the thread cannot be inherited. The second argument is a DWORD value indicating the stack size for the thread. If 0, the default stack size for the executable is assigned. The third parameter is the name of the function that the thread starts. The fourth parameter is a void pointer used for passing in arguments to the thread. The fifth parameter indicates any flags we wish to pass in; we will put 0 here. The final parameter is a pointer to a DWORD value to store the thread’s ID.

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

volatile int iCount;
volatile int iCount2;

DWORD WINAPI CountThreadA(void *lpParameter) {
 while (true) {
 iCount++;
 Sleep(200);
 }
}

DWORD WINAPI CountThreadB(void *lpParameter) {
 while (true) {
 iCount2++;
 Sleep(500);
 }
}

int main()
{
 iCount = 1;
 iCount2 = 1;

 char szInput[56];
 HANDLE hThreadOne, hThreadTwo;
 DWORD dwThreadID1, dwThreadID2;

 //start up the threads
 hThreadOne = CreateThread(0, 0, CountThreadA, 0, 0, &dwThreadID1);
 hThreadTwo = CreateThread(0, 0, CountThreadB, 0, 0, &dwThreadID2);

 while (true) {
 printf("Enter A to see the count for the first thread, ");
 printf("B to see the count for the second thread, or ");
 printf("X to end the program: ");

 fgets(szInput, sizeof(szInput), stdin);
 
 if (szInput[0] == 'A'|| szInput[0] == 'a') {
 printf("Thread ID %d Count %d\n", dwThreadID1, iCount);
 }
 if (szInput[0] == 'B'|| szInput[0] == 'b') {
 printf("Thread ID %d Count %d\n", dwThreadID2, iCount2);
 }
 if (szInput[0] == 'X' || szInput[0] == 'x') {
 printf("Exiting");
 break;
 }
 }

 return 0;
}

Note the use of the volatile modifier for the iCount and iCount2 variables. Essentially, we use the volatile modifier for any global variable that will be modified in a multi-threaded application. The volatile keyword is used to basically tell the compiler no to apply any optimizations to the variable.

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.

Threads of Execution via win32

The Windows NT kernel implements a threading schedule by taking control of the processor when a quantum is finished, pausing the running thread, and resuming the scheduled thread. Every time the thread is changed is known as a context switch. In order to perform a context switch, the Windows NT kernel must save the contents of the processor’s register for the pausing thread, and restore the processor’s register contents for the resuming thread. Of course, this imposes a time penalty, which is a factor when deciding upon thread quantum size.

Adding to the complexity of thread scheduling is the fact that some processes and threads are more important than other ones. Reflecting this, we can assign the Windows process and thread objects a priority. Priorities tell the scheduler to run some threads longer than others, in essence.

Note as well that interprocess context switches are more costly than intraprocess context switches.

#include <Windows.h>
#include <stdlib.h>
#include <stdio.h>

int _tmain(int argc, _TCHAR* argv[])
{
	//initialize the SYSTEM_INFO data structure
	SYSTEM_INFO structSI;

	GetSystemInfo(&structSI);

	//display number of processors
	printf("%d processors.\n", structSI.dwNumberOfProcessors);

	return 0;
}

One of the nice features of a thread is the ability to suspend itself or be suspended. When a thread is suspended, the kernel knows not to include that thread in the schedule until it resumes. Once a thread switches from the suspended to the running state, it simply takes its place in the run schedule.

Threads are kernel objects, and the process that creates them references them through object handles. An active thread object is either suspended, ready to run, or running on a processor.

We use the CreateThread() API to start a new thread of execution. The CreateThread() system call makes a new entry in the system’s list of threads to execute. The CreateThread() call takes five parameters.

The first parameter describes the security to associate the new thread object. This information tells the system what accounts to allow and deny access to the thread for modification and synchronization. The first parameter is of type LPSECURITY_ATTRIBUTES. By specifying NULL for this parameter, we use the default security.

The second parameter is a DWORD value that defines the size of the stack the thread will receive. The process typically accepts the default by setting 0 for this parameter.

The third parameter is the address of the function to execute. Once this function finishes executing, the thread will terminate. Often, this function is called ThreadProc. A ThreadProc function is a user-defined function that returns a DWORD value and accepts a single parameter of type LPVOID.

The fourth parameter is a pointer to the value we wish to pass into the ThreadProc function, if any.

The second-to-last parameter is a process creation flag, of which there two that are allowable to use, the main one being CREATE_SUSPENDED. The final parameter, which is optional, is a pointer to a DWORD value to store the thread identifier for the new thread.

#include <Windows.h>
#include <stdlib.h>
#include <stdio.h>

//prototype for function we will send 
//to CreateThread()
DWORD WINAPI ThreadProc(LPVOID lpParam);

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

	hNewThread = CreateThread(
		NULL, //default security
		0, //default stack
		ThreadProc, //thread function name 
		NULL, //no argument to thread function
		0, //default creation flags
		NULL //don't care about thread ID
		);

	if(hNewThread != INVALID_HANDLE_VALUE){
		//success!
		printf("New thread created...\n");
	} else {
                exit(EXIT_FAILURE);
        }

	//do something in this thread
	for(int i = 0; i < 5; i++){
		printf("Hello from the old thread.\t");
		printf("(thread ID %d)\n", GetCurrentThreadId());
		Sleep(100);
	}

	//does the new thread get to finish? 
	//most likely not!
	CloseHandle(hNewThread);

	return 0;
}


DWORD WINAPI ThreadProc(LPVOID lpParam){
	for(int i = 0; i < 15; i++){
		Sleep(100);
		printf("\tHello from the new thread.\t");
		printf("(thread ID %d)\n", GetCurrentThreadId());
	}

	//return 0
	return 0;
}

Note that WINAPI is an alias, er, macro, for __stdcall. The __stdcall macro specifies that the callee cleans the stack, specifically, the parameters that were pushed onto the stack. The __stdcall convention is mainly used by the Windows API.

As we saw when running the program above, we need the parent thread to wait for the caller thread to exit… at least, if we want to be sure the new thread finishes doing whatever it is that it is doing. We do this with the WaitForSingleObject() function. The WaitForSingleObject() function waits until the object specified by the first HANDLE parameter is in the signaled state, or until the time out interval indicated in the second parameter has elapsed.

#include <Windows.h>
#include <stdlib.h>
#include <stdio.h>

//prototype
DWORD WINAPI ThreadProc(LPVOID lpParam);

int _tmain(int argc, _TCHAR* argv[])
{
	DWORD dwRValue;
	HANDLE hThread = CreateThread(
		NULL, //default security
		0, //default stack size
		ThreadProc, //start address of function
		NULL, // no parameters
		0, //no creation flags
		NULL //don't need the creation ID
		);

	if(hThread == INVALID_HANDLE_VALUE){
		printf("Failed to create new thread...\n");
		exit(EXIT_FAILURE);
	}

	do {
		dwRValue = WaitForSingleObject(hThread, 200);
		printf("\n yo, you done yet??? \n");
	} while(dwRValue != WAIT_OBJECT_0);
	
	printf("New thread is finished!\n");

	CloseHandle(hThread);

	return 0;

}

DWORD WINAPI ThreadProc(LPVOID lpParam){
	for(int i = 0; i < 20; i++){
		printf("Doing Thangs... ");
		Sleep(100);
	}
	return 0;
}

Note that the Do…While loop above might not be so hot if we weren’t sure ahead of time that the new thread was going to be running for longer than one timeout interval.

The WaitForSingleObject() function’s return value is of type DWORD. The WAIT_TIMEOUT value indicates that the function has exceeded its timeout parameter. The WAIT_OBJECT_0, that’s a 0 as in zero by the way, value indicates the indicated object is now in the signaled state.

#include <Windows.h>
#include <stdlib.h>
#include <stdio.h>

//let's define a custom parameter 
//to send to our new thread
struct structThreadParam{
	char cLetter;
	int iNumber;
};

DWORD WINAPI ThreadProc(LPVOID lpParam);

int _tmain(int argc, _TCHAR* argv[])
{
	structThreadParam structParam;
	structParam.cLetter = 'Z';
	structParam.iNumber = 90210;

	HANDLE hNewThread = CreateThread(
		NULL,
		0, 
		ThreadProc,
		&structParam,
		0,
		NULL
		);

	if(hNewThread != INVALID_HANDLE_VALUE){
		printf("New thread created.\n");
	} else {
		exit(EXIT_FAILURE);
	}

	//let's wait as long as it takes
	WaitForSingleObject(hNewThread, INFINITE);

	return 0;

}

DWORD WINAPI ThreadProc(LPVOID lpParam){
	//note that conversion is implicit in C
	//but let's be verbose
	structThreadParam *structPtr = (structThreadParam*)lpParam;
	printf("In thread %d: ", GetCurrentThreadId());
	printf("Received %c%d \n", structPtr->cLetter, structPtr->iNumber);

	return 0;
}

Note that we can use the INFINITE value to tell WaitForSingleObject() to wait until the specified thread is finished.

Interested in learning standard C? Check out my book at amazon.com/Big-Als-C-Standard-ebook/dp/B00A4JGE0M/