к своему окну. Программа просмотра может послать пять различных сообщений:
WM_ASKCBFORMATNAME - Это сообщение посылается для запроса копии имени
формата у владельца буфера обмена. При этом не следует забывать о том, что сам
буфер обмена содержит лишь идентификатор CF_OWNERDISPLAY, поэтому про-
грамма просмотра имеет возможность самостоятельно принимать решение о том,
нужно ли ей знать реальный тип данных. Сообщение WM_ASKCBFORMATNAME
сопровождается указанием в аргументе wParam количества копируемых байтов. Ар-
гумент lParam содержит указатель на буфер, в который должен быть отправлен ре-
зультат.
WM_HSCROLLCLIPBOARD / WM_VSCROLLCLIPBOARD - Эти сообщения посылают-
ся в том случае, когда программа просмотра содержит вертикальную или горизон-
тальную полосу прокрутки и связанные с ними события должны передаваться вла-
дельцу буфера обмена. В аргументе wParam хранится дескриптор окна программы
просмотра, а в аргументе lParam - стандартные идентификаторы, которые сопровож-
дают сообщения WM_HSCROLL и WM_VSCROLL.
WM_PAINTCLIPBOARD - Это сообщение посылается в качестве запроса на обновление
окна программы просмотра, возможно, в ответ на сообщение WM_PAINT. Аргумент
wParam содержит дескриптор окна программы просмотра. Аргумент lParam пред-
ставляет собой глобальный дескриптор объекта, содержащего структуру
PAINTSTRUCT, которая задает обновляемую область экрана. Чтобы определить, вся
ли рабочая область окна требует обновления, владелец буфера обмена должен срав-
нить размеры области рисования, которые содержатся в поле repaint структуры
PAINTSTRUCT, с размерами, указанными в последнем сообщении
WM_SIZECLIPBOARD.
WM_SIZECLIPBOARD - Это сообщение посылается для указания того, что окно про-
граммы просмотра изменило свои размеры. Аргумент wParam содержит дескриптор
|
окна просмотра. Аргумент lParam представляет собой глобальный дескриптор объек-
та, содержащего структуру RECT, которая задает обновляемую область.
В ответ на любое из этих сообщений владелец буфера обмена должен вызвать
функцию InvalidateRect() или обновить окно просмотра и соответствующим образом ус-
тановить позицию бегунков полос прокрутки.
Приложения могут также объявлять собственныечастныеформатыбуфераоб-
мена, зарегистрировав новый формат с помощью функции RegisterClipboardFormat():
wFormat = RegisterClipboardFormat(lpszFormatTitle);
Новый идентификатор wFormat получает значение из диапазона от 0xC000 до
0xFFFF и может использоваться как параметр функций SetClipboardData() и GetClip-
boardData(). Чтобы другие приложение или другой экземпляр того же приложения смог-
ли прочитать данные из буфера обмена в этом формате, им необходимо предоставить
аналогичный идентификатор wFormat. Его значение может передаваться через буфер
обмена в формате CF_TEXT.
В качестве альтернативы можно использовать функцию EnumClipboardFormats(),
которая рассматривалась ранее. Эта функция возвращает идентификаторы всех форма-
тов, после чего следует вызвать функцию GetClipboardFormatName(), с тем чтобы полу-
чить имя формата в виде ASCII-строки:
GetClipboardFormatName(wFormat, lpszBuffer, nCharCount);
Следует подчеркнуть, что Windows не требует никакой информации о структуре
данных, которые передаются при использовании частных форматов. Интерпретация та-
ких данных находится исключительно в компетенции самого приложения. Операцион-
ная система должна знать только имя формата и дескриптор блока памяти.
|
§ 8.2.Обменинформациейпосредствомканалов
8.2.1. Общие положения и классификация каналов
Процессы, запущенные пользователем, не всегда имеют отношение друг к другу,
однако способность процессов обмениваться информацией иногда значительно повыша-
ет эффективность приложения и делает возможным решение задач, которые были бы не
под силу совокупности независимых процессов. Например, программа набора телефон-
ного номера и графический редактор имеют очень мало общего, однако если графиче-
ский редактор должен передавать информацию в удаленную систему, целесообразность
его связи с программой набора номера уже не вызывает сомнения. Windows обеспечива-
ет много возможностей для связи между приложениями (например, через буфер обмена,
DDE, OLE и т.д.).
Каналы представляют собой удобный механизм для обмена информацией между
процессами в различных программах [12]. В отличие от некоторых альтернативных ме-
ханизмов, с каналами не связаны формальные стандарты или протоколы передачи ин-
формации. Эта особенность каналов делает их более удобными в использовании и более
гибкими по сравнению, скажем, с механизмом DDE. Однако она же ограничивает воз-
можность применения каналов только программами, способными правильно распозна-
вать информацию, которой они обмениваются.
Поддержка каналов была впервые введена в Windows NT, а впоследствии - в Win-
dows 95 и Windows 98 (в виде анонимных каналов). Однако только Windows NT поддер-
живает возможность использования именованных каналов, которые не применимы в
Windows 98.
Канал представляет собой участок совместно используемой памяти, где процессы
|
оставляют свои сообщения друг для друга. Канал подобен файлу, данные в который за-
писываются одной программой, а считываются - другой. Поскольку каналы предназна-
чены для связи между процессами, Win32 API предоставляет набор команд, обеспечи-
вающих обмен информацией. Концептуально канал является гибридом файла и
электронного почтового ящика. Один процесс записывает в этот файл определенную
информацию, а другой - просматривает ее.
Канал появляется после того, как одна из программ принимает решение создать
его. Программа, которая создает канал, называется сервером канала. Другие процессы,
которые называются клиентами, могут связываться с каналом на другом его конце.
Сервер поддерживает жизнеспособность канала. Любой процесс может служить как сер-
вером, так и клиентом, или одновременно и сервером, и клиентом для разных каналов.
После того как канал будет создан и к нему подключится другой процесс, клиент
или сервер или одновременно и клиент, и сервер начнут записывать данные на своем
конце канала. Информация, записанная на одном конце канала, читается на другом его
конце. Операции чтения и записи выполняются с помощью тех же команд, которые
применяются для работы с любыми другими файлами: ReadFile() и WriteFile(). Обычно
процесс, ожидающий сообщения, создает для их получения новый поток. Этот поток пе-
риодически вызывает функцию ReadFile() и блокируется, оставаясь в пассивном состоя-
нии до поступления нового сообщения.
Наконец, сервер принимает решение о том, что диалог закончен, и разрывает со-
единение. Для уничтожения канала сервер вызывает функцию CloseHandle(). Но канал,
на самом деле, не будет уничтожен до тех пор, пока не будут закрыты все дескрипторы,
указывающие на него как со стороны клиента, так и со стороны сервера. Кроме того,
сервер может подключить к старому каналу нового клиента.
Имеется несколько разновидностей каналов [12]:
• входные, выходные и дуплексные (двунаправленные);
• байтовые и каналы сообщений;
• блокируемые и неблокируемые;
• именованные и анонимные.
Большинство атрибутов канала определяется при его создании. Рассмотрим основ-
ные виды каналов белее подробно.
Входные,выходныеидуплексныеканалы. Названия каналов, относящихся к пер-
вой группе, - входные, выходные и дуплексные - указывают на направление передачи
информации. Входные и выходные каналы являются однонаправленными: с одной их
стороны осуществляется запись информации, а с другой - чтение. Входной канал позво-
ляет клиенту передавать, а серверу принимать информацию; выходной, наоборот; серве-
ру - передавать, а клиенту - принимать. Дуплексный канал позволяет передавать и при-
нимать данные обеим сторонам.
Байтовыеканалыиканалысообщений. Характер записываемой информации оп-
ределяет, в каком режиме, байтовогоканала или каналасообщений, будет происхо-
дить чтение. Режим канала помогает системе принять решение о том, когда следует ос-
тановить операцию чтения. В режиме байтового канала чтение информации
прекращается при поступлении последнего байта из канала или при заполнении его бу-
фера чтения. В режиме сообщений чтение информации прекращается при достижении
конца сообщения.
Внутри канала, работающего в режиме сообщений, система размещает в начале
каждого записываемого фрагмента заголовок, хранящий сведения о длине этого фраг-
мента информации. Программы, которые находятся на концах канала, не видят этого за-
головка, однако команда ReadFile() прекращает чтение, достигая конца очередного
фрагмента данных.
Блокируемыеинеблокируемыеканалы. Каналы бывают блокируемыми или не-
блокируемыми. Данный атрибут влияет на результаты выполнения операций чтения,
записи и соединения. В случае ошибки выполнения любой из этих функций в неблоки-
руемом канале возвращается код завершения, представляющий собой код ошибки. В ка-
нале, который допускает блокировку, функции не возвращают результата до тех пор,
пока они не завершатся, или до тех пор, пока существует ошибка.
Именованныеианонимныеканалы. Канал может быть именованным, если про-
грамма-создатель снабдила его строковым значением, однозначно идентифицирующим
его имя, или анонимным, если с ним не связано никакое идентифицирующее строковое
значение. Подобно синхронизирующим объектам (простым и исключающим семафо-
рам), имя канала помогает другим процессам правильно его распознавать. Windows 98
не поддерживает именованных каналов; в этой операционной системе все каналы явля-
ются анонимными.
Анонимные каналы занимают меньше системных ресурсов, однако по сравнению с
именованными они выполняют лишь ограниченный набор задач. В частности, они могут
передавать сообщения только в одном направлении: или от клиента к серверу, или от
сервера к клиенту (но не в обоих направлениях одновременно). Кроме того, анонимные
каналы не работают в сетях. Клиент и сервер должны находиться на одном компьютере.
Именованные каналы, которые поддерживаются в Windows NT, выполняют неко-
торые задачи, недоступные для анонимных каналов. Так, они передают информацию в
обоих направлениях, соединяют посредством сети процессы, происходящие на различ-
ных удаленных компьютерах. Кроме того, именованные каналы могут существовать в
нескольких экземплярах и имеют дополнительные режимы работы. Способность суще-
ствовать в нескольких экземплярах позволяет именованным каналам соединять один
сервер с несколькими клиентами. Каждый экземпляр канала представляет собой отдель-
ную независимую линию связи. Сообщения одного экземпляра канала не смешиваются с
сообщениями другого экземпляра. Несколько экземпляров канала создаются в том слу-
чае, когда один или несколько серверов передают одно и то же идентифицирующее имя
в качестве аргумента функции CreateNamedPipe().
8.2.2. API-функции для работы с каналами
Как уже было сказано выше, основное предназначение каналов - поддержка связи
между процессами. Канал представляет собой буфер в памяти, где система сохраняет
данные в период между записью их одним процессом и чтением другим. API-команды
позволяют трактовать буфер как канал, или трубу, по которой информация "перетекает"
из одного места в другое. Канал имеет два конца. Односторонний канал позволяет запи-
сывать данные только на одном своем конце и читать их на другом конце, т.е. вся ин-
формация "перетекает" от одного процесса к другому. Двухсторонние каналы дают воз-
можность обоим процессам выполнять как запись, так и чтение информации, поэтому
информация может "перетекать" одновременно в двух направлениях. Кроме того, при
создании канала необходимо принять решение о том, будет он анонимным или имено-
ванным [12].
Создание анонимных каналов. Анонимный канал передает информацию только в
одном направлении, причем оба конца канала должны находиться на одном и том же ло-
кальном компьютере. Процесс, создающий анонимный канал, получает два дескриптора:
для записи и для чтения. Чтобы связаться с каким-либо процессом, сервер должен пере-
дать ему один из дескрипторов.
BOOL CreatePipe(PHANDLE phRead, // переменная для дескриптора чтения
// (входной канал)
PHANDLE phWrite,
// переменная для дескриптора записи
// (выходной канал)
LPSECURITY_ATTRIBUTES lpsa, // привилегии доступа
DWORD dwPipeSize);
// размер буфера канала (0 по умолчанию)
От размера буфера канала зависит, какой максимальный объем информации может
находиться в канале. Передача сообщений по заполненному каналу невозможна до тех
Пор, пока на другом конце канала не будет прочитана старая информация.
При успешном завершении функция CreatePipe() возвращает значение TRUE и за-
писывает два новых дескриптора в переменные, заданные параметрами типа PHANDLE.
После этого процесс, создавший канал, обычно передает один из дескрипторов другому
процессу. Какой из дескрипторов вы будете передавать, зависит от того, что должен де-
лать процесс посредством данного канала - посылать (записывать) или получать (читать)
информацию. Вы можете передать дескриптор дочернему процессу с помощью его ко-
мандной строки или воспользовавшись стандартными дескрипторами ввода/вывода. Не-
связанные процессы должны передавать друг другу дескрипторы иными средствами,
например с помощью механизма DDE или посредством совместно используемого файла.
Связь по анонимному каналу проще реализовать в том случае, если процессы связаны
друг с другом [12].
Создание именованных каналов. Многим системным объектам Windows NT при-
сваиваются идентифицирующие их строковые значения. Хотя команды создания имено-
ванных каналов могут компилироваться только в среде Windows NT, приложения, ис-
пользующие именованные каналы, работают и в среде Windows 98, где именованные
каналы функционируют точно так же, как и анонимные. Преимущество применения
именованных каналов заключается в том, что имена каналов позволяют процессам более
простым способом локализовать эти объекты. Анонимные объекты распознаются только
по своим дескрипторам, а дескрипторы действительны лишь в том процессе, где они
были созданы. С другой стороны, любой процесс, который знает имя объекта, может с
помощью операционной системы определить его место в иерархии. По имени система
может найти любой объект на другом сетевом компьютере.
Если канал имеет имя, программа-клиент не должна ожидать, когда сервер пере-
даст ей дескриптор этого канала. Вместо этого клиент может запросить дескриптор, вы-
звав функцию CreateFile() или CallNamedPipe(). В любом случае клиенту достаточно
знать имя канала. Родитель посредством командной строки может передать своему до-
чернему процессу строковое значение, содержащее имя канала. Кроме того, это значе-
ние может передаваться от одного произвольного процесса другому не связанному с
ним процессу с помощью совместно используемого файла или механизма DDE. Однако
чаще всего процессы, совместно использующие именованный канал, создаются одним и
тем же разработчиком, поэтому они заранее знают имя канала.
Перечисленные ниже функции работают только с именованными каналами (не
создавайте анонимных каналов, если вы хотите воспользоваться операциями, которые
выполняются с помощью этих функций):
CallNamedPipe()
ConnectNamedPipe()
CreateFile()
DisconnectNamedPipe()
GetNamedPipeHandleState()
GetNamedPipeInfo()
RevertToSelf()
SetNamedPipeHandleS-
tate()
TransactNamedPipe()
CreateNamedPipe()
ImpersonateNamedPipeClient() WaitNamedPipe()
Рассмотрим основные функции более подробно. Для создания именованных кана-
лов предназначена функция CreateNamedPipe():
HANDLE CreateNamedPipe(LPTSTR lpszPipeName, // строка с именем нового канала
DWORD fdwOpenMode,
DWORD fdwPipeMode,
DWORD dwMaxInstances,
DWORD dwOutBuf,
DWORD dwInBuf,
DWORD dwTimeout,
// доступ, перекрытие и сквозная запись
// тип, режимы чтения и ожидания
// максимальное число экземпляров
// размер выходного буфера, байты
// размер входного буфера/байты
// время паузы, миллисекунды
LPSECURITY_ATTRIBUTES lpsa);
// привилегии доступа
Поскольку именованные каналы имеют больше возможностей по сравнению с ано-
нимными каналами, функция CreateNamedPipe() имеет больше параметров, чем функция
CreatePipe(). Рассмотрим значения параметров этой функции.
Параметр lpszPipeName указывает на строковое значение, которое будет играть
роль имени нового объекта. Операционная система записывает это имя в иерархическую
структуру имен системных объектов. Строки, содержащие имя канала, должны иметь
следующий синтаксис:
\\.\рiре\< имя канала>
Первый символ обратной косой черты указывает на корневой узел иерархической
структуры имен системных объектов. Еще три обратные косые разделяют имена после-
дующих узлов. Точка (.) обозначает локальный компьютер. Хотя каналы могут соеди-
няться с клиентами других сетевых серверов, новый объект-канал всегда появляется на
том локальном сервере, где он был создан.
После имени сервера указывается узел, который называется pipe. Здесь содержатся
имена всех каналов, расположенных на локальном компьютере. В строке имени про-
граммно задается только один фрагмент, а именно <имя канала>. Эта подстрока должна
содержать не более 256 символов. Регистр при наборе символов не имеет значения, по-
скольку имена объектов не чувствительны к нему. И серверы, и клиенты для представ-
ления локального компьютера используют точку (.), однако клиент, который хочет от-
крыть канал на удаленном сервере, должен знать имя этого сервера. Один из способов
определения имени удаленного сервера. Заключается в получении списка имен с помо-
щью функций WNetOpenEnum(), WNetEnumResource() и WNetCloseEnum(), однако де-
лается это очень медленно.
Функции CreateNamedPipe(), CreateFile(), WaitNamedPipe() и CallNamedPipe() тре-
буют, чтобы в качестве параметра было задано имя канала.
Параметр fdwOpenMode, содержит комбинацию флагов, которые задают некото-
рые атрибуты канала. Наиболее важным атрибутом является режим доступа, опреде-
ляющий направление передачи информации в канале. Параметр fdwOpenMode должен
содержать один из трех возможных флагов доступа.
PIPE_ACCESS_OUTBOUND - Сервер только записывает, а клиент только читает ин-
формацию.
PIPE_ACCESS_INBOUND - Сервер только читает, а клиент только записывает инфор-
мацию.
PIPE_ACCESS_DUPLEX - Сервер и клиент могут как записывать, так и читать инфор-
мацию.
Еще два флага этого параметра являются необязательными.
FILE_FLAG_WRITE_THROUGH - Запрещает буферизацию в сети.
FILE_FLAG_OVERLAPРЕD - Разрешает асинхронные операции чтения и записи.
С целью повышения эффективности работы канала при соединении с удаленным
компьютером система старается не сразу же отсылать каждое сообщение. Она, наоборот,
пытается накопить несколько коротких сообщений в буфере и передать их по каналу за
одну операцию. Если проходит определенное время, а буфер остается заполненным
лишь частично, система все же передает его содержимое. Флаг
FILE_FLAG_WRITE_THROUGH запрещает системе производить буферизацию каждое
сообщение будет отправляться немедленно, а команды записи не завершатся до тех пор,
пока не будут переданы их выходные значения. Буферизацию целесообразно отключать
в том случае, если вы собираетесь посылать сообщения лишь изредка.
Операции записи и чтения обычно выполняются медленно, поскольку работа осу-
ществляется с физическими устройствами. Второй необязательный флаг,
FILE_FLAG_OVERLAPPED, позволяет командам чтения и записи завершаться, в то
время как инициированные ими действия будут продолжаться в фоновом режиме (асин-
хронный ввод-вывод, рассмотренный ранее).
Параметр fdwPipeMode содержит еще одну комбинацию флагов, позволяющую
задать другой набор характеристик канала: флаг режима чтения, флаг режима ввода и
флаг ожидания. Флаги режима чтения и режима ввода тесно связаны друг с другом - бо-
лее удобно было бы назвать их соответственно флагом чтения и флагом записи. Они со-
вместно определяют принципы организации и интерпретации данных, передаваемых по
каналу. Оба флага допускают выбор между байтовым режимом и режимом сообщений.
К флагам ввода (режим записи) относятся
PIPE_TYPE_BYTE и
PIPE_TYPE_MESSAGE, а к флагам режима чтения - PIPE_READMODE_BYTE и
PIPE_READMODE_MESSAGE.
В каналах, работающих в байтовом режиме, информация записывается и читается в
обычном двоичном виде и воспринимается просто как последовательность байтов.
Однако иногда бывает удобным разбить передаваемую по каналу информацию на
дискретные сообщения, в которых результат каждой операции записи соответствует от-
дельному сообщению. Каналы, работающие в режиме сообщений, автоматически и не-
заметно для пользователя предваряют каждое новое сообщение невидимым заголовком,
задающим длину сообщения. Заголовок обеспечивает автоматическую остановку опера-
ции чтения при достижении конца сообщения. Получатель читает сообщения одно за
другим точно в том виде, в котором они записывались в канал.
Так, если одна программа посылает другой длинную последовательность целых
чисел, ей целесообразно выбрать байтовый режим канала, поскольку для получателя не
имеет значения, сколько целых чисел будет передано в канал в течение одной операции
записи. Он просто поочередно читает целые числа из канала. Однако если Программа
посылает команды, написанные на языке сценариев, адресат должен получать эти ко-
манды целиком, в том виде, в котором они были записаны, чтобы правильно произвести
их Синтаксический анализ. Поскольку разные команды могут иметь различную длину,
обе программы должны использовать канал, работающий в режиме сообщений.
Режимы записи и чтения назначаются независимо друг от друга, однако не все
комбинации являются допустимыми. В частности, нельзя одновременно использовать
флаги PIPE_TYPE_BYTE и PIPE_READMODE_MESSAGE. Канал байтового типа осу-
ществляет запись информации, не разделяя сообщения заголовками, поэтому на прием-
ном конце канала эта информация не может быть разбита на отдельные сообщения. С
другой
стороны,
комбинация
флагов
PIPE_TYPE_MESSAGE
и
PIPE_READMODE_BYTE является вполне допустимой. В этом случае программа-
отправитель записывает в канал сообщения, сопровождая их заголовками, однако полу-
чатель игнорирует эти заголовки, принимая данные в виде последовательности неразде-
ленных байтов (иными словами, получатель не воспринимает невидимые заголовки со-
общений).
Наряду с флагами режимов записи и чтения параметр fdwPipeMode может содер-
жать еще один флаг, устанавливающий для канала режим ожидания. Режим ожидания
определяет, что произойдет с каналом, если какие-то условия временно воспрепятству-
ют завершению команды. Например, при попытке прочитать информацию из пустого
канала некоторые команды должны забыть о незавершенной операции чтения и перейти
к выполнению следующих операторов, в то время как другие программы, прежде чем
продолжить работу, должны дождаться поступления считываемого сообщения.
По умолчанию каналы заставляют читающие потоки блокироваться и ожидать по-
ступления сообщения, однако вы можете предотвратить их блокировку, добавив в пара-
метр fdwPipeMode флаг PIPE_NOWAIT. По умолчанию указанный параметр содержит
флаг PIPE_WAIT. Режим ожидания влияет как на команды записи, так и на команды
чтения. Программа, пытающаяся записать информацию при заполненном буфере канала,
обычно блокируется до тех пор, пока другая программа не освободит место в буфере,
прочитав сообщение на другом конце канала. Режим ожидания также влияет и на работу
сервера, который пытается соединиться с клиентом. Если функция ConnectNamedPipe()
не обнаруживает клиента, готового к соединению, режим ожидания канала определяет,
будет ли команда ожидать готовности клиента, чтобы установить с ним соединение, или
же она немедленно завершится.
Параметр dwMaxInstances. Программа-сервер может открывать каналы для не-
скольких клиентов, однако она должна заранее знать, со сколькими клиентами ей при-
дется поддерживать связь. Вводить новое имя канала для каждого клиента неудобно.
Каким же образом клиенты будут заранее узнавать, какое имя канала они должны ис-