Dynamic-Link 라이브러리에서 스레드 로컬 스토리지 사용

이 섹션에서는 DLL 진입점 함수를 사용하여 다중 스레드 프로세스의 각 스레드에 대한 프라이빗 스토리지를 제공하도록 TLS(스레드 로컬 스토리지) 인덱스를 설정하는 방법을 보여 줍니다.

TLS 인덱스는 전역 변수에 저장되므로 모든 DLL 함수에서 사용할 수 있습니다. 이 예제에서는 TLS 인덱스가 DLL을 로드하는 각 프로세스에 대해 반드시 동일하지는 않기 때문에 DLL의 전역 데이터가 공유되지 않는다고 가정합니다.

진입점 함수는 TlsAlloc 함수를 사용하여 프로세스가 DLL을 로드할 때마다 TLS 인덱스를 할당합니다. 그런 다음 각 스레드는 이 인덱스 를 사용하여 자체 메모리 블록에 대한 포인터를 저장할 수 있습니다.

진입점 함수가 DLL_PROCESS_ATTACH 값으로 호출되면 코드는 다음 작업을 수행합니다.

  1. TlsAlloc 함수를 사용하여 TLS 인덱스를 할당합니다.
  2. 프로세스의 초기 스레드에서만 사용할 메모리 블록을 할당합니다.
  3. TlsSetValue 함수 호출에서 TLS 인덱스를 사용하여 인덱스와 연결된 TLS 슬롯에 메모리 블록의 주소를 저장합니다.

프로세스가 새 스레드를 만들 때마다 진입점 함수는 DLL_THREAD_ATTACH 값으로 호출됩니다. 그런 다음 진입점 함수는 새 스레드에 대한 메모리 블록을 할당하고 TLS 인덱스를 사용하여 포인터를 저장합니다.

함수가 TLS 인덱스와 연결된 데이터에 액세스해야 하는 경우 TlsGetValue 함수에 대한 호출에서 인덱스를 지정합니다. 이 경우 데이터의 메모리 블록에 대한 포인터인 호출 스레드에 대한 TLS 슬롯의 내용을 검색합니다. 프로세스에서 이 DLL과 로드 시간 연결을 사용하는 경우 진입점 함수는 스레드 로컬 스토리지를 관리하기에 충분합니다. LoadLibrary 함수가 호출되기 전에 존재하는 스레드에 대해 진입점 함수가 호출되지 않으므로 런타임 연결을 사용하는 프로세스에서 문제가 발생할 수 있으므로 이러한 스레드에 TLS 메모리가 할당되지 않습니다. 이 예제에서는 TlsGetValue 함수에서 반환된 값을 확인하고 값이 이 스레드의 TLS 슬롯이 설정되지 않았다는 것을 나타내는 경우 메모리를 할당하여 이 문제를 해결합니다.

각 스레드가 더 이상 TLS 인덱스를 사용할 필요가 없는 경우 포인터가 TLS 슬롯에 저장된 메모리를 해제해야 합니다. 모든 스레드가 TLS 인덱스 사용을 완료한 경우 TlsFree 함수를 사용하여 인덱스를 해제합니다.

스레드가 종료되면 진입점 함수가 DLL_THREAD_DETACH 값으로 호출되고 해당 스레드의 메모리가 해제됩니다. 프로세스가 종료되면 진입점 함수가 DLL_PROCESS_DETACH 값으로 호출되고 TLS 인덱스의 포인터에서 참조하는 메모리가 해제됩니다.

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

다음 코드에서는 이전 예제에 정의된 DLL 함수를 사용하는 방법을 보여 줍니다.

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

동적 링크 라이브러리 데이터

스레드 로컬 스토리지 사용