The API function to create a new thread of execution is named CreateThread. The function has the following syntax:
hThread = CreateThread (&security_attributes, dwStackSize, ThreadProc, pParam, dwFlags, &idThread) ;
The first argument is a pointer to a structure of type SECURITY_ATTRIBUTES. This argument is ignored in Windows 98.
It can also be set to NULL in Windows NT. The second argument is an initial stack size for the new thread; this argument
can be set to 0 for a default value. In any case, Windows dynamically lengthens the stack, if necessary.
The third argument to CreateThread is a pointer to the Thread function. This can have any name but must have the syntax
DWORD WINAPI ThreadProc (PVOID pParam) ;
The fourth argument to CreateThread becomes the argument to ThreadProc. This is how a main thread and a secondary thread
can share data.
The fifth argument to CreateThread is usually 0 but can be the flag CREATE_SUSPENDED if the thread is to be created but
not immediately executed. The thread will remain suspended until ResumeThread is called. The sixth argument is a pointer to
a variable that will receive the value of the thread ID.
Most Windows programmers instead prefer to use a C run-time library named _beginthread that is declared in the PROCESS.H
header file. This function has the following syntax:
hThread = _beginthread (ThreadProc, uiStackSize, pParam) ;
It's just a bit simpler and is perfectly fine for most applications. This Thread function has the syntax
void __cdecl ThreadProc (void * pParam) ;
The Critical Section
There are four functions for using critical sections. To use these functions, you must define a critical section object,
which is global variable of type CRITICAL_SECTION. For example,
CRITICAL_SECTION cs ;
This CRITICAL_SECTION data type is a structure, but the fields are used only internally to Windows. This critical section
object must first be initialized by one of the threads in the program by calling
InitializeCriticalSection (&cs) ;
This creates a critical section object named cs. The online documentation for this function includes the following warnings:
"A critical section object cannot be moved or copied. The process must also not modify the object, but must treat it as logically
opaque." This can be translated as "Don't mess around with it, and don't even look at it."
After the critical section object has been initialized, a thread enters a critical section by calling
EnterCriticalSection (&cs) ;
At this point, the thread is said to "own" the critical section object. No two threads can own the critical section object at
the same time. Thus, if another thread has entered a critical section, the next thread calling EnterCriticalSection with the same
critical section object will be suspended in the function call. The function will return only when the first thread leaves the critical
section by calling
LeaveCriticalSection (&cs) ;
At that time, the second thread-suspended in its call to EnterCriticalSection-will own the critical section and the function
call will return, allowing the thread to proceed.
When the critical section object is no longer needed by the program, it can be deleted by calling
DeleteCriticalSection (&cs) ;
This frees up any system resources that might have been allocated to maintain the critical section object.
Thread Local Storage
Global variables in a multithreaded program, as well as any allocated memory, are shared among all the threads in the program.
Local static variables in a function are also shared among all threads using that function. Local automatic variables in a function
are unique to each thread because they are stored on the stack and each thread has its own stack.
It might be necessary to have persistent storage that is unique to each thread. For example, the C strtok function I mentioned
earlier in this chapter requires this type of storage. Unfortunately, the C language does not support such a variable. But Windows
includes four functions that implement a mechanism to do it, and the Microsoft extensions to C also support it. As we've seen,
this is called thread local storage.
Here's how the APIs work:
First define a structure that contains all the data that needs to be unique among the threads. For example,
typedef struct
{
int a ;
int b ;
}
DATA, * PDATA ;
The primary thread calls TlsAlloc to obtain an index value:
dwTlsIndex = TlsAlloc () ;
This index value can be stored in a global variable or passed to the Thread function in the argument structure.
The Thread function begins by allocating memory for the data structure and calling TlsSetValue using the index obtained above:
TlsSetValue (dwTlsIndex, GlobalAlloc (GPTR, sizeof (DATA)) ;
This associates a pointer with a particular thread and a particular thread index. Now any function that needs to use this pointer,
including the original Thread function itself, can include code like so:
PDATA pdata ;
...
pdata = (PDATA) TlsGetValue (dwTlsIndex) ;
Now it can set or use pdata->a and pdata->b. Before the Thread function terminates, it frees the allocated memory:
GlobalFree (TlsGetValue (dwTlsIndex)) ;
When all the threads using this data have terminated, the primary thread frees the index:
TlsFree (dwTlsIndex) ;
This process might be confusing at first, so perhaps it might be helpful to see how thread local storage might be implemented.
(I have no knowledge of how Windows actually does it, but the following is plausible.) First, TlsAlloc might simply allocate a block of
memory (zero bytes in length) and return an index value that is a pointer to this block. Every time TlsSetValue is called with that index,
the block of memory is increased by 8 bytes by reallocating it. Stored in these 8 bytes is the ID of the thread calling the function-obtained
by calling GetCurrentThreadId-and the pointer passed to the TlsSetValue function. TlsGetValue simply uses the thread ID to search the table
and then return the pointer. TlsFree frees up the block of memory. So, as you see, this is something you could probably easily implement yourself,
but it's nice to have the facility already done for us.
A Microsoft extension to C makes this even more simple. Just preface any variable that needs to be different for each thread with __declspec (thread),
like so
__declspec (thread) int iGlobal = 1 ;
for static variables external to any function, or like so
__declspec (thread) static int iLocal = 2 ;
for static variables within functions.
The Library Entry and Exit Point
The DllMain function is called when the library first begins and when it terminates. The first parameter to DllMain is the instance handle of the library.
If your library uses resources that require an instance handle (such as DialogBox), you should save hInstance as a global variable. The last parameter to DllMain
is reserved by the system.
The fdwReason parameter can be one of four values that indicate why Windows is calling the DllMain function. In the following discussion, keep in mind that
a single program can be loaded multiple times and run concurrently under Windows. Each time a program is loaded, it is considered a separate process.
A fdwReason value of DLL_PROCESS_ATTACH indicates that the dynamic-link library has been mapped into the address space of a process. This is a cue for the library
to do any initialization tasks it requires to service subsequent requests from the process. Such initialization might include memory allocation, for example. During
the time that a process is running, DllMain is called with a DLL_PROCESS_ATTACH parameter only once during the lifetime of that process. Any other process using the
same DLL causes another call to DllMain with a DLL_PROCESS_ATTACH parameter, but that on behalf of the new process.
If the initialization is successful, DllMain should return a nonzero value. Returning zero will cause Windows to not run the program.
When fdwReason has a value of DLL_PROCESS_DETACH, it means that the DLL is no longer needed by the process. This provides an opportunity for the library to clean
up after itself. Under the 32-bit versions of Windows often this is not strictly necessary, but it's a good programming practice.
Similarly, when DllMain is called with an fdwReason parameter of DLL_THREAD_ATTACH, it means that an attached process has created a new thread. When the thread
terminates, Windows calls DllMain with an fdwReason parameter of DLL_THREAD_DETACH. Be aware that it's possible to get a DLL_THREAD_DETACH call without an earlier
DLL_THREAD_ATTACH call if the dynamic-link library is attached to a process after the thread has been created.
The thread still exists when DllMain is called with a parameter of DLL_THREAD_DETACH. It can even send the thread messages during this process. But it shouldn't
use PostMessage because the thread might be gone before the message is retrieved.
The linker has to be told about shared. In the Project Settings dialog box, select the Link tab. In the Project Options field for STRLIB (in both the Release and
Debug configurations), include the following linker argument:
/SECTION:shared,RWS
The RWS letters indicate that the section has read, write, and shared attributes. Or you can specify the linker option directly in the DLL source code, as is done
in STRLIB.C:
#pragma comment(linker,"/SECTION:shared,RWS")
Load resources from a DLL
During processing of the WM_CREATE message, SHOWBIT gets a handle to BITLIB.DLL:
if ((hLibrary = LoadLibrary (TEXT ("BITLIB.DLL"))) == NULL)
If BITLIB.DLL isn't in the same directory as SHOWBIT.EXE, Windows will search for it as discussed earlier in this chapter. If LoadLibrary returns NULL, SHOWBIT
displays a message box reporting the error and returns a -1 from the WM_CREATE message. This causes the CreateWindow call in WinMain to return NULL, and the program
terminates.
SHOWBIT can obtain a handle to a bitmap by calling LoadBitmap with the library handle and the number of the bitmap:
hBitmap = LoadBitmap (hLibrary, MAKEINTRESOURCE (iCurrent)) ;
This returns an error if the bitmap corresponding to the number iCurrent isn't valid or if not enough memory exists to load the bitmap.
While processing the WM_DESTROY message, SHOWBIT frees the library:
FreeLibrary (hLibrary) ;
When the last instance of SHOWBIT terminates, the reference count of BITLIB.DLL drops to 0 and the memory it occupies is freed. As you can see, this is a simple
method of implementing a "clip art" program that could load precreated bitmaps (or metafiles or enhanced metafiles) into the clipboard for use by other programs.
hThread = CreateThread (&security_attributes, dwStackSize, ThreadProc, pParam, dwFlags, &idThread) ;
The first argument is a pointer to a structure of type SECURITY_ATTRIBUTES. This argument is ignored in Windows 98.
It can also be set to NULL in Windows NT. The second argument is an initial stack size for the new thread; this argument
can be set to 0 for a default value. In any case, Windows dynamically lengthens the stack, if necessary.
The third argument to CreateThread is a pointer to the Thread function. This can have any name but must have the syntax
DWORD WINAPI ThreadProc (PVOID pParam) ;
The fourth argument to CreateThread becomes the argument to ThreadProc. This is how a main thread and a secondary thread
can share data.
The fifth argument to CreateThread is usually 0 but can be the flag CREATE_SUSPENDED if the thread is to be created but
not immediately executed. The thread will remain suspended until ResumeThread is called. The sixth argument is a pointer to
a variable that will receive the value of the thread ID.
Most Windows programmers instead prefer to use a C run-time library named _beginthread that is declared in the PROCESS.H
header file. This function has the following syntax:
hThread = _beginthread (ThreadProc, uiStackSize, pParam) ;
It's just a bit simpler and is perfectly fine for most applications. This Thread function has the syntax
void __cdecl ThreadProc (void * pParam) ;
The Critical Section
There are four functions for using critical sections. To use these functions, you must define a critical section object,
which is global variable of type CRITICAL_SECTION. For example,
CRITICAL_SECTION cs ;
This CRITICAL_SECTION data type is a structure, but the fields are used only internally to Windows. This critical section
object must first be initialized by one of the threads in the program by calling
InitializeCriticalSection (&cs) ;
This creates a critical section object named cs. The online documentation for this function includes the following warnings:
"A critical section object cannot be moved or copied. The process must also not modify the object, but must treat it as logically
opaque." This can be translated as "Don't mess around with it, and don't even look at it."
After the critical section object has been initialized, a thread enters a critical section by calling
EnterCriticalSection (&cs) ;
At this point, the thread is said to "own" the critical section object. No two threads can own the critical section object at
the same time. Thus, if another thread has entered a critical section, the next thread calling EnterCriticalSection with the same
critical section object will be suspended in the function call. The function will return only when the first thread leaves the critical
section by calling
LeaveCriticalSection (&cs) ;
At that time, the second thread-suspended in its call to EnterCriticalSection-will own the critical section and the function
call will return, allowing the thread to proceed.
When the critical section object is no longer needed by the program, it can be deleted by calling
DeleteCriticalSection (&cs) ;
This frees up any system resources that might have been allocated to maintain the critical section object.
Thread Local Storage
Global variables in a multithreaded program, as well as any allocated memory, are shared among all the threads in the program.
Local static variables in a function are also shared among all threads using that function. Local automatic variables in a function
are unique to each thread because they are stored on the stack and each thread has its own stack.
It might be necessary to have persistent storage that is unique to each thread. For example, the C strtok function I mentioned
earlier in this chapter requires this type of storage. Unfortunately, the C language does not support such a variable. But Windows
includes four functions that implement a mechanism to do it, and the Microsoft extensions to C also support it. As we've seen,
this is called thread local storage.
Here's how the APIs work:
First define a structure that contains all the data that needs to be unique among the threads. For example,
typedef struct
{
int a ;
int b ;
}
DATA, * PDATA ;
The primary thread calls TlsAlloc to obtain an index value:
dwTlsIndex = TlsAlloc () ;
This index value can be stored in a global variable or passed to the Thread function in the argument structure.
The Thread function begins by allocating memory for the data structure and calling TlsSetValue using the index obtained above:
TlsSetValue (dwTlsIndex, GlobalAlloc (GPTR, sizeof (DATA)) ;
This associates a pointer with a particular thread and a particular thread index. Now any function that needs to use this pointer,
including the original Thread function itself, can include code like so:
PDATA pdata ;
...
pdata = (PDATA) TlsGetValue (dwTlsIndex) ;
Now it can set or use pdata->a and pdata->b. Before the Thread function terminates, it frees the allocated memory:
GlobalFree (TlsGetValue (dwTlsIndex)) ;
When all the threads using this data have terminated, the primary thread frees the index:
TlsFree (dwTlsIndex) ;
This process might be confusing at first, so perhaps it might be helpful to see how thread local storage might be implemented.
(I have no knowledge of how Windows actually does it, but the following is plausible.) First, TlsAlloc might simply allocate a block of
memory (zero bytes in length) and return an index value that is a pointer to this block. Every time TlsSetValue is called with that index,
the block of memory is increased by 8 bytes by reallocating it. Stored in these 8 bytes is the ID of the thread calling the function-obtained
by calling GetCurrentThreadId-and the pointer passed to the TlsSetValue function. TlsGetValue simply uses the thread ID to search the table
and then return the pointer. TlsFree frees up the block of memory. So, as you see, this is something you could probably easily implement yourself,
but it's nice to have the facility already done for us.
A Microsoft extension to C makes this even more simple. Just preface any variable that needs to be different for each thread with __declspec (thread),
like so
__declspec (thread) int iGlobal = 1 ;
for static variables external to any function, or like so
__declspec (thread) static int iLocal = 2 ;
for static variables within functions.
The Library Entry and Exit Point
The DllMain function is called when the library first begins and when it terminates. The first parameter to DllMain is the instance handle of the library.
If your library uses resources that require an instance handle (such as DialogBox), you should save hInstance as a global variable. The last parameter to DllMain
is reserved by the system.
The fdwReason parameter can be one of four values that indicate why Windows is calling the DllMain function. In the following discussion, keep in mind that
a single program can be loaded multiple times and run concurrently under Windows. Each time a program is loaded, it is considered a separate process.
A fdwReason value of DLL_PROCESS_ATTACH indicates that the dynamic-link library has been mapped into the address space of a process. This is a cue for the library
to do any initialization tasks it requires to service subsequent requests from the process. Such initialization might include memory allocation, for example. During
the time that a process is running, DllMain is called with a DLL_PROCESS_ATTACH parameter only once during the lifetime of that process. Any other process using the
same DLL causes another call to DllMain with a DLL_PROCESS_ATTACH parameter, but that on behalf of the new process.
If the initialization is successful, DllMain should return a nonzero value. Returning zero will cause Windows to not run the program.
When fdwReason has a value of DLL_PROCESS_DETACH, it means that the DLL is no longer needed by the process. This provides an opportunity for the library to clean
up after itself. Under the 32-bit versions of Windows often this is not strictly necessary, but it's a good programming practice.
Similarly, when DllMain is called with an fdwReason parameter of DLL_THREAD_ATTACH, it means that an attached process has created a new thread. When the thread
terminates, Windows calls DllMain with an fdwReason parameter of DLL_THREAD_DETACH. Be aware that it's possible to get a DLL_THREAD_DETACH call without an earlier
DLL_THREAD_ATTACH call if the dynamic-link library is attached to a process after the thread has been created.
The thread still exists when DllMain is called with a parameter of DLL_THREAD_DETACH. It can even send the thread messages during this process. But it shouldn't
use PostMessage because the thread might be gone before the message is retrieved.
The linker has to be told about shared. In the Project Settings dialog box, select the Link tab. In the Project Options field for STRLIB (in both the Release and
Debug configurations), include the following linker argument:
/SECTION:shared,RWS
The RWS letters indicate that the section has read, write, and shared attributes. Or you can specify the linker option directly in the DLL source code, as is done
in STRLIB.C:
#pragma comment(linker,"/SECTION:shared,RWS")
Load resources from a DLL
During processing of the WM_CREATE message, SHOWBIT gets a handle to BITLIB.DLL:
if ((hLibrary = LoadLibrary (TEXT ("BITLIB.DLL"))) == NULL)
If BITLIB.DLL isn't in the same directory as SHOWBIT.EXE, Windows will search for it as discussed earlier in this chapter. If LoadLibrary returns NULL, SHOWBIT
displays a message box reporting the error and returns a -1 from the WM_CREATE message. This causes the CreateWindow call in WinMain to return NULL, and the program
terminates.
SHOWBIT can obtain a handle to a bitmap by calling LoadBitmap with the library handle and the number of the bitmap:
hBitmap = LoadBitmap (hLibrary, MAKEINTRESOURCE (iCurrent)) ;
This returns an error if the bitmap corresponding to the number iCurrent isn't valid or if not enough memory exists to load the bitmap.
While processing the WM_DESTROY message, SHOWBIT frees the library:
FreeLibrary (hLibrary) ;
When the last instance of SHOWBIT terminates, the reference count of BITLIB.DLL drops to 0 and the memory it occupies is freed. As you can see, this is a simple
method of implementing a "clip art" program that could load precreated bitmaps (or metafiles or enhanced metafiles) into the clipboard for use by other programs.