Секция ресурсов в FASM

Метки:  , , , , , , , , ,

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

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

С какой целью разработчикам из Microsoft потребовалось создавать дополнительную структуру под названием секция ресурсов, по какой причине ресурсы вообще появились на свет? Ведь, если разобраться, любые данные можно свободно описать в основном исходном тексте (или во внешних/подключаемых файлах) привычным способом с помощью констант/переменных? Конечно можно, я например, как тот еще "заправский" программист, очень люблю так вот по старинке (скорее по привычке) определять все данные в коде :) Но у этого решения имеется ряд очевидных недостатков:

  • далеко не все используемые приложением типы данных удобно описывать в исходном коде в виде переменных. Ресурсы удобнее использовать в агрументах функций WinAPI;
  • при использовании ресурсов удобнее манипулировать данными, размещающимися во внешних файлах;

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

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

У автора появляется довольно богатый выбор:

  • для совсем простых данных использовать константы/переменные;
  • для предопределенных данных малой/средней сложности использовать ресурсы;
  • для данных повышенной сложности/размера/специфической логики использовать внешние подключаемые модули (файлы, библиотеки);

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

  1. Ресурсы компилируются вместе с приложением в единый исполняемый файл и доступны "напрямую" (прямым указанием в аргументах функций) через специальные константы-идентификаторы, определяемые в исходном коде;
  2. Ресурсы могут быть логически сгруппированы в языковые наборы, у приложения появляется возможность использовать несколько "наборов ресурсов" в зависимости от используемого в данный момент языка.
  3. Значение того или иного ресурса может быть [легко] изменено в уже скомпилированном бинарном модуле при помощи программ, относящихся к категории редакторов ресурсов;
  4. Ресурсы в бинарном исполняемом файле создаются в виде трехуровневого, двоично-отсортированного дерева, оптимизированного с целью ускорения обхода;

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

  • в исходном коде приложения: для размещения используется отдельная [ресурсная] секция;
  • во внешнем файле: (чаще всего имеющим расширение .rc), подключаемом к приложению на этапе компиляции;
  • в памяти процесса: данные ресурсов могут формироваться "с нуля" в виртуальном адресном пространстве процесса приложения "на ходу", непосредственно во время выполнения специализированных функций по созданию ресурсов;

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

  • Стандартизация часто используемых в приложениях типов данных. Представьте ситуацию, что вы из раза в раз, на протяжении долгого времени, используете одни и те же самописные программные конструкции для конфигурирования каких-либо элементов (меню, диалогов, изображений и прч.). Это может привести со временем к образованию собственной "библиотеки структур". Не проще ли озвучить разработчику операционной системы (под которой ведется разработка), идею описания всех этих часто используемых структур на системном уровне, посредством создания нового системного механизма?
  • Достижение "автономности" ресурсов. Если выделить ресурсы в отдельные файлы, подключаемые к приложению, то можно добиться независимости (модульности) от основного приложения, так называемой "взаимозаменяемости", когда без модификации основного кода можно менять любые аспекты подключаемых данных. К примеру, в многоязычных приложениях используют подключаемые ресурсные библиотеки, они являются фактически единственным удобным способом обеспечения трансляции интерфейса программы под разные языки.
  • Обеспечение упрощенного механизма использования стандартизованных данных (иконки, диалоги, курсоры). Можно было бы расположить все эти данные в отдельных файлах, однако представьте себе ситуацию, что без такого механизма как ресурсы мы вынуждены были хранить все их наряду с основным исполняемым модулем (и перемещать при необходимости). Намного проще встроить их внутрь исполняемого файла [программы].

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

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

И вот тут то стоит обратить внимание на один достаточно важный момент, осознание которого впоследствии упростит вам понимание структуры секции ресурсов, и, соответственно, принципов ее описания:

Ресурсы представляют собой многоуровневое двоичное дерево, структура которого позволяет углубляться на 231 уровней от корня (на практике используются только три из них):

  1. Первый (верхний) уровень -- это Тип (Type);
  2. Второй уровень -- это Имя (Name);
  3. Третий уровень -- это Язык (Language);

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

  1. корневая директория ресурса;
  2. ресурсные поддиректории;
  3. данные ресурса;

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

  1. Ручной: разработчик основного приложения, с использованием предпочитаемой среды разработки (читать: текстового редактора), самостоятельно описывает ресурсную секцию при помощи макросов-директив, применяемых для описания того или иного типа ресурса.
  2. Автоматический: разработчик основного приложения использует специализированную программу, относящуюся к категории редакторов ресурсов. Подобные программы позволяют (относительно) упростить, визуализировать процесс, сделав его более интуитивным.
Ресурсы загружаются в адресное пространство процесса на этапе подготовки .exe-образа к исполнению. Секция ресурсов (в которой обычно располагаются ресурсы) проецируется в виртуальное адресное пространство процесса в отдельную страницу памяти. Спроецированные таким образом ресурсы доступны [практически] аналогично константам, с той лишь разницей, что доступ к ним осуществляется через специальные идентификаторы, определяемые разработчиком в исходном коде [в виде констант/идентификаторов].

Секция ресурсов в исходном коде

В этом разделе мы сосредоточимся на основном направлении изучения секции ресурсов, то есть на принципах описания ресурсов в исходном коде нашей программы. В каждом уважающем себя компиляторе существует язык описания ресурсов, в каких-то разработках по своей семантике напоминающий язык C, где то похожий на XML. При помощи директив этого языка разработчик имеет возможность описания данных ресурса, с целью последующего получения доступа к ним из кода приложения путем указания их в качестве аргумента функции Win32 API. Ресурсы как таковые в диалекте ассемблера FASM описываются внутри предназначенной исключительно для этого секции ресурсов.

Задача понимания структуры секции ресурсов в исходном тексте сводится к изучению синтаксиса и формата основных ключевых директив, используемых в FASM для секции ресурсов.

Давайте приведем типовой (общий) шаблон секции ресурсов в исходном тексте на языке Ассемблера:

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

Описание начинается с определения секции при помощи директивы section, и указываемой за ней серии параметров: название (.rsrc), тип (resource data), атрибуты доступа (readable). Местоположение секции ресурсов в исходном коде может быть выбрано произвольно, однако распространенная практика да и просто хорошая идея состоит в том, что бы определить секцию ресурсов в конце исходного кода, в хвостовой части файла.

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

За описателем секции следует набор специальных макросов (макродиректив). По описанию видно, что структура секции ресурсов представляет собой набор директив[-контейнеров], которые размещаются в секции определенным порядком от "корня" дерева к "ветвям", а далее к "листьям":

  • Корневая директория: содержит перечисление всех ресурсов приложения;
  • Ресурсные поддиректории: определяет поддиректории ресурсов, в которых группируются те или иные ресурсы;
  • Данные ресурсов: содержатся непосредственно данные ресурсов;

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

Наименование Определение
Макроопределение directory Каталог ресурсов. Корневая директория, в которую включаются все существующие в приложении ресурсы, то есть в ней перечисляются и описываются все ресурсы, задаваемые в секции ресурсов приложения. Макрос directory указывается первым после описателя секции, в самом начале секции ресурсов. После макроса directory следуют пары, разделенные запятыми: первое в паре — тип ресурса (к какой категории относится), второе — имя поддиректории ресурса, содержащей ресурсы указанного типа. Фактически директива определяет все типы ресурсов, используемых в приложении.
Макроопределение resource Точка входа каталога. Поддиректория идентификаторов, содержащая записи вложенных ресурсов (или поддиректорий). После определения resource следует имя поддиректории, за ним следуют группы из трех параметров ресурса. Давайте опишем их подробнее:

  • Имя поддиректории (оно задается выше в парах параметров макроса directory);
  • Числовой идентификатор ресурса. Назначается разработчиком, используется для доступа к ресурсу из кода приложения;
  • Язык ресурса (опционально). Определяет язык/подъязык ресурса для дифференциации в зависимости от установленных национальных параметров. Windows идентифицирует пользовательскую локализацию по двум полям: идентификатор языка и идентификатор подъязыка. Некоторые типы ресурсов не нуждаются в языковом определении, поэтому зачастую используется константа LANG_NEUTRAL;
  • Имя ресурса (идентично имени, указанным выше в макросе directory). Используется для связи непосредственно с данными ресурса;

К одному имени поддиректории идентификаторов может быть привязано несколько трехпараметральных групп.

Тип ресурса, задаваемый в качестве параметра в директиве 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 и некоторые другие.

Все доступные в FASM для описания тех или иных типов ресурсов макроопределения можно найти в файле \INCLUDE\MACRO\resource.inc, входящем в дистрибутив поставки.

Меню

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

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

В приведенном выше примере 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_END - обязательный идентификатор последнего (завершающего) пункта.
Идентификатор Назначение
MFR_POPUP Идентификатор, описывающий элемент меню, который открывает другое меню или подменю;
MFR_END Идентификатор, описывающие элемент меню, который является последним в этом меню или подменю;

Однако это только основные стили, за более расширенной информацией можно пройти по ссылке: стили меню

Диалог

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

Традиционно, в самом начале секции ресурсов у нас присутствует запись каталога ресурсов 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 и IDR_ICON это задаваемые разработчиком идентификаторы иконок, по которым он в любой момент может обратиться к ресурсу.

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

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

Секция ресурсов в бинарном файле

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

Как вы уже догадались, в данном разделе мы будем иметь дело уже не с исходными текстами программы, а заглянем внутрь получающегося в процессе компиляции наших исходных текстов исполняемого бинарного файла и посмотрим, в каком виде ресурсы размещаются уже в нем.
Напомним себе, что исполняемый .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).

Не смотря на заявленную "необязательность" данного заголовка, он присутствует в подавляющем большинстве PE-файлов.

Опциональный подзаголовок располагается по смещению 98h относительно начала PE-файла.

Каталог (директории) данных

Внутри опционального заголовка располагается массив описателей каталога данных под названием DataDirectory (на скриншоте выделен темно-голубым цветом), каждый элемент которого имеет тип IMAGE_DATA_DIRECTORY и занимает 8 байт.

Количество элементов данного массива задается параметром NumberOfRVAandSizes, который располагается в опциональном заголовке непосредственно перед массивом описателей каталога данных. Значение параметра обычно равно 16 элементов (записей).

Структура IMAGE_DATA_DIRECTORY имеет следующий формат:

В свете поиска секции ресурсов нас интересует третий (индекс 2) элемент массива описателей, именуемый как IMAGE_DIRECTORY_ENTRY_RESOURCE и содержащий указатель на искомую секцию. Для данного элемента соответствующий член VirtualAddress является указателем, содержащим относительный виртуальный адрес директории ресурсов, а член Size содержит размер.

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

Таблица (заголовки) секций

Только что мы получили относительный виртуальный адрес (RVA) ресурсной секции, что далее? Теперь требуется найти секцию, содержащую данный RVA, для этого мы просматриваем следующий подзаголовок PE-файла под названием таблица секций (Section Table). Таблица секций располагается сразу за директориями данных и представляет собой следующие друг за другом (структуры) заголовки всех секций, присутствующих в конкретном исполняемом файле. Структура выглядит следующим образом:

..и имеет размер в 40 байт. И вот из таких 40-байтовых заголовков и состоит вся таблица секций.
Полученное ранее из директории данных значение члена VirtualAddress структуры IMAGE_DATA_DIRECTORY мы сравниваем с аналогичным членом VirtualAddress каждой секции IMAGE_SECTION_HEADER. Как только мы нашли структуру IMAGE_SECTION_HEADER, в которой значения эти членов идентичны, член PointerToRawData найденной таким образом структуры содержит смещение первого байта секции ресурсов (относительно начала файла).

В нашем конкретном случае это выделенное на скриншоте красным цветом значение 000c0000.

Первый уровень: корневая директория ресурсов

Перемещаемся по полученному нами адресу. В первом байте секции ресурсов размещается (главная) структура, имеющая тип IMAGE_RESOURCE_DIRECTORY, фактически представляющая собой корневую директорию секции ресурсов:

В (главной, корневой) структуре IMAGE_RESOURCE_DIRECTORY имеет значение только поле NumberOfIdEntries, которое содержит количество ресурсных типов, размещенных в PE-заголовке.

В формате Portable Executable (PE), и корневая директория ресурсной секции и её поддиректории имеют единый структурный тип IMAGE_RESOURCE_DIRECTORY и называются нодами или узлами дерева ресурсов.

Второй уровень: поддиректории ресурсов

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

Третий уровень: массив записей ресурсов

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

Записи имен располагаются первыми, отсортированы по алфавиту в порядке возрастания, за ними следуют записи идентификаторов в порядке возрастания. Значение поля NameId имеет двойное применение: если самый старший бит (бит знака) обнулен, то младшие 16 бит это идентификатор (ID) ресурса, если же старший бит установлен, то младшие 31 бит образуют смещение от начала данных ресурса до строки имени этого ресурса. Значение указателя *Data так же имеет двойное применение: если старший бит установлен, то оставшиеся 31 бит образует смещение от начала данных текущего ресурса до следующей структуры IMAGE_RESOURCE_DIRECTORY, фактически до следующего узла (директории) дерева ресурсов. Если бит сброшен, то запись является конечной точкой ветвления, образно "листом" дерева, в этом случае *Data содержит смещение от начала данных ресурса до структуры, описывающей данные, специфичные для этого типа ресурсов.

Четвертый уровень: данных ресурса

И последним уровнем иерархии являются уже сами данные ресурса, которые описываются в структуре:

Указатель *Data структуры IMAGE_RESOURCE_DATA_ENTRY содержит относительный виртуальный адрес (RVA) актуальных данных ресурса.

Выводы

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

Комментарии: 7

  1. brute

    Много текста, наверное, интересного и полезного. Имхо, упущена возможность быстро создать в редакторе ресурсов всё необходимое и прилинковать к файлу.

    1. einaare

      да, Вы правы. Будет желание - обязательно добавлю раздел. Спасибо. Может накидаете ссылок на наиболее функциональные инструменты?

  2. Marylin

    Асм этим и отличается от остальных языков, что программист должен понимать, что он делает и зачем. А в редакторе-ресурсов и обезьяна может нарисовать окно. Спасибо автору за развёрнутое объяснение!

    1. einaare

      не за что!! :)

  3. Владимир

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

  4. Wolf

    Спасибо. Многое почерпнул для себя!

  5. Руслан

    Супер! Очень информативно!

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *