
- •WINDOWS
- •Джеффри Рихтер
- •ЧАCTЬ I МАТЕРИАЛЫ ДЛЯ ОБЯЗАТЕЛЬНОГО ЧТЕНИЯ
- •ГЛАВА 1. Обработка ошибок
- •Вы тоже можете это сделать
- •Программа-пример ErrorShow
- •ГЛАВА 2 Unicode
- •Наборы символов
- •Одно- и двухбайтовые наборы символов
- •Unicode: набор широких символов
- •Почему Unicode?
- •Windows 2000 и Unicode
- •Windows 98 и Unicode
- •Windows CE и Unicode
- •В чью пользу счет?
- •Unicode и СОМ
- •Как писать программу с использованием Unicode
- •Unicode и библиотека С
- •Типы данных, определенные в Windows для Unicode
- •Unicode- и ANSI-функции в Windows
- •Строковые функции Windows
- •Ресурсы
- •Текстовые файлы
- •Перекодировка строк из Unicode в ANSI и обратно
- •ГЛАВА 3 Объекты ядра
- •Что такое объект ядра
- •Учет пользователей объектов ядра
- •Защита
- •Таблица описателей объектов ядра
- •Создание объекта ядра
- •Закрытие объекта ядра
- •Совместное использование объектов ядра несколькими процессами
- •Наследование описателя объекта
- •Изменение флагов описателя
- •Именованные объекты
- •Пространства имен Terminal Server
- •Дублирование описателей объектов
- •ЧАСТЬ II НАЧИНАЕМ РАБОТАТЬ
- •ГЛАВА 4 Процессы
- •Ваше первое Windows-приложение
- •Описатель экземпляра процесса
- •Описатель предыдущего экземпляра процесса
- •Командная строка процесса
- •Переменные окружения
- •Привязка к процессорам
- •Режим обработки ошибок
- •Текущие диск и каталог для процесса
- •Текущие каталоги для процесса
- •Определение версии системы
- •Функция CreateProcess
- •Параметры pszApplicationName и pszCommandLine
- •Параметры psaProcess, psaThread и blnheritHandles
- •Параметр fdwCreate
- •Параметр pvEnvironment
- •Параметр pszCurDir
- •Параметр psiStartlnfo
- •Параметр ppiProclnfo
- •Завершение процесса
- •Возврат управления входной функцией первичного потока
- •Функция ExitProcess
- •Функция TerminateProcess
- •Когда все потоки процесса уходят
- •Что происходит при завершении процесса
- •Дочерние процессы
- •Запуск обособленных дочерних процессов
- •Перечисление процессов, выполняемых в системе
- •Программа-пример Processlnfo
- •ГЛАВА 5 Задания
- •Определение ограничений, налагаемых на процессы в задании
- •Включение процесса в задание
- •Завершение всех процессов в задании
- •Получение статистической информации о задании
- •Уведомления заданий
- •Программа-пример JobLab
- •ГЛАВА 6 Базовые сведения о потоках
- •В каких случаях потоки создаются
- •И в каких случаях потоки не создаются
- •Ваша первая функция потока
- •Функция CreateThread
- •Параметр psa
- •Параметр cbStack
- •Параметры pfnStartAddr и pvParam
- •Параметр fdwCreate
- •Параметр pdwThreadlD
- •Завершение потока
- •Возврат управления функцией потока
- •Функция ExitThread
- •Функция TerminateThread
- •Если завершается процесс
- •Что происходит при завершении потока
- •Кое-что о внутреннем устройстве потока
- •Некоторые соображения по библиотеке С/С++
- •Ой, вместо _beginthreadex я по ошибке вызвал CreateThread
- •Библиотечные функции, которые лучше не вызывать
- •Как узнать о себе
- •Преобразование псевдоописателя в настоящий описатель
- •ГЛАВА 7 Планирование потоков, приоритет и привязка к процессорам
- •Приостановка и возобновление потоков
- •Приостановка и возобновление процессов
- •Функция Sleep
- •Переключение потоков
- •Определение периодов выполнения потока
- •Структура CONTEXT
- •Приоритеты потоков
- •Абстрагирование приоритетов
- •Программирование приоритетов
- •Динамическое изменение уровня приоритета потока
- •Подстройка планировщика для активного процесса
- •Программа-пример Scheduling Lab
- •Привязка потоков к процессорам
- •ГЛАВА 8 Синхронизация потоков в пользовательском режиме
- •Кэш-линии
- •Более сложные методы синхронизации потоков
- •Худшее, что можно сделать
- •Критические секции
- •Критические секции: важное дополнение
- •Критические секции и спин-блокировка
- •Критические секции и обработка ошибок
- •Несколько полезных приемов
- •Не занимайте критические секции надолго
- •ГЛАВА 9 Синхронизация потоков с использованием объектов ядра
- •Wait-функции
- •Побочные эффекты успешного ожидания
- •События
- •Программа-пример Handshake
- •Ожидаемые таймеры
- •Ожидаемые таймеры и АРС-очередь
- •И еще кое-что о таймерах
- •Семафоры
- •Мьютексы
- •Отказ от объекта-мьютекса
- •Мьютексы и критические секции
- •Программа-пример Queue
- •Сводная таблица объектов, используемых для синхронизации потоков
- •Другие функции, применяемые в синхронизации потоков
- •Асинхронный ввод-вывод на устройствах
- •Функция WaitForlnputldle
- •Функция MsgWaitForMultipleObjects(Ex)
- •Функция WaitForDebugEvent
- •Функция SignalObjectAndWait
- •ГЛАВА 10 Полезные средства для синхронизации потоков
- •Реализация критической секции: объект-оптекс
- •Программа-пример Optex
- •Создание инверсных семафоров и типов данных, безопасных в многопоточной среде
- •Программа-пример lnterlockedType
- •Синхронизация в сценарии "один писатель/группа читателей"
- •Программа-пример SWMRG
- •Реализация функции WaitForMultipleExpressions
- •Программа-пример WaitForMultExp
- •ГЛАВА 11 Пулы потоков
- •Сценарий 1: асинхронный вызов функций
- •Сценарий 2: вызов функций через определенные интервалы времени
- •Программа-пример TimedMsgBox
- •Сценарий 3: вызов функций при освобождении отдельных объектов ядра
- •Сценарий 4; вызов функций по завершении запросов на асинхронный ввод-вывод
- •ГЛАВА 12 Волокна
- •Работа с волокнами
- •Программа-пример Counter
- •ЧАСТЬ III УПРАВЛЕНИЕ ПАМЯТЬЮ
- •Виртуальное адресное пространство процесса
- •Как адресное пространство разбивается на разделы
- •Увеличение раздела для кода и данных пользовательского режима до 3 Гб на процессорах x86 (только Windows 2000)
- •Закрытый раздел размером 64 Кб (только Windows 2000)
- •Раздел для общих MMF (только Windows 98)
- •Регионы в адресном пространстве
- •Передача региону физической памяти
- •Физическая память и страничный файл
- •Физическая память в страничном файле не хранится
- •Атрибуты защиты
- •Защита типа «копирование при записи»
- •Специальные флаги атрибутов защиты
- •Подводя итоги
- •Блоки внутри регионов
- •Особенности адресного пространства в Windows 98
- •Выравнивание данных
- •ГЛАВА 14 Исследование виртуальной памяти
- •Системная информация
- •Программа-пример Syslnfo
- •Статус виртуальной памяти
- •Программа-пример VMStat
- •Определение состояния адресного пространства
- •Функция VMQuery
- •Программа-пример VMMap
- •ГЛАВА 15 Использование виртуальной памяти в приложениях
- •Резервирование региона в адресном пространстве
- •Передача памяти зарезервированному региону
- •Резервирование региона с одновременной передачей физической памяти
- •В какой момент региону передают физическую память
- •Возврат физической памяти и освобождение региона
- •В какой момент физическую память возвращают системе
- •Программа-пример VMAIloc
- •Изменение атрибутов защиты
- •Сброс содержимого физической памяти
- •Программа-пример MemReset
- •Механизм Address Windowing Extensions (только Windows 2000)
- •Программа-пример AWE
- •ГЛАВА 16 Стек потока
- •Стек потока в Windows 98
- •Функция из библиотеки С/С++ для контроля стека
- •Программа-пример Summation
- •ГЛАВА 17 Проецируемые в память файлы
- •Проецирование в память EXE- и DLL-файлов
- •Статические данные не разделяются несколькими экземплярами EXE или DLL
- •Программа-пример Applnst
- •Файлы данных, проецируемые в память
- •Метод 1: один файл, один буфер
- •Метод 2: два файла, один буфер
- •Метод 3: один файл, два буфера
- •Метод 4: один файл и никаких буферов
- •Использование проецируемых в память файлов
- •Этап1: создание или открытие объекта ядра «файл»
- •Этап 2: создание объекта ядра «проекция файла»
- •Этап 3: проецирование файловых данных на адресное пространство процесса
- •Этап 4: отключение файла данных от адресного пространства процесса
- •Этапы 5 и 6: закрытие объектов «проекция файла» и «файл»
- •Программа-пример FileRev
- •Обработка больших файлов
- •Проецируемые файлы и когерентность
- •Базовый адрес файла, проецируемого в память
- •Особенности проецирования файлов на разных платформах
- •Совместный доступ процессов к данным через механизм проецирования
- •Файлы, проецируемые на физическую память из страничного файла
- •Программа-пример MMFShare
- •Частичная передача физической памяти проецируемым файлам
- •Программа-пример MMFSparse
- •ГЛАВА 18 Динамически распределяемая память
- •Стандартная куча процесса
- •Дополнительные кучи в процессе
- •Защита компонентов
- •Более эффективное управление памятью
- •Локальный доступ
- •Исключение издержек, связанных с синхронизацией потоков
- •Быстрое освобождение всей памяти в куче
- •Создание дополнительной кучи
- •Выделение блока памяти из кучи
- •Изменение размера блока
- •Определение размера блока
- •Освобождение блока
- •Уничтожение кучи
- •Использование куч в программах на С++
- •Другие функции управления кучами
- •ЧАСТЬ IV ДИНАМИЧЕСКИ ПОДКЛЮЧАЕМЫЕ БИБЛИОТЕКИ
- •ГЛАВА 19 DLL: основы
- •DLL и адресное пространство процесса
- •Общая картина
- •Создание DLL-модуля
- •Что такое экспорт
- •Создание DLL для использования с другими средствами разработки (отличными от Visual C++)
- •Создание ЕХЕ-модуля
- •Что такое импорт
- •Выполнение ЕХЕ-модуля
- •ГЛАВА 20 DLL: более сложные методы программирования
- •Явная загрузка DLL и связывание идентификаторов
- •Явная загрузка DLL
- •Явная выгрузка DLL
- •Явное подключение экспортируемого идентификатора
- •Функция входа/выхода
- •Уведомление DLL_PROCESS_ATTACH
- •Уведомление DLL_PROCESS_DETACH
- •Уведомление DLL_THREAD_ATTACH
- •Уведомление DLL_THREAD_DETACH
- •Как система упорядочивает вызовы DIIMain
- •Функция DllMain и библиотека С/С++
- •Отложенная загрузка DLL
- •Программа-пример DelayLoadApp
- •Переадресация вызовов функций
- •Известные DLL
- •Перенаправление DLL
- •Модификация базовых адресов модулей
- •Связывание модулей
- •ГЛАВА 21 Локальная память потока
- •Динамическая локальная память потока
- •Использование динамической TLS
- •Статическая локальная память потока
- •Пример внедрения DLL
- •Внедрение DLL c использованием реестра
- •Внедрение DLL с помощью ловушек
- •Утилита для сохранения позиций элементов на рабочем столе
- •Внедрение DLL с помощью удаленных потоков
- •Программа-пример lnjLib
- •Библиотека lmgWalk.dll
- •Внедрение троянской DLL
- •Внедрение DLL как отладчика
- •Внедрение кода в среде Windows 98 через проецируемый в память файл
- •Внедрение кода через функцию CreateProcess
- •Перехват API-вызовов: пример
- •Перехват API-вызовов подменой кода
- •Перехват API-вызовов с использованием раздела импорта
- •Программа-пример LastMsgBoxlnfo
- •ЧАСТЬ V СТРУКТУРНАЯ ОБРАБОТКА ИСКЛЮЧЕНИЙ
- •ГЛАВА 23 Обработчики завершения
- •Примеры использования обработчиков завершения
- •Funcenstein1
- •Funcenstein2
- •Funcenstein3
- •Funcfurter1
- •Проверьте себя: FuncaDoodleDoo
- •Funcenstein4
- •Funcarama1
- •Funcarama2
- •Funcarama3
- •Funcarama4: последний рубеж
- •И еще о блоке finally
- •Funcfurter2
- •Программа-пример SEHTerm
- •ГЛАВА 24 Фильтры и обработчики исключений
- •Примеры использования фильтров и обработчиков исключений
- •Funcmeister1
- •Funcmeister2
- •EXCEPTION_EXECUTE_HANDLER
- •Некоторые полезные примеры
- •Глобальная раскрутка
- •Остановка глобальной раскрутки
- •EXCEPTION_CONTINUE_EXECUTION
- •Будьте осторожны с EXCEPTION_CONTINUE_EXECUTION
- •EXCEPTION_CONTINUE_SEARCH
- •Функция GetExceptionCode
- •Функция GetExceptionlnformation
- •Программные исключения
- •ГЛАВА 25 Необработанные исключения и исключения С++
- •Отладка по запросу
- •Отключение вывода сообщений об исключении
- •Принудительное завершение процесса
- •Создание оболочки вокруг функции потока
- •Создание оболочки вокруг всех функций потоков
- •Автоматический вызов отладчика
- •Явный вызов функции UnhandledExceptionFilter
- •Функция UnhandledExceptionFilter изнутри
- •Исключения и отладчик
- •Программа-пример Spreadsheet
- •Исключения С++ и структурные исключения
- •Перехват структурных исключений в С++
- •ЧАСТЬ VI ОПЕРАЦИИ С ОКНАМИ
- •ГЛАВА 26 Оконные сообщения
- •Очередь сообщений потока
- •Посылка асинхронных сообщений в очередь потока
- •Посылка синхронных сообщений окну
- •Пробуждение потока
- •Флаги состояния очереди
- •Алгоритм выборки сообщений из очереди потока
- •Пробуждение потока с использованием объектов ядра или флагов состояния очереди
- •Передача данных через сообщения
- •Программа-пример CopyData
- •ГЛАВА 27 Модель аппаратного ввода и локальное состояние ввода
- •Поток необработанного ввода
- •Локальное состояние ввода
- •Ввод с клавиатуры и фокус
- •Управление курсором мыши
- •Подключение к очередям виртуального ввода и переменным локального состояния ввода
- •Программа-пример LISLab
- •Программа-пример LISWatch

VOID BaseProcessStart(PPROCESS_START_BOUTINE pfnStartAddr)
{
__try
{
ExitThread((pfnStartAdd r)());
}
_except(UnhandledFxceptionFilter(GetExceptionInformation()))
{
ExitProcess(GettxceptionCode());
}
// ПРИМЕЧАНИЕ, мы никогда не попадем сюда
}
Единственное различие между этими функциями в отсутствии ссылки на параметр pvParam. Функция BaseProcessStart обращается к стартовому коду библиотеки С/С++, который выполняет необходимую инициализацию, а затем вызывает Ramy входную функцию main, wmain, WinMain или wWinMain. Когда входная функция возвращает управление, стартовый код библиотеки С/С++ вызываст ExitProcess. Поэтому первич ный поток приложения, написанного на С/С++, никогда не возвращается в Base ProcessStart.
Некоторые соображения по библиотеке С/С++
Microsoft поставляет с Visual С++ шесть библиотек С/С++. Их краткое описание пред ставлено в следующей таблице.
Имя библиотеки |
Описание |
|
|
LibC.lib |
Статически подключаемая библиотека для однопоточных приложений |
|
|
LibCD.lih |
Отладочная версия статически подключаемой библиотеки для однопо |
|
|
LibCMt.lib |
Статически подключаемая библиотека для многопоточных приложений |
|
|
LibCMtD.lib |
Отладочная версия статически подключаемой библиотеки для много |
MSVCRt.lib |
Библиотека импорта для динамического подключения рабочей версии |
|
|
MSVCRtD.lib |
Библиотека импорта дли динамического подключения отладочной версии |
|
MSVCRtD.dll; поддерживает как одно-, так и многопоточные приложения |
При реализации любого проекта нужно знать, с какой библиотекой его следует связать. Конкретную библиотеку можно выбрать в диалоговом окне Project Settings: на вкладке С/С++ в списке Category укажите Code Generation, а в списке Use Run-Time Library —
одну из шести библиотек.

Наверное, Вам уже хочется спросить: "А зачем мне отдельные библиотеки для од нопоточных и многопоточных программ?» Отвечаю. Стандартная библиотека С была разработана где-то в 1970 году — задолго до появления самого понятия многопоточ ности. Авторы этой библиотеки, само собой, не задумывались о проблемах, связан ных с многопоточными приложениями.
Возьмем, к примеру, глобальную переменную errno из стандартной библиотеки С. Некоторые функции, если происходит какая-нибудь ошибка, записывают в эту пере менную соответствующий код. Допустим, у Вас есть такой фрагмент кода:
BOOL fFailure = (system("NOTEPAD.EXE README.TXT") == -1);
if (fFailure)
{
switch (errno)
{
case E2BIG:
// список аргументов или размер окружения слишком велик break;
case ENOENT:
// командный интерпретатор не найден break;
case ENOEXEC;
// неверный формат командного интерпретатора break;
case ENOMEM:
// недостаточно памяти для выполнения команды break;
}
Теперь представим, что поток, выполняющий показанный выше код, прерван после вызова функции system и до оператора if. Допустим также, поток прерван для выпол
нения другого потока (в том же процессе), который обращается к одной из функций библиотеки С, и та тоже заиосит какое то значение в глобальную переменную errno Смотрите, что получается когда процессор вернется к выполнению первого потока, в
переменной errno окажется вовсе не то значение, которое было записано функци ей system Поэтому для решения этой проблемы нужно закрепить за каждым потоком свою переменную errno Кроме того, понадобится какой-то механизм, который позво лит каждому потоку ссылаться на свою переменную errno и нс трогать чужую
Это лишь один пример того, что стандартная библиотека С/С++ не рассчитана на многопоточные приложения Кроме errno, в ней есгь еще целый ряд переменных и функций, с которыми возможны проблемы в многопоточной среде _doserrno, strtok, _wcstok, strerror, _strerror, tmpnam, tmpfile, a<tcttme, _wascttme, gmttme, _ecvt, _Jcvt — спи сок можно продолжить
Чтобы многопоточные программы, использующие библиотеку С/С++, работали корректно, требуется создать специальную структуру данных и связать ее с каждым потоком, из которого вызываются библиотечные функции Более того, они должны знать, что, когда Вы к ним обращаетесь, нужно просматривать этот блок данных в вызывающем потоке чтобы не повредить данные в каком-нибудь другом потоке
Так откуда же система знает, что при создании нового потока надо создать и этот блок данных3 Ответ очень прост не знает и знать не хочет Вся ответственность — исключительно на Вас Если Вы пользуетесь небезопасными в многопоточной среде функциями, то должны создавать потоки библиотечной функцией _begmthreadex, а не
Windows-функцией CreateThread
unsigned long _beginthreadex( void *secunty unsigned stack size unsigned (*start_address)(void *) void *arglist unsigned initflag unsigned *thrdaddr)
У функции _beginthreadGX тот же список параметров, что и у CreateTbread, но их имена и типы несколько отличаются (Группа, которая отвечает в Microsoft за разра ботку и поддержку библиотеки С/С++, считает, что библиотечные функции не долж ны зависеть от типов данных Wmdows) Как и CreateTbread, функция _beginthreadex возвращает описатель только что созданного потока Поэтому, если Вы раньше поль зовались функцией CreateThread, cc вызовы в исходном коде несложно заменить на вызовы _begtnthreadex Однако из-за некоторого расхождения в типах данных Вам придется позаботиться об их приведении к тем, которые нужны функции _begin threadex, и тогда компилятор будет счастлив Лично я создал небольшой макрос chBEGINTHREADEX, который и делает всю эту работу в исходном коде
typedef unsigned ( stdcall *PTHREAD START) (void *)
#define chBEGINTHREADEX(psa cbStack pfnStartAddr \ pvParam fdwCreate pdwThreadID) \
((HANDLE) _beginthreadex( \ (void *) (psa) \
(unsigned) (cbStack), \ (PTHREAD_START) (pfnStartAddr) \ (void *) (pvParam) \
(unsigned) (fdwCreate) \ (unsigned *) (pdwThreadID)))
Заметьте, что функция _beginthreadex существует только в многопоточных верси ях библиотеки С/С++. Связав проект с однопоточной библиотекой, Вы получите от компоновщика сообщение об ошибке "unresolved external symbol». Конечно, это сде лано специально, потому что однопоточная библиотека не может корректно рабо тать в мпогопоточном приложении. Также обратите внимание на то, что при созда нии нового
проекта Visual Studio по умолчанию выбирает однопоточную библиоте ку. Этот вариант не самый безопасный, и для многопоточных приложений Вы долж ны сами выбрать одну из многопоточных версий библиотеки С/С++.
Поскольку Microsoft поставляет исходный код библиотеки С/С++, несложно разоб раться в том, что такого делает _beginthreadex, чего не делает CreateThread, На дистри бутивном компакт-диске Visual Studio ее исходный код содержится в файле Threadex.c. Чтобы нс перепечатывать весь код, я решил дать Вам cc версию в псевдокоде, выде лив самые интересные места.
unsigned long _cdocl _beginthreadex ( void *psa, unsigned cbStack,
unsigned (__stdcall * pTnStartAddr) (void *), void *pvParam, unsigned fdwCreate, unsigned *pdwThreadID)
{
_ptiddata ptd;
//указатель на блок данных потока unsigned long thdl,
//описатель потока
//выделяется блок данных для нового потока
if ((ptd = _calloc_crt(1, sizeof(struct tiddata))) == NULl) goto error_returnж
//инициализация блока данных initptd(ptd);
//здесь запоминается нужная функция потока и параметр,
//который мы хотим поместить в блок данных
ptd->_initaddr = (void *) pfnStartAddr; ptd->_initarg = pvParam;
// создание Honoio потока
thdl = (unsigned long)
CreateThread(psa, cbStack, _threadstartex, (PVOID) ptd, fdwCreate, pdwThrcadID);
if (thdl == NULl) {
//создать поток не удалось, проводится очистка и сообщается об ошибке
goto error_return;
}
//поток успешно создан; возвращается его описатель return(thdl);
error_return:
// ошибка! не удалось создать блок данных или сам поток
_free_crt(ptd);
return((unsigned long)0L);
}
Несколько важных моментов, связанных с _beginthreadex

Каждый поток получает свой блок памяти tiddata, выделяемый из кучи, кото рая принадлежит библиотеке С/Г++ (Структура tiddata определена в файле Mtdll h. Она довольно любопытна, и я привел ее на рис 6-2 )
Адрес функции потока, переданный _beginthreadex, запоминается в блоке па мяти tiddata Там же сохраняется и параметр, который должен быть передан этой функции
Функция _beginthreadex вызывает CreateThread, так как лишь с ее помощью операционная система может создать новый поток
При вызове CreateThread сообщается, что она должна начагъ выполнение но вого потока с функции _threadstartex, а не с того адреса, на который указыва ет fnStartAddr Кроме тою, функции потока передается не параметр рvParam а адрес структуры tiddata
Если все проходит успешно, beginthreadex, как и CreateThread, возвращает описатель потока В ином случае возвращается NULL
struct tiddata
{
unsigned long _tid; /* идентификатор потока */ unsigned long _thandle; /* описатель потока */ int terrno; /* значение errno */
unsigned long tdoserrno; /* значение _doserrno */ unsigned int _fpds; /* сегмент данных Floating Point */
unsigned lonq _holdrand; /* зародышевое значение для rand() */ char * _token; /* указатель (ptr) на метку strtok() */
#ifdef _WIN32
wchar_t *_wtoken; /* ptr на метку wcstok() */ #endif /* _WIN32 */
unsigned char * _mtoken; /* ptr на метку _mbstok() */
/* следующие указатели обрабатываются функцией malloc в период выполнения */
char * _errmsg; /* ptr на буфер strerror()/_strerror() */ char * _namebuf0; /* ptr на буфер tmpnam() */
#ifdef _WIN32
wchar_t * _wnarnebuf0; /* ptr на буфер_wtmpnam() */ #endif /* _WIN32 */
char * _namebuf1 /* ptr на буфер tmpfile() */
#ifdef _WIN32
wchar_t * _wnamebuf1; /* ptr ма буфер wTmpfi]e() */ #endif /* _WIN32 */
char * _asctimebuf; /* ptr на буфер asctime() */
#ifdef _WIN32
wchar_t * _wasctimebuf; /* ptr на буфер _wasctime() */ #endif /* _WIN32 */
void * _gmtimebuf; /* ptr на структуру gmtime() */ char * _cvtbuf; * /* ptr на буфер ecvt()/fcvt */
/* следующие поля используются кодом _beginthread */
void * _initaddr; /* начальный адррс пользовательское потока */ void * _initarg; /* начальный аргумент пользовательского потока */
/* следующие три поля нужны для поддержки функции signal и обработки ошибок, возникающих в период выполнения */
void * _pxcptaottab; /* ptr на таблицу исключение-действие */
void * _tpxcptaofoptrs; /* ptr на указагели к информации об исключении
*/
int _tfpecode; /* код исключения для операций над числами с плавающей точкой */
/* следующее поле нужно подпрограммам NLG */ unsigned long _NLG_dwCode;
/* данные для отдельного потока используемые при обработке исключений в С++ */
void * _terminate; /* подпрограмма terminate() */ void * _unexpected; /* подпрограмма unexpected() */ void * _translator; /* транслятор S E */
void * _curexception; /* текущее исключение */
void * _curcontext; /* контекст текущего исключения */
#if defined (_M_MRX000) void * _pFrameInfoChain; void * _pUnwindContext; void * _pExitContext, int _MipsPtdDelta;
int _MipsPtdEpsilon; #elif defined (_M_PPC) void * __pExitContext; void * _pUnwindContext; void * _pFrameInfoChain; int _FrameInfo[6];
#endif /* defined (_M_PPC) */
};
typedef struct _tiddata * _ptiddata;
Рис. 6-2. Локальная структура tiddata потока, определенная в библиотеке С/С++
Выяснив, как создается и инициализируется структура tiddata для нового потока, посмотрим, как она сопоставляется с этим потоком Взгляните на исходный код фун кции _threadstartex (который тоже содержится в файле Threadex с библиотеки С/С++) Вот моя версия этой функции в псевдокоде
static unsigned long WINAPI threadstartex (void* ptd)
{
//Примечание ptd - это адрес блока tiddata данного потока
//блок tiddata сопоставляется с данным потоком
TlsSetValue( __tlsindex ptd);
//идентификатор этого потока записывается в tiddata
((_ptiddata) ptd)->_tid = GetCurrentThreadId();
//здесь инициализируется поддержка операций над числами с плавающей точкой
//(код не показан)

//пользовательская функция потока включается в SEH-фрейм для обработки
//ошибок периода выполнения и поддержки signal
__try
{
//здесь вызывается функция потока, которой передается нужный параметр;
//код завершения потока передается _endthreadex
_endthreadex( ( (unsigned (WINAPI *)(void *))(((_ptiddata)ptd)- >_initaddr) ) ( ((_ptiddata)ptd)->_initarg ) ) ;
}
_except(_XcptFilter(GetExceptionCode(), GetExceptionInformation()))
{
// обработчик исключений из библиотеки С не даст нам попасть сюда
_exit(GetExceptionGode());
}
// здесь мы тоже никогда не будем, так как в этой функции поток умирает
return(0L);
}
Несколько важных моментов, связанных со _threadstartex.
Новый поток начинает выполнение с BaseThreadStart (в Kernel32.dll), а затем переходит в _threadstartex.
В качестве единственного параметра функции _threadstartex передается адрес блока tiddata нового потока,
Windows-функция TlsSetValue сопоставляет с вызывающим потоком значение, которое называется локальной памятью потока (Thread Local Storage, TLS) (о ней я расскажу в главе 21), a _threadstartex сопоставляет блок tiddata с новым потоком.
Функция потока заключается в SEH-фрейм. Он предназначен для обработки ошибок периода выполнения (например, не перехваченных исключений С++), поддержки библиотечной функции signal и др. Этот момент, кстати, очень ва жен. Если бы Вы создали поток с помощью CreateThread, а потом вызвали биб лиотечную функцию signal, она работала бы некорректно.
Далее вызывается функция потока, которой передается нужный параметр. Ад рес этой функции и ее параметр были сохранены в блоке tiddata функцией
_beginthreadex.
Значение, возвращаемое функцией потока, считается кодом завершения это го потока. Обратите внимание: _threadstartex не возвращается в BaseThreadStart. Иначе после уничтожения потока его блок tiddata так и остался бы в памяти. А это привело бы к утечке памяти в Вашем приложении. Чтобы избежать этого, threadstartex вызывает другую библиотечную функцию, _endthreadex, и пере дает ей код завершения.
Последняя функция, которую нам нужно рассмотреть, — это _endthreadex (ее ис ходный код тоже содержится в файле Threadex.c). Вот как она выглядит в моей вер сии (в псевдокоде)

void _cdecl _endthreadex (unsigned retcode)
{
_ptiddata ptd;
//указатель на блок данных потока
//здесь проводится очистка ресурсов, выделенных для поддержки операций
//над числами с плавающей точкой (код не показан)
//определение адреса блока tiddata данного потока
ptd = _getptd();
//высвобождение блока tiddata
_freeptd(ptd);
//завершение потока
ExitThread(retcode);
}
Несколько важных моментов, связанных с _endthreadex
Библиотечная функция _getptd обращается к Windows-функции TlsGetValue, которая сообщает адрес блока памяти tiddata вызывающего потока
Этот блок освобождается, и вызовом ExttThread поток разрушается. При этом, конечно, передается корректный код завершения.
Где-то в начале главы я уже говорил, что прямого обращения к функции ExitThread следует иpбегать Это правда, и я не отказываюсь от своих слов. Тогда же я сказал, что это приводит к уничтожению вызывающего потока и не позволяет ему вернуться из выполняемой в данный момент функции А поскольку она не возвращает управление, любые созданные Вами С++-объекты не разрушаются. Так вот, теперь у Вас есть еще одна причина не вызывать ExitThread. она не дает освободить блок памяти tiddata потока, из-за чего в Вашем приложении может наблюдаться утечка памяти (до его pавершения)
Разработчики Microsoft Visual C++, конечно, прекрасно понимают, что многие все равно будут пользоваться функцией ExitThread, поэтому они кое-что сделали, чтобы свести к минимуму вероятность утечки памяти. Если Вы действительно так хотите самостоятельно уничтожить свой поток, можете вызвать из него _endthreadex (вмес то ExitTbread) и тем самым освободить его блок tiddata. И все же я не рекомендую этого
Сейчас Вы уже должны понимать, зачем библиотечным функциям нужен отдель ный блок данных для каждого порождаемого потока и каким образом после вызова _beginthreadex происходит создание и инициализация этого блока данных, а также его связывание с только что созданным потоком. Кроме того, Вы уже должны разби раться в том, как функция _endthreadex освобождает этот блок по завершении потока.
Как только блок данных инициализирован и сопоставлен с конкретным потоком, любая библиотечная функция, к которой обращается поток, может легко узнать ад рес его блока и таким образом получить доступ к данным, принадлежащим этому потоку.
Ладно, с функциями все ясно, теперь попробуем проследить, что происходит с глобальными переменными вроде errno. В заголовочных файлах С эта переменная определена так:
#if defined(_MT) || defined(_DLL) extern int * _cdecl _errno(void);
#define errno (*_еггпо())
#else /* ndef _MT && ndef _DLL */ extern int errno;
#endif /* MT | | _DLL */
Создавая многопоточное приложение, надо указывать в командной строке ком пилятора один из ключей /MT (многопоточное приложение) или /MD (многопоточ
ная DLL); тогда компилятор определит идентификатор _MT. После этого, ссылаясь на errno, Вы будете на самом деле вызывать внутреннюю функцию _errno из библиотеки С/С++. Она возвращает адрес элемента данных errno в блоке, сопоставленном с вы зывающим потоком. Кстати, макрос errno составлен так, что позволяет получать co держимое памяти по этому адресу А сделано это для того, чтобы можно было писать, например, такой код
int *p = &errno;
if (*p == ENOMEM){
...
}
Если бы внутренняя функция _errno просто возвращала значение errno, этот код не удалось бы скомпилировать.
Многопоточная версия библиотеки С/С++, кроме того, "обертывает" некоторые функции синхронизирующими примитивами Всдь если бы два потока одновремен но вызывали функцию malloc, куча могла бы быть повреждена. Поэтому в многопо точной версии библиотеки потоки не могут одновременно выделять память из кучи. Второй поток она заставляет ждать до тех пор, пока первый не выйдет из функции malloc, и лишь тогда второй поток получает доступ к malloc. (Подробнее о синхрони зации потоков мы поговорим в главах 8, 9 и 10.)
Конечно, все эти дополнительные операции нс могли не отразиться на быстро действии многопоточной версии библиотеки Поэтому Microsoft, кроме многопоточ ной, поставляет и однопоточную версию статически подключаемой библиотеки С/С++.
Динамически подключаемая версия библиотеки С/С++ вполне универсальна ее могут использовать любые выполняемые приложения и DLL, которые обращаются к библиотечным функциям. По этоЙ причине данная библиотека существует лишь в многопоточной версии. Поскольку она поставляется в виде DLL, ее код не нужно вклю чать непосредственно в EXE- и DLL-модули, что существенно уменьшает их размер. Кроме того, если Microsoft исправляет какую-то ошибку в такой библиотеке, то и программы, построенные на ее основе, автоматически избавляются от этой ошибки
Как Вы, наверное, и предполагали, стартовый код из библиотеки С/С++ создает и инициализирует блок данных для первичного потока приложения. Это позволяет без всяких опасений вызывать из первичного потока любые библиотечные функции А когда первичный поток заканчивает выполнение своей входной функции, блок дан ных завершаемого потока освобождается самой библиотекой Более того, стартовый код делает все необходимое для сгруктурной обработки исключений, благодаря чему из первичного потока можно спокойно обращаться и к библиотечной функции signal.