本节介绍使用DLL入口点函数设置线程本地存储(TLS)索引,为多线程进程的每个线程提供私有存储。
入门点函数使用TlsAlloc函数在进程加载DLL时分配TLS索引。然后,每个线程都可以使用此索引来存储指向自己的内存块的指针。
当使用DLL_PROCESS_ATTACH值调用入门点函数时,代码执行以下操作:
1.使用TlsAlloc函数分配TLS索引。
2.分配一个内存块,由进程的初始线程专门使用。
3.在调用TlsSetValue函数时使用TLS索引来存储指向分配的内存的指针。
每次进程创建一个新线程时,入口点函数都使用DLL_THREAD_ATTACH值来调用。然后,入口点函数为新线程分配一个内存块,并使用TLS索引存储一个指针。每个线程可以使用TLS索引来调用TlsGetValue来检索指向自己的内存块的指针。
当线程终止时,使用DLL_THREAD_DETACH值调用入口点函数,并释放该线程的内存。当进程终止时,使用DLL_PROCESS_DETACH值调用入口点函数,并释放TLS索引中指针引用的内存。
TLS索引存储在全局变量中,使其可用于所有DLL函数。以下示例假定DLL的全局数据不是共享的,因为每个加载DLL的进程的TLS索引不一定相同。
static DWORD dwTlsIndex; //共享内存的地址
// DllMain()是此DLL的入门点函数。
BOOL DllEntryPoint(HINSTANCE hinstDLL, // DLL模块句柄
DWORD fdwReason, //原因叫
LPVOID lpvReserved) //保留
{
LPVOID lpvData;
BOOL fIgnore;
switch(fdwReason){
//由于进程,DLL正在加载
//初始化或调用LoadLibrary。
case DLL_PROCESS_ATTACH:
//分配一个TLS索引。
if ((dwTlsIndex = TlsAlloc()) == 0xFFFFFFFF)
return FALSE;
//无中断:初始化第一个线程的索引。
//附加的进程创建一个新的线程。
case DLL_THREAD_ATTACH:
//初始化此线程的TLS索引。
lpvData = (LPVOID) LocalAlloc(LPTR, 256);
if (lpvData != NULL)
fIgnore = TlsSetValue(dwTlsIndex, lpvData);
break;
//附加进程的线程终止。
case DLL_THREAD_DETACH:
//释放此线程分配的内存。
lpvData = TlsGetValue(dwTlsIndex);
if (lpvData != NULL)
LocalFree((HLOCAL) lpvData);
break;
//由于进程终止或调用FreeLibrary DLL卸载。
case DLL_PROCESS_DETACH:
//释放此线程分配的内存。
lpvData = TlsGetValue(dwTlsIndex);
if (lpvData != NULL)
LocalFree((HLOCAL) lpvData);
//释放TLS索引。
TlsFree(dwTlsIndex);
break;
默认:
break;
}
return TRUE;
UNREFERENCED_PARAMETER(hinstDLL);
UNREFERENCED_PARAMETER(lpvReserved);
}
当进程使用与此DLL的加载时间链接时,入口点功能足以管理线程本地存储。在使用运行时链接的过程中可能会出现问题,因为在调用LoadLibrary函数之前,不存在线程调用入口点函数,因此不为这些线程分配TLS内存。以下示例通过检查TlsGetValue函数返回的值并分配内存来解决此问题,如果该值指示此线程的TLS插槽未设置。
LPVOID lpvData;
//检索当前线程的数据指针。
lpvData = TlsGetValue(dwTlsIndex);
//如果为NULL,则为此线程分配内存。
if (lpvData == NULL) {
lpvData = (LPVOID) LocalAlloc(LPTR, 256);
if (lpvData != NULL)
TlsSetValue(dwTlsIndex, lpvData);
}