Blog. Just Blog

Работа с иконками файлов на Ассемблере

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Образ мышления: Assembler | Автор: ManHunter
Работа с иконками файлов на Ассемблере
Работа с иконками файлов на Ассемблере

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

Первая функция - ExtractIcon, она по индексу извлекает иконку из исполняемого файла, динамической библиотеки или файла иконки. С ее же помощью можно узнать количество иконок в файле. Главный недостаток использования ExtractIcon заключается в том, что извлекаются только большие иконки 32х32, если значок, находящийся по запрошенному индексу, имеет другой размер, то он будет отмасштабирован.
  1. ...
  2. fname  db 'source.exe',0
  3. ...
  4.         invoke  GetModuleHandle,NULL
  5.         mov     ebx,eax
  6.         invoke  ExtractIcon,ebx,fname,0
  7.         ; EAX - хэндл первой иконки
  8.         invoke  ExtractIcon,ebx,fname,1
  9.         ; EAX - хэндл второй иконки
ExtractIconEx - как можно понять из названия, представляет собой расширенный вариант предыдущей функции. Кроме того, что с ее помощью также можно получить количество иконок в файле, она позволяет извлекать как большие 32х32, так и маленькие 16х16 иконки, причем сразу пачками. Источником может быть исполняемый файл, динамическая библиотека или файл иконки.
  1. ...
  2. fname  db 'source.exe',0
  3.  
  4. hIconLarge dd ?
  5. hIconSmall dd ?
  6.  
  7. arrSmall   rd 4
  8. ...
  9.         ; Извлечь по одной большой и маленькой иконке с индексом 0
  10.         invoke  ExtractIconEx,fname,0,hIconLarge,hIconSmall,1
  11.         ; [hIconLarge] - хэндл большой иконки
  12.         ; [hIconSmall] - хэндл маленькой иконки
  13.  
  14.         ; Извлечь из файла только маленькие иконки в количестве 4 штук,
  15.         ; начиная с индекса 0 и до 3
  16.         invoke  ExtractIconEx,fname,0,NULL,arrSmall,4
  17.         ; [arrSmall] - массив с четырьмя хэндлами маленьких иконок
Здесь следует сделать небольшое отступление. Дело в том, что в системе используются термины "большие иконки" и "маленькие иконки". Исторически сложилось, что размер больших иконок составляет 32х32 пиксела, а маленьких 16х16 пикселов. Однако, эти значения не являются непоколебимыми константами. Если вы пишете максимально устойчивое и адаптивное приложение, вам надо обязательно проверять размеры иконок, заданные в системе. Делается это при помощи функции GetSystemMetrics с флагами SM_CXICON, SM_CYICON для больших и SM_CXSMICON, SM_CYSMICON для маленьких иконок. Конечно, в абсолютном большинстве случаев полученные размеры иконок не будут отличаться от привычных 16 и 32, но мало ли. Для простоты понимания в статье и исходниках я буду подразумевать, что размеры больших и маленьких иконок стандартные.
  1.         ; Получить системные размеры больших и маленьких иконок
  2.         invoke  GetSystemMetrics,SM_CXICON
  3.         ; EAX = ширина большой иконки
  4.         invoke  GetSystemMetrics,SM_CYICON
  5.         ; EAX = высота большой иконки
  6.         invoke  GetSystemMetrics,SM_CXSMICON
  7.         ; EAX = ширина маленькой иконки
  8.         invoke  GetSystemMetrics,SM_CYSMICON
  9.         ; EAX = высота маленькой иконки
А как быть, если в файле хранятся иконки с другими размерами, например, 64х64 или вовсе 256х256? Как их извлечь, не забираясь в дебри ресурсов? Делается это при помощи следующей функции - PrivateExtractIcons. Если почитать ее описание на MSDN, то там английским по белому написано, что эта функция не рекомендована к использованию и может быть в любой момент изменена или вовсе исключена из очередной версии Windows. Как бы то ни было, PrivateExtractIcons исправно работает аж со времен Windows 2000 и до настоящего времени никаких проблем с ее использованием нет. Более того, это единственная функция, позволяющая наиболее быстро и просто извлекать из файла иконки нужных размеров, отличных от "больших" и "маленьких". Если по запрошенному индексу нет иконки подходящего размера, то будет загружена наиболее подходящая по размеру и отмасштабирована под заданные пропорции. Поддерживается и пакетное извлечение иконок, но только из исполняемых файлов и динамических библиотек. Одиночные изображения с помощью функции PrivateExtractIcons можно загружать из файлов иконок, анимированных курсоров и даже BMP-файлов. Поэтому функцию можно использовать для быстрой загрузки изображений с автоматическим изменением размера.
  1. ...
  2. fname  db 'source.exe',0
  3.  
  4. hIcon dd ?
  5. tmp   dd ?
  6. ...
  7.         ; Загрузить иконки разных размеров
  8.         invoke  PrivateExtractIcons,fname,0,24,24,hIcon,tmp,1,LR_LOADTRANSPARENT
  9.         ; [hIcon] - хэндл загруженной иконки
  10.         invoke  PrivateExtractIcons,fname,2,64,64,hIcon,tmp,1,LR_LOADTRANSPARENT
  11.         invoke  PrivateExtractIcons,fname,3,256,256,hIcon,tmp,1,LR_LOADTRANSPARENT
  12.         ; Иконка несуществующего размера 22х60
  13.         invoke  PrivateExtractIcons,fname,1,22,60,hIcon,tmp,1,LR_LOADTRANSPARENT
  14.         ; [hIcon] - хэндл загруженной иконки
Начиная с Windows Vista, в списке API для работы с иконками появилась еще одна функция - LoadIconWithScaleDown. С ее помощью можно загружать иконки и при этом подгонять их под нужный размер. В отличие от LoadImage, эта функция ищет иконку равную запрашиваемому размеру, а если именно такой нет, то ближайшую большую по размеру. Тем самым выполняется не растягивание маленькой иконки до размеров большой, а уменьшение большой до маленькой, в результате чего качество получившейся иконки будет лучше. Важное замечание. Чтобы программа могла использовать функцию LoadIconWithScaleDown, она должна быть скомпилирована с манифестом, в котором заявлена поддержка динамической библиотеки comctl32.dll версии 6.
  1.         ; Загрузить модуль с иконками
  2.         invoke  LoadLibrary,fname
  3.         invoke  LoadIconWithScaleDown,eax,1,64,64,hIcon
  4.         ; [hIcon] - хэндл загруженной иконки
  5.  
  6.         ; Загрузить стандартную иконку щита UAC размером 16х16
  7.         IDI_SHIELD = 0x7F06
  8.         invoke  LoadIconWithScaleDown,0,IDI_SHIELD,16,16,hIcon
  9.         ; [hIcon] - хэндл загруженной иконки
Следующий способ получения иконок из файлов - использование функции SHGetFileInfo. С ее помощью можно узнать много интересного о любом файле, в том числе получить хэндлы большой или маленькой иконки, связанной с ним. Говоря "связанной", подразумевается, что для исполняемых файлов, динамических библиотек и файлов иконок будет извлечена их собственная иконка, а для прочих файлов - иконка приложения, ассоциированного с ними. Кроме того, SHGetFileInfo может получать иконки не только файлов, но и папок. Для FASM "из коробки" эта функция неизвестна, поэтому придется сперва объявить структуру и константы, необходимые для работы.
  1. struct SHFILEINFO
  2.   hIcon dd ?
  3.   iIcon dd ?
  4.   dwAttributes dd ?
  5.   szDisplayName rb MAX_PATH
  6.   szTypeName rb 80
  7. ends
  8.  
  9. SHGFI_ICON      = 0x000000100
  10. SHGFI_SMALLICON = 0x000000001
  11. SHGFI_LARGEICON = 0x000000000
Использование же SHGetFileInfo очень простое:
  1. ...
  2. fname  db 'source.exe',0
  3. fdir   db 'c:\windows',0
  4.  
  5. FileInfo SHFILEINFO
  6. ...
  7.         invoke  SHGetFileInfo,fname,0,FileInfo,sizeof.SHFILEINFO,\
  8.                 SHGFI_ICON+SHGFI_LARGEICON
  9.         ; [FileInfo.hIcon] - хэндл большой иконки, связанной с файлом
  10.  
  11.         invoke  SHGetFileInfo,fname,0,FileInfo,sizeof.SHFILEINFO,\
  12.                 SHGFI_ICON+SHGFI_SMALLICON
  13.         ; [FileInfo.hIcon] - хэндл маленькой иконки, связанной с файлом
  14.  
  15.         ; Папка
  16.         invoke  SHGetFileInfo,fdir,0,FileInfo,sizeof.SHFILEINFO,\
  17.                 SHGFI_ICON+SHGFI_LARGEICON
  18.         ; [FileInfo.hIcon] - хэндл большой иконки, связанной с папкой
  19.  
  20.         invoke  SHGetFileInfo,fdir,0,FileInfo,sizeof.SHFILEINFO,\
  21.                 SHGFI_ICON+SHGFI_SMALLICON
  22.         ; [FileInfo.hIcon] - хэндл маленькой иконки, связанной с папкой
Последний, наиболее навороченный способ извлечения иконок - работа с COM-объектами, а точнее IExtractIconW. С его помощью можно извлекать иконки исполняемых файлов и динамических библиотек, а также ассоциированные иконки. Для работы сперва понадобится описать несколько интерфейсов и GUID'ов, так как FASM не в курсе и про них тоже.
  1. ; GUID {93F2F68C-1D1B-11D3-A30E-00C04F79ABD1}
  2. IID_IShellFolder2 dd 093F2F68Ch
  3.                   dw 01D1Bh
  4.                   dw 011D3h
  5.                   db 0A3h, 00Eh, 000h, 0C0h, 04Fh, 079h, 0ABh, 0D1h
  6.  
  7. ; GUID {000214FA-0000-0000-C000-000000000046}
  8. IID_IExtractIconW dd 0000214FAh
  9.                   dw 00000h
  10.                   dw 00000h
  11.                   db 0C0h, 000h, 000h, 000h, 000h, 000h, 000h, 046h
  12.  
  13. ; IID_IShellFolder2 Interface
  14. struct IShellFolder2
  15.     QueryInterface      dd ?
  16.     AddRef              dd ?
  17.     Release             dd ?
  18.  
  19.     ; IShellFolder
  20.     ParseDisplayName    dd ?
  21.     EnumObjects         dd ?
  22.     BindToObject        dd ?
  23.     BindToStorage       dd ?
  24.     CompareIDs          dd ?
  25.     CreateViewObject    dd ?
  26.     GetAttributesOf     dd ?
  27.     GetUIObjectOf       dd ?
  28.     GetDisplayNameOf    dd ?
  29.     SetNameOf           dd ?
  30. ends
  31.  
  32. ; IID_IExtractIconW Interface
  33. struct IExtractIcon
  34.     QueryInterface      dd ?
  35.     AddRef              dd ?
  36.     Release             dd ?
  37.  
  38.     GetIconLocation     dd ?
  39.     Extract             dd ?
  40. ends
Методы этого COM-объекта работает исключительно с юникодом, все пути к файлам должны быть не только полные, но и корректные в плане наличия файла. После получения иерархии объектов и их интерфейсов, с помощью метода GetIconLocation определяется местоположение файла, а с помощью метода Extract получается хэндл иконки нужного размера. Если иконки запрошенного размера в файле нет, то ищется ближайшая иконка большего размера и масштабируется до нужной высоты и ширины. Если иконки большего размера тоже нет, то хэндл иконки не возвращается. Такой способ работы с иконками неоправданно трудоемкий и при этом ограниченный по возможностям, поэтому, на мой взгляд, использовать его надо только в крайних случаях. Но знать о его существовании будет полезно.
  1. ...
  2. psfDesktop   dd ?
  3. pidl         dd ?
  4. appObject    dd ?
  5. extractIcon  dd ?
  6.  
  7. pidlRelative dd ?
  8. iconLocation rw MAX_PATH
  9. iconIndex    dd ?
  10. iconFlags    dd ?
  11. hiconLarge   dd ?
  12. hiconSmall   dd ?
  13.  
  14. uname1 du 'c:\windows\notepad.exe',0
  15. uname2 du 'c:\windows\explorer.exe',0
  16. ...
  17.         ; Получить объект рабочего стола
  18.         invoke  SHGetDesktopFolder,psfDesktop
  19.  
  20.         ; Получить объект файла
  21.         mov     eax,[psfDesktop]
  22.         mov     eax,[eax]
  23.         stdcall dword [eax+IShellFolder2.ParseDisplayName],[psfDesktop],\
  24.                 NULL,NULL,uname1,NULL,pidl,NULL
  25.  
  26.         ; Клонировать последнюю запись структуры ITEMIDLIST
  27.         invoke  ILFindLastID,[pidl]
  28.         invoke  ILClone,eax
  29.         mov     [pidlRelative],eax
  30.         ; Удалить последнюю запись структуры ITEMIDLIST
  31.         invoke  ILRemoveLastID,[pidl]
  32.  
  33.         ; Получить интерфейс IShellFolder2
  34.         mov     eax,[psfDesktop]
  35.         mov     eax,[eax]
  36.         stdcall dword [eax+IShellFolder2.BindToObject],[psfDesktop],\
  37.                 [pidl],NULL,IID_IShellFolder2,appObject
  38.  
  39.         ; Очистить структуру ITEMIDLIST
  40.         invoke  ILFree,[pidl]
  41.  
  42.         ; Получить интерфейс IExtractIcon
  43.         mov     eax,[appObject]
  44.         mov     eax,[eax]
  45.         stdcall dword [eax+IShellFolder2.GetUIObjectOf],[appObject],\
  46.                 NULL,1,pidlRelative,IID_IExtractIconW,NULL,extractIcon
  47.  
  48.         ; Очистить структуру ITEMIDLIST
  49.         invoke  ILFree,[pidlRelative]
  50.  
  51.         ; Получить хранилище и индекс иконки
  52.         mov     eax,[extractIcon]
  53.         mov     eax,[eax]
  54.         stdcall dword [eax+IExtractIcon.GetIconLocation],[extractIcon],\
  55.                 NULL,iconLocation,MAX_PATH,iconIndex,iconFlags
  56.  
  57.         ; Извлечь иконку 64х64
  58.         mov     eax,[extractIcon]
  59.         mov     eax,[eax]
  60.         stdcall dword [eax+IExtractIcon.Extract],[extractIcon],\
  61.                 iconLocation,[iconIndex],hiconLarge,hiconSmall,(16 shl 16 + 64)
  62.         ; [hiconLarge] - хэндл большой иконки
  63.         ; [hiconSmall] - хэндл маленькой иконки
  64.  
  65.         ; Извлечь иконку 16х16
  66.         mov     eax,[extractIcon]
  67.         mov     eax,[eax]
  68.         stdcall dword [eax+IExtractIcon.Extract],[extractIcon],\
  69.                 iconLocation,[iconIndex],hiconLarge,hiconSmall,(16 shl 16 + 16)
  70.         ; [hiconLarge] - хэндл большой иконки
  71.         ; [hiconSmall] - хэндл маленькой иконки
  72.  
  73.         ; Извлечь иконку и отмасштабировать ее в 6х6
  74.         mov     eax,[extractIcon]
  75.         mov     eax,[eax]
  76.         stdcall dword [eax+IExtractIcon.Extract],[extractIcon],\
  77.                 iconLocation,[iconIndex],hiconLarge,hiconSmall,(16 shl 16 + 6)
  78.         ; [hiconLarge] - хэндл большой иконки
  79.         ; [hiconSmall] - хэндл маленькой иконки
  80.  
  81.         ; Извлечь иконку 48х48 по прямому пути и индексу
  82.         mov     eax,[extractIcon]
  83.         mov     eax,[eax]
  84.         stdcall dword [eax+IExtractIcon.Extract],[extractIcon],\
  85.                 uname2,1,hiconLarge,hiconSmall,(16 shl 16 + 48)
  86.         ; [hiconLarge] - хэндл большой иконки
  87.         ; [hiconSmall] - хэндл маленькой иконки
Раз уж мы затронули тему иконок, ассоциированных с файлами разных типов, логично было бы рассмотреть функции, которые используются для их получения. На различных форумах чаще всего рекомендуют пользоваться стандартной функцией ExtractAssociatedIcon. Казалось бы, очень удобная функция: передал указатель на имя проверяемого файла, получил хэндл иконки с нужным индексом. Для и для исполняемых файлов тоже годится. На самом деле все получается не так красиво. Функция ExtractAssociatedIcon возвращает хэндл только большой иконки, то есть 32х32, иконки других размеров будут отмасштабированы. А самые приколы начинаются, если попытаться вызвать функцию для несуществующего файла. В этом случае буфер с переданным именем файла перезапишется чем-то вроде "C:\Windows\system32\shell32.dll" и никакого хэндла иконки не вернется. Если имя файла хранится в защищенном от записи участке памяти или под него зарезервирована более короткая строка, то результат может получиться непредсказуемым. Отловить такую ошибку будет непросто. А главный недостаток в том, что через ExtractAssociatedIcon нельзя просто взять и получить иконку, связанную с каким-либо расширением, без указания реально существующего файла с таким расширением.
  1. ...
  2. fname   db 'source.exe',0
  3. fext    db '*.jpg',0
  4. iconIdx dd ?
  5. xfile   rb MAX_PATH
  6. ...
  7.         invoke  GetModuleHandle,NULL
  8.         mov     ebx,eax
  9.  
  10.         ; Извлечь первую и вторую иконки из исполняемого файла
  11.         mov     [iconIdx],0
  12.         invoke  lstrcpy,xfile,fname
  13.         invoke  ExtractAssociatedIcon,ebx,xfile,iconIdx
  14.         ; EAX - хэндл первой большой иконки
  15.         mov     [iconIdx],1
  16.         invoke  lstrcpy,xfile,fname
  17.         invoke  ExtractAssociatedIcon,ebx,xfile,iconIdx
  18.         ; EAX - хэндл второй большой иконки
  19.  
  20.         ; Попытаться извлечь иконку из несуществующего файла
  21.         mov     [iconIdx],0
  22.         invoke  lstrcpy,xfile,fext
  23.         invoke  ExtractAssociatedIcon,ebx,xfile,iconIdx
  24.         ; Строка xfile перезаписана
Всех перечисленных недостатков функции ExtractAssociatedIcon лишена уже знакомая нам функция SHGetFileInfo. При извлечении ассоциированных иконок всего лишь потребуется указать один дополнительный флаг, в остальном все остается прежним.
  1. SHGFI_USEFILEATTRIBUTES = 0x000000010
  1. ...
  2. fname   db 'c:\Documents\export.xls',0
  3. fext    db '*.jpg',0
  4.  
  5. FileInfo SHFILEINFO
  6. ...
  7.         ; Имя файла, не обязательно существующего
  8.         invoke  SHGetFileInfo,fname,0,FileInfo,sizeof.SHFILEINFO,\
  9.                 SHGFI_ICON+SHGFI_LARGEICON+SHGFI_USEFILEATTRIBUTES
  10.         ; [FileInfo.hIcon] - хэндл большой иконки, связанной с файлом
  11.         invoke  SHGetFileInfo,fname,0,FileInfo,sizeof.SHFILEINFO,\
  12.                 SHGFI_ICON+SHGFI_SMALLICON+SHGFI_USEFILEATTRIBUTES
  13.         ; [FileInfo.hIcon] - хэндл маленькой иконки, связанной с файлом
  14.  
  15.         ; Расширение без указания конкретного файла
  16.         invoke  SHGetFileInfo,fext,0,FileInfo,sizeof.SHFILEINFO,\
  17.                 SHGFI_ICON+SHGFI_LARGEICON+SHGFI_USEFILEATTRIBUTES
  18.         ; [FileInfo.hIcon] - хэндл большой иконки, связанной с расширением
  19.         invoke  SHGetFileInfo,fext,0,FileInfo,sizeof.SHFILEINFO,\
  20.                 SHGFI_ICON+SHGFI_SMALLICON+SHGFI_USEFILEATTRIBUTES
  21.         ; [FileInfo.hIcon] - хэндл маленькой иконки, связанной с расширением
Я встречал еще один способ получения иконок, ассоциированных с расширениями файлов. Но там данные о приложениях, связанных с расширением, читались из реестра, затем из найденных приложений извлекались иконки одним из вышеперечисленных способов. Из-за абсолютной неэффективности и монстрячности этого способа я его даже не буду рассматривать.

Еще может пригодиться функция PathParseIconLocation, которая парсит текстовую строку вида "path\filename.exe,3" и возвращает имя файла и индекс иконки, которую надо из него извлечь. Такой формат записи характерен для всяких конфигурационных файлов. Но пользоваться этой функцией надо с большой осторожностью, так как исходная строка модифицируется. Зато при правильном использовании отпадает необходимость в изобретении велосипедов, всю грязную работу сделает система.

Ну и совсем напоследок код, который сбрасывает кеш иконок в Проводнике. Это бывает полезно, например, когда надо обновить данные после изменения ассоциаций.
  1.         invoke SHChangeNotify,SHCNE_ASSOCCHANGED,SHCNF_IDLIST,NULL,NULL
В приложении пример программы с исходным текстом, которая извлекает иконки из файлов всеми перечисленными способами. Результат сразу же показывается в диалоговом окне.

Пример программы с исходным текстом (FASM)Пример программы с исходным текстом (FASM)

Read.Icons.Demo.zip (40,574 bytes)


Поделиться ссылкой ВКонтакте
Просмотров: 2225 | Комментариев: 2

Метки: Assembler, иконки, COM
Внимание! Статья опубликована больше года назад, информация могла устареть!

Комментарии

Отзывы посетителей сайта о статье
ManHunter (10.11.2020 в 15:56):
Добавил пример работы с иконками через COM, архив обновлен.
ManHunter (09.11.2020 в 16:48):
Добавил пример работы с LoadIconWithScaleDown. Архив обновлен.

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

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2024
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.07 сек. / MySQL: 2 (0.0052 сек.) / Память: 4.5 Mb
Наверх