DllMain 入口点

动态链接库的可选入口点 (DLL) 。 当系统启动或终止进程或线程时,它会使用进程的第一个线程为每个加载的 DLL 调用入口点函数。 当 DLL 使用 LoadLibraryFreeLibrary 函数加载或卸载 DLL 时,系统还会调用该函数的入口点函数。

示例

BOOL WINAPI DllMain(
    HINSTANCE hinstDLL,  // handle to DLL module
    DWORD fdwReason,     // reason for calling function
    LPVOID lpvReserved )  // 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:
        
            if (lpvReserved != nullptr)
            {
                break; // do not do cleanup if process termination scenario
            }
            
         // Perform any necessary cleanup.
            break;
    }
    return TRUE;  // Successful DLL_PROCESS_ATTACH.
}

这是 动态链接库Entry-Point函数的示例。

警告

对于在 DLL 入口点中可以安全地执行的操作,通常存在很大限制。 请参阅有关在 DllMain 中调用不安全的特定 Windows API 的 一般最佳做法 。 如果需要进行复杂的初始化,可在 DLL 的初始化函数中完成。 你可以要求应用程序在 DllMain 运行之后以及调用 DLL 中的其他任何函数之前调用初始化函数。

语法

BOOL WINAPI DllMain(
  _In_ HINSTANCE hinstDLL,
  _In_ DWORD     fdwReason,
  _In_ LPVOID    lpvReserved
);

参数

hinstDLL [in]

DLL 模块的句柄。 该值是 DLL 的基址。 DLL 的 HINSTANCE 与 DLL 的 HMODULE 相同,因此可以在调用需要模块句柄的函数时使用 hinstDLL

fdwReason [in]

指示调用 DLL 入口点函数的原因代码。 此参数的取值可为下列值之一:

含义
DLL_PROCESS_ATTACH
1
由于启动进程或调用 LoadLibrary,DLL 正在加载到当前进程的虚拟地址空间中。 DLL 可以利用此机会初始化任何实例数据或使用 TlsAlloc 函数来分配线程本地存储 (TLS) 索引。
lpvReserved 参数指示 DLL 是静态加载还是动态加载。
DLL_PROCESS_DETACH
0
DLL 正从调用进程的虚拟地址空间中卸载,因为它加载失败,或者引用计数已达到零, (进程每次调用 LoadLibrary) 时,都会终止或调用 FreeLibrary
lpvReserved 参数指示是由于 FreeLibrary 调用、加载失败还是进程终止而卸载 DLL。
DLL 可以使用此机会调用 TlsFree 函数,以释放使用 TlsAlloc 分配的任何 TLS 索引,并释放任何线程本地数据。
请注意,接收 DLL_PROCESS_DETACH 通知的线程不一定是接收 DLL_PROCESS_ATTACH 通知的同一线程。
DLL_THREAD_ATTACH
2
当前进程正在创建新线程。 发生这种情况时,系统会调用当前附加到进程的所有 DLL 的入口点函数。 调用是在新线程的上下文中进行的。 DLL 可以使用此机会初始化线程的 TLS 槽。 调用具有 DLL_PROCESS_ATTACH 的 DLL 入口点函数的线程不调用具有 DLL_THREAD_ATTACH的 DLL 入口点函数。
请注意,DLL 的入口点函数仅由进程加载 DLL 后创建的线程调用此值。 使用 LoadLibrary 加载 DLL 时,现有线程不会调用新加载 DLL 的入口点函数。
DLL_THREAD_DETACH
3
线程正在完全退出。 如果 DLL 已将指向已分配内存的指针存储在 TLS 槽中,则应使用此机会释放内存。 系统调用具有此值的所有当前加载 DLL 的入口点函数。 调用是在退出线程的上下文中进行的。

lpvReserved [in]

如果 fdwReasonDLL_PROCESS_ATTACH,则动态加载 的 lpvReservedNULL ,静态加载为非 NULL。

如果 fdwReasonDLL_PROCESS_DETACH,则如果调用 FreeLibrary 或 DLL 加载失败,则 lpvReservedNULL;如果进程终止,则为 NULL

返回值

当系统调用具有DLL_PROCESS_ATTACH值的 DllMain 函数时,如果函数成功,则返回 TRUE;如果初始化失败,则返回 FALSE。 如果调用 DllMain 时返回值为 FALSE,因为进程使用 LoadLibrary 函数,则 LoadLibrary 返回 NULL。 (系统立即使用DLL_PROCESS_DETACH调用入口点函数并卸载 DLL.) 如果在进程初始化期间调用 DllMain 时返回值为 FALSE,则进程将终止并显示错误。 要获得更多的错误信息,请调用 GetLastError

当系统使用除DLL_PROCESS_ATTACH以外的任何值调用 DllMain 函数时,将忽略返回值。

注解

DllMain 是库定义的函数名称的占位符。 必须在生成 DLL 时指定使用的实际名称。 有关详细信息,请参阅开发工具随附的文档。

在初始进程启动或调用 LoadLibrary 之后,系统会扫描加载的 DLL 列表以查找进程。 对于尚未使用 DLL_PROCESS_ATTACH 值调用的每个 DLL,系统调用 DLL 的入口点函数。 此调用是在导致进程地址空间更改的线程的上下文中进行的,例如进程的主线程或调用 LoadLibrary 的线程。 对入口点的访问由系统按进程范围序列化。 DllMain 中的线程保留加载程序锁,因此无法动态加载或初始化其他 DLL。

如果 DLL 的入口点函数在 DLL_PROCESS_ATTACH 通知后返回 FALSE,则会收到 DLL_PROCESS_DETACH 通知,并立即卸载 DLL。 但是,如果 DLL_PROCESS_ATTACH 代码引发异常,入口点函数将不会收到 DLL_PROCESS_DETACH 通知。

在某些情况下,即使从未使用线程 的DLL_THREAD_ATTACH 调用入口点函数,也为终止线程调用入口点函数:

  • 线程是进程中的初始线程,因此系统会使用 DLL_PROCESS_ATTACH 值调用入口点函数。
  • 调用 LoadLibrary 函数时,线程已在运行,因此系统从未为其调用入口点函数。

当由于 DLL 加载失败、进程终止或对 FreeLibrary 的调用而从进程卸载 DLL 时,系统不会使用进程的单个线程 的DLL_THREAD_DETACH 值调用 DLL 的入口点函数。 DLL 仅发送 DLL_PROCESS_DETACH 通知。 DLL 可以利用此机会清理 DLL 已知的所有线程的所有资源。

处理 DLL_PROCESS_DETACH时,只有在动态卸载 DLL (lpvReserved 参数为 NULL) 时,DLL 才应释放诸如堆内存之类的资源。 如果进程正在终止 (lpvReserved 参数为非 NULL) ,则进程中的所有线程(当前线程除外)已退出,或者已由 对 ExitProcess 函数的调用显式终止,这可能会使某些进程资源(如堆)处于不一致状态。 在这种情况下,DLL 无法清理资源。 相反,DLL 应允许操作系统回收内存。

如果通过调用 TerminateProcessTerminateJobObject 终止进程,该进程的 DLL 不会收到 DLL_PROCESS_DETACH 通知。 如果通过调用 TerminateThread 终止线程,该线程的 DLL 不会收到 DLL_THREAD_DETACH 通知。

入口点函数应仅执行简单的初始化或终止任务。 它不得调用 LoadLibraryLoadLibraryEx 函数 (或调用这些函数的函数) ,因为这可能会在 DLL 加载顺序中创建依赖项循环。 这可能会导致在系统执行初始化代码之前使用 DLL。 同样,入口点函数不得调用 FreeLibrary 函数 (或在进程终止期间调用 FreeLibrary) ,因为这可能会导致在系统执行终止代码后使用 DLL。

由于调用入口点函数时,可以保证在进程地址空间中加载Kernel32.dll,因此调用Kernel32.dll中的函数不会在执行初始化代码之前使用 DLL。 因此,入口点函数可以在不加载其他 DLL 的Kernel32.dll调用函数。 例如, DllMain 可以创建 同步对象 ,例如关键部分和互斥体,并使用 TLS。 遗憾的是,Kernel32.dll中没有全面的安全函数列表。

调用需要除Kernel32.dll以外的 DLL 的函数可能会导致难以诊断的问题。 例如,调用 User、Shell 和 COM 函数可能会导致访问冲突错误,因为某些函数加载其他系统组件。 相反,在终止期间调用此类函数可能会导致访问冲突错误,因为相应的组件可能已被卸载或未初始化。

由于 DLL 通知已序列化,入口点函数不应尝试与其他线程或进程通信。 因此,可能会发生死锁。

有关编写 DLL 时的最佳做法的信息,请参阅 动态链接库最佳做法

如果 DLL 与 C 运行时库 (CRT) 链接,CRT 提供的入口点将调用全局和静态 C++ 对象的构造函数和析构函数。 因此, DllMain 的这些限制也适用于构造函数和析构函数以及从它们调用的任何代码。

在接收DLL_PROCESS_ATTACH时,请考虑调用 DisableThreadLibraryCalls,除非 DLL 与静态 C 运行时库 (CRT) 链接。

要求

要求
最低受支持的客户端
Windows XP [仅限桌面应用]
最低受支持的服务器
Windows Server 2003 [仅限桌面应用]
标头
Process.h

另请参阅

动态链接库Entry-Point函数

动态链接库函数

FreeLibrary

GetModuleFileName

LoadLibrary

TlsAlloc

TlsFree

DisableThreadLibraryCalls