    ОБЪЕКТЫ ЯДРА 

    г      л        а        в        а              2 

    Изучение Win32 API мы начнем с объектов ядра и их описателей (handles). 
Эта глава посвящена сравнительно абстрактным концепциям, т. е. мы, не 
углубляясь в специфику тех или иных объектов ядра, рассмотрим их общие 
свойства. Я бы предпочел начать с чего-то более конкретного, но без четкого 
понимания объектов ядра Вам не стать настоящим профессионалом в области 
разработки Win32-nporpaMM. Эти объекты используются системой и нашими 
приложениями для управления множеством самых разных ресурсов: процессами, 
потоками, файлами и т. д. 
    Концепции, представленные здесь, будут встречаться по всей книге. Однако я 
прекрасно понимаю, что часть материалов не уляжется у Вас в голове до тех пор, 
пока Вы не приступите к работе с объектами ядра, используя реальные функции. И 
при чтении последующих глав книги Вы, наверное, будете время от времени 
возвращаться к этой главе. 

    Что такое объект ядра 

    Создание, открытие и прочие операции с объектами ядра станут для Вас, как 
разработчика \Уш32-приложений, повседневной рутиной. Система позволяет 
создавать и оперировать с несколькими типами таких объектов, в том числе: 

    Каналами         Почтовыми ящиками     Семафорами 
    Мьютексами      Проекциями файлов     Событиями 
    Потоками         Процессами             Файловыми объектами 

    Эти объекты создаются '\Ут32-функциями. Например, CreateFileMapping 
заставляет систему сформировать объект «проекция файла» (file-mapping object). 
Каждый объект ядра — на самом деле блок памяти, выделенный ядром и доступный 
только ему. Этот блок представляет собой структуру данных, в элементах которой 
содержится информация об объекте. Некоторые элементы (имя объекта, дескриптор 
защиты, счетчик числа пользователей и др.) присутствуют во всех объектах, но 
большая их часть специфична для объектов конкретного типа. Например, у объекта 
«процесс» есть идентификатор процесса, базовый приоритет и код завершения, а у 
объекта «файл» — смещение в байтах, режим разделения и режим открытия. 
    Поскольку структуры объектов ядра доступны только ядру, приложение не 
может самостоятельно найти эти структуры в памяти и напрямую модифицировать их 
содержимое. Такое ограничение Microsoft ввела намеренно, чтобы ни одна 
программа не нарушила целостность структур объектов ядра. Это же ограничение 
позволяет Microsoft вводить, удалять или изменять элементы структур, не 
нарушая работы каких-либо приложений. 

    8   ______________________________Windows для профессионалов 

    Но вот вопрос: если мы не можем напрямую модифицировать эти структуры, то 
как же наши приложения оперируют с объектами ядра? Ответ в том, что Win32 API 
предусматривает набор функций, обрабатывающих эти объекты по строго 
определенным правилам. Мы получаем доступ к объектам ядра только через эти 
функции. Когда Вы вызываете функцию, создающую объект ядра, она возвращает 
описатель, идентифицирующий созданный объект. Описатель следует рассматривать 
как «непрозрачное» 32-битное значение, которое может быть использовано любым 
потоком Вашего процесса. Вы передаете его ЛУт32-функциям, сообщая системе, 
какой объект ядра Вас интересует. Но об описателях мы поговорим позже (в этой главе). 
    Для большей надежности операционной системы Microsoft сделала так, чтобы 
значения описателей зависели от конкретного процесса. Поэтому, если Вы 
передадите такое значение (с помощью какого-либо механизма межпроцессной 
связи) потоку другого процесса, любой вызов из того процесса со значением 
описателя, полученного в Вашем процессе, даст ошибку. Но не волнуйтесь, в 
конце главы мы рассмотрим три механизма корректного использования несколькими 
процессами одного объекта ядра. 

    Учет пользователей объектов ядра 

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

    Защита 

    Объекты ядра можно защитить дескриптором защиты (security descriptor), 
который описывает, кто создал объект и кто имеет права на доступ к нему. 
Дескрипторы защиты обычно используют при написании серверных приложений; 
создавая клиентское приложение, Вы можете игнорировать это свойство объектов 
ядра. Кстати, в Windows 95 дескрипторы защиты отсутствуют, так как она не 
предназначена для выполнения серверных приложений. 
    NDOWS/ В Windows 95 нет полной поддержки защиты. Тем не менее Вы должны 
знать о тонкостях, связанных с защитой, и реализовать соответствующие 
механизмы, чтобы Ваше приложение корректно работало и в Windows NT. 
    Почти все функции, создающие объекты ядра, принимают указатель на 
структуру SECURITY_ATTRIBUTES как аргумент, например: 

    HANDLE CreateFileMapping(HANDLE hFile, 
    LPSECURITY_ATTRIBUTES IpFileMappingAttnbutes, DWORD flProtect, 
    DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow, LPCTSTR IpszName): 

    Большинство приложений вместо этого аргумента передают NULL и создают 
объект с защитой по умолчанию. Такая защита подразумевает, что администратор и создатель 

    Глава 2. Объекты ядра   9 

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

    typedef struct _SECURITY_ATTRIBUTES { 
    DWORD nLength; 
    LPVOID IpSecurityDescriptor; 
    BOOL  bInheritHandle; 
    } SECURITY_ATTRIBUTES; 

    Хотя структура называется SECURITY_ATTRIBUTES, лишь один ее элемент имеет 
отношение к защите — IpSecurityDescriptor. Если надо ограничить доступ к 
созданному Вами объекту ядра, создайте дескриптор защиты и инициализируйте 
структуру SECURITY_ATTRIBUTES следующим образом: 

    SECURITY_ATTRIBUTES sa: 
    sa.nLength = slzeof(sa);            // используется для выяснения версий 
    sa.IpSecurityDescriptor = pSD:      // адрес инициализированной SD 
    sa.bInheritHandle = FALSE;          // об этом позже 
    HANDLE hFileMapping = CreateFileMapping((HANDLE) Oxffffffff, &sa, 
    PAGE_READWRITE. 0, 1024, "MyFileMapping"); 

    Рассмотрение элемента bInheritHandle я отложу до раздела о наследовании, 
так как этот элемент не имеет ничего общего с защитой. 
    Желая получить доступ к существующему объекту ядра (вместо того, чтобы 
создавать новый), сообщите, какие операции Вы намерены проводить над объектом. 
Например, если бы я захотел считывать данные из существующего объекта ядра 
«проекция файла», то вызвал бы функцию OpenFileMapping таким образом: 

    HANDLE hFileMapping = OpenFileMapping(FILE_MAP_READ, FALSE, "MyFileMapping"); 

    Передавая FILE_MAP_READ первым параметром в функцию OpenFileMapping, я 
сообщаю, что как только мне предоставят доступ к проекции файла, я буду 
считывать из нее данные. Функция OpenFileMapping, прежде чем вернуть 
действительный описатель, проверяет тип защиты объекта. Если меня, как 
легального пользователя (шутка), допускают к существующему объекту ядра 
«проекция файла», OpenFileMapping возвращает действительный описатель. Но если 
мне отказывают в доступе, OpenFileMapping возвращает NULL, а вызов 
GetLastError дает код ошибки 5 (или ERROR_ACCESS_DENIED). Но опять же, в 
основной массе приложений защиту не используют, Windows 95 ее вообще не 
поддерживает, и поэтому я больше не буду задерживаться на этой теме. 
    Кроме объектов ядра Ваша программа может использовать объекты других типов 
— меню, окна, курсоры мыши, кисти и шрифты. Они относятся к объектам User или 
GDI (graphics device interface — интерфейс графических устройств). Новичок в 
программировании для Win32 может запутаться, пытаясь отличить объекты User или 
GDI от объектов ядра. Как узнать, например, чьим объектом — User или ядра — 
является данный значок? Выяснить, принадлежит ли объект ядру, проще всего так: 
проанализировать \Ут32-функцию, создающую объект. Практически у всех функций, 
создающих объекты ядра, есть параметр, позволяющий указать атрибуты защиты, — 
как у CreateFileMapping. 
    В то же время у функций, создающих объекты User или GDI, нет параметра 
типа LPSECURITY_ATTRIBUTES, и пример тому — функция Createlcon: 

    HICON CreateIcon(HINSTANCE hinst, int nWidth, int nHeight, 
    BYTE cPlanes, BYTE cBitsPixel, 
    CONST BYTE -IpbANDbits, CONST BYTE *lpbXORbits): 


    10 ______________________________Windows для профессионалов 


    Таблица описателей объектов ядра 

    При инициализации процесса система создает в нем таблицу описателей, 
используемую только для объектов ядра. Сведения о структуре этой таблицы и 
управлении ею незадокументированы. Вообще-то я обычно воздерживаюсь от 
рассмотрения незадокументированных частей операционных систем. Но в данном 
случае, пожалуй, сделаю исключение — квалифицированный Win32-nporpaMMHCT, на 
мой взгляд, должен понимать, как устроена таблица описателей в процессе. 
Информация о таблице описателей незадокументирована, так что я не ручаюсь за 
ее стопроцентную достоверность, к тому же эта таблица по-разному реализуется в 
Windows NT, Windows 95, Win32s и Windows CE. Таким образом, следующие разделы 
помогут понять, что представляет собой таблица описателей, но вот что система 
действительно делает с ней — этот вопрос я оставляю открытым. 
    На рис. 2-1 показано, как выглядит таблица описателей, принадлежащая 
процессу. Как видите, это просто массив структур данных. Каждая структура 
содержит 32-битный указатель на какой-нибудь объект ядра, маску доступа и 
некоторые флаги. 

    Маска доступа (значение    
Флаги (значение типа  
Указатель на блок типа DWORD с набором    DWORD с набором 
    Индекс     памяти объекта ядра     битовых флагов)            битовых флагов) 
    1               Ox????????                       Ox????????                
           Ox???????? 
    2             Ox"??^??                     Ox????????                      
  Ox???????? 

    Рис. 2-1. Структура таблицы описателей, принадлежащей процессу 


    Создание объекта ядра 

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

    HANDLE CreateThread(LPSECURITY_ATTRIBUTES IpThreadAttributes, 
    DWORD dwStackSize, LPTHREAD_START_ROUTINE IpStartAddress, 
    LPVOID IpParameter, DWORD dwCreationFlags, LPDWORD IpThreadId); 
    
    HANDLE CreateFile(LPCTSTR IpFileName, DWORD dwDesiredAccess, 
    DWORD dwShareMode, LPSECURITY_ATTRIBUTES IpSecurityAttributes, 
    DWORD dwCreationDistribution, DWORD dwFlagsAndAttributes, 
    HANDLE hTemplateFile); 
    
    HANDLE CreateFileMapping(HANDLE hFile, 
    LPSECURITY_ATTRIBUTES IpFileMappingAttributes, DWORD flProtect, 
    DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow, LPGTSTR IpszName); 


    Глава 2. Объекты ядра 11 


    HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTES IpSemaphoreAttributes, LONG 
lInitialCount, LONG IMaximumCount, LPCTSTR IpszName); 

    Все функции, создающие объекты ядра, возвращают описатели, которые 
привязаны к конкретному процессу и могут быть использованы в любом потоке 
данного процесса. Значение описателя представляет собой индекс в таблице 
описателей, принадлежащей процессу, и таким образом идентифицирует место, где 
хранится информация, связанная с объектом ядра. Вот поэтому при отладке своего 
приложения и просмотре фактического значения описателя объекта ядра Вы и 
видите такие малые величины: 1, 2 и т. д. Но помните, что физическое 
содержимое описателей не задокументировано и может быть изменено. Кстати, в 
Windows NT это значение определяет, по сути, не индекс, а скорее байтовое 
смещение нужной записи от начала таблицы описателей. 
    Всякий раз, когда Вы вызываете УУш32-функцию, принимающую описатель 
объекта ядра как аргумент, Вы передаете ей значение, возвращенное одной из 
СгеяГе-функций. При этом функция «заглядывает» в таблицу описателей, 
принадлежащую Вашему процессу, и считывает адрес нужного объекта ядра. 
    Если Вы передаете неверный индекс (описатель), функция завершается с 
ошибкой и GetLastError возвращает 6 (ERROR_INVALID_HANDLE). А все дело в том, 
что описатели на самом деле — индексы в таблице, их значения привязаны к 
конкретному процессу и недействительны в других процессах. 
    Если вызов функции, создающей объект ядра, неудачен, то обычно 
возвращается О (NULL). Такая ситуация возможна только при острой нехватке 
памяти или при наличии проблем с защитой. К сожалению, отдельные функции 
возвращают в таких случаях не 0, а -1 (INVALID_HANDLE_VALUE). Например, если 
CreateFile не сможет открыть указанный файл, она вернет именно 
INVALID_HANDLE_VALUE. Так что будьте очень осторожны при проверке значения, 
возвращаемого функцией, создающей объект ядра. Скажем, для CreateMutex 
проверка на INVALID_HANDLE_VALUE бессмысленна: 

    HANDLE hMutex = CreateMutex(...): 
    if (hMutex == INVALID_HANDLE_VALUE) { 
    // этот код никогда не будет выполнен, так как 
    // при ошибке CreateMutex возвращает NULL 


    Закрытие объекта ядра 

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

    BOOL CloseHandle(HANDLE hobj): 

    Эта функция сначала проверяет таблицу описателей, принадлежащую 
вызывающему процессу, чтобы убедиться, идентифицирует ли переданный ей индекс 
(описатель) объект, к которому этот процесс действительно имеет доступ. Если 
переданный описатель неверен, функция возвращает FALSE, a GetLastError— код 
ERROR_INVALID_HANDLE. Если же индекс достоверен, система получает адрес 
структуры данных объекта ядра и уменьшает в этой структуре счетчик числа 
пользователей; как только счетчик обнулится, ядро удалит объект из памяти. 
    Перед самым возвратом управления CloseHandle удаляет соответствующую 
запись из таблицы описателей: данный описатель теперь недействителен в Вашем 
процессе и использовать его нельзя. При этом запись удаляется независимо от 
того, разрушен объект ядра или нет! После вызова CloseHandle Вы больше не 
получите доступа к этому объекту ядра; но, если его счетчик не обнулен, объект 
остается в памяти. Тут все нормально, это значит лишь, 

    12 ______________________________Windows для профессионалов 

    что объект используется другим процессом (или процессами). Когда и 
остальные процессы завершат свою работу с этим объектом (тоже вызвав 
CloseHandle), он будет разрушен. 
    А вдруг Вы забыли вызвать CloseHandle— будет ли утечка памяти? И да, и 
нет. Утечка ресурсов (тех же объектов ядра) вполне вероятна, пока процесс еще 
исполняется. Однако по завершении процесса операционная система гарантированно 
освобождает все ресурсы, принадлежавшие этому процессу, и в случае объектов 
ядра действует так: в момент завершения процесса просматривает его таблицу 
описателей и закрывает любые открытые описатели. 

    Совместное использование объектов ядра несколькими процессами 

    Время от времени возникает необходимость в разделении объектов ядра между 
потоками, исполняемыми в разных процессах. Причин тому несколько: 
    • объекты «проекции файлов» позволяют двум процессам, исполняемым на одной 
машине, совместно использовать одни и те же блоки данных; 
    • почтовые ящики (mailslots) и именованные каналы (named pipes) дают 
возможность программам обмениваться данными с процессами, исполняемыми на 
других машинах в сети; 
    • мьютексы (mutexes), семафоры (semaphores) и события (events) позволяют 
синхронизировать потоки, исполняемые в разных процессах, чтобы одно приложение 
могло уведомить другое об окончании той или иной операции. 
    Но поскольку описатели объектов ядра имеют смысл только в конкретном 
процессе, разделение объектов ядра между несколькими процессами в Win32 — 
задача весьма непростая. У Microsoft было несколько веских причин сделать 
описатели «процессо-зависимыми», и самая главная — устойчивость операционной 
системы к сбоям. Если бы описатели объектов ядра были общесистемными, то один 
процесс мог бы запросто получить описатель объекта, используемого другим 
процессом, и устроить в нем (этом процессе) настоящий хаос. Другая причина — 
защита. Объекты ядра защищены, и процесс, прежде чем оперировать с ними, 
должен запрашивать разрешение на доступ к ним. Процесс — создатель объекта 
может предотвратить несанкционированный доступ к этому объекту со стороны 
другого процесса. 
    Три механизма, позволяющие процессам совместно использовать одни и те же 
объекты ядра, мы рассмотрим в следующем разделе. 

    Наследование описателя объекта 

    Наследование применимо, только когда процессы связаны «родственными» 
отношениями (родительский-дочерний). Например, родительскому процессу доступны 
один или несколько описателей объектов ядра, и он решает, породив дочерний 
процесс, передать ему по наследству доступ к своим объектам ядра. Чтобы такой 
сценарий наследования сработал, родительский процесс должен выполнить 
несколько операций. 
    Во-первых, еще при создании объекта ядра сообщить системе, что ему нужен 
наследуемый описатель этого объекта. (Имейте в виду: описатели объектов ядра 
наследуются, но сами объекты ядра — нет.) 
    Чтобы создать наследуемый описатель, родительский процесс выделяет и 
инициализирует структуру SECURITY_ATTRIBUTES, а затем передает ее адрес 
требуемой Create-функции. Следующий код создает объект-мьютекс и возвращает 
его описатель: 

    Глава 2. Объекты ядра 13 


    SECURITY_ATTRIBUTES sa; 
    sa.nLength = sizeof(sa): 
    sa.IpSecurityDescriptor = NULL; 
    sa.bInheritHandle = TRUE; 
    HANDLE hMutex = CreateMutex(&sa, FALSE, NULL); 


    Этот код инициализирует структуру SECURITY_ATTRIBUTES, указывая, что 
объект следует создать с защитой по умолчанию (в Windows 95 это игнорируется) 
и что возвращаемый описатель должен быть наследуемым. 
    ^WINDOWS/ Windows 95 не полностью поддерживает защиту, однако поддерживает 
наследование и поэтому корректно обрабатывает элемент bInheritHandle. 
    А теперь перейдем к флагам, которые хранятся в таблице описателей, 
принадлежащей процессу. В каждой ее записи присутствует битовый флаг, 
сообщающий, является ли данный описатель наследуемым. Если Вы, создавая объект 
ядра, передадите в параметре типа LPSECURITY_ATTRIBUTES значение NULL, то 
получите ненаследуемый описатель, и этот флаг будет нулевым. А если элемент 
bInheritHandle равен TRUE, флагу присваивается 1. 

    Допустим, какому-то процессу принадлежит таблица описателей, как на рис. 2-2. 

    Маска доступа (значение    Флаги (значение типа 
    Указатель на блок       типа DWORD с набором    DWORD с набором Индекс     
памяти объекта ядра     битовых флагов)            битовых флагов) 
    1            OxFOOOOOOO                Ox????????                      0х00000000 
    2          0х00000000              (неприменим)              (неприменим) 
    3            OxFOOOOOlO                Ox????????                      0х00000001 

    Рис. 2-2. Таблица описателей с двумя действительными записями 

    Таблица свидетельствует, что данный процесс имеет доступ к двум объектам ядра: 
    описатель 1 (ненаследуемый) и 3 (наследуемый). 
    Следующий этап — родительский процесс порождает дочерний. Это делается с 
помощью функции CreateProcess: 

    BOOL CreateProcess(LPCTSTR IpAppllcationName, LPTSTR IpCommandLine, 
    LPSECURITY_ATTRIBUTES IpProcessAttr-ibutes, 
    LPSECURITY_ATTRIBUTES IpThreadAttributes, BOOL bInheritHandles, 
    DWORD dwCreationFlags, LPVOID IpEnvironment, 
    LPCTSTR IpCurrentDirectory, LPSTARTUPINFO IpStartupInfo, 
    LPPROCESS_INFORMATION IpProcessInformation); 

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

    14 ______________________________Windows для профессионалов 

    система создает дочерний процесс, но не дает ему немедленно начать свою 
работу. Сформировав в нем, как обычно, новую (пустую) таблицу описателей, она 
считывает таблицу родительского процесса и копирует все ее действительные 
записи в таблицу дочернего — причем в те же позиции. Последний факт 
чрезвычайно важен, так как означает, что описатели будут идентичны в обоих 
процессах (родительском и дочернем). 
    Помимо копирования записей из таблицы описателей, система увеличивает 
значения счетчиков соответствующих объектов ядра, поскольку эти объекты теперь 
используются обоими процессами. Чтобы уничтожить какой-то объект ядра, его 
описатель должны закрыть (вызовом CloseHandle) оба. процесса. Кстати, сразу 
после возврата управления функцией CreateProcess родительский процесс может 
закрыть свой описатель объекта, и это никак не отразится на способности 
дочернего процесса манипулировать с этим объектом. 
    На рис. 2-3 показано состояние таблицы описателей в дочернем процессе — 
перед самым началом его исполнения. Как видите, записи 1 и 2 не 
инициализированы, и поэтому данные описатели неприменимы в дочернем процессе. 
Однако индекс 3 действительно идентифицирует объект ядра по тому же (что и в 
родительском) адресу OxFOOOOOlO. При этом маска доступа и флаги в родительском 
и дочернем процессах тоже идентичны. Так что, если дочерний процесс в свою 
очередь породит новый («внука» по отношению к исходному родительскому), «внук» 
унаследует данный описатель объекта ядра с теми же значением, правами доступа 
и флагами, а счетчик числа пользователей этого объекта ядра вновь увеличится 
на единицу. 

    Маска доступа (значение    Флаги (значение типа 
    Указатель на блок       типа DWORD с набором    DWORD с набором 
    Индекс     памяти объекта ядра     битовых флагов)            битовых флагов) 
    1           0х00000000              (неприменим)              (неприменим) 
    2          0х00000000              (неприменим)              (неприменим) 
    3            OxFOOOOOlO                Ox????????                      0х00000001 

    Рис. 2-3. Таблица описателей в дочернем процессе (после того как он 
унаследовал от родительского один наследуемый описатель) 

    Наследуются только описатели объектов, существующие на момент создания 
дочернего процесса. Если родительский процесс создаст после этого новые 
объекты ядра с наследуемыми описателями, то эти описатели дочернему процессу 
уже недоступны. 
    Для наследования описателей объектов характерно одно очень странное 
свойство: дочерний процесс не имеет ни малейшего понятия, что он унаследовал 
какие-то описатели. Поэтому наследование описателей объектов ядра полезно, 
только когда дочерний процесс сообщает, что при его создании родительским 
процессом он ожидает доступа к какому-нибудь объекту ядра. Тут надо заметить, 
что обычно родительское и дочернее приложения пишутся одной фирмой, но в 
принципе дочернее приложение может написать и сторонняя фирма, если в этой 
программе задокументировано, чего именно она ждет от родительского процесса. 
    Для этого в дочерний процесс обычно передают значение ожидаемого им 
описателя Т_ объекта ядра как аргумент в командной строке. Инициализирующий 
код дочернего процесса анализирует командную строку (чаще всего вызовом 
sscanf), извлекает из нее значение описателя, и дочерний процесс получает 
неограниченный доступ к объекту. При этом механизм наследования срабатывает 
только потому, что значение описателя общего объекта 

    Глава 2. Объекты ядра 15 

    ядра в родительском и дочернем процессах одинаково, — и именно по этой 
причине родительский процесс может передать значение описателя как аргумент в 
командной строке. 
    Для наследственной передачи описателя объекта ядра от родительского 
процесса дочернему, конечно же, годятся и другие формы межпроцессной связи. 
Один из приемов заключается в том, что родительский процесс дожидается 
окончания инициализации дочернего (через функцию WaitForInputIdle, описанную в 
главе 11), а затем посылает (синхронно или асинхронно) сообщение окну, 
созданному потоком дочернего процесса. 
    Еще один прием: родительский процесс добавляет в свой блок переменных 
окружения новую переменную. Она должна быть «узнаваема» дочерним процессом и 
содержать значение наследуемого описателя объекта ядра. Далее родительский 
процесс создает дочерний, тот наследует переменные окружения родительского 
процесса и, вызвав GetEnvironmentVariable, получает нужный описатель. Такой 
прием особенно хорош, когда дочерний процесс тоже порождает процессы, — ведь 
все переменные окружения вновь наследуются. 

    Изменение флагов описателей 

    Иногда родительский процесс создает объект ядра с наследуемым описателем, 
а затем порождает два дочерних процесса. Но наследуемый описатель нужен только 
одному из них. Иначе говоря, время от времени возникает необходимость 
контролировать, какой из дочерних процессов наследует описатели объектов ядра. 
Для этого модифицируйте флаг наследования, связанный с описателем объекта 
ядра, вызвав SetHandleInformation: 

    BOOL SetHandleInformationCHANDLE hObject, DWORD dwMask, DWORD dwFlags); 

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

    #define HANDLE_FLAG_INHERIT                0х00000001 
    Sdefine HANDLE_FLAG_PROTECT_FROM_CLOSE     0х00000002 

    Чтобы изменить сразу все флаги объекта, нужно объединить их побитовой 
операцией OR. И, наконец, третий параметр — dwFlags — указывает, в какое 
состояние следует перевести флаги. Например, чтобы установить флаг 
наследования для описателя объекта ядра: 

    SetHandleInf о Filiation (hob j. HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); 

    а чтобы сбросить этот флаг: 

    SetHandleInformation(hobJ, HANDLE_FLAG_INHERIT, 0); 

    Флаг HANDLE_FLAG_PROTECT_FROM_CLOSE сообщает системе, что данный описатель 
закрывать нельзя: 

    SetHandleInforniation(hobj, HANDLE_FLAG_PROTECT_FROM_CLOSE, 
    HANDLE_FLAG_PROTECT_FROM_CLOSE); 
    CloseHandle(hobj);     // генерируется исключение 

    Если какой-нибудь поток попытается закрыть защищенный описатель, 
CloseHandle приведет к исключению. Необходимость в такой защите возникает 
очень редко. Однако этот флаг весьма полезен, когда процесс порождает 
дочерний, а тот в свою очередь — еще один. При этом родительский процесс может 
ожидать, что его «внук» унаследует определенный описатель объекта, переданный 
дочернему. Но тут вполне возможно, что дочерний процесс, прежде чем породить 
новый процесс, закрывает нужный описатель. Тогда родительский процесс теряет 
связь с «внуком», поскольку тот не унаследовал требуемый объект ядра. Ну а 
защитив описатель отзакрытия, Вы исправите ситуацию, и «внук» унаследует 
предназначенный ему объект. 

    16 ______________________________Windows для профессионалов 

    У этого подхода, впрочем, есть один недостаток. Дочерний процесс, вызвав: 

    SetHandleInformation(hobj, HANDLE_FLAG_PROTECT_FROM_CLOSE, 0); 
    CloseHandle(hobj); 

    может сбросить флаг HANDLE_FLAG_PROTECT_FROM_CLOSE и закрыть затем 
соответствующий описатель. Родительский процесс делает ставку на то, что 
дочерний не исполнит этот код. Но одновременно он ставит и на то, что дочерний 
процесс породит ему «внука», поэтому в целом ставки не слишком рискованны. 
    Для полноты картины стоит, пожалуй, упомянуть и функцию GetHandleInformation: 

    BOOL GetHandleInformation(HANDLE hObj, LPDWORD IpdwFlags); 

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

    DWORD dwFlags: 
    GetHandleInformation(hObj, &dwFlags): 
    BOOL fHandleIsInheritable = (0 != (dwFlags & HANDLE_FLAG_INHERITABLE)); 


    Именованные объекты 

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

    HANDLE CreateMutex(LPSECURITY_ATTRIBUTES IpMutexAttributes. 
    BOOL bInitialOwner, LPCTSTR IpszName); 
    
    
    HANDLE CreateEvent(LPSECURITY_ATTRIBUTES IpEventAttnbutes, 
    BOOL bManualReset, BOOL bInitialState, LPCTSTR IpszName): 
    
    HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTES IpSemaphoreAttributes, 
    LONG UnitialCount, LONG IMaximumCount, LPCTSTR IpszName); 
    
    HANDLE CreateWaitableTimei-(LPSECURITY_ATTRIBUTES IpTimerAttributes, 
    BOOL bManualReset, LPCTSTR IpszName); 
    
    HANDLE CreateFileMapping(HANDLE hFile, 
    
    LPSECURITY_ATTRIBUTES IpFileMappingAttributes, DWORD flProtect, 
    DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow, LPCTSTR IpszName): 

    У всех этих функций первый (у CreateFileMapping— второй) и последний 
параметры одинаковы. Первый мы уже рассмотрели, теперь сосредоточимся на 
последнем — IpszName. Передавая в нем NULL, Вы создаете безымянный объект 
ядра. В этом случае Вы можете разделять объект между процессами либо через 
наследование (см. предыдущий раздел), либо с помощью DuplicateHandle (см. 
следующий раздел). А чтобы разделять объект по имени, Вы должны присвоить ему 
какое-нибудь имя. Тогда вместо NULL в параметре IpszName нужно передать адрес 
строки с именем (она завершается нулевым символом). Имя может быть длиной до 
МАХ_РАТН знаков (это значение определено как 260) и не должно содержать 
символов «\». К сожалению, Microsoft ничего не сообщает о правилах именования 
объектов ядра. Например, создавая объект с именем JeffObj, Вы никак не 
застрахованы от того, что в системе уже есть объект ядра с таким именем. И что 
хуже, мьютексы, события, семафоры, проекции файлов и таймеры ожидания 
(waitable timers) — все делят единое пространство имен. Поэтому следующий 
вызов CreateSemaphore будет всегда возвращать NULL: 

    Глава 2. Объекты ядра 17 

    HANDLE hMutex = CreateMutex(NULL, FALSE, "JeffObJ"): 
    HANDLE hSem = CreateSemaphore(NULL, 1, 1. "JeffObJ"); 
    DWORD dwError-Code = GetLastError(); 

    После выполнения этого фрагмента значение dwErrorCode будет равно 6 
(ERROR_INVALID_HANDLE). Полученный код не слишком вразумителен, но другого не дано. 
    Теперь, когда Вы научились именовать объекты, рассмотрим, как разделять их 
между процессами по именам. Допустим, после запуска процесса А вызывается функция: 

    HANDLE hMutexProcessA = CreateMutex(NULL, FALSE, "JeffMutex"): 

    Этот вызов заставляет систему создать «новенький, как с иголочки» объект 
ядра — мьютекс и присвоить ему имя JeffMutex. Заметьте, что описатель 
hMutexProcessA в процессе А не является наследуемым, — он и не должен быть 
таковым при разделении объектов по именам. 
    Спустя какое-то время некий процесс порождает процесс В. Необязательно, 
чтобы последний был дочерним от процесса А; его может породить Explorer или 
любое другое приложение. (В этом-то и состоит преимущество механизма 
именованных объектов перед наследованием.) Когда процесс В приступает к 
работе, исполняется код: 

    HANDLE hMutexProcessB = CreateMutex(NULL, FALSE, "JeffMutex"); 

    При этом вызове система сначала проверяет, не существует ли уже объект 
ядра с таким именем. Обнаружив, что таковой существует, ядро проверяет его 
тип. Поскольку мы пытаемся создать мьютекс и его имя тоже JeffMutex, система 
считает вызов CreateMutex успешным и создает в таблице описателей, 
принадлежащей процессу В, новую запись, а затем инициализирует ее так, чтобы 
она указывала на существующий объект ядра. 
    Но хотя процесс В вызвал CreateMutex, новый объект-мьютекс не создан. 
Вместо этого он потучил свой описатель, который идентифицирует существующий 
объект-мьютекс. Счетчик объекта, конечно же, увеличился на единицу, и теперь 
он не разрушится, пока его описатели не закроют оба процесса — А и В. 
Заметьте, что значения описателей этого объекта в обоих процессах скорее всего 
разные, но так и должно быть: каждый процесс будет оперировать с данным 
объектом ядра, используя свой описатель. 
    Разделяя объекты ядра по именам, помните об одной очень важной вещи. 
Вызывая CreateMutex, процесс В передает ей атрибуты защиты и второй параметр. 
Так вот, эти параметры игнорируются, если объект с указанным именем уже 
существует! Приложение мажетГбпределить, что оно делает: создает новый объект 
ядра или просто открывает уже существующий, — вызвав GetLastError сразу после 
вызова одной из Сгея^е-функций: 

    HANDLE hMutex = CreateMutex(NULL, FALSE, "JeffOb]"); 
    if (GetLastErrorO == ERROR_ALREADY_EXISTS) { 
    // открыт описатель существующего объекта 
    } else { 
    // создан совершенно новый объект 
    } 

    Есть и другой способ разделения объектов по именам. Вместо вызова 
Create-функции процесс может обратиться к одной из следующих Open-функций: 
    
    HANDLE OpenMutex(DWORD dwDesir-edAccess, BOOL bInheritHandle, 
    LPCTSTR IpszName); 
    
    HANDLE OpenEvent(DWORD dwDesir-edAccess, BOOL bInheritHandle, 
    LPCTSTR IpszName); 

    см. след. стр. 


    18 ______________________________Windows для профессионалов 
    

    HANDLE OpenSemaphore(DWORD dwDesireaAccess. BOOL bInheritHandle. 
    LPCTSTR IpszName); 
    
    HANDLE OpenWaitableTimer(DWORD dwDesiredAccess, BOOL bInheritHandle, 
    LPCTSTR IpszName); 
    
    HANDLE OpenFileMapping(DWORD dwDesiredAccess, BOOL bInheritHandle, 
    LPCTSTR IpszName): 

    Заметьте: все эти функции имеют один прототип. Последний параметр, 
IpszName, определяет имя объекта ядра. В нем нельзя передать NULL — только 
адрес строки с нулевым символом в конце. Эти функции просматривают единое 
пространство имен объектов ядра, пытаясь найти совпадение. Если объекта ядра с 
указанным именем нет, функции возвращают NULL, a GetLastError — код 2 
(ERROR_FILE_NOT_FOUND). Но если объект ядра с заданным именем существует и 
если его тип идентичен тому, что Вы указали, система проверяет, разрешен ли к 
данному объекту доступ запрошенного вида (через параметр dwDesiredAccess). 
Если разрешен, таблица описателей в вызывающем процессе обновляется, и счетчик 
числа пользователей объекта возрастает на единицу. Присвоив параметру 
bInheritHandle значение TRUE, Вы получите наследуемый описатель. 
    Главное отличие между вызовом Create- и Open-функций в том, что при 
отсутствии указанного объекта Q-eafe-функция создает его, а Open-функция 
просто сообщает об ошибке. 

    Дублирование описателей объектов 

    Последний механизм совместного использования объектов ядра несколькими 
процессами требует функции DuplicateHandle: 

    BOOL DuplicateHandle( 
    HANDLE hSourceProcessHandle, HANDLE hSourceHandle, 
    HANDLE hTargetProcessHandle, LPHANDLE IpTargetHandle, 
    DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwOptions); 

    Говоря по-простому, эта функция берет запись в таблице описателей одного 
процесса и создает ее копию в таблице другого. DuplicateHandle принимает 
несколько параметров, но на самом деле весьма незамысловата. Обычно ее 
применение требует наличия в системе трех разных процессов. 
    Первый и третий параметры функции DuplicateHandle представляют собой 
описатели объектов ядра, специфичные для вызывающего процесса. Кроме того, эти 
параметры должны идентифицировать объекты ядра «процессы» — функция завершится 
с ошибкой, если Вы передадите описатели на объекты ядра любого другого типа. 
Подробнее об объектах ядра «процессы» мы поговорим в главе 3, а сейчас Вам 
достаточно знать только одно: объект ядра «процесс» создается при каждой 
инициации в системе нового процесса. 
    Второй параметр, hSourceHandle, — описатель объекта ядра любого типа. 
Однако его значение специфично не для процесса, вызывающего DuplicateHandle, а 
для того, на который указывает описатель hSourceProcessHandle. Параметр 
IpTargetHandle— адрес переменной типа HANDLE, в которой возвращается индекс 
записи с копией описателя из процессаисточника. Значение возвращаемого 
описателя специфично для процесса, идентифицируемого параметром hTargetProcessHandle. 
    Предпоследние два параметра DuplicateHandle позволяют задать маску доступа 
и флаг наследования, устанавливаемые для данного описателя объекта ядра в процессе-приемнике. 

    __________________________________Глава 2. Объекты ядра 19 

    И, наконец, параметр dwOptions может быть 0 или любой комбинацией двух 
флагов: DUPLICATE_SAME_ACCESS и DUPLICATE_CLOSE_SOURCE. 
    Первый флаг подсказывает функции DuplicateHandle: у описателя, получаемого 
процессом-приемником, должна быть та же маска доступа, что и у описателя в 
процессе-источнике. Этот флаг заставляет DuplicateHandle игнорировать параметр dwDesiredAccess. 
    Второй флаг приводит к закрытию описателя в процессе-источнике. Он 
позволяет процессам обмениваться объектом ядра как эстафетной палочкой. При 
этом счетчик объекта не меняется. Правда, я еще не сталкивался с 
необходимостью использовать этот флаг. 
    XwiNDowsy В Microsoft знают о «жучке» в Windows 95, связанном с флагом 
DUPLICA\^/ TE_CLOSE_SOURCE. ЁсшГвызывающий процесс не является источником, 
опи>/   сатель не закрывается. 
    Попробуем проиллюстрировать работу функции DuplicateHandle на примере. 
Таблица описателей в процессе Х (рис. 2-4) содержит два индекса — 1 и 2. 
Описатель с первым значением идентифицирует объект ядра «процесс S», описатель 
со вторым значением — объект ядра «процесс Т». 
    На рис. 2-5 показана таблица описателей в процессе S, содержащая 
единственную запись со значением описателя, равным 2. Этот описатель может 
идентифицировать объект ядра любого типа, а не только «процесс». 

    Маска доступа (значение    Флаги (значение типа 
    Указатель на блок       типа DWORD с набором    DWORD с набором 
    Индекс     памяти объекта ядра     битовых флагов)            битовых флагов) 
    1            OxFOOOOOOO                Ox????????                     0х00000000 
    (объект ядра процесса S) 
    2            OxFOOOOOlO                Ox????????                     0х00000000 
    (объект ядра процесса Т) 
    Рис. 2-4. Таблица описателей в процессе Х 
    Маска доступа (значение    Флаги (значение типа 
    Указатель на блок       типа DWORD с набором    DWORD с набором Индекс     
памяти объекта ядра     битовых флагов)            битовых флагов) 
    1           0х00000000              (неприменим)              (неприменим) 
    2            OxF0000020               Ox????????                     0х00000000 
    (объект ядра любого типа) 

    Рис. 2-5. Таблица описателей в процессе S 

    На рис. 2-6 показано, что именно содержит таблица описателей в процессе Т 
перед вызовом процессом Х функции DuplicateHandle. Как видите, в ней всего 
одна запись со значением описателя, равным 2, а запись с индексом 1 пока 
пуста.   

    20 ______________________________Windows для профессионалов 


    Маска доступа (значение    Флаги (значение типа 

    Указатель на блок       типа DWORD с набором    DWORD с набором 

    Индекс     памяти объекта ядра     битовых флагов)            битовых флагов) 
    1           0х00000000              (неприменим)              (неприменим) 
    2            ОхРОООООЗО               Ох????????                     0х00000000 
    (объект ядра любого типа) 

    Рис. 2-6. Таблица описателей в процессе Т перед вызовом DuplicateHandle 

    Если процесс Х теперь вызовет DuplicateHandle так: 
    DupllcateHandle(1, 2, 2, &h0b], 0, TRUE, DUPLICATE_SAME_ACCESS); 
    то после вызова изменится только таблица описателей в процессе Т (рис. 2-7). 
    Маска доступа (значение    Флаги (значение типа 
    Указатель на блок       типа DWORD с набором    DWORD с набором 
    Индекс     памяти объекта ядра     битовых флагов)            битовых флагов) 
    1            OxF0000020                Ox????????                      0х00000001 
    2            ОхРОООООЗО               Ох????????                     0х00000000 
    (объект ядра любого типа) 

    Рис. 2-7. Таблица описателей в процессе Т после вызова DuplicateHandle 

    Вторая строка таблицы описателей в процессе S скопирована в первую строку 
таблицы описателей в процессе Т. Функция DuplicateHandle присвоила также 
переменной hObj процесса Т значение 1 — индекс той строки таблицы в процессе 
Т, в которую и занесен новый описатель. 
    Поскольку в функцию DuplicateHandle передан флаг DUPLICATE_SAME_ACCESS, 
маска доступа для этого описателя в процессе Т идентична маске доступа в 
процессе S. Кроме того, данный флаг заставляет DuplicateHandle игнорировать 
параметр dwDesiredAccess. Заметьте также, что система установила битовый флаг 
наследования, так как в параметре bInheritHandle функции DuplicateHandle мы 
передали TRUE. 
    Очевидно, Вы никогда не станете передавать в DuplicateHandle жестко 
зашитые значения, как это сделал я, чтобы просто продемонстрировать работу 
функции. В реальных программах значения различных описателей хранятся в 
переменных, и, конечно же, именно эти переменные передаются в функции. 
    Как и механизм наследования, функция DuplicateHandle тоже обладает одной 
странностью: процесс-приемник никак не уведомляется о том, что он получил 
доступ к новому объекту ядра. Поэтому процесс Х должен каким-то образом 
сообщить процессу Т, что тот имеет теперь доступ к новому объекту; для этого 
нужно воспользоваться одной из форм межпроцессной связи и передать в процесс Т 
значение описателя в переменной hObj. Ясное дело, в данном случае не годится 
ни командная строка, ни изменение переменных окружения процесса Т, поскольку 
этот процесс уже запущен и исполняется, — подойдет механизм Windows-сообщений 
или другие механизмы межпроцессной связи. 
    Я рассказал Вам о DuplicateHandle в самом общем виде. Надеюсь, Вы увидели, 
насколько это гибкая функция. Но она редко используется в ситуациях, требующих 
участия трех разных процессов. Обычно ее вызывают применительно к двум 
процессам. Представьте, что один процесс имеет доступ к объекту, к которому 
хочет обратиться другой процесс, или что один процесс хочет предоставить 
другому доступ к «своему» объекту ядра. Например, процесс А 

    Глава 2. Объекты ядра 21 

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

    // весь приведенный ниже код исполняется процессом А 
    
    // создаем объект-мьютекс, доступный процессу А 
    HANDLE hObjProcessA = CreateMutex(NULL, FALSE, NULL); 
    
    // открываем описатель объекта ядра "процесс В" 
    HANDLE hProcessB = OpenProcess(PROCESS_ALL_ACGESS, FALSE, dwProcessIdB); 
    
    HANDLE hObjProcessB;   // неинициализированный описатель, 
    // связанный с процессом В 
    
    // предоставляем процессу В доступ к объекту-мьютексу 
    DuplicateHandle(GetCurrentProcess(), nObjProcessA, hObjProcessB, 
    &hObjProcessB, 0, FALSE, DUPLICATE_SAME_ACCESS); 
    
    // используем какую-нибудь форму межпроцессной связи, чтобы передать 
    // значение описателя в процесс В через hObjProcessB 
    
    // связь с процессом В больше не нужна 
    CloseHandle(hProcessB): 
    
    // если процессу А не нужен объект-мьютекс, он должен закрыть его 
    CloseHandle(hObjProcessA); 

    Вызов GetCurrentProcess возвращает псевдоописатель, который всегда 
идентифицирует вызывающий процесс, в данном случае — процесс А. Как только 
функция DuplicateHandle возвращает управление, hObjProcessB становится 
описателем, связанным с процессом В и идентифицирующим тот же объект, что и 
описатель hObjProcessA (когда на него ссылается код процесса А). При этом 
процесс А ни в коем случае не должен исполнять такой код: 
    
    // процесс А никогда не должен пытаться исполнять код, 
    // закрывающий продублированный описатель 
    CloseHandle(hObjProcessB); 

    Если процесс А выполнит этот код, вызов может дать (а может и не дать) 
ошибку. Он будет успешен, если у процесса А случайно окажется описатель с тем 
же значением, что и в hObjProcessB. При этом процесс А закроет неизвестно 
какой объект, и что будет потом, можно только гадать. 
    Теперь о другом способе применения DuplicateHandle. Предположим, некий 
процесс имеет доступ к объекту-мьютексу. Поток этого процесса порождает новый 
поток, и оба хотят обратиться к одному объекту-мьютексу. Поскольку Windows — 
операционная система с вытесняющей многозадачностью, сказать заранее, какой из 
потоков первым реализует свое «желание», нельзя. Однако, когда они оба 
прекратят использовать мьютекс, объект надо будет разрушить. И тут можно 
прибегнуть к весьма элегантному решению. Суть его в том, чтобы переложить учет 
пользователей мьютекса на систему, для чего вызвать DuplicateHandle и 
искусственно увеличить счетчик объекта на единицу. Вот программная реализация 
этого способа: 

    22  ______________________________Windows для профессионалов 
    
    int WINAPI WinMain(HINSTANCE hinstExe, HINSTANCE hinstExePrev, 
    LPSTR IpszCmdLine, int nCmdShow) { 
    
    HANDLE hThread;  // мы собираемся создать рабочий поток 
    DWORD dwThreadId; 
    
    // создаем новенький объект-мьютекс 
    HANDLE hMutex = CreateMutex(NULL, FALSE, NULL): 
    
    // в этом процессе будет два разных описателя 
    // одного объекта-мьютекса 
    HANDLE hMutexWorker: 
    // увеличиваем счетчик мьююкса на единицу 
    // и даем процессу еще один описатель этого объекта 
    DupllcateHandle(GetCurrentProcess(), hMutex, 
    GetCuг rent Process(), ShMutexWorker, 
    0, FALSE, DUPLICATE_SAME_ACCESS); 
    
    // создаем рабочий поток и передаем ему второй 
    // описатель мьютекса 
    
    hThread = CreateThread(NULL, 0, ThreadFunc, 
    (LPVOID) hMutexWorker, 0, &dwThreadId); 
    
    // этот поток продолжает работать и манипулирует 
    // мьютексом через описатель в hMutex 
    // когда этот поток заканчивает работу с мьютексом, он закрывает его; 
    
    // если счетчик обнуляется, система разрушает объект 
    Clo3eHandle(hMutex); 
    
    // ждем завершения рабочего потока 
    WaitForSingleObject(hThread, INFINITE); 
    
    // закрываем описатель объекта ядра "рабочий поток" 
    CloseHandle(hThread); 
    return(O); 
    DWORD WINAPI ThreadFunc(PVOID pvParam) { 
    
    // описатель мьютекса передается в аргументе pvParam 
    HANDLE hMutex = (HANDLE)pvParam; 
    
    // этот поток продолжает работать и манипулирует мьютексом 
    // через описатель в локальной переменной hMutex 
    
    // когда этот поток заканчивает работу с мьютексом, он закрывает его; 
    
    // если счетчик обнуляется, система разрушает объект 
    CloseHandle(hMutex); 
    return(O); 
