Winapi 只有动态卸载DLL时,DLL才应释放堆内存?

Winapi 只有动态卸载DLL时,DLL才应释放堆内存?,winapi,visual-c++,dll,entry-point,dllmain,Winapi,Visual C++,Dll,Entry Point,Dllmain,问题的目的:对现实进行检查 这是一个“常识”,你不应该做太多的事情,有些事情你绝对不能做 我现在在文档中偶然发现了一块新的宝石,这对我来说毫无意义:(我的) 当处理DLL\u PROCESS\u DETACH时,DLL应该释放资源,例如 仅当动态卸载DLL时堆内存( lpReserved参数为空)。如果进程正在终止,则 lpvReserved参数为非NULL),进程中的所有线程 当前线程已退出或已显式退出 通过调用函数终止,该函数可能会离开 某些进程资源,例如处于不一致状态的堆。在这个 在这种情

问题的目的:对现实进行检查

这是一个“常识”,你不应该做太多的事情,有些事情你绝对不能做

我现在在文档中偶然发现了一块新的宝石,这对我来说毫无意义:(我的)

当处理
DLL\u PROCESS\u DETACH
时,DLL应该释放资源,例如 仅当动态卸载DLL时堆内存(
lpReserved
参数为空)。如果进程正在终止,则 lpvReserved参数为非NULL),进程中的所有线程 当前线程已退出或已显式退出 通过调用函数终止,该函数可能会离开 某些进程资源,例如处于不一致状态的堆。在这个 在这种情况下,DLL清理资源是不安全的。相反 DLL应该允许操作系统回收内存

<>因为在DLMNE/DELCH中清理全局C++对象,这意味着全局C++对象不能释放任何动态内存,因为堆可能处于不一致状态。当DLL“静态链接”到可执行文件时。/当然不是我在那里看到的——全局C++对象(IFF)有各种(我们的和第三方)库在它们的析构函数中分配和释放。(除其他订购错误外,o.c.)

那么,此警告针对的具体技术问题是什么?


由于该段提到线程终止,当某些线程没有正确清理时,是否会出现堆损坏问题?

通常,
ExitProcess
API执行以下操作:

  • 进入装载机锁定临界区
  • lock主进程堆(返回者)通过
    (GetProcessHeap())
    (当然可以通过
    RtlLockHeap
    )进行锁定(这是避免死锁的非常重要的一步)
  • 然后终止进程中的所有线程,当前线程除外(通过调用
    NtTerminateProcess(0,0)
  • 然后调用
    LdrShutdownProcess
    -在这个api加载程序中,按加载的模块列表遍历并发送
    DLL\u PROCESS\u DETACH
    ,其中
    lpvReserved
    为非空
  • 最后调用
    NtTerminateProcess(NtCurrentProcess(),ExitCode)
    ,终止进程
这里的问题是线程在任意位置终止。例如,线程可以从任何堆中分配或释放内存,并在其终止时位于堆关键部分内。因此,如果在
DLL\u PROCESS\u DETACH
过程中,代码试图从同一堆中释放一个块,那么它在尝试进入该堆的关键部分时会死锁(当然,如果堆实现使用它)

请注意,这不会影响主进程堆,因为我们在终止所有线程(当前线程除外)之前为其调用
HeapLock
。这样做的目的是:我们在这个调用中等待,直到所有其他线程从进程堆关键部分退出,并且在我们获得关键部分之后,其他线程都不能进入它——因为主进程堆被锁定

因此,当我们在锁定主堆后终止线程时,我们可以确保主堆关键部分或堆结构中没有处于不一致状态的其他被杀死的线程。感谢
RtlLockHeap
调用。但这只与主进程堆有关。进程中的任何其他堆都不会被锁定。因此,它们可能在
DLL\u进程\u分离期间处于不一致的状态,或者可以由已经终止的线程独占获取

因此,在这里使用
HeapFree
作为
GetProcessHeap
或说
LocalFree
是安全的(但没有文档记录)

如果在进程终止期间调用了
DllMain
,则对任何其他堆使用
HeapFree
是不安全的

另外,如果多个线程使用另一个自定义数据结构,它可能处于不一致的状态,因为另一个线程(可以使用它)在任意点终止


因此,此注释警告,当lpvReserved参数为非NULL时(即在进程终止期间调用DllMain的意思),您需要特别小心地清理资源。无论如何,当进程终止时,操作系统将释放所有内部内存分配。

作为RbMm极好答案的补充,我将添加一段引用自
ExitProcess
,它比DllMain文档做得好得多,用于解释堆操作(或任何操作,实际上)可能受到危害的原因:

如果进程中的一个终止线程持有锁,则 加载的DLL之一中的DLL分离代码尝试获取相同的DLL 锁定,然后调用
ExitProcess
会导致死锁。相反,如果 进程通过调用 进程已附加到,但未通知进程终止。 因此,如果您不知道系统中所有线程的状态 进程,调用
TerminateProcess
比调用
ExitProcess
更好。注 从应用程序的主函数返回将导致 调用
ExitProcess

因此,这一切归结为:如果应用程序有可能持有任何锁的“失控”线程,(CRT)堆锁就是一个突出的例子,那么当您需要访问“失控”线程正在使用的相同结构(例如堆)时,您在关机期间会遇到一个大问题


这表明你应该以一种可控的方式关闭所有线程。

当他们有一个太多的支持电话时,就会添加这种措辞。记录未记录参数的唯一原因。一个示例场景是程序员使用SetUnhandledExceptionFilter(),执行一些对他有用的操作