| 每个DLL都可以有一个入口点函数DllMain,系统会在不同的时刻调用此函数。以下是DllMain的一般形式: 
                           
                            | BOOL WINAPI DllMain(HINSTANCE hinstDLL,  // handle to DLL module
 DWORD fdwReason,     // reason for calling function
 LPVOID lpReserved )  // reserved
 {
 // Perform actions based on the reason for calling.
 switch( fdwReason )
 {
 case DLL_PROCESS_ATTACH:
 // Initialize once for each new process.
 // Return FALSE to fail DLL load.
 break;
  case DLL_THREAD_ATTACH:// Do thread-specific initialization.
 break;
  case DLL_THREAD_DETACH:// Do thread-specific cleanup.
 break;
  case DLL_PROCESS_DETACH:// Perform any necessary cleanup.
 break;
 }
 return TRUE; // Successful DLL_PROCESS_ATTACH.
 }
 |  
                          以上代码摘自MSDN,几乎所有的DllMain都以这种形式呈现。 
                          先来看一下这个函数传递进来的参数: 
                          1、 HINSTANCE hinstDLL 
                          这个参数是该DLL实例的句柄,也就是此DLL映射到进程地址空间后,在该进程地址空间中的位置。 
                          2、 DWORD fdwReason 
                          此参数标示了调用DllMain函数的原因。有四种值,就是函数中case后的取值。各个取值的含义,稍后论述。 
                          3、 LPVOID lpReserved  
                          保留。 
                          现在我们来讨论一下fdwReason的四种取值,这些取值,也直接反映了操作系统会在何种情况下调用DllMain。 
                          1、DLL_PROCESS_ATTACH 
                          当系统第一次将一个DLL映射到进程地址空间中时,会调用DllMain,并为fdwReason传入DLL_PROCESS_ATTACH。 
                          注意,只有在第一次映射的时候,才会这样。如之后,另一线程再次显式加载此DLL,则操作系统只是增加该DLL的使用计数,而不会再次使用DLL_PROCESS_ATTACH来调用DllMain。 
                          对DLL_PROCESS_ATTACH的处理,代表了DLL的初始化。 
                          DllMain的返回值,也是针对DLL_PROCESS_ATTACH消息的。对于其余的三种取值,不起作用。 
                          对于隐式加载,如DllMain返回FALSE,则程序会启动失败。对于显式加载,则会使LoadLibrary返回NULL。 
                          2、DLL_PROCESS_DETACH 
                          当系统将一个DLL从进程地址空间中撤销映射时,则会向DllMain传入DLL_PROCESS_DETACH。我们应当在此处放置一些清理代码。 
                          当使用FreeLibrary时,如该线程的使用计数为0时,操作系统才会使用DLL_PROCESS_DETACH来调用DllMain。如使用计数大于0,则只是单纯的减少该DLL的计数。 
                          3、DLL_THREAD_ATTACH 
                          当进程创建一个线程,则系统会检查当前已映射到该进程空间中的所有DLL映像,并用DLL_THREAD_ATTACH来调用每个DLL的DllMain。 
                          只有当所有DLL都完成了对DLL_THREAD_ATTACH的处理后,新线程才会执行它的线程函数。 
                          另外,主线程不可能用DLL_THREAD_ATTACH来调用DllMain,因为主线程必然是在进程初始化的时候,用DLL_PROCESS_ATTACH调用DllMain的。 
                          4、DLL_THREAD_DETACH 
                          线程若要终止,会调用ExitThread,但是此函数不会立即终止线程,而是会利用DLL_THREAD_DETACH来调用当前进程地址空间中的所有DLL镜像的DllMain. 
                          当每个DLL的DllMain都处理完后,系统才会真正的结束线程。 
                          最后看一下DllMain的序列化调用 
                          举个例子: 
                          进程中有两个线程,A与B。进程的地址空间中,映射了一个名为SomeDll.dll的DLL。两个线程都准备通过CreateThread来创建另两个线程,C和D。 
                          当线程A调用CreateThread来创建线程C的时候,系统会用DLL_THREAD_ATTACH来调用SomeDll.dll的DllMain,当线程C执行其中代码的时候,线程B调用CreateThread来创建线程D。 
                          这时,系统同样会用DLL_THREAD_ATTACH来调用SomeDll.dll的DllMain,这次是让线程D来执行其中的代码。 
                          但是此时,系统会对DllMain执行序列化,它会将线程D挂起,直至线程C执行完DllMain中的代码返回为止。 
                          当C线程执行完DllMain中的代码并返回时,可以继续执行C的线程函数。此时,系统会唤醒线程D,让D执行DllMain中的代码。当返回后,线程D开始执行线程函数。 
                           |