DllMain 入口点

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

如果DLL_PROCESS_ATTACH fdwReason则 lpvReserved 对于动态加载为 NULL,对于静态加载为非 NULL。

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

返回值

当系统使用DLL_PROCESS_ATTACH值调用 DllMain 函数时,如果成功,则函数返回 TRUE;如果初始化失败,则返回 FALSE。 如果由于进程使用 LoadLibrary 函数而调用 DllMain 时返回值为 FALSE则 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 与静态 C 运行时库 (CRT) 链接,否则在接收DLL_PROCESS_ATTACH时,请考虑调用 DisableThreadLibraryCalls

要求

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

另请参阅

动态链接库Entry-Point函数

动态链接库函数

FreeLibrary

GetModuleFileName

LoadLibrary

TlsAlloc

TlsFree

DisableThreadLibraryCalls