Freigeben über


Verwenden des lokalen Threadspeichers in einer Dynamic-Link Bibliothek

In diesem Abschnitt wird die Verwendung einer DLL-Einstiegspunktfunktion zum Einrichten eines TLS-Index (Thread Local Storage) veranschaulicht, um privaten Speicher für jeden Thread eines Multithreadprozesses bereitzustellen.

Der TLS-Index wird in einer globalen Variablen gespeichert, sodass er für alle DLL-Funktionen verfügbar ist. In diesem Beispiel wird davon ausgegangen, dass die globalen Daten der DLL nicht freigegeben werden, da der TLS-Index nicht unbedingt für jeden Prozess identisch ist, der die DLL lädt.

Die Einstiegspunktfunktion verwendet die TlsAlloc--Funktion, um einen TLS-Index zuzuweisen, wenn ein Prozess die DLL lädt. Jeder Thread kann dann diesen Index verwenden, um einen Zeiger auf einen eigenen Speicherblock zu speichern.

Wenn die Einstiegspunktfunktion mit dem wert DLL_PROCESS_ATTACH aufgerufen wird, führt der Code die folgenden Aktionen aus:

  1. Verwendet die TlsAlloc--Funktion, um einen TLS-Index zuzuweisen.
  2. Weist einen Speicherblock zu, der ausschließlich vom anfänglichen Thread des Prozesses verwendet werden soll.
  3. Verwendet den TLS-Index in einem Aufruf der TlsSetValue--Funktion, um die Adresse des Speicherblocks im dem Index zugeordneten TLS-Steckplatz zu speichern.

Jedes Mal, wenn der Prozess einen neuen Thread erstellt, wird die Einstiegspunktfunktion mit dem DLL_THREAD_ATTACH Wert aufgerufen. Die Einstiegspunktfunktion weist dann einen Speicherblock für den neuen Thread zu und speichert mithilfe des TLS-Index einen Zeiger darauf.

Wenn eine Funktion Zugriff auf die Daten benötigt, die einem TLS-Index zugeordnet sind, geben Sie den Index in einem Aufruf der TlsGetValue--Funktion an. Dadurch wird der Inhalt des TLS-Steckplatzes für den aufrufenden Thread abgerufen, der in diesem Fall ein Zeiger auf den Speicherblock für die Daten ist. Wenn ein Prozess die Ladezeitverknüpfung mit dieser DLL verwendet, reicht die Einstiegspunktfunktion aus, um den lokalen Threadspeicher zu verwalten. Probleme können mit einem Prozess auftreten, der Laufzeitverknüpfung verwendet, da die Einstiegspunktfunktion nicht für Threads aufgerufen wird, die vorhanden sind, bevor die LoadLibrary--Funktion aufgerufen wird, sodass TLS-Speicher für diese Threads nicht zugewiesen wird. Dieses Beispiel löst das Problem, indem es den von der TlsGetValue Funktion zurückgegebenen Wert überprüft und Speicher zuweist, wenn der Wert darauf hinweist, dass der TLS-Slot für diesen Thread nicht gesetzt ist.

Wenn jeder Thread keinen TLS-Index mehr verwenden muss, muss er den Speicher freigeben, dessen Zeiger im TLS-Steckplatz gespeichert ist. Wenn alle Threads mit der Verwendung eines TLS-Index fertig sind, verwenden Sie die TlsFree--Funktion, um den Index freizugeben.

Wenn ein Thread beendet wird, wird die Einstiegspunktfunktion mit dem wert DLL_THREAD_DETACH aufgerufen, und der Speicher für diesen Thread wird freigegeben. Wenn ein Prozess beendet wird, wird die Einstiegspunktfunktion mit dem DLL_PROCESS_DETACH-Wert aufgerufen, und der speicher, auf den der Zeiger im TLS-Index verweist, wird freigegeben.

// 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

Der folgende Code veranschaulicht die Verwendung der im vorherigen Beispiel definierten DLL-Funktionen.

#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); 
}

Dynamic-Link Bibliotheksdaten

Verwenden von lokalen Threadspeicher-