Дублирование описателей объектов
Последний механизм совместного использования объектов ядра несколькими процессами требует функции DuplicateHandle:
BOOL DuplicateHandle(
HANDLE hSourceProcessHandle,
HANDLE hSourceHandle,
HANDLE hTargetProcessHandle,
PHANDLE phTargetHandle,
DWORD dwDesiredAccess,
BOOL bInheritHandle,
DWORD dwOptions);
Говоря по-простому, эта функция берет запись в таблице описателей одного процесса и создает ее копию в таблице другого 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", описатель со вторым значением — объект ядра "процесс Т"
Индекс |
Указатель на блок памяти объекта ядра |
Маска доступа (DWORD с набором битовых флагов) |
Флаги (DWORD с битовых флагов) набором |
1 |
0xF0000000 (объект ядра процесса S) |
0x???????? | 0x00000000 |
2 | 0xF0000010 (обьект ядра процесса Т) | 0x???????? | 0x00000000 |
Таблица 3-5 иллюстрирует таблицу описателей в процессе S, содержащую единственную запись со значением описателя, равным 2. Этот описатель может идентифицировать объект ядра любого типа, а не только "процесс".
Индекс |
Указатель на блок памяти объекта ядра |
Маска доступа (DWORD с набором битовых флагов) |
Флаги (DWORD с набором битовых флагов) |
1 | 0x00000000 | (неприменим) | (неприменим) |
2 |
0xF0000020 (объект ядра любого типа) |
0x???????? | 0x00000000 |
В таблице 3-6 показано, что именно содержит таблица описателей в процессе Т перед вызовом процессом С функции DuplicateHandle. Как видите, в ней всего одна запись со значением описателя, равным 2, а запись с индексом 1 пока пуста.
Индекс | Указатель на блок памяти объекта ядра | Маска доступа (DWORD с набором битовых флагов) |
Флаги (DWORD с набором битовых флагов |
1 | 0x00000000 | (неприменим) | (неприменим) |
2 |
0xF0000030 (объект ядра любого типа) |
0x???????? | 0x00000000 |
Таблица 3-6. Табпица описателей в процессе Т перед вызовом DuplicateHandle
Если процесс С теперь вызовет DuplicateHandle так:
DuplicateHandle(1, 2, 2, &hObj, 0, TRUE, DUPLICATE_SAME_ACCESS);
то после вызова изменится только таблица описателей в процессе Т (см. таблицу 3-7).
Индекс | Указатель на блок памяти объекта ядра | Маска доступа (DWORD с набором битовых флагов) | Флаги (DWORD с набором битовых флагов) |
1 | 0xF0000020 | 0х???????? | 0x00000001 |
2 |
0xF0000030 (объект ядра любого типа) |
0х???????? | 0x00000000 |
Вторая строка таблицы описателей в процессе S скопирована в первую строку таблицы описателей в процессе Т. Функция DuplicateHandle присвоила также переменной bObj процесса С значение 1 — индекс той строки таблицы в процессе Т, в которую занесен новый описатель.
Поскольку функции DuplicateHandle передан флаг DUPLICATE_SAME_ACCESS, маска доступа для этого описателя в процессе Т идентична маске доступа в процессе S. Кроме того, данный флаг заставляет DuplicateHandle проигнорировать параметр dwDesiredAccess. Заметьте также, что система установила битовый флаг наследования, так как в параметре bInberitHandle функции DuplicateHandle мы передали TRUE.
Очевидно, Вы никогда не станете передавать в DuplicateHandle жестко зашитые значения, как это сделал я, просто демонстрируя работу функции. В реальных программах значения описателей хранятся в переменных и, конечно же, именно эти переменные передаются функциям.
Как и механизм наследования, функция DuplicateHandle тоже обладает одной странностью: процесс-приемник никак не уведомляется о том, что он получил доступ к новому объекту ядра. Поэтому процесс С должен каким-то образом сообщить процессу Т, что тот имеет теперь доступ к новому объекту; для этого нужно воспользоваться одной из форм межпроцессной связи и передать в процесс Т значение описателя в переменной bObj. Ясное дело, в данном случае не годится ни командная строка, ни изменение переменных окружения процесса Т, поскольку этот процесс уже выполняется.
Здесь придется послать сообщение окну или задействовать какой-нибудь другой механизм межпроцессной связи.
Я рассказал Вам о функции DuplicateHandle в самом общем виде. Надеюсь, Вы увидели, насколько она гибка. Но эта функция редко используется в ситуациях, требующих участия трех разных процессов Обычно ее вызывают применительно к двум процессам. Представьте, что один процесс имеет доступ к объекту, к которому хочет обратиться другой процесс, или что один процесс хочст предоставить другому доступ к "своему" объекту ядра. Например, если процесс S имеет доступ к объекту ядра и Вам нужно, чтобы к этому объекту мог обращаться процесс Т, используйте DuplicateHandle так:
// весь приведенный ниже код исполняется процессом S
// создаем объект-мьютекс, доступный процессу S
HANDLE hObjProcessS = CreateMutex(NULL, FALSE, NULL);
// открываем описатель объекта ядра "процесс Т"
HANDLE hProcessT = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessIdT);
HANDLE hObjProcessT; // неинициализированный описатель,
// связанный с процессом Т
// предоставляем процессу Т доступ к объекту-мьютексу
DuplicateHandle(GetCurrentProcess(), hObjProcessS, hProcessT,
&hObjProcessT, 0, FALSE, DUPLICATE_SAME_ACCESS);
// используем какую-нибудь форму межпроцессной связи, чтобы передать
// значение описателя из hOb]ProcessS в процесс Т
// связь с процессом Т больше не нужна
CloseHandle(hProcessT),
// если процессу S не нужен объект-мьютекс, он должен закрыть его
CloseHandle(hObjProcessS);
Вызов GetCurrentProcess возвращает псевдоописатель, который всегда идентифицирует вызывающий процесс, в данном случае — процесс S. Как только функция DuplicateHandle возвращает управление, bObjProcessT становится описателем, связанным с процессом Т и идентифицирующим тот же объект, что и описатель bObjProcessS (когда на него ссылается код процесса S). При этом процесс S ни в коем случае не должен исполнять следующий код:
// процесс S никогда не должен пытаться исполнять код,
// закрывающий продублированный описатель
CloseHandle(hObjProcessT);
Если процесс S выполнит этот код, вызов может дать (а может и не дать) ошибку. Он будет успешен, если у процесса S случайно окажется описатель с тем же значением, что и в hObjProcessT. При этом процесс S закроет неизвестно какой объект, и что будет потом — остается только гадать.
Теперь о другом способе применения DuplicateIlandle. Допустим, некий процесс имеет полный доступ (для чтения и записи) к объекту "проекция файла" и из этого процесса вызывается функция, которая должна обратиться к проекции файла и считать из нее какие-то данные. Так вот, если мы хотим повысить отказоустойчивость приложения, то могли бы с помощью DuplicateHandle создать новый описатель существующего объекта и разрешить доступ только для чтения. Потом мы передали бы этот описатель функции, и та уже не смогла бы случайно что-то записать в проекцию файла. Взглянше на код, который иллюсгрирует этот пример:
int WINAPI WinMain(HINSTANCE hinstExe, HINSTANCE,
PSTR pszCmdLine, int nCmdShow) {
// создаем объект "проекция файла",
// его описатель разрешает доступ как для чтения, так и для записи
HANDLE hFileMapRW = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGF_READWRITE, 0, 10240, NULL);
// создаем другой описатель на тот же обьект;
// этог описатель разрешает дocтyп только для чтения
HANDLE hFileMapRO;
DuplicateHandle(GetCurrentProcess(), hFileMapRW, GetCurrentProcess(), &hFileMdpRO, FILE_MAP_READ, FALSE, 0);
// вызываем функцию, которая не должна ничего записывать в проекцию файла
ReadFromTheFileMapping(hFileMapRO);
// закрываем объект "проекция файла" , доступный только для чтения
CloseHandle(hFileMapRO);
// проекция файла нам по-прежнему полностью доступна через hFileMapRW
.
.
.
// если проекция файла больше не нужна основному коду, закрываем ее
CloseHandle(hFileMapRW);
}