DLL 可以选择指定入口点函数。 如果存在,则每当进程或线程加载或卸载 DLL 时,系统会调用入口点函数。 它可用于执行简单的初始化和清理任务。 例如,它可以在创建新线程时设置线程本地存储,并在线程终止时进行清理。
如果将 DLL 与 C 运行时库链接,它可能会提供入口点函数,并允许你提供单独的初始化函数。
如果要提供自己的入口点,可以声明DllMain 函数。 注意名称 DllMain 是用户定义的函数的占位符,必须指定生成 DLL 时使用的实际名称,这一点可以在编译器中指定,下面是某个DllMain的例子:
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.
}
调用 DllMain 函数
每当发生以下任一事件时,系统都调用入口点函数:
- 进程加载 DLL。 对于使用加载时动态链接的进程,DLL 在进程初始化期间加载。 对于使用运行时链接的进程,DLL 在 LoadLibrary 或 LoadLibraryEx 返回之前加载;
- 进程卸载 DLL。 当进程终止或调用 FreeLibrary 函数且引用计数变为零时,将卸载 DLL。 如果进程由于 TerminateProcess 或 TerminateThread 函数而终止,则系统不会调用 DLL 入口点函数;
- 在已加载 DLL 的进程中创建一个新线程。 可以使用 DisableThreadLibraryCalls 函数在创建线程时禁用通知;
- 加载 DLL 的进程线程正常终止,不使用 TerminateThread 或 TerminateProcess。 当进程卸载 DLL 时,入口点函数仅为整个进程调用一次,而不是为进程的每个现有线程调用一次。 可以使用 DisableThreadLibraryCalls 在线程终止时禁用通知;
注意一次只能有一个线程可以调用入口点函数,这一点由windows系统保证。
系统在导致调用函数的进程或线程的上下文中调用入口点函数。 这允许 DLL 使用其入口点函数在调用进程的虚拟地址空间中分配内存,或打开进程可访问的句柄。 入口点函数还可以通过使用线程本地存储 (TLS) 为新线程分配专用的内存。
DllMain 函数定义
必须使用标准调用调用约定声明 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 来加载其他系统组件。 相反,在终止期间调用这些函数可能会导致访问冲突错误,因为相应的组件可能已卸载或未初始化。
DllMain函数返回值
当由于加载进程而调用 DLL 入口点函数时,该函数返回 TRUE 以指示成功。 对于使用加载时链接的进程,返回值 FALSE 会导致进程初始化失败,并且进程终止。 对于使用运行时链接的进程,返回值 FALSE 会导致 LoadLibrary 或 LoadLibraryEx 函数返回 NULL,表示失败。 接下来windows系统使用 DLL_PROCESS_DETACH 立即调用入口点函数,并卸载 DLL。当出于任何其他原因调用该函数时,将忽略入口点函数的返回值。