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

лекции / Shchupak_Yu._Win32_API_Razrabotka_prilozheniy_dlya_Windows

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

441

9Многозадачность

Эта глава содержит краткое изложение поддержки многозадачности в Win32 API. Более подробную информацию вы можете найти в книге [5] или в справочных материалах MSDN.

Объекты ядра

Современные процессоры позволяют выполнять программы в одном из двух режи" мов: Kernel (режим ядра) или User (режим пользователя). В режиме ядра приложе" нию, кроме всего прочего, разрешено выделять память для других приложений. Поэтому изменить важные данные в памяти компьютера, например, управляющие распределением памяти, может только приложение, работающее в режиме ядра.

Архитектура операционной системы Windows NT/2000 состоит из двух основ" ных частей: привилегированной подсистемы режима ядра (privileged kernel mode part) и непривилегированной подсистемы пользовательского режима (nonprivileged user mode part).

Подсистема ядра содержит следующие компоненты:

HAL (Hardware Abstraction Layer) — уровень, абстрагирующий другие компо" ненты от аппаратных различий, зависимых от платформы.

Микроядро (MicroKernel), которое отвечает за планирование потоков, пере" ключение задач, обработку прерываний и исключительных ситуаций, много" процессорную синхронизацию.

Драйверы устройств — драйверы оборудования, файловой системы и сетевой поддержки, реализующие пользовательские функции ввода"вывода.

Управление окнами и графическая подсистема реализуют функции графичес" кого интерфейса.

Исполнительная часть отвечает за управление памятью, управление процес" сами и потоками, безопасность, ввод"вывод данных и взаимодействия между процессами.

Объекты ядра используются системой и приложениями для управления са" мыми разнообразными ресурсами, например процессами, потоками, файлами, се" мафорами, событиями и многими другими.

442

Глава 9. Многозадачность

 

 

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

кструктурам данных, представляющим собой объекты ядра. Оперировать объек" тами ядра приложение может только через специальные функции Windows.

Когда вы вызываете функцию, создающую объект ядра (например, CreateFile), она возвращает дескриптор созданного объекта. Этот дескриптор может быть ис" пользован любым потоком вашего процесса. Обычно значения дескрипторов объектов ядра действительны только в адресном пространстве процесса, их создав" шего. Поэтому все попытки использования такого дескриптора в другом процес" се приводят к ошибкам. Исключение составляют объекты ядра, предназначенные для синхронизации или обмена данными между процессами. Более подробно о них будет сказано ниже.

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

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

Многие объекты ядра находятся всегда в одном из двух состояний: свободном состоянии (signaled state) либо несвободном состоянии (nonsignaled state). Переход из одного состояния в другое осуществляется по правилам, определенным Micro" soft для каждого из объектов ядра. Эти состояния могут использоваться так называ" емыми wait функциями для синхронизации выполнения потоков.

Объекты ядра можно защитить с помощью дескриптора защиты (security descriptor). Он содержит информацию о том, кто создал объект и кто имеет пра" ва на доступ к нему. Дескрипторы защиты обычно используют при написании серверных приложений. Создавая клиентское приложение, можно просто игно" рировать это свойство объектов ядра.

Почти все функции, создающие объекты ядра, принимают в качестве аргумен" та указатель на структуру SECURITY_ATTRIBUTES. В большинстве случаев на месте соответствующего аргумента можно указать значение NULL, и тогда объект созда" ется с защитой по умолчанию. В этом случае создатель объекта и любой член груп" пы администраторов получают полный доступ к объекту, а все прочие процессы

кобъекту не допускаются.

Вне зависимости от того, как был создан объект ядра, после окончания работы с ним его нужно закрыть вызовом функции CloseHandle:

BOOL CloseHandle(HANDLE hObject);

Эта функция сначала проверяет таблицу дескрипторов данного процесса, что" бы убедиться, что процесс имеет доступ к объекту hObject. Если доступ разрешен,

Процессы и потоки

443

 

 

то система получает адрес структуры данных объекта hObject и уменьшает в ней счетчик количества пользователей. Как только счетчик обнулится, ядро удаляет объект из памяти.

Если же дескриптор неверен, то функция CloseHandle возвращает значение FALSE, а функция GetLastError — код ERROR_INVALID_HANDLE.

Перед самым возвратом управления функция CloseHandle удаляет соответству" ющую запись из таблицы дескрипторов. После этого дескриптор hObject считает" ся недоступным для данного процесса, и его нельзя более использовать. Но если счетчик пользователей этого объекта не обнулен, то объект остается в памяти. Это означает, что объект используется другими процессами. Когда и остальные процессы завершат свою работу с этим объектом, тоже вызвав функцию CloseHandle, он будет разрушен.

Ранее указывалось, что объекты ядра используются только в рамках процес" са, их создавшего. Но все же иногда возникает необходимость в совместном ис" пользовании объектов ядра несколькими процессами, например, в следующих ситуациях:

объект ядра «проекция файла» позволяет двум процессам, исполняемым на одной машине, совместно использовать одни и те же блоки памяти;

почтовые ящики и именованные каналы дают возможность программам обме" ниваться данными с процессами, исполняемыми на других машинах в сети;

мьютексы, семафоры и события позволяют синхронизировать потоки, испол" няемые в разных процессах, чтобы одно приложение могло уведомить другое приложение об окончании той или иной операции.

Win32 API предоставляет три механизма, позволяющие процессам исполь" зовать одни и те же объекты ядра: а) наследование дескриптора объекта в дочер" нем процессе; б) именованные объекты; в) дублирование дескрипторов объек" тов. Второй из указанных механизмов основан на использовании совпадающих имен для разделяемых объектов. Именно он будет применен в программных примерах, рассматриваемых ниже в разделе «Обмен данными между процес" сами».

Процессы и потоки

При запуске приложения операционная система Windows создает процесс. Про цесс (process) — это совокупность ресурсов, необходимых для выполнения про" граммы. Процесс владеет виртуальным адресным пространством, выполняемым кодом, данными, дескрипторами необходимых объектов и иными ресурсами. Од" нако сам по себе процесс не выполняется. Вместо этого он запускает единствен" ный поток, который часто называют первичным потоком (primary thread). Если процесс имеет только один первичный поток, то, фактически, понятия «процесс» и «поток» совпадают. Первичный поток может создавать другие потоки, те, в свою очередь, новые потоки и т. д.

Поток (thread) — это основная выполняемая единица, для которой операци" онная система выделяет процессорное время. Каждый поток работает со своим контекстом. Контекст потока (thread context) — это структура, содержащая зна"

444

Глава 9. Многозадачность

 

 

чения всех регистров процессора. Кроме того, поток имеет доступ ко всем ресур" сам своего процесса, включая память, открытые файлы и другие ресурсы.

Обычно приложение содержит только один процесс, поэтому термины «про" грамма» и «процесс» часто используются как синонимы. В то же время любой поток процесса может создать дочерний процесс, выполняющийся одновременно с родительским процессом.

Планирование потоков

Чтобы все потоки работали, операционная система выделяет каждому из них оп" ределенное процессорное время. Тем самым создается иллюзия одновременного выполнения потоков. Разумеется, для многопроцессорных систем возможен ис" тинный параллелизм.

Каждый поток может находиться в одном из трех состояний, показанных на рис. 9.1.

Рис. 9.1. Диаграмма состояний потока

В однопроцессорной системе в любой момент времени только один поток мо" жет находиться в состоянии выполнения. Все остальные потоки находятся либо в состоянии готовности, либо в состоянии блокировки.

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

Выбранный для выполнения поток работает в течение некоторого периода, называемого квантом. Windows оперирует квантом потока не как отрезком вре" мени, а как целым числом. Обычно поток стартует со значением кванта, равным 6 — для Windows 2000 Professional или 36 — для Windows 2000 Server.

Каждый раз, когда возникает прерывание от системного таймера, из кванта выполняющегося потока вычитается фиксированное значение 3, и так продолжается до тех пор, пока значение кванта не достигнет нуля. Поэтому под управлением Windows 2000 Professional поток будет выполняться в течение двух интервалов системного таймера, а под управлением Windows 2000 Server — в течение 12 интер" валов.

Интервал (или период) системного таймера обычно равен 10 мс или около 15 мс, в зависимости от аппаратной платформы. Точное значение этого интервала

Планирование потоков

445

 

 

можно получить с помощью функции GetSystemTimeAdjustment (см. главу 10). На" пример, для моего компьютера с процессором Intel Celeron CPU 2.0 ГГц период срабатывания системного таймера равен 15,625 мс. При таком периоде кванту потока соответствует временной интервал 15,625 . 2 = 31,25 мс.

Когда после очередного прерывания квант потока становится равным нулю, Windows переводит поток в состояние готовности и ищет в системе поток с са" мым высоким приоритетом, находящимся в состоянии готовности. Если в состоя" нии готовности находятся несколько потоков с приоритетом не ниже предыдуще" го выполняемого потока, то следующий поток из очереди будет переведен в состояние выполнения и начнет функционировать. Таким образом, потоки с оди" наковым уровнем приоритета обслуживаются в циклическом порядке. Впрочем, при отсутствии других претендентов предыдущий поток может получить еще один квант.

Однако выполняемый поток не всегда полностью использует свой квант. Его выполнение может быть прервано при ненулевом кванте в двух ситуациях:

когда появился в состоянии готовности другой поток с более высоким при" оритетом; при этом текущий поток вытесняется и переводится в состояние го" товности;

текущему потоку потребовался какой"либо системный ресурс (или объект ядра), который в настоящий момент времени является занятым; в этом случае поток переводится в состояние блокировки (ожидания события).

Выбрав новый поток, операционная система переключает контекст. Эта опе" рация заключается в сохранении содержимого регистров процессора для вытес" ненного потока и загрузке контекста для выбранного потока.

Классы приоритетов процесса и приоритеты потоков

Windows поддерживает 32 приоритета (от 0 до 31) — чем больше номер, тем выше приоритет. Приоритет потока складывается из двух составляющих: класса при оритета процесса, его создавшего, и относительного приоритета потока внутри этого класса.

Классы приоритетов процессов приведены в табл. 9.1.

Таблица 9.1. Классы приоритетов

Класс

Флаг в функции CreateProcess

Базовый уровень

 

 

 

Idle

IDLE_PRIORITY_CLASS

4

Below normal

BELOE_NORMAL_PRIORITY_CLASS

6

Normal

NORMAL_PRIORITY_CLASS

8

Above normal

ABOVE_NORMAL_PRIORITY_CLASS

10

High

HIGH_PRIORITY_CLASS

13

Realtime

REALTIME_PRIORITY_CLASS

24

 

 

 

Классы Below normal и Above normal стали использоваться, начиная с Windows 2000. Класс Idle назначается процессу, который должен простаивать в случае ак" тивности других процессов, например для приложения — хранителя экрана.

446

Глава 9. Многозадачность

 

 

Процессам, запускаемым пользователем, присваивается класс Normal. Это са" мые многочисленные процессы в системе. Как правило, они являются интерак" тивными, то есть требуют постоянного взаимодействия с пользователем, как, на" пример, графические или текстовые редакторы. Процессы класса Normal делятся на процессы переднего плана (foreground) и фоновые (background). Для процесса,

скоторым пользователь в данный момент работает, то есть для процесса передне" го плана, уровень приоритета поднимается на две единицы. Это повышает ком" фортабельность общения пользователя с прикладной программой.

Создавать процессы, относящиеся к классу High, следует с большой осторож" ностью. Если поток с классом приоритета High занимает процессор достаточно долго, то в это время другие потоки вообще не получат доступа к процессору. Обыч" но с классом High работают некоторые системные процессы, которые большую часть времени ожидают какого"либо события, например, winlogon.exe. Если в вашем приложении какая"то подзадача требует быстрой реакции на некоторое событие, то вы можете повышать класс приоритета процесса до значения High именно на тот период, когда решается эта подзадача, а затем возвращать его к зна" чению Normal. Для изменения класса приоритета процесса во время работы при" ложения может применяться функция SetPrioriryClass.

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

По умолчанию создаваемый поток получает базовый приоритет в соответствии

склассом своего процесса. После создания потока его приоритет может изме" няться как операционной системой, так и приложением с помощью функции SetThreadPriority. В табл. 9.2 приведены относительные приоритеты потоков.

Таблица 9.2. Относительные приоритеты потоков

Относительный

Флаг в функции

Описание

приоритет

SetThreadPriority

 

 

 

 

Idle

THREAD_PRIORIRY_IDLE

Для процессов класса Realtime прио-

 

 

ритет потока равен 16, для процес-

 

 

сов остальных классов равен 1

Lowest

THREAD_PRIORIRY_LOWEST

Приоритет потока меньше базового

 

 

приоритета на 2

Below normal

THREAD_PRIORIRY_BELOW_NORMAL

Приоритет потока меньше базового

 

 

приоритета на 1

Normal

THREAD_PRIORIRY_NORMAL

Приоритет потока равен базовому

 

 

приоритету

Above normal

THREAD_PRIORIRY_ABOVE_NORMAL

Приоритет потока больше базового

 

 

приоритета на 1

Highest

THREAD_PRIORIRY_HIGHEST

Приоритет потока больше базового

 

 

приоритета на 2

Time critical

THREAD_PRIORIRY_TIME_CRITICAL

Для процессов класса Realtime прио-

 

 

ритет потока равен 31, для процес-

 

 

сов остальных классов равен 15

 

 

 

Управление процессами

447

 

 

Управление процессами

Самый распространенный способ начала процесса — это осуществить запуск при" ложения в Проводнике (Explorer), либо в меню Пуск (Start), либо набрав название программы в командной строке. Кроме того, Win32 API содержит несколько функ" ций, которые можно использовать для создания и управления процессами.

Использование функции CreateProcess

Функция CreateProcess создает новый процесс и его первичный поток. Она имеет следующий прототип:

BOOL CreateProcess(

LPCTSTR lpApplicationName, // имя исполняемого файла LPTSTR lpCommandLine, // командная строка

LPSECURITY_ATTRIBUTES processAttributes, // атрибуты доступа к процессу LPSECURITY_ATTRIBUTES threadAttributes, // атрибуты доступа к потоку BOOL bInheritHandles, // флаг наследования дескрипторов

DWORD dwCreationFlags, // флаги создания и флаги класса приоритета LPVOID lpEnvironment, // указатель на параметры настройки окружения LPCTSTR lpCurrentDirectory, // путь к текущему каталогу LPSTARTUPINFO lpStartupInfo, // указатель на структуру STARTUPINFO LPPROCESS_INFORMATION lpProcessInformation // указатель на структуру

// PROCESS_INFORMATION

);

Имя исполняемого файла можно задать в первом или втором параметре. Параметр lpCommandLine позволяет указать полную командную строку, исполь"

зуемую функцией CreateProcess при создании нового процесса. Разбирая эту строку, функция полагает, что первый компонент в ней представляет собой имя исполняе" мого файла. Если в этом имени расширение не указано, она считает его .exe. Далее функция приступает к поиску заданного файла и делает это в следующем порядке:

1.Текущий каталог вызывающего процесса.

2.Системный каталог Windows.

3.Основной каталог Windows.

4.Каталоги, перечисленные в переменной окружения PATH.

Конечно, если в имени файла указан полный путь доступа, то система сразу обращается туда и не просматривает эти каталоги.

Чаще всего параметру lpApplicationName передается значение NULL, а имя ис" полняемого файла содержится в параметре lpCommandLine.

Перед вызовом функции CreateProcess вы должны определить две структурные переменные типа STARTUPINFO и PROCESS_INFORMATION:

STARTUPINFO si; PROCESS_INFORMATION pi;

Адреса этих структур передаются в двух последних параметрах функции

CreateProcess.

Элементы структуры STARTUPINFO задают атрибуты отображения нового про" цесса. Надо сказать, что большинство приложений порождает процессы с атрибу" тами по умолчанию. Но и в этом случае вы должны инициализировать все поля структуры STARTUPINFO хотя бы нулевыми значениями, а в поле cb занести размер этой структуры.

448

Глава 9. Многозадачность

 

 

Если запуск нового процесса осуществлен успешно, то функция CreateProcess возвращает значение TRUE и помимо этого заполняет поля структуры PROCESS_ INFORMATION, которая определена следующим образом:

typedef struct _PROCESS_INFORMATION {

HANDLE hProcess;

// дескриптор нового процесса

HANDLE hThread;

// дескриптор первичного потока нового процесса

DWORD

dwProcessId; // идентификатор нового процесса

DWORD

dwThreadId;

// идентификатор первичного потока нового процесса

} PROCESS_INFORMATION;

Описание остальных параметров функции CreateProcess можно найти в спра" вочных материалах MSDN. Для этих параметров часто используются значения по умолчанию.

Когда поток в приложении вызывает функцию CreateProcess, система создает объект ядра «процесс» с начальным значением счетчика его пользователей, рав" ным единице. Затем система создает для нового процесса виртуальное адресное пространство1 и загружает в него код и данные как для исполняемого файла, так и для любых DLL, если они используются в работе. Далее система формирует объект ядра «поток» со счетчиком, равным единице, для первичного потока ново" го процесса.

Первичный поток начинает с исполнения стартового кода из библиотеки C/C++, который в конечном счете вызывает функцию WinMain, wWinMain, main или wmain2 в запускаемой программе.

Завершение процесса

Процесс можно завершить четырьмя способами:

Входная функция первичного потока, например WinMain, возвращает управле" ние (предпочтительный способ).

Один из потоков процесса вызывает функцию ExitProcess.

Поток другого процесса вызывает функцию TerminateProcess (нежелательный способ).

Все потоки процесса завершаются по своей воле. Но это случается очень редко.

Рекомендуется проектировать приложение так, чтобы его процесс завершался только после возврата управления функцией первичного потока. Это единствен" ный способ, который гарантирует корректную очистку всех ресурсов, принадле" жащих первичному потоку. При таком завершении любые объекты C++, создан" ные данным потоком, уничтожаются соответствующими деструкторами. Система освобождает память, которую занимал стек потока, и устанавливает код заверше" ния процесса, который и возвращает входная функция.

Вы можете также завершить процесс, вызвав функцию ExitProcess. В справоч" ных материалах MSDN этот способ указан как рекомендуемый. В то же время Дж. Рихтер указывает [5], что возможны ситуации, когда при данном способе

1Более подробно о виртуальном адресном пространстве говорится в разделе «Виртуальная память. Адресное пространство процесса».

2С символа «w» начинаются имена Unicode"версий входных функций первичного потока. Функции main и wmain используются в консольных приложениях.

Управление процессами

449

 

 

завершения процесса не для всех объектов C++ будут вызваны деструкторы. Ко" нечно, операционная система и в этом случае корректно очистит все ресурсы, выде" ленные процессу. Но при этом весьма вероятна утечка памяти или других ресурсов.

Вызов функции TerminateProcess тоже завершает процесс. Главное отличие этой функции от ExitProcess заключается в том, что ее может вызвать любой поток и за" вершить при этом любой процесс. Пользуйтесь функцией TerminateProcess лишь в крайнем случае, когда иным способом завершить процесс не удается. Процесс не получает абсолютно никаких уведомлений, что он завершается, и приложение не может выполнить очистку ресурсов или предотвратить свое неожиданное за" вершение. При этом теряются все данные, которые программа не успела перепи" сать из памяти на диск. Но операционная система и в этом случае освобождает все принадлежавшие процессу ресурсы.

Четвертая ситуация может возникнуть, если все потоки вызвали ExitThread или они были закрыты другими потоками, вызвавшими функцию TerminateThread. Об" наружив, что в процессе нет исполняющихся потоков, операционная система не" медленно завершает его. Код завершения процесса приравнивается к коду завер" шения последнего потока.

В случае завершения процесса системой выполняются следующие действия.

Выполнение всех потоков в процессе прекращается.

Все User" и GDI"объекты, созданные процессом, уничтожаются, а объекты ядра закрываются, если их не использует другой процесс.

Объект ядра «процесс» переходит в свободное, или незанятое (signaled), со" стояние.

Счетчик пользователей объекта ядра «процесс» уменьшается на единицу.

Запуск обособленных дочерних процессов

В большинстве случаев приложение создает другие процессы как обособленные (detached processes). Это значит, что после создания и запуска нового процесса родительскому процессу нет нужды взаимодействовать с ним или ждать, пока он закончит работу. Именно так и действует Explorer. Это приложение запускает для пользователя новые процессы, а после этого более не следит за их судьбой.

Приведенное ниже приложение CreateMyProcess демонстрирует, как можно выз" вать из вашей программы стандартное приложение Windows Калькулятор (calc.exe).

Создавая приложение CreateMyProcess, добавьте к нему ресурс меню с иден" тификатором IDR_MENU1. Меню должно содержать один пункт с именем Create process и идентификатором IDM_CREATE_PROCESS. Скопируйте также в папку про" екта CreateMyProcess файлы KWnd.h и KWnd.cpp из листинга 1.2 и добавьте их в состав проекта.

Листинг 9.1. Проект CreateMyProcess

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

// CreateMyProcess.cpp #include <windows.h> #include "resource.h" #include "KWnd.h"

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //====================================================================

450 Глава 9. Многозадачность

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)

{

MSG msg;

KWnd mainWnd("CreateMyProcess", hInstance, nCmdShow, WndProc, MAKEINTRESOURCE(IDR_MENU1), 100, 100, 400, 300);

while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg);

}

return (msg.wParam);

}

//==================================================================== LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

HDC hDC; PAINTSTRUCT ps;

STARTUPINFO si;

static PROCESS_INFORMATION pi; BOOL success;

switch (uMsg)

{

case WM_COMMAND:

switch (LOWORD(wParam))

{

case IDM_CREATE_PROCESS: ZeroMemory( &si, sizeof(si) ); si.cb = sizeof(si);

success = CreateProcess( NULL, "calc.exe", NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);

if (!success)

MessageBox(hWnd, "Error of CreateProcess", NULL, MB_OK); break;

default:

break;

}

case WM_PAINT:

hDC = BeginPaint(hWnd, &ps); EndPaint(hWnd, &ps);

break;

case WM_DESTROY: CloseHandle(pi.hProcess); CloseHandle(pi.hThread); PostQuitMessage(0); break;

default:

return DefWindowProc(hWnd, uMsg, wParam, lParam);

}

return 0;

}

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