在动态链接库中使用线程本地存储

【勇芳软件工作室】汉化HomePreviousNext

本节介绍使用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);

}