лекции / Shchupak_Yu._Win32_API_Razrabotka_prilozheniy_dlya_Windows
.pdf
Локальная память потока (TLS) |
531 |
|
|
Динамическая TLS
Организация и функционирование динамической TLS поясняются на рис 11.2. Когда стартует любой поток, операционная система выделяет ему в памяти так называемый массив TLS слотов. Элементы этого массива представляют собой че тырехбайтные ячейки, которые и называют слотами.
Рис. 11.2. Организация динамической TLS
Стартовавший поток не имеет доступа к массиву TLS слотов, пока не получит от системы индекс первого свободного слота. Дело в том, что индексация всех сло тов для всех потоков одного процесса является общей. Поэтому, если стартовав шие ранее потоки уже зарезервировали для себя слоты, например, с индексами 0, 1, 2, то новый поток имеет возможность «приватизировать» слот с индексом 3. Для получения свободного слота вызывается функция TlsAlloc:
gdwTlsIndex = TlsAlloc();
Функция TlsAlloc резервирует первый свободный слот и возвращает его ин декс, который обычно сохраняется в глобальной переменной gdwTlsIndex. В слу чае неудачи (нет свободных слотов) функция возвращает значение –1.
Индекс gdwTlsIndex может использоваться в дальнейшем всеми потоками про цесса для доступа к зарезервированному слоту. Обратите внимание на важный момент: информация, содержащаяся в «одноименных» слотах разных потоков, является уникальной для каждого потока.
532 |
Глава 11. Библиотеки динамической компоновки DLL |
|
|
Количество ячеек n в массиве TLS слотов определяется константой TLS_MINIMUM_AVAILABLE. Для Windows 95/NT эта константа равна 64, но, начиная с Win dows 2000, она уже имеет значение 1088.
Заметим, что функция TlsAlloc в своей реализации сканирует глобальную бито вую карту, содержащую n разрядов. Каждый разряд в этой карте находится в со стоянии free или in_use, указывая, свободен или занят соответствующий TLS–слот. Если найден бит со значением free, функция TlsAllocизменяет его значение на in_use и возвращает найденный индекс. Перед возвратом управления функция проходит по всем потокам и обнуляет TLS слоты с выделенным индексом. Данная операция облегчает потокам корректную инициализацию своих слотов.
Непосредственного доступа к слоту у потока нет. Чтобы записать значение в слот, поток вызывает функцию TlsSetValue, передавая ей значение индекса gdwTlsIndex:
TlsSetValue(gdwTlsIndex, pGlobData);
Здесь pGlobData — адрес в памяти, по которому размещены данные, глобальные в контексте потока, но недоступные для других потоков. Как правило, такие дан ные содержат более чем одну переменную. В принципе, можно было бы для каж дой переменной резервировать отдельный TLS индекс. Но, с учетом имеющихся ограничений на количество слотов, выделенных процессу, это было бы крайне не рационально.
Поэтому обычно работа с локальной памятью потока организуется следующим образом:
Все необходимые переменные собираются в общую структуру, например:
struct GlobData { int x;
int y;
int count; // . . .
char buffer[256];
};
Затем объявляется указатель на эту структуру:
GlobData* pGlobData;
Поток выделяет память в куче (heap) для сохранения структуры типа GlobData. Для этого вызывается одна из функций резервирования памяти, например,
LocalAlloc:
pGlobData = (GlobData*)LocalAlloc(LPTR, sizeof(GlobData));
Полученный адрес pGlobData сохраняется в соответствующем TLS слоте при помощи функции TlsSetValue:
TlsSetValue(gdwTlsIndex, pGlobData);
Когда той или иной функции требуется доступ к данным в TLS, вызывается функция TlsGetValue:
GlobData* pGlobData = (GlobData*)TlsGetValue(gwTlsIndex);
Полученный указатель pGlobData позволяет использовать любое поле струк туры GlobData как для чтения, так и для записи.
Перед завершением работы приложения использующийся слот должен быть освобожден. Для этой операции вызывается функция TlsFree, принимающая
Локальная память потока (TLS) |
533 |
|
|
в качестве параметра индекс gdwTlsIndex. Результатом выполнения TlsFree яв ляется установка соответствующего разряда битовой карты в состояние free.
TLS может применяться в любом многопоточном приложении. Особенно акту ально использование механизма локальной памяти потока при разработке библио тек динамической компоновки. Ведь DLL могут загружаться любыми приложени ями, в том числе и многопоточными.
Покажем использование описанной технологии на примере разработки библио теки MyMtLib, которая имеет такую же функциональность, как и MyLib, но адапти рована для работы с многопоточными приложениями.
Напомним, что библиотека MyLib предоставляет своим клиентам единственную функцию
void Print(LPCTSTR text);
которая выводит на экран окно сообщений с текстом text и заголовком вида:
MyLib: Print: вызов N
где N — целое число, обозначающее номер вызова функции Print.
Мы уже выяснили, что для того, чтобы нумерация вызовов функции Print отно силась бы именно к тому потоку, из которого она вызывается, необходимо помес тить глобальную переменную count в локальную память потока.
Другим недостатком интерфейса библиотеки MyLib является отсутствие воз можности управлять позицией вывода окна сообщений на экране. Функция MessageBox, использованная в реализации функции Print, всегда выводит это окно
вцентре экрана. А это неудобно, когда несколько потоков одновременно вызыва ют Print: диалоговые окна «складываются» в стопку — каждое следующее поверх предыдущего.
Очевидный путь решения этой проблемы — отказаться от использования фун кции MessageBox и реализовать окно сообщений «врукопашную», при помощи мо дального диалогового окна. Однако такой путь порождает новые проблемы. Необ ходимо где то сохранять:
строку text, передаваемую через параметр функции Print, чтобы потом исполь зовать эту строку в диалоговой процедуре;
строку title с заголовком диалогового окна, также используемую в диалоговой процедуре;
значения координат x, y, определяющих позицию вывода диалогового окна.
Иопять единственно возможным корректным решением является хранение всех этих данных в локальной памяти потока.
Все эти соображения учтены в проекте MyMtLib (листинг 11.7).
Кроме функции Print библиотека MyMtLib содержит: а) функцию InitMyMtLib, позволяющую настроить позицию вывода окна сообщений; б) функцию PrintBoxDlgProc, исполняющую роль диалоговой процедуры; в) функцию точки входа DllMain, в которую помещены действия по инициализации/деинициализации ло кальной памяти потока.
Для того чтобы функционирование библиотеки MyMtLib стало более наглядным,
вее коде сделаны вставки функции TRACE, обеспечивающей вывод отладочной ин формации в окно Output. Эта функция определена в файле KWndEx.cpp (см. листинг 8.4 в главе 8).
534 |
Глава 11. Библиотеки динамической компоновки DLL |
|
|
Листинг 11.7. Проект MyMtLib
//////////////////////////////////////////////////////////////////////
//MyMtLib.h #include <windows.h>
#define EXPORT extern "C" __declspec(dllexport) EXPORT void Print(LPCTSTR text);
EXPORT void InitMyMtLib(int x, int y);
//////////////////////////////////////////////////////////////////////
//MyMtLib.cpp
#include <stdio.h> #include "MyMtLib.h" #include "KWndEx.h" #include "resource.h"
struct GlobData { int x;
int y;
int count;
char title[100]; char buffer[256];
};
HINSTANCE hInstDll;
DWORD gdwTlsIndex;
BOOL CALLBACK PrintBoxDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); //====================================================================
EXPORT void Print(LPCTSTR text)
{
// Отладочный вывод
DWORD threadID = GetCurrentThreadId();
TRACE("Print: вызов из потока %X, параметр text = '%s'\n", threadID, text);
// Получаем адрес памяти, связанный с индексом gdwTlsIndex GlobData* pGlobData = (GlobData*)TlsGetValue(gdwTlsIndex); pGlobData->count++;
sprintf(pGlobData->title, "MyMtLib: Print: вызов %d", pGlobData->count); strncpy(pGlobData->buffer, text, 99);
DialogBox(hInstDll, MAKEINTRESOURCE(IDD_PRINT_BOX), NULL, PrintBoxDlgProc);
}
//==================================================================== EXPORT void InitMyMtLib(int x, int y)
{
// Получаем адрес памяти, связанный с индексом gdwTlsIndex GlobData* pGlobData = (GlobData*)TlsGetValue(gdwTlsIndex);
pGlobData->x = x; pGlobData->y = y; pGlobData->count = 0;
}
//====================================================================
BOOL CALLBACK PrintBoxDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
RECT r; |
продолжение |
|
Локальная память потока (TLS) |
535 |
|
|
Листинг 11.7. (продолжение)
GlobData* pGlobData = (GlobData*)TlsGetValue(gdwTlsIndex);
switch (uMsg) {
case WM_INITDIALOG:
SetWindowText(hDlg, pGlobData->title); SetWindowText(GetDlgItem(hDlg, IDC_EDIT1), pGlobData->buffer);
GetWindowRect(hDlg, &r);
MoveWindow(hDlg, pGlobData->x, pGlobData->y, r.right - r.left, r.bottom - r.top, TRUE);
return TRUE;
case WM_COMMAND:
switch (LOWORD(wParam)) { case IDOK:
EndDialog(hDlg, 0); return TRUE;
}
break;
}
return FALSE;
}
//====================================================================
BOOL APIENTRY DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
GlobData* pGlobData; hInstDll = hinstDLL; DWORD threadID;
switch (fdwReason) {
case DLL_PROCESS_ATTACH: // Отладочный вывод
TRACE("DllMain: подключение DLL, hinstDLL = %X\n", hinstDLL);
// Размещаем TLS-индекс
if ((gdwTlsIndex = TlsAlloc()) == 0xFFFFFFFF) return FALSE;
TRACE("DllMain: получен индекс gdwTlsIndex = %d первого свободного слота\n", gdwTlsIndex);
break;
case DLL_THREAD_ATTACH: // появился новый поток // Отладочный вывод
threadID = GetCurrentThreadId();
TRACE("DllMain: создание процессом нового потока, threadID = %X\n", threadID);
if (TlsGetValue(gdwTlsIndex) == NULL) // если память в TLS еще не выделена
{
// Выделяем память в куче
pGlobData = (GlobData*)LocalAlloc(LPTR, sizeof(GlobData));
Локальная память потока (TLS) |
537 |
|||
|
|
|||
Листинг 11.8. Проект MtTlsClient |
|
|||
////////////////////////////////////////////////////////////////////// |
|
|||
// MtTlsClient.cpp |
|
|||
#include |
<windows.h> |
|
||
#include |
"MyMtLib.h" |
|
||
DWORD |
WINAPI |
ThreadFuncA(LPVOID); |
|
|
DWORD |
WINAPI |
ThreadFuncB(LPVOID); |
|
|
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //====================================================================
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
HANDLE hThreadA = CreateThread(NULL, 0, ThreadFuncA, 0, 0, NULL); HANDLE hThreadB = CreateThread(NULL, 0, ThreadFuncB, 0, 0, NULL); MessageBox(NULL, "Завершить основной поток?", "WinMain", MB_OK); return 0;
}
//==================================================================== DWORD WINAPI ThreadFuncA(LPVOID lpv)
{
InitMyMtLib(400, 350); Print("Поток А: Привет!");
Print("Поток А: Эксперимент удался!"); Print("Поток А: Пока!");
return 0;
}
//==================================================================== DWORD WINAPI ThreadFuncB(LPVOID lpv)
{
InitMyMtLib(660, 350); Print("Поток Б: Hello!");
Print("Поток Б: The experiment was a success!"); Print("Поток Б: By!");
return 0;
}
//////////////////////////////////////////////////////////////////////
Создайте проект типа Win32 Application с именем MtTlsClient и добавьте в его со став файлы MtTlsClient.cpp и MyMtLib.h. Скопируйте в папку проекта библиотеку импорта MyMtLib.lib. Добавьте в настройки компоновщика ссылку на библиотеку импорта MyMtLib.lib. После компиляции проекта скопируйте в папку Debug файл MyMtLib.dll и запустите программу на выполнение в отладочном режиме (F5). На экране должны появиться три окна, как показано на рис. 11.3.
Поочередно щелкая на кнопке OK двух верхних окон, вы можете убедиться, что нумерация вызовов функции Print осуществляется для каждого потока от дельно.
А теперь посмотрите на отладочный вывод в окне Output. С некоторыми сокра щениями он должен выглядеть примерно так:
Loaded symbols for 'F:\ProgWinApi\MtTlsClient\Debug\MyMtLib.dll'
...
DllMain: подключение DLL, hinstDLL = 10000000
DllMain: получен индекс gdwTlsIndex = 3 первого свободного слота DllMain: создание процессом нового потока, threadID = 8E0 DllMain: выделена память в куче с адресом 143778
DllMain: адрес 143778 записан в слот с индексом 3
Print: вызов из потока 8E0, параметр text = 'Поток А: Привет!'
538 Глава 11. Библиотеки динамической компоновки DLL
DllMain: создание процессом нового потока, threadID = AEC DllMain: выделена память в куче с адресом 143900
DllMain: адрес 143900 записан в слот с индексом 3
Print: вызов из потока AEC, параметр text = 'Поток Б: Hello!'
...
Print: вызов из потока 8E0, параметр text = 'Поток А: Эксперимент удался!'
Print: вызов из потока AEC, параметр text = 'Поток Б: The experiment was a success!' Print: вызов из потока 8E0, параметр text = 'Поток А: Пока!'
Print: вызов из потока AEC, параметр text = 'Поток Б: By!' DllMain: завершение потока, threadID = 8E0
DllMain: завершение потока, threadID = AEC DllMain: отключение DLL
Рис. 11.3. Результат первого вызова функции Print из потоков А и Б
Статическая TLS
Использование этого вида локальной памяти потока на порядок проще. Достаточ но при объявлении переменной добавить модификатор __declspec(thread), например:
__declspec(thread) int count = 0;
чтобы каждый поток работал с собственной копией этой переменной. Модификатор __declspec(thread) поддерживается компилятором Visual C++.
Он сообщает компилятору, что соответствующую переменную следует поместить в специальный раздел исполняемого файла, имеющий имя .tls.
Функционирование статической TLS происходит в тесном взаимодействии с операционной системой. Загружая приложение в память, система отыскивает
висполняемом файле раздел .tls и выделяет блок памяти для хранения всех стати ческих TLS переменных. При этом учитываются переменные, объявленные как
всамом EXE файле, так и во всех DLL с неявной компоновкой. Каждая ссылка на TLS переменную переадресуется к участку, расположенному в выделенном блоке памяти. Поэтому компилятору приходится генерировать дополнительный код для ссылок на статические TLS переменные, что увеличивает размер приложения и за медляет скорость его работы.
Но главным недостатком статической TLS является то, что если вы объявите переменные с модификатором __declspec(thread) в коде DLL, то эту библиотеку нельзя будет загружать на этапе выполнения приложения при помощи функции LoadLibrary. Если же вы попытаетесь это сделать, то получите ошибку «Access violati on». Поэтому при разработке DLL следует использовать только динамическую TLS.
540 |
Глава 12. Специальные приложения |
|
|
Использование региона отсечения (CreateEllipticRgn, SelectClipRgn) для выделе ния в прямоугольном растре области с изображением мяча, которая затем копи руется при помощи функции BitBlt.
Мировые преобразования (SetWorldTransform) для перемещения и вращения изоб ражения мяча.
Функции SaveDC и RestoreDC, применяемые для сохранения и восстановления текущего состояния контекста устройства.
Листинг 12.1. Проект BounceBall1
//////////////////////////////////////////////////////////////////////
// BounceBall.cpp #include <windows.h> #include <stdio.h> #include <math.h> #include "KWnd.h" #include "resource.h"
#define Pi 3.14159265
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void DrawBall(HWND, HDC, HBITMAP, BITMAP, FLOAT, FLOAT, int); //==================================================================== int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
MSG lpMsg;
KWnd mainWnd("Bounce ball", hInstance, nCmdShow, WndProc);
while (GetMessage(&lpMsg, NULL, 0, 0)) { TranslateMessage(&lpMsg); DispatchMessage(&lpMsg);
}
return (lpMsg.wParam);
}
//==================================================================== LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static FLOAT x = 0, y = 0; |
// текущая позиция мяча |
||
static FLOAT dX, dY; |
|
// приращения координат для сдвига на новую позицию |
|
static int alpha = 0; |
|
// угол, определяющий вращение мяча |
|
static HDC hDC; |
// контекст дисплея |
||
static HBRUSH hBkBrush; |
// узорная кисть |
||
HBITMAP hBmpBkgr; |
// растр для узорной кисти |
||
static HBITMAP hBmpBall; // растр с изображением мяча |
|||
static BITMAP bm; |
// параметры растра |
||
RECT rect; |
// прямоугольник клиентской области |
||
RECT rBall; |
// прямоугольник, ограничивающий изображение мяча |
||
switch(uMsg) |
|
|
|
{ |
|
|
|
case WM_CREATE: |
|
|
|
hDC = |
GetDC(hWnd); |
|
продолжение |
|
|
|
|
1 Не забудьте добавить к проекту файлы KWnd.h и KWnd.cpp, текст которых приведен в листинге 1.2.
