DllMain 入口点

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

示例

BOOL WINAPI DllMain(
    HINSTANCE hinstDLL,  // handle to DLL module
    DWORD fdwReason,     // reason for calling function
    LPVOID lpReserved )  // 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:
         // 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 [中]

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

fdwReason [中]

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

含义
DLL _进程 _ 附加
1
当进程启动时,或作为调用 LoadLibrary的结果,将该 DLL 加载到当前进程的虚拟地址空间。 Dll 可以利用此机会初始化任何实例数据,或使用 TlsAlloc 函数 (TLS) 索引分配线程本地存储。
LpReserved 参数指示是以静态方式还是动态方式加载 DLL。
DLL _处理 _ 分离
0
正在从调用进程的虚拟地址空间中卸载该 DLL,因为它未成功加载或引用计数已达到零 (进程每次调用) LoadLibrary时都已终止或调用 FreeLibrary一次。
LpReserved 参数指示是否正在通过 FreeLibrary调用、无法加载或进程终止来卸载 DLL。
DLL 可以利用此机会调用 TlsFree 函数,以释放使用 TlsAlloc 分配的任何 TLS 索引,并释放任何线程的本地数据。
请注意,接收 dll _ 进程 _ 分离 通知的线程不一定是接收 dll _ 进程 _ 附加 通知的同一个线程。
DLL _线程 _ 附加
2
当前进程正在创建新线程。 发生这种情况时,系统将调用当前附加到进程的所有 Dll 的入口点函数。 在新线程的上下文中进行调用。 Dll 可以利用此机会为线程初始化 TLS 槽。 调用 dll 入口点函数与 dll _ 进程 _ 附加 的线程不会调用 dll 入口点函数与 dll 的 _ _ 附加线程
请注意,使用此值调用 DLL 的入口点函数只是在该进程加载 DLL 之后创建的线程。 使用 LoadLibrary加载 dll 时,现有线程不会调用新加载的 dll 的入口点函数。
DLL _线程 _ 分离
3
线程完全退出。 如果 DLL 已存储一个指向 TLS 槽中分配的内存的指针,则应使用此机会释放内存。 系统调用当前加载的具有此值的所有 Dll 的入口点函数。 在退出线程的上下文中进行调用。

lpvReserved [中]

如果 fdwReasonDLL _ 进程 _ 附加,动态加载的 lpvReservedNULL ,静态负载为非 null。

如果 fdwReasonDLL _ 进程 _ 分离,则在调用 FreeLibrary或 dll 加载失败时, lpvReservednull ; 如果进程正在终止,则为非 NULL

返回值

当系统调用带有 DLL _ 进程 _ 附加 值的 DllMain 函数时,该函数将返回 TRUE ; 如果初始化失败,则返回 FALSE 。 如果调用 DllMain 时返回值为 FALSE ,因为进程使用 LoadLibrary函数, LoadLibrary 将返回 NULL。 (系统立即使用 dll _ 进程 _ 分离 并卸载 dll 来调用入口点函数 ) 。如果在进程初始化期间调用 DllMain 时返回值为 FALSE ,则进程会终止,并出现错误。 要获得更多的错误信息,请调用 GetLastError

当系统用除 DLL _ 进程 _ 附加 以外的任何值调用 DllMain 函数时,将忽略返回值。

备注

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

在初始进程启动期间或对 LoadLibrary的调用后,系统将扫描已加载的进程的列表。 对于尚未通过 dll _ 进程 _ 附加 值调用的每个 dll,系统将调用 dll 的入口点函数。 此调用在导致进程地址空间更改的线程的上下文中进行,例如进程的主线程或调用 LoadLibrary 的线程。 对入口点的访问由系统在进程范围内进行序列化。 DllMain 中的线程保留加载程序锁,因此不能动态加载或初始化其他 dll。

如果 DLL 的入口点函数在 dll _ 进程 _ 附加 通知后返回 FALSE,它将接收 dll _ 进程 _ 分离 通知并立即卸载该 dll。 但是,如果 DLL _ 进程 _ 附加 代码引发异常,则入口点函数将不会收到 DLL _ 进程 _ 分离 通知。

在某些情况下,将为终止线程调用入口点函数,即使从未使用 DLL _ 线程 _ 附加 为线程调用入口点函数也是如此:

  • 线程是进程中的初始线程,因此系统调用入口点函数和 DLL _ 进程 _ 附加 值。
  • LoadLibrary 函数进行调用时,该线程已在运行,因此系统永远不会为其调用入口点函数。

如果 DLL 是从进程中卸载的,因为 DLL 的加载失败、终止进程或对 FreeLibrary的调用,则系统不会使用进程的单个线程的 dll _ 线程 _ 分离 值调用 dll 的入口点函数。 仅发送 dll _ 进程 _ 分离 通知。 Dll 可以利用此机会清理 DLL 已知的所有线程的所有资源。

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

如果通过调用 TerminateProcessTerminateJobObject终止进程,该进程的 Dll 不会接收 dll _ 进程 _ 分离 通知。 如果通过调用 TerminateThread终止线程,该线程的 dll 不会收到 dll _ 线程 _ 分离 通知。

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

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

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

由于 DLL 通知是序列化的,因此入口点函数不应尝试与其他线程或进程进行通信。 这可能会导致死锁。

有关写入 DLL 的最佳实践的信息,请参阅 https://docs.microsoft.com/windows/win32/dlls/dynamic-link-library-best-practices

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

请考虑在接收 dll _ 进程 _ 附加 时调用 DisableThreadLibraryCalls ,除非 dll 与静态 C 运行时库链接 (CRT) 。

要求

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

另请参阅

动态链接库Entry-Point函数

动态链接库函数

FreeLibrary

GetModuleFileName

LoadLibrary

TlsAlloc

TlsFree

DisableThreadLibraryCalls