先看一下使用Delphi开发DLL时如何使用MAIN函数,
通常情况下并不会使用到DLL的MAIN函数,因为delphi的框架已经把Main函数隐藏起来
而工程函数的 begin end 默认就是MAIN函数的DLL_PROCESS_ATTACH事件的处理代码,如需要完整的处理其他事件,
如 DLL_PROCESS_DETACH,DLL_THREAD_ATTACH, DLL_THREAD_DETACH,可在工程文件中做如下处理:
procedure DLLEntryPoint(Reason:DWord); begin case Reason of DLL_PROCESS_ATTACH: StartMyThreadsAndWaitBegin(); // 创建并等待线程开始,这样会导致卡死 DLL_PROCESS_DETACH: StopMyThreadsAndWaitEnd(); // 停止并等待线程结束(或直接结束进程),这样会导致卡死 DLL_THREAD_ATTACH:; DLL_THREAD_DETACH:; end; end; begin DllProc := @DLLEntryPoint; DLLEntryPoint(DLL_PROCESS_ATTACH); end.
其中 DllProc 是SysInit中的全局变量,可简单理解为保存DLL Entry Point入口函数的地址(实际上RTL内部还有InitLib 和StartLib函数,由编译器自动处理)。
以上都是题外话,本文主要说明在DLL入口函数里面创建和退出线程为什么卡死和如何解决的问题。
1)在 DLL_PROCESS_ATTACH 事件中 创建线程 出现卡死的问题
通常情况下在这事件中仅仅是创建并唤醒线程,是不会卡死的,但如果同时有等待线程正式执行的代码,则会卡死,因为在该事件中,任何启动的线程都会由于LdrLoadDll中的LdrpLoaderLock 进入锁定状态而处于等待,无法进入线程函数,所以也就永远无法检测到正式执行的机会。
LdrpLoaderLock是系统的PE Loader的一个重要锁,保证系统资源的安全,而DLL 入口函数是在PE Loader 结束前执行的,LdrInitializeThunk等函数处理PE 映像 到内存中的过程中,LdrpLoaderLock是处于锁定状态的。
所以解决办法就是 在 DLL_PROCESS_ATTACH 事件中,仅创建并唤醒线程即可(此时即使是唤醒了,线程也是处理等待状态),线程函数会在DLL_PROCESS_ATTACH事件结束后才正式执行(实际上如果是通过LoadLibrary加载DLL,则会在LoadLibrary结束前后的某一时刻正式执行)。
2)在DLL_PROCESS_DETACH中结束线程出现卡死的问题
同样的原因,该事件是调用LdrUnloadDll中执行的,LdrpLoaderLock仍然是锁定状态的,而结束线程最终会调用LdrShutdownThread,均会释放PE Loader所维护的系统内部的共同资源(包括PEB 和TEB等模块信息和线程TLS数据等),此类共同资源刚好都是使用LdrpLoaderLock进行同步,所以在DLL_PROCESS_DETACH中调用ExitThread->LdrShutdownThread,必然导致卡死。
另外有一个特殊的现象,就是DLL_PROCESS_DETACH事件中,线程处于挂起状态,这是因为系统分配线程执行时间片的过程中由于PE Loader有资源处于锁定而导致线程无法进行下一个时间片,最终表现为线程函数处于假死状态,此状态基本上等同于线程的挂起(suspend)状态。
解决办法同样是避免在 DLL_PROCESS_DETACH事件中结束线程,那么我们可以在该事件中,创建并唤醒另外一个线程,在该新的线程里,结束需要结束的线程,并在完成后结束自身即可。
总体上代码如下:
procedure DLLEntryPoint(Reason:DWord); begin case Reason of DLL_PROCESS_ATTACH: TThread.CreateAnonymousThread(procedure begin StartMyThreadsAndWaitBegin(); end).Start; DLL_PROCESS_DETACH: TThread.CreateAnonymousThread(procedure begin StopMyThreadsAndWaitEnd(); end).Start; DLL_THREAD_ATTACH:; DLL_THREAD_DETACH:; end; end; begin DllProc := @DLLEntryPoint; DLLEntryPoint(DLL_PROCESS_ATTACH); end.
注: 此问题是属于系统多线程处理的问题,或者说是属于Windows API的使用方法问题,使用其他VB VC等开发的人员也可以参考此解决方法。