Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

лекции / Shchupak_Yu._Win32_API_Razrabotka_prilozheniy_dlya_Windows

.pdf
Скачиваний:
0
Добавлен:
11.02.2026
Размер:
13.15 Mб
Скачать

Локальная память потока (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));

536

Глава 11. Библиотеки динамической компоновки DLL

 

 

//Связываем TLS-индекс потока с адресом памяти pGlobData TlsSetValue(gdwTlsIndex, pGlobData);

//Отладочный вывод

TRACE("DllMain: выделена память в куче с адресом %X\n", pGlobData); TRACE("DllMain: адрес %X записан в слот с индексом %d\n", pGlobData,

gdwTlsIndex);

}

break;

case DLL_THREAD_DETACH: // Отладочный вывод

threadID = GetCurrentThreadId();

TRACE("DllMain: завершение потока, threadID = %X\n", threadID);

// Освобождаем размещенную память для этого потока pGlobData = (GlobData*)TlsGetValue(gdwTlsIndex); if (pGlobData != NULL)

LocalFree((HLOCAL)pGlobData);

break;

case DLL_PROCESS_DETACH: // Отладочный вывод

TRACE("DllMain: отключение DLL\n");

// Освобождаем TLS-index. TlsFree(gdwTlsIndex); break;

}

return TRUE;

}

//////////////////////////////////////////////////////////////////////

Заметим, что при вызове функции LocalAlloc, выделяющей блок памяти в куче, ее первый параметр имеет значение LPTR. Константа LPTR — это флаг, включающий в себя комбинацию двух других флагов: LMEM_FIXED è LMEM_ZEROINIT. Значение LMEM_FIXED побуждает функцию выделить фиксированный (неперемещаемый) блок памяти в куче и вернуть указатель на начало этого блока. Значение LMEM_ZEROINIT обеспечивает инициализацию содержимого выделенного блока па мяти нулями.

Создайте проект типа Win32Dynamic-LinkLibraryс именем MyMtLibи добавьте в его состав файлы MyMtLib.h, MyMtLib.cpp, KWndEx.h и KWndEx.cpp.

Спомощью редактора ресурсов добавьте в проект ресурс диалогового окна.

Всвойствах диалогового окна установите ID равным IDD_PRINT_BOX и снимите фла жок System menu. Уменьшите высоту окна примерно на 1/3.

Удалите кнопку Cancel, а кнопку OK разместите по центру у нижней границы окна.

В центре окна поместите элемент управления Edit box, установив для него сле дующие свойства: выравнивание текста — по центру, флажок Border — снят, флаж ки Multiline, AutoHScroll, Read only — установлены.

После компиляции проекта в папке Debug должны появиться файлы MyMtLib.dll

и MyMtLib.lib.

Теперь подготовим клиентское приложение MtTlsClient (листинг 11.8) для ис пытания новой библиотеки.

Локальная память потока (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.

Анимация

539

12 Специальные приложения

В этой главе предлагаются примеры решений для двух практических областей. Сна чала излагаются основы техники анимации в Windows программах. Затем рассмат ривается рисование в реальном времени в приложениях, имитирующих бортовую аппаратуру.

Анимация

Создание анимационных приложений — это весьма обширная тема, и ей посвящено несколько статей в MSDN. В связи с ограничениями на объем данной книги мы рассмотрим только самую простую технику анимации, суть которой заключается в чередовании нескольких шагов. Нужно нарисовать объект, через некоторое время стереть его и изменить позицию объекта. Промежуток времени, в течение которого объект остается в текущей позиции, обычно задается таймером.

Приложение со стандартным таймером

В качестве примера рассмотрим программу «Прыгающий мячик», в которой имити руется полет мяча в прямоугольном боксе от одной стенки к другой. Ударяясь о стенку бокса, мяч отскакивает под соответствующим углом и продолжает движе ние в новом направлении. Роль «бокса» в этой программе исполняет клиентская область окна приложения. Чтобы сделать пример более интересным, дополним его двумя требованиями: а) мяч должен вращаться во время движения; б) фон, на кото ром происходит полет мяча, должен изображать некоторую текстуру.

Первая попытка анимации содержится в листинге 12.1. В этой программе де монстрируется применение следующих средств Win32 API:

Использование узорной кисти (CreatePatternBrush) для фона окна (SetClassLong) и для операции стирания (FillRect) предыдущего изображения мяча.

Контекст устройства в памяти1 (CreateCompatibleDC) для размещения в нем DDB растра с изображением мяча (SelectObject) и последующего его вывода в контекст дисплея (BitBlt).

1 Или совместимый контекст устройства.

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.