DLL 可以选择指定入口点函数。 如果存在,则每当进程或线程加载或卸载 DLL 时,系统都调用入口点函数。 它可用于执行简单的初始化和清理任务。 例如,它可以在创建新线程时设置线程本地存储,并在线程终止时对其进行清理。
如果要将 DLL 与 C 运行时库链接,它可能会为你提供入口点函数,并允许你提供单独的初始化函数。 有关详细信息,请查看运行时库的文档。
如果要提供自己的入口点,请参阅 DllMain 函数。 DllMain 的名称 是用户定义的函数的占位符。 必须指定生成 DLL 时使用的实际名称。 有关详细信息,请参阅开发工具附带的文档。
调用 Entry-Point 函数
每当发生以下任一事件时,系统都调用入口点函数:
- 进程加载 DLL。 对于使用加载时动态链接的进程,在进程初始化期间加载 DLL。 对于使用运行时链接的进程,在 LoadLibrary 或 LoadLibraryEx 返回之前加载 DLL。
- 进程卸载 DLL。 当进程终止或调用 FreeLibrary 函数并且引用计数变为零时,将卸载 DLL。 如果进程因 TerminateProcess 或 TerminateThread 函数而终止,则系统不会调用 DLL 入口点函数。
- 在加载 DLL 的进程中创建一个新线程。 可以使用 DisableThreadLibraryCalls 函数在创建线程时禁用通知。
- 加载 DLL 的进程线程正常终止,不使用 TerminateThread 或 TerminateProcess。 当进程卸载 DLL 时,对于整个进程,入口点函数只调用一次,而不是针对进程的每个现有线程调用一次。 可以使用 DisableThreadLibraryCalls 在线程终止时禁用通知。
一次只能调用一个线程来调用入口点函数。
系统在导致调用函数的进程或线程的上下文中调用入口点函数。 这允许 DLL 使用其入口点函数在调用进程的虚拟地址空间中分配内存,或打开进程可访问的句柄。 入口点函数还可以使用线程本地存储(TLS)分配专用到新线程的内存。 有关线程本地存储的详细信息,请参阅 线程本地存储。
Entry-Point 函数定义
必须使用标准调用调用约定声明 DLL 入口点函数。 如果未正确声明 DLL 入口点,则不会加载 DLL,并且系统会显示一条消息,指示必须使用 WINAPI 声明 DLL 入口点。
在函数正文中,可以处理以下方案(调用 DLL 入口点)的任意组合:
- 进程加载 DLL(DLL_PROCESS_ATTACH)。
- 当前进程创建新的线程(DLL_THREAD_ATTACH)。
- 线程正常退出(DLL_THREAD_DETACH)。
- 进程卸载 DLL (DLL_PROCESS_DETACH)。
入口点函数应仅执行简单的初始化任务。 它不得调用 LoadLibrary 或 LoadLibraryEx 函数(或调用这些函数的函数),因为这可能会在 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 的返回值会导致 LoadLibrary 或 LoadLibraryEx 函数返回 NULL,指示失败。 (系统立即使用 DLL_PROCESS_DETACH 调用入口点函数并卸载 DLL。当出于任何其他原因调用函数时,将忽略入口点函数的返回值。