|
|
|||
|
Ноутбуки, мониторы, комлектующие и другие полезные вещи
wm-help.net -> Электронная библиотека -> C++/C#/C -> Джеффри РИХТЕР "Windows для профессионалов" -> Глава 3. Объекты ядраГлава 3. Объекты ядраИзучение Windows API мы начнем с объектов ядра и их описателей (handles). Эта глава посвящена сравнительно абстрактным концепциям, т. e. мы, не углубляясь в специфику тех или иных объектов ядра, рассмотрим их общие свойства. Я бы предпочел начать с чего-то более конкретного, но без четкого понимания объектов ядра Вам не стать настоящим профессионалом в области разработки Windows-программ. Эти объекты используются системой и нашими приложениями для управления множеством самых разных ресурсов процессами, потоками, файлами и т. д. Концепции, представленные здесь, будут встречаться на протяжении всей книги. Однако я прекрасно понимаю, что часть материалов не уляжется у Вас в голове до тех пор, пока Вы не приступите к работе с объектами ядра, используя реальные функции. И при чтении последующих глав книги Вы, наверное, будете время от времени возвращаться к этой главе. Что такое объект ядраСоздание,
открытие и прочие операции с объектами ядра станут для Вас, как разработчика
Windows-приложений, повседневной рутиной. Система позволяет создавать и
оперировать с несколькими типами таких объектов, в том числе, маркерами доступа
(access token objects), файлами (file objects), проекциями файлов (file-mapping
objects), портами завершения ввода-вывода (I/O completion port objects),
заданиями (job objects), почтовыми ящиками (mailslot objects), мьютсксами (mutex
objects), каналами (pipe objects), процессами (process objects), семафорами
(semaphore objects), потоками (thread objects) и ожидаемыми таймерами (waitable
timer objects). Эти объекты создаются Windows-функциями Например,
CreateFtleMapping заставляет систему Поскольку структуры объектов ядра доступны только ядру, приложение не может самостоятельно найти эти структуры в памяти и напрямую модифицировать их содержимое Такое ограничение Microsoft ввела намеренно, чтобы ни одна программа не нарушила целостность структур объектов ядра. Это же ограничение позволяет Microsoft вводить, убирать или изменять элементы структур, нс нарушая работы каких-либо приложений. Но вот вопрос: если мы не можем напрямую модифицировать эти структуры, то как же наши приложения оперируют с объектами ядра? Ответ в том, что в Windows предусмотрен
набор функций, обрабатывающих структуры объектов ядра по строго определенным
правилам. Мы получаем доступ к объектам ядра только через эти функции. Когда Вы
вызываете функцию, создающую объект ядра, она возвращает описатель,
идентифицирующий созданный объект, Описатель следует рассматривать как
"непрозрачное" значение, которое может быть использовано любым потоком Вашего
процесса. Этот описатель Вы передаете Windows-функциям, сообщая системе,
какой Для большей надежности операционной системы Microsoft сделала так, чтобы значения описателей зависели от конкретного процесса. Поэтому, если Вы передадите такое значение (с помощью какого-либо механизма межпроцессной связи) потоку другого процесса, любой вызов из того процесса со значением описателя, полученного в Вашем процессе, даст ошибку. Но не вользуйтесь, в конце главы мы рассмотрим три механизма корректного использования несколькими процессами одного объекта ядра. Учет пользователей объектов ядраОбъекты ядра принадлежат ядру, а не процессу. Иначе говоря, если Ваш процесс вызывает функцию, создающую объект ядра, а затем завершается, объект ядра может быть не разрушен. В большинстве случаев такой объект все же разрушается; но если созданный Вами объект ядра используется другим процессом, ядро запретит разрушение объекта до тех пор, пока от него не откажется и тот процесс. Ядру известно, сколько процессов использует конкретный объект ядра, посколь ку в каждом объекте есть счетчик числа его пользователей. Этот счетчик — один из элементов данных, общих для всех типов объектов ядра. В момепт создания объекта счетчику присваивается 1. Когда к существующему объекту ядра обращается другой процесс, счетчик увеличивается на 1. А когда какой-то процесс завершается, счетчики всех используемых им объектов ядра автоматически уменьшаются на 1. Как только счетчик какого-либо объекта обнуляется, ядро уничтожает этот объект. ЗащитаОбъекты ядра можно защитить дескриптором защиты (security descriptor), который описывает, кто создал объект и кто имеет права на доступ к нему. Дескрипторы защиты обычно используют при написании серверных приложений; создавая клиентское приложение, Вы можете игнорировать это свойство объектов ядра.
Почти все функции, создающие объекты ядра, принимают указатель на структуру SECURITY_ATTRIBUTES как аргумент, например:
Большинство приложений вместо этого аргумента передает NULL и гоздает объект с защитой по умолчанию. Такая защита подразумевает, что создатель объекта и любой член группы администраторов получают к нему полный доступ, а все прочие к объекту не допускаются Однако Вы можете создать и инициализировать структуру SECURITY_ATTRIBUTES, а затем передать ее адрес. Она выглядит так:
Хотя структура называется SECURITY__ATTRIBUTES, лишь один cc элемент имеет отношение к защите — lpSecuntyDescnptor. Если надо ограничить доступ к созданному Вами объекту ядра, создайте дескриптор защиты и инициализируйте структуру SECURITY_ATTRIBUTES следующим образом:
Рассмотрение элемента bInheritHandle я отложу до раздела о наследовании, так как этот элемент не имеет ничего общего с защитой. Желая
получить доступ к существующему объекту ядра (вместо того чтобы создавать
новый), укажите, какие операции Вы намерены проводить над обьектом. Например,
если бы я захотел считывать данные из существующей проекции файла, то вызвал бы
функцию OpenFileMapping таким образом;
Передавая
FILE_MAPREAD первым параметром в функцию OpenFileMapping, я сообщаю, что, как
только мне предоставят доступ к проекции файла, я буду считывать из нее данные.
Функция OpenFileMapping, прежде чсм вернуть действительный описатель, проверяет
тип защиты объекта. Если меня, как зарегистрировавшегося пользователя, допускают
к существующему объекту ядра "проекция файла", OpenFileMapping возвращает
действительный описатель. Но если мне отказывают в доступе,
OpenFileMapping
Кроме объектов ядра Ваша программа может использовать объекты других типов — меню, окна, курсоры мыши, кисти и шрифты. Они относятся к объектам User или GDI Новичок в программировании для Windows может запутаться, пытаясь отличить объекты User или GDI от объектов ядра. Как узнать, например, чьим объектом — User или ядра — является данный значок? Выяснить, не принадлежит ли объект ядру, проще всего так проанализировать функцию, создающую объект. Практически у всех функций, создающих объекты ядра, есть параметр, позволяющий указать атрибуты защиты, — как у CreateFileMapping. В то же время у функций, создающих объекты User или GDI, нет параметра типа PSECURITY_ATTRIBUTES, и пример тому — функция CreateIcon
Таблица описателей объектов ядраПри инициализации процесса система создает в нем таблицу описатслсй, используемую только для объектов ядра. Сведения о структуре этой таблицы и управлении ею незадокументированы Вообще-то я воздерживаюсь от рассмотрения недокументиро ванных частей операционных систем. Но в данном случае стоит сделать исключение, — квалифицированный Windows-программист, на мой взгляд, должен понимать, как устроена таблица описателей в процессе. Поскольку информация о таблице описателей незадокументирована, я не ручаюсь за ее стопроцентную достоверность и к тому же эта таблица по-разному реализуется в Windows 2000, Windows 98 и Windows СЕ. Таким образом, следующие разделы помогут понять, что представляет собой таблица описателей, но вот что система действительно делает с ней — этот вопрос я оставляю открытым. В таблице 3-1 показано, как выглядит таблица описателей, принадлежащая про цессу Как видите, это просто массив структур данных Каждая структура содержит указатель на какой-нибудь объект ядра, маску доступа и некоторые флаги
Таблица 3-1. Структура таблицы описателей, принадлежащей процессу Создание объекта ядраКогда процесс инициализируется в первый paз, таблица описателей еще пуста. Но стоит одному из его потоков вызвать функцию, создающую объект ядра (например, CreateFtleMapping), как ядро выделяет для этого объекта блок памяти и инициализирует его, далее ядро просматривает таблицу описателей, принадлежащую данному процессу, и отыскивает свободную запись. Поскольку таблица еще пуста, ядро обнаруживает структуру с индексом 1 и инициализирует ее. Указатель устанавливается на внутренний адрес структуры данных объекта, маска доступа — на доступ без ограничений и, наконец, определяется последний компонент — флаги (О флагах мы поговорим позжс, в разделе о наследовании ) Вот некоторые функции, создающие объекты ядра (список ни в коей мере на полноту не претендует)
Все функции, создающие объекты ядра, возвращают описатели, которые привязаны к конкретному процессу и могут быть использованы в любом потоке данного процесса Значение описателя представляет собой индекс в таблице описателей, принадлежащей процессу, и таким образом идентифицирует место, где хранится информация, связанная с объектом ядра. Вот поэтому при отладке своего приложения и просмотре фактического значения описателя объекта ядра Вы и видите такие малые величины: 1, 2 и т. д. Но помните, что физическое содержимое описателей не задокументировано и может быть изменено. Кстати, в Windows 2000 это значение определяет, по сути, не индекс, а скорее байтовое смещение нужной записи от начала таблицы описателей. Всякий раз, когда Вы вызываете функцию, принимающую описатель объекта ядра как аргумент, Вы передаете ей значение, возвращенное одной из Create-функций. При этом функция смотрит в таблицу описателей, принадлежащую Вашему процессу, и считывает адрес нужного объекта ядра. Если Вы передаете неверный индекс (описатель), функция завершается с ошибкой и GetLastError возвращает 6 (ERROR_INVALID_HANDLE). Это связано с тем, что на самом деле описатели представляют собой индексы в таблице, их значения привязаны к конкретному процессу и недейовительны в других процессах. Если вызов функции, создающей объект ядра, оказывается неудачен, то обычно возвращается 0 (NULL). Такая ситуация возможна только при острой нехватке памяти или при наличии проблем с защитой. К сожалению, отдельные функции возвращают в таких случаях пе 0, а -1 (INVALID_HANDLE_VALUE) Например, если CreateFile не сможет открыть указанный файл, она вернет именно INVALID_HANDLE_VALUE. Будьте очень осторожны при проверке значения, возвращаемого функцией, которая создает объект ядра. Так, для CreateMutex проверка на INVALID_HANDlE_VALUE бессмысленна:
Закрытие объекта ядраНезависимо от того, как именно Вы создали объект ядра, по окончании работы с ним его нужно закрьпь вызовом CloseHandle BOOL CloseHandle(HANDLE hobj); Эта функция сначала проверяет таблицу описателей, принадлежащую вызывающему процессу, чтобы убедиться, идентифицирует ли переданный ей индекс (описа тель) объект, к которому этот процесс действительно имеет доступ. Если переданный индекс правилен, система получает адрес структуры данных объекта и уменьшает в этой структуре счетчик числа пользователей; как только счетчик обнулится, ядро удалит объект из памяти. Если же описатель невереи, происходит одно из двух. В нормальном режиме выполнения процесса CloseHandle возвращает FALSE, a GetLastError — код ERROR_INVALID_HANDLE. Но при выполнении процесса в режиме отладки система просто уведомляет отладчик об ошибке. Перед самым
возвратом управления CloseHandle удаляет соответствующую запись из таблицы
описателей: данный описатель тспсрь недействителен в Вашем процессе и
использовать его нельзя. При этом запись удаляется независимо от того, разрушен
объект ядра или нет! После вызова CloseHandle Вы больше не получите доступ к
это- А вдруг Вы забыли вызвать CloseHandle — будет ли утечка памяти? И да, и нет. Утечка ресурсов (тех же объектов ядра) вполне вероятна, пока процесс еще исполняется. Однако по завершении процесса операционная система гарантированно освобождает все ресурсы, принадлежавшие этому процессу, и в случае объектов ядра действует так: в момент завершения процесса просматривает его таблицу описателей и закрывает любые открытые описатели. Совместное использование объектов ядра несколькими процессамиВремя от времени возникает необходимость в разделении объектов ядра между потоками, исполняемыми в разных процессах. Причин тому может быть несколько: • объекты "проекции файлов" позволяют двум процессам, исполняемым на одной машине, совместно использовать одни и те же блоки данных; • почтовые ящики и именованные каналы дают возможность программам обмениваться данными с процессами, исполняемыми на других машинах в сети; • мьютексы, семафоры и события позволяют синхронизировать потоки, исполняемые в разных процессах, чтобы одно приложение могло уведомитьдругое об окончании той или иной операции. Но поскольку описатели объектов ядра имеют смысл только в конкретном процессе, разделение объектов ядра между несколькими процессами — задача весьма непростая. У Microsoft было несколько веских причин сделать описатели процессно-зависимыми", и самая главная — устойчивость операционной системы к сбоям. Если бы описатели объектов ядра были общесистемными, то один процесс мог бы запросто получить описатель объекта, используемого другим процессом, и устроить в нем (этом процессе) настоящий хаос. Другая причина — защита. Объекты ядра защищены, и процесс, прежде чсм оперировать с ними, должен запрашивать разрешение на доступ к ним. Три механизма, позволяющие процессам совместно использовать одни и те же объекты ядра, мы рассмотрим в следующем разделе. Наследование описателя объектаНаследование применимо, только когда процессы связаны родственными отношениями (родительский-дочерний). Например, родительскому процессу доступен один или несколько описателей объектов ядра, и он решает, породив дочерний процесс, передать ему по наследству доступ к своим объектам ядра. Чтобы такой сценарий наследования сработал, родительский процесс должен выполнить несколько операций. Во-первых, еще при создании объекта ядра этот процесс должен сообщить системе, что ему нужен наследуемый описатель данного объекта. (Имейте в виду описатели объектов ядра наследуются, но сами объекты ядра — нет.) Чтобы создать наследуемый описатель, родительский процесс выделяет и инициализирует структуру SECURITY_ATTRIBUTES, а затем передает ее адрес требуемой Create-функции. Следующий код создаст объект-мьютекс и возвращает его описатель:
Этот код инициализирует структуру SECURTY_ATTRIBUTES, указывая, что объект следует создать с защитой по умолчанию (в Windows 98 это игнорируется) и что возвращаемый описательдолжен быть наследуемым.
А теперь
перейдем к флагам, которые хранятся в таблице описателей, принадлежащей процессу
В каждой ее записи присутствует битовый флаг, сообщающий, является данный
описатель наследуемым или нет. Если Вы, создавая объект ядра, передадите в
парамере типа PSECURITY_ATTRIBUTES значение NULL, то получите ненаследуемый
описатель, и этот флаг будет нулевым. А если элемент bInheritHandle равен TRUE,
флaгy пpиcвaивaeтcя 1. Допустим,
какому-то процессу принадлежит таблица описателей, как в таблице
3-2.
Таблица 3-2. Таблица описателей с двумя действительными
записями Эта таблица свидетельствует, что данный процесс имеет доступ к двум объектам ядра: описатель 1 (ненаследуемый) и 3 (наследуемый) Следующий этап — родительский процесс порождает дочерний. Это делается с помощью функции CreateProcess,
Подробно мы рассмотрим эту функцию в следующей главе, а сейчас я хочу лишь обратить Ваше внимание на параметр blnberitHandles. Создавая процесс, Вы обычно передаете в этом параметре FALSE, тем самым сообщая системе, что дочерний процесс не должен наследовать наследуемые описатели, зафиксированные в таблице родительского процесса. Если же Вы передаете TRUE, дочерний процесс наследует описатели родительского. Тогда операционная система создает дочерний процесс, но не дает ему немедленно начать свою работу. Сформировав в нем, как обычно, новую (пустую) таблицу описателей, она считывает таблицу родительского процесса и копирует все ее действительные записи в таблицу дочернего — причем в те же позиции. Последний факт чрезвычайно важен, так как означает, что описатели будут идентичны в обоих процессах (родительском и дочернем). Помимо копирования записей из таблицы описателей, система увеличивает значения счетчиков соответствующих объектов ядра, поскольку эти объекты теперь используются обоими процессами. Чтобы уничтожить какой-то объект ядра, его описатель должны закрыть (вызовом CloseHandle) оба процесса. Кстати, сразу после возврата управления функцией CreateProcess родительский процесс может закрыть свой описатель объекта, и это никак не отразится на способности дочернего процесса манипулировать с этим объектом. В таблице 3-3
показано состояние таблицы описателей в дочернем процессе — перед самым началом
его исполнения. Как видите, записи 1 и 2 не инициализированы, и поэтому данные
описатели неприменимы в дочсрнсм процессе Однако индекс 3 действительно
идентифицирует объект ядра по тому же (что и в родительском) адресу 0xF0000010.
При этом маска доступа и флаги в родительском и дочернем процессах тоже
идентичны. Так что, если дочерний процесс в свою очередь породит новый ("внука"
по отношению к исходному родительскому), "внук" унаследует данный описатель
объекта ядра с теми же значением, нравами доступа и флагами, а счетчик числа
пользователей этого объекта ядра вновь увеличится на 1.
Таблица 3-3. Таблица описателей в дочернем процессе (после того как он
унаследовал от родительского один наследуемый описатель) Наследуются только описатели объектов, существующие на момент создания дочернего процесса. Если родительский процесс создаст после этого новые объекты ядра с
наследуемыми описателями, то эти описатели будут уже недоступны дочернему
процессу. Для наследования описателей объектов характерно одно очень странное свойство: дочерний процесс не имеет ни малеЙшего понятия, что он унаследовал какие-то описатсли. Поэтому наследование описятелей объектов ядра полезно, только когда дочерний процесс сообщает, что при его создании родительским процессом он ожидает доступа к какому-нибудь объекту ядра. Тут надо заметить, что обычно родительское и дочернее приложения пишутся одной фирмой, но в принципе дочернее приложение может написать и сторонняя фирма, если в этой программе задокументировано, чего именно она ждет от родительского процесса. Для этого в
дочерний процесс обычно передают значение ожидаемого им описателя объекта ядра
как аргумент в командной строке. Инициализирующий код дочернего процесса
анализирует командную строку (чаще всего вызовом sscanf), извлекает из нее
значение описателя, и дочерний процесс получает неограниченный доступ
к Для наследственной передачи описателя объекта ядра от родительского процесса дочернему, конечно же, годятся и другие формы межпроцессной сяязи Один из приемов заключается в том, что родительский процесс дожидается окончания инициализации дочернего (через функцию WaitForInputIdle рассматриваемую в главе 9), а затем посылает (синхронно или асинхронно) сообщение окну, созданному потоком дочернего процесса. Еще один прием: родительский процесс добавляет в свой блок переменных окружения новую переменную Она должна быть "узнаваема" дочерним процессом и содержать значение наследуемого описятеля объекта ядра, Далее родительский процесс создает дочерний, тот наследует переменные окружения родительского процесса и, вызвав GetEnvironmentVariable, получает нужный описатель. Такой прием особенно хорош, когда дочерний процесс тоже порождает процессы, — ведь все переменные окружения вновь наследуются. Изменение флагов описателя Иногда встречаются ситуации, в которых родительский процесс создает объект ядра с наследуемым описателем, а затем порождает два дочерних процесса. Но наследуемый описатель нужен только одному из них. Иначе говоря, время от времени возникает необходимость контролировать, какой из дочерних процессов наследует описатели объектов ядра. Для этого модифицируйте флаг наследования, связанный с описателем, вызовом SetHandleInformation
Как видите, эта функция принимает три параметра. Первый (bObject) идентифицирует допустимый описатель. ВтороЙ (dwMask) сообщает функции, какой флаг (или флаги) Вы хотите изменить На сегодняшний день с каждым описателем связано два флага:
Чтобы
изменить сразу все флаги объекта, нужно объединить их побитовой операцией
OR. И, наконец, третий параметр функции SetHandleInformation — dwFlags — указывает, в какое именно состояние следует перевести флаги. Например, чтобы установить флаг наследования для описателя объекта ядра
а чтобы сбросить этот флаг:
Флаг
HANDLE_FLAGPROTECT_FROM_CLOSE сообщает системе, что данный описатель закрывать
нельзя:
Если
какой-нибудь поток попытается закрыть защищенный описатель, CloseHandle приведет
к исключению. Необходимость в такой защите возникает очень редко. Однако этот
флаг весьма полезен, когда процесс порождает дочерний, а тот в свою очередь —
еще один процесс. При этом родительский процесс может ожидать, что его "внук"
унаследует определенный описатель объекта, переданный дочернему. Но тут вполне
возможно, что дочерний процесс, прежде чем породить новый процесс, закрывает
нужный описатель. Тогда родительский процесс теряет связь с "внуком", поскольку
тот не унаследовал требуемый объект ядра. Защитив описатель от закрытия, Вы
исправите ситуацию, и "внук" унаследует предназначенный ему
объект. У этого
подхода, впрочем, есть один недостаток. Дочерний процесс, вызвав:
может
сбросить флаг HANDLE_FLAG_PROTECT_FROM_CLOSE и закрыть затем соответствующий
описатель. Родительский процесс ставит на то, что дочерний не исполнит этот код.
Но одновременно он ставит и на то, что дочерний процесс породит ему "внука",
поэтому в целом ставки не слишком рискованны. Для полноты картины стоит, пожалуй, упомянуть и функцию GetHandleInformation:
Эта функция возвращает текущие флаги для заданного описателя в переменной типа DWORD, на которую укапывает pdwFlags. Чтобы проверить, является ли описатель наследуемым, сделайте так:
Именованные объектыВторой способ, позволяющий нескольким процессам совместно использовать одни и те же объекты ядра, связан с именованием этих объектов. Именование допускают многие (но не все) объекты ядра. Например, следующие функции создают именованные объекты ядра
Последний параметр, pszName, у всех этих функций одинаков. Передавая в нем NULL, Вы создаете безымянный (анонимный) объект ядра. В этом случае Вы можете разделять объект между процессами либо через наследование (см. предыдущий раздел), либо с помощью DuplicateHandle (см. следующий раздел). А чтобы разделять объект по имени, Вы должны присвоить ему какое-нибудь имя. Тогда вместо NULL в параметре pszName нужно передать адрес строки с именем, завершаемой нулевым символом. Имя может быть длиной до MAX_PATH знаков (это значение определено как 260). К сожалению, Microsoft ничего не сообщает о правилах именования объектов ядра. Например, создавая объект с именем JeffObj, Вы никак не застрахованы от того, что в системе еще нет объекта ядра с таким именем. И что хуже, все эти объекты делят единое пространство имен. Из-за этого следующий вызов CreateSemaphore будет всегда возвращать NULL:
После выполнения этого фрагмента значение dwErrorCode будет равно 6 (ERROR_INVALID_HANDLE). Полученный код ошибки не слишком вразумителен, но другого не дано. Теперь, когда Вы научились именовать объекты, рассмотрим, как разделять их между процессами по именам. Допустим, после запуска процесса А вызывается функция:
Этот вызов заставляет систему создать новенький, как с иголочки, объект ядра "мъютекс" и присвоить ему имя JeffMutex Заметьте, что описатель hMutexProcessA в процессе А не является наследуемым, — он и не должен быть таковым при простом именовании объектов. Спустя
какое-то время некий процесс порождает процесс В. Необязательно, что-бы
последний был дочерним от процесса А; он может быть порожден Explorer или любым
другим приложением. (В этом, кстати, и состоит преимущество механизма именования
объектов перед наследованием.) Когда процесс В приступает к
работе,
При этом вызове система сначала проверяет, не существует ли уже объект ядра с таким именем. Если да, то ядро проверяет тип этого объекта. Поскольку мы пытаемся создать мьютекс и его имя тоже JeffMutex, система проверяет права доступа вызывающего процесса к этому объекту. Если у него есть все права, в таблице описателей, принадлежащей процессу В, создается новая запись, указывающая на существующий объект ядра. Если же вызывающий процесс не имеет полных прав на доступ к объекту или если типы двух объектов с одинаковыми именами не совпадают, вызов CreateMutex заканчивается неудачно и возвращается NULL. Однако, хотя процесс В успешно вызвал CreateMutex, новый объект-мьютекс он не создал. Вместо этого он получил свой описатель существующего объекта-мьютекса. Счетчик объекта, конечно же, увеличился на 1, и теперь этот объект не разрушится, пока его описатели не закроют оба процесса — А и В. Заметьте, что значения описателей объекта в обоих процессах скорее всего разные, но так и должно быть, каждый процесс будет оперировать с данным объектом ядра, используя свой описатель.
Вызывая CreateMutex, процесс В передает ей атрибуты защиты и второй параметр. Так вот, эти параметры игнорируются, если объект с указанным именем уже существует! Приложение может определить, что оно делает: создает новый объект ядра или просто открывает уже существующий, — вызвав GetLastError сразу же после вызова одной из Create-функций:
Есть и другой способ разделения объектов по именам. Вместо вызова Create-функции процесс может обратиться к одной из следующих Open-функций:
Заметьте: все
эти функции имеют один прототип. Последний параметр, pszName, определяет имя
объекта ядра. В нем нельзя передать NULL — только адрес строки с нулевым
символом в конце Эти функции просматривают единое пространство имен объектов
ядра, пытаясь найти совпадение. Если объекта ядра с указанным именем нет,
функции возвращают NULL, a GetLastError — код 2 (ERROR_FILE_NOT_FOUND). Но если
объект ядра с заданным именем существует и если его тип идентичен тому, что Вы
указали, система проверяет, разрешен ли к данному объекту доступ запрошенного
вида (через параметр dwDesiredAccess). Если такой вид доступа разрешен, таблица
описателей в вызывающем процессе обновляется, и счетчик числа пользователей
объекта возрастает на 1 Если Вы присвоили параметру bInheritHandle значение
TRUE, то получше наследуемый описатель. Главное отличие между вызовом Create- и Open-функций в том, что при отсутствии указанного объекта Create-функция создает его, а Open-функция просто уведомляет об ошибке. Как я уже говорил, Microsoft ничего не сообщает о правилах именования объектов ядра Но представьте себе, что пользователь запускает две программы от разных компаний и каждая программа пытается создать объект с именем "MyObject". Ничего хорошего из этого не выйдет. Чтобы избежать такой ситуации, я бы посоветовал создавать GUID и использовать его строковое представление как имя объекта. Именованные объекты часто применяются для того, чтобы не допустить запуска нескольких экземпляров одного приложения. Для этого Вы просто вызываете одну из Create-функций в своей функции main или WinMain и создаете некий именованный объект. Какой именно — не имеет ни малейшего значения. Сразу после Create-функции Вы должны вызвать GetLastError Если она вернет ERROR_ALREADY_EXISTS, значит, один экземпляр Вашего приложения уже выполняется и новый его экземпляр можно закрыть. Вот фрагмент кода, иллюстрирующий этот прием:
Пространства имен Terminal Server Terminal Server несколько меняет описанный выше сценарий На машине с Terminal Server существует множество пространств имен для объектов ядра. Объекты, которые должны быть доступны всем клиентам, используют одно глобальное пространство имен. (Такие объекты, как правило, связаны с сервисами, предоставляемыми клиентским программам.) В каждом клиентском сеансе формируется свое пространство имен, чтобы исключить конфликты между несколькими сеансами, в которых запускается одно и то же приложение Ни из какого сеанса нельзя получить доступ к объектам другого сеанса, даже если у их объектов идентичные имена. Именованные объекты ядра, относящиеся к какому-либо сервису, всегда находятся в глобальном пространстве имен, а аналогичный объект, связанный с приложением, Terminal Server по умолчанию помещает в пространство имен киентского сеанca. Однако и его можно перевести в глобальное пространство имен, поставив перед именем объекта префикс "Global\", как в примере ниже.
Если Вы хотите явно указать, что объект ядра должен находиться в пространстве имен клиентского сеанса, используйте префикс "Local\":
Microsoft рассматривает префиксы Global и Local как зарезервированные ключевые слова, которые не должны встречаться в самих именах объектов. К числу таких слов Microsoft относит и Session, хотя на сегодняшний день оно не связано ни с какой функциональностью. Также обратите внимание на две вещи, все эти ключевые слова чувствительны к регистру букв и игнорируются, если компьютер работает без Terminal Server. Дублирование описателей объектовПоследний механизм совместного использования объектов ядра несколькими процессами требует функции DuplicateHandle;
Говоря по-простому, эта функция берет запись в таблице описателей одного процесса и создает ее копию в таблице другого DuplicateHandle принимает несколько параметров, но на самом деле весьма незамысловата Обычно ее применение требует наличия в системе трех рапных процессов. Первый и третий параметры функции DuplicateHandle представляют собой описатели объектов ядра, специфичные для вызывающего процесса Кроме того, эти параметры должны идентифицировать именно процессы — функция завершится с ошибкой, если Вы передадите описатели на объекты ядра любого другого типа. Подробнее объекты ядра "процессы" мы обсудим в главе 4, а сейчас Вам достаточно знать только одно- объект ядра "процесс" создается при каждой инициации в системе нового процесса Второй параметр, hSourceHandle, — описатель объекта ядра любого типа. Однако его значение специфично нс для процесса, вызывающего DuplicateHandle, а для того, на который указывает описатель hSourceProcessHandie. Параметр pbTargetHandle — это адрес переменной типа HANDLE, в которой возвращается индекс записи с копией описателя из процесса-источника. Значение возвращаемого описателя специфично для процесса, определяемого параметром bTargetProcessHandle. Предпоследние два параметра DuplicateHandle позволяют задать маску доступа и флаг наследования, устанавливаемые для данного описателя в процессе-приемнике. И, наконец, параметр dwOptions может быть 0 или любой комбинацией двух флагов. DUPLICATE_SAME_ACCESS и DUPLICATE_CLOSE_SOURCE Первый флаг подсказывает DuplicateHandle: у описателя, получаемого процессом-приемником, должна быть та же маска доступа, что и у описателя в процессе-источнике Этот флаг заставляет DuplicateHandle игнорировать параметр dwDesiredAccess. Второй флаг приводит к закрытию описателя в процессе-источнике. Он позволяет процессам обмениваться объектом ядра как эстафетной палочкой При этом счетчик объекта не меняется. Попробуем проиллюстрировать работу функции Duplicatellandle на примере. Здесь S — это процесс-источник, имеющий доступ к какому-то объекту ядра, Т — это процесс-приемник, который получит доступ к тому же объекту ядра, а С — процесскатализатор, вызывающий функцию DuplicateHandle Таблица
описателей в процессе С (см таблицу 3-4) содержит два индекса - 1 и 2. Описатель
с первым значением идентифицирует объект ядра "процесс S", описатель со вторым
значением — объект ядра "процесс Т"
Таблица 3-4. Таблица описателей в процессе С Таблица 3-5
иллюстрирует таблицу описателей в процессе S, содержащую единственную запись со
значением описателя, равным 2. Этот описатель может идентифицировать объект ядра
любого типа, а не только "процесс".
Таблица 3-5. Таблица описателей в процессе S В таблице 3-6 показано, что именно содержит таблица описателей в процессе Т перед вызовом процессом С функции DuplicateHandle. Как видите, в ней всего одна запись со значением описателя, равным 2, а запись с индексом 1 пока пуста.
Таблица 3-6. Табпица описателей в процессе Т перед вызовом DuplicateHandle Если процесс С теперь вызовет DuplicateHandle так
то после
вызова изменится только таблица описателей в процессе Т (см. таблицу
3-7).
Таблица 3-7. Таблица описателей в процессе Т после вызова
DuplicateHandle Вторая строка таблицы описателей в процессе S скопирована в первую строку таблицы описателей в процессе Т. Функция DuplicateHandle присвоила также переменной bObj процесса С значение 1 — индекс той строки таблицы в процессе Т, в которую занесен новый описатель. Поскольку функции DuplicateHandle передан флаг DUPLICATE_SAME_ACCESS, маска доступа для этого описателя в процессе Т идентична маске доступа в процессе S. Кроме того, данный флаг заставляет DuplicateHandle проигнорировать параметр dwDesiredAccess. Заметьте также, что система установила битовый флаг наследования, так как в параметре bInberitHandle функции DuplicateHandle мы передали TRUE. Очевидно, Вы никогда не станете передавать в DuplicateHandle жестко зашитые значения, как это сделал я, просто демонстрируя работу функции. В реальных программах значения описателей хранятся в переменных и, конечно же, именно эти переменные передаются функциям. Как и механизм наследования, функция DuplicateHandle тоже обладает одной странностью: процесс-приемник никак не уведомляется о том, что он получил доступ к новому объекту ядра. Поэтому процесс С должен каким-то образом сообщить процессу Т, что тот имеет теперь доступ к новому объекту; для этого нужно воспользоваться одной из форм межпроцессной связи и передать в процесс Т значение описателя в переменной bObj. Ясное дело, в данном случае не годится ни командная строка, ни изменение переменных окружения процесса Т, поскольку этот процесс уже выполняется. Здесь придется послать сообщение окну или задействовать какой-нибудь другой механизм межпроцессной связи. Я рассказал Вам о функции DuplicateHandle в самом общем виде. Надеюсь, Вы увидели, насколько она гибка. Но эта функция редко используется в ситуациях, требующих участия трех разных процессов Обычно ее вызывают применительно к двум процессам. Представьте, что один процесс имеет доступ к объекту, к которому хочет обратиться другой процесс, или что один процесс хочст предоставить другому доступ к "своему" объекту ядра. Например, если процесс S имеет доступ к объекту ядра и Вам нужно, чтобы к этому объекту мог обращаться процесс Т, используйте DuplicateHandle так:
Вызов GetCurrentProcess возвращает псевдоописатель, который всегда идентифицирует вызывающий процесс, в данном случае — процесс S Как только функция DuplicateHandle возвращает управление, bObjProcessT становится описателем, связанным с процессом Т и идентифицирующим тот же объект, что и описатель bObjProcessS (когда на него ссылается код процесса S). При этом процесс S ни в коем случае не должен исполнять следующий код:
Если процесс S выполнит этот код, вызов может дать (а может и не дать) ошибку Он будет успешен, если у процесса S случайно окажется описатель с тем же значением, что и в hObjProcessT При этом процесс S закроет неизвестно какой объект, и что будет потом — остается только гадать Теперь о другом способе применения DuplicateIlandle Допустим, некий процесс имеет полный доступ (для чтения и записи) к объекту "проекция файла" и из этого процесса вызывается функция, которая должна обратиться к проекции файла и считать из нее какие-то данные Так вот, если мы хотим повысить отказоустойчивость приложения, то могли бы с помощью DuplicateHandle создать новый описатель существующего объекта и разрешить доступ только для чтения. Потом мы передали бы этот описатель функции, и та уже не смогла бы случайно что-то записать в проекцию файла. Взглянше на код, который иллюсгрирует этот пример:
|
|
| бодибилдинг | Строим Домик | RU-домены за 170 рублей | Copyright © "В помощь Веб-Мастеру" (Alexander D. Belyaev) 2005-2008. При перепечатке любого материала видимая ссылка на источник "В помощь Веб-Мастеру" и все имена, ссылки авторов обязательны! Время генерации страницы: 0.102 |