Затрагиваемая нами сегодня тема, пожалуй, является не такой уж и заметной в общем процессе освоения программирования на языке ассемблера под Windows. Однако возможность обеспечения разрабатываемого приложения различного рода визуальными элементами, если хотите украшательствами, заложена в саму концепцию большинства современных операционных систем с графическим интерфейсом. Сегодня мы обсудим такую несущественную деталь как иконка приложения. Согласитесь, в сущности то мелочь, а придает, так сказать, некий дополнительный уровень основательности в восприятии приложения. В очередной раз как-то на днях увидел элегантную иконку напротив имени какой-то программы в проводнике, и.. понравилось. Эстетика, не более, никакого особенного функционала в себе иконки не несут, однако восприятие графического окна приложения как то сразу меняется. Ведь, если разобраться, в современном программировании наличие собственного значка у приложения является уже своего рода хорошим тоном, так сказать распространенной практикой. Ну, а поскольку для меня программирование под Windows является областью пока еще новой, поэтому решил разобраться, как же собственные приложения, написанные на ассемблере, оснащать иконками. Иконка приложения, о которой идет речь, видна в верхнем левом углу главного окна программы, отображается в проводнике напротив имени файла и её так же можно наблюдать в миниокне приложения на панели задач, по сути, она доступна через стандартные системные механизмы, поэтому появляется везде, где её запрашивают и выводят.
За основу мы возьмем исходный код шаблона приложения на ассемблере, который, в свою очередь, взят из комплекта стандартного дистрибутива 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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
format PE GUI 4.0 ; Формат PE. Версия GUI 4.0. entry start ; Точка входа include '%include%\win32a.inc' ; Делаем стандартное включение. _style equ WS_VISIBLE+WS_DLGFRAME+WS_SYSMENU ; ручное задание стиля. ЭКВИВАЛЕНТЫ должны задаваться ДО основного кода ;=== сегмент кода ============================================================ section '.text' code readable executable start: invoke GetModuleHandle,0 ; Получим дескриптор приложения. mov [wc.hInstance],eax ; Сохраним дескриптор приложения в поле структуры окна (wc) invoke LoadIcon,[wc.hInstance],2 ; Загружаем иконку из секции ресурсов mov [wc.hIcon],eax ; Сохраним дескриптор иконки в поле структуры окна (wc) invoke LoadCursor,0,IDC_ARROW ; Загружаем стандартный курсор IDC_ARROW mov [wc.hCursor],eax ; Сохраним дескриптор курсора в поле структуры окна (wc) mov [wc.lpfnWndProc],WindowProc ; Зададим указатель на нашу процедуру обработки окна mov [wc.lpszClassName],_class ; Зададим имя класса окна mov [wc.hbrBackground],COLOR_WINDOW+1 ; Зададим цвет кисти invoke RegisterClass,wc ; Зарегистрируем наш класс окна test eax,eax ; Проверим на ошибку (eax=0). jz error ; Если 0, то ошибка - прыгаем на error. invoke CreateWindowEx,0,_class,_title,_style,128,128,256,192,NULL,NULL,[wc.hInstance],NULL ; Создадим экземпляр окна на основе зарегистрированного класса. в eax возвращает дескриптор окна. test eax,eax ; Проверим на ошибку (eax=0). jz error ; Если 0, то ошибка - прыгаем на error. mov [wHMain],eax ; сохраним дескриптор созданного окна ;--- цикл обработки сообщений ------------------------------------------------ msg_loop: invoke GetMessage,msg,NULL,0,0 ; Получаем сообщение из очереди сообщений приложения or eax,eax ; Сравнивает eax с 0 jz end_loop ; Если 0 то пришло сообщение WM_QUIT - выходим из цикла ожидания сообщений, если не 0 - продолжаем обрабатывать очередь msg_loop_2: invoke TranslateMessage,msg ; Дополнительная функция обработки сообщения. Конвертирует сообщения клавиатуры отправляет их обратно в очередь. invoke DispatchMessage,msg ; Пересылает сообщения соответствующим процедурам обработки сообщений (WindowProc ...). jmp short msg_loop ; Зацикливаемся error: invoke MessageBox,NULL,_error,NULL,MB_ICONERROR+MB_OK ; Выводим окно с ошибкой end_loop: invoke ExitProcess,[msg.wParam] ; Выход из программы. ;--- процедура обработки окна (функция окна, оконная процедура, оконная функция) proc WindowProc hWnd,wMsg,wParam,lParam push ebx esi edi ; сохраним все регистры cmp [wMsg],WM_DESTROY ; Проверим на WM_DESTROY je .wmdestroy ; на обработчик wmdestroy cmp [wMsg],WM_CREATE ; Проверим на WM_CREATE je .wmcreate ; на обработчик wmcreate .defwndproc: invoke DefWindowProc,[hWnd],[wMsg],[wParam],[lParam] ; Функция по умолчанию. Обрабатывает все сообщения, которые обрабатывает наш цикл. jmp .finish .wmcreate: xor eax,eax jmp .finish .wmdestroy: ; Обработчик сообщения WM_DESTROY. Обязателен. invoke PostQuitMessage,0 ; Посылает сообщение WM_QUIT в очередь сообщений, что вынуждает GetMessage вернуть 0. Посылается для выхода из программы. Посылается только основным окном. xor eax,eax ; Если наша процедура окна обрабатывает какое-либо сообщение, то она должна вернуть в eax 0. Иначе программа поведет себя непредсказуемо. .finish: pop edi esi ebx ; восстановим все регистры ret endp ;=== сегмент данных ========================================================== section '.data' data readable writeable _class db 'FASMWIN32',0 ; Название собственного класса. _title db 'Win32 application icon',0 ; Текст в заголовке окна. _error db 'Startup failed.',0 ; Текст ошибки wHMain dd ? ; дескриптор окна wc WNDCLASS ; Структура окна. Для функции RegisterClass msg MSG ; Структура системного сообщения, которое система посылает нашей программе. ;=== таблица импорта ========================================================= section '.idata' import data readable writeable library kernel32,'KERNEL32.DLL',user32,'USER32.DLL' include 'api\kernel32.inc' include 'api\user32.inc' ;=== секция ресурсов ========================================================= section '.rsrc' resource data readable directory RT_ICON,icons,\ RT_GROUP_ICON,group_icons resource icons,\ 1,LANG_NEUTRAL,icon_data resource group_icons,\ 2,LANG_NEUTRAL,main_icon icon main_icon,\ icon_data,'icon.ico' |
Как мы видим, изменений исходного шаблона немного и связаны они с модификацией кода загрузки иконки (строка 14
) и добавлением секции ресурсов (строки: 90
-104
), описывающей данные пиктограммы выходного исполняемого файла.
Начнем изучение изменений с самого начала листинга. У нас еще с первого примера в исходном коде присутствует функция загрузки иконки LoadIcon, которую можно увидеть в строке 14
. В начальном шаблоне функция LoadIcon использовалась для загрузки стандартной системной иконки. Однако, в случае с пользовательской пиктограммой (иконкой) стоит более внимательно изучить документацию по функции, дабы понять, что нам необходимо скорректировать входные параметры. В частности, параметр hInstance должен принять значение дескриптора модуля, чей исполняемый файл содержит в своей ресурсной секции битовую карту иконки. Очевидно, что это значение у нас поступает в виде выходного параметра функции GetModuleHandle (строка 12
) и на момент вызова LoadIcon содержится в переменной wc.hInstance (или в регистре eax). Затем, второй входной параметр lpIconName функции LoadIcon должен принимать значение идентификатора загружаемого ресурса (иконки), вот тут то и начинается самое интересное. Что такое значение 2
идентификатора ресурса и ресурс вообще? Это термины отсылают нас к понятию секции ресурсов.
Секция ресурсов
Думаю будет более чем уместным упомянуть о секции ресурсов, поскольку описание ресурсов пиктограммы является краеугольным камнем понимания всей стратегии задания иконки приложения. Настоятельно рекомендую немного отвлечься и изучить информацию по секции ресурсов, которую я собрал в отдельной статье. Тут можно лишь отметить, что ресурсы - индексированный массив статических (неизменяемых) данных, используемых в коде приложения или иным образом относящихся к нему: меню, диалоги, курсоры, иконки, картинки, звуки и прочее. Для описания ресурсов, как вы уже поняли, используется специальная секция, которая начинается с директивы section
, и определяемых после неё различных опции, таких как название (.rsrc), тип (resource data), атрибуты доступа (readable).
Поэтому, в статье, которую я порекомендовал выше, достаточно подробно описаны основные ключевые макросы, используемые в FASM для секции ресурсов. Тут мы поясним только лишь некоторые моменты:
Наименование | Определение |
---|---|
Макроопределение icon |
Данные (ресурс) иконки. Макрос icon завершает секцию ресурсов в строке 103 , он призван организовать размещение иконки приложения в секции ресурсов. В параметрах макроса указаны: имя ресурса группы иконок (main_icon), имя ресурса отдельной иконки (icon_data), имя файла с иконкой (icon.ico). Понятное дело, что при запуске исполняемого файла нам сам файл иконки уже будет не нужен, однако на стадии сборки исполняемого файла компилятор должен откуда то получить данные, дабы сформировать соответствующую поддиректорию секции ресурсов. |
В нашем примере подразумевается, что файл icon.ico размещается в той же директории, что и исходный код программы, однако нет никаких проблем с выбором произвольного местоположения внешних данных для секции ресурсов, поэтому, если у Вас вдруг возникло желание положить иконки в отдельную поддиректорию, то необходимо будет соответствующим образом подкорректировать параметр.
Но, вернемся к нашему примеру. В итоге всех наших манипуляций, подключенную нами иконку можно разглядеть в левом верхнем углу основного окна приложения:
В дополнение к этому, иконка приложения появляется и в пиктограмме на панели задач. Ну и ко всему прочему такой вот вид теперь принимает исполняемый файл нашей программы в проводнике:
видите слева от имени программы появилась иконка? Похоже код проводника тоже опрашивает ресурсные секции файлов каталога, кеширует их и затем отображает в открывшемся окне проводника.
Формат и размерность иконки
Тут возникает резонный вопрос, а почему в качестве формата иконок выбран .ico? Честно говоря, за время, проведенное в работе с операционными системами Windows, у меня сложилась устойчивая ассоциация, что стандартом де-факто для иконок (пиктограмм) в этой системе непременно должен являться указанный формат. И действительно, об этом же красноречиво говорит и Wikipedia:
Однако, развитие видов представления в различных компонентах ОС способствовало увеличению необходимых разрешений иконок. Теоретически, размер иконки приложения может быть любым, однако разработчиками из Microsoft рекомендованы следующие размеры: 16×16, 24×24, 32×32, 48×48, 256×256. В нашем же примере я особо не мудрствовал и нашел в Сети первый попавшийся .ico-файл размером 16х16, который и был использован в коде.
В принципе, в одиночной иконке нет ничего плохого, поскольку она является самым простым решением, однако этот метод имеет один недостаток: в последних версиях Windows, при использовании разнообразных режимов просмотра (таблица, значки), одна единственная пиктограмма начинает масштабироваться, что в ряде случаев отрицательно влияет на качество наблюдаемого изображения. Соответственно, всем кто хочет реализовать более универсальный подход, рекомендуется подключать в качестве ресурса (и соответственно описывать) файл(ы), содержащий в себе несколько вариантов (размеров) иконки для различных ракурсов отображения, благо есть отличный материал на этот счет (правда попасть на сайт автора не так уж и просто, поскольку на ресурсе в автомате забанено большое количество адресов из динамических диапазонов домашних подсетей, вероятно не совсем корректно работающим скриптом защиты).
Размер файла иконки
Потом я заметил, что моя иконка занимает на диске 2.5 Кб, а сам скомпилированный исполняемый файл тестовой программы - всего-лишь 2 Кб. Вот это да, оказывается что иконка приложения у меня "весит" больше нежели сама программа! Реалии современного программирования под Windows таковы, что размеры давно никого уже не стесняют, ведь если какой-нибудь инсталлятор современного пакета занимает несколько сотен мегабайт, то какое кому дело до размера иконки? Но я лично привык к рациональному использованию ресурсов системы, поэтому у меня возникло острое желание оптимизировать иконку по размеру.
В этом случае можно попробовать:
- Оптимизировать .ico-файл при помощи RIOT (+PNGout Xtreme);
- Создать иконку в PNG-формате, а потом собрать из неё ico-файл;