Загрузка иконки напрямую из памяти
Загрузка иконки напрямую из памяти
Уже который раз сталкиваюсь с тем, что для решения какой-нибудь простейшей задачи приходится сперва перекопать огромную кучу информации, а затем хитро извернуться, потому что штатных решений нет в принципе. Одна из таких задач выглядит на первый взгляд очень просто: в память загружен файл иконки, надо из него сделать хэндл HICON для дальнейшего использования.
Дополнительным условием является то, что физически файла с иконкой на диске нет. Например, иконка загружена из каких-нибудь интернетов или передана приложению иным образом. Функция LoadImage, которая обычно используется для создания иконок, работает или с файлом изображения, или с ресурсами файла, где это изображение хранится. Записывать данные во временный файл, а затем читать его - это не вариант, потери производительности будут просто колоссальными. Значит решение с загрузкой из файла абсолютно точно отпадает.
Попробуем копнуть в сторону ресурсов. Функция CreateIconFromResourceEx умеет создавать иконку напрямую из двоичных данных ресурса, соответствующего этой иконке. Но у нас иконка находится в памяти, а не в ресурсах, поэтому узнать адрес двоичных данных при помощи функции LoadResource не получится. То есть решение задачи сводится к тому, чтобы найти эти данные в памяти самостоятельно.
Для начала давайте посмотрим официальную документацию от Microsoft, в которой описывается внутренний формат ICO-файла. Согласно этой документации, заголовок ICO-файла состоит из структуры ICONDIR, в которой описывается тип и количество изображений в файле, и одной или нескольких структур ICONDIRENTRY, в которых, соответственно, содержится информация уже о конкретном изображении. Выглядят они так:
Code (Assembler) : Убрать нумерацию
- struct ICONDIR
- idReserved dw ? ; Зарезервировано (должно быть 0)
- idType dw ? ; Тип ресурса (1 для иконок)
- idCount dw ? ; Количество изображений в иконке
- ends
Code (Assembler) : Убрать нумерацию
- struct ICONDIRENTRY
- bWidth db ? ; Ширина изображения в пикселах
- bHeight db ? ; Высота изображения в пикселах
- bColorCount db ? ; Количество цветов
- bReserved db ? ; Зарезервировано
- wPlanes dw ? ; Зарезервировано
- wBitCount dw ? ; Зарезервировано
- dwBytesInRes dd ? ; Размер данных изображения
- dwImageOffset dd ? ; Смещение данных относительно начала файла
- ends
Осталось превратить теорию в практику. У меня получилась вот такая функция для загрузки иконки нужного размера напрямую из памяти.
Code (Assembler) : Убрать нумерацию
- ;----------------------------------------------------------------
- ; Функция загрузки иконки из памяти
- ; by ManHunter / PCL (www.manhunter.ru)
- ;----------------------------------------------------------------
- ; Параметры:
- ; lpMem - указатель на загруженный файл иконки
- ; dSize - желательный размер иконки или 0 - любая
- ; На выходе:
- ; EAX - хэндл иконки или 0, если загрузить не удалось или
- ; иконка нужного размера не найдена
- ;----------------------------------------------------------------
- proc LoadIconFromMemory lpMem:DWORD, dSize:DWORD
- push esi edi ecx ebx
- ; По умолчанию иконка не загружена
- xor eax,eax
- mov esi,[lpMem]
- ; Это файл иконки?
- cmp word [esi+ICONDIR.idReserved],0
- jne .loc_ret
- cmp word [esi+ICONDIR.idType],1
- jne .loc_ret
- ; Количество иконок в файле
- movzx ecx,word [esi+ICONDIR.idCount]
- cmp ecx,0
- je .loc_ret
- ; Указатель на первую запись ICONDIRENTRY
- add esi,6
- .loc_scan:
- ; Размер иконки
- movzx ebx,byte [esi+ICONDIRENTRY.bWidth]
- or ebx,ebx
- ; Нужна иконка конкретного размера?
- cmp [dSize],0
- jz .load_icon
- cmp ebx,[dSize]
- jne .next_icon
- .load_icon:
- mov edi,[esi+ICONDIRENTRY.dwImageOffset]
- add edi,[lpMem]
- ; Это иконка в формате PNG?
- cmp dword [edi],474E5089h
- je .next_icon
- invoke CreateIconFromResourceEx,edi,[esi+ICONDIRENTRY.dwBytesInRes],\
- TRUE,0x30000,ebx,ebx,LR_DEFAULTCOLOR
- or eax,eax
- jnz .loc_ret
- .next_icon:
- ; Следующая иконка
- add esi,sizeof.ICONDIRENTRY
- sub ecx,1
- jnz .loc_scan
- ; Иконка нужного размера не найдена
- xor eax,eax
- .loc_ret:
- pop ebx ecx edi esi
- ret
- endp
Важно! В мультииконках могут встретиться изображения размером 256х256 в формате PNG. С точки зрения системы, это вполне допустимые варианты, особенно на современных версиях Windows. Но из-за того, что значение 256 выходит за границы байта, отведенного под описание размера, в структуре ICONDIRENTRY такие иконки имеют нулевые значения ширины и высоты. При обработке эти изображения будут всегда игнорироваться. Также подразумевается, что все иконки в файле имеют строго квадратную форму, хотя в реальности это может оказаться совсем не так. Еще обратите внимание, что в приведенной выше функции не проверяются ни выход указателя за реальные размеры файла, ни соответствие размера данных заявленному размеру иконки. Подразумевается, что файл с иконкой корректный и его никто злонамеренно не модифицировал. В реальных проектах такие моменты надо обязательно учитывать, особенно в тех случаях, когда иконки на обработку приходят извне.
В приложении пример программы с исходным текстом, в которой используется функция загрузки иконки напрямую из памяти.
Просмотров: 2724 | Комментариев: 14
Внимание! Статья опубликована больше года назад, информация могла устареть!
Комментарии
Отзывы посетителей сайта о статье
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
Решение твоей задачи есть, но оно за гранью добра и зла. Информация к размышлению: 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)?
stdcall MessageBoxIn,[hInst],[hwnddlg],msgAbout,cptAbout,MB_OK,101
101 - имя иконки в ресурсах. А можно ли каким-то макаром использовать тут иконку из памяти (как ниже спросил, hIcon -> ID или же напрямую hICon)?
addhaloka
(07.04.2018 в 10:13):
Полный вопрос не влез :(, поэтому основное:
Возможно ли получить из hIcon некий ID, который можно использовать с MessageBoxIndirect и т. п.?
Возможно ли получить из hIcon некий ID, который можно использовать с MessageBoxIndirect и т. п.?
ЖК
(03.04.2018 в 20:02):
>Важно! В мультииконках могут встретиться изображения размером 256х256 в формате PNG
Вот да. Доводилось столкнуться с программой, в которой вообще для всего использовалась одна-единственная (!) такая PNG-иконка. Оказывается Винды, начиная с Vista, способны автоматом масштабировать размер и глубину цвета такой иконки в зависимости от текущих настроек экрана, и отображать её везде - в Explorer, в панели задач и даже при минимизации в трей (выглядит так себе, но тем не менее). Случай, понятно, экзотический, но технически возможный.
Вот да. Доводилось столкнуться с программой, в которой вообще для всего использовалась одна-единственная (!) такая 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.
Использую обычно API типа ExtractIcon, иногда ExtractIconEx, или даже SHGetFileInfo.
Добавить комментарий
Заполните форму для добавления комментария
На MessageBoxIndirect после его открытия загружается иконка из HICON