如何解决在DLL的入口函数中建立或结束线程时卡死

先看一下使用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函数,由编译器自动处理)。spa

以上都是题外话,本文主要说明在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事件中结束线程,那么咱们能够在该事件中,建立并唤醒另一个线程,在该新的线程里,结束须要结束的线程,并在完成后结束自身便可。惟一须要注意的是,一旦DLL_PROCESS_DETACH结束,内存中与DLL相关的PE映像资源可能会被释放掉,因此在后续的操做中尽可能不要再对原来的数据进行操做,不然容易致使内存溢出(但其实释放与否是由内核决定的,也许未来通过某一个版本的补丁后,相关资源仍然会保留在内存能够使用)。

      提醒: 标准的作法仍是建议遵循MS的规则,不要在DLL入口函数中作线程相关的建立和释放操做。

 

整体上代码以下:

复制代码

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等开发的人员也能够参考此解决方法。

相关文章
相关标签/搜索