Dynamic-Link ライブラリでのスレッド ローカル Storageの使用

このセクションでは、DLL エントリ ポイント関数を使用してスレッド ローカル ストレージ (TLS) インデックスを設定し、マルチスレッド プロセスの各スレッドにプライベート ストレージを提供する方法について説明します。

TLS インデックスはグローバル変数に格納され、すべての DLL 関数で使用できます。 この例では、DLL を読み込むプロセスごとに TLS インデックスが必ずしも同じとは限らないため、DLL のグローバル データが共有されていないことを前提としています。

エントリ ポイント関数は、プロセスが DLL を読み込むたびに TlsAlloc 関数を使用して 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); 
}

ダイナミック リンク ライブラリ データ

スレッド ローカル ストレージの使用