The Windows Processes and Threads 18

 

 

Creating Threads Program Example

 

The CreateThread() function creates a new thread for a process. The creating thread must specify the starting address of the code that the new thread is to execute. Typically, the starting address is the name of a function defined in the program code. This function takes a single parameter and returns a DWORD value. A process can have multiple threads simultaneously executing the same function. The following is a simple example that demonstrates how to create a new thread that executes the locally defined function, MyThreadFunction(). The calling thread uses the WaitForMultipleObjects() function to persist until all worker threads have terminated. The calling thread blocks while it is waiting; to continue processing, a calling thread would use WaitForSingleObject() and wait for each worker thread to signal its wait object. Note that if you were to close the handle to a worker thread before it terminated, this does not terminate the worker thread. However, the handle will be unavailable for use in subsequent function calls.

 

 

Create a new empty Win32 console application project. Give a suitable project name and change the project location if needed.

 

Creating Threads Program Example: Creating new Win32 C++ console application project in Visual C++ .NET

 

Then, add the source file and give it a suitable name.

 

Creating Threads Program Example: Adding new C++ source file for C++ source code to the existing C++ project

 

Next, add the following source code.

 

#include <windows.h>

#include <strsafe.h>

 

// Constants

#define MAX_THREADS 3

#define BUF_SIZE 255

 

// Prototypes

DWORD WINAPI MyThreadFunction(LPVOID lpParam);

void ErrorHandler(LPTSTR lpszFunction);

 

// Sample custom data structure for threads to use.

// This is passed by void pointer so it can be any data type

// that can be passed using a single void pointer (LPVOID).

typedef struct MyData {

    int val1;

    int val2;

} MYDATA, *PMYDATA;

 

// This should be the parent process

int wmain(int argc, WCHAR **argv)

{

    PMYDATA pDataArray[MAX_THREADS];

    DWORD   dwThreadIdArray[MAX_THREADS];

    HANDLE  hThreadArray[MAX_THREADS];

    DWORD Ret = 0;

     

    wprintf(LParent process ID: %u\n, GetCurrentProcessId());

    wprintf(LParent thread ID: %u\n, GetCurrentThreadId());

 

    // Create MAX_THREADS worker threads, in this case = 3

    for(int i=0; i<MAX_THREADS; i++)

    {

        // Allocate memory for thread data

        pDataArray[i] = (PMYDATA)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(MYDATA));

 

        if(pDataArray[i] == NULL)

        {

            // If the array allocation fails, the system is out of memory

            // so there is no point in trying to print an error message.

            // Just terminate execution.

            wprintf(L\nHeapAlloc() failed!\n);

            ExitProcess(2);

        }

 

            wprintf(L\nHeapAlloc() for thread #%u should be fine!\n, i);

 

        // Generate unique data for each thread to work with

        pDataArray[i]->val1 = i;

        pDataArray[i]->val2 = i+100;

 

        // Create the thread to begin execution on its own

        hThreadArray[i] = CreateThread(

            NULL,                   // default security attributes

            0,                      // use default stack size 

            MyThreadFunction,       // thread function name - a pointer to the application-defined

                                                      // function to be executed by the thread

            pDataArray[i],          // argument to thread function

            0,                      // use default creation flags

            &dwThreadIdArray[i]);   // returns the thread identifier

 

 

        // Check the return value for success.

        // If CreateThread() fails, terminate execution.

        // This will automatically clean up threads and memory.

        if (hThreadArray[i] == NULL)

        {

           ErrorHandler(LCreateThread());

           ExitProcess(3);

        }

            wprintf(LCreateThread() for thread #%i is fine!\n, i);

            wprintf(LCurrent process ID: %u\n, GetCurrentProcessId());

            wprintf(LCurrent thread ID: %u\n, dwThreadIdArray[i]);

    } // End of main thread creation loop.

 

    // Wait until all threads have terminated

    Ret = WaitForMultipleObjects(MAX_THREADS, hThreadArray, TRUE, INFINITE);

    wprintf(LWaitForMultipleObjects() return value is 0X%.8X\n, Ret);

 

    // Close all thread handles and free memory allocations

    wprintf(L\n);

    for(int i=0; i<MAX_THREADS; i++)

    {

        wprintf(LClosing thread's handle #%i\n, i);

        CloseHandle(hThreadArray[i]);

        if(pDataArray[i] != NULL)

        {

            HeapFree(GetProcessHeap(), 0, pDataArray[i]);

            pDataArray[i] = NULL;    // Ensure address is not reused.

        }

    }

    return 0;

}

 

// Thread creation function

DWORD WINAPI MyThreadFunction(LPVOID lpParam)

{

    HANDLE hStdout;

    PMYDATA pDataArray;

 

    WCHAR msgBuf[BUF_SIZE];

    size_t cchStringSize;

    DWORD dwChars;

 

    // Make sure there is a console to receive output results

    hStdout = GetStdHandle(STD_OUTPUT_HANDLE);

    if(hStdout == INVALID_HANDLE_VALUE)

        return 1;

    else

        wprintf(LHandle to the standard output is OK!\n);

 

    // Cast the parameter to the correct data type

    // The pointer is known to be valid because

    // it was checked for NULL before the thread was created

    pDataArray = (PMYDATA)lpParam;

 

    // Print the parameter values using thread-safe functions

    StringCchPrintf(msgBuf, BUF_SIZE, LParameters = %d, %d\n, pDataArray->val1, pDataArray->val2);

    StringCchLength(msgBuf, BUF_SIZE, &cchStringSize);

    WriteConsole(hStdout, msgBuf, (DWORD)cchStringSize, &dwChars, NULL);

 

    return 0;

}

 

void ErrorHandler(LPTSTR lpszFunction)

{

    // Retrieve the system error message for the last-error code

    LPVOID lpMsgBuf;

    LPVOID lpDisplayBuf;

    DWORD dw = GetLastError();

 

    FormatMessage(

        FORMAT_MESSAGE_ALLOCATE_BUFFER |

        FORMAT_MESSAGE_FROM_SYSTEM |

        FORMAT_MESSAGE_IGNORE_INSERTS,

        NULL,

        dw,

        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),

        (LPTSTR) &lpMsgBuf,

        0, NULL );

 

    // Display the error message

    lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT, (lstrlen((LPCTSTR) lpMsgBuf) + lstrlen((LPCTSTR) lpszFunction) + 40) * sizeof(WCHAR));

    StringCchPrintf((LPTSTR)lpDisplayBuf, LocalSize(lpDisplayBuf) / sizeof(WCHAR), L%s failed with error %d: %s, lpszFunction, dw, lpMsgBuf);

    MessageBox(NULL, (LPCTSTR) lpDisplayBuf, LError, MB_OK);

 

    // Free error-handling buffer allocations

    LocalFree(lpMsgBuf);

    LocalFree(lpDisplayBuf);

}

 

Build and run the project. The following screenshot is a sample output.

 

Creating Threads Program Example: A sample console program output in action

 

 

From the sample output, you should notice something that was expected. We have the same process ID (3024) for all threads, which is the main or parent program (wmain()).We loop the thread creation three times, however there are 4 threads. The extra thread is the wmain()'s thread. The MyThreadFunction() function avoids the use of the C run-time library (CRT), as many of its functions are not thread-safe, particularly if you are not using the multithreaded CRT. If you would like to use the CRT in a ThreadProc() function, use the _beginthreadex() function instead. It is risky to pass the address of a local variable if the creating thread exits before the new thread, because the pointer becomes invalid. Instead, either pass a pointer to dynamically allocated memory or make the creating thread wait for the new thread to terminate. Data can also be passed from the creating thread to the new thread using global variables. With global variables, it is usually necessary to synchronize access by multiple threads. The creating thread can use the arguments to CreateThread() to specify the following:

 

  1. The security attributes for the handle to the new thread. These security attributes include an inheritance flag that determines whether the handle can be inherited by child processes. The security attributes also include a security descriptor, which the system uses to perform access checks on all subsequent uses of the thread's handle before access is granted.
  2. The initial stack size of the new thread. The thread's stack is allocated automatically in the memory space of the process; the system increases the stack as needed and frees it when the thread terminates.
  3. A creation flag that enables you to create the thread in a suspended state. When suspended, the thread does not run until the ResumeThread() function is called.

 

You can also create a thread by calling the CreateRemoteThread() function. This function is used by debugger processes to create a thread that runs in the address space of the process being debugged.

 

 

< Processes & Threads 17 | Win32 Process & Thread Programming | Win32 Programming | Processes & Threads 19 >