Blog. Just Blog

Загрузка иконки напрямую из памяти

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

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

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

Попробуем копнуть в сторону ресурсов. Функция CreateIconFromResourceEx умеет создавать иконку напрямую из двоичных данных ресурса, соответствующего этой иконке. Но у нас иконка находится в памяти, а не в ресурсах, поэтому узнать адрес двоичных данных при помощи функции LoadResource не получится. То есть решение задачи сводится к тому, чтобы найти эти данные в памяти самостоятельно.

Для начала давайте посмотрим официальную документацию от Microsoft, в которой описывается внутренний формат ICO-файла. Согласно этой документации, заголовок ICO-файла состоит из структуры ICONDIR, в которой описывается тип и количество изображений в файле, и одной или нескольких структур ICONDIRENTRY, в которых, соответственно, содержится информация уже о конкретном изображении. Выглядят они так:
  1. struct ICONDIR
  2.     idReserved dw ? ; Зарезервировано (должно быть 0)
  3.     idType     dw ? ; Тип ресурса (1 для иконок)
  4.     idCount    dw ? ; Количество изображений в иконке
  5. ends
  1. struct ICONDIRENTRY
  2.     bWidth        db ? ; Ширина изображения в пикселах
  3.     bHeight       db ? ; Высота изображения в пикселах
  4.     bColorCount   db ? ; Количество цветов
  5.     bReserved     db ? ; Зарезервировано
  6.     wPlanes       dw ? ; Зарезервировано
  7.     wBitCount     dw ? ; Зарезервировано
  8.     dwBytesInRes  dd ? ; Размер данных изображения
  9.     dwImageOffset dd ? ; Смещение данных относительно начала файла
  10. ends
Чтобы получить информацию о всех иконках, содержащихся в файле (а в нашем случае речь идет о файле, загруженном в память), надо сначала получить количество изображений, затем последовательно обойти все структуры ICONDIRENTRY в заголовке. Тут нас интересует два параметра: bWidth для проверки, соответствует ли размер этой иконки нужному нам, и dwImageOffset, чтобы получить указатель на двоичные данные иконки для загрузки их при помощи CreateIconFromResourceEx.

Осталось превратить теорию в практику. У меня получилась вот такая функция для загрузки иконки нужного размера напрямую из памяти.
  1. ;----------------------------------------------------------------
  2. ; Функция загрузки иконки из памяти
  3. ; by ManHunter / PCL (www.manhunter.ru)
  4. ;----------------------------------------------------------------
  5. ; Параметры:
  6. ;   lpMem - указатель на загруженный файл иконки
  7. ;   dSize - желательный размер иконки или 0 - любая
  8. ; На выходе:
  9. ;   EAX - хэндл иконки или 0, если загрузить не удалось или
  10. ;   иконка нужного размера не найдена
  11. ;----------------------------------------------------------------
  12. proc LoadIconFromMemory lpMem:DWORD, dSize:DWORD
  13.         push    esi edi ecx ebx
  14.  
  15.         ; По умолчанию иконка не загружена
  16.         xor     eax,eax
  17.  
  18.         mov     esi,[lpMem]
  19.  
  20.         ; Это файл иконки?
  21.         cmp     word [esi+ICONDIR.idReserved],0
  22.         jne      .loc_ret
  23.         cmp     word [esi+ICONDIR.idType],1
  24.         jne      .loc_ret
  25.  
  26.         ; Количество иконок в файле
  27.         movzx   ecx,word [esi+ICONDIR.idCount]
  28.         cmp     ecx,0
  29.         je      .loc_ret
  30.  
  31.         ; Указатель на первую запись ICONDIRENTRY
  32.         add     esi,6
  33.  
  34. .loc_scan:
  35.         ; Размер иконки
  36.         movzx   ebx,byte [esi+ICONDIRENTRY.bWidth]
  37.         or      ebx,ebx
  38.  
  39.         ; Нужна иконка конкретного размера?
  40.         cmp     [dSize],0
  41.         jz      .load_icon
  42.         cmp     ebx,[dSize]
  43.         jne     .next_icon
  44. .load_icon:
  45.         mov     edi,[esi+ICONDIRENTRY.dwImageOffset]
  46.         add     edi,[lpMem]
  47.  
  48.         ; Это иконка в формате PNG?
  49.         cmp     dword [edi],474E5089h
  50.         je      .next_icon
  51.  
  52.         invoke  CreateIconFromResourceEx,edi,[esi+ICONDIRENTRY.dwBytesInRes],\
  53.                 TRUE,0x30000,ebx,ebx,LR_DEFAULTCOLOR
  54.         or      eax,eax
  55.         jnz     .loc_ret
  56.  
  57. .next_icon:
  58.         ; Следующая иконка
  59.         add     esi,sizeof.ICONDIRENTRY
  60.         sub     ecx,1
  61.         jnz     .loc_scan
  62.  
  63.         ; Иконка нужного размера не найдена
  64.         xor     eax,eax
  65. .loc_ret:
  66.         pop     ebx ecx edi esi
  67.         ret
  68. endp
На входе два параметра: lpMem - указатель на участок памяти, куда загружена иконка, и dSize - желаемый размер иконки или 0, если надо загрузить первую попавшуюся иконку или единственную имеющуюся без привязки к размеру. На выходе в регистре EAX вернется хэндл загруженной иконки или 0, если по какой-то причине иконку загрузить не удалось или нет иконки нужного размера.

Важно! В мультииконках могут встретиться изображения размером 256х256 в формате PNG. С точки зрения системы, это вполне допустимые варианты, особенно на современных версиях Windows. Но из-за того, что значение 256 выходит за границы байта, отведенного под описание размера, в структуре ICONDIRENTRY такие иконки имеют нулевые значения ширины и высоты. При обработке эти изображения будут всегда игнорироваться. Также подразумевается, что все иконки в файле имеют строго квадратную форму, хотя в реальности это может оказаться совсем не так. Еще обратите внимание, что в приведенной выше функции не проверяются ни выход указателя за реальные размеры файла, ни соответствие размера данных заявленному размеру иконки. Подразумевается, что файл с иконкой корректный и его никто злонамеренно не модифицировал. В реальных проектах такие моменты надо обязательно учитывать, особенно в тех случаях, когда иконки на обработку приходят извне.

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

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

Load.Icons.from.Memory.Demo.zip (14,678 bytes)


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

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

Комментарии

Отзывы посетителей сайта о статье
ManHunter (09.11.2020 в 17:05):
addhaloka, вот тут готовый пример: https://www.manhunter.ru/assem..._knopku.html
На MessageBoxIndirect после его открытия загружается иконка из HICON
ManHunter (22.09.2020 в 09:26):
После очередного обновления системы код отвалился. Исправил ошибку с ICONDIRENTRY.dwBytesInRes, теперь все нормально.
addhaloka (09.04.2018 в 05:07):
ЦитатаРешение твоей задачи есть, но оно за гранью добра и зла.

Спасибо, покопаюсь. Вообще, как альтернативу, можно юзать диалог вместо MessageBox, типа invoke DialogBoxParam,[hInst],1001,[hwnddlg],DialogAboutProc,NULL
Там уже можно и иконки, и многое другое лепить без извращений. Пример с закосом под MessageBox: http://s1.bild.me/bilder/11041...9_045853.png :)
ManHunter (08.04.2018 в 17:44):
Неа, обычными средствами нельзя. Или ресурсы родительского процесса, или одна из стандартных системных иконок.

Решение твоей задачи есть, но оно за гранью добра и зла. Информация к размышлению: http://www.manhunter.ru/assemb...sagebox.html
и просто оставлю тут значение GWL_ID = 0x0014
addhaloka (07.04.2018 в 10:28):
Т. е. такая функция (в masm'ских примерах есть, MessageBoxIndirect + MSGBOXPARAMS):
stdcall MessageBoxIn,[hInst],[hwnddlg],msgAbout,cptAbout,MB_OK,101
101 - имя иконки в ресурсах. А можно ли каким-то макаром использовать тут иконку из памяти (как ниже спросил, hIcon -> ID или же напрямую hICon)?
addhaloka (07.04.2018 в 10:13):
Полный вопрос не влез :(, поэтому основное:
Возможно ли получить из hIcon некий ID, который можно использовать с MessageBoxIndirect и т. п.?
ЖК (03.04.2018 в 20:02):
>Важно! В мультииконках могут встретиться изображения размером 256х256 в формате PNG

Вот да. Доводилось столкнуться с программой, в которой вообще для всего использовалась одна-единственная (!) такая PNG-иконка. Оказывается Винды, начиная с Vista, способны автоматом масштабировать размер и глубину цвета такой иконки в зависимости от текущих настроек экрана, и отображать её везде - в Explorer, в панели задач и даже при минимизации в трей (выглядит так себе, но тем не менее). Случай, понятно, экзотический, но технически возможный.
addhaloka (01.04.2018 в 22:49):
Спасибо! Когда-то пытался подобное запилить, но не осилил. Сделал в итоге через GdipCreateHICONFromBitmap по мотивам: http://www.manhunter.ru/assemb...hyu_gdi.html
Но этот вариант получше будет. :)
wet (25.03.2018 в 05:53):
Цитатачто иконка скачана из сети, находится в памяти,

Понятно. Дело в том, что я пишу на PureBasic (компилятор FASM), а там есть встроенная функция CatchImage(0, *MemoryAddress). Потому и не было необходимости искать обходные пути при загрузке иконки из памяти.
ManHunter (24.03.2018 в 21:24):
Имеется в виду, что иконка скачана из сети, находится в памяти, но никуда больше не записывается. Естественно, адрес памяти, куда она скачивалась, известен. Или, как в прилагаемом примере, иконка находится в секции кода, а не в ресурсах. И тоже, естественно, с известным адресом.
Петренко (24.03.2018 в 20:55):
ЦитатаОткуда то иконка в память должна попасть и мы должны точно знать указатель на загруженный файл иконки.

Вот Вам указатель на загруженный файл иконки: http://www.manhunter.ru/favicon.ico . Попробуйте скормить его ExtractIcon, ExtractIconEx, или SHGetFileInfo. О результатах можете не отписываться, они известны заранее.
wet (24.03.2018 в 14:48):
Цитатафайл с иконкой есть только в памяти, на диске его нет.

Значит я просто не сталкивался с такой ситуацией. Откуда то иконка в память должна попасть и мы должны точно знать указатель на загруженный файл иконки.
ManHunter (24.03.2018 в 10:07):
wet, напомню условие задачи: файл с иконкой есть только в памяти, на диске его нет. Эти API в такой ситуации не помогут.
wet (24.03.2018 в 06:52):
Познавательно и как то мудрёно( для меня лично).
Использую обычно API типа ExtractIcon, иногда ExtractIconEx, или даже SHGetFileInfo.

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

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

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