Что такое ресурсы

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

У Windows есть некоторые предопределенные данные (предопределенные курсоры, иконки и кисти). Точно так же, почти в каждой программе для Windows есть некоторые данные, которые определяются еще до начала работы программы, особым образом добавляются в выполняемый файл и используются при работе программы. Яркими примерами таких данных являются иконки и курсоры мыши. Кроме них, к числу ресурсов относятся:
 - используемые в программе изображения;
 - строки символов;
 - меню;
 - ускорители клавиатуры;
 - диалоговые окна;
 - шрифты;
 - ресурсы, определяемые пользователем;

Следует отметить, что выполняемым файлом может быть файл программы .exe, файл динамической библиотеки .dll и другие бинарные файлы. Для удобства буду их называть bin-файлами.

Помимо того, что ресурсы определяются до начала работы программы и добавляются в bin-файл, у них есть еще одна характерная черта. При загрузке bin-файла в память, РЕСУРСЫ В ПАМЯТЬ НЕ ЗАГРУЖАЮТСЯ. Только в случае, если тот или иной ресурс требуется для работы программы, программа сама загружает ресурс в память.

Возможность использования того или иного атрибута в качестве ресурса не означает, что программист не может создавать эти атрибуты в программе. Яркий пример тому можно найти в работе старого доброго Program Manager'а. При перетаскивании иконки с место на место курсор меняет свою форму и принимает форму, подобную перетаскиваемой иконке. Естественно, что в этом случае курсоры определяются программой. Помимо этого, вспомним drag-and-drop в Explorer'е и изменение формы курсора при этом.

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

Ресурсы стандартные и нестандартные

Все ресурсы, заранее определенные в Win32 API, называются стандартными. Для работы с ними существуют специальные функции. Но именно эта стандартность и ограничивает возможности программиста. Стандарт, он и есть стандарт.

Для того чтобы можно было преодолеть эти ограничения, был создан особый тип ресурсов - определяемые пользователем ресурсы. Используя именно этот тип, мы можем предоставить в распоряжение программы практически любые данные. Но, как известно, бесплатным бывает только сыр в мышеловке. В данном случае платой за универсальность является усложнение программы, так как забота о манипулировании данными из ресурсов лежит уже не на системе, а на программе, использующей эти ресурсы. Программа может только получить указатель на данные ресурсов, загруженные в память средствами Windows. Дальнейшая работа с ними ложится ИСКЛЮЧИТЕЛЬНО на плечи программы!

Подключение ресурсов к исполняемому файлу

Ресурсы создаются отдельно от файлов программы и добавляются в bin-файл при линковании программы. Подавляющее большинство ресурсов содержится в файлах ресурсов, имеющих расширение .RC. Имя файла ресурсов обычно совпадает с именем bin-файла программы. Так, если имя программы MYPROG.EXE, то имя файла ресурсов - MYPROG.RC.

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

Я часто использую "смешанный" способ редактирования ресурсов. Например, при визуальном редактировании диалоговых окон достаточно трудно точно установить элементы диалогового окна именно так, как хочется. Устанавливаю все элементы ПРИБЛИЗИТЕЛЬНО на те места, где они должны находиться, после чего сохраняю ресурсы в виде файла с расширением RC. Затем редактирую RC-файл как обычный текстовый файл, точно указывая при этом все размеры и позиции.

При создании RC-файлов программист может столкнуться с одной тонкостью. Некоторые ресурсы, такие, как иконки, курсоры, диалоговые окна, изображения (bitmap'ы) могут быть сохранены в отдельных файлах с расширениями .ico, .cur, .dlg, .bmp соответственно. В этом случае в RC-файлах делаются ссылки на упомянутые файлы.

Файл ресурсов создан - теперь его нужно откомпилировать. Компилируется он специальным компилятором ресурсов. Обычно имя компилятора ресурсов заканчивается на RC.EXE. В частности, в Borland 5.0 он называется BRC.EXE.

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

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

Последовательность создания файла ресурсов

Действие Используемое средство
Создание RC-файла (при необходимости включающего ссылки на файлы с расширением .ico, .cur, .bmp, .dlg, .mnu и т.д.)
Редактор ресурсов (при необходимости может быть использован текстовый редактор и графические редакторы)
Редактирование RC-файла в текстовом виде Текстовый редактор
Компиляция RC-файла - получение RES-файла Компилятор ресурсов
Добавление ресурсов, содержащихся в RES-файле, в bin-файл Линкер
Подключение меню к окну

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

Давайте представим себе главное меню программы как древовидную структуру. Основная часть - корень нашего дерева - это непосредственно главное меню. Само по себе оно представляет только структуру в памяти, не отображается на экране, не содержит ни одного элемента, но хранит указатель на список структур, описывающих подключаемые к нему элементы и всплывающие (popup) меню. В свою очередь, popup-меню должно указать на список структур очередного, более низкого уровня и т.д. Конечные элементы меню никаких указателей на списки не имеют, но хранят некий идентификатор действия (назовем его идентификатором элемента меню), которое должна произвести программа при выборе данного элемента меню. Используя эти структуры, мы можем построить меню практически любой глубины и сложности.

Эта многоуровневая древовидная структура описывается в файле ресурсов. Описание меню имеет вид:

MenuName  MENU  [параметры] ; это - главное меню
{
  Описание всех popup-меню и элементов меню второго уровня
}
В данном случае MenuName - это имя создаваемого нами меню. Слово MENU обозначает начало определения меню.

В Win32 API для описания меню существуют два ключевых слова. Первое - POPUP - специфицирует всплывающее меню. Второе - MENUITEM - описывает обычный элемент меню.

Всплывающие меню описывается следующим образом:

POPUP  "Имя"  [,параметры]  ; описание popup-меню
{
  Описание всех popup-меню и элементов очередного уровня
}
У конечного элемента меню в его описании есть еще одна характеристика - тот самый идентификатор действия:
MENUITEM  "Имя",  MenuID  [,параметры]
В обоих случаях "Имя" - это тот текст, который будет выведен на экран при отображении меню (обратите внимание - при описании главного меню выводимого на экран текста нет!). В том случае, когда вместо имени окна записано слово SEPARATOP (без кавычек!), на месте элемента меню появляется горизонтальная линия. Обычно эти горизонтальные линии (сепораторы или разделители) используются для разделения элементов подменю на логические группы (логика определяется только программистом и никаких особенностей не содержит).

Если в имени меню встречается символ "&", то следующий за амперсандом символ на экране будет подчеркнут одинарной чертой. Этот элемент меню можно будет вызывать с клавиатуры посредством одновременного нажатия клавиши Alt и подчеркнутого символа.

MenuID - идентификатор действия. Он может быть передан функции окна, содержащего меню. Значение идентификатора определяется пользователем. Функция окна в зависимости от полученного MenuID производит определенные действия.

Параметры же описывают способ появления элемента на экране. Возможные значения параметров приведены в таблице:

Параметры, описывающие элемент меню в файле ресурсов

Флаг Значение
CHECKED Рядом с именем элемента может отображаться небольшой значек, говорящий о том, что соответствующий флаг установлен
ENABLED Элемент меню доступен
DISABLED Элемент меню недоступен, но отображается как обычный
GRAYED Элемент меню недоступен и отображается серым цветом
MENUBREAK Горизонтальные меню размещают следующие элементы в новой строке, а вертикальные - в новом столбце
MENUBARBREAK То же, что и предыдущее, но в случае вертикального меню столбцы разделяются вертикальной линией

Попробуем создать описание небольшого меню. Горизонтальное меню (menubar) позволит выбирать подменю "File", "Examples" и конечный элемент "Help". Подменю "File" будет содержать элементы "Open" и "Exit", разделенные горизонтальной линией, а подменю "Examples" - несколько конечных элементов.

Ниже приведен текст скрипта для этого меню:

  MyMenu MENU
   {
     POPUP "&File"
      {
        MENUITEM "&Open", 101
        MENUITEM SEPARATOR
        MENUITEM "E&xit", 102
      }
     POPUP "&Examples"
      {
        POPUP "Example 1"
         {
           MENUITEM "1&1", 103
           MENUITEM "1&2", 104
         }
        POPUP "Example 2"
         {
           MENUITEM "2&1", 105
           MENUITEM "2&2", 106
         }
      }
     MENUITEM "&Help", 111
   }
Следует обратить внимание на то, что идентификаторы действия есть только у MENUITEM'ов. Popup-меню идентификаторов не содержит.
Диалоговые окна и их элементы

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

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

Модальные и немодальные диалоги

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

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

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

Кнопки, списки и прочее

Элемент управления - это ОКНА более низкого по отношению к диалоговому окну уровня. Предлагаю отметить то, что элементы управления никогда не могут использоваться как самостоятельные окна. Они всегда используются на фоне какого-то окна, которое является для них родительским окном. Элементы управления, таким образом, всегда являются дочерними окнами, другими словами, у них всегда присутствует стиль WM_CHILD.

Как и любые другие окна, элементы управления могут получать и выдавать сообщения. Правда, это относится не ко всем элементам управления, но... Стоп! Давайте прервемся на секунду.

Мне бы хотелось обратить внимание читателя на один интересный момент. Для посылки сообщения обычно используют функции SendMessage() и SendDlgItemMessage(). Дело в том, что значение, которое возвращают эти функции, зависит только от того сообщения, которое они отправили. Таким образом, если вам необходимо узнать по возвращенному значению, что произошло в результате обработки того или иного сообщения, ищите описание возвращаемых значений не в описаниях функций, а описаниях сообщений.

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

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

Элементами управления могут быть кнопки (buttons), которые мы уже использовали в окнах сообщений, переключатели (check boxes), селекторы (radio buttons), списки (list boxes), комбинированные списки (combo boxes), линейки проктутки (scroll bars) и статические элементы (statics). Все элементы в этом перечне относятся к категории базовых, и все они присутствовали и в Windows 3.x. На их основе Microsoft разработала серию новых элементов управления (common controls), которые позволили расширить возможности интерфейса с пользователем и улучшить внешний вид приложений. Мы рассмотрим как базовые, так и новые общие (как еще можно перевести на русский язык название "common controls"?) элементы управления.

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

Создание диалогового окна

Диалоговое окно, как и меню, может быть создано несколькими способами: во-первых, с помощью описания его в файле ресурсов и, во-вторых, во время выполнения программы. Наиболее часто используется описание диалога в файле ресурсов. Лучше всего при создании диалого воспользоваться редактором ресурсов, с помощью которого может быть создан текстовый файл, содержащий описание диалогового окна. Ресурсы диалога в этом текстовом файле задаются оператором DIALOG, который имеет следующий формат:

DialogName DIALOG [DISCARDABLE]    X,Y,Width,Height
CAPTION "Заголовок окна"
STYLE <Стили диалогового окна>
FONT n, <имя шрифта>
 {
   Описание элементов диалога
 }
В данном случае DialogName - это имя диалогового окна. Опция DISCARDABLE станет совершенно ясной при рассмотрении вопроса об организации памяти в Windows. Параметры X и Y - это координаты верхнего левого угла диалового окна, Width и Height - ширина и высота диалога. STYLE описывает стили окна. Здесь могут использоваться как стили, применяемые для описания обычных окон, так и стили, применяемые только в диалоговых окнах. Эти новые стили приведены в таблице:

 

Стили диалоговых окон

 
Стиль Значение Эффект
DS_ABSALIGN 0x0001L Положение диалогового окна исчисляется в экранных координатах
DS_SYSMODAL 0x0002L Создается системное модальное диалоговое окно
DS_3DLOOK 0x0004L Создается диалоговое окно, имеющее зрительную иллюзию трехмерности
DS_FIXEDSYS 0x0008L Вместо SYSTEM_FONT используется SYSTEM_FIXED_FONT
DS_NOFAILCREATE 0x0010L Диалоговое окно создается, несмотря на то, что при его создании произошли ошибки
DS_LOCALEDIT 0x0020L В 32-битных приложениях не используется
DS_SETFONT 0x0040L Определяет шрифт, который будет применятся в диалоговом окне
DS_MODALFRAME 0x0080L Создается модальное диалоговое окно
DS_NOIDLEMSG 0x0100L
DS_SETFOREGROUND 0x0200L Поместить диалоговое окно на передний план
DS_CONTROL 0x0400L
DS_CENTER 0x0800L Диалоговое окно помещается в центр рабочей области
DS_CENTERMOUSE 0x1000L
DS_CONTEXTHELP 0x2000L

Приведенных выше сведений вполне достаточно, чтобы написать заготовку диалогового окна в файле ресурсов. Но какой смысл описывать диалоговое окно, если в нем нет ни одного из элементов управления? Ведь даже закрыть такое диалоговое окно (если в нем, конечно, нет системного меню) невозможно! Значит, нам необходимо срочно научиться описывать эти элементы!

Я уже упоминал о том, что в "недрах" Win32 есть масса предопределенных объектов. В частности, там находятся и некоторые предопределенные классы окон. К таким классам относятся кнопки (класс "button"), списки (класс "listbox"), комбинированные списки (класс "combobox"), окна редактирования (класс "edit"), полосы прокрутки (класс "scrollbar"), статистические элементы (класс "static"). У каждого класса есть свой определенный набор стилей, которые определяют внешний вид и поведение элементов управления, относящихся к данному классу.

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

Кнопки

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

Кнопка - это имитация на экране обычной кнопки или переключателя. В этом разделе под кнопками я также подразумеваю не только PushButons (обычные нажимаемые кнопки), но и Check Boxes (обычно это небольшие квадратики, в которых можно установить или не установить пометку) и Radio Buttons (небольшие кружочки, В ОДНОМ из которых стоит точка). Пользователь может установить курсор мыши на кнопку, щелкнуть клавишей мыши - и кнопка пошлет диалоговому окну сообщение WM_COMMAND. То же произойдет и в случае, если пользователь сначала выберет кнопку клавишей Tab, а потом нажмет Enter.

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

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

Говорит о состоянии одной RadioButton бессмысленно. Дело в том, что RadioButton'ы предназначены для осуществления выбора одного из нескольких взаимоисключаемых вариантов, поэтому можно говорить о группе (иногда говорят кластере) RadioButton'ов. В частности, для объединения RadioButton'ов в кластеры служит такой элемент управления, как группа (GroupBox). Обычно группы используются для группирования органов управления только для улучшения дизайна диалоговых окон. Что же касается RadioButton'ов, то без обрамляющей группы существование кластера не имеет смысла.

Формат описания кнопок в окне диалога достаточно прост:

  CONTROL  "Заголовок", ButtonID, class, styles, X, Y, Width, Height
X, Y, Width, Height - это все ясно. Все то же самое, что и при описании непосредственно диалогового окна. "Заголовок" - надпись на кнопке или рядом с кнопкой. ButtonID - идентификатор кнопки, т.е. значение, которое посылается диалоговому окну при нажатии кнопки в качестве LOWORD(wParam). Через HIWORD(wParam) диалоговое окно получает код нотификации, т.е. код того действия, которое произвел пользователь. Примерами действия пользователя могут служить нажатие клавиши Enter, двойной щелчек правой или левой клавишей мыши и так далее. А источник, т.е. хэндл инициировавшего сообщение окна, сообщения содержится в lParam (я напомню, что если сообщение приходит от меню, то lParam всегда равен 0, а если от акселератора - 1). Все легко и просто. Сложности начинаются при рассмотрении класса, определяющегося полем class, типа кнопки и стиля кнопки, которые определяются параметром style.

Для кнопок, вне зависимости от того, PushButton ли это, RadioButton или CheckBox, класс всегда определяется как "button".

Читатель, наверное, уже привык к тому, что ответы на большинство вопросов можно найти в файлах заголовках и файлах помощи Win32. Поэтому и сейчас, как всегда, смотрим в заголовочный файл winuser.h, выбираем оттуда стили кнопок, которые начинаются с букв BS_, и сводим их в таблицу. Надоело изучать эти таблицы? Ничего, тяжело в учении - легко в бою! © А.В. Суворов, "Наука побеждать".

 

Стили кнопок

Флаг Значение Эффект
BS_PUSHBUTTON 0x00000000L Создается обычная кнопка
BS_DEFPUSHBUTTON 0x00000001L Создается обычная кнопка, которая срабатывает при нажатии "Enter" даже тогда, когда не выбрана
BS_CHECKBOX 0x00000002L Создается CheckBox, при нажатии состояние автоматически не изменяется, забота об этом ложится на программу
BS_AUTOCHECKBOX 0x00000003L Создается CheckBox, который автоматически меняет свое состояние при нажатии
BS_RADIOBUTTON 0x00000004L Создается Radio Button, автоматически состояние не меняется
BS_3STATE 0x00000005L То же, что и BS_CHECKBOX, но имеет три состояния - включенное, отключенное и неопределенное, автоматически состояние не меняет
BS_AUTO3STATE 0x00000006L То же, что и предыдущее, но состояние меняется автоматически
BS_GROUPBOX 0x00000007L Группа
BS_USERBUTTON 0x00000008L Устаревший стиль, необходимо использовать BS_OWNERDRAW
BS_AUTORADIOBUTTON 0x00000009L То же, что и RadioButton, но при нажатии состояние меняется автоматически
BS_OWNERDRAW 0x0000000BL За прорисовку кнопки отвечает программа, а не система
BS_LEFTTEXT 0x00000020L Текст помещается слева от RadioButton'а или CheckBox'а, то же, что и BS_RIGHTBUTTON
BS_TEXT 0x00000000L Внутри или рядом с кнопкой отображается текст
BS_ICON 0x00000040L Внутри кнопки или рядом с кнопкой отображается иконка
BS_BITMAP 0x00000080L Внутри кнопки или рядом с кнопкой отображается bitmap
BS_LEFT 0x00000100L Размещает текст у левого края прямоугольника, выделенного для размещения кнопки
BS_RIGHT 0x00000200L Размещает текст у правого края прямоугольника, выделенного для размещения кнопки
BS_CENTER 0x00000300L Размещает текст по горизонтали в центре прямоугольника, выделенного для размещения кнопки
BS_TOP 0x00000400L Размещает текст у верхнего края прямоугольника, выделенного для размещения кнопки
BS_BOTTOM 0x00000800L Размещает текст у нижнего края прямоугольника, выделенного для размещения кнопки
BS_VCENTER 0x00000C00L Размещает текст по вертикали в центре прямоугольника, выделенного для размещения кнопки
BS_PUSHLIKE 0x00001000L Делает CheckBox или RadioButton внешне похожими на PushButton
BS_MULTILINE 0x00002000L При необходимости текст разбивается на несколько строк
BS_NOTIFY 0x00003000L Разрешает посылку родительскому окну нотификационных сообщений BN_DBLCLK, BN_KILLFOCUS и BN_SETFOCUS
BS_FLAT 0x00008000L Не добавляется имитация трехмерности изображения элемента управления
BS_RIGHTBUTTON 0x00000020L RadioButton или CheckBox размещаются справа от надписи (то же, что и BS_LEFTTEXT)
 

Icon


Для использования собственной иконки нужно создать файл ресурса:

-->Begin icon.rc <--

  ICON_1 ICON 180.ico
  ICON_SM ICON 163.ico

-->End of icon.rc<--

Затем icon.rc откомпилировать с помощью какого-нибудь компилятора ресурсов(res-файл должен быть 32-bit Windows compatible). Возьмем brcc32.exe:

  brcc32 icon.rc
Получим icon.res - понадобится при линковании.

 

WNDCLASSEX struct
  clSize           dword    ?
  clStyle          dword    ?
  clLpfnWndProc    dword    ?
  clCbClsExtra     dword    ?
  clCbWndExtra     dword    ?
  clHInstance      dword    ?
  clHIcon          dword    ?
  clHCursor        dword    ?
  clHbrBackground  dword    ?
  clLpszMenuName   dword    ?
  clLpszClassName  dword    ?
  clHIconSm        dword    ?
WNDCLASSEX ends

clHIcon: Содержит дискриптор иконки получаемый после вызова LoadIcon.
clHIconSm: Тоже самое для маленькой иконки, если null то используется hIcon.

--------------------------------------------------
.data
  szIconName       db 'ICON_1', 0
  szIconSmName     db 'ICON_SM', 0
  ...
--------------------------------------------------
.code
  ...
  push    offset szIconName
  push    [hInst]
  call    LoadIconA
  mov     [wc.clHIcon], eax

  push    offset szIconSmName
  push    [hInst]
  call    LoadIconA
  mov     [wc.clHIconSm], eax
  ...
  регистируем класс, создаем окно...
---------------------------------------------------

При линковании нужно указать файл ресурса:

  tlink32 [options] my.obj,my.exe,,,,my.res

Menu 1


Общий вид ресурса меню:

MyMenu  MENU
{
  [menu list]
}
или
MyMenu  MENU
BEGIN
  [menu list]
END
Поле [menu list] может быть MENUITEM или POPUP. Синтаксис MENUITEM:
  MENUITEM "&text", ID [,options]
&text - название пункта меню, амперсанд ставится перед подчеркиваемым символом.
ID - уникальный идентификатор, будет передоваться процедуре окна(wparam), если этот пункт меню выберут.
Параметры options:
  GRAYED - Недоступный пункт меню, текст выводится серым цветом.
  INACTIVE - Тоже недоступный пункт меню, но текст нормальный.
  MENUBREAK - Этот пункт и следующие пункты появляются в новой строке меню.
  HELP - Пункт меню и следующие пункты выводятся в правой части строки меню.

Синтаксис POPUP:
POPUP "&text" [,options]
{
  [menu list]
}
MENUITEM SEPARATOR - разделитель для отделения элементов меню.
Далее при регистрации класса в поле clsLpszMenuName указать на строку с названием ресурса меню.
Пример:
-- Begin menu.rc --
#define IDM_1 1
#define IDM_2 2
#define IDM_EXIT 3
#define IDM_ABOUT 4
MENU_1 MENU
    BEGIN
      POPUP "File"
        BEGIN
             MENUITEM "MenuItem &GRAYED",  IDM_1, GRAYED
             MENUITEM "MenuItem &INACTIVE",  IDM_2, INACTIVE
             MENUITEM SEPARATOR
             MENUITEM "&Exit",  IDM_EXIT
        END
      MENUITEM "&About",  IDM_ABOUT
    END
-- End of menu.rc --

-- Begin cr_menu1.asm --
.386
.model flat, stdcall
include win32.inc

extrn            CreateWindowExA:PROC
extrn            DefWindowProcA:PROC
extrn            DispatchMessageA:PROC
extrn            ExitProcess:PROC
extrn            GetMessageA:PROC
extrn            GetModuleHandleA:PROC
extrn            LoadCursorA:PROC
extrn            LoadIconA:PROC
extrn            MessageBoxA:PROC
extrn            PostQuitMessage:PROC
extrn            RegisterClassA:PROC
extrn            ShowWindow:PROC
extrn            TranslateMessage:PROC
extrn            UpdateWindow:PROC

IDM_1     EQU 1
IDM_2     EQU 2
IDM_EXIT  EQU 3
IDM_ABOUT EQU 4

.data

newhwnd          dd 0
msg              MSGSTRUCT   
wc               WNDCLASS    

hInst            dd 0

szMenuName       db 'MENU_1', 0  <<название ресурса>>
szTitleName      db 'Win32 Assembly Program', 0
szClassName      db 'ASMCLASS32',0
mb_message       db 'Win32 Assembler: Menu',0
mb_title         db 'About',0

.code
;----------------------------------------------------------------------
start:

        push    0
        call    GetModuleHandleA        ; get hmod (in eax)
        mov     [hInst], eax            ; hInstance is same as HMODULE
                                        ; in the Win32 world
; initialize the WndClass structure
        mov     [wc.clsStyle], CS_HREDRAW + CS_VREDRAW + CS_GLOBALCLASS
        mov     [wc.clsLpfnWndProc], offset WndProc
        mov     [wc.clsCbClsExtra], 0
        mov     [wc.clsCbWndExtra], 0

        mov     eax, [hInst]
        mov     [wc.clsHInstance], eax

        push    IDI_APPLICATION
        push    0
        call    LoadIconA
        mov     [wc.clsHIcon], eax

        push    IDC_ARROW
        push    0
        call    LoadCursorA
        mov     [wc.clsHCursor], eax

        mov     [wc.clsHbrBackground], COLOR_WINDOW + 1
        mov     dword ptr [wc.clsLpszMenuName], offset szMenuName
        <<указываем смещение на строку с названием ресурса>>
        mov     dword ptr [wc.clsLpszClassName], offset szClassName

        push    offset wc
        call    RegisterClassA

        push    0                        ; lpParam
        push    [hInst]                  ; hInstance
        push    0                        ; menu
        push    0                        ; parent hwnd
        push    CW_USEDEFAULT            ; height
        push    CW_USEDEFAULT            ; width
        push    CW_USEDEFAULT            ; y
        push    CW_USEDEFAULT            ; x
        push    WS_OVERLAPPEDWINDOW      ; Style
        push    offset szTitleName       ; Title string
        push    offset szClassName       ; Class name
        push    0                        ; extra style

        call    CreateWindowExA

        mov     [newhwnd], eax

        push    SW_SHOWNORMAL
        push    [newhwnd]
        call    ShowWindow

        push    [newhwnd]
        call    UpdateWindow

msg_loop:
        push    0
        push    0
        push    0
        push    offset msg
        call    GetMessageA

        cmp     ax, 0
        je      end_loop

        push    offset msg
        call    TranslateMessage

        push    offset msg
        call    DispatchMessageA

        jmp     msg_loop

end_loop:
        push    [msg.msWPARAM]
        call    ExitProcess


;--------------------------------------------------------------
WndProc          proc uses ebx edi esi, hwnd:DWORD, wmsg:DWORD,
                 wparam:DWORD, lparam:DWORD

        cmp     [wmsg], WM_DESTROY
        je      wmdestroy
        cmp     [wmsg], WM_COMMAND
        je      wmcommand

        push    [lparam]
        push    [wparam]
        push    [wmsg]
        push    [hwnd]
        call    DefWindowProcA
        jmp     finish

wmdestroy:
        push    0
        call    PostQuitMessage
        mov     eax, 0
        jmp     finish

wmcommand:
        mov     eax, [wparam]
        cmp     eax, IDM_ABOUT
        jne     wmnext
        push   0
        push   offset mb_title
        push   offset mb_message
        push   0
        call   MessageBoxA
wmnext:
        cmp     eax, IDM_EXIT
        je      wmdestroy

finish:
        ret
WndProc          endp
ends
end start
-- End of cr_menu1.asm --

Menu 2


Меню можно добавить в процессе создания окна, использовав при этом аргумент hMenu - дискриптор меню. Его возращает функция LoadMenu.

==>Begin cr_menu2.asm<==
.386
.model flat, stdcall
include win32.inc

extrn            CreateWindowExA:PROC
extrn            DefWindowProcA:PROC
extrn            DispatchMessageA:PROC
extrn            ExitProcess:PROC
extrn            GetMessageA:PROC
extrn            GetModuleHandleA:PROC
extrn            LoadCursorA:PROC
extrn            LoadIconA:PROC
extrn            LoadMenuA:PROC
extrn            MessageBoxA:PROC
extrn            PostQuitMessage:PROC
extrn            RegisterClassA:PROC
extrn            ShowWindow:PROC
extrn            TranslateMessage:PROC
extrn            UpdateWindow:PROC


IDM_1     EQU 1
IDM_2     EQU 2
IDM_EXIT  EQU 3
IDM_ABOUT EQU 4

.data

newhwnd          dd 0
hMenu            dd ?
msg              MSGSTRUCT   
wc               WNDCLASS    

hInst            dd 0

szMenuName       db 'MENU_1', 0
szTitleName      db 'Win32 Assembly Program', 0
szClassName      db 'ASMCLASS32',0
mb_message       db 'Win32 Assembler: Menu',0
mb_title         db 'About',0

.code
;-----------------------------------------------------------------------------
start:

        push    0
        call    GetModuleHandleA        ; get hmod (in eax)
        mov     [hInst], eax            ; hInstance is same as HMODULE
                                        ; in the Win32 world
; initialize the WndClass structure
        mov     [wc.clsStyle], CS_HREDRAW + CS_VREDRAW + CS_GLOBALCLASS
        mov     [wc.clsLpfnWndProc], offset WndProc
        mov     [wc.clsCbClsExtra], 0
        mov     [wc.clsCbWndExtra], 0

        mov     eax, [hInst]
        mov     [wc.clsHInstance], eax

        push    IDI_APPLICATION
        push    0
        call    LoadIconA
        mov     [wc.clsHIcon], eax

        push    IDC_ARROW
        push    0
        call    LoadCursorA
        mov     [wc.clsHCursor], eax

        push    offset szMenuName
        push    [hInst]
        call    LoadMenuA
        mov     [hMenu], eax

        mov     [wc.clsHbrBackground], COLOR_WINDOW + 1
        mov     dword ptr [wc.clsLpszMenuName], 0
        mov     dword ptr [wc.clsLpszClassName], offset szClassName

        push    offset wc
        call    RegisterClassA

        push    0                        ; lpParam
        push    [hInst]                  ; hInstance
        push    [hMenu]                  ; menu
        push    0                        ; parent hwnd
        push    CW_USEDEFAULT            ; height
        push    CW_USEDEFAULT            ; width
        push    CW_USEDEFAULT            ; y
        push    CW_USEDEFAULT            ; x
        push    WS_OVERLAPPEDWINDOW      ; Style
        push    offset szTitleName       ; Title string
        push    offset szClassName       ; Class name
        push    0                        ; extra style

        call    CreateWindowExA

        mov     [newhwnd], eax

        push    SW_SHOWNORMAL
        push    [newhwnd]
        call    ShowWindow

        push    [newhwnd]
        call    UpdateWindow

msg_loop:
        push    0
        push    0
        push    0
        push    offset msg
        call    GetMessageA

        cmp     ax, 0
        je      end_loop

        push    offset msg
        call    TranslateMessage

        push    offset msg
        call    DispatchMessageA

        jmp     msg_loop

end_loop:
        push    [msg.msWPARAM]
        call    ExitProcess


;-----------------------------------------------------------------------------
WndProc          proc uses ebx edi esi, hwnd:DWORD, wmsg:DWORD,
                 wparam:DWORD, lparam:DWORD

        cmp     [wmsg], WM_DESTROY
        je      wmdestroy
        cmp     [wmsg], WM_COMMAND
        je      wmcommand

        push    [lparam]
        push    [wparam]
        push    [wmsg]
        push    [hwnd]
        call    DefWindowProcA
        jmp     finish

wmdestroy:
        push    0
        call    PostQuitMessage
        mov     eax, 0
        jmp     finish

wmcommand:
        mov     eax, [wparam]
        cmp     eax, IDM_ABOUT
        jne     wmnext
        push   0
        push   offset mb_title
        push   offset mb_message
        push   0
        call   MessageBoxA
wmnext:
        cmp     eax, IDM_EXIT
        je      wmdestroy

finish:
        ret
WndProc          endp
ends
end start
==>End of cr_menu2.asm<==

Menu 3


Меню можно создать динамически, без создания ресурса.
Программа cr_menu3.asm имеет в памяти шаблон меню, потом из шаблона создаст "объект" меню с помощью LoadMenuIndirect, которая вернет дискриптор меню. Меню может затем быть добавлено при создании окна, устанавливая дискриптор меню в соответствующий аргумент CreateWindowEx.

Вы можете также создать окно без меню и потом дополнить используя SetMenu.
Эта функция API может также использоваться для изменения меню. Меню могут удалить используя NULL вместо дискриптора меню.

        push    offset MenuTemplate ;шаблон меню
        call    LoadMenuIndirectA
        mov     [hMenu], eax        ;hMenu - menu handle
Шаблон меню состоит из заголовка и списка пунктов меню.
MENUEX_TEMPLATE_HEADER struct
    wVersion       dw ?
    wOffset        dw ?
    dwHelpId       dd ?
MENUEX_TEMPLATE_HEADER ends
wVersion - версия шаблона, должна быть равна 1.
wOffset - смещение относительно конца этого поля. Если первое определение пункта меню следует за dwHelpId, то wOffset должен быть 4.
dwHelpId - идентификатор подсказки.

Заголовок должен выравниваться по DWORD границе.
Пункт меню:
 typedef struct {
     DWORD  dwType;
     DWORD  dwState;
     UINT   uId;
     WORD   bResInfo;
     WCHAR  szText[1];
     // DWORD dwHelpId;
 } MENUEX_TEMPLATE_ITEM;
dwType - тип пункта меню.
dwState - состояние пункта меню.
UINT - уникальный идентификатор.
bResInfo - величина введенная для обозначения последнего пункта меню, подменю...
szText - текст пункта меню. Это Unicode строка оканчивающаяся 0, выравнивается по WORD границе.
dwHelpId - идентификатор подсказки. Нужен только при создании POPUP меню.

Пример:
MenuTemplate      MENUEX_TEMPLATE_HEADER<1,4,0>

                  ; &File
                  dd MFT_STRING  ;     Type
                  dd MFS_ENABLED ;     State
                  dd 0           ;     Id
                  dw MFR_POPUP   ;     ResInfo
                  dw '&','F','i','l','e',0,0   ;     Text
                  dd 0           ;     HelpId

                  ; MenuItem &GRAYED
                  dd MFT_STRING  ;     Type
                  dd MFS_GRAYED  ;     State
                  dd IDM_1       ;     Id
                  dw 0           ;     ResInfo
                  dw 'M','e','n','u','I','t','e','m',' '   ; Text
                  dw '&','G','R','A','Y','E','D',0
                  ; dd 0 - HelpId only with popup menu

                  ; MenuItem &DISABLED
                  dd MFT_STRING  ;     Type
                  dd MF_DISABLED ;     State
                  dd IDM_2       ;     Id
                  dw 0           ;     ResInfo
                  dw 'M','e','n','u','I','t','e','m',' '   ; Text
                  dw '&','D','I','S','A','B','L','E','D',0
                  ; dd 0         ;     HelpId

                  ; ------------------
                  dd MFT_SEPARATOR,0,0
                  dw 0,0

                  ; E&xit
                  dd MFT_STRING  ;     Type
                  dd MFS_ENABLED ;     State
                  dd IDM_EXIT    ;     Id
                  dw MFR_END     ;     ResInfo
                  dw 'E','&','x','i','t',0,0    ; Text
                  ; dd 0         ;     HelpId

                  ; &About
                  dd MFT_STRING  ;     Type
                  dd MFS_ENABLED ;     State
                  dd IDM_ABOUT   ;     Id
                  dw MFR_END     ;     ResInfo
                  dw '&','A','b','o','u','t',0    ; Text
                  ;  dd 0        ;     HelpId

 

 

Menu 4


Установить новое меню можно с помощью функции SetMenu, указав ей дескриптор нового меню.
Вот пример:

  push    offset szMenuName2 ;новое меню
  push    [hInst]
  call    LoadMenuA ;получаем hMenu
;eax - hMenu
  push    eax
  push    [hwnd]    ; handle текущего окна
  call    SetMenu   ; устанавливаем меню
Для удаления меню нужно использовать DestroyMenu. В отличие от SetMenu с hMenu=NULL, DestroyMenu освобождает память.
  push    [hwnd]
  call    GetMenu   ; получим hMenu
;eax - hMenu
  push    eax
  call    DestroyMenu

  push    [hwnd]
  call    DrawMenuBar ;перерисуем меню, точнее очистим