Dynamic-Link库Entry-Point函数

DLL 可以选择指定入口点函数。 如果存在,则每当进程或线程加载或卸载 DLL 时,系统会调用入口点函数。 它可用于执行简单的初始化和清理任务。 例如,它可以在创建新线程时设置线程本地存储,并在线程终止时进行清理。

如果将 DLL 与 C 运行时库链接,它可能会提供入口点函数,并允许你提供单独的初始化函数。 有关详细信息,请查看运行时库的文档。

如果要提供自己的入口点,请参阅 DllMain 函数。 名称 DllMain 是用户定义的函数的占位符。 必须指定生成 DLL 时使用的实际名称。 有关详细信息,请参阅开发工具附带的文档。

调用 Entry-Point 函数

每当发生以下任一事件时,系统都调用入口点函数:

  • 进程加载 DLL。 对于使用加载时动态链接的进程,DLL 在进程初始化期间加载。 对于使用运行时链接的进程,DLL 在 LoadLibraryLoadLibraryEx 返回之前加载。
  • 进程卸载 DLL。 当进程终止或调用 FreeLibrary 函数且引用计数变为零时,将卸载 DLL。 如果进程由于 TerminateProcessTerminateThread 函数而终止,则系统不会调用 DLL 入口点函数。
  • 在已加载 DLL 的进程中创建一个新线程。 可以使用 DisableThreadLibraryCalls 函数在创建线程时禁用通知。
  • 加载 DLL 的进程线程正常终止,不使用 TerminateThreadTerminateProcess。 当进程卸载 DLL 时,入口点函数仅为整个进程调用一次,而不是为进程的每个现有线程调用一次。 可以使用 DisableThreadLibraryCalls 在线程终止时禁用通知。

一次只能有一个线程可以调用入口点函数。

系统在导致调用函数的进程或线程的上下文中调用入口点函数。 这允许 DLL 使用其入口点函数在调用进程的虚拟地址空间中分配内存,或打开进程可访问的句柄。 入口点函数还可以通过使用线程本地存储 (TLS) 为新线程分配专用的内存。 有关线程本地存储的详细信息,请参阅 线程本地存储

Entry-Point 函数定义

必须使用标准调用调用约定声明 DLL 入口点函数。 如果未正确声明 DLL 入口点,则不会加载 DLL,并且系统会显示一条消息,指示必须使用 WINAPI 声明 DLL 入口点。

在函数主体中,可以处理调用 DLL 入口点的以下方案的任意组合:

  • 进程DLL_PROCESS_ATTACH) 加载 DLL (
  • 当前进程 (DLL_THREAD_ATTACH) 创建新的 线程。
  • 线程通常 (DLL_THREAD_DETACH) 退出。
  • 进程DLL_PROCESS_DETACH) 卸载 DLL (

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

由于Kernel32.dll保证在调用入口点函数时加载到进程地址空间中,因此调用 Kernel32.dll 中的函数不会导致在执行其初始化代码之前使用 DLL。 因此,入口点函数可以创建 同步对象 (如关键部分和互斥体),并使用 TLS,因为这些函数位于Kernel32.dll。 例如,调用注册表函数是不安全的,因为它们位于 Advapi32.dll。

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

以下示例演示如何构造 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 入口点函数时,该函数返回 TRUE 以指示成功。 对于使用加载时链接的进程,返回值 FALSE 会导致进程初始化失败,并且进程终止。 对于使用运行时链接的进程,返回值 FALSE 会导致 LoadLibraryLoadLibraryEx 函数返回 NULL,表示失败。 (系统使用 DLL_PROCESS_DETACH 立即调用入口点函数,并卸载 DLL.) 当出于任何其他原因调用该函数时,将忽略入口点函数的返回值。