1. Новости, хорошие и разные

Проектируя операционную систему Windows 95, фирма Microsoft использовала весь опыт, накопленный ей при создании Windows версии 3.1 а также Windows NT. В результате операционной системе Microsoft Windows 95 присущи лучшие черты Microsoft Windows версии 3.1 и Microsoft Windows NT.

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

Программистам же предлагается создавать новые, 32-разрядные приложения, пользуясь системным интерфейсом Win32.

Что это означает для вас, как программиста, привыкшего к 16-разрядной Microsoft Windows? Не придется ли вам переучиваться заново, а также полностью переделывать свои наработки?

Спешим успокоить: перейти от 16- к 32-разрядному программированию гораздо проще, чем от программирования для DOS к программированию для Microsoft Windows. Хотя все же вам придется узнать много нового и привыкнуть к работе в сплошной модели памяти без использования сегментов и селекторов.

Тем не менее, затраченный на изучение Win32 труд полностью компенсируется, так как перед вами откроются новые возможности, ранее недостижимые или достижимые лишь с большим трудом.

Например, сплошная модель памяти позволит вам определять массивы и структуры любого размера, лишь бы они поместились в виртуальную память (реально это десятки и даже сотни мегабайт!). Такие популярные органы управления, как Toolbar , Statusbar, ProgressBar, движковые регуляторы и пр. создаются теперь на базе встроенных в систему классов. Новая файловая система позволяет задавать длинные имена файлов, избавляя пользователей от необходимости называть свои документы 8-буквенными сокращениями вроде OLDLET94.DOC. Стала доступной компонентная модель объектов СOM (Component Object Model), позволяющая приложениям использовать функции, экспортируемые другими приложениями и выполнять обмен данными между различными объектами. Кстати, сами приложения работают теперь в режиме вытесняющей мультизадачности и в изолированных адресных пространствах.

Словом, изменилось многое. Слишком многое, чтобы это можно было перечислить в одном абзаце.

1.1. Хорошие новости

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

Адресация памяти

В операционных системах Microsoft Windows 95 и Microsoft Windows NT для 32-разрядных приложений используется сплошная, несегментированная модель памяти (FLAT-модель ). Основанная на аппаратных особенностях процессоров Intel 80386, 80486 и Pentium, эта модель позволяет адресовать память при помощи 32-разрядного смещения.

Забудьте про сегментные регистры. Их содержимое равно нулю и вам нельзя работать с сегментными регистрами в своих приложениях (даже если вы создаете драйвер устройства).

Забудьте про ключевые слова NEAR и FAR . Сплошная модель памяти похожа на модель памяти TINY , в которой программа адресуется к данным и функциям только при помощи смещения без использования сегментов. Отличие, однако, заключается в том, что размер виртуального адресного пространства в операционных системах Microsoft Windows 95 и Microsoft Windows NT достигает 4 Гбайт.

Сказанное не означает, что любое приложение имеет доступ ко всей памяти. Так как процессор работает в защищенном режиме, адресация памяти выполняется с использованием страничной адресации, глобальной таблицы дескрипторов GDT , а также локальных таблиц дескрипторов LDT . А раз так, то операционная система может ограничить права обычных приложений, "не допуская" их к системной памяти и памяти других приложений (подробное описание схемы адресации памяти в защищенном режиме вы найдете в 6 томе "Библиотеки системного программиста").

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

Теперь о том, как используется виртуальное адресное пространство в операционной системе Microsoft Windows 95 (рис. 1.1).

IMG00001.GIF (4208 bytes)

Рис. 1.1. Распределение виртуального адресного пространства в операционной системе Microsoft Windows 95

Верхний гигабайт адресного пространства зарезервирован для использования операционной системой (неплохой запас, не правда ли?). В этой области располагаются виртуальные драйверы, работающие в нулевом кольце защиты, планировщик виртуальных машин, расширитель DOS (DOS extender ), а также сервер DPMI.

Системная область хорошо защищена от обычных приложений и программ DOS, так как последние работают в третьем кольце защиты.

Третий гигабайт виртуального адресного пространства (область совместного использования) нужна для создания блоков памяти, доступных одновременно из нескольких приложений. Из этой области выделяется память для 16-разрядных приложений Windows, а также память, полученная от сервера DPMI.

32-разрядные приложения также работают с этой областью. В частности, здесь находятся некоторые системные 32-разрядные DLL-библиотеки (KERNEL32.DLL , USER32.DLL , GDI32.DLL ), из этой области выделяется память для работы с отображаемыми файлами (об отображаемых файлах мы расскажем в разделе "Изменения в файловой системе"). Кроме того, область совместного использования нужна виртуальным драйверам для создания глобальных блоков памяти, доступных в любой момент времени.

Область, которая простирается от границы первых 4 Мбайт до границы 2 Гбайт содержит адресное пространство текущего 32-разрядного процесса (который в данный момент активен). Здесь размещается исполнимый код процесса, его данные и полученные динамически блоки памяти. Защита адресных пространств разных процессов достигается при помощи механизма страничной адресации.

И, наконец, первые четыре мегабайта виртуального адресного пространства зарезервированы для системы. К этой области имеют доступ 16-разрядные приложения Windows и программы DOS, причем текущая виртуальная машина располагается в пределах первого мегабайта. Остальные три мегабайта нужны для загрузки резидентных программ и драйверов DOS (теперь вряд ли вы теперь сможете пожаловаться на то, что для резидентных программ и драйверов выделяется слишком мало памяти). Заметим также, что первые четыре мегабайта виртуального адресного пространства недоступны для 32-разрядных приложений.

Реальная мультизадачность

Приложения Microsoft Windows версии 3.1 работали в режиме невытесняющей ("добровольной") мультизадачности, передавая друг другу управление в момент выборки сообщения из очереди функцией GetMessage . Этот способ не лишен недостатков, так как ошибка в одном из приложений могла привести к краху всей системы.

В среде Microsoft Windows 95 (и, разумеется, Microsoft Windows NT) организована вытесняющая мультизадачность , когда для работы приложениям выделяются кванты времени. Соответствующая подсистема основана на использовании аппаратных прерываний таймера, поэтому она устойчива к ошибкам приложений.

Однако самая приятная новость заключается в том, что теперь приложения могут создавать подзадачи. И эти подзадачи будут работать параллельно с создавшим их приложением и параллельно с другими приложениями.

В вашем распоряжении есть средства синхронизации задач (семафоры ), а также средства обмена данными между задачами и процессами (каналы и совместно используемая память ).

Все это облегчает создание сложных приложений, допускающих мультизадачную обработку.

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

Файловой системе DOS присущи многие недостатки, один из которых - ограничение длины имени файла. В операционной системе Microsoft Windows 95 используется так называемая таблица размещения файлов защищенного режима (Protected-mode FAT или VFAT ), которая позволяет использовать имена файлов длиной до 260 символов (включая двоичный нуль, закрывающий строку имени).

Одно из достоинств таблицы VFAT заключается в том, что она совместима с обычной таблицей FAT . При этом файлы и каталоги, имеющие длинные имена, видны и старым программам, правда, в несколько необычном виде.

Для программ, которые могут воспринимать только короткие имена файлов "в стандарте 8.3" файловая система Microsoft Windows 95 создает альтернативные имена (алиасы). Эти алиасы состоят из первых 6 символов длинного имени, вслед за которыми следует символ "тильда" (~) и число.

Вот, например, как выглядит содержимое каталога Program Files, если его распечатать при помощи команды DIR:

 Volume in drive C is MS-DOS_6   
 Volume Serial Number is 1E76-A1EB
 Directory of C:\Program Files
.              <DIR>    17.08.95  20:15 .
..             <DIR>    17.08.95  20:15 ..
PLUS!          <DIR>    17.08.95  21:47 Plus!
ACCESS~1       <DIR>    17.08.95  20:15 Accessories
THEMIC~1       <DIR>    17.08.95  20:18 The Microsoft Network
MICROS~1       <DIR>    17.08.95  20:17 Microsoft Exchange
Q        DOC         0  05.09.95  10:30 q.doc
         1 file(s)              0 bytes
         6 dir(s)      33 841 152 bytes free

Обратите внимание, что в правой части листинга выводятся полные имена файлов и каталогов.

Конечно, работать с алиасами неудобно. Тем не менее, на наш взгляд эта проблема в Microsoft Windows 95 решена лучше, чем в операционной системе IBM OS/2 Warp версии 3.0. Последняя вовсе не позволяет программам DOS работать с длинными именами файлов и каталогов, расположенных на дисках HPFS . Такие файлы и каталоги не видны, например, из программы Norton Commander for DOS. Поэтому в IBM OS/2 Warp версии 3.0 программы DOS имеют ограниченный доступ к разделам HPFS.

Другое, весьма существенное новшество, которое появилось в Microsoft Windows 95 (а также в Microsoft Windows NT) - файлы, отображаемые на память .

Программист может открыть файл таким образом, что его содержимое будет автоматически отображаться в виртуальное адресное пространство (в область совместного использования, имеющую размер 1 Гбайт). Далее приложение может выполнять операции чтения и записи в файл, просто работая с оперативной памятью через полученный от системы указатель. Таким образом, вы можете легко "превратить" файл в массив, расположенный в оперативной памяти.

Файл, отображаемый в память, можно сделать доступным одновременно для нескольких работающих параллельно приложений, организовав таким образом обмен данными между этими процессами.

Регистрационная база данных

Регистрационная база данных сама по себе не является чем-то новым - она существовала и раньше в Microsoft Windows версии 3.1. Есть она и в Microsoft Windows NT. Как правило, эта база использовалась приложениями, работающими с DDE и OLE . Такие приложения хранят в регистрационной базе данных различную информацию о себе, например, имя сервера.

Вся остальная информация о собственной конфигурации записывалась 16-разрядными приложениями в текстовые файлы с расширением имени ini, которые называются ini-файлами.

Размер системного файла WIN.INI ограничен величиной 64 Кбайт, поэтому приложения вынуждены создавать свои собственные ini-файлы. Большое количество ini-файлов вызывает путаницу (так как нет никакого стандарта на формат записанной там информации), усложняя или делая невозможной процедуру удаления (деинсталляции) ненужных приложений.

В операционных системах Microsoft Windows 95 и Microsoft Windows NT вместо ini-файлов практически всегда используется регистрационная база данных (хотя в каталоге Microsoft Windows 95 присутствуют файлы SYSTEM.INI и WIN.INI).

Регистрационная база данных имеет иерархическую структуру. Вы можете просмотреть или отредактировать ее содержимое при помощи приложения REGEDIT.EXE (рис. 1.2).

Рис. 1.2. Редактирование содержимого регистрационной базы данных

Позже мы научим вас работать с регистрационной базой данных. Пока только скажем, что это несложно, так как в составе Win32 имеются соответствующие функции.

При установке приложения в регистрационную базу данных среди всего прочего записываются сведения, необходимые для деинсталляции, поэтому пользователь сможет при необходимости легко удалить приложение.

Новые органы управления

Для того чтобы придать приложению современный вид, вы должны создать в его главном окне линейку инструментальных средств (орган управления Toolbar ), область для отображения сообщений (орган управления Statusbar ), а также обеспечить удобные средства просмотра списков (если приложение работает со списками). Если приложение выполняет какую-либо длительную операцию, необходимо отображать процент ее выполнения в наглядном виде. Эта задача выполняется обычно органом управления Progressbar . Существуют и другие элементы пользовательского интерфейса, нужные в большинстве приложений.

К сожалению, в программном интерфейсе операционной системы Microsoft Windows версии 3.1 не было предусмотрено никаких средств для создания перечисленных выше органов управления. Программисты были вынуждены создавать их самостоятельно на базе обычных окон, кнопок, диалоговых панелей и т. п. Это было непростым делом, так как каждый такой орган управления состоял из множества взаимодействующих между собой объектов.

Для упрощения процедуры создания приложений с такими органами управления, как Toolbar и Statusbar, широко используют библиотеки объектных классов Microsoft MFC и Borland OWL , а также автоматические генераторы исходного текста приложений, такие как AppWizard из пакета Microsoft Visual C++.

Тем не менее, для стандартизации внешнего вида и поведения приложений было бы неплохо, если бы операционная система содержала внутри себя все необходимые средства для создания элементов пользовательского интерфейса.

Очень хорошей новостью является то, что приложения Microsoft Windows 95 могут создавать сложные органы управления на базе предопределенных классов окна. Таким образом, теперь процедура создания таких органов управления, как Toolbar , Statusbar и Progressbar не намного сложнее процедуры создания кнопки или списка типа Combobox.

Многие из новых органов управления, создаваемых на базе предопределенных в Microsoft Windows 95 классов, вы можете увидеть в главном окне приложения Explorer (рис. 1.3), заменяющего приложения File Manager и Program Manager из Microsoft Windows версии 3.1.

Рис. 1.3. Новые органы управления в окне приложения Explorer

В верхней и нижней части главного окна вы можете заметить органы управления Toolbar и Statusbar. С ними вы знакомы по таким приложениям, как, например, Microsoft Word for Windows.

Обратите внимание на окно в левой части рис. 1.3. В этом окне отображается иерархическая структура пространства имен Microsoft Windows 95. Для отображения используется орган управления Tree view , созданный на базе предопределенного класса. Пользуясь этим органом управления, вы можете раскрывать или закрывать отдельные папки (каталоги), что очень удобно, если просматривается очень большое дерево. Вообразите себе, как было бы трудно создать такой орган управления с использованием только тех средств, которые есть в составе программного интерфейса Microsoft Windows версии 3.1!

Правая часть главного окна приложения Explorer также очень интересна. Она состоит из двух частей. Вверху находятся заголовки столбцов (Name, Size, Type, Modified), сделанные в виде органа управления Column Header . Заголовки столбцов можно передвигать мышью. Если же сделать по заголовку щелчок, данные в соответствующем столбце будут отсортированы. Можно сделать двойной щелчок по разделителю столбцов в заголовке таблицы. В этом случае ширина соответствующей колонки будет установлена так, чтобы в колононке поместилась самая длинная строка из данного столбца.

Ниже окна органа управления Column Header находится список, который создан на базе органа управления Listview. Этот список содержит не только текстовые строки, но и графические изображения (пиктограммы).

Вы сможете легко создать в своем приложении органы управления Column Header и Listview на базе преопределенных классов окна. Реализация этой задачи старыми средствами потребовала бы значительных усилий. А ведь таблицы и списки - неотъемлемая часть приложений, ориентированных на обработку электронных таблиц и баз данных.

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

Рис. 1.4. Использование движкового регулятора в приложении Media Player

Как и следовало ожидать, в операционной системе Microsoft Windows 95 предусмотрен класс окна для создания такого регулятора.

Большое впечатление производит на многих орган управления Animation , показанный в действии (насколько это возможно в книге) на рис. 1.5.

Рис. 1.5. "Летающие" листки бумаги, созданные с использованием органа управления Animation

Как работает такой орган управления?

Очень просто. В прямоугольном окне проигрывается avi-файл, в котором записан небольшой видеофрагмент. О том как создавать такие файлы без использования дорогостоящей видеоаппаратуры (например, на основе заранее подготовленного набора картинок - мультфильма) вы можете узнать из 15 тома "Библиотеки системного программиста", который называется "Мультимедиа для Windows".

Заметим, что орган управления Animation создается очень легко на базе предопределенного класса окна. Таким же образом вы можете создать и орган управления Progressbar, расположенный в нижней части диалоговой панели, показанной на рис. 1.5.

Еще один орган управления, встроенный в систему, называется Property Sheet . Он показан на рис. 1.6.

Рис. 1.6. Использование органа управления Property Sheet для настройки режима работы программы DOS

Для чего нужен такой орган управления, объяснять не нужно. Современные приложения достаточно сложны и имеют многочисленные параметры, настройка которых через систему меню или диалогов бесконечной вложенности не вызывает у пользователей никакого энтузиазма. Достаточно вспомнить, как эта операция выполнялась в ранних системах разработки Borland C++.

Орган управления Property Sheet создается на базе предопределенного класса и набора диалоговых панелей. Он очень нагляден и удобен в работе.

В Microsoft Windows 95 есть и "волшебный" орган управления Wizard. Что это такое?

Претворяя в жизнь идею о приближении компьютера к человеку, многие современные системы и приложения выполняют достаточно сложные процедуры в полуавтоматическом режиме, задавая вам лишь самые необходимые вопросы. Такие системы называют "волшебниками" (wizard), способными сделать почти все самостоятельно без вмешательства человека.

В качестве примера можно привести средства автоматического создания сложных документов и таблиц в текстовом процессоре Microsoft Word for Windows, систему автоматической генерации исходного текста приложений AppWizard из пакета Microsoft Visual C++, а также, конечно, многочисленные системы, встроенные в Microsoft Windows 95.

Все подобные автоматические системы общаются с вами с помощью набора диалоговых панелей. Пример такой панели показан на рис. 1.7.

Рис. 1.7. Одна из диалоговых панелей системы автоматического создания теневых пиктограмм

В каждой из таких панелей есть кнопки "Back", "Next" и "Cancel", предназначенные, соответственно, для перехода к следующей или предыдущей панели, а также для отмены выполняемого в автоматическом режиме процесса. Кроме этих кнопок на панели имеются другие органы управления, с помощью которых и вы сможете принять посильное участие в происходящем.

Система управления диалоговыми панелями встроена в Microsoft Windows 95, поэтому ваши приложения смогут легко воспользоваться ей. Учтите, что операционная система, какая бы она не была умная, не сможет по волшебству решить любую вашу задачу. Она лишь поможет управиться с диалоговыми панелями, остальное лежит на ваших плечах.

А что вы скажите о новом органе управления, предназначенном для отображения и редактирования текста с использованием шрифтового оформления и различных способов выравнивания отдельных параграфов (рис. 1.8)?

Рис. 1.8. Орган управления Rich Edit Control , на базе которого создано приложение WordPad

Вы можете создать такой орган управления (разумеется, без изображенных на рис. 1.8 кнопок, линеек и т. п.) простым вызовом функции CreateWindow. Далее, вы сможете загружать в этот редактор и выгружать из него текстовые файлы в формате RTF, который понимают многие текстовые процессоры. Вы даже можете разместить в окне редактирования OLE -объекты, например, изображения, созданные приложением Paint.

Предусмотрен специальный орган управления Hot Key, с помощью которого пользователь может легко определить комбинация клавиш активизации приложения. Внешне он похож на однострочный редактор текста. Подробности вы узнаете из седьмой главы нашей книги.

И под конец мы покажем вам еще один новый встроенный в систему орган управления. Возможно, он не самый впечатляющий, но тем не менее удобный в работе. Этот орган управления называется Up-Down и предназначен для изменения значений чисел или перебора текстовых строк (рис. 1.9).

Рис. 1.9. Использование органа управления Up-down вместе с однострочным редкатором текста EDIT для установки количества копий документа при печати

Новый вид стандартных диалоговых панелей

Обратим ваше внимание на новый вид стандартных диалоговых панелей, предназначенных для выполнения таких операций, как открытие файла или документа, сохранение файла или документа, печать и т. д.

Эти изменения вызваны, с одной стороны, необходимостью отображения длинных имен файлов (до 260 символов), с другой - необходимостью отображения пространства имен оболочки Microsoft Windows 95.

Что касается имен файлов, тут все ясно. Длинные имена не поместятся в узких рамках старых диалоговых панелей. Поэтому соответствующие органы управления занимают в новых панелях почти всю ширину (рис. 1.10).

Рис. 1.10. Новый вид стандартной диалоговой панели "Open"

Под пространством имен оболочки Microsoft Windows 95 понимается совокупность имен файлов, каталогов, принтеров, дисковых устройств, сетевых ресурсов и т. д. Новые диалоговые панели позволяют работать со всеми этими объектами.

Пространство имен имеет иерархическую структуру и в этом оно похоже на иерархическую систему каталогов и файлов. Отличие, однако, заключается в том, что в пространство имен включаются не только файлы и каталоги, но и другие объекты (см. рис. 1.3).

Изменился также внешний вид диалоговой панели, предназначенной для печати документов (рис. 1.11).

Рис. 1.11. Новая диалоговая панель, предназначенная для печати документов

Подробнее мы рассмотрим новые стандартные диалоговые панели позже, в отдельной главе одной из следующих книг. Сейчас отметим только, что для внесения изменений в стандартные диалоговые панели вам теперь не нужно готовить свой собственный шаблон - вы можете добавить новые органы управления в уже существующую панель.

Объектно-ориентированная оболочка

Одно из новшеств операционной системы Microsoft Windows 95 - объектно-ориентированная оболочка (shell). В большинстве случаев эта оболочка избавляет пользователя от необходимости работать с древовидной файловой системой. Вместо этого она предоставляет ему модель рабочего стола с лежащими на нем папками и другими объектами.

Пользователь может открывать и закрывать папки , создавать новые (как на поверхности рабочего стола, так и внутри других папок), создавать в папках новые документы и перекладывать из одной папки в другую существующие - словом, делать то, что он делает, когда работает в своей конторе за обычным столом. Печать документов выполняется простым перемещением пиктограммы документа на пиктограмму принтера, удаление - перемещением пиктограммы документа на пиктограмму мусорной корзины.

Такая технология удобна, особенно для начинающих пользователей компьютера, не знакомых с командами DOS, каталогами и файлами. Однако и более опытные пользователи легко смогут к ней привыкнуть.

Контекстное меню

Важной особенностью объектно-ориентированной оболочки Microsoft Windows 95 является возможность работы с так называемыми контекстными меню. Пример такого меню показан на рис. 1.12.

Рис. 1.12. Контекстное меню для принтера

Для того чтобы вызвать контекстное меню, пользователь должен сделать щелчок правой клавишей мыши, установив курсор на нужную пиктограмму или другой объект, либо на свободную поверхность рабочего стола. Практически каждый объект оболочки имеет контекстное меню, содержание которого определяется самим объектом.

Блокнот свойств объекта

Обратите внимание на строку "Properties" в контекстном меню принтера (рис. 1.12). Если выбрать эту строку, на экране появится блокнот свойств объекта , сделанный на базе органа управления Property Sheet (рис. 1.13).

Рис. 1.13. Блокнот свойств объекта (принтера)

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

Программные интерфейс Microsoft Windows 95 позволяет вам относительно легко определять для своих объектов контекстное меню и блокнот свойств. Это легче сделать с использованием библиотеки классов MFC , которая входит в состав Microsoft Visual C++.

Теневые пиктограммы

Еще одно из нововведений - теневые пиктограммы , предназначенные для ускоренного запуска приложений или работы с другими объектами, такими как папки, документы, принтеры, локальные и сетевые дисковые устройства и т. д. Вы можете отличить эти пиктограммы по названию, которое имеет вид "Shortcut to ...".

Теневая пиктограмма является визуальным представлением объекта данных, который служит для доступа к другому объекту данных. Последний может находится в любом месте пространства имен оболочки (например, в любой папке).

Ваше приложение может работать с теневыми пиктограммами через интерфейс COM . Соответствующие программы составляются, как правило, на базе библиотеки классов MFC.

Просмотр содержимого файлов и документов

Одно из достоинств популярной программы Norton Commander заключается в возможности форматного просмотра файлов: текстовых, графических, а также файлов баз данных. Просмотр выполняется очень просто - достаточно выделить нужный файл и нажать клавишу <F3>. В зависимости от формата файла автоматически вызывается соответствующая программа просмотра.

В составе оболочки Microsoft Windows 95 имеется похожая по назначению система просмотра. С ее помощью, например, вы можете просматривать содержимое doc-файлов в формате текстового процессора Microsoft Word for Windows, даже если у вас нет этого текстового процессора. Систему просмотра файлов вы можете расширить, добавив в нее собственные средства просмотра файлов новых форматов.

Библиотеки динамической загрузки

По сравнению с Microsoft Windows версии 3.1 в Microsoft Windows 95 и Microsoft Windows NT структура и методы работы с библиотеками динамической загрузки (DLL-библиотеками ) сильно изменились. Например, функция инициализации библиотеки вызывается не один раз при первой загрузке библиотеки в память, а каждый раз, когда к библиотеке подключается новый процесс или новая задача.

1.2. Особенности программирования для Win32

В этом разделе мы собрали основные сведения, которые нужны, чтобы быстро перейти от 16-разрядного программирования в среде Windows версии 3.1 к 32-разрядному для операционных систем Microsoft Windows 95 и Microsoft Windows NT.

Для краткости далее мы будем называть 16-разрядный программный интерфейс Microsoft Windows версии 3.1 интерфейсом Win16 , а новый, 32-разрядный, - интерфейсом Win32 . Эта терминология соответствует использованной в Microsoft SDK.

Итак, начнем с самого начала - с функции WinMain.

Функция WinMain

В 11 томе "Библиотеки системного программиста", мы описывали функцию WinMain для приложений Win16 следующим образом:

int PASCAL WinMain(HINSTANCE hInstance, 
  HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow);

Для приложений Win32 это описание немного изментися:

int APIENTRY WinMain(HINSTANCE hInstance, 
  HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow);

На первый взгляд, изменений немного - мы заменили PASCAL на APIENTRY . Однако есть и другие, невидимые невооруженным глазом изменения.

Прежде всего, вспомним определение типа int. Разрядность переменной этого типа зависит от системы. Для DOS и Microsoft Windows версии 3.1 эта разрядность равна 16 бит, для Microsoft Windows 95 и Microsoft Windows NT - 32 бит. Если вы собираетесь составлять программу таким образом, чтобы она работала в разных системах (или даже на разных платформах), не следует делать никаких предположений относительно разрядности переменных типа int.

Что же касается типов PASCAL и pascal, то в 32-разрядной системе разработки Microsoft Visual C++ версии 2.0 они не рекомендуются для использования, хотя и определены для совместимости следующим образом:

#define PASCAL  __stdcall
#define pascal  __stdcall

Вместо этих типов вы должны указывать типы APIENTRY, WINAPI, CALLBACK (последний используется в функциях обратного вызова, например, в функции окна):

#define CALLBACK    __stdcall
#define WINAPI      __stdcall
#define APIENTRY    WINAPI

Согласно документации, ключевое слово __stdcall определяет функцию, аргументы которой помещаются в стек справа налево.

Займемся теперь параметрами функции WinMain.

Приложениям Win16 через первые два параметра передаются, соответственно, идентификатор приложения hInstance и идентификатор предыдущей копии приложения hPrevInstance, если в системе одновременно работает несколько копий одного приложения. Если же запущена только одна копия приложения, параметр hPrevInstance имеет значение NULL.

Последнее обстоятельство часто используется приложениями Win16 для того чтобы разрешать пользователю запускать только одну копию приложения.

Приложения Win32 работают в разных адресных пространствах, поэтому они не могут воспользоваться идентификатором своей предыдущей копии, как приложения Win16. Более того, такое приложение вообще никак не сможет использовать параметр hPrevInstance , так как его значение для 32-разрядных приложений Microsoft Windows 95 и Microsoft Windows NT всегда равно NULL.

Сказанное, однако, не означает, что теперь вы не сможете запретить пользователям запускать несколько копий вашего приложения. Просто для этого потребуются другие средства (например, семафоры или прямой поиск окон функцией FindWindow). Соответствующие примеры приложений будет приведены ниже.

Другое новшество касается не только идентификаторов приложений, но и всех других идентификаторов в 32-разрядных версиях Microsoft Windows. Идентификаторы теперь стали 32-разрядными. И если раньше вы могли определять переменные для хранения идентификаторов как int, unsigned и т. п., то теперь необходимо пользоваться специально предназначенными для этого типами (HINSTANCE, HICON, HBRUSH и т. д.). Впрочем, так нужно было поступать и раньше, при создании приложений Win16.

Что же касается параметров lpCmdLine и nCmdShow, то здесь практически ничего не изменилось.

Параметр lpCmdLine по-прежнему указывает на строку параметров, а параметр nCmdShow содержит их количество. Однако теперь тип LPSTR соответствует 32-разрядному смещению переменной типа CHAR, а не дальнему указателю на эту переменную в формате <селектор:смещение>.

Параметр nCmdShow определяет способ отображения окна (минимизированное SW_MINIMIZE, нормальное SW_SHOW и т. д.). Мы уже рассказывали об использовании этого параметра в 11 томе "Библиотеки системного программиста".

Функция окна WndProc

Для приложений Win32 вы можете использовать одно из двух приведенных ниже описаний функции окна:

LRESULT WINAPI
WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK
WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

Сравним это со старым описанием, которым мы пользовались в приложениях Win16:

LRESULT CALLBACK __export
WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

Обратите внимание, что в новом варианте отсутствует ключевое слово __export. В 32-разрядных приложениях необходимость в явном экспортировании функции окна отпала. Транслятор Microsoft Visual C++ версии 2.0 вообще не распознает __export как ключевое слово.

Идентификатор окна hWnd, имеющий тип HWND, стал в Microsoft Windows 95 и Microsoft Windows NT 32-разрядным, так же как и все остальные идентификаторы. Код сообщения msg, имеющий тип UINT, также стал 32-разрядным.

Однако наибольшие изменения затронули параметры wParam и lParam. Они не только оба стали 32-разрядными, но и изменили свой формат по сравнению с тем, что был в приложениях Win16.

Напомним, что в приложениях Win16 параметр wParam был 16-разрядный, а lParam - 32-разрядный. Теперь оба эти параметра стали 32-разрядными.

Казалось бы, это не должно вызвать никаких существенных изменений, однако давайте вспомним, что все идентификаторы в Win32 стали 32-разрядными. В результате изменился формат сообщений.

Обработка 32-разрядных сообщений

В приложениях Win16 параметр lParam часто использовался для передачи сразу двух значений, соответственно, через младшее и старшее слово. Для удобства выделения этих значений предназначались специальные макрокоманды LOWORD и HIWORD .

Например, для сообщения WM_COMMAND в младшем слове параметра lParam передавался 16-разрядный идентификатор дочернего окна hWnd (органа управления, пославшего это сообщение), в старшем - 16-разрядный код извещения wCmd. Через параметр wParam передавался 16-разрядный идентификатор органа управления wId (рис. 1.14).

Рис. 1.14. Формат параметров wParam и lParam для сообщения WM_COMMAND в приложениях Win16

В приложениях Win32 идентификатор окна hWnd стал 32-разрядным, поэтому его никак нельзя разместить в старшем слове параметра lParam. Теперь приходится отводить для идентификатора окна все разряды параметра lParam.

А что делать с кодом извещения wCmd, который оказался "выселен" из хорошо обжитого им параметра lParam?

Его пришлось перенести в старшее слово параметра wParam, удвоившего свою разрядность (рис. 1.15).

Рис. 1.15. Формат параметров wParam и lParam для сообщения WM_COMMAND в приложениях Win32

Таким образом, идентификатор окна остался на месте, а код извещения "переехал" в старшее слово параметра wParam. Аналогичным образом изменился формат и других сообщений.

Приведем два фрагмента обработчика сообщения WM_COMMAND, первый из которых используется в приложениях Win16, а второй - в приложениях Win32.

Итак, первый фрагмент.

case WM_COMMAND:
{
  wId  = wParam;
  hWnd = LOWORD(lParam);
  nCmd = HIWORD(lParam);
  ...
}

Приложения Win32 должны разбирать сообщение WM_COMMAND на составные части по-другому:

case WM_COMMAND:
{
  wId  = LOWORD(wParam);
  hWnd = (HWND)(UINT)lParam;
  nCmd = HIWORD(wParam);
  ...
}

В файле windowsx.h определены макрокоманды разборки сообщений (message crackers), которые позволяют "извлечь" из сообщений отдельные параметры.

Приведем для примера определения макрокоманд разборки сообщения WM_COMMAND из файла windowsx.h:

#define GET_WM_COMMAND_ID(wp, lp)   LOWORD(wp)
#define GET_WM_COMMAND_HWND(wp, lp) (HWND)(lp)
#define GET_WM_COMMAND_CMD(wp, lp)  HIWORD(wp)
#define GET_WM_COMMAND_MPS(id, hwnd, cmd) \
  (WPARAM)MAKELONG(id, cmd), (LONG)(hwnd)

Есть и более впечатляющие макрокоманды, позволяющие значительно упростить внешний вид обработчика сообщений в функции окна.

Предположим, нам надо организовать обработку сообщений WM_CREATE и WM_LBUTTONDOWN . Мы готовим две функции с именами WndProc_OnCreate и WndProc_OnLButtonDown, которые будут заниматься обработкой этих сообщений:

BOOL WndProc_OnCreate(HWND hWnd, 
  LPCREATESTRUCT lpCreateStruct);
void WndProc_OnLButtonDown(HWND hWnd, BOOL fDoubleClick, 
  int x, int y, UINT keyFlags);

Имена функций не играют никакой роли, однако Microsoft рекомендует составлять их из префикса имени функции окна (например, WndProc) с добавлением строки _On и названия сообщения, в котором удален префикс WM_.

Затем в функции окна мы используем макрокоманду HANDLE_MSG , указывая ей в качестве первого параметра идентификатор окна, в качестве второго - номер сообщения и, наконец, в качестве третьего - имя функции, которая будет обрабатывать сообщение:

LRESULT WINAPI 
WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  switch(msg)
  {
     HANDLE_MSG(hWnd, WM_CREATE, WndProc_OnCreate);
     HANDLE_MSG(hWnd, WM_LBUTTONDOWN, WndProc_OnLButtonDown);
	default:
      return(DefWindowProc(hWnd, msg, wParam, lParam));
  }
}

Теперь оператор switch будет выглядеть компактнее, так как для обработки каждого сообщения в него добавляется только одна строка.

Следует, однако, заметить, что для работы с макрокомандой HANDLE_MSG вы должны использовать в качестве имен для последних двух параметров функции WndProc имена wParam и lParam (только эти имена).

Как выглядит функция обработчика сообщения, вызываемая макрокомандой HANDLE_MSG?

Так же, как и обычная функция, за одним исключением - она должна оканчиваться вызовом макрокоманды FORWARD_<имя сообщения> в операторе return (даже если функция имеет тип void):

void WndProc_OnLButtonDown(HWND hWnd, BOOL fDoubleClick, 
  int x, int y, UINT keyFlags)
{
  ...  
  return FORWARD_WM_LBUTTONDOWN(hWnd, fDoubleClick, 
           x, y, keyFlags, DefWindowProc);
}

Но при обработке сообщения WM_CREATE вы должны в случае успешной инициализации данных окна вернуть значение TRUE, иначе окно так и не будет создано:

BOOL WndProc_OnCreate(HWND hWnd, 
  LPCREATESTRUCT lpCreateStruct)
{
  ...  
  return TRUE;
}

Вы можете создать перечисленные выше макрокоманды и для обработки собственных сообщений, взяв за основу определения из файла windowsx.h.

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

#pragma warning(disable: 4098)

Функции программного интерфейса Win32

Так как приложения Win32 работают с 32-разрядными идентификаторами, а также 32-разрядными координатами (графические функции), очевидно, что в Microsoft Windows 95 и Microsoft Windows NT должны были измениться многие функции программного интерфейса.

Например, функции, возвращающие 32-разрядное слово, в старшей и младшей половине которого упакованы 16-разрядные значения, должны быть изменены или заменены.

Если раньше в приложении Win16 вы могли вызывать функции прерывания INT 21h для выполнения вызовов DOS, теперь вам придется пользоваться соответствующим набором функций интерфейса Win32.

Так как по сравнению с Microsoft Windows версии 3.1 в операционных системах Microsoft Windows 95 и Microsoft Windows NT полностью изменились структура памяти и методы управления памятью, многие привычные вам функции больше не нужны (например, функция GlobalFix ).

1.3. Первое приложение для Microsoft Windows 95

К настоящему моменту мы перечислили только основные отличия, которые сразу бросаются в глаза при изучении программного интерфейса Win32. Более тонкие нюансы мы разберем дальше на примерах конкретных приложений.

Итак, перейдем к практике. Поставим перед собой задачу создать простейшее приложение Win32 с одним главным окном, пока без меню, линейки инструментов Toolbar и других элементов пользовательского интерфейса, специфических для Microsoft Windows 95.

Приложение Window Application

За основу мы возьмем приложение WINDOW из 11 тома "Библиотеки системного программиста".

Внешний вид главного окна 32-разрядного варианта приложения WINDOW, запущенного в среде Microsoft Windows 95, показан на рис. 1.16.

Рис. 1.16. Внешний вид приложения Window Application

Если сделать щелчок левой клавишей мыши по внутренней области окна, на экране появится сообщение (рис. 1.17).

Рис. 1.17. Сообщение, которое появляется после щелчка левой клавишей мыши внутри окна приложения Window Application

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

Надпись на заголовке окна выровнена влево, а не отцентрирована, как это было раньше в Microsoft Windows версии 3.1.

В правой части заголовка появились пиктограммы, с помощью которых можно выполнить следующие действия:

Пиктограмма Назначение
Минимизация окна приложения
Восстановление нормального размера окна
Удаление окна, которое приведет к завершению работы приложения (если удаляется главное окно)

Исходные тексты приложения Window Application

Теперь мы перейдем к исходным текстам приложения Window Application. Основной файл исходного текста приложения приведен в листинге 1.1.

Листинг 1.1. Файл window\window.c

#include <windows.h>
#include <windowsx.h>
#include "afxres.h"
#include "resource.h"

// -----------------------------------------------------
// Описание функций
// -----------------------------------------------------

// Функция главного окна
LRESULT WINAPI
WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

// Функция для обработки сообщения WM_CREATE
BOOL WndProc_OnCreate(HWND hWnd, 
  LPCREATESTRUCT lpCreateStruct);

// Функция для обработки сообщения WM_LBUTTONDOWN
void WndProc_OnLButtonDown(HWND hWnd, BOOL fDoubleClick, 
  int x, int y, UINT keyFlags);

// Функция для обработки сообщения WM_DESTROY
void WndProc_OnDestroy(HWND hWnd);

// -----------------------------------------------------
// Глобальные переменные
// -----------------------------------------------------

// Идентификатор приложения
HINSTANCE hInst;

// Название приложения
char szAppName[] = "Window";

// Заголовок главного окна приложения
char szAppTitle[] = "Window Application";

// -----------------------------------------------------
// Функция WinMain
// -----------------------------------------------------
int APIENTRY 
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
        LPSTR lpCmdLine, int nCmdShow)
{
  WNDCLASSEX wc;
  HWND hWnd;
  MSG msg;
  
  hInst = hInstance;

  // Преверяем, не было ли это приложение запущено ранее
  hWnd = FindWindow(szAppName, NULL);
  if(hWnd)
  {
    // Если окно приложения было свернуто в пиктограмму,
    // восстанавливаем его
    if(IsIconic(hWnd))
      ShowWindow(hWnd, SW_RESTORE);

    // Выдвигаем окно приложения на передний план
    SetForegroundWindow(hWnd);
    return FALSE;
  }

  // Регистрируем класс окна
  memset(&wc, 0, sizeof(wc));
  
  // Поля wc.cbSize и wc.hIconSm определены в структуре
  // WNDCLASSEX, которой можно пользоваться для
  // регистрации класса окна в Windows 95
  wc.cbSize = sizeof(WNDCLASSEX);

  // Поле wc.hIconSm задает идентификатор маленькой
  // пиктограммы, которая будет отображаться в левой
  // части заголовка окна (в области системного меню).
  // Загружаем пиктограмму из ресурсов приложения при
  // помощи функции LoadImage, так как функция
  // LoadIcon может загрузить только обычную пиктограмму
  wc.hIconSm = LoadImage(hInst,
    MAKEINTRESOURCE(IDI_APPICON_SM), IMAGE_ICON, 16, 16, 0);
  
  // Завершаем заполнение структуры WNDCLASSEX
  wc.style = CS_HREDRAW | CS_VREDRAW;
  wc.lpfnWndProc = (WNDPROC)WndProc;
  wc.cbClsExtra  = 0;
  wc.cbWndExtra  = 0;
  wc.hInstance = hInst;
  
  // Для загрузки обычной пиктограммы вы можете
  // использовать функцию LoadImage
  wc.hIcon = LoadImage(hInst,
    MAKEINTRESOURCE(IDI_APPICON), IMAGE_ICON, 32, 32, 0);
  
  wc.hCursor = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
  wc.lpszMenuName = NULL;
  wc.lpszClassName = szAppName;

  // Вызываем функцию RegisterClassEx, которая выполняет
  // регистрацию окна
  if(!RegisterClassEx(&wc))

    // В случае ошибки пытаемся зарегистрировать окно
    // функцией RegisterClass
    if(!RegisterClass((LPWNDCLASS)&wc.style))
      return FALSE;
    
  // Создаем главное окно приложения
  hWnd = CreateWindow(szAppName, szAppTitle,
    WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 
    0, NULL, NULL, hInst, NULL);
  if(!hWnd) return(FALSE);

  // Отображаем окно
  ShowWindow(hWnd, nCmdShow);
  UpdateWindow(hWnd);

  // Запускаем цикл обработки сообщений
  while(GetMessage (&msg, NULL, 0, 0))
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
  return msg.wParam;
}

// -----------------------------------------------------
// Функция WndProc
// -----------------------------------------------------
LRESULT WINAPI
WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  switch(msg)
  {
    // Для сообщения WM_CREATE назначаем обработчик,
    // расположенный в функции WndProc_OnCreate
    HANDLE_MSG(hWnd, WM_CREATE, WndProc_OnCreate);

    // Для сообщения WM_LBUTTONDOWN назначаем обработчик,
    // расположенный в функции WndProc_OnLButtonDown
    HANDLE_MSG(hWnd, WM_LBUTTONDOWN, WndProc_OnLButtonDown);

    // Для сообщения WM_DESTROY назначаем обработчик,
    // расположенный в функции WndProc_OnDestroy
    HANDLE_MSG(hWnd, WM_DESTROY, WndProc_OnDestroy);

    default:
      return(DefWindowProc(hWnd, msg, wParam, lParam));
  }
}

// -----------------------------------------------------
// Функция WndProc_OnCreate
// -----------------------------------------------------
BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct)
{
  return TRUE;
}

// -----------------------------------------------------
// Функция WndProc_OnDestroy
// -----------------------------------------------------
// Отключаем предупреждающее сообщение о том, что
// функция типа void возвращает управление при помощи
// оператора return. Этот оператор нужен для
// использования макрокоманды FORWARD_WM_LBUTTONDOWN
#pragma warning(disable: 4098)
void WndProc_OnDestroy(HWND hWnd)
{
  PostQuitMessage(0);
  return FORWARD_WM_DESTROY(hWnd, DefWindowProc);
}

// -----------------------------------------------------
// Функция WndProc_OnLButtonDown
// -----------------------------------------------------

#pragma warning(disable: 4098)
void 
WndProc_OnLButtonDown(HWND hWnd, BOOL fDoubleClick, 
  int x, int y, UINT keyFlags)
{
  MessageBox(NULL, "Hello, 32-bit world!", "Window", MB_OK);

  return FORWARD_WM_LBUTTONDOWN(hWnd, fDoubleClick, 
           x, y, keyFlags, DefWindowProc);
}

Файл resource.h (листинг 1.2) был создан автоматически системой Microsoft Visual C++ и содержит символические определения идентификаторов ресурсов приложения.

Листинг 1.2. Файл window\resource.h

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by window.rc
//
#define IDI_APPICON                     101
#define IDI_APPICON_SM                  102

// Next default values for new objects
// 
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NO_MFC                     1
#define _APS_NEXT_RESOURCE_VALUE        105
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1000
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif

В файле ресурсов определены две пиктограммы - стандартная (с идентификатором IDI_APPICON) и маленькая (с идентификатором IDI_APPICON_SM).

Файл resource.h автоматически включается в файл определения ресурсов window.rc (листинг 1.3). Последний был создан также автоматически системой Microsoft Visual C++.

Листинг 1.3. Файл window\window.rc

//Microsoft Visual C++ generated resource script.
//
#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS
//////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "afxres.h"

//////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS

#ifdef APSTUDIO_INVOKED
//////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//

1 TEXTINCLUDE DISCARDABLE 
BEGIN
    "resource.h\0"
END

2 TEXTINCLUDE DISCARDABLE 
BEGIN
    "#include ""afxres.h""\r\n"
    "\0"
END

3 TEXTINCLUDE DISCARDABLE 
BEGIN
    "\r\n"
    "\0"
END

//////////////////////////////////////////////////////////////
#endif    // APSTUDIO_INVOKED

//////////////////////////////////////////////////////////////
//
// Icon
//
IDI_APPICON             ICON    DISCARDABLE     "window.ico"
IDI_APPICON_SM          ICON    DISCARDABLE     "windowsm.ico"

#ifndef APSTUDIO_INVOKED
//////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
//////////////////////////////////////////////////////////////
#endif    // not APSTUDIO_INVOKED

Функция WinMain

Прежде всего приложение сохраняет свой идентификатор в глобальной переменной hInst. Затем оно выполняет проверку, не было ли это приложение запущено ранее.

Напомним, что для выполнения проверки мы не можем воспользоваться параметром hPrevInstance, так как для приложений Win32 этот параметр всегда равен NULL.

Воспользуемся функцией FindWindow , которая позволяет найти окно верхнего уровня по имени класса окна или по заголовку окна:

HWND FindWindow(
  LPCTSTR lpClassName,  // указатель на имя класса
  LPCTSTR lpWindowName  // указатель на имя окна
);

Через параметр lpClassName передается указатель на текстовую строку, закрытую двоичным нулем, в которую необходимо записать имя класса искомого окна. Вместо имени можно также передать через этот параметр атом, созданный функцией GlobalAddAtom , соответствующей строке имени класса. С атомами вы уже встречались в 11 томе "Библиотеки системного программиста".

Напомним, что атом является 16-разряной величиной, поэтому вы должны записать его значение в младшую половину 32-разрядного параметра lpClassName. Старшая половина при этом должна содержать нулевое значение.

На базе одного и того же класса можно создать несколько окон. Если вам нужно найти окно с заданным заголовком, то имя заголовка следует передать через параметр lpWindowName. Если же подойдет любое окно, параметр lpWindowName может иметь значение NULL.

Если окно будет найдено, функция FindWindow вернет его идентификатор. В противном случае она возвратит значение NULL.

В нашем случае мы можем ограничится указанием только первого параметра:

hWnd = FindWindow(szAppName, NULL);
if(hWnd)
{
  if(IsIconic(hWnd))
    ShowWindow(hWnd, SW_RESTORE);
  SetForegroundWindow(hWnd);
  return FALSE;
}

Пользователь может не помнить, какие приложения уже запущены, а какие нет. Когда он запускает приложение, делая двойной щелчок по соответствующей пиктограмме, то ожидает что на экране появится его главное окно. И в этом он абсолютно прав. Если приложение Window Application было запущено ранее, целесообразно активизировать и выдвинуть на передний план его главное окно. Это именно то, к чему приготовился пользователь.

Прежде всего мы проверяем, не было ли окно приложения минимизировано. Для этого мы вызываем известную вам из предыдущих томов "Библиотеки системного программиста" функцию IsIconic . Если окно минимизировано, восстанавливаем его нормальный размер с помощью функции ShowWindow , передавая ей в качестве первого параметра идентификатор найденного окна, а в качестве второго - константу SW_RESTORE .

Затем вне зависимости от того, было окно минимизировано или нет, выдвигаем его на передний план и активизируем с помощью функции SetForegroundWindow :

BOOL SetForegroundWindow(
  HWND hwnd // идентификатор активизируемого окна
);

В случае успеха эта функция возвращает значение TRUE, при ошибке - FALSE.

Если была найдена работающая копия приложения Window Application, мы завершаем работу текущей копии приложения. Если же копия работающего приложения не найдена, функция WinMain приступает к инициализации приложения.

Прежде всего выполняется регистрация класса окна. Приложения Win32 должны выполнять такую регистрацию немного не так, как это делают приложения Win16.

Напомним, что для регистрации класса окна приложения Win16 должны заполнить структуру типа WNDCLASS и затем передать ее адрес функции RegisterClass . В операционной системе Microsoft Windows 95 эта структура была расширена и теперь имеет тип WNDCLASSEX :

typedef struct _WNDCLASSEX 
{
  UINT    cbSize; 
  UINT    style;           // --------------------------------
  WNDPROC lpfnWndProc;     // Эта часть структуры WNDCLASSEX
  int     cbClsExtra;      // идентична структуре WNDCLASS
  int     cbWndExtra;      //
  HANDLE  hInstance;       //
  HICON   hIcon;           //
  HCURSOR hCursor;         //
  HBRUSH  hbrBackground;   //
  LPCTSTR lpszMenuName;    //
  LPCTSTR lpszClassName;   // --------------------------------
  HICON   hIconSm; 
} WNDCLASSEX;

В начало структуры было добавлено поле cbSize (размер структуры WNDCLASSEX), в конец - поле hIconSm (идентификатор маленькой пиктограммы, которая отображается в левом верхнем углу заголовка главного окна приложения). В остальном структура WNDCLASSEX в точности соответствует структуре WNDCLASS.

Для того чтобы зарегистрировать класс окна в операционной системе Microsoft Windows 95, вы должны заполнить структуру WNDCLASSEX и передать ее адрес функции RegisterClassEx . Заполнение структуры выполняется следующим образом:

memset(&wc, 0, sizeof(wc));
wc.cbSize = sizeof(WNDCLASSEX);
wc.hIconSm = LoadImage(hInst,
    MAKEINTRESOURCE(IDI_APPICON_SM), IMAGE_ICON, 16, 16, 0);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC)WndProc;
wc.cbClsExtra  = 0;
wc.cbWndExtra  = 0;
wc.hInstance = hInst;
wc.hIcon = LoadImage(hInst,
  MAKEINTRESOURCE(IDI_APPICON), IMAGE_ICON, 32, 32, 0);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
wc.lpszMenuName = NULL;
wc.lpszClassName = szAppName;

Для получения идентификатора маленькой пиктограммы мы воспользовались новой функцией LoadImage , так как функция LoadIcon позволяет загрузить только стандартную пиктограмму.

Функция LoadImage - это возможно именно та функция, которой вам не хватало для работы с изображениями в программном интерфейсе Win16. С ее помощью вы можете загрузить пиктограмму, курсор, а также битовое изображение, причем не только из ресурсов приложения, но и из файла:

HANDLE LoadImage(
  HINSTANCE hInst,     // идентификатор модуля, 
                       // содержащего изображение
  LPCTSTR   lpszName,  // имя или идентификатор изображения
  UINT      uType,     // тип изображения
  int       cxDesired, // желаемая ширина изображения
  int       cyDesired, // желаемая высота изображения
  UINT      fuLoad     // флаги загрузки
);

Прежде всего мы перечислим возможные значения параметра fuLoad, определяющие выполняемые этой функцией действия.

Параметр Описание
LR_LOADFROMFILE Функция загружает изображение из файла, заданного параметром lpszName
LR_DEFAULTSIZE Если значение параметров cxDesired и cyDesired равны нулю, при загрузке изображения используются размеры по умолчанию
LR_LOADREALSIZE Если значение параметров cxDesired и cyDesired равны нулю, при загрузке пиктограммы или курсора используются размеры по умолчанию, взятые из системных метрик
LR_DEFAULTCOLOR Для отображения используется текущий цветовой формат
LR_MONOCROME Изображение загружается как черно-белое (монохромное)
LR_LOADMAP3DCOLORS При загрузке функция ищет в изображении таблицу цветов и заменяет темно-серый, серый и светло-серый цвета на системные, которые используются для того чтобы придать изображению трехмерный вид. Конкретно, выполняется такая замена:Исходный цвет RGB На что меняется128, 128, 128 COLOR_3DSHADOW 192, 192, 192 COLOR_3DFACE 223, 223, 223 COLOR_3DLIGHT
LR_LOADTRANSPARENT Загрузка в "прозрачном" режиме. В процессе загрузки выбирается значение цвета первого пиксела изображения и заменяется соответствующим значением из таблицы цветов, содержащим цвет окна по умолчанию COLOR_WINDOW . Все пикселы изображения, которые используют это значение таблицы, приобретают цвет COLOR_WINDOW
LR_SHARED Если изображение загружается несколько раз, его идентификатор будет использован повторно. Этот флаг не рекомендуется использовать для изображений с нестандартными размерами, которые могут изменяться, а также для изображений, загружаемых из файла

Загружая пиктограмму из ресурсов приложения при заполнении структуры WNDCLASSEX мы установили значение параметра fuLoad равным 0, что соответствует LR_DEFAULTCOLOR.

Через параметр hInst передается идентификатор модуля, в ресурсах которого находится изображение. В нашем случае это идентификатор приложения, полученный от функции WinMain.

С помощью функции LoadImage вы можете загрузить системные ресурсы (битовые изображения, пиктограммы и курсоры, которые используются операционной системой). Соответствующие изображения называются OEM-изображениями. Их идентификаторы определены в файле winuser.h. Идентификаторы пиктограмм имеют префикс OIC_, курсоров - OCR_, битовых изображений - OBM_.

Если вы загружаете системные ресурсы, через параметр hInst необходимо передать нулевое значение. Идентификатор ресурса при этом должен передаваться через младшее слово параметра lpszName.

Параметр uType определяет тип загружаемого изображения и может принимать следующие значения:

Значение Описание
IMAGE_BITMAP Битовое изображение
IMAGE_CURSOR Курсор
IMAGE_ICON Пиктограмма

Через параметры cxDesired и cyDesired, как нетрудно догадаться, передается желаемая ширина и высота изображения в пикселах. Если же указать нулевые значения, будут использованы величины, принятые по умолчанию.

При заполнении структуры WNDCLASSEX мы вызываем функцию LoadImage два раза. В первый раз мы загружаем маленькую пиктограмму с размерами 16 х 16 пикселов, во второй раз - стандартную пиктограмму с размерами 32 х 32 пиксела.

И наконец, через параметр lpszName необходимо передать идентификатор ресурса (если изображение загружается из ресурсов модуля) или адрес текстовой строки, содержащий имя файла (если изображение загружается из файла).

Заполнив поля структуры WNDCLASSEX, мы вызываем функцию RegisterClassEx :

if(!RegisterClassEx(&wc))
  if(!RegisterClass((LPWNDCLASS)&wc.style))
    return FALSE;

В среде Microsoft Windows 95 функция RegisterClassEx должна выполнить все необходимые для регистрации действия и вернуть значение атома, который идентифицирует зарегистрированный класс.

Однако если вы запустите наше приложение в среде Microsoft Windows NT версии 3.5, функция RegisterClassEx вернет значение NULL. В программном интерфейсе операционной системы этой версии регистрация класса окна должны выполняться функцией RegisterClass.

В следующих версиях Microsoft Windows NT, когда в эту операционную систему будет встроена объектно-ориентированная оболочка, аналогичная оболочке Microsoft Windows 95, вы сможете выполнить расширенную регистрацию с помощью функции RegisterClassEx. Поэтому, для того чтобы наша программа смогла работать и в среде Microsoft Windows 95, и в среде Microsoft Windows NT версии 3.5 (а также более новых версий), мы вначале пытаемся использовать для регистрации функцию RegisterClassEx, а в случае неудачи - функцию RegisterClass, передавая последней адрес обычной структуры WNDCLASS.

После регистрации приложение Window Application создает главное окно, отображает его и запускает цикл обработки сообщений. Все эти действия были подробно описаны в 11 томе "Библиотеки системного программиста".

Функция WndProc

Функция WndProc предназначена для обработки сообщений, поступающих в очередь нашего приложения:

LRESULT WINAPI
WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  switch(msg)
  {
    HANDLE_MSG(hWnd, WM_CREATE, WndProc_OnCreate);
    HANDLE_MSG(hWnd, WM_LBUTTONDOWN, WndProc_OnLButtonDown);
    HANDLE_MSG(hWnd, WM_DESTROY, WndProc_OnDestroy);
    default:
      return(DefWindowProc(hWnd, msg, wParam, lParam));
  }
}

Ее особенностью является использование макрокоманды HANDLE_MSG, сокращающей объем листинга функции.

Макрокоманда HANDLE_MSG определена в файле windowsx.h следующим образом:

#define HANDLE_MSG(hwnd, msg, fn) \
 case (msg): return HANDLE_##msg((hwnd), \
 (wParam), (lParam), (fn))

Согласно этому определению, для обработки сообщения WM_CREATE вызывается макрокоманда HANDLE_WM_CREATE. Аналогично, для обработки сообщения WM_LBUTTONDOWN вызывается макрокоманда HANDLE_WM_LBUTTONDOWN, а для обработки сообщения WM_DESTROY - макрокоманда HANDLE_.WM_DESTROY.

Эти макрокоманды, наряду с аналогичными для других сообщений, определены в файле windowsx.h:

#define HANDLE_WM_CREATE(hwnd, wParam, lParam, fn) \
  ((fn)((hwnd), (LPCREATESTRUCT)(lParam)) ? 0L : (LRESULT)-1L)
#define HANDLE_WM_DESTROY(hwnd, wParam, lParam, fn) \
    ((fn)(hwnd), 0L)
#define HANDLE_WM_LBUTTONDOWN(hwnd, wParam, lParam, fn) \
    ((fn)((hwnd), FALSE, (int)(short)LOWORD(lParam), \
    (int)(short)HIWORD(lParam), (UINT)(wParam)), 0L)

Макрокоманда HANDLE_WM_CREATE вызывает функцию обарботки сообщения WM_CREATE, адрес которой передается ей через последний параметр. Прототип этой функции вы сможете найти все в том же файле windowsx.h рядом с определением макрокоманды HANDLE_WM_CREATE:

BOOL Cls_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct)

Аналогичные прототипы определены и для других функций обработки сообщений.

Функции обработки сообщений

Функции обработки сообщений обычно выполняют всю работу, для которой создается приложение. Приложение Window Application практически ничего не делает, поэтому и функции обработки сообщений несложны:

BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct)
{
  return TRUE;
}

void WndProc_OnDestroy(HWND hWnd)
{
  PostQuitMessage(0);
  return FORWARD_WM_DESTROY(hWnd, DefWindowProc);
}

void WndProc_OnLButtonDown(HWND hWnd, BOOL fDoubleClick, 
  int x, int y, UINT keyFlags)
{
  MessageBox(NULL, "Hello, 32-bit world!", "Window", MB_OK);
  return FORWARD_WM_LBUTTONDOWN(hWnd, fDoubleClick, 
           x, y, keyFlags, DefWindowProc);
}

Функция WndProc_OnCreate вызывается при создании окна и предназначена для инициализации данных, связанных с этим окном. В нашем случае она просто возвращает значение TRUE, сообщая тем самым, что инициализация завершилась успешно.

Функция WndProc_OnLButtonDown в ответ на щелчок мышью внутри окна выводит диалоговую панель с сообщением. Она завершается вызовом макрокоманды FORWARD_WM_LBUTTONDOWN, определенной в файле windowsx.h следующим образом:

#define FORWARD_WM_LBUTTONDOWN \
  (hwnd, fDoubleClick, x, y, keyFlags, fn) \
  (void)(fn)((hwnd), (fDoubleClick) ? WM_LBUTTONDBLCLK : \
  WM_LBUTTONDOWN, (WPARAM)(UINT)(keyFlags), \
  MAKELPARAM((x), (y)))

В качестве последнего параметра этой макрокоманде передается адрес функции, которая используется для обработки сообщений по умолчанию. В нашем случае это адрес функции DefWindowProc.

Макрокоманда FORWARD_WM_LBUTTONDOWN вызывает функцию DefWindowProc , передавая ей все необходимые параметры.

При уничтожении главного окна приложения вызывается функция WndProc_OnDestroy, которая помещает в очередь сообщение WM_QUIT. Это приводит к завершению цикла обработки сообщений, и, соответственно, к завершению работы приложения.

Использование Microsoft Visual C++ версии 2.0 в среде Windows 95

Система разработки Microsoft Visual C++ версии 2.0 предназначена для Windows NT, поэтому, хотя вы и можете работать с ней в среде Windows 95, у вас могут возникнуть некоторые проблемы.

Если в процессе компиляции файла ресурсов приложения *.rc происходит зацикливание, отредактируйте файл system.ini. Найдите в нем раздел [keyboard] и удалите (либо закройте символом комментария) в строке oemansi.bin= имя файла xlat866.bin:

[keyboard]
subtype=
type=4
keyboard.dll=
oemansi.bin=xlat866.bin

Вот что у вас должно получиться в результате:

oemansi.bin=

Вторая проблема, которая может перед вами возникнуть, вызвана тем, что Microsoft Visual C++ версии 2.0 отмечает загрузочные модули создаваемых приложений как предназначенные для работы в среде Windows NT, в то время как приложения Microsoft Windows 95 должны быть отмечены по-другому. В результате окна приложения могут не получать некоторые извещения. Могут возникнуть проблемы и с диалоговыми панелями.

Выход заключается в ручном редактировании параметров редактора связи для каждого создаваемого вновь приложения. Для этого из меню Project выберите строку Settings. На экране появится блокнот Project Settings. Откройте в этом блокноте страницу Link (рис. 1.18).

Рис. 1.18. Страница Link в блокноте Project Settings системы разработки Microsoft Visual C++ версии 2.0

Выбирая по одному проекты Win32 Debug и Win32 Release в списке Settings For в списке Project Options укажите параметр:

/SUBSYSTEM:windows,4.0

Кроме того, в список объектных модулей и библиотек Object/Library Modules добавьте библиотеку comctl32.lib. Эта библиотека необходима, если вы собираетесь работать с новыми органами управления Microsoft Windows 95.

Для того чтобы в палитре органов управления редактора диалогов появились новые органы управления, необходимо внести изменения в регистрационную базу данных. Эти изменения описаны в разделе "Орган управления Trackbar" седьмой главы этой книги.

Если вы приобрели SDK для Microsoft Windows 95, необходимо настроить параметры системы разработки Microsoft Visual C++ версии 2.0 таким образом, чтобы вначале использовались h- и lib-файлы из SDK, а только потом из Microsoft Visual C++.

Для этого выберите строку Options из меню Tools и в появившемся блокноте откройте страницу Directories. В списке Show Directories for выберите строку Include files. Затем нажмите кнопку Add и добавьте путь к каталогу include системы SDK. С помощью кнопок Move Up и Move Down добейтесь, чтобы этот каталог был указан до каталогов msvc20\include и msvc20\mfc\include.

Аналогичную операцию следует выполнить и для файлов библиотек, выбрав из списка Show Directories for строку Library files.

Если в вашем компьютере установлено мало оперативной памяти, вы не сможете использовать интегрировнную среду разработки Microsoft Visual C++. Тем не менее, вам будет доступна пакетная программа nmake.exe, с помощью которой можно выполнить трансляцию исходных текстов приложений. Образцы файла makefile, который необходимо подготовить для этой программы, вы найдете в каталоге с исходными текстами приложений, которые поставляются в составе SDK.

И последнее. Вместо встроенного отладчика, который не всегда работает в среде Microsoft Windows 95, используйте отладчик WinDbg, который поставляется в составе SDK для Microsoft Windows 95.