About this article
This article is a bug that I am in checking a thread in an ATL Singlet not fully exited, reviewing more books, and has made a number of test programs. If there is an incorrect place, welcome to correct.
text
In the Windows operating system, the DLL (Dynamic Link Library) technology has many advantages. For example, multiple applications can share a DLL file, which really implements the resource "sharing", which greatly reduces the execution code of the application, effectively utilizing memory, and DLL files as a separate program module, encapsulation, independence Well, help improve software development and maintenance efficiency.
DllMain is an optional DLL portal pointer, which is called when the process and thread starts and terminates, and performs operations such as creating resources and release resources, specifically, or when the DLL is loaded into the process space (ie DLLMAIN response DLL_PROCESS_ATTACH notification When you create a thread, the thread is ended when the DLL is uninstalled from the process space (ie the DLLMAIN response DLL_PROCESS_DETACH notification). However, in dllmain, whether or not, create a thread or end thread, pay special attention to a rule, that is, the order of DLLMain's order calling rules.
1, DLLMAIN's order calling rules
The WINDOWS operating system is the inlet function dllmain in the order calling DLL. When the process is created, the system also creates a mutex for the process. Each process has its own mutual exclusive object. One role of the process mutex is that serialization is required to perform 4 cases that need to call DLLMain: DLL_Process_attach, dll_thread_attach, dll_thread_detach, and dll_process_detachdll. The second parameter of the DLLMAIN function indicates the cause of DLLMain.
When you create a thread or terminate the thread in Dllmain, if you violate the order of DLLMAIN, the program will have a deadlock. The deadlocks in both the DLLMain are created in both the DLLMAIN, and the deadlocks in both cases of terminating the thread are described below.
2, deadlock when you create and terminate the thread in Dllmain
2.1, why not running the thread created when loading a DLL
Considering that in a multi-threaded program, when a DLL is loaded, the DLL's DLLMain launched a thread and then immediately calls a WaitForsingleObject function of a response event object to confirm that before proceeding with the rest of the DLLMAIN. The resulting thread can perform some operations correctly. Similar code is as follows:
// ----------------------------------
Handle g_thread_handle = null; // The handle of the DLL internal thread
DWORD G_THREAD_ID = 0; // The ID of the DLL internal thread
Handle g_hevent = null; / / Handle of the answer event
DWORD WINAPI INSIDEDLL_THREADPROC (LPVOID P)
{
/ * Represents some operations.
If "---- Operations .----" is printed into the Output window,
Description This thread function is executed. * /
Outputdebugstring ("---- Operations .---- / n");
/ * After the operation of Insidedll_ThreadProc,
Notifying the thread waiting at G_HEvent, you can continue to run. * /
SetEvent (g_hevent);
Return 1;
}
Bool apientry dllmain (Handle Hmodule,
DWORD UL_REASON_FOR_CALL, LPVOID LPRESERVED) {
Switch (ul_reason_for_call)
{
Case DLL_Process_attach:
// DLL is being mapped to the process address space
{
// Prohibition of the line library call,
DisableThreadLibraryCalls (Hinstance) hmodule;
// Create an event object used in the DLL
g_hevent = :: CreateEvent (Null, False, False, _t ("Hello11));
// Create a DLL internal thread object
g_thread_handle = :: CreateThread (null, 0,
Insidedll_threadProc, (LPVOID) 0, 0, & (g_thread_id));
// Wait for the thread that is just created to complete the relevant operation
:: WaitForsingleObject (g_hevent, infinite);
// Remove resources
:: closehandle (g_thread_handle);
g_thread_id = 0;
g_thread_handle = NULL;
:: CloseHandle (g_hevent);
g_hevent = NULL;
}
Break;
Case DLL_PROCESS_DETACH:
// DLL is uninstalling from the process address space
Break;
}
Return True;
}
// ----------------------------------
If you debug such a program, you can see the thread process within the DLLMAIN inside, and there is no printed "---- Operations .----" statement in the Output window. Visible thread function in Insidedll_threadproc does not have an opportunity to run.
In combination with the order of DLLMain, the answer is very simple. During the program run, the first thread causes the LoadLibrary call caused the operating system to acquire the process mutual exclusive object and call the DLLMain with the DLL_PROCESS_ATTACH value. The DLL's dllmain function generates a second thread. Whenever you generate a new thread, the operating system will get a process mutual exclusive object so that it can call the DLL_THREAD_ATTACH value to call each loaded DLL MAIN function. In this particular program, the second thread blocks because the first thread maintains a process mutually exclusive object. Unfortunately, the first thread then calls WaitForsingleObject to confirm that the second thread can do some operations correctly. Because the second thread is blocked on the process mutex, this process mutually exclusive object is also held by the first thread, and the first thread is waiting to be blocked, and the result will cause death. lock. As shown below.
In addition, the DisabletReadlibraryCalls function does not relieve this deadlock, and the related reasons have more detailed description in the "Windows Core Programming" book, and will not be described here.
2.2, why not completely exit when uninstalling a DLL?
It is estimated that many people know that multi-threaded chains in the DLL process are because of the order of DLLMain, but few people understand that multi-line interlocking in the DLL process is also due to the same reason. For example, if the code of a DLL DLLMAIN is written in the following form, and if there is at least one DLL DLLMAIN in the process, the DLLTHREADLIBRARRARLS function is not called, then the DLL internal thread is not completely in the process of DLLMain. Exited error.
// ----------------------------------
Handle g_thread_handle = null; // The handle of the DLL internal thread DWORD g_thread_id = 0; // The ID of the DLL internal thread
Handle g_hevent = null; / / Handle of the answer event
DWORD WINAPI INSIDEDLL_THREADPROC (LPVOID P)
{
While (1) {
/ / Exit the thread function when you receive the notification
DWORD RET = :: waitforsingleObject (g_hevent, infinite);
IF (wait_timeout = = RET || WAIT_Object_0 = = RET) Break;
}
Return True;
}
Bool apientry dllmain (Handle Hmodule,
DWORD UL_REASON_FOR_CALL,
LPVOID LPRESERVED
)
{
Switch (ul_reason_for_call)
{
Case DLL_Process_attach:
// thread is being mapped to the process address space
{
// Create an event object used by threads within a DLL
g_hevent = :: CreateEvent (Null, False, False, _t ("Hello11));
// Create thread objects within a DLL
g_thread_handle = :: CreateThread (null, 0,
Insidedll_threadProc, (LPVOID) 0, 0, & (g_thread_id));
// Prohibition of the line library call,
DisableThreadLibraryCalls (Hinstance) hmodule;
}
Break;
Case DLL_PROCESS_DETACH:
// DLL is uninstalling from the process address space
{
/ / Notify the internal thread g_thread_handle exit
:: setEvent (g_hevent);
// Wait for internal thread g_thread_handle to exit
:: WaitforsingleObject (g_thread_handle, infinite);
// Remove resources
:: closehandle (g_thread_handle);
g_thread_id = 0;
g_thread_handle = NULL;
:: CloseHandle (g_hevent);
g_hevent = NULL;
}
Break;
}
Return True;
}
// ----------------------------------
The flow of the above code is like this:
(1) When loading the DLL, create a thread g_thread_handle and event object g_hevent, and thread g_thread_handle waits in the event object g_hevent.
(2) When uninstalling the DLL, the WaitForsingleObject function waits for the WaitForsingleObject function waits until the WaitForsingleObject function is called. If the thread g_thread_handle terminates, the clerution work is performed.
However, if you debug such a program, it will find that the program does not quit when exiting, waiting for a long time and has not been exited.
Check out the thread call stack window, notice that the program is waiting for the thread g_thread_handle inside the DLLMAIN. Although thread g_thread_handle's thread function has returned, the entire G_Thread_Handle thread has not been completely terminated in NTDLL.DLL of the operating system, causing the entire DLL to be released smoothly. Thread g_thread_handle why is there not fully exited?
It turns out that the system does not cancel it immediately when the thread function returns. Instead, the system is to take out this upcoming thread that allows it to call all the DLL_THREAD_DETACH values with the mapped DLL, and the DLLMAIN function is not called the DisableThreadLibraryCalls function. DLL_THREAD_DETACH Notification tells all DLLs to perform clear operations for each thread, for example, DLL version of the C / C runtime library to release it to manage data blocks of multi-threaded applications. The DisabletReadlibraryCalls function tells the system that a specific DLL DLLMAIN function does not have to receive DLL_THREAD_ATTACH and DLL_THREAD_DETACH notifications.
However, the system is the DLLMain function that calls the DLL sequence.
When the thread function returns, there is a DLLMAIN function that is not called the DisabletReadlibraryCalls function in the system check process. If there is, the system is called the WaitForsingleObject function in the thread in the thread in the thread. Once this will terminate the running thread possesses the process mutually exclusive object, the system allows the thread to use DLL_THREAD_DETACH to call each DLL MAIN function that does not call the DisabletReadlibraryCalls function. Thereafter, the system releases the ownership of the process mutually exclusive object.
In the application described in this example, the exit of the process causes an operating system acquisition process mutex to cause the operating system to call DLLMAIN (). The DLL's DLLMAIN () function notification thread g_thread_handle terminates the run. Whenever the process terminates a thread, the operating system will get a process mutual exclusive object, so that it can notify the DLL_THREAD_DETACH notification to call each loaded DLLMAIN function that calls the DisabletReadlibraryCalls function. In this particular program, thread g_thread_handle blocks after returning, because CMYSINGLETON's DLLMAIN (), the threads in which the process is maintained. Unfortunately, the thread where the Dllmain is then called WaitForsingleObject to confirm if the g_thread_handle thread is completely terminated. Because the g_thread_handle thread is blocked on the process mutex, this process mutual exclusive object is also held by the DLLMAIN thread. The DLLMAIN thread is waiting for the g_thread_handle thread and it is also blocked, and the result causes a deadlock. As shown below:
Note that from Figure 2, it can be seen that if all DLLs in the current process calls the DisabletReadLibraryCalls function, then the DLL in the above code can also exit normally. I have written a program, in addition to loading a problematic DLL does not load other DLLs (except for the system DLL), the program can stand up.
3, conclusion
Obviously a lesson is to avoid any wait * within DLLMain. But the problem with process mutual exclusive object is not limited to the wait * function. The operating system is acquired in the background to get the process mutual exclusive object in a function such as CreateProcess, GetModuleFileName, GetProcaddress, WGLmakecurrent, LoadLibrary, and Freeelibrary, so these functions should not be called in DLLMain. Because the DLLMAIN gets the process mutual exclusive object, only one thread can perform DLLMAIN at a time. The FinalConstruct function and FinalRelease functions of ATL Singleton are called by DLLMAIN, respectively, in response to DLL_PROCESS_ATTACH and DLL_PROCESS_DETACH, so it is also possible to pay also attention to the problems described herein.