Утилиты
PC Magazine/RE logo
©СК Пресс 1996
PC Magazine June 11, 1996, p. 281

"Горячие" клавиши Windows 95

Грегори А. Уолкинг


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

Помните функцию Shortcut Key (быстрая клавиша) из диспетчера программ Program Manager (Windows 3.1), которая позволяла запускать программу нажатием комбинации клавиш? Вы, возможно, не знаете этого, но в Windows 95 также есть похожие оперативные клавиши, хот механизм их использования в лучшем случае неудобен, а в худшем - содержит ошибки и, кроме того, плохо документирован. (Очевидно, программисты Microsoft решили, что если они не могут довести функцию до ума, то не стоит привлекать к ней излишнее внимание.) В данной статье я поделюсь с вами добытыми мною сведениями о реализации "горячих" клавиш в программе Explorer и предложу утилиту, которая поможет эффективно использовать это средство, избегая некоторых имеющихс в нем ошибок и ловушек.

Самая серьезная проблема, связанная с реализацией оперативных клавиш в Windows 95, заключается в том, что Explorer в отличие от Program Manager не исключает дублирования "горячих" клавиш. Единственный признак, по которому можно обнаружить ошибку этого рода в Windows 95, - это странное поведение системы: если вы назначаете "горячую" клавишу и она активизирует различные действия, вероятно, в зависимости от фазы Луны или цвета ваших носков, значит, где-то в системе имеются дублированные "горячие" клавиши.

Самое неприятное то, что в Explorer не предусмотрено совершенно никакого способа, чтобы узнать, какие "горячие" клавиши используются и каким командам быстрого вызова они назначены. Для нахождени дубликатов придется открыть страницу Properties (свойства) для каждой команды быстрого вызова во всем дереве папок Start Menu (главное меню) и на "рабочем столе", а затем вручную составить список всех обнаруженных вами "горячих" клавиш - если в вашем распоряжении нет утилиты Hotkey Detective.

Hotkey Detective перечисляет все используемые в вашей системе "горячие" клавиши вместе с назначенными им командами, и запускает Explorer в случае, если вы захотите внести изменения. Поскольку список упорядочен по именам "горячих" клавиш, любые элементы с дублированными назначениями будут сгруппированы вместе. Помимо этого, Hotkey Detective автоматически обновляет список, отображая в нем все сделанные вами изменения, он может также распечатать список "горячих" клавиш. Вы можете получить утилиту Hotkey Detective по почте или загрузить ее из электронной информационной службы PC Magazine Online. Те, кто интересуется, как работает программа, могут также загрузить из сети исходный текст для компилятора Microsoft Visual C++, версия 4.0.

Использование "горячих" клавиш в Windows 95

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

Во-первых, следует оговориться, что в Windows 95 дл обозначения понятия, известного всем специалистам в области компьютеров как hotkey ("горячая" клавиша), используется термин shortcut key (клавиша быстрого вызова, системная абббревиатура). Я предпочел сохранить верность традиционной терминологии, поскольку термин shortcut используется в Windows 95 слишком часто дл обозначения множества различных понятий, относящихс как к использованию Windows, так и к подготовке Windows-программ. В данной статье я использую термин "аббревиатура ", "ярлык" (shortcut), когда речь идет о файле с расширением LNK (или PIF в контексте DOS-программ), используемом утилитой Explorer дл указания на другую программу, документ или папку, и термин "горячая клавиша" (hotkey) для обозначени комбинации клавиш, которые служат в Explorer дл активизации команд быстрого вызова (КБВ).

Program Manager и Explorer схожи между собой в том, что обе программы позволяют ставить "горячие" клавиши в соответствие КБВ (в Program Manager КБВ называютс программными элементами - program items). В обеих оболочках "горячие" клавиши реализованы с помощью внутренней таблицы. Но на этом сходство заканчивается. В ряде важных моментов Program Manager дает больше возможностей для такой реализации. Она позволяет использовать более широкий набор комбинаций клавиш, нежели Explorer, и достаточно интеллектуальна, чтобы предотвратить назначение одной "горячей" клавиши двум разным командам. Диспетчер программ также обладает более совершенными средствами для обслуживания своей внутренней таблицы "горячих" клавиш при изменении ранее сделанных назначений.

В Explorer команды быстрого вызова могут существовать в двух формах. Когда вы формируете КБВ дл исполнимого файла, документа или папки Windows, Explorer создает файл с расширением LNK. При генерации КБВ для исполнимого DOS-файла Explorer создает файл с расширением PIF. Обратите внимание, что для PIF-файлов КБВ не существует. Хотя при использовании Explorer кажется, что вы можете сформировать такую команду, на самом деле он создает дубликат PIF-файла и помещает его в нужный каталог. Другими словами, вы никогда не встретите LNK-файл, указывающий на PIF-файл.

Explorer может назначить "горячую" клавишу почти любой КБВ в вашей системе. Однако он позволит вам использовать "горячую" клавишу только в том случае, если связанная с ней команда быстрого вызова находитс либо непосредственно на вашем "рабочем столе", либо где-то внутри дерева папок вашего Start Menu. В утилите Hotkey Detective перечисляются лишь те "горячие" клавиши, которые в действительности используются в Explorer. Нажатие на назначенную "горячую" клавишу запускает или активизирует соответствующую команду быстрого вызова независимо от того, в каком месте среды Windows вы находитесь - даже если вы выполняете прикладную программу DOS на полном экране. Это значительное улучшение по сравнению с Windows 3.1, где прежде, чем "горячая" клавиша сможет запустить программу, необходимо было открыть окно программы Program Manager. Если окно Program Manager не было активным, то "горячая" клавиша могла активизировать только выполняемую в данный момент программу. Возможно, фирма Microsoft позаимствовала идею из утилиты Дугласа Боулинга HK.EXE (см. PC Magazine, October 12, 1993), которая наделила Windows 3.1 такой функциональной возможностью.

Единственный тип КБВ, к которому нельзя получить доступ с помощью "горячей" клавиши, - это прикладна DOS-программа, настроенная для выполнения в режиме MS-DOS, поскольку этот режим требует закрытия всех других прикладных программ и (в ряде случаев) перезапуска системы. Если вы настроили прикладную программу для выполнения в режиме MS-DOS, то ее поле выбора клавиши быстрого вызова Shortcut Key будет заблокировано.

Чтобы назначить команде быстрого вызова "горячую" клавишу, нужно щелкнуть на системной аббривиатуре правой клавише мыши и выбрать пункт Properties (cвойства) из его контекстного меню. Щелкните на закладке Shortcut (для Windows-программ) или на закладке Program (для DOS-программ), затем - на поле клавиши быстрого вызова Shortcut Key. Нажмите нужную вам клавишу, затем щелкните на кнопке OK. Если вы хотите отменить назначение "горячей" клавиши, нажмите клавиши Del, Spacebar или Backspace после активизации поля Shortcut Key.

Этот метод также срабатывает, когда нужно назначить "горячую" клавишу непосредственно для исполнимого файла DOS (COM, EXE или BAT). При выполнении такого назначения Explorer автоматически создает в этой же папке новый файл сокращателей (PIF), в названии которого стоят слова "Shortcut to" с последующим именем исходного файла. Для прикладных программ Windows вам необходимо создать команду быстрого вызова прежде, чем вы получите возможность назначить ей "горячую" клавишу.

Вы можете получить подсказку об использовании пол Shortcut Key, щелкнув на нем правой клавишей мыши, а затем выбрав пункт What's This? (Что это?) из контекстного меню. Всплывающая на экране подсказка сообщит вам полезную информацию о том, как выполнить назначение клавиши, но грешит серьезными неточностями, когда дело касается правил, определяющих разрешенные комбинации клавиш. На самом деле существуют два совершенно различных набора правил, один дл PIF-файлов, используемых DOS-программами, а другой дл LNK-файлов, используемых Windows-программами.

Для PIF-файлов вы можете использовать почти любую нужную вам комбинацию клавиш при условии, что в нее входит хотя бы одна клавиша-модификатор (Shift), (Ctrl) или (Alt). Если в момент, когда вы выполняете назначение, включен режим NumLock, то вы можете использовать клавиши на цифровой клавиатуре сами по себе, без каких-бы то ни было модификаторов. Однако не слишком увлекайтесь, поскольку некоторые комбинации могут привести к серьезным осложнениям. Например, вы имеете полное право назначить комбинацию Shift-A в качестве "горячей" клавиши для PIF-файла. К сожалению, при этом всякий раз, когда вы введете с клавиатуры заглавную букву A с использованием клавиши Shift, программа Explorer будет перехватывать нажатие на клавишу и активизировать соответствующую КБВ. Важно помнить, что после того, как однажды вы связали "горячую" клавишу с КБВ, эта комбинация клавиш становится недоступной для любой другой программы, даже для самого Explorer!

Когда вы назначаете "горячую" клавишу для LNK-файла, начинают происходить странные вещи. Если базова клавиша не находится на цифровой клавиатуре и режим NumLock не включен, то Explorer пытается навязать модификаторы Ctrl и Alt. Например, нажатие клавиши Ctrl неминуемо приводит к комбинации Ctrl- Alt, так же, впрочем, как и нажатие одной клавиши Alt. В Explorer допускается назначение клавиш смены регистра (NumLock, ScrollLock и CapsLock) в качестве базовых, даже несмотря на то, что такие комбинации, будучи назначенными, редко работают должным образом. По-видимому, единственное последовательно соблюдаемое правило состоит в том, что ваша комбинация "горячих" клавиш не может содержать клавиши Spacebar, Tab, Enter, Esc, PrintScrn, Backspace и Pause.

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

Это действительно так - вы можете назначить одну и ту же "горячую" клавишу всем без исключения КБВ в вашей системе. И тогда будет практически невозможно предсказать, какая команда будет запущена совместно используемой "горячей" клавишей. К счастью, утилита Hotkey Detective обнаружит эту ошибку и позволит вам исправить ее.

Использование утилиты Hotkey Detective

Утилита Hotkey Detective состоит из единственного исполнимого файла, HKD.EXE. Она не требует никаких дополнительных модулей, подключаемых во врем выполнения, или дополнительных файлов. Для ее инсталляции требуется лишь поместить файл HKD.EXE в выбранный вами каталог и при желании создать для него команду быстрого вызова в папке Start Menu. Не играет роли, включен ли выбранный вами для инсталляции каталог в предложении PATH или нет.

После запуска утилита Hotkey Detective просматривает папку "рабочий стол" и целиком дерево папок Start Menu, извлекая информацию о назначенных "горячих" клавишах из всех встреченных ею LNK- и PIF-файлов. Она представляет упорядоченный в алфавитном порядке список найденных назначений в окне Key List. Если утилита обнаруживает дублированные назначения "горячих" клавиш, то на экран рядом с окном выводится соответствующее сообщение и первое из дублированных сообщений в списке выделяется.

Окно Key List выполняет две функции: показывает, с какими КБВ ассоциированы ваши горячие клавиши, и помогает находить сами команды. Когда вы выбираете из списка название клавиши, в расположенном в нижней части окна поле Shortcut Filename (имя файла КБВ) появляетс полное имя файла соответствующей команды. Если длина имени файла слишком велика для того, чтобы уместиться в текстовом поле, то вы можете изменить размеры диалогового окна и органы управления автоматически подстраиваются.

Для изменения назначения "горячих" клавиш можно дважды щелкнуть мышью на имени клавиши, или выбрать им клавиши и либо щелкнуть на кнопке Locate with Explorer (найти с помощью Explorer), либо нажать клавишу Enter. Затем Hotkey Detective запускает Explorer в разделенной на два подокна области просмотра. Выбранная (и готова к редактированию) КБВ находится в правом подокне, а соответствующий участок дерева папок открывается в левом подокне. Обратите внимание, что Hotkey Detective запускает отдельный экземпляр программы Explorer всякий раз, когда вы используете эту функцию. Таким образом, ваш экран примет намного более аккуратный вид, если вы будете закрывать Explorer после внесения каждого изменения и перед использованием утилиты Hotkey Detective с целью отыскания новой КБВ для последующего редактирования.

Существуют две причины, по которым оказываетс предпочтительным передать функцию внесения изменений программе Explorer, нежели выполнить ее внутри Hotkey Detective. Первая состоит в том, что структура PIF- и LNK-файлов недокументирована. Мне потребовалось много часов кропотливой работы с двоичным редактором, чтобы выяснить, как хранится информация о "горячих" клавишах, поэтому было бы рискованно (чтобы не сказать - глупо) непосредственно редактировать эти файлы. Вторая причина состоит в том, что даже если бы я располагал точными сведениями о структуре файлов и был уверен, что смогу изменить их, избежав нежелательных последствий, то я не смог бы заставить Explorer заново прочитать измененные файлы и обновить внутреннюю таблицу "горячих" клавиш. Передача обязанностей по внесению изменений программе Explorer благополучно разрешает обе трудности.

Когда вы завершите процесс редактирования и Explorer запишет обновленные PIF- и LNK-файлы на диск, утилита Hotkey Detective позаботится о внесении изменений в файловую систему и автоматически обновит список "горячих" клавиш. Эта функция использует отдельный процесс, называемый рабочим (worker) потоком, дл слежения за состоянием вашей файловой системы (более подробно об этом будет рассказано позже). Рабочий поток, выполняющий эту функцию, почти не занимает времени процессора, пока ожидает внесения изменений, поэтому он не окажет заметного влияния на общую производительность системы. Однако запускаемый после обнаружения изменения процесс просмотра файлов подвергает диск довольно серьезной нагрузке. Если вы внесли в Start Menu значительные изменения и пришли к выводу, что постоянная активность утилиты Hotkey Detective чересчур замедляет работу вашей системы, то можете отключить эту функцию, сбросив флажок проверки Automatic Refresh (автоматическое обновление). (Обратите внимание, что Automatic Refresh работает, даже если главное окно свернуто.) Повторное включение режима Automatic Refresh вызывает немедленное обновление списка. При завершении работы Hotkey Detective сохраняет установочные параметры Automatic Refresh и восстанавливает их при следующем запуске программы.

Нажатие кнопки Refresh Key List (обновить список клавиш) заставляет утилиту Hotkey Detective немедленно выполнить поиск в вашей системе независимо от того, включен или нет режим автоматического обновления. Заметьте, что пока идет обновление, эта кнопка будет блокирована и вам не удастся запустить процедуру обновления вручную в ходе автоматического процесса.

Я ввел эту функцию в программу, поскольку при некоторых необычных обстоятельствах не исключаетс возможность, что Hotkey Detective пропустит изменение файла в процессе обновления списка "горячих" клавиш. Это происходит из-за компромисса, обусловленного способом, который применяется в Windows для выдачи сообщений об изменениях, которые вносятся в файловую систему. Некоторые действия могут повлечь за собой ряд связанных с внесением изменений событий, следующих друг за другом в быстрой последовательности. Например, перемещение КБВ из одной папки в другую вызывает четыре разных события, связанных с внесением изменений: создание нового файла, изменение целевой папки, удаление первоначального файла и изменение в исходной папке. Системные функции, которые отслеживают изменения, не могут сообщить, какие изменени произошли, и лишь ставят в известность о том, что они были произведены. В результате, когда происходит изменение, утилита Hotkey Detective должна заново прочитать все области файловой системы, на которых это изменение могло отразиться.

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

Кнопка Print Key List (печать списка клавиш) выполняет именно ту функцию, на которую указывает ее название. Щелкнув мышью, вы увидите стандартный диалог вывода информации на печать, в ходе которого выбираетс устройство для распечатки списка. После щелчка на кнопке OK появляется диалоговое окно Select Print Size (выбор кегля). Принимаемый по умолчанию кегль - 10, но при желании вы можете выбрать и иную высоту литер (в диапазоне от 8 до 24) из ниспадающего списка. Щелчок на кнопке Cancel (отмена) в любом из этих диалоговых окон отменяет всю процедуру печати.

Чрезвычайно важно отметить, что информация, предоставляемая утилитой Hotkey Detective, отображает установки ваших "горячих" клавиш в том виде, в котором они хранятся на диске. Внутренняя таблица "горячих" клавиш Explorer может не точно соответствовать этим параметрам. Если вы заметили, что ваши "горячие" клавиши не активизируют некоторые выводимые на экран утилитой Hotkey Detective КБВ, не поддавайтесь панике. Просто завершите работу на своем компьютере и перезапустите его. Требуется полный перезапуск; нельз использовать недокументированную функцию "перезапуска из памяти" ("warm boot"), вызываемую после выбора пункта Restart Your Computer (перезапуск вашего компьютера) при нажатой клавише Shift.

Hotkey Detective изнутри

Я составил утилиту Hotkey Detective, использу компилятор Microsoft Visual C++, версия 4.0, и библиотеку базовых классов Microsoft Foundation Classes, версия 4.0. Если вы загрузили из сети исходный текст программы, то переместите файлы из ZIP-архива в их собственный каталог, используя при распаковке ключ -d, для того чтобы сохранить структуру каталогов.

В ходе работы над утилитой Hotkey Detective мне пришлось решить две основные проблемы. Наиболее важна из них состояла в определении, где и как Explorer хранит параметры "горячих" ключей. Я не обнаружил их в системном (реестре) Registry, но зато подобрал ключ к этой проблеме. Просматривая установочные параметры реестра (в HKEY_CLASSES_ROOT) в поисках файлов типа LNK и PIF, я обнаружил, что Explorer использует два различных обработчика: Context Handler (обработчик контекста) для LNK-файлов и Property Sheet Handler (обработчик списка свойств) для PIF-файлов. Именно эти обработчики выводят на экран всплывающее меню, когда вы щелкаете правой клавишей мыши на КБВ, а также диалоговое окно Shortcut Properties (свойства КБВ) при выборе пункта Properties (свойства) из всплывающего меню. "Ага, - подумал я, - "Горячие" клавиши должны храниться в самих файлах". Корпорация Microsoft рассматривает данные о структуре этих файлов как информацию для служебного пользования, поэтому предпринял небольшое самостоятельное детективное расследование.

Вся работа сводилась к изнурительной процедуре: сделать копию файла КБВ, изменить пареметр, сравнить два файла, чтобы выяснить, что изменилось (очень похоже на то, что я проделывал при написании моей последней утилиты, RecEdit). Как выяснилось, LNK-файлы содержат информацию о "горячих" клавишах в двух байтах, имеющих смещение 40h от начала файла. Первый байт содержит код "виртуальной клавиши"; второй - маску, указывающую, какие модификаторы наложены на данную клавишу (разряд 0 представляет клавишу Shift, разряд 1 - клавишу Ctrl, а разряд 2 - клавишу Alt; разряд 3 используется дл идентификации расширенных клавиш, таких, как клавиши управления движением курсора в средней секции 102-клавишных клавиатур). Если КБВ не поставлена в соотвествие никакая "горячая" клавиша, то оба байта будут содержать нулевые значения.

Декодирование PIF-файлов оказалось несколько более сложным. Начать с того, что я обнаружил в своей системе три различных типа PIF-файлов, имеющих разные размеры: 545, 967 и 995 байт. Самые короткие - это старые PIF- файлы, унаследованные от Windows 3.x. Каждый раз, когда я вносил какие- нибудь изменения в эти PIF-файлы, Explorer переписывал их одним из файлов другого размера.

Я определил, что PIF-файлы содержат информацию о "горячих" клавишах, используя три байта в одном из двух возможных наборов несмежных ячеек памяти. Первый байт (1B5h или 1D1h) - клавиатурный код. Второй байт (1B7h или 1D3h) содержит маску, в которой биты 0 и 1 представляют левую и правую клавиши Shift (несмотря на то, что Explorer не отличает правую клавишу от левой), бит 2 представляет клавишу Ctrl, а бит 3 - клавишу Alt. Третий байт (1BBh или 1D7h) содержит 1, если клавиша представляет собой расширенную клавишу, и 0, если нет. Я не обнаружил никакой связи между размером файла и используемыми смещениями. В некоторых длинных файлах горячие клавиши располагались в ячейках с меньшим смещением, а в некоторых коротких файлах - в ячейках с большим смещением.

Я заметил, что четыре старших разряда правильно организованного байта маски всегда содержат 0, а другое возможное местоположение всегда было в середине строки ASCII-текста, не содержащей управляющих символов (значений, меньших, чем 20h). Таким образом, я понял, что могу определить, в каких ячейках содержатс параметры, методом исключения. Для того чтобы найти верное положение ячеек для конкретного файла, проверял два смещения с масками и использовал тот набор, в котором четыре старших бита были нулевыми. Этот прием может показаться волюнтаристским и похожим на неуклюжую заплату, но он работал!

Автоматическое определение

Следующей важной задачей было обеспечить своевременную передачу в Hotkey Detective информации о текущих событиях. Windows - многозадачная среда, в которой пользователь может изменить установки "горячих" клавиш и даже полностью реорганизовать Start Menu и папки "рабочего стола" во время работы утилиты Hotkey Detective. Я мог бы предоставить пользователю возможность вручную обновлять диалоговое окно, но этот метод показался мне неуклюжим: в конце концов, предназначение компьютера состоит как раз в том, чтобы автоматизировать выполнение рутинных задач, подобных этой. Поскольку существует множество различных операций, которые могут повлиять на пользовательские установки "горячих" клавиш, я должен был найти способ, позволяющий утилите Hotkey Detective распознавать эти изменения и автоматически обновлять собственную информацию.

Я достиг этой цели, использовав несколько потоков. При включенном режиме автоматического обновления (при загрузке, или при установке пользователем флажка Automatic Refresh) утилита Hotkey Detective всегда запускает отдельный поток, выполняемый в фоновом режиме и отслеживающий изменения в определенных местах файловой системы. В частности, он фиксирует следующие события:

Так как для этого потока не нужно прямое взаимодействие с пользователем, я сделал его рабочим потоком, а не потоком пользовательского интерфейса. Основное различие между двумя типами потоков состоит в том, что рабочий поток не имеет подкачки сообщений (message pump - функции, требующейся для обработки стандартных сообщений пользовательского интерфейса Windows, таких, как сообщения о нажатиях на клавиши и щелчки мышью), потому что он не взаимодействует непосредственно с пользователем. Рабочий поток обычно выполняет специфическую задачу в фоновом режиме, в то время как главная прикладная программа продолжает взаимодействовать с пользователем. Эта задача обычно односторонняя: она не требует никакой реакции со стороны пользователя, и после завершения задачи рабочий поток автоматически завершает свою работу. Рабочие потоки часто используются для задач, подобных функции автоматического сохранения информации текстового процессора.

Рабочий поток утилиты Hotkey Detective несколько необычен, поскольку момент завершения его работы неопределен: он должен продолжать отслеживать состояние файловой системы до тех пор, пока не получит команду на завершение работы от главной прикладной программы. При проектировании рабочего потока наибольшие трудности вызвало нахождение эффективного метода его завершени из главной прикладной программы. Это критически важно, так как если рабочий поток будет продолжать свою работу после завершения основного потока прикладной программы, то он будет напрасно занимать память системы. Рабочий поток также должен иметь возможность передавать информацию в главное диалоговое окно после обнаружени изменений в файловой системе. Чтобы обеспечить такую возможность, я объявил глобальную управляющую функцию рабочего потока, Watch_File_System(), как "друга" класса CHKDDlg (см. лист. 1). Как вы, наверное, догадались, управляющая функция определяет, какую задачу будет решать поток. Когда управляющая функци передает управление вызывающей программе, поток завершается.


Лист. 1. Управляющая функция рабочего потока. UINT Watch_File_System(LPVOID pParam) { // Получить указатель на объект главной // прикладной программы, переданный через Afx BeginThread. CHKDDlg* pThis = (CHKDDlg*) pParam; HANDLE hChanges[3] = (NULL, NULL, NULL); DWORD wait_status; // Удостовериться в том, что диалог был своевременно обновлен. pThis -> OncmdRefresh(); // Получить дескриптор mutex-объекта. hChanges[2] = pThis->hmy_mutex: while (TRUE) { // Создать дескрипторы для изменений в файловой системе, // которые нам необходимо отслеживать. Во-первых, только // изменения в файлах папки "рабочего стола". hChanges[0] = FindFirstChangeNotification (pThis -> desktop.folder, FALSE, FILE_NOTIFY_CHANGE_FILE_NAHE | FILE_NOTIFY_CHANGE_LAST_WRITE); // Во вторых, изменения в файлах и папках во // всем дереве папок, корень которого находитс // в папке Start Menu. hChanges[1] = FindFirstChangeNotification (pThis -> start_menu_folder, TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE); // Войти в бесконечный цикл ожидания; Windows // поставит нас в известность, когда произойдет // любое из указанных событий. wait_status = WaitForMultipleObjects(3, hChanges, FALSE, INFINITE); // Закрыть дескрипторы ожидания файловой системы. FindCloseChangeNotification(hChanges[0]); FindCloseChangeNotification(hChanges[1]); // Если функция ожидания не завершилась неудачей. if ((wait_status < WAIT_ABANDONED_0) && (wait_status != WAIT_FAILED)) { // Выяснить, какой объект удовлетворил условию ожидания. if ((wait_status - WAIT_OBJECT_0) == 2) // Если это был mutex-объект, закончить. break; else; { // В противном случае удостовериться, что главный // диалог не занят. while (!pThis->m_cmdRefresh.IsWindowEnabled()) Sleep(100); // Передать ему указание на обновление. pThis -> Oncmd Refresh(); } } else { // Если функция ожидания завершилась неудачей, // закончить выполнение самостоятельно и // удостовериться, что главная прикладна // программа знает о том, что поток не выполняется. pThis->m_chkAutoScan.SetCheck(0); pThis->my_thread = NULL; break; } } // Отказаться от mutex-объекта ReleaseMutex(hChanges[2]); return 0; }
Функция Launch_Worker() (см. лист. 2) выполняет работу по запуску рабочего потока. Я начал с формирования mutex-объекта (mutual exclusive access - взаимно исключающий доступ). Объект с взаимно исключающим доступом обеспечивает простой и эффективный механизм синхронизации выполнения потоков, так как в каждый данный момент времени он может принадлежать только одному процессу. Рабочий поток передает mutex-дескриптор (handle) вызову API WaitForMultipleObjects(). При отказе главного окна от mutex- объекта этот вызов API сигнализирует рабочему потоку о необходимости завершения работы.

Лист. 2. Функция Launch_Worker () создает поток слежения за состоянием файловой системы. void CHKDDlg::Launch_Worker(void) { // Сформировать mutex-объект и получить его дескриптор. // Параметр TRUE указывает, что данный процесс немедленно // получает объект в свое владение. hmy_mutex = Create_Mutex(NULL, TRUE, NULL); // Запустить поток. my_thread = AfxBeginThread(Watch_File_System, this, THREAD_PRIORITY_NORMAL, 0, 0, NULL); // Если запуск не удался, то функция AfxBeginThread () // передает в вызывающую программу нулевой указатель. if (my_thread == NULL) { CloseHandle(hmy_mutex); Do_Error_Message("Unable to launch file system monitor."); } }
Сформировав mutex-объект и сохранив дескриптор (hmy_mutex) объекта, так чтобы рабочий поток мог получить к нему доступ, я обращаюсь к функции AfxBeginThread() для создания рабочего потока, указыва Watch_File_System() в качестве управляющей функции потока. Поскольку я не установил флаг CREATE_SUSPENDED при вызове функции AfxBeginThread(), управляюща функция начинает выполняться немедленно.

Из функции Watch_File_System() я вызываю функцию диалога OncmdRefresh(), чтобы удостовериться в том, что диалог был своевременно обновлен, затем устанавливаю массив (hChanges[]) из трех дескрипторов дл использования функцией API WaitForMultipleObjects(). Я сделал третий элемент копией дескриптора mutex-объекта, сформированного родительским процессом, и затем вошел в бесконечный цикл while().

При каждом проходе цикла я делаю два первых элемента массива hChanges[] дескрипторами событий, связанных с изменениями файловой системы. Я передаю все три дескриптора в функцию API WaitForMultipleObjects(). Эта функция API передает управление вызывающей программе, как только удовлетворяется любое из условий ожидани или истекает оговоренный период времени. В данном случае я установил время ожидания бесконечным, чтобы свести к минимуму отнимаемое у процессора время. В результате рабочий поток большую часть времени пребывает в ожидании сообщения операционной системы, сигнализирующего об указанных событиях.

Когда функция WaitForMultipleObjects() передает управление вызывающей программе, я проверяю код возврата. Если его значение указывает на ошибку, то делаю указатель my_thread нулевым (NULL); таким образом главная прикладная программа уведомляется о том, что рабочий поток не выполняется, и прекращает работу. Если третий дескриптор удовлятворяет условию ожидани (показывая, что главный диалог отказался от владени mutex-объектом), то я прерываю цикл и выхожу из функции, завершая поток. Если же условию выхода из цикла удовлетворяет какой-либо из первых двух дескрипторов, то это значит, что функция ожидани обнаружила изменение в файловой системе. Если процесс обновления еще не начинался, то я вызываю элемент-функцию диалога OncmdRefresh(), щелкая на кнопке Refresh Key List (обновить список клавиш). Если в момент, когда в файловой системе произошло изменение, запущенная вручную операция обновления уже выполнялась, необходимо обновить диалог вторично, потому что неизвестно, выполнялась ли процедура сканировани каталогов в изменившейся области диска.

Когда пользователь отменяет режим автоматического обновления или закрывает диалог, необходимо завершить рабочий поток с помощью функции Kill_Worker() (см. лист. 3). Если указатель my_thread является нулевым (NULL), значит, поток не выполняется и функци Kill_Worker() просто передает управление вызывающей программе. В противном случае я передаю дескриптор рабочему потоку. Этот шаг очень важен, поскольку после завершения потока невозможен доступ к его дескриптору через объект.


Лист. 3. Функция Rill_Worker () завершает рабочий поток. void CHKDDlg::Kill_Worker(void) { HANDLE hThread; // Если указатель потока ненулевой, if (my_thread) { // получить дескриптор рабочего потока. hThread = my_thread->m_hThread; // Освобождение mutex-объекта передает управление им // рабочего потоку, что служит сигналом для его завершения. ReleaseMutex(hmy_mutex); // Используйте дескриптор потока, чтобы дождаться его // завершения. while (WaitForSingleObject(hThread, 500) == WAIT_TIMEOUT); // Установить указатель потока в нуль. mythread = NULL; // Удалить mutex-объект, закрыв его единственный дескриптор. CloseHandle(hmy_mutex); } }

На следующем шаге я обращаюсь к функции ReleaseMutex(), чтобы отказаться от владени mutex-объектом и подать рабочему потоку сигнал к завершению. Затем я передаю дескриптор потока функции WaitForSingleObject(), которая возвращает управление, когда поток завершается. Наконец, я делаю указатель my_thread нулевым, уведомляя прочие части программы о том, что рабочий поток более не выполняется, а затем удаляю mutex-объект, закрыв его дескриптор.

Я провел много часов, работая над этой программой, но это время не было потрачено впустую. По ходу дела узнал много нового о Windows 95, в особенности о мощных многопотоковых возможностях операционной системы. Я уверен, что Hotkey Detective станет полезным дополнением к вашему арсеналу утилит, и надеюсь, что использование утилиты доставит вам такое же удовольствие, какое я испытал, работая над ней.


ОБ АВТОРЕ: Грегори А. Уолкинг - главный системный оператор форума Utilities/Tips Forum сети ZD Net.