Utilisation du stockage local de threads dans une bibliothèque Dynamic-Link

Cette section montre l’utilisation d’une fonction de point d’entrée DLL pour configurer un index de stockage local de thread (TLS) afin de fournir un stockage privé pour chaque thread d’un processus multithread.

L’index TLS est stocké dans une variable globale, ce qui le rend disponible pour toutes les fonctions DLL. Cet exemple suppose que les données globales de la DLL ne sont pas partagées, car l’index TLS n’est pas nécessairement le même pour chaque processus qui charge la DLL.

La fonction point d’entrée utilise la fonction TlsAlloc pour allouer un index TLS chaque fois qu’un processus charge la DLL. Chaque thread peut ensuite utiliser cet index pour stocker un pointeur vers son propre bloc de mémoire.

Lorsque la fonction de point d’entrée est appelée avec la valeur DLL_PROCESS_ATTACH, le code effectue les actions suivantes :

  1. Utilise la fonction TlsAlloc pour allouer un index TLS.
  2. Alloue un bloc de mémoire à utiliser exclusivement par le thread initial du processus.
  3. Utilise l’index TLS dans un appel à la fonction TlsSetValue pour stocker l’adresse du bloc de mémoire dans l’emplacement TLS associé à l’index.

Chaque fois que le processus crée un thread, la fonction de point d’entrée est appelée avec la valeur DLL_THREAD_ATTACH. La fonction de point d’entrée alloue ensuite un bloc de mémoire pour le nouveau thread et stocke un pointeur vers celui-ci à l’aide de l’index TLS.

Lorsqu’une fonction nécessite l’accès aux données associées à un index TLS, spécifiez l’index dans un appel à la fonction TlsGetValue . Cela récupère le contenu de l’emplacement TLS pour le thread appelant, qui dans ce cas est un pointeur vers le bloc de mémoire pour les données. Lorsqu’un processus utilise la liaison au temps de chargement avec cette DLL, la fonction de point d’entrée est suffisante pour gérer le stockage local du thread. Des problèmes peuvent se produire avec un processus qui utilise la liaison au moment de l’exécution, car la fonction de point d’entrée n’est pas appelée pour les threads qui existent avant l’appel de la fonction LoadLibrary , de sorte que la mémoire TLS n’est pas allouée pour ces threads. Cet exemple résout ce problème en vérifiant la valeur retournée par la fonction TlsGetValue et en allouant la mémoire si la valeur indique que l’emplacement TLS de ce thread n’est pas défini.

Quand chaque thread n’a plus besoin d’utiliser un index TLS, il doit libérer la mémoire dont le pointeur est stocké dans l’emplacement TLS. Lorsque tous les threads ont terminé d’utiliser un index TLS, utilisez la fonction TlsFree pour libérer l’index.

Lorsqu’un thread se termine, la fonction de point d’entrée est appelée avec la valeur DLL_THREAD_DETACH et la mémoire de ce thread est libérée. Lorsqu’un processus se termine, la fonction de point d’entrée est appelée avec la valeur DLL_PROCESS_DETACH et la mémoire référencée par le pointeur dans l’index TLS est libérée.

// The DLL code

#include <windows.h>

static DWORD dwTlsIndex; // address of shared memory
 
// DllMain() is the entry-point function for this DLL. 
 
BOOL WINAPI DllMain(HINSTANCE hinstDLL, // DLL module handle
    DWORD fdwReason,                    // reason called
    LPVOID lpvReserved)                 // reserved
{ 
    LPVOID lpvData; 
    BOOL fIgnore; 
 
    switch (fdwReason) 
    { 
        // The DLL is loading due to process 
        // initialization or a call to LoadLibrary. 
 
        case DLL_PROCESS_ATTACH: 
 
            // Allocate a TLS index.
 
            if ((dwTlsIndex = TlsAlloc()) == TLS_OUT_OF_INDEXES) 
                return FALSE; 
 
            // No break: Initialize the index for first thread.
 
        // The attached process creates a new thread. 
 
        case DLL_THREAD_ATTACH: 
 
            // Initialize the TLS index for this thread.
 
            lpvData = (LPVOID) LocalAlloc(LPTR, 256); 
            if (lpvData != NULL) 
                fIgnore = TlsSetValue(dwTlsIndex, lpvData); 
 
            break; 
 
        // The thread of the attached process terminates.
 
        case DLL_THREAD_DETACH: 
 
            // Release the allocated memory for this thread.
 
            lpvData = TlsGetValue(dwTlsIndex); 
            if (lpvData != NULL) 
                LocalFree((HLOCAL) lpvData); 
 
            break; 
 
        // DLL unload due to process termination or FreeLibrary. 
 
        case DLL_PROCESS_DETACH: 
 
            // Release the allocated memory for this thread.
 
            lpvData = TlsGetValue(dwTlsIndex); 
            if (lpvData != NULL) 
                LocalFree((HLOCAL) lpvData); 
 
            // Release the TLS index.
 
            TlsFree(dwTlsIndex); 
            break; 
 
        default: 
            break; 
    } 
 
    return TRUE; 
    UNREFERENCED_PARAMETER(hinstDLL); 
    UNREFERENCED_PARAMETER(lpvReserved); 
}

// The export mechanism used here is the __declspec(export)
// method supported by Microsoft Visual Studio, but any
// other export method supported by your development
// environment may be substituted.

#ifdef __cplusplus    // If used by C++ code, 
extern "C" {          // we need to export the C interface
#endif

__declspec(dllexport)
BOOL WINAPI StoreData(DWORD dw)
{
   LPVOID lpvData; 
   DWORD * pData;  // The stored memory pointer 

   lpvData = TlsGetValue(dwTlsIndex); 
   if (lpvData == NULL)
   {
      lpvData = (LPVOID) LocalAlloc(LPTR, 256); 
      if (lpvData == NULL) 
         return FALSE;
      if (!TlsSetValue(dwTlsIndex, lpvData))
         return FALSE;
   }

   pData = (DWORD *) lpvData; // Cast to my data type.
   // In this example, it is only a pointer to a DWORD
   // but it can be a structure pointer to contain more complicated data.

   (*pData) = dw;
   return TRUE;
}

__declspec(dllexport)
BOOL WINAPI GetData(DWORD *pdw)
{
   LPVOID lpvData; 
   DWORD * pData;  // The stored memory pointer 

   lpvData = TlsGetValue(dwTlsIndex); 
   if (lpvData == NULL)
      return FALSE;

   pData = (DWORD *) lpvData;
   (*pdw) = (*pData);
   return TRUE;
}
#ifdef __cplusplus
}
#endif

Le code suivant illustre l’utilisation des fonctions DLL définies dans l’exemple précédent.

#include <windows.h> 
#include <stdio.h> 
 
#define THREADCOUNT 4 
#define DLL_NAME TEXT("testdll")

VOID ErrorExit(LPSTR); 

extern "C" BOOL WINAPI StoreData(DWORD dw);
extern "C" BOOL WINAPI GetData(DWORD *pdw);
 
DWORD WINAPI ThreadFunc(VOID) 
{   
   int i;

   if(!StoreData(GetCurrentThreadId()))
      ErrorExit("StoreData error");

   for(i=0; i<THREADCOUNT; i++)
   {
      DWORD dwOut;
      if(!GetData(&dwOut))
         ErrorExit("GetData error");
      if( dwOut != GetCurrentThreadId())
         printf("thread %d: data is incorrect (%d)\n", GetCurrentThreadId(), dwOut);
      else printf("thread %d: data is correct\n", GetCurrentThreadId());
      Sleep(0);
   }
   return 0; 
} 
 
int main(VOID) 
{ 
   DWORD IDThread; 
   HANDLE hThread[THREADCOUNT]; 
   int i; 
   HMODULE hm;
 
// Load the DLL

   hm = LoadLibrary(DLL_NAME);
   if(!hm)
   {
      ErrorExit("DLL failed to load");
   }

// Create multiple threads. 
 
   for (i = 0; i < THREADCOUNT; i++) 
   { 
      hThread[i] = CreateThread(NULL, // default security attributes 
         0,                           // use default stack size 
         (LPTHREAD_START_ROUTINE) ThreadFunc, // thread function 
         NULL,                    // no thread function argument 
         0,                       // use default creation flags 
         &IDThread);              // returns thread identifier 
 
   // Check the return value for success. 
      if (hThread[i] == NULL) 
         ErrorExit("CreateThread error\n"); 
   } 
 
   WaitForMultipleObjects(THREADCOUNT, hThread, TRUE, INFINITE); 

   FreeLibrary(hm);
 
   return 0; 
} 
 
VOID ErrorExit (LPSTR lpszMessage) 
{ 
   fprintf(stderr, "%s\n", lpszMessage); 
   ExitProcess(0); 
}

Données de bibliothèque de liens dynamiques

Utilisation de TLS (Thread Local Storage)