Когда я, в процессе своего обучения, подошел к написанию приложений, активно использующих различные графические элементы управления, раз за разом начал обращать внимание на одну интересную проблему. Дело в том, что при запуске двоичного исполняемого файла, все элементы, находящиеся в окне, выглядели "плоско", то есть имели упрощенный стиль, а-ля Windows 95/98. Долгое время я как-то не мог сообразить, почему у меня из раза в раз оформление окон получается каким-то упрощенным, без выпуклого объема кнопок, теней, объемных полос прокруток, элегантных тонких линий-рамок у групп элементов, а у подавляющего большинства сторонних оконных приложений, видимых мною в Сети, тема оформления окна как раз была "объемной"? Особенно это заметно в диалоговых окнах, где группируется большое количество разнообразных дочерних элементов управления, волей-неволей начинаешь к ним присматриваться, изучать детали.. и тут вот осознаешь, что в твоих то приложениях они получаются не такими элегантными. Оказалось, что если не предпринимать каких-либо дополнительных действий, то приложения, компилируемые FASM и использующие оконный графический интерфейс, все как на подбор получаются с упрощенной стилизацией интерфейса. Что я только не делал, уже и все стили элементов перебрал вдоль и поперек, ставя в параметры ресурсов всяческие мыслимые и немыслимые комбинации, эффект был разный, но неизменно далекий от желаемого. Как заставить приложение выглядеть в новом, современном стиле? В ходе практических изысканий, когда уже все варианты кончились, наконец-то начал думать :) Как оказалось, ответ кроется в изменившихся принципах работы с общими элементами управления, подключаемых посредством механизма под названием манифест приложения.
Поскольку темой серии статей является изучение программирования под Windows на языке ассемблера, то и рассматривать мы будем тут преимущественно особенности подключения манифеста приложения применительно к приложению на ассемблере. Для начала обратимся к официальным источникам, согласно определению манифеста с сайта Microsoft:
Упрощенно, основной функциональной особенностью манифеста приложения является связывание нашего приложения с определенными общими и частными версиями библиотек. "Ну и что?" ,- скажете Вы, как это влияет на визуальное оформление элементов управления окна? Дело в том, что среди прочих в системе имеется библиотека общих элементов оформления (контролов), регламентирующая внешний вид этих самых элементов.
Использование тем оформления
Чтобы указать приложению использовать темы оформления, необходимо использовать comctl32.dll версии 6 или более поздней. Осуществляется подключение библиотеки Common Controls при помощи задания в манифесте имени атрибута Microsoft.Windows.Common-Controls с непосредственным указанием версии 6.0.0.0, тем самым инициируя связывание процесса приложения (на стадии подготовки/выполнения) с библиотекой comctl32.dll шестой версии. Вследствие подобного связывания, все стандартные контролы рисуются в соответствии с активированной в системе темой оформления. Эта особенность впервые появилась в Windows XP и до сих пор актуальна для более поздних её версий.
Алгоритм настройки манифеста приложения
Давайте непосредственно от теории перейдем к практике и попытаемся сконфигурировать манифест приложения на ассемблере.
Загрузка общих элементов управления
В самом начале листинга программы, до вызова функции GetModuleHandle, добавляем строку:
1 2 3 |
. . . invoke InitCommonControlsEx . . . |
Вызываемая таким образом функция InitCommonControlsEx регистрирует заданные в структуре INITCOMMONCONTROLSEX (у нас опущена) классы общих элементов управления из загруженной в адресное пространство приложения библиотеки общих элементов управления (comctl32.dll). По рекомендациям разработчиков, Ваше приложение должно вызывать эту функцию до создания каких-либо элементов управления, то есть хорошая практика вставлять вызов именно в начало приложения.
Добавление записей в секцию импорта
Для того, что бы вызов функции InitCommonControlsEx был возможен, необходимо подключить библиотеку общих элементов управления. Для этого:
- в секцию
.idata
, к макроопределениюlibrary
в конце добавить строку: ,comctl32,'COMCTL32.DLL'; - в ту же секцию
.idata
, в конце, добавляем подключаемые определения строкой: include 'api\comctl32.inc';
Полная получившаяся в итоге секция импорта может выглядеть, например, таким вот образом:
1 2 3 4 5 6 7 8 9 |
. . . section '.idata' import data readable writeable library kernel32,'KERNEL32.DLL',user32,'USER32.DLL',comctl32,'COMCTL32.DLL' include 'api\kernel32.inc' include 'api\user32.inc' include 'api\comctl32.inc' . . . |
Добавление записей в секцию ресурсов
- В ресурсной секции
.rsrc
, в макроопределенииdirectory
, описываем новую директорию manifests с идентификатором RT_MANIFEST (24). Получившаяся строка может выглядеть следующим образом:
123. . .directory RT_MANIFEST,manifests. . .Константа RT_MANIFEST, как мы видим, не требует непосредственного определения в приложении, поскольку она уже инициализирована в подключаемых файлах kernel32.inc и kenel64.inc. - Затем, в этой же секции
.rsrc
конфигурируем ресурсную запись с именемmanifests
и идентификатором 1:
1234. . .resource manifests,\1,LANG_ENGLISH+SUBLANG_DEFAULT,manifest. . .
Добавление манифеста
Если Вы хотите разместить данные манифеста непосредственно в исходном коде (что несомненно удобно), то в самый конец секции ресурсов (обычно в конце исходного кода) добавляем:
1 2 3 4 5 6 7 8 9 10 11 12 |
resdata manifest db '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>',13,10 db '<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">',13,10 db '<assemblyIdentity name="Application name" processorArchitecture="x86" version="5.1.0.0" type="win32"/> ',13,10 db '<description>Application name</description>',13,10 db '<dependency>',13,10 db '<dependentAssembly>',13,10 db '<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="x86" publicKeyToken="6595b64144ccf1df" language="*" />',13,10 db '</dependentAssembly>',13,10 db '</dependency>',13,10 db '</assembly>',13,10 endres |
assemblyIdentity
, в нем можно указать имя приложения в произвольном формате (с пробелом и без). В строке 5 у нас располагается элемент description
, в котором можно задавать описание, зачастую совпадающее с именем.Если же Вам удобнее присоединять манифест в секцию ресурсов из внешнего файла (консервативно) а этапе компиляции, то добавляем уже другой фрагмент:
1 2 3 |
resdata manifest file 'manifest.xml' endres |
.. в этом случае особое внимание следует обратить на факт присутствия файла manifest.xml в директории проекта с таким вот содержимым:
1 2 3 4 5 6 7 8 9 10 |
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <assemblyIdentity name="Application name" processorArchitecture="x86" version="5.1.0.0" type="win32"/> <description>Application name</description> <dependency> <dependentAssembly> <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="x86" publicKeyToken="6595b64144ccf1df" language="*" /> </dependentAssembly> </dependency> </assembly> |
Существует еще один (достаточно кривой) вариант полного внешнего размещения манифеста. Для этого, вышеуказанный файл размещаем в директории, где находится Ваше приложение и переименовываем его таким образом, что бы имя совпадало с именем программы и содержало добавочное расширение .manifest
. Например:
<имя_программы>.exe.manifest