Откровенно говоря, когда программист впервые сталкивается с необходимостью использования ресурсов в своем коде, каких-либо особых трудностей у него не возникает, благо в Сети имеется некоторое (не сказать что большое) количество материала, на основании которого можно составить некое начальное представление. Действительно, организация секции ресурсов на уровне описания в исходном коде выполнена на доступном уровне. Тем не менее, существует ряд неудобств, первое из которых заключается в том, что для формата ассемблера FASM перед глазами нет настольного материала, к которому можно время от времени возвращаться за разного рода разъяснениями. Ну а второе (и основное) состоит в совершенно не тривиальной внутренней организации ресурсов приложения на "низком" уровне (уровне исполняемого модуля). Поэтому, думаю будет не лишним обратить внимание на эту тему и составить своего рода памятку по основным терминам и определениям, да и понимание принципов построения ресурсов не является лишним как для разработчика, так и для реверс-инженера (реверсера), изучающего внутреннее устройство Windows.
С какой целью разработчикам из Microsoft потребовалось создавать дополнительную структуру под названием секция ресурсов, по какой причине ресурсы вообще появились на свет? Ведь, если разобраться, любые данные можно свободно описать в основном исходном тексте (или во внешних/подключаемых файлах) привычным способом с помощью констант/переменных? Конечно можно, я например, как тот еще "заправский" программист, очень люблю так вот по старинке (скорее по привычке) определять все данные в коде :) Но у этого решения имеется ряд очевидных недостатков:
- далеко не все используемые приложением типы данных удобно описывать в исходном коде в виде переменных. Ресурсы удобнее использовать в агрументах функций WinAPI;
- при использовании ресурсов удобнее манипулировать данными, размещающимися во внешних файлах;
Конечно же, можно прибегнуть к использованию библиотек. Действительно, в некоторых случаях использование библиотек для описания данных оправдано, но во многих задачах это приводит к избыточному (по отношению к уровню задачи) усложнению логики при реализации таких программных примитивов как меню, диалогов, иконок и растровых изображений, ведь работа с данными библиотеки требует дополнительных "накладных расходов" не только от разработчика, но и от загрузчика образов (системы). Вероятно, было найдено промежуточное решение:
У автора появляется довольно богатый выбор:
- для совсем простых данных использовать константы/переменные;
- для предопределенных данных малой/средней сложности использовать ресурсы;
- для данных повышенной сложности/размера/специфической логики использовать внешние подключаемые модули (файлы, библиотеки);
Помимо всего этого, ресурсы по сравнению с классическими видами данных, имеют ряд концептуальных отличий, заключающихся в следующем:
- Ресурсы компилируются вместе с приложением в единый исполняемый файл и доступны "напрямую" (прямым указанием в аргументах функций) через специальные константы-идентификаторы, определяемые в исходном коде;
- Ресурсы могут быть логически сгруппированы в языковые наборы, у приложения появляется возможность использовать несколько "наборов ресурсов" в зависимости от используемого в данный момент языка.
- Значение того или иного ресурса может быть [легко] изменено в уже скомпилированном бинарном модуле при помощи программ, относящихся к категории редакторов ресурсов;
- Ресурсы в бинарном исполняемом файле создаются в виде трехуровневого, двоично-отсортированного дерева, оптимизированного с целью ускорения обхода;
В зависимости от начального местоположения, описание ресурсов может располагаться:
- в исходном коде приложения: для размещения используется отдельная [ресурсная] секция;
- во внешнем файле: (чаще всего имеющим расширение .rc), подключаемом к приложению на этапе компиляции;
- в памяти процесса: данные ресурсов могут формироваться "с нуля" в виртуальном адресном пространстве процесса приложения "на ходу", непосредственно во время выполнения специализированных функций по созданию ресурсов;
Помимо оговоренных уже основных моментов, целью создания такой структуры как ресурс, является:
- Стандартизация часто используемых в приложениях типов данных. Представьте ситуацию, что вы из раза в раз, на протяжении долгого времени, используете одни и те же самописные программные конструкции для конфигурирования каких-либо элементов (меню, диалогов, изображений и прч.). Это может привести со временем к образованию собственной "библиотеки структур". Не проще ли озвучить разработчику операционной системы (под которой ведется разработка), идею описания всех этих часто используемых структур на системном уровне, посредством создания нового системного механизма?
- Достижение "автономности" ресурсов. Если выделить ресурсы в отдельные файлы, подключаемые к приложению, то можно добиться независимости (модульности) от основного приложения, так называемой "взаимозаменяемости", когда без модификации основного кода можно менять любые аспекты подключаемых данных. К примеру, в многоязычных приложениях используют подключаемые ресурсные библиотеки, они являются фактически единственным удобным способом обеспечения трансляции интерфейса программы под разные языки.
- Обеспечение упрощенного механизма использования стандартизованных данных (иконки, диалоги, курсоры). Можно было бы расположить все эти данные в отдельных файлах, однако представьте себе ситуацию, что без такого механизма как ресурсы мы вынуждены были хранить все их наряду с основным исполняемым модулем (и перемещать при необходимости). Намного проще встроить их внутрь исполняемого файла [программы].
Для описания ресурсов в исходном коде во многих языках применяются специальные директивы, сгруппированные в отведенной для этого области - секции ресурсов. Соответственно, после компиляции исходного кода в исполняемый файл, секция ресурсов создается и в итоговом бинарном файле приложения. Исходя из этого, стоит различать секцию ресурсов по местоположению, и хотя это, по сути, одни и те же данные.
- Секция ресурсов в исходном коде - иерархическая структура (область описания) директив[-контейнеров], предназначенная для определения ресурсов, используемых в коде приложения.
- Секция ресурсов в бинарном исполняемом файле приложения - секция данных, представляющая собой многоуровневое двоичное дерево (индексированный массив), оптимизированное с целью ускорения доступа к данным.
И вот тут то стоит обратить внимание на один достаточно важный момент, осознание которого впоследствии упростит вам понимание структуры секции ресурсов, и, соответственно, принципов ее описания:
- Первый (верхний) уровень -- это Тип (Type);
- Второй уровень -- это Имя (Name);
- Третий уровень -- это Язык (Language);
Из этого следует, что для описания ресурсов должны применяться принципы иерархии, схожие с теми, которые используются в структуре файловой системы: корень, директории, файлы. В случае с ресурсами это интерпретируется в следующем виде:
- корневая директория ресурса;
- ресурсные поддиректории;
- данные ресурса;
Поскольку речь у нас сегодня пойдет о ресурсах в ассемблерном коде диалекта ассемблера FASM, то было бы не лишним отметить, что FASM позволяет размещать ресурсы непосредственно в исходном коде, минимизируя при этом количество используемых при разработке приложения файлов (исходных текстов). Это вполне типичная для современных языков ситуация, поскольку некоторые разработчики предпочитают описывать ресурсы "руками" в исходном коде основного модуля, если проекты настолько незначительны, что большего то и не требуется. Само по себе модульное программирование, идея которого состоит в размещении различных частей приложения в автономных файлах, не всегда удобно, в некоторых ситуациях действительно проще производить описание ресурсов в основном коде программы. Тем не менее, всё зависит от уровня конкретной задачи и предпочтений автора, ведь встречаются и ситуации, когда удобнее описывать ресурсы в подключаемом внешнем файле ресурсов, тут у разработчика, что называется, существует выбор.
Подготовка ресурса может производиться несколькими способами:
- Ручной: разработчик основного приложения, с использованием предпочитаемой среды разработки (читать: текстового редактора), самостоятельно описывает ресурсную секцию при помощи макросов-директив, применяемых для описания того или иного типа ресурса.
- Автоматический: разработчик основного приложения использует специализированную программу, относящуюся к категории редакторов ресурсов. Подобные программы позволяют (относительно) упростить, визуализировать процесс, сделав его более интуитивным.
Секция ресурсов в исходном коде
В этом разделе мы сосредоточимся на основном направлении изучения секции ресурсов, то есть на принципах описания ресурсов в исходном коде нашей программы. В каждом уважающем себя компиляторе существует язык описания ресурсов, в каких-то разработках по своей семантике напоминающий язык C, где то похожий на XML. При помощи директив этого языка разработчик имеет возможность описания данных ресурса, с целью последующего получения доступа к ним из кода приложения путем указания их в качестве аргумента функции Win32 API. Ресурсы как таковые в диалекте ассемблера FASM описываются внутри предназначенной исключительно для этого секции ресурсов.
Давайте приведем типовой (общий) шаблон секции ресурсов в исходном тексте на языке Ассемблера:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
. . . section '.rsrc' resource data readable ; корневая директория ресурсов ------------------------------------------ directory тип_ресурса,имя_поддиректории,\ тип_ресурса,имя_поддиректории,\ тип_ресурса,имя_поддиректории,\ тип_ресурса,имя_поддиректории ; ресурсные поддиректории ----------------------------------------------- resource имя_поддиректории,\ идентификатор,язык,имя_ресурса resource имя_поддиректории,\ идентификатор,язык,имя_ресурса resource имя_поддиректории,\ идентификатор,язык,имя_ресурса resource имя_поддиректории,\ идентификатор,язык,имя_ресурса ; макросы ресурсов ------------------------------------------------------ макроопределение_ресурса имя_ресурса макроопределение текст,идентификатор,стиль макроопределение текст,идентификатор макроопределение макроопределение имя,идентификатор,стиль макроопределение текст,идентификатор,стиль макроопределение имя,идентификатор,стиль макроопределение_ресурса имя_ресурса,имя_ресурса,стиль макроопределение_ресурса имя_ресурса,идентификатор,стиль,язык,\ текст,текст,\ текст,текст,\ текст,текст,\ . . . |
Описание начинается с определения секции при помощи директивы section
, и указываемой за ней серии параметров: название (.rsrc), тип (resource data), атрибуты доступа (readable). Местоположение секции ресурсов в исходном коде может быть выбрано произвольно, однако распространенная практика да и просто хорошая идея состоит в том, что бы определить секцию ресурсов в конце исходного кода, в хвостовой части файла.
За описателем секции следует набор специальных макросов (макродиректив). По описанию видно, что структура секции ресурсов представляет собой набор директив[-контейнеров], которые размещаются в секции определенным порядком от "корня" дерева к "ветвям", а далее к "листьям":
- Корневая директория: содержит перечисление всех ресурсов приложения;
- Ресурсные поддиректории: определяет поддиректории ресурсов, в которых группируются те или иные ресурсы;
- Данные ресурсов: содержатся непосредственно данные ресурсов;
На самом верху секции, описывается корень дерева, задаваемый первой директивой под именем directory
, говорящее название которой относит нас к тому, что структура ресурсов очень схожа с другой известной структурой из области операционных систем - файловой системой. Таким образом directory
определяет ни что иное как корневую директорию ресурсов (по аналогии с корневой директорией файловой системы). Сразу за корневой директорией следуют описания вложенных поддиректорий разных уровней, а следом уже и непосредственно самих ресурсов. Давайте подробнее рассмотрим ключевые директивы (макросы), используемые для описания ресурсов на верхних уровнях дерева ресурсов:
Наименование | Определение |
---|---|
Макроопределение directory |
Каталог ресурсов. Корневая директория, в которую включаются все существующие в приложении ресурсы, то есть в ней перечисляются и описываются все ресурсы, задаваемые в секции ресурсов приложения. Макрос directory указывается первым после описателя секции, в самом начале секции ресурсов. После макроса directory следуют пары, разделенные запятыми: первое в паре — тип ресурса (к какой категории относится), второе — имя поддиректории ресурса, содержащей ресурсы указанного типа. Фактически директива определяет все типы ресурсов, используемых в приложении. |
Макроопределение resource |
Точка входа каталога. Поддиректория идентификаторов, содержащая записи вложенных ресурсов (или поддиректорий). После определения resource следует имя поддиректории, за ним следуют группы из трех параметров ресурса. Давайте опишем их подробнее:
К одному имени поддиректории идентификаторов может быть привязано несколько трехпараметральных групп. |
Тип ресурса, задаваемый в качестве параметра в директиве directory
, является ни чем иным, как предопределенной системной константой, описанной в файлах \INCLUDE\QUATES\kernel32.inc и \INCLUDE\QUATES\kernel64.inc. Приведем основные типы:
Тип ресурса | Описание |
---|---|
RT_CURSOR | Аппаратный курсор. Термин "аппаратный курсор" (Hardware-dependent cursor) означает, что отрисовкой этого типа курсора занимается не программный слой операционной системы, а напрямую графический процессор, что позволяет ускорить обработку и исключить дополнительную логику по перерисовке видеобуфера. Задает растровое изображение, определяющее вид обычного аппаратного курсора. Растровое изображение курсора должно быть в формате .cur. По принципу применения аналогичен типу RT_ICON. |
RT_BITMAP | Изображение. Задает растровое изображение, которое может применяться в качестве фона/формы любого графического элемента окна. Форматом растрового изображения является bmp (Bitmap Picture). |
RT_ICON | Иконка (пиктограмма) для приложения. Пиктограмма, отображаемая в различных частях интерфейса приложения/проводника: заголовочной части окна приложения, в проводнике, непосредственно напротив имени файла. |
RT_MENU | Меню. Задает перечень используемых пунктов быстрого доступа к различным частям функционала приложения. Визуально меню можно представить в виде дерева, корень которого отображается в верхней части окна, непосредственно под шапкой, в все элементы сгруппированы и доступны через ниспадающие окна. |
RT_DIALOG | Диалог. Задает шаблон диалогового окна, на основе которого создается окно диалогового типа. Включает в себя параметры всех дочерних статических/динамических элементов, размещаемых в диалоговом окне. |
RT_STRING | Таблица строк. Данный тип ресурса позволяет описывать массивы групп строк с архитектурой, приближенной к базам данных. Строки хранятся в формате Unicode с лидирующим счетчиком длины, тем самым отличаясь от привычных нам нуль-терминированных строк. Каждый ресурс данного типа представляет собой группу из 16 строк с соответствующим уникальным идентификатором каждой строки. Обычно подобный тип ресурса используется для отображения текста в информационных окнах, подсказках, списках или строках состояния. Однако кроме текста могут располагаться управляющие символы и заменители, плейсхолдеры (например, %d или %s и прочие). |
RT_FONTDIR | Набор (местоположение) шрифтов. Ресурсы шрифтов отличаются от других типов ресурсов тем, что они обычно обычно не добавляются к ресурсам того или иного приложения. Вместо этого, на этапе компиляции формируется специфический .exe-файл, который фактически является библиотекой, а не приложением. Часто расширение полученных таким образом файлов меняют на .fon. |
RT_FONT | Шрифт. Определяет файл, содержащий шрифт. |
RT_ACCELERATOR | Таблица клавиш-ускорителей. Так называемый ускоритель (акселератор) позволяет задавать горячие клавиши для различных элементов меню. Таким образом, при использовании комбинаций как бы ускоряется работа с меню. |
RT_RCDATA | Нестандартный (нетипизированный) ресурс. Данный тип ресурса может быть использован для описания любых нестандартных (сырых) бинарных данных, не стандартизированных в спецификации формата PE и Win32, которые приложение может использовать по своему усмотрению. Например, удобно применять для размещения форматов mp3, avi, midi, wav, png и прочих. |
RT_MESSAGETABLE | Таблица сообщений. Таблицы сообщений это специальные строковые ресурсы, в котором строки размещается в виде массива с соответствующим индексом. Обычно подобная структура удобна для вывода сообщений, привязанных к коду события. |
RT_GROUP_CURSOR | Программный курсор. Может применяться для описания набора курсоров под разные разрешения/масштабы элементов рабочего стола. Почему в данном случае Microsoft использует термин "Hardware-independent" (аппаратно-независимый или программный) не совсем понятно, скорее всего при задании групп курсоров для различных разрешений, в управлении этим видами используется программный слой операционной системы? |
RT_GROUP_ICON | Коллекция иконок (пиктограмм). Этот тип ресурса должен быть связан с одним/несколькими ресурсам типа RT_ICON, что дает возможность объявлять иконки различных параметров (размеров, цвета) под общим идентификатором ресурса. |
RT_VERSION | Версии. Поля, доступные через контекстное меню проводника, описывающие некоторые сведения о приложении: описание, тип, версия, название, авторские права и прч. |
RT_DLGINCLUDE | Данный тип ресурса используется для ассоциации ресурсной строки со внешним .rc-файлом. Строка представляет собой имя заголовочного файла, содержащего символические имена ресурсов. В проектах FASM никогда не наблюдал, пишут, что используется для внутренних нужд компиляторов ресурсов. |
RT_PLUGPLAY | Ресурс Plug-and-Play. |
RT_VXD | Вероятно как то относится к VXD - модели драйверов устройств, используемой в операционных системах Windows 3.1x/95/98/Me. |
RT_ANICURSOR | Анимированный курсор |
RT_ANIICON | Анимированная иконка |
RT_HTML | HTML-ресурс. |
RT_MANIFEST | Манифест сборки. Относится к механизму WinSxS (Side-by-Side). Манифест представляет собой документ формата XML (внешний XML-файл или же внутренний ресурс), определяющий имена общих и приватных сборок (в том числе и библиотек), с которыми приложение должно быть "связано" на этапе загрузки/выполнения. Фактически манифест определяет зависимость исполняемого файла от версий тех или иных библиотек. |
Далее, для описания уже непосредственно различных типов ресурсов, в FASM используются такие макросы как: bitmap, icon, cursor, menu, menuitem, menuseparator, dialog, dialogitem, enddialog и некоторые другие.
Меню
Пожалуй данный вид объектов графического интерфейса без преувеличения можно назвать одним из самых распространенных, поскольку меню является одним из базовых механизмов в концепции оконной подсистемы Windows. Во многих оконных приложениях меню представляет собой традиционный (часто просто необходимый) элемент оформления, поскольку обеспечивает классический, исторически сложившийся и доказавший свою состоятельность способ навигации по функциональным блокам приложения. В исходном коде меню представляет собой группу значений, описывающих пункты, при активации которых (посредством клавиатуры/мыши) вызываются соответствующие обработчики, обеспечивающие тот или иной функционал. Это один из самых частоиспользуемых элементов интерфейса, с которым пользователь приложения (без преувеличения) постоянно имеет дело.
Для начала давайте приведем пример описания элементов простого меню в секции ресурсов:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
. . . section '.rsrc' resource data readable ; --- (корневая) директория ресурсов ------------------- directory RT_MENU,menus, ; --- поддиректории ресурсов --------------------------- resource menus,\ IDR_MENU,LANG_ENGLISH+SUBLANG_DEFAULT,main_menu ; --- ресурсы ------------------------------------------ menu main_menu menuitem '&File',0,MFR_POPUP menuitem '&New',IDM_NEW menuseparator menuitem 'E&xit',IDM_EXIT,MFR_END menuitem '&Help',0,MFR_POPUP + MFR_END menuitem '&About...',IDM_ABOUT,MFR_END . . . |
В приведенном выше примере menus является назначенным разработчиком именем поддиректории меню, main_menu - имя ресурса, а константы IDR_MENU, IDM_NEW и IDM_ABOUT это заданные разработчиком константы-идентификаторы ресурса меню, описываемые в произвольном месте исходного кода и необходимые для того, чтобы с элементами можно было программно взаимодействовать (например, обрабатывать нажатия на пункты меню в коде приложения). Далее в разделе описания ресурсов следуют несколько новых макроопределений, который не мешало бы изучить:
Макроопределение | Описание |
---|---|
menu |
Ресурс меню. Макрос menu описывает структуру меню приложения. В качестве аргумента к макросу используется имя ресурса меню. Фактически это информации, определяющая внешний вид и функциональные особенности меню приложения. В качестве единственного аргумента макрос использует имя ресурса меню для связывания. |
menuitem |
Ресурс меню. Макрос menuitem описывает пункт меню. Аргументы: текст пункта (отображаемый в окне меню), идентификатор, опции. |
menuseparator |
Горизонтальная линия-разделитель пунктов. Макрос menuseparator описывает разделитель, представляющий собой обычную горизонтальную линию. Аргументы отсутствуют. |
Макрос menu
является макросом верхнего уровня, определяющим содержимое ресурса меню. Вложенный подмакрос menuitem
имеет следующий синтаксис:
menuitem текст, идентификатор, [опции]
Макрос menuitem
, описывающий пункт меню, может иметь до пяти параметров. Первые два параметра являются обязательными, а остальные три опциональными. Первый параметр — строка, содержащая текст пункта меню; Второй — уникальный идентификатор пункта, который будет передаваться в сообщении соответствующей оконной процедуре; Третий (необязательный) параметр — это один из двух возможных флагов MFR — MFR_POPUP (всплывающее меню) и MFR_END (последний пункт меню). Четвертый (необязательный) параметр — флаг состояния пункта меню — например, MFS_CHECKED или MFS_DISABLED; Пятый (необязательный) параметр — флаг типа меню MFT. Список этих и других разрешенных параметров меню вы можете найти в файле \INCLUDE\EQUATES\USER32.INC в секции Menu flags
.
Идентификатор | Назначение |
---|---|
MFR_POPUP | Идентификатор, описывающий элемент меню, который открывает другое меню или подменю; |
MFR_END | Идентификатор, описывающие элемент меню, который является последним в этом меню или подменю; |
Однако это только основные стили, за более расширенной информацией можно пройти по ссылке: стили меню
Диалог
Ресурсы играют так же ключевую роль и в описании диалога, поскольку являются одним из наиболее удобных способов описания элементов диалоговых окон. Дело в том, что так называемый шаблон диалога, который используется в качестве массива параметров для настройки как самого диалогового окна, так и вложенных в него элементов, чаще всего размещается разработчиком в секции ресурсов.
Пример описания элементов диалога в секции ресурсов:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
. . . section '.rsrc' resource data readable ; --- (корневая) директория ресурсов ------------------- directory RT_DIALOG,dialogs ; --- поддиректории ресурсов --------------------------- resource dialogs,\ 37,LANG_ENGLISH+SUBLANG_DEFAULT,demonstration ; --- ресурсы ------------------------------------------ dialog demonstration,'Create message box',70,70,190,175,WS_CAPTION+WS_POPUP+WS_SYSMENU+DS_MODALFRAME dialogitem 'STATIC','&Caption:',-1,10,10,70,8,WS_VISIBLE dialogitem 'EDIT','',ID_CAPTION,10,20,170,13,WS_VISIBLE+WS_BORDER+WS_TABSTOP dialogitem 'STATIC','&Message:',-1,10,40,70,8,WS_VISIBLE dialogitem 'EDIT','',ID_MESSAGE,10,50,170,13,WS_VISIBLE+WS_BORDER+WS_TABSTOP+ES_AUTOHSCROLL dialogitem 'BUTTON','&Icon',-1,10,70,80,70,WS_VISIBLE+BS_GROUPBOX dialogitem 'BUTTON','&Error',ID_ICONERROR,20,82,60,13,WS_VISIBLE+BS_AUTORADIOBUTTON+WS_TABSTOP+WS_GROUP dialogitem 'BUTTON','I&nformation',ID_ICONINFORMATION,20,95,60,13,WS_VISIBLE+BS_AUTORADIOBUTTON dialogitem 'BUTTON','&Question',ID_ICONQUESTION,20,108,60,13,WS_VISIBLE+BS_AUTORADIOBUTTON dialogitem 'BUTTON','&Warning',ID_ICONWARNING,20,121,60,13,WS_VISIBLE+BS_AUTORADIOBUTTON dialogitem 'BUTTON','&Style',-1,100,70,80,70,WS_VISIBLE+BS_GROUPBOX dialogitem 'BUTTON','&Top most',ID_TOPMOST,110,82,60,13,WS_VISIBLE+WS_TABSTOP+BS_AUTOCHECKBOX dialogitem 'BUTTON','OK',IDOK,85,150,45,15,WS_VISIBLE+WS_TABSTOP+BS_DEFPUSHBUTTON dialogitem 'BUTTON','C&ancel',IDCANCEL,135,150,45,15,WS_VISIBLE+WS_TABSTOP+BS_PUSHBUTTON enddialog . . . |
Традиционно, в самом начале секции ресурсов у нас присутствует запись каталога ресурсов directory
с заданным типом RT_DIALOG
, в которой описывается ресурс с пользовательским именем dialogs
. В свою очередь, макрос resource
описывает поддиректорию диалога с назначаемым разработчиком идентификатором (в нашем примере 37
), который часто используется в качестве одного из входных параметров функции создания диалога (например: DialogBoxParam
). В коде используется макродиректива dialog
, открывающая область описания шаблона и предназначающаяся для задания параметров самого диалогового окна. Формат макродирективы следующий:
dialog label,title,x,y,cx,cy,style,exstyle,menu,fontname,fontsize
параметры (слева-направо):
Название | Тип | Описание |
---|---|---|
label |
DWORD | Метка диалога. Задает произвольное имя, использующееся для связывания с директорией и ресурсом. |
title |
STRING | Название. Строка, выводимая в заголовке (верхняя строка) окна диалога. |
x |
WORD | Координата X левого верхнего угла диалогового окна. Указывается в единицах диалога? |
y |
WORD | Координата Y левого верхнего угла диалогового окна. Указывается в единицах диалога? |
cx |
WORD | Ширина диалогового окна. |
cy |
WORD | Высота диалогового окна. |
style |
DWORD | Стили диалогового окна. Задают множество свойств окна диалога. |
exstyle |
DWORD | Расширенные стили диалогового окна. Задают дополнительные стили. Были введены в какой версии? |
menu |
DWORD | Указатель на меню. В нашем примере не используется. |
fontname |
DWORD | Строка, описывающая класс шрифта. |
fontsize |
DWORD | Размер шрифта в единицах диалога? |
Описание dialog
должно непременно завершаться парной закрывающей директивой enddialog
, поскольку данная пара директив образует своеобразный контейнер. Вложенные в dialog
макроопределения dialogitem
представляют собой параметры, описывающие все расположенные в окне элементы диалога. Эти макроопределения подобно списку следуют друг за другом и описывают каждый атомарный элемент управления, размещенный в пределах окна. Описатели представляют для нас непосредственный интерес, поскольку в них содержатся все параметры элементов управления. Формат следующий:
dialogitem class,title,id,x,y,cx,cy,style,exstyle
параметры (слева-направо):
Название | Тип | Описание |
---|---|---|
class |
DWORD | Класс элемента управления диалогового окна. Может принимать значения: BUTTON, EDIT, STATIC, LISTBOX, SCROLLBAR, COMBOBOX. |
title |
STRING | Строка, выводимая внутри элемента управления. Например, для клавиши это название, печатаемое непосредственно на самой клавише, для поля редактирования это содержимое поля. |
id |
WORD | Дескриптор (идентификатор) элемента управления. Используется для связи с функциями в коде приложения. Каждый статический/динамический элемент управления (контрол) диалогового окна, доступ к которому вы хотите иметь в коде приложения, должен иметь собственный уникальный идентификатор. Значение -1 (0FFFFh) отключает использование идентификатора у элемента управления. |
x |
WORD | Координата X левого верхнего угла элемента управления. Указывается в единицах диалога? |
y |
WORD | Координата Y левого верхнего угла элемента управления. Указывается в единицах диалога? |
cx |
WORD | Ширина элемента управления. |
cy |
WORD | Высота элемента управления. |
style |
DWORD | Стили элемента управления. Задают множество свойств элемента. |
exstyle |
DWORD | Расширенные стили элемента управления. Задают дополнительные стили. Были введены в какой версии? |
Иконка
Ресурс иконка является неотъемлемой частью интерфейса как самой операционной системы, так и визуальным атрибутом приложения. Применяется для эстетических целей в качестве персонального визуального идентификатора приложения, придания ему достойного вида. Отображается в проводнике (различные виды просмотра), а так же в неклиентской (служебной) области окна приложения. Обычно данные иконки в специальном формате хранятся в отдельном внешнем файле.
Пример описания элементов иконки в секции ресурсов:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
. . . ; --- (корневая) директория ресурсов ------------------- directory RT_ICON,icons,\ RT_GROUP_ICON,group_icons ; --- поддиректории ресурсов --------------------------- resource icons,\ 1,LANG_NEUTRAL,icon_data resource group_icons,\ IDR_ICON,LANG_NEUTRAL,main_icon ; --- ресурсы ------------------------------------------ icon main_icon,icon_data,'minipad.ico' . . . |
где 1
и IDR_ICON
это задаваемые разработчиком идентификаторы иконок, по которым он в любой момент может обратиться к ресурсу.
Наименование | Определение |
---|---|
Макроопределение icon |
Ресурс иконки. Макрос icon описывает местоположение (размещение) иконки: либо в подключаемом внешнем файле, либо в данных. В агрументах макроса указываются: имя ресурса группы иконок, имя ресурса автономной иконки, имя файла (источника) с иконкой. Понятное дело, что при выполнении исполняемого файла сам внешний файл иконки будет уже не нужен, однако на стадии сборки исполняемого файла компилятор должен откуда то получить данные, дабы сформировать соответствующую поддиректорию секции ресурсов. |
В приведенном выше примере подразумевается, что файл minipad.ico размещается в той же директории, что и файл, содержащий исходный код программы (.asm), однако нет никаких проблем с выбором произвольного местоположения внешних данных для секции ресурсов, поэтому, если у Вас вдруг возникло желание положить иконки в отдельную поддиректорию, то необходимо будет соответствующим образом подкорректировать параметр. При компиляции данные файла ресурсов иконки (пиктограммы) объединяются с типовым объектным файлов приложения, в результате чего получается единый исполняемый файл, который наряду с кодом содержит ресурс "иконка" (пиктограмма).
Секция ресурсов в бинарном файле
Как вы уже догадались, в данном разделе мы будем иметь дело уже не с исходными текстами программы, а заглянем внутрь получающегося в процессе компиляции наших исходных текстов исполняемого бинарного файла и посмотрим, в каком виде ресурсы размещаются уже в нем.
Напомним себе, что исполняемый .exe-файл в операционной системе Windows имеет формат PE (Portable Executable). PE-файл можно образно представить в виде блоков данных, следующих друг за другом:
- заголовок
- секции
- данные
В качестве образца для наглядной демонстрации техники навигации по заголовкам и ресурсным записям, я выбрал .exe-файл одного из собственных простеньких проектов и открыл его на просмотр в шестнадцатеричном редакторе:
Только что виденное нами на скриншоте определенно требует пояснения:
Наименование | Смещение (местное) | Размер | Описание | |
---|---|---|---|---|
DOS_HEADER | 00000000h | 64 байта | Так называемый DOS-заголовок (IMAGE_DOS_HEADER ), использующийся для совместимости с исполняемыми файлами MS-DOS. В последних 4 байтах которого записывается смещение следующей интересующей нас структуры - PE-заголовка (в нашем случае 00000080h ). |
|
PE Header | MS-DOS Stub | 00000040h | 64 байт | Заглушка для MS-DOS. Представляет собой полноценное приложение, запускаемое при вызове файла из MS-DOS. |
PE-сигнатура | 00000080h | 4 байта | ||
COFF Header | 00000084h | <=96 байт | Обязательный заголовок (IMAGE_FILE_HEADER ). Содержит ключевые параметры PE |
|
Optional Header | 00000098h | IMAGE_FILE_HEADER. SizeOfOptionalHeader | Опциональный заголовок | |
Section Table | 00000178h | IMAGE_FILE_HEADER.NumberOfSections * sizeof(IMAGE_SECTION_HEADER) | Таблица секций (IMAGE_SECTION_HEADER ). |
Опциональный (необязательный) заголовок
Структура под названием "опциональный (необязательный) заголовок", или IMAGE_OPTIONAL_HEADER
, начинается с сигнатуры 010Bh
(Magic number).
Опциональный подзаголовок располагается по смещению 98h
относительно начала PE-файла.
Каталог (директории) данных
Внутри опционального заголовка располагается массив описателей каталога данных под названием DataDirectory
(на скриншоте выделен темно-голубым цветом), каждый элемент которого имеет тип IMAGE_DATA_DIRECTORY
и занимает 8 байт.
Структура IMAGE_DATA_DIRECTORY
имеет следующий формат:
1 2 3 4 |
typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; // относительный адрес соответствующей директории DWORD Size; // размер } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY; |
В свете поиска секции ресурсов нас интересует третий (индекс 2
) элемент массива описателей, именуемый как IMAGE_DIRECTORY_ENTRY_RESOURCE и содержащий указатель на искомую секцию. Для данного элемента соответствующий член VirtualAddress является указателем, содержащим относительный виртуальный адрес директории ресурсов, а член Size содержит размер.
Таблица (заголовки) секций
Только что мы получили относительный виртуальный адрес (RVA) ресурсной секции, что далее? Теперь требуется найти секцию, содержащую данный RVA, для этого мы просматриваем следующий подзаголовок PE-файла под названием таблица секций (Section Table
). Таблица секций располагается сразу за директориями данных и представляет собой следующие друг за другом (структуры) заголовки всех секций, присутствующих в конкретном исполняемом файле. Структура выглядит следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
typedef struct _IMAGE_SECTION_HEADER { CHAR[8] Name; DWORD Misc; DWORD VirtualAddress; DWORD SizeOfRawData; DWORD PointerToRawData; // смещение в файле до начала непосредственно данных секций. значение всегда кратно значению параметра FileAlignment из опционального заголовка. DWORD PointerToRelocations; DWORD PointerToLinenumbers; WORD NumberOfRelocations; WORD NumberOfLinenumbers; DWORD Characteristics; } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER; |
..и имеет размер в 40 байт. И вот из таких 40-байтовых заголовков и состоит вся таблица секций.
Полученное ранее из директории данных значение члена VirtualAddress структуры IMAGE_DATA_DIRECTORY
мы сравниваем с аналогичным членом VirtualAddress каждой секции IMAGE_SECTION_HEADER
. Как только мы нашли структуру IMAGE_SECTION_HEADER
, в которой значения эти членов идентичны, член PointerToRawData найденной таким образом структуры содержит смещение первого байта секции ресурсов (относительно начала файла).
000c0000
.Первый уровень: корневая директория ресурсов
Перемещаемся по полученному нами адресу. В первом байте секции ресурсов размещается (главная) структура, имеющая тип IMAGE_RESOURCE_DIRECTORY
, фактически представляющая собой корневую директорию секции ресурсов:
1 2 3 4 5 6 7 8 9 |
struct IMAGE_RESOURCE_DIRECTORY { DWORD Characteristics; DWORD TimeDateStamp; WORD MajorVersion; WORD MinorVersion; WORD NumberOfNamedEntries; // количество типов ресурсов WORD NumberOfIdEntries; } |
В (главной, корневой) структуре IMAGE_RESOURCE_DIRECTORY
имеет значение только поле NumberOfIdEntries, которое содержит количество ресурсных типов, размещенных в PE-заголовке.
IMAGE_RESOURCE_DIRECTORY
и называются нодами или узлами дерева ресурсов.Второй уровень: поддиректории ресурсов
По аналогии с корневой директорией, вторым уровнем иерархии являются аналогичные вложенные структуры (фактически поддиректории) того же типа IMAGE_RESOURCE_DIRECTORY
. Тут уже значащими являются оба поля NumberOfNamedEntries и NumberOfIdEntries, сумма значений которых определяет общее количество записей в текущей (под)директории. В структуре отсутствуют какие-либо указатели на следующую (под)директорию, поскольку записи располагаются в двоичном файле последовательно (друг за другом), непосредственно за описанной записью директории.
Третий уровень: массив записей ресурсов
Внутри директорий располагаются записи. Каждая запись имеет структуру типа IMAGE_RESOURCE_DIRECTORY_ENTRY
, которая описывается следующим образом:
1 2 3 4 5 |
struct IMAGE_RESOURCE_DIRECTORY_ENTRY { long NameId; long *Data; } |
Записи имен располагаются первыми, отсортированы по алфавиту в порядке возрастания, за ними следуют записи идентификаторов в порядке возрастания. Значение поля NameId имеет двойное применение: если самый старший бит (бит знака) обнулен, то младшие 16 бит это идентификатор (ID) ресурса, если же старший бит установлен, то младшие 31 бит образуют смещение от начала данных ресурса до строки имени этого ресурса. Значение указателя *Data так же имеет двойное применение: если старший бит установлен, то оставшиеся 31 бит образует смещение от начала данных текущего ресурса до следующей структуры IMAGE_RESOURCE_DIRECTORY
, фактически до следующего узла (директории) дерева ресурсов. Если бит сброшен, то запись является конечной точкой ветвления, образно "листом" дерева, в этом случае *Data содержит смещение от начала данных ресурса до структуры, описывающей данные, специфичные для этого типа ресурсов.
Четвертый уровень: данных ресурса
И последним уровнем иерархии являются уже сами данные ресурса, которые описываются в структуре:
1 2 3 4 5 6 7 |
struct IMAGE_RESOURCE_DATA_ENTRY { long *Data; long Size; long CodePage; long Reserved; } |
Указатель *Data структуры IMAGE_RESOURCE_DATA_ENTRY
содержит относительный виртуальный адрес (RVA) актуальных данных ресурса.
Выводы
Становится очевидным, что вся описанная структура напоминает файловую систему и навигация по иерархии ресурсных директорий (дереву) схожа с обходом файловой системы. По аналогии, у ресурсов имеется собственная "точка входа", это основная (корневая) директория самого верхнего уровня, которая включает в себя записи, являющиеся поддиректориями. Поддиректории корневой директории описывают все типы ресурсов, содержащиеся в файле и называются поддиректориями типа. Они, в свою очередь, имеют вложенные поддиректории, называемые поддиректориями идентификаторов. Может присутствовать по одной поддиректории идентификатора для каждого экземпляра данного типа ресурса. Они могут указывать непосредственно на данные (например, данные диалога, иконки, меню) либо на вложенные поддиректории далее по дереву.
Много текста, наверное, интересного и полезного. Имхо, упущена возможность быстро создать в редакторе ресурсов всё необходимое и прилинковать к файлу.
да, Вы правы. Будет желание - обязательно добавлю раздел. Спасибо. Может накидаете ссылок на наиболее функциональные инструменты?
Асм этим и отличается от остальных языков, что программист должен понимать, что он делает и зачем. А в редакторе-ресурсов и обезьяна может нарисовать окно. Спасибо автору за развёрнутое объяснение!
не за что!! :)
Спасибо огромное, потрясающая статья, такого подробного и понятного описания нигде не встречал. Спасибо большое!
Не останавливайтесь, пожалуйста, очень ждём с нетерпением новых статей!
Спасибо. Многое почерпнул для себя!
Супер! Очень информативно!