Автор Тема: FAQ по использованию ресурсов в AutoIt  (Прочитано 74959 раз)

0 Пользователей и 1 Гость просматривают эту тему.

Оффлайн Yashied [?]

  • AutoIt MVP
  • Глобальный модератор
  • *
  • Сообщений: 5379
  • Репутация: 2693
  • Пол: Мужской
    • Награды
  • Версия AutoIt: 3.3.x.x
Так как меня часто спрашивают о том, как использовать ресурсы в скомпилированных AutoIt скриптах, я решил подробно рассмотреть здесь эту тему и написать нечто вроде руководства для начинающих (и не только). Итак, для начала вам потребуется обзавестись следующими утилитами (если у вас их еще нет):


Советую сразу положить библиотеки Icons.au3 и WinAPIEx.au3 в папку c установленным AutoIt - "...\AutoIt3\Include", чтобы в последствии не было проблем с включением этих библиотек в ваши скрипты. Есть еще знаменитая библиотека Resources.au3 от Zedna, которой многие здесь наверное пользуются, но я решил обойтись без нее, т.к. при ее использовании у вас может появиться еще больше вопросов (я с этим уже неоднократно сталкивался). По мере возможности, я буду расширять эту тему и добавлять здесь новую информацию по ресурсам. Если у кого-нибудь возникнут вопросы относительно этого материала, то задавайте их в теме Обсуждение FAQ'а по использованию ресурсов в AutoIt.


Содержание.

« Последнее редактирование: Март 20, 2012, 04:06:57 от CreatoR »


Думай, прежде чем говорить.

Русское сообщество AutoIt

FAQ по использованию ресурсов в AutoIt
« Отправлен: Октябрь 10, 2010, 02:18:55 »

Оффлайн Yashied [?]

  • AutoIt MVP
  • Глобальный модератор
  • *
  • Сообщений: 5379

  • Автор темы
  • Репутация: 2693
  • Пол: Мужской
    • Награды
  • Версия AutoIt: 3.3.x.x
Re: FAQ по использованию ресурсов в AutoIt
« Ответ #1, Отправлен: Октябрь 14, 2010, 14:55:47 »

Общая информация.

Скомпилированный AutoIt скрипт представляет собой обычный исполняемый .exe файл, который не нуждается в наличии установленного на компьютере AutoIt и каких-либо дополнительных библиотек, которые были задействованы при его (скрипта) написании. Таким образом, после компиляции вашего скрипта в исполняемый файл, вы получаете полностью самостоятельную программу, которая будет успешно работать на любом другом компьютере. Как и любые другие приложения Windows, програмы, написанные на AutoIt, могут содержать в себе разнообразные ресурсы (иконки, картинки, курсоры и т.д.), которые вы сами можете добавлять в исполняемый файл, а затем использовать их в ходе выполнения программы. Собственно здесь у многих и возникают два основных вопроса: как добавить ресурсы в тело программы и как их потом использовать? Вот именно об этом и пойдет дальше речь.

Все ресурсы в файле делятся на типы, однако в AutoIt не все они могут быть задействованы, например DIALOG или MENU не имеют никакого смысла для программ, написанных на AutoIt. Поэтому я приведу лишь те типы ресурсов, которые наиболее часто используются при написании AutoIt программ:

  • BITMAP (2)
  • CURSOR (1)
  • FONT (8)
  • ICON (3)
  • RCDATA (10)
  • STRING (6)
  • VERSION (16)
  • User-defined

Ресурсы в Windows могут быть как стандартного (предопределенного) типа (BITMAP, CURSOR, FONT и т.д.), так и типа, заданного пользователем (User-defined). Если используется один из стандартных типов, то доступ к этому ресурсу осуществляется посредством целочисленного значения (константы), определяющего этот ресурс (BITMAP - 2, CURSOR - 1, FONT - 8 и т.д.). В случае User-defined ресурсов, тип задается любым строковым значением, например "JPEG" или "SOUND". Каждый из вышеперечисленных ресурсов, кроме RCDATA и User-defined, записываются в исполнямый файл в специальном формате, присущем именно этому типу ресурса. Например ресурс типа ICON не является копией файла иконки (.ico), вставленной куда-то там в .exe файл. Наибольший интерес здесь наверное представляют как раз типы RCDATA и User-defined, но об этом позже. А начну я пожалуй с ICON, как самый частоиспользуемый ресурс. Но сначала скажу несколько слов о том, как можно добавить ресурсы в AutoIt программы.

Есть два самых популярных способа (ручное редактирование в каких-либо редакторах ресурсов я здесь не рассматриваю) добавления ресурсов в исполняемый файл: с помощью утилиты AutoIt3Wrapper или c помощью бесплатного редактора ресурсов Resource Hacker, благодаря поддержки им командной строки. Выбирая AutoIt3Wrapper, вы получаете относительную простоту использования, но очень урезанные возможности. Например AutoIt3Wrapper поддерживает далеко не все типы ресурсов, кроме того, нет никакой возможности удалять или изменять уже существующие после компиляции ресурсы. Но в большинстве случаев этого более чем достаточно. Если вы решили использовать Resource Hacker, то тут у вас не будет никаких ограничеий, вы - царь и бог. Можете добавлять, удалять, изменять и т.д. практически любые ресурсы (ну или почти любые) в вашем файле. Но для этого нужно будет изучить синтаксис командной строки (далеко не самый простой) Resource Hacker'а и создать командный .bat (.cmd) файл, прописав в нем все вызовы. А еще можно вызвать Resource Hacker непосредственно из AutoIt3Wrapper'а, чем многие (в том числе и я) и пользуются. Далее, при рассмотрении непосредственно использования каждого ресурса в отдельности, я буду приводить примеры как для AutoIt3Wrapper'а, так и для Resource Hacker'а.

Содержание
« Последнее редактирование: Октябрь 14, 2010, 15:52:11 от Yashied, Причина: Объединение сообщений »

Оффлайн Yashied [?]

  • AutoIt MVP
  • Глобальный модератор
  • *
  • Сообщений: 5379

  • Автор темы
  • Репутация: 2693
  • Пол: Мужской
    • Награды
  • Версия AutoIt: 3.3.x.x
Re: FAQ по использованию ресурсов в AutoIt
« Ответ #2, Отправлен: Октябрь 14, 2010, 16:14:36 »

Иконки.

Все иконки в исполняемом файле находятся в ресурсах типа ICON, точнее в GROUP_ICON, который в свою очередь может объединять несколько ICON ресурсов. Дело в том, что иконкой называется не .ico файл, состоящий из нескольких иконок разного размера и цветности (16x16, 32x32 и т.д.), а только одно изображение, например 16x16x32bpp, 16x16x4bpp и т.д. Каждое такое отдельное изображение и есть ICON, и имеет свой уникальный номер. А совокупность нескольких иконок называется GROUP_ICON, и именно имя ресурса GROUP_ICON мы видим во многих редакторах ресурсов. Но для простоты я буду дальше называть это ICON или просто иконка.



На скриншоте показаны все ресурсы, которые присутствуют по умолчанию в скомпилированном AutoIt скрипте, и выделена иконка с именем ресурса 99. Также, эта иконка является иконкой самой программы, т.к. стоит первой в списке, и именно она будет отображаться в проводнике Windows для этого файла. 2057 - это идентификатор языка ресурса, об этом позже. Номера 4, 5, 6 и т.д., это уникальные идентификаторы для каждой отдельной иконки в пределах всего ресурса (не только группы 99). Но нам это пока знать не обязательно. Здесь важно запомнить, что каждая иконка может состоять из нескольких отдельных изображений, различающихся размерами и количеством цветов. Начиная с Windows 2000, иконки с цветностью менее 32bpp используются редко и в основном присутствуют для совместимости с различными устройствами вывода или более ранними версиями Windows.

Как видно на скриншоте, после компилияции AutoIt скрипта в исполняемый файл, по умолчанию в нем уже будут присутствовать 4 иконки с названиями 99, 162, 164 и 169. Первая иконка, как я уже сказал выше, является непосредственно иконкой самой программы, и представляет собой логотип AutoIt. Оставшиеся три иконки нужны AutoIt для внутренних целей. Например иконка под номером 164 отображается в системном трее при активации паузы, а 169 используется в ListView при перетаскивании (Drag-n-Drop) его элементов. Но, как ни странно, в большинстве случаев эти иконки вам не понадобятся, и их можно просто удалить из ресурсов.

Итак, первое, что многие захотят сделать, это изменить иконку своей программы, т.к. это, можно сказать, яыляется ее лицом, и это правильно. Давайте попробуем изменить иконку с помощью утилиты AutoIt3Wrapper. Команды AutoIt3Wrapper'а представляют собой набор директив компилятора, которые должны располагаться в самом начале вашего скрипта, начиная с первой строки.

Код: AutoIt [Выделить]
#Region
#AutoIt3Wrapper_Icon=MyProg.ico
#EndRegion



MyProg.ico

Директивы #Region и #EndRegion обозначают начало и конец секции, в данном случае для AutoIt3Wrapper, а #AutoIt3Wrapper_Icon задает путь к иконке, которую мы хотим использовать в качестве основной иконки для нашей программы. Во время компиляции, AutoIt3Wrapper заменит умолчальную иконку с логотипом AutoIt на MyProg.ico. Название ресурса при этом не изменится и останется 99. Важно заметить, что скрипт должен быть откомпилирован посредством самой AutoIt3Wrapper. Если вы используете полную версию SciTE, то достаточно просто нажать F7 или "Tools - Build", если вы пишите AutoIt скрипты в другом редакторе, то вам необходимо в ручную запустить AutoIt3Wrapper со следующими параметрами:

Utilities\AutoIt3Wrapper.exe /in MyProg.au3
Здесь и далее предполагается, что AutoIt3Wrapper и другие необходимые утилиты лежат в папке "Utilities", которая находится в той же папке, что и ваш AutoIt скрипт. А все используемые иконки и другие ресурсы в одной папке с .au3 файлом.

Хорошо, иконку программы мы изменили, а как быть, если мы хотим добавить в исполняемый файл еще одну дополнительную иконку? Для этого существует директива #AutoIt3Wrapper_Res_Icon_Add. Формат записи для нее аналогичен #AutoIt3Wrapper_Icon, но в отличии от последней, может быть использована несколько раз, для каждой дополнительной иконки, что мы хотим добавить, отдельная строка (в данном примере я добавляю только одну иконку - CPU.ico, остальные две строчки я закомментировал для наглядности):

Код: AutoIt [Выделить]
#Region
#AutoIt3Wrapper_Icon=MyProg.ico
#AutoIt3Wrapper_Res_Icon_Add=CPU.ico
;#AutoIt3Wrapper_Res_Icon_Add=
;#AutoIt3Wrapper_Res_Icon_Add=
#EndRegion



CPU.ico

После компиляции, ресурсы в файле MyProg.exe будут выглядеть следующим образом:




Как видно на этом скриншоте, у нас появилась еще одна иконка с названием 201, т.е. AutoIt3Wrapper начинает добавлять дополнительные иконки, начиная с номера 201. Следующие иконки будут иметь номера 202, 203 и т.д. Ну, теперь у нас вообще все шоколадно, но остался еще один вопрос - как использовать эту добавленную нами иконку? Нет ничего проще. Все функции в AutoIt, которые используют иконки, работают непосредственно с файлами и позволяют задавать помимо .ico файла еще и исполняемый файл, указав номер иконки или название ресурса в нем. В нашем случае это будет сам скомпилированный скрипт. Следующий код создает окно и помещает в его центр ту самую иконку под номером 201, которая находится в ресурсах MyProg.exe. Размер иконки сделаем 128x128.

Код: AutoIt [Выделить]
#Region
#AutoIt3Wrapper_Icon=MyProg.ico
#AutoIt3Wrapper_Res_Icon_Add=CPU.ico
#EndRegion

#Include <Icons.au3>

GUICreate("MyProg", 256, 256)
$Icon = GUICtrlCreateIcon("", 0, 64, 64, 128, 128)
GUICtrlSetImage($Icon, @ScriptFullPath, 201)
GUISetState()

Do
Until GUIGetMsg() = -3
 



Естественно, что бы иконка отобразилась в GUI, необходимо запустить скомпилированный скрипт, а не .au3 файл. Здесь в функцию GUICtrlSetImage() вторым параметром мы передаем непосредственно название ресурса нашей иконки - 201, но в большинстве случаев, удобнее использовать индексы, а не названия. Первая иконка в ресурсах (99) имеет индекс 1, вторая (162) - 2 и т.д. С учетом этого, наша иконка (201) имеет индекс 5. В отличии от названий ресурсов, индексы должны передаваться в функцию со знаком "-":

Код: AutoIt [Выделить]

ВАЖНО. Не путать индексы в AutoIt с индексами в Windows, которые могут встречаться например в реестре. Во-первых, в Windows нумерация начинается с нуля, а не с 1, а во-вторых, отрицательные значения там имеют совершенно другой смысл. Для совместимости индексов в системе с индексами в AutoIt, я рекомендую использовать следующий формат записи:

Код: AutoIt [Выделить]
GUICtrlSetImage($Icon, -($iIndex + ($iIndex > -1)))


Здесь $iIndex - порядковый номер иконки, начиная с нуля (0 - первая иконка в файле), или отрицательный индекс, который используется в системе.

В этом примере я использовал только AutoIt3Wrapper. Давайте теперь попробуем сделать тоже самое, но только с применением Resource Hacker'а, и заодно удалим неиспользуемые иконки под номерами 162, 164 и 169. Командная строка для добавления иконки в файл имеет следующий вид:

Utilities\ResHacker.exe -add MyProg.exe, MyProg.exe, CPU.ico, Icon, 201,
Для удаления иконок:

Utilities\ResHacker.exe -delete MyProg.exe, MyProg.exe, Icon, 162,
Utilities\ResHacker.exe -delete MyProg.exe, MyProg.exe, Icon, 164,
Utilities\ResHacker.exe -delete MyProg.exe, MyProg.exe, Icon, 169,

Для изменения иконки программы:

Utilities\ResHacker.exe -addoverwrite %out%, %out%, MyProg.ico, Icon, 99,
Можно конечно запустить Resource Hacker из .bat (.cmd) файла после того, как скрипт будет скомпилирован, но лучше будет реализовать все это в одном .au3 файле. Тем более AutoIt3Wrapper позволяет запускать любые программы до или после компиляции скрипта. Для запуска программы после компиляции служит директива #AutoIt3Wrapper_Run_After:

Код: AutoIt [Выделить]
#Region
#AutoIt3Wrapper_UseUpx=n
#AutoIt3Wrapper_Run_After=Utilities\ResHacker.exe -add %out%, %out%, CPU.ico, Icon, 201,
#AutoIt3Wrapper_Run_After=Utilities\ResHacker.exe -delete %out%, %out%, Icon, 162,
#AutoIt3Wrapper_Run_After=Utilities\ResHacker.exe -delete %out%, %out%, Icon, 164,
#AutoIt3Wrapper_Run_After=Utilities\ResHacker.exe -delete %out%, %out%, Icon, 169,
#AutoIt3Wrapper_Run_After=Utilities\ResHacker.exe -addoverwrite %out%, %out%, MyProg.ico, Icon, 99,
#AutoIt3Wrapper_Run_After=Utilities\Upx.exe %out% --best --no-backup --overlay=copy --compress-exports=1 --compress-resources=0 --strip-relocs=1
#EndRegion

GUICreate("MyProg", 256, 256)
$Icon = GUICtrlCreateIcon("", 0, 64, 64, 128, 128)
GUICtrlSetImage($Icon, @ScriptFullPath, 201)
GUISetState()

Do
Until GUIGetMsg() = -3


Здесь переменная %out% - это путь к нашей программе MyProg.exe (если конечно ваш скрипт называется MyProg.au3).

ВАЖНО. Resource Hacker не может работать с файлами, которые имеют компрессию (в данном случае UPX). Поэтому необходимо отключить компрессию при компиляции скрипта. Это делается с помощью директивы #AutoIt3Wrapper_UseUpx с параметром "n". В противном случае, на выходе вы получите испорченный .exe файл. Для того, что бы все таки уменьшить размер нашей программы, я в этом примере запускаю утилиту UPX после того, как все ресурсы в MyProg.exe будут изменены.

Utilities\Upx.exe %out% --best --no-backup --overlay=copy --compress-exports=1 --compress-resources=0 --strip-relocs=1
После компиляции этого скрипта, ресурсы в MyProg.exe будут выглядеть так (собственно, именно этого мы и хотели добиться):




На этом, в общем-то, можно было бы и закончить этот раздел, но есть еще один важный момент. Все родные AutoIt функции для работы с иконками используют в качестве параметра непосредственно путь к файлу, который содержит иконку. Это обеспечивает относительную простоту применения этих функций, но если вы будете использовать в своих программах GUI, то рано или поздно столкнетесь с тем, что вам станет не хватать GUICtrl... функций, и вы вынуждены будете пользоваться разнообразными UDF, для решения поставленных задач, которые, в большинстве своем, работают не с именами файлов, а с хендлами (HANDLE). Более того, дольшинство _GUICtrl*_* функций из дистрибутива AutoIt, также работают с хендлами. Хендл, это просто указатель на какой-либо объект, который находится в памяти. В данном контексте, это будет указатель на объект типа иконка. Откуда же нам взять этот хендл? Существует много разных функций для загрузки иконок в память и получения их хендлов, но я рекомендую использовать функцию _WinAPI_ShellExtractIcon() из библиотеки WinAPIEx.au3. Формат вызова для нее имеет следующий вид:

_WinAPI_ShellExtractIcon ( $sIcon, $iIndex, $iWidth, $iHeight )
Параметры:

$sIconПуть к файлу, в котором находится иконка.
$iIndexИндекс иконки в файле, начиная с нуля или отрицательное (обратное) его значение.
$iWidthТребуемая ширина иконки.
$iHeightТребуемая высота иконки.

При успешном завершении, эта функция загрузит иконку требуемого размера из указанного файла в память и возвратит хендл иконки (HICON). В случае ошибки будет возвращен 0. Если в файле отсутствует иконка с такими размерами, то будет взята ближайщая по размеру иконка и увеличена / уменьшена до необходимых размеров. Что бы не было интерполяции, я рекомендую задавать точные размеры иконки. После того, как иконка станет не нужна, ее необходимо удалить, вызвав функцию _WinAPI_DestroyIcon(), тем самым, освободив занимаемую ей (иконкой) память.

Для иллюстрации работы _WinAPI_ShellExtractIcon(), я немного изменил предыдущий пример для работы с HICON. Наша многострадальная иконка имеет здесь индекс 4, т.к. является четвертой в списке ресурсов (см. скриншот выше). Так же, функция GUICtrlSetImage() нам уже не подходит, поэтому я использовал функцию _SetHIcon() из библиотеки Icons.au3. Она делает тоже самое, что и GUICtrlSetImage(), но только для HICON:

Код: AutoIt [Выделить]
#Region
#AutoIt3Wrapper_Icon=MyProg.ico
#AutoIt3Wrapper_Res_Icon_Add=CPU.ico
#EndRegion

#Include <Icons.au3>
#Include <WinAPIEx.au3>

GUICreate("MyProg", 256, 256)
$Icon = GUICtrlCreateIcon("", 0, 64, 64)
$hIcon = _WinAPI_ShellExtractIcon(@ScriptFullPath, 4, 128, 128)
_SetHIcon($Icon, $hIcon)
_WinAPI_DestroyIcon($hIcon)
GUISetState()

Do
Until GUIGetMsg() = -3


Содержание
« Последнее редактирование: Июнь 02, 2017, 21:28:29 от Garrett »

Оффлайн Yashied [?]

  • AutoIt MVP
  • Глобальный модератор
  • *
  • Сообщений: 5379

  • Автор темы
  • Репутация: 2693
  • Пол: Мужской
    • Награды
  • Версия AutoIt: 3.3.x.x
Re: FAQ по использованию ресурсов в AutoIt
« Ответ #3, Отправлен: Октябрь 14, 2010, 16:15:15 »

Картинки.

Я думаю здесь сразу необходимо дать пару важных замечаний. Не смотря на то, что существует множество разных форматов изображений, так уж исторически сложилось, что Windows работает только с изображениями типа битмап (Bitmap). Все функции в WinAPI, включая функции для загрузки и сохранения изображений, не воспринимают ничего, кроме битмапов, даже иконки представляют собой ни что иное, как битмапы. Для работы с другими форматами, мы вынуждены использовать разнообразные дополнительные библиотеки, начиная с GDI+ и заканчивая, например FreeImage и др. Второе замечание касается непосредственно типов ресурсов. Для каждого стандартного (см. "Общая информация") типа ресурса, Windows предоставляет соответствующие функции для его загрузки, например для CURSOR есть функция _WinAPI_LoadCursor(), для STRING - _WinAPI_LoadString(), для BITMAP - _WinAPI_LoadBitmap() и т.д. Это очень удобно. Нет, нам конечно ничего не мешает, например, взять и загрузить битмап в ресурс типа RCDATA или User-defined, но в этом случае мы не сможем воспользоваться функцией _WinAPI_LoadBitmap(), и вынуждены будем писать свою функцию для его загрузки. А зачем? Поэтому, я очень рекомендую вам загружать ресурсы именно в те разделы, которые специально для них и предназначены. Но увы, в случае с картинками тут особо не разгуляешься, легкую жизнь нам обещают только с битмапами. Многие наверное уже догадались, к чему я здесь все это говорю... Правильно, по возможности, старайтесь использовать именно битмапы (.bmp), и будет вам всегда светить солнце. Но к сожалению, это не всегда возможно, так что читаем дальше.

Ну раз такое дело, давайте начнем с битмапов. Попробуем загрузить .bmp файл в ресурсы нашей программы с помощью AutoIt3Wrapper'а (для порядка оставим название MyProg.au3). Для добавления любых ресурсов, кроме ICON и VERSION, служит директива #AutoIt3Wrapper_Res_File_Add. В случае добавления битмапа, строка будет выглядеть следующим образом:

Код: AutoIt [Выделить]
#Region
#AutoIt3Wrapper_Res_File_Add=CrashXP.bmp, 2, 200
#EndRegion


Если вы решили использовать Resource Hacker, то эта секция будет иметь следующий вид:

Код: AutoIt [Выделить]
#Region
#AutoIt3Wrapper_UseUpx=n
#AutoIt3Wrapper_Run_After=Utilities\ResHacker.exe -add %out%, %out%, CrashXP.bmp, 2, 200,
#AutoIt3Wrapper_Run_After=Utilities\Upx.exe %out% --best --no-backup --overlay=copy --compress-exports=1 --compress-resources=0 --strip-relocs=1
#EndRegion



CrashXP.bmp

Здесь число 200, это просто произвольное название ресурса. Вы можете его изменить на любое другое, но оно не должно повторяться в пределах данного ресурса. В противном случае, вы удалите предыдущий ресурс с таким же именем. 2 - значение, определяющее стандартный тип ресурса BITMAP. Если бы мы написали слово "BITMAP" вместо числа 2, то был бы создан User-defined ресурс c названием "BITMAP", вместо стандартного BITMAP типа. Почему об этом нет ни слова в документации по AutoIt3Wrapper'у, я не знаю. Но в любом случае, аналогичным образом задаются типы и в WinAPI. Это справедливо и для других стандартных ресурсов, т.е., если мы создаем ресурс одного из предопределенных типов, то пишем соответствующее ему (типу) числовое значение, иначе будет создан User-defined ресурс с этим названием. После компиляции этого скрипта, ресурсы в MyProg.exe будут выглядеть примерно так:


Теперь давайте разберемся, как собственно этот битмап можно использовать в нашей программе. Как я уже сказал выше, для загрузки стандартных типов ресурсов, в Windows есть готовые функции. Для битмапов существует две основные функции: _WinAPI_LoadBitmap() и _WinAPI_LoadImage(). Последняя является более универсальной и предназначена для загрузки не только битмапов, но и курсоров и иконок, и не только из ресурсов. Мы ее сейчас рассматривать не будем, а обойдемся более простой - _WinAPI_LoadBitmap(), которая может загружать только битмапы. Формат вызова для этой функции имеет следующий вид:

_WinAPI_LoadBitmap ( $hInstance, $sBitmap )
Параметры:

$hInstanceУказатель (HANDLE) на модуль, из которого необходимо загрузить битмап.
$sBitmapНазвание ресурса.

При успешном завершении, функция загрузит битмап из ресурсов указанного модуля в память и возвратит хендл битмапа (HBITMAP). В случае ошибки будет возвращен 0. После того, как битмап станет не нужен, его необходимо удалить и освободить занимаемую им память с помощью функции _WinAPI_DeleteObject().

В нашем случае, параметр $hInstance есть не что иное, как указатель на нашу программу, т.к. ресурсы находятся в ней самой. Получить его можно с помощью функции _WinAPI_GetModuleHandle() с параметром 0. А параметр $sBitmap должен содержать значение 200, т.к. мы добавили битмап в ресурс с именем 200. Итак, резюмируя все вышесказанное, пишем окончательный скрипт, запускаем и наслаждаемся результатом:

Код: AutoIt [Выделить]
#Region
#AutoIt3Wrapper_Res_File_Add=CrashXP.bmp, 2, 200
#EndRegion

#Include <Icons.au3>
#Include <WinAPIEx.au3>

GUICreate("MyProg", 640, 480)
$Pic = GUICtrlCreatePic("", 0, 0, 640, 480)
$hInstance = _WinAPI_GetModuleHandle(0)
$hBitmap = _WinAPI_LoadBitmap($hInstance, 200)
_SetHImage($Pic, $hBitmap)
_WinAPI_DeleteObject($hBitmap)
GUISetState()

Do
Until GUIGetMsg() = -3



К сожалению, функция GUICtrlSetImage(), как вообщем-то и любая другая функция в AutoIt, работает непосредственно с файлами, и не предназначена для работы с хендлами. Поэтому, я здесь использовал функцию _SetHImage() из библиотеки Icons.au3. Эта функция, как и функция _SetHIcon() для иконок, перед тем, как поместить битмап в элемент Pic, делает его (битмапа) копию. Поэтому, после вызова _SetHImage() можно смело удалять битмап, если, конечно, он вам больше не понадобится.

Ну, с битмапами, я думаю, более менее разобрались. А что делать, если нам нужа картинка, содержащая прозрачность - PNG, или для уменьшения размера исполняемого файла, мы хотим использовать JPEG, т.к. в бльшинстве своем .bmp файлы занимают достаточно много места. Напрмер CrashXP.bmp, который я здесь использовал, весит ~900 KB! А если таких файлов будет штук 10 или больше? Поэтому, в некоторых случаях целесообразнее использовать не битмапы, а изображения, записанные в каких-нибудь других, более подходящих для наших целей, форматах. К сожалению, как я уже сказал выше, в Windows нет стандартных типов ресурсов для каких-либо изображений, кроме битмапов. И, как следствие, нет никаких готовых функций для загрузки таких изображений. Обидно. Но расстраиваться еще рано.

Для данных произвольного формата существует ресурс типа RCDATA или User-defined. Разница в них заключается лишь в том, что первый является стандартным типом ресурса со значением 10, а второй может иметь любое название. Например, если вы решили добавить в ресурсы несколько .png файлов, то логично было бы их все разместить в ресурсе типа User-defined с именем "PNG" (для наглядности). Хотя с другой стороны, можно и в RCDATA. Для нас, в данном случае, принципиальной разницы между типами RCDATA и User-defined нет. Я предпочитаю не сваливать все в RCDATA, а аккуратно распределить, так сказать по полочкам, все необходимые данные в User-defined ресурсах: .png файлы в "PNG", .jpg в "JPEG", звуки в "WAV" или "SOUND" и т.д. Данные в эти ресурсы помещаются "как есть", т.е., в отличии, например, от иконок (ICON), файл в RCDATA или User-defined ресурс будет загружен в том виде, в котором он существует на диске. Это очень удобно, поскольку позволяет добавлять в ресурсы практически любые данные, вплоть до .exe файла. В дальнейшем, я еще неоднократно буду касаться этих типов ресурсов, а сейчас давайте попробуем добавить в нашу программу какое-нибудь изображение. Дабы не разделять тему, предлагаю продолжить написание предыдущего скрипта, который мы создали для битмапа. В AutoIt3Wrapper'е, файлы в ресурсы типа RCDATA или User-defined добавляются с помощью все той же директивы #AutoIt3Wrapper_Res_File_Add:

Код: AutoIt [Выделить]
#Region
#AutoIt3Wrapper_Res_File_Add=CrashXP.bmp, 2, 200
#AutoIt3Wrapper_Res_File_Add=NVIDIA.png, PNG, NVIDIA
#EndRegion


Для Resource Hacker'а, это будет выглядеть следующим образом:

Код: AutoIt [Выделить]
#Region
#AutoIt3Wrapper_UseUpx=n
#AutoIt3Wrapper_Run_After=Utilities\ResHacker.exe -add %out%, %out%, CrashXP.bmp, 2, 200,
#AutoIt3Wrapper_Run_After=Utilities\ResHacker.exe -add %out%, %out%, NVIDIA.png, PNG, NVIDIA,
#AutoIt3Wrapper_Run_After=Utilities\Upx.exe %out% --best --no-backup --overlay=copy --compress-exports=1 --compress-resources=0 --strip-relocs=1
#EndRegion



NVIDIA.png

Здесь я добавил файл NVIDIA.png в ресурс типа User-defined и назвал его "PNG". Непосредственно сам ресурс, я решил назвать по содержанию картинки - "NVIDIA". При желании, вы можете эти названия изменить по своему вкусу. Если вы захотите добавить картинку в ресурс типа RCDATA, то вместо "PNG", нужно написать число 10 - идентификатор стандартного типа RCDATA. Компилируем полученный скрипт в исполняемый файл и проверяем с помощью Resource Hacker'а его ресурсы:


Так как данные в RCDATA или User-defined записываются в двоичном формате, Resource Hacker их и отображает соответственно. Хотя, во многих других редакторах ресурсов может отображаться непосредственно и само изображение. Но для нас это не важно, главное, что файл NVIDIA.png был успешно добавлен в нашу программу и именно туда, куда мы и хотели ("PNG" - "NVIDIA"). Все вышесказанное справедливо не только для изображений в формате PNG, но и любых других (JPEG, TIFF и т.д.), поддерживаемых в системе. При желании, можно даже загрузить тот же битмап, но это будет выглядеть как-то бессмысленно. Теперь встает главный вопрос: как его теперь оттуда выцепить? Хороший вопрос! Еще раз напомню, что функция _WinAPI_LoadBitmap() здесь бесполезна, даже, если это изображение будет являться .bmp файлом (тип ресурса не тот). Как же тогда быть? Ничего не поделаешь, придется писать свою функцию для загрузки NVIDIA.png из ресурсов. Скажу честно, я уже написал такую функцию, называется она _LoadResourceImage() и позволяет загружать из RCDATA или User-defined ресурсов любые изображения, с которыми в состоянии работать сам Windows. Так как функция использует GDI+, вам необходимо будет включить в скрипт соответствующую библиотеку. С вашего позваления, я не буду подробно описывать как работает _LoadResourceImage(), далее, я еще расскажу об общих принципах работы с ресурсами, а пока просто предоставлю готовый код, чтобы вы смогли им воспользовться. Формат вызова для _LoadResourceImage() имеет следующий вид:

_LoadResourceImage ( $hInstance, $sResType, $sResName [, $iResLanguage] )
Параметры:

$hInstanceУказатель (HANDLE) на модуль, из которого необходимо загрузить изображение.
$sResTypeТип ресурса.
$sResNameНазвание ресурса.
$iResLanguageИдентификатор языка ресурса. (Опционально)

При успешном завершении, функция загрузит изображение, соответствующее указанному типу, названию и языку ресурса, в память и возвратит его хендл (HIMAGE). В случае ошибки будет возвращен 0.

ВАЖНО. Не путать хендл, возвращаемый этой функцией (HIMAGE), который используется в рамках GDI+, с хендлом (HBITMAP), который возвращает функция _WinAPI_LoadBitmap(). Последний используется только для работы с функциями из библиотеки GDI (без "+"). Если вы попытаетесь использовать HBITMAP с функциями из GDI+, или наоборот, HIMAGE с GDI, то, скорее всего, ваш скрипт "вылетит" с ошибкой. Для "конвертации" HIMAGE в HBITMAP воспользуйтесь функцией _GDIPlus_BitmapCreateHBITMAPFromBitmap() из AutoIt библиотеки GDIPlus.au3. Для обратного преобразования служит функция _GDIPlus_BitmapCreateFromHBITMAP(). За более подробной информацией загляните в справку по AutoIt.

Как и в случае с _WinAPI_LoadBitmap(), $hInstance представляет собой указатель на нашу программу, конечно же MyProg.exe. Параметры $sResType и $sResName принимают значения "PNG" и "NVIDIA" соответственно. Идентификатор языка ресурса - $iResLanguage, мы здесь не используем, о нем я еще расскажу. Осталось выяснить последний момент, а именно, как теперь отобразить загруженное изображение? Ведь оно являлось .png файлом, содержащим прозрачность. GUICtrlSetImage() не поддерживает изображения с прозрачностью, да и к тому же не работает с хендлами. Ну так давайте опять воспользуемся функцией _SetHImage(). Помимо всех прочих достоинств, эта функция может помещать изображения в элементы Pic, сохраняя при этом их (изображений) прозрачность, правда с небольшими оговорками. Единственная заковырка здесь, это то, что _SetHImage() работает только с HBITMAP, а функция _LoadResourceImage() возвращает HIMAGE. Многие возможно скажут, а почему бы не сделать так, чтобы _LoadResourceImage() возвращала бы сразу HBITMAP? А все потому, что преобразование из HIMAGE в HBITMAP в большинстве случаев будет необратимым, а HIMAGE может понадобиться, например для объединения его с другими такими же изображениями, что содержат прозрачность, или для отрисовки его непосредственно средствами GDI+ в каком-нибудь окне. Не беда, воспользуемся _GDIPlus_BitmapCreateHBITMAPFromBitmap(). Вот теперь у нас вроде все сходится. Итак, берем предыдущий скрипт с битмапом и добавляем в него изображение из файла NVIDIA.png:

Код: AutoIt [Выделить]
#Region
#AutoIt3Wrapper_Res_File_Add=CrashXP.bmp, 2, 200
#AutoIt3Wrapper_Res_File_Add=NVIDIA.png, PNG, NVIDIA
#EndRegion

#Include <GDIPlus.au3>
#Include <Icons.au3>
#Include <WinAPIEx.au3>

GUICreate("MyProg", 640, 480)
$Pic1 = GUICtrlCreatePic("", 0, 0, 640, 480)
$Pic2 = GUICtrlCreatePic("", 237, 157, 166, 166)
$hInstance = _WinAPI_GetModuleHandle(0)
$hBitmap = _WinAPI_LoadBitmap($hInstance, 200)
_SetHImage($Pic1, $hBitmap)
_WinAPI_DeleteObject($hBitmap)
_GDIPlus_Startup()
$hImage = _LoadResourceImage($hInstance, "PNG", "NVIDIA")
$hBitmap = _GDIPlus_BitmapCreateHBITMAPFromBitmap($hImage)
_GDIPlus_ImageDispose($hImage)
_GDIPlus_Shutdown()
_SetHImage($Pic2, $hBitmap)
_WinAPI_DeleteObject($hBitmap)
GUISetState()

Do
Until GUIGetMsg() = -3

Func _LoadResourceImage($hInstance, $sResType, $sResName, $iResLanguage = 0)

    Local $hInfo, $hData, $pData, $iSize, $hMem, $pMem, $hStream, $hImage

    If $iResLanguage Then
        $hInfo = _WinAPI_FindResourceEx($hInstance, $sResType, $sResName, $iResLanguage)
    Else
        $hInfo = _WinAPI_FindResource($hInstance, $sResType, $sResName)
    EndIf
    $hData = _WinAPI_LoadResource($hInstance, $hInfo)
    $iSize = _WinAPI_SizeOfResource($hInstance, $hInfo)
    $pData = _WinAPI_LockResource($hData)
    If @error Then
        Return SetError(1, 0, 0)
    EndIf
    $hMem = DllCall("kernel32.dll", "ptr", "GlobalAlloc", "uint", 2, "ulong_ptr", $iSize)
    If @error Then
        Return SetError(1, 0, 0)
    EndIf
    $pMem = DllCall("kernel32.dll", "ptr", "GlobalLock", "ptr", $hMem[0])
    If @error Then
        Return SetError(1, 0, 0)
    EndIf
    DllCall("kernel32.dll", "none", "RtlMoveMemory", "ptr", $pMem[0], "ptr", $pData, "ulong_ptr", $iSize)
    DllCall("kernel32.dll", "int", "GlobalUnlock", "ptr", $hMem[0])
    $hStream = _WinAPI_CreateStreamOnHGlobal($hMem[0])
    If @error Then
        Return SetError(1, 0, 0)
    EndIf
    _GDIPlus_Startup()
    $hImage = DllCall("gdiplus.dll", "uint", "GdipCreateBitmapFromStream", "ptr", $hStream, "ptr*", 0)
    If (@error) Or ($hImage[0]) Or (Not $hImage[2]) Then
        $hImage = 0
    EndIf
    _GDIPlus_Shutdown()
    DllCall("kernel32.dll", "ptr", "GlobalFree", "ptr", $hMem[0])
    If Not IsArray($hImage) Then
        Return SetError(1, 0, 0)
    EndIf
    Return $hImage[2]
EndFunc   ;==>_LoadResourceImage



Не правда ли красиво?

Содержание
« Последнее редактирование: Июнь 02, 2017, 22:11:38 от Garrett »

Русское сообщество AutoIt

Re: FAQ по использованию ресурсов в AutoIt
« Ответ #3 Отправлен: Октябрь 14, 2010, 16:15:15 »

Оффлайн Yashied [?]

  • AutoIt MVP
  • Глобальный модератор
  • *
  • Сообщений: 5379

  • Автор темы
  • Репутация: 2693
  • Пол: Мужской
    • Награды
  • Версия AutoIt: 3.3.x.x
Re: FAQ по использованию ресурсов в AutoIt
« Ответ #4, Отправлен: Октябрь 14, 2010, 16:15:41 »

Строки.

Все текстовые данные обычно располагаются в стандартном ресурсе типа STRING. Но в AutoIt, этот ресурс используется редко, т.к. существует целый вагон других способов хранения строк (текста). Начиная от внедрения текста в код скрипта и заканчивая созданием отдельного текстового файла. В любом случае, этот тип ресурса заслуживает того, чтобы о нем написать пару слов.

К сожалению, у меня так и не получилось добавить что-нибудь в ресурс STRING с помощью AutoIt3Wrapper'а. Либо данные вообще не добавлялись, либо получался битый ресурс. Поэтому, я обычно поступаю следующим образом. В том же Resource Hacker'е создаю отдельный ресурс STRING, помещаю в него необходимые текстовые данные, а затем сохраняю все это в файл ресурсов (.res). После этого, можно легко добавить полученный файл в скомпилированный скрипт с помощью командной строки Resource Hacker'а. Давайте поступим аналогичным образом и создадим .res файл, содержащий один единственный ресурс STRING. Дабы не описывать лишний раз, как это делается непосредственно в Resource Hacker'е, я уже подготовил необходимый файл для ресурса STRING, содержащего всего две строки. Просто откройте его в программе и вы увидите примерно следующую картину:



String.res

Как видно на скриншоте, каждая строка здесь начинается с некоего номера - идентификатора строки. Именно по этим идентификаторам мы в дальнейшем и будем обращаться к необходимым нам строкам. Теоретически, значение идентификатора может быть от 0 до 65535, но поскольку скомпилированный AutoIt скрипт уже содержит в себе кучу строк с разными номерами, необходимых ему для нормальной работы, и дабы не затереть уже существующие строки или того хуже, сделать файл полностью нерабочим, я рекомендую начать нумерацию с 6000. В данном случае мы имеем две строки с номерами 6000 и 6001. Первую мы будем использовать в названии окна, вторую просто отобразим в GUI. Как я уже сказал выше, AutoIt3Wrapper здесь нам не помощник, поэтому прдется использовать Resource Hacker:

Код: AutoIt [Выделить]
#Region
#AutoIt3Wrapper_UseUpx=n
#AutoIt3Wrapper_Run_After=Utilities\ResHacker.exe -add %out%, %out%, String.res,,,
#AutoIt3Wrapper_Run_After=Utilities\Upx.exe %out% --best --no-backup --overlay=copy --compress-exports=1 --compress-resources=0 --strip-relocs=1
#EndRegion


Заметьте, тип и название ресурса здесь не указаны, т.к. эта информация уже присутствует в файле String.res. Теперь, если скомпилировать этот скрипт и открыть исполняемый файл в Resource Hacker'е, ресурсы в нем будут выглядеть следующим образом:


Как и ожидалось, все ресурсы из файла String.res были успешно добавлены в нашу программу в том виде, в котором они и находились в .res файле. Ну, полдела мы уже сделали. Осталось научиться использовать добавленные строки. Здесь, как и в случае с битмапами нет ничего сложного. Т.к. ресурсы типа STRING являются стандартными, то для них есть готовая функция. Называется она незамысловато - _WinAPI_LoadString(), и имеет следующий формат вызова:

_WinAPI_LoadString ( $hInstance, $iStringId )
Параметры:

$hInstanceУказатель (HANDLE) на модуль, из которого необходимо загрузить строку.
$iStringIdИдентификатор строки.

При успешном выполнении, функция возвратит строку, соответствующую указанному идентификатору. В случае ошибки будет возвращена пустая строка (""), также флаг @error будет содержать ненулевое значение.

Ну а теперь, наконец, давайте напишим окончательный скрипт. Для того, чтобы загрузить из ресурса типа STRING определенную строку, достаточно просто вызвать функцию _WinAPI_LoadString() с параметром $iStringId, равным значению идентификатора требуемой строки, параметр $hInstance здесь представляет собой хендл нашей программы (см. "Картинки"). Как мы договорились ранее, строка с номером 6000 будет содержать название окна, а строка с номером 6001 - просто текст, который мы отобразим в этом окне.

Код: AutoIt [Выделить]
#Region
#AutoIt3Wrapper_UseUpx=n
#AutoIt3Wrapper_Run_After=Utilities\ResHacker.exe -add %out%, %out%, String.res,,,
#AutoIt3Wrapper_Run_After=Utilities\Upx.exe %out% --best --no-backup --overlay=copy --compress-exports=1 --compress-resources=0 --strip-relocs=1
#EndRegion

#Include <WinAPIEx.au3>

$hInstance = _WinAPI_GetModuleHandle(0)
GUICreate(_WinAPI_LoadString($hInstance, 6000), 244, 100)
GUICtrlCreateLabel(_WinAPI_LoadString($hInstance, 6001), 60, 28, 124, 46)
GUICtrlSetFont(-1, 26, 700, 0, "Arial")
GUISetState()

Do
Until GUIGetMsg() = -3



Да, в использовании ресурсов типа STRING нет ничего особо сложного, но многие наверное найдут для себя не очень удобным вызов функции _WinAPI_LoadString() для загрузки каждой строки в отдельности. Конечно можно загрузить все необходимые строки в массив в самом начале программы и далее уже ссылаться на этот массив, но все же... Есть и другой способ работы с текстом. А что нам собственно мешает записать все строки в один текстовый файл и поместить его в ресурс типа RCDATA или User-defined? Да ничего. Плюс такого подхода заключается в том, что во-первых, мы в этом случае можем обойтись одним AutoIt3Wrapper'ом, а во-вторых, можем загрузить все текстовые данные за один раз. Ну так давайте попробуем это реализовать. Как уже говорилось в предыдущем разделе, существенной разницы между RCDATA и User-defined для нас нет, поэтому я предлагаю использовать User-defined ресурс для хранения наших строк. Для начала создайте в Notepad'е текстовый файл String.txt с таки содержанием (возмем за основу предыдущий пример):

Моя программа
Привет

ВАЖНО. Файл не должен быть сохранен в кодировке Unicode, в противном случае, в ресурсах вы получите абру-кадабру. Так же желательно, чтобы в конце файла не было пустых строк.

Готово? Теперь, уже по накатанной схеме, добавляем с помощью AutoIt3Wrapper'а и уже хорошо знакомой нам директивы #AutoIt3Wrapper_Res_File_Add созданный нами текстовый файл в ресурсы программы (по традиции - MyProg.exe):

Код: AutoIt [Выделить]
#Region
#AutoIt3Wrapper_Res_File_Add=String.txt, TEXTDATA, RUS, 1049
#EndRegion


Тоже самое, но только для Resource Hacker'а:

Код: AutoIt [Выделить]
#Region
#AutoIt3Wrapper_UseUpx=n
#AutoIt3Wrapper_Run_After=Utilities\ResHacker.exe -add %out%, %out%, String.txt, TEXTDATA, RUS, 1049
#AutoIt3Wrapper_Run_After=Utilities\Upx.exe %out% --best --no-backup --overlay=copy --compress-exports=1 --compress-resources=0 --strip-relocs=1
#EndRegion


Здесь создается User-defined ресурс с названием типа "TEXTDATA", сам ресурс я решил назвать "RUS", просто потому что строки на русском языке. Естественно, названия "TEXTDATA" и "RUS" вы можете изменить на любые другие. Еще здесь появился новый параметр - 1049 - это идентификатор языка ресурса, соответствующий русскому языку. Теоретически, можно обойтись и без него, тем более в нашем случае, он все равно не будет задействован, но порядка ради... Если кому интересно, значения идентификаторов для большинства используемых в Windows языков, вы можете посмотреть в документации к библиотеке WinAPIEx.au3 или в MSDN. Теперь компилируем скрипт в исполняемый файл и смотрим на его ресурсы:


Как видно, пока все идет гладко, ресурс был успешно добавлен в файл. Но встает вопрос, как теперь достучаться до него? _WinAPI_LoadString() здесь конечно же не будет работать, т.к. предназначена только для ресурсов типа STRING, а у нас здесь какой-то "TEXTDATA". Как и в предыдущем разделе для изображений, что не являются битмапами, я предоставлю готовую функцию _LoadResourceText() для загрузки текстовых данных из ресурсов, особо не вдаваясь в подробности ее работы. Формат вызова этой функции аналогичен _LoadResourceImage():

_LoadResourceText ( $hInstance, $sResType, $sResName [, $iResLanguage] )
Параметры:

$hInstanceУказатель (HANDLE) на модуль, из которого необходимо загрузить текстовые данные.
$sResTypeТип ресурса.
$sResNameНазвание ресурса.
$iResLanguageИдентификатор языка ресурса. (Опционально)

При успешном завершении, функция возвратит строку (текст), соответствующую указанному типу, названию и языку ресурса. В случае ошибки будет возвращена пустая строка (""), также флаг @error будет содержать ненулевое значение.

Как нетрудно догадаться, для того, чтобы загрузить добавленный нами ранее ресурс из файла String.txt, необходимо вызвать _LoadResourceText() со следующими параметрами (идентификатор языка ресурса мы здесь не используем, поэтому оставим его в покое):

Код: AutoIt [Выделить]
_LoadResourceText($hInstance, "TEXTDATA", "RUS")


Ну и окончательный вариант скрипта:

Код: AutoIt [Выделить]
#Region
#AutoIt3Wrapper_Res_File_Add=String.txt, TEXTDATA, RUS, 1049
#EndRegion

#Include <WinAPIEx.au3>

$hInstance = _WinAPI_GetModuleHandle(0)
$sText = _LoadResourceText($hInstance, "TEXTDATA", "RUS")
$aText = StringSplit(StringStripCR($sText), @LF, 2)
GUICreate($aText[0], 244, 100)
GUICtrlCreateLabel($aText[1], 60, 28, 124, 46)
GUICtrlSetFont(-1, 26, 700, 0, "Arial")
GUISetState()

Do
Until GUIGetMsg() = -3

Func _LoadResourceText($hInstance, $sResType, $sResName, $iResLanguage = 0)

    Local $hInfo, $hData, $pData, $iSize

    If $iResLanguage Then
        $hInfo = _WinAPI_FindResourceEx($hInstance, $sResType, $sResName, $iResLanguage)
    Else
        $hInfo = _WinAPI_FindResource($hInstance, $sResType, $sResName)
    EndIf
    $hData = _WinAPI_LoadResource($hInstance, $hInfo)
    $iSize = _WinAPI_SizeOfResource($hInstance, $hInfo)
    $pData = _WinAPI_LockResource($hData)
    If @error Then
        Return SetError(1, 0, "")
    EndIf
    Return DllStructGetData(DllStructCreate("char[" & $iSize & "]", $pData), 1)
EndFunc   ;==>_LoadResourceText


Каким из двух (или может быть у вас есть свой способ), описанных выше способов работы с текстовыми данными, вы будете пользоваться, решать конечно же вам, но я лично предпочитаю второй способ - _LoadResourceText().

Содержание
« Последнее редактирование: Июнь 02, 2017, 23:01:27 от Garrett »

Оффлайн Yashied [?]

  • AutoIt MVP
  • Глобальный модератор
  • *
  • Сообщений: 5379

  • Автор темы
  • Репутация: 2693
  • Пол: Мужской
    • Награды
  • Версия AutoIt: 3.3.x.x
Re: FAQ по использованию ресурсов в AutoIt
« Ответ #5, Отправлен: Октябрь 15, 2010, 02:31:59 »

Шрифты.

Как не трудно догадаться, шрифты должны находиться в стандартном ресурсе FONT, но мне не встречалась ни одна функция связанная непосредственно с этим ресурсом. Кроме того, мои попытки загрузить шрифт с помощью Resource Hacker'а в FONT, закончились полной неудачей. Толи Resource Hacker не поддерживает этот ресурс, толи я что-то не так делал, в любом случае, я решил не заморачиваться с этим и загрузить шрифты в User-Defined ресурс с названием TTF, т.к. я использовал TrueType шрифты. Именно об этом способе я и расскажу дальше.

С помощью AutoIt3Wrapper'а и директивы #AutoIt3Wrapper_Res_File_Add создаем User-defined ресурс с именем типа "TTF". Название ресурса давайте сделаем по имени самого шрифта - "STILL_TIME_CYR", чтобы в последствии не путаться:

Код: AutoIt [Выделить]
#Region
#AutoIt3Wrapper_Res_File_Add=Still Time Cyr.ttf, TTF, STILL_TIME_CYR
#EndRegion


Аналогично пишем для Resource Hacker'а:

Код: AutoIt [Выделить]
#Region
#AutoIt3Wrapper_UseUpx=n
#AutoIt3Wrapper_Run_After=Utilities\ResHacker.exe -add %out%, %out%, Still Time Cyr.ttf, TTF, STILL_TIME_CYR,
#AutoIt3Wrapper_Run_After=Utilities\Upx.exe %out% --best --no-backup --overlay=copy --compress-exports=1 --compress-resources=0 --strip-relocs=1
#EndRegion



Still Time Cyr.ttf

Если мы сейчас скомпилируем этот скрипт в исполняемый файл и откроем его в Resource Hacker'е, то увидим примерно следующую картину, как говорится, что хотели, то и получили:


А сейчас начинается самое интересное. Напомню, что на данный момент у нас в ресурсе "STILL_TIME_CYR" сидит целиком файл Still Time Cyr.ttf, т.к. данные в ресурсы типа User-defined помещаются как "row binary data", т.е. "как есть". Так вот, для работы с хендлами шрифтов (HFONT) в Windows существует целый вагон разнообразных функций, а непосредственно для работы с файлами шрифтов - кот наплакал, некоторые даже недокументированы. К счастью, среди этого минимума есть одна функция, которая нам подходит - _WinAPI_AddFontMemResourceEx(). Но вот только так просто, как в случае с _WinAPI_LoadBitmap() или _WinAPI_LoadString(), воспользоваться ей не получится. Я не знаю, почему Windows так не любит работать с файлами шрифтов (может именно поэтому шрифты в ресурсах встречаются крайне редко), но, в любом случае, нам придется что-нибудь придумывать. И это "что-нибудь", я уже конечно придумал. Как всегда, готовая функция - _LoadResourceFont(). Формат вызова для нее имеет следующий вид:

_LoadResourceFont ( $hInstance, $sResType, $sResName [, $iResLanguage] )
Параметры:

$hInstanceУказатель (HANDLE) на модуль, из которого необходимо загрузить шрифт.
$sResTypeТип ресурса.
$sResNameНазвание ресурса.
$iResLanguageИдентификатор языка ресурса. (Опционально)

При успешном завершении, функция установит соответствующий указанному типу, названию и языку ресурса шрифт в систему и возвратит его уникальный идентификатор. В случае ошибки будет возвращен 0. Для удаления установленного шрифта, необходимо вызвать функцию _WinAPI_RemoveFontMemResourceEx() с параметром, соответствующим идентификатору шрифта, который возвращает эта функция. Кроме того, при завершении программы, установленный с помощью _LoadResourceFont() шрифт будет автоматически удален из системы.

ВАЖНО. Установленные с помощью функции _LoadResourceFont() шрифты являются приватными, т.е. использовать их будет возможно только в той программе, в которой была вызвана _LoadResourceFont(). Более того, установленные таким образом шрифты располагаются в памяти, и никакой информации о них не сохраняется ни в реестре, ни на диске.

Теперь осталось только написать рабочий скрипт. Подставляем в функцию _LoadResourceFont() необходимые параметры ($hInstance здесь, как и во всех предыдущих примерах, является хендлом нашей программы):

Код: AutoIt [Выделить]
_LoadResourceFont($hInstance, "TTF", "STILL_TIME_CYR")


И пишем окончательный код:

Код: AutoIt [Выделить]
#Region
#AutoIt3Wrapper_Res_File_Add=Still Time Cyr.ttf, TTF, STILL_TIME_CYR
#EndRegion

#Include <WinAPIEx.au3>

GUICreate("MyProg", 280, 100)
GUICtrlCreateLabel("Still Time Cyr", 38, 23, 204, 54)
$hInstance = _WinAPI_GetModuleHandle(0)
_LoadResourceFont($hInstance, "TTF", "STILL_TIME_CYR")
GUICtrlSetFont(-1, 34, 700, 0, "Still Time Cyr")
GUICtrlSetColor(-1, 0xE00000)
GUISetState()

Do
Until GUIGetMsg() = -3

Func _LoadResourceFont($hInstance, $sResType, $sResName, $iResLanguage = 0)

    Local $hInfo, $hData, $pData, $iSize, $hFont

    If $iResLanguage Then
        $hInfo = _WinAPI_FindResourceEx($hInstance, $sResType, $sResName, $iResLanguage)
    Else
        $hInfo = _WinAPI_FindResource($hInstance, $sResType, $sResName)
    EndIf
    $hData = _WinAPI_LoadResource($hInstance, $hInfo)
    $iSize = _WinAPI_SizeOfResource($hInstance, $hInfo)
    $pData = _WinAPI_LockResource($hData)
    If @error Then
        Return SetError(1, 0, 0)
    EndIf
    $hFont = _WinAPI_AddFontMemResourceEx($pData, $iSize)
    If @error Then
        Return SetError(1, 0, 0)
    EndIf
    Return $hFont
EndFunc   ;==>_LoadResourceFont



И еще одно замечание. В функции GUICtrlSetFont() нужно указывать последним параметром не название ресурса или .ttf файла, а именно название самого шрифта, в данном случае "Still Time Cyr". Узнать его можно с помощью стандартной утилиты в Windows - Font Preview или любым другим способом, например с помощью утилиты Font Viewer. Также в этом примере я не стал вызывать функцию _WinAPI_RemoveFontMemResourceEx(). В этом нет никакой необходимости, т.к. при завершении программы, установленный шрифт будет автоматически удален из системы.

Содержание
« Последнее редактирование: Июнь 02, 2017, 23:08:15 от Garrett »

Оффлайн Yashied [?]

  • AutoIt MVP
  • Глобальный модератор
  • *
  • Сообщений: 5379

  • Автор темы
  • Репутация: 2693
  • Пол: Мужской
    • Награды
  • Версия AutoIt: 3.3.x.x
Re: FAQ по использованию ресурсов в AutoIt
« Ответ #6, Отправлен: Октябрь 16, 2010, 04:29:04 »

Звуки.

Первым делом следует сказать, что для звуков не предусмотрено каких-либо стандартных типов ресурсов. Поэтому придется использовать ресурс типа RCDATA или User-defined. К счастью, в Windows есть одна очень полезная функция - _WinAPI_PlaySound(), которая позволяет проигрывать звуки непосредственно из ресурсов файла, но здесь есть два важных момента. Во-первых, эта функция может проигрывать звуки только в формате WAVE (.wav файлы), а во-вторых, все звуки должны быть расположены в User-defined ресурсах и иметь название типа "SOUND" или "WAVE". Если привязку к названию мы еще переживем, то ограничение типом WAVE, в некоторых случаях может быть очень существенным недостатком. Поскольку WAVE формат представляет звуковые данные без сжатия, то соответственно и весить такие файлы будут намного больше, чем, например те же .mp3 файлы. В случае, если в программе предполагается использовать большое количество звуковых файлов с большой длительностью, то возможно целесообразнее будет не размещать их все в ресурсах в WAVE формате, а приложить как отдельные звуковые файлы в MP3 или другом подходящем формате. Как вариант, можно разместить, например, .mp3 файлы в ресурсах, но только как двоичные данные, а после запуска программы распаковать их на диск (об этом я расскажу в разделе "RCDATA и User-defined") и дальше проигрывать уже эти файлы. Правда в этом случае, наверное проще будет использовать стандартную AutoIt функцию FileInstall(). Но речь сейчас не об этом. Раз уж мы говорим здесь о ресурсах, то и использовать их предполагается не прибегая к созданию каких-либо временных файлов.

Давайте, учитывая все вышесказанное, добавим в нашу программу с помощью AutoIt3Wrapper'а какой-нибудь звуковой файл в WAVE формате. Вспоминаем про директиву #AutoIt3Wrapper_Res_File_Add и пишем следующий код:

Код: AutoIt [Выделить]
#Region
#AutoIt3Wrapper_Res_File_Add=Airplane.wav, WAVE, AIRPLANE
#EndRegion


В случае использования Resource Hacker'а, это будет выглядеть следующим образом:

Код: AutoIt [Выделить]
#Region
#AutoIt3Wrapper_UseUpx=n
#AutoIt3Wrapper_Run_After=Utilities\ResHacker.exe -add %out%, %out%, Airplane.wav, WAVE, AIRPLANE
#AutoIt3Wrapper_Run_After=Utilities\Upx.exe %out% --best --no-backup --overlay=copy --compress-exports=1 --compress-resources=0 --strip-relocs=1
#EndRegion



Airplane.wav

Здесь "WAVE" представляет название типа ресурса. При желании, вы можете заменить его на "SOUND"). Напомню, если это название будет отличаться от "WAVE" или "SOUND", то функция _WinAPI_PlaySound() не будет работать с такими ресурсами. "AIRPLANE" - это произвольное название ресурса, и может быть любой строкои или числом на ваше усмотрение. По традиции, давайте проверим ресурсы и убедимся, что файл Airplane.wav был успешно добавлен в соответствующую секцию ("WAVE" - "AIRPLANE"), открыв скомпилированный скрипт в Resource Hacker'е (при желании, его даже можно прослушать):


Теперь самое время вернуться к функции _WinAPI_PlaySound(). Формат вызова для нее имеет следующий вид:

_WinAPI_PlaySound ( $sSound [, $iFlags [, $hInstance]] )
Параметры:

$sSoundПуть к .wav файлу, название ресурса или указатель на область памяти, содержащую звуковые данные.
$iFlagsПараметры (флаги) воспроизведения звука. Может быть 0 или комбинацией из $SND_* констант.
$hInstanceУказатель (HANDLE) на модуль, из которого необходимо воспроизвести звук.

Как видно, _WinAPI_PlaySound() является универсальной функцией, и может воспроизводить звуки не только из ресурсов программы, но и из .wav файла или непосредственно из памяти, где расположены соответствующие звуковые данные. Я не буду детально описывать работу этой функции, кто хочет, может почитать про нее в документации к библиотке WinAPIEx.au3. Сейчас нам важно лишь научиться ей пользоваться в рамках данного материала, т.е. проигрывание звуков из ресурсов файла. Параметр $sSound в нашем случае должен содержать название ресурса, в котором расположен файл Airplane.wav - "AIRPLANE". Заметьте, что название типа ресурса ("WAVE") здесь уже не указывается. $hInstance - указатель (хендл) на нашу программу, содержащую WAVE ресурсы. Осталось только разобраться с параметром $iFlags. Он представляет собой целочисленное значение, соответствующее комбинации флагов (битов), которые в свою очередь определены $SND_* константами в библиотеке WinAPIEx.au3. Для объединения двух или более флагов можно воспользоваться функцией BitOR() или просто сложить их, хотя первое предпочтительнее. Как я уже написал выше, функция _WinAPI_PlaySound() является универсальной, и большинство из этих флагов указывают ей как и откуда именно нужно воспроизводить звук. Из всех $SND_* флагов, интерес для нас сейчас представляют только три: $SND_ASYNC, $SND_LOOP и $SND_RESOURCE. $SND_ASYNC позволяет воспроизводить звук в асинхронном режиме, т.е. после вызова _WinAPI_PlaySound() с этим флагом, наша программа продолжит свою работу, в то время, как звук будет проигрываться. В случае, если $SND_ASYNC не указан, выполнение программы будет приостановлено до тех пор, пока звук не будет проигран до конца. $SND_LOOP указывает, что воспроизведение звука должно циклически повторяться. И наконец флаг $SND_RESOURCE говорит функции о том, что звук, который необходимо воспроизвести, находиться в ресурсах программы, на которую указывает параметр $hInstance, а сам ресурс имеет название, заданное параметром $sSound. И последнее, чтобы остановить воспроизведение звука (имеется ввиду асинхронный режим), нужно вызвать _WinAPI_PlaySound() с параметром $sSound, равным 0 или пустой строке ("").

Ну, наконец-то пришло время написать окончательный код. Airplane.wav, который используется в нашем примере, содержит звук пролетающего самолета (из одного уха в другое). Давайте его "зациклим", воспользовавшись флагом $SND_LOOP, пусть себе летает туда-сюда. А для того, чтобы во время "летания" программа не зависала, и мы могли в любое время остановить "полет", добавим флаг $SND_ASYNC. Итак, пишем вызов для функции _WinAPI_PlaySound():

Код: AutoIt [Выделить]
_WinAPI_PlaySound("AIRPLANE", BitOR($SND_ASYNC, $SND_LOOP, $SND_RESOURCE), _WinAPI_GetModuleHandle(0))


И сам скрипт:

Код: AutoIt [Выделить]
#Region
#AutoIt3Wrapper_Res_File_Add=Airplane.wav, WAVE, AIRPLANE
#EndRegion

#Include <GUIConstantsEx.au3>
#Include <WinAPIEx.au3>

Global $Play = False

GUICreate("MyProg", 200, 200)
$Button = GUICtrlCreateButton("Play", 70, 70, 60, 60)
GUISetState()

While 1
    $Msg = GUIGetMsg()
    Switch $Msg
        Case $GUI_EVENT_CLOSE
            ExitLoop
        Case $Button
            $Play = Not $Play
            If $Play Then
                _WinAPI_PlaySound("AIRPLANE", BitOR($SND_ASYNC, $SND_LOOP, $SND_RESOURCE), _WinAPI_GetModuleHandle(0))
                GUICtrlSetData($Button, "Stop")
            Else
                _WinAPI_PlaySound("")
                GUICtrlSetData($Button, "Play")
            EndIf
    EndSwitch
WEnd



Нажмите на кнопку "Play", чтобы начать воспроизведение звука, повторное нажатие ("Stop") его остановит и т.д.

Содержание
« Последнее редактирование: Июнь 02, 2017, 23:17:02 от Garrett »

Оффлайн Yashied [?]

  • AutoIt MVP
  • Глобальный модератор
  • *
  • Сообщений: 5379

  • Автор темы
  • Репутация: 2693
  • Пол: Мужской
    • Награды
  • Версия AutoIt: 3.3.x.x
Re: FAQ по использованию ресурсов в AutoIt
« Ответ #7, Отправлен: Октябрь 16, 2010, 04:53:09 »

Курсоры.

Все, что было сказано ранее для иконок в первой части раздела "Иконки" справидливо и для курсоров. Все курсоры располагаются в ресурсах типа CURSOR, которые в свою очередь объеденены в GROUP_CURSOR. Технически, курсор и есть не что иное, как иконка, и отличается от последней только наличием координат его (курсора) центра. Даже большинство функций в Windows, предназначенных для работы с иконками, могут работать также и с курсорами, не делая при этом различий между ними. Обычно курсоры находятся в .cur файлах (речь здесь пойдет о статических курсорах, не анимированных - .ani). Как и в большинстве случаев при работе со стандартными типами ресурсов, для курсоров тоже есть своя готовая функция - _WinAPI_LoadCursor(), которая работает только с этим типом ресурсов. К сожалению, AutoIt3Wrapper не поддерживает курсоры, поэтому, для того, чтобы добавить курсоры в скомпилированный скрипт, придется воспользоваться Resource Hacker'ом. Для этого пишем следующий код:

Код: AutoIt [Выделить]
#Region
#AutoIt3Wrapper_UseUpx=n
#AutoIt3Wrapper_Run_After=Utilities\ResHacker.exe -add %out%, %out%, Lens.cur, 12, 100,
#AutoIt3Wrapper_Run_After=Utilities\ResHacker.exe -add %out%, %out%, Move.cur, 12, 101,
#AutoIt3Wrapper_Run_After=Utilities\Upx.exe %out% --best --no-backup --overlay=copy --compress-exports=1 --compress-resources=0 --strip-relocs=1
#EndRegion



Lens.cur


Move.cur

Здесь я добавил два курсора из файлов Lens.cur и Move.cur (файлы должны находиться в той же папке, что и файл скрипта). Заметьте, я указал идентификатор именно для типа GROUP_CURSOR - 12, а не CURSOR - 1. Названия для ресурсов я решил сделать соответственно 100 и 101. Используя именно эти названия, мы в дальнейшем и будем загружать наши курсоры. Если сейчас скомпилировать этот код в исполняемый файл, то мы увидим примерно следующую картину:



Как видно на скриншоте, курсоры, также, как и иконки, имеют свои уникальные идентификаторы, в данном случае 50 и 51. Эти идентификаторы присваевает курсорам автоматически сам Resource Hacker, и нужны они для объединения курсоров в группы. Сами же курсоры, Resource Hacker зачем-то отображает в отдельной секции "Cursor". К слову, например известный редактор ресурсов Restorator показывае ресурсы в более наглядной форме и все курсоры отображает в одной секции. Но это лишь вопрос предпочтений и для нас не имеет особого значения. Главное, что мы здесь научились добавлять курсоры в ресурсы программы, несмотря на то, что вынуждены были использовать для этой цели Resource Hacker. Ну, полдела сделано, осталось только научиться использовать эти курсоры в самой программе. Для загрузки курсора в память и получения его хендла, в Windows есть готовая функция - _WinAPI_LoadCursor(). Формат вызова для нее имеет следующий вид:

_WinAPI_LoadCursor ( $hInstance, $sName )
Параметры:

$hInstanceУказатель (HANDLE) на модуль, из которого необходимо загрузить курсор.
$sNameНазвание ресурса, в котором находится курсор, или одно из $IDC_* значений, определяющее стандартный (предопределенный) курсор в системе. Во втором случае, значение параметра $hInstance должно быть равным 0.

При успешном завершении, функция загрузит курсор из ресурсов указанного модуля в память и возвратит его хендл (HCURSOR). В случае ошибки будет возвращен 0. После того, как курсор станет не нужен, его необходимо удалить и освободить занимаемую им память с помощью функции _WinAPI_DestroyCursor().

Если вы читали предыдущие разделы, то не трудно будет догадаться, что в нашем случае, строка вызова функции _WinAPI_LoadCursor() для загрузки курсора из ресурса с названием 100 (файл Lens.cur) должна выглядеть так:

Код: AutoIt [Выделить]
_WinAPI_LoadCursor($hInstance, 100)


Для ресурса 101 (Move.cur):

Код: AutoIt [Выделить]
_WinAPI_LoadCursor($hInstance, 101)


Здесь параметр $hInstance есть указатель (хендл) на исполняемый файл нашей программы - MyProg.exe, содержащий необходимые ресурсы. Получить этот указатель можно, вызвав функцию _WinAPI_GetModuleHandle() с параметром, равным 0. Еще раз напомню, значения 100 и 101 - это названия групп курсоров (то, что мы указывали при вызове Resource Hacker'а в директиве #AutoIt3Wrapper_Run_After), а не их идентификаторы (50 и 51).

У многих наверное уже возник вопрос: загрузить то курсоры мы загрузили, а как теперь их использовать? Да, здесь не все так просто как в случае с иконками. До сих пор мы действовали по схеме: загружаем ресурс и отображаем его в GUI с помощью тех или иных функций. С курсорами это не пройдет. В AutoIt правда есть функция GUICtrlSetCursor(), но с помощью нее можно установить только предопределенный в системе курсор ("стрелка", "знак вопроса" и т.д.), и она не работает с файлами курсоров. А у нас тут два своих собственных курсора, да еще и их хендлы. Написать функцию на подобии _SetHIcon(), но только для курсоров тоже не получится. Дело в том, что в отличии от иконок, курсоры представляют собой динамические объекты. При перемещении мыши изображение курсора постоянно изменяется. Например, если вы подведете курсор мыши к краю какого-нибудь окна в проводнике, то изображение курсора изменится на "двойную стрелку", переместите курсор еще дальше, и его изображение вернется к первоначальному виду. Поэтому, даже если мы и установим свой курсор в программе, он сразу же будет восстановлен на умолчальный. Как же тогда быть? Для этого в Windows существует механизм, так называемых WM-сообщений ("WM" расшифровывается как "Window Messages"). Давайте сейчас немного наморщим ум и разберемся, что же это такое и как с этим работать. Результат, уверяю вас, будет того стоить.

Существует ряд событий в системе (и их достаточно много), после свершения котрых (или до их свершения, в зависимости от типа события), Windows информирует об этом факте программы, посылая им сообщения, связанные с этими событиями. Точнее будет сказать не программы, а окна, которые существуют в системе, ведь не даром это "Windows". И программы, написанные на AutoIt, не являются здесь исключением. Так вот, любая программа может "подписаться" на такие сообщения и далее, по мере их поступления, либо игнорировать эти сообщения, либо производить какие-либо соответствующие действия. Например, когда вы нажимаете на кнопку закрытия окна в его заголовке, Windows посылает этому окну сообщение WM_CLOSE, при сворачивании окна в трей - WM_ACTIVATE, и т.д. Таким образом, программа узнает о том, что ее окно хотят закрыть или свернули в трей. Или еще один пример, когда вы вставляете в ваш компьютер флешку, Windows посылает всем окнам в системе сообщение WM_DEVICECHANGE, тем самым информируя запущенные программы о том, что в систему было добавлено новое устройство. WM-сообщения являются основой Windows, и не одна программа не может обойтись без них. Другое дело, что в AutoIt многие такие сообщения обрабатываются автоматически, дабы вас не обременять этим. Возможно, именно поэтому, многие и предпочитают AutoIt другим языкам программирования.

Вернемся к нашим курсорам. Я не буду перечислять все события, для которых предусмотрены WM-сообщения, если кто заинтересовался, то может об этом почитать в MSDN, скажу только, что среди всего этого множества сообщений, существует и такое, которое посылается программе, в случае необходимости изменения курсора. Называется это сообщение WM_SETCURSOR. Вот именно на него мы и должны подписаться. К большому-большому счастью, AutoIt поддерживает работу с WM-сообщениями. Для того, чтобы зарегистрировать (подписаться) какое-нибудь сообщение, существует функция GUIRegisterMsg(). В нашем случае, для регистрации сообщения WM_SETCURSOR, ее вызов будет выглядеть следующим образом:

Код: AutoIt [Выделить]
GUIRegisterMsg($WM_SETCURSOR, "WM_SETCURSOR")


Здесь $WM_SETCURSOR представляет собой константу (см. WindowsConstants.au3), имеющую значение, соответствующее сообщению WM_SETCURSOR. "WM_SETCURSOR" - функция-обработчик для этого сообщения, которую нам сейчас предстоит написать. Функция-обработчик для любых WM-сообщений должна иметь определеннй заголовок:

Код: AutoIt [Выделить]
Func WM_SETCURSOR($hWnd, $iMsg, $wParam, $lParam)


Количество параметров здесь должно быть строго 4, и каждое имеет свое назначение. Естественно, название функции и ее параметров может быть другим, на ваше усмотрение, но я все же рекомендую именно такие названия переменных, а название функции желательно, чтобы соответствовало названию самого сообщения. При отправке Windows сообщения нашей программе, параметр $hWnd будет содержать хендл окна (HWND), для которого предназначено это сообщение. Поскольку функция-обработчик для каждого сообщения может существовать в программе только в одном экземпляре, а окон может быть несколько, то сообщения данного типа для всех окон будут сыпаться в одну эту функцию, и нам необходимо сортировать получателя сообщения, иначе результат может быть непредсказуемым. $iMsg будет содержать код самого сообщения - $WM_SETCURSOR (0x0020). В параметрах $wParam и $lParam могут передоваться дополнительные данные, для каждого сообщения они имеют свое назначение. Что именно передается в $wParam и $lParam в случае с WM_SETCURSOR, вы можете почитать здесь, нам сейчас это все равно не понадобится. И еще пару важных замечаний: во-первых, ничего внутри функции-обработчике не должно приостанавливать выполнение программы, т.е. никаких Sleep(), MsgBox() и т.д., в противном случае ваша программа наглухо зависнит, и во-вторых, если вы, по каким-либо причинам, игнорируете пришедшее сообщение, необходимо возвратить строку "GUI_RUNDEFMSG", которая задана константой $GUI_RUNDEFMSG, чтобы AutoIt мог самостоятельно обработать это сообщение.

Код: AutoIt [Выделить]
Func WM_SETCURSOR($hWnd, $iMsg, $wParam, $lParam)
   
    ...
   
    Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_SETCURSOR


Итак, что же нужно делать, когда Windows пришлет нам сообщение WM_SETCURSOR? Давайте сначала решим как мы будем использовать добавленные нами ранее курсоры. Я предлагаю создать внутри основного окна небольшую прямоугольную область, и в случае попадания в нее курсора, мы поменяем его на курсор из ресурса с названием 100. А при нажатии на левую кнопку мыши (опять же внутри этой области), будет отображаться курсор из ресурса 101. Тогда, при получении сообщения WM_SETCURSOR, мы должны проверить координаты курсора мыши, и в случае, если курсор находиться внутри заданного прямоугольника, установить свой собственный курсор с названием 100 или 101, в зависимости от того, нажата левая кнопка мыши или нет. Если условия удовлетворяют, и мы устанавливаем свой курсор, то необходимо вернуть значение 1, чтобы AutoIt дальше не обрабатывал сообщение WM_SETCURSOR, в противном случае, обработчик AutoIt изменит наш курсор на умолчальный и все наши труды пойдут на смарку. Если все же курсор не попадает в заданную область и мы не устанавливаем свой курсор, то нужно вернуть значение $GUI_RUNDEFMSG, дав AutoIt'у понять, что он тоже должен обработать это сообщение, т.е. установить курсор.

На первый взгляд, многим может показаться это слишком сложным, но на самом деле, все не так уж и страшно. Это обычная практика при написании программ для Windows. Здесь главное набить руку (мозги), и дальше все пойдет как по маслу. Если вы планируете и дальше писать программы на AutoIt, то без этого вам никак не обойтись, и именно по этому, я так подробно здесь все это расписываю. И последнее, для установки курсора, в Windows существует функция _WinAPI_SetCursor() с одним единственным параметром, соответствующим хендлу курсора, который мы раньше получили с помощью функции _WinAPI_LoadCursor().

Ну чтож, давайте переварим все вышесказанное и напишим окончательный скрипт:

Код: AutoIt [Выделить]
#Region
#AutoIt3Wrapper_UseUpx=n
#AutoIt3Wrapper_Run_After=Utilities\ResHacker.exe -add %out%, %out%, Lens.cur, 12, 100,
#AutoIt3Wrapper_Run_After=Utilities\ResHacker.exe -add %out%, %out%, Move.cur, 12, 101,
#AutoIt3Wrapper_Run_After=Utilities\Upx.exe %out% --best --no-backup --overlay=copy --compress-exports=1 --compress-resources=0 --strip-relocs=1
#EndRegion

#Include <GUIConstantsEx.au3>
#Include <WinAPIEx.au3>
#Include <WindowsConstants.au3>

Opt('MustDeclareVars', 1)

Global $hForm, $hInstance, $hLens, $hMove, $Label

$hForm = GUICreate("MyProg", 300, 300)
$hInstance = _WinAPI_GetModuleHandle(0)
$hLens = _WinAPI_LoadCursor($hInstance, 100)
$hMove = _WinAPI_LoadCursor($hInstance, 101)
$Label = GUICtrlCreateLabel("", 60, 60, 180, 180)
GUICtrlSetState(-1, $GUI_DISABLE)
GUICtrlSetBkColor(-1, 0xFFD0D0)
GUIRegisterMsg($WM_SETCURSOR, "WM_SETCURSOR")
GUISetState()

Do
Until GUIGetMsg() = $GUI_EVENT_CLOSE

Func WM_SETCURSOR($hWnd, $iMsg, $wParam, $lParam)

    Local $aInfo

    Switch $hWnd
        Case $hForm
            $aInfo = GUIGetCursorInfo($hForm)
            If (Not @error) And ($aInfo[4] = $Label) Then
                If $aInfo[2] Then
                    _WinAPI_SetCursor($hMove)
                Else
                    _WinAPI_SetCursor($hLens)
                EndIf
                Return 1
            EndIf
    EndSwitch
    Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_SETCURSOR


   

Небольшое пояснение. Я в этом коде применил одну хитрость, вместо того, чтобы вычислять местоположение курсора в функции-обработчике WM_SETCURSOR(), а зетем сравнивать его с располажением нашей области в основном окне (а еще необходимо проверить активно в данный момент окно или нет), я воспользовался функцией GUIGetCursorInfo(), которая сама даст знать, если курсор будет находиться поверх элемента Label. А заодно, эта функция вернет состояние левой кнопи мыши. Получается, что я здесь убил одним выстрелом двух зайцев. И последнее, я не стал вызывать функцию _WinAPI_DestroyCursor() для удаления загруженных курсоров и освобождения занимаемой ими памяти, т.к. при завершении программы, это будет сделано автоматически.

Содержание
« Последнее редактирование: Июнь 04, 2017, 00:42:58 от Garrett »

Русское сообщество AutoIt

Re: FAQ по использованию ресурсов в AutoIt
« Ответ #7 Отправлен: Октябрь 16, 2010, 04:53:09 »

Оффлайн Yashied [?]

  • AutoIt MVP
  • Глобальный модератор
  • *
  • Сообщений: 5379

  • Автор темы
  • Репутация: 2693
  • Пол: Мужской
    • Награды
  • Версия AutoIt: 3.3.x.x
Re: FAQ по использованию ресурсов в AutoIt
« Ответ #8, Отправлен: Октябрь 16, 2010, 04:54:03 »

Видео.

Несмотря на то, что внутренняя реализация отображения (проигрывания) видео файлов не такая уж и простая, использовать видео в AutoIt очень и очень просто. Для этого есть встроенная функция GUICtrlCreateAvi(), которая помимо проигрывания видео файлов, может проигрывать видео непосредственно из ресурсов программы. Вот давайте от нее и начнем плясать. Как понятно из названия, функция поддерживает видео файлы только в формате AVI. Да, это конечно существенное ограничение, но мы ведь не собираемся запихивать в ресурсы 2.5 часовой фильм. Обычно в ресурсы загружают небольшие анимационные ролики, улучшающие восприятие графического интерфейса.

Итак, давайте сначала разберемся, как мы можем приспособить GUICtrlCreateAvi() для проигрывания видео из ресурсов нашей программы. Первым параметром в этой функции стоит путь к .avi файлу или исполняемому файлу, в ресурсах которого находится необходимое для воспроизведения видео. Ну тут я думаю все понятно, поскольку мы собираемся разместить видео в ресурсах исполняемого файла самой программы (MyProg.exe), то и путь должен указывать на нее, или можно просто написать @ScriptFullPath. Второй параметр должен содержать название ресурса, в котором собственно и расположен видео файл, или (-1), в случае, если предполагается проигрывать видео из .avi файла. Остальные параметры здесь аналогичны всем GUICtrl... функциям и хорошо описаны справке. А вот теперь самое интересное. Для того, чтобы эта функция смогла воспроизвести видео из ресурсов нашей программы, оно (видео) должно находиться в ресурсах типа User-defined c именем "AVI", а названия самих ресурсов должны представлять собой целочисленные значения. Таким образом, если вы напишите например так:

Код: AutoIt [Выделить]

или

Код: AutoIt [Выделить]

то ничего хорошего из этого не выйдет. В лучшем случае, видео просто не будет проиграно, а в худшем, ваша программа завершится с ошибкой. Стоит сказать, что функция GUICtrlCreateAvi() довольно капризная и иногда может не прощать ошибок. Остается только гадать, почему в оффициальной справке об этом не сказано ни слова, но факт остается фактом.

Хорошо, с GUICtrlCreateAvi() разобрались. Теперь давайте добавим в ресурсы нашей MyProg.exe видео файл Timer.avi. Раз уж название типа ресурса должно быть "AVI", то пусть так оно и будет, а название ресурса с видео файлом давайте для разнообразия сделаем 500 (без кавычек!). Добавляется видео файл в ресурсы так же, как и всегда. В случае AutoIt3Wrapper'а пишем следующие строки:

Код: AutoIt [Выделить]
#Region
#AutoIt3Wrapper_Res_File_Add=Timer.avi, AVI, 500
#EndRegion


Для Resource Hacker'а это будет выглядеть так:

Код: AutoIt [Выделить]
#Region
#AutoIt3Wrapper_UseUpx=n
#AutoIt3Wrapper_Run_After=Utilities\ResHacker.exe -add %out%, %out%, Timer.avi, AVI, 500,
#AutoIt3Wrapper_Run_After=Utilities\Upx.exe %out% --best --no-backup --overlay=copy --compress-exports=1 --compress-resources=0 --strip-relocs=1
#EndRegion



Timer.avi


Теперь остается только написать пару строк для воспроизведения в GUI этого видео:

Код: AutoIt [Выделить]
#Region
#AutoIt3Wrapper_Res_File_Add=Timer.avi, AVI, 500
#EndRegion

#Include <AVIConstants.au3>

GUICreate("MyProg", 200, 200)
GUISetBkColor(0xFFFFFF)
GUICtrlCreateAvi(@ScriptFullPath, 500, 68, 68, 64, 64, BitOR($ACS_AUTOPLAY, $ACS_NONTRANSPARENT))
GUISetState()

Do
Until GUIGetMsg() = -3



В заключении скажу пару слов об используемых в видео файлах кодеках. Конечно вы можете использовать любой установленный в системе кодек при создании .avi файла, но может случиться так, что на каком-то компьютере, где будет запускаться ваша программа, этот кодек будет отсутствовать и, как результат, никто ваших красот не увидит. Поэтому я рекомендую использовать кодек Run Length Encoding (RLE) от Microsoft. Во-первых, в силу того, что он использует 256-цветовую палитру, вы получаете видео файлы небольшого размера (например файл Timer.avi, который я использовал в этом примере, весит всего 12.5 KB), а во-вторых, этот кодек присутствует по умолчанию во всех версиях Windows, и у вас не будет проблем с воспроизведением видео на разных компьютерах и в разных системах.

Ну и на последок хочу поделиться с вами двумя небольшими .avi файлами собственного изготовления, иммитирующими процесс загрузки чего-либо. Вы можете использовать их как угодно на свое усмотрение. Размер обоих фалов 30x30, фон - белый (0xFFFFFF), кодек - RGB.


Busy1.avi


Busy2.avi

Содержание
« Последнее редактирование: Июнь 04, 2017, 01:09:47 от Garrett »

Оффлайн Yashied [?]

  • AutoIt MVP
  • Глобальный модератор
  • *
  • Сообщений: 5379

  • Автор темы
  • Репутация: 2693
  • Пол: Мужской
    • Награды
  • Версия AutoIt: 3.3.x.x
Re: FAQ по использованию ресурсов в AutoIt
« Ответ #9, Отправлен: Октябрь 16, 2010, 04:54:49 »

VERSION.

VERSION - это стандартный тип ресурсов (16), в котором, как не трудно догадаться, находится вся информация о вашей программе (версия продукта, его название, описание и т.д.). Эта информация в основном используется Windows Eplorer'е (в диалоговом окне "File Properties" - "Details") и очень редко в самой программе. VERSION может и отсутствовать в исполняемом файле, но, как правило, в большинстве случаев этот ресурс все же заполняется разработчиками. А чем мы хуже? Поэтому я настоятельно рекомендую вам всегда добавлять необходимые данные о вашей программе. Стоит также сказать, что при компиляции AutoIt скрипта в исполняемый файл, AutoIt3Wrapper автоматически создает ресурс VERSION, в который по умолчанию добавляет версию AutoIt в качестве версии файла программы.


Конечно, можно все и так оставить, но это собственно ничего не говорит о самой программе и о ее предназначении. Так что давайте все-таки заполним недостающие поля, а заодно и поменяем версию AutoIt на актуальную версию файла нашей программы, допустим 1.0.0.0. Все это делается с помощью AutoIt3Wrapper'а, используя нижеприведенные директивы:

#AutoIt3Wrapper_Res_LanguageИдентификатор языка ресурса (1033 - Английский (США), 1049 - Русский и т.д.)
#AutoIt3Wrapper_Res_FileVersionВнутренний номер версии файла в формате "x.x.x.x".
#AutoIt3Wrapper_Res_DescriptionКраткое описание программы.
#AutoIt3Wrapper_Res_LegalCopyrightАвторское право.
#AutoIt3Wrapper_Res_CommentКомментарий (если необходимо).
#AutoIt3Wrapper_Res_FieldДополнительные данные в формате "переменная|значение".

В самом скрипте, это будет выглядеть примерно так:

Код: AutoIt [Выделить]
#Region
#AutoIt3Wrapper_Res_Language=1049
#AutoIt3Wrapper_Res_FileVersion=1.0.0.0
#AutoIt3Wrapper_Res_Description=Пример использования Version Info в AutoIt
#AutoIt3Wrapper_Res_LegalCopyright=©2010 Yashied
#AutoIt3Wrapper_Res_Comment=Демонстрационная программа
#AutoIt3Wrapper_Res_Field=OriginalFilename|MyProg.exe
#AutoIt3Wrapper_Res_Field=ProductName|Ресурсы в AutoIt
#AutoIt3Wrapper_Res_Field=ProductVersion|1.0
#EndRegion


Естественно, эти данные будут другими для вашей программы (особенно поле "LegalCopyright") и не обязательно должны присутствовать все сразу. Теперь, если скомпилировать этот скрипт и открыть "File Properties" - "Details", то мы увидим следующую картину:


Ну вот, это уже совсем другое дело, а то какая-то там 3.3.6.1... Одно замечание - в разных системах могут отсутствовать в этом диалоге те или иные поля. Например в Windows 7 не выводятся данные из "Comment", которые мы тут добавили, в то время, как в Windows XP они присутствуют. Ну и для тех, кто хочет вообще избавиться от ресурса VERSION, можете его полностью удалить из исполняемого файла. Правда для этого вам придется воспользоваться Resource Hacker'ом:

Код: AutoIt [Выделить]
#Region
#AutoIt3Wrapper_UseUpx=n
#AutoIt3Wrapper_Run_After=Utilities\ResHacker.exe -delete %out%, %out%, 16, 1,
#AutoIt3Wrapper_Run_After=Utilities\Upx.exe %out% --best --no-backup --overlay=copy --compress-exports=1 --compress-resources=0 --strip-relocs=1
#EndRegion


Здесь число 16 - это значение, определяющее стандартный тип ресурса VERSION (см. "Общая информация"), а 1 - идентификатор ресурса, который создает компилятор AutoIt.

Как я уже сказал ранее, непосредственно в программе, данные из VERSION используются достаточно редко, особенно из ресурсов этой же программы (файла). Но тем не менее, в AutoIt есть функция FileGetVersion(), с помощью которой можно достаточно просто получить практически любые данные из ресурса VERSION. Как пользоваться этой функцией подробно написано в документации к ней, и я, с вашего позволения, не буду на этом останавливаться, а просто приеду простой пример:

Код: AutoIt [Выделить]
#Region
#AutoIt3Wrapper_Res_Language=1049
#AutoIt3Wrapper_Res_FileVersion=1.0.0.0
#AutoIt3Wrapper_Res_Description=Пример использования Version Info в AutoIt
#AutoIt3Wrapper_Res_LegalCopyright=©2010 Yashied
#AutoIt3Wrapper_Res_Comment=Демонстрационная программа
#AutoIt3Wrapper_Res_Field=OriginalFilename|MyProg.exe
#AutoIt3Wrapper_Res_Field=ProductName|Ресурсы в AutoIt
#AutoIt3Wrapper_Res_Field=ProductVersion|1.0
#EndRegion

MsgBox(0, 'Vesion Info', _
        'File description: ' & FileGetVersion(@ScriptFullPath, 'FileDescription') & @CR & _
        'File version: ' & FileGetVersion(@ScriptFullPath, 'FileVersion') & @CR & _
        'Product name: ' & FileGetVersion(@ScriptFullPath, 'ProductName') & @CR & _
        'Product version: ' & FileGetVersion(@ScriptFullPath, 'ProductVersion') & @CR & _
        'Comments: ' & FileGetVersion(@ScriptFullPath, 'Comments') & @CR & _
        'Copyright: ' & FileGetVersion(@ScriptFullPath, 'LegalCopyright') & @CR & _
        'Original filename: ' & FileGetVersion(@ScriptFullPath, 'OriginalFilename'))



Теперь стоит сказать пару слов о языке ресурса. VERSION, да и вообще все ресурсы, помимо типа и названия делятся еще и по идентификатору языка. Например в исполняемом файле могут присутствовать два ресурса одного типа с одинаковыми названиями, но с разными идентификаторами языка. Для чего это нужно? Допустим файл нашей программы имеет два ресурса типа VERSION c названиями (ID) 1, но только язык одного из них - английский (1033), а другого - русский (1049). Если эта программа будет запущена в системе, в которой язык интерфейса установлен английский, то будет использоваться ресурс с идентификатором языка 1033, и соответственно, если язык интерфейса установлен русский, то используется ресурс с идентификатором языка 1049. Таким образом, при запуске программы в разных ОС, автоматически будет задействован именно тот ресурс, который соответствует языку этой ОС. Если ресурс имеет только один язык, то всегда будет использоваться только этот ресурс, независимо от того, какой язык установлен в системе. Лично мне такой автоматический выбор не очень нравится, но в ряде случаев, это может оказаться полезным.

А сейчас попробуем все вышесказанное реализовать на практике в AutoIt. В предыдущем примере мы использовали только русский язык - 1049. Давайте добавим в этот ресурс еще и английский язык. Как ни крути, а с помощью AutoIt3Wrapper'а это сделать не получится. AutoIt3Wrapper позволяет задать только один язык посредством директивы #AutoIt3Wrapper_Res_Language. Но не беда, воспользуемся Resource Hacker'ом, тем более, что мы уже делали нечто подобное в разделе "Строки". Для начала вам потребуется создать .res файл с необходимой информацией для ресурса VERSION. Это можно сделать в любом редакторе ресурсов, в том числе и в Resource Hacker'е. Дабы не разбираться в структуре этого файла, я рекомендую открыть скомпилированный в предыдущем примере файл MyProg.exe в Resource Hacker'е и просто взять от туда целиком ресурс VERSION, сохранив его в .res файл. Затем измените в AutoIt скрипте информацию о программе для английской версии ресурса (не забудьте поменять идентификатор языка на 1033), скомпилируйте его в исполняемый файл и аналогичным образом создайте второй .res файл для английского языка. В любом случае, я все это уже проделал, вот два файла ресурсов VERSION для английского и русского языка соответственно (при желании можно оба файла объединить в один):


Version_Eng.res



Version_Rus.res


Теперь остается только добавить ресурсы из этих двух файлов в нашу программу. Делается это с помощью того же Resource Hackerа'а, пишем следующие команды:

Код: AutoIt [Выделить]
#Region
#AutoIt3Wrapper_UseUpx=n
#AutoIt3Wrapper_Run_After=Utilities\ResHacker.exe -delete %out%, %out%, 16, 1,
#AutoIt3Wrapper_Run_After=Utilities\ResHacker.exe -add %out%, %out%, Version_Eng.res,,,
#AutoIt3Wrapper_Run_After=Utilities\ResHacker.exe -add %out%, %out%, Version_Rus.res,,,
#AutoIt3Wrapper_Run_After=Utilities\Upx.exe %out% --best --no-backup --overlay=copy --compress-exports=1 --compress-resources=0 --strip-relocs=1
#EndRegion


Так как компилятор AutoIt автоматически создает ресурс VERSION (16) c идентификатором 1, его необходимо удалить из файла перед тем, как добавлять свои собственные ресурсы того же типа. Для этого служит команда "-delete". Далее последовательно добавляются ресурсы из файлов Version_Eng.res и Version_Rus.res - команда "-add". Еще раз напомню, т.к. информация о типе, названии и языке ресурса уже содержаться в самом .res файле, то при вызове ResHacker'а, эти параметры не указываются. Для собственного спокойствия можете скомпилировать этот скрипт в исполняемый файл и посмотреть результат приложенных усилий:




Вот собственно и все. Если вы замените секцию AutoIt3Wrapper'а из предыдущего примера на эту, то получите ту же самую программу с той лишь разницей, что информация о программе будет выводиться в MsgBox() из разных ресурсов, в зависимости от установленного в системе языка интерфейса - русский или английский.

Код: AutoIt [Выделить]
#Region
#AutoIt3Wrapper_UseUpx=n
#AutoIt3Wrapper_Run_After=Utilities\ResHacker.exe -delete %out%, %out%, 16, 1,
#AutoIt3Wrapper_Run_After=Utilities\ResHacker.exe -add %out%, %out%, Version_Eng.res,,,
#AutoIt3Wrapper_Run_After=Utilities\ResHacker.exe -add %out%, %out%, Version_Rus.res,,,
#AutoIt3Wrapper_Run_After=Utilities\Upx.exe %out% --best --no-backup --overlay=copy --compress-exports=1 --compress-resources=0 --strip-relocs=1
#EndRegion

MsgBox(0, 'Vesion Info', _
        'File description: ' & FileGetVersion(@ScriptFullPath, 'FileDescription') & @CR & _
        'File version: ' & FileGetVersion(@ScriptFullPath, 'FileVersion') & @CR & _
        'Product name: ' & FileGetVersion(@ScriptFullPath, 'ProductName') & @CR & _
        'Product version: ' & FileGetVersion(@ScriptFullPath, 'ProductVersion') & @CR & _
        'Comments: ' & FileGetVersion(@ScriptFullPath, 'Comments') & @CR & _
        'Copyright: ' & FileGetVersion(@ScriptFullPath, 'LegalCopyright') & @CR & _
        'Original filename: ' & FileGetVersion(@ScriptFullPath, 'OriginalFilename'))



Если у вас установлена Windows Vista/7 Ultimate (или другая ее версия, поддерживающая смену языка интерфейса), то вы можете легко проверить работу этой программы, изменив язык интерфейса в контрольной панели ("Region and Language" - "Keyboards and Languages").


Естественно, все вышесказанное относительно языка ресурса справедливо для всех типов ресурсов, не только для VERSION. Просто, как правило, мультиязычные ресурсы создают в основном для типов STRING и VERSION (DIALOG и MENU в AutoIt не используются). Но теоретически вы можете таким образом разделить по языкам абсолютно любой тип ресурсов, включая User-defined. Все зависит от конкретных целей.

Содержание
« Последнее редактирование: Июнь 17, 2017, 17:26:29 от Garrett »

Оффлайн Yashied [?]

  • AutoIt MVP
  • Глобальный модератор
  • *
  • Сообщений: 5379

  • Автор темы
  • Репутация: 2693
  • Пол: Мужской
    • Награды
  • Версия AutoIt: 3.3.x.x
Re: FAQ по использованию ресурсов в AutoIt
« Ответ #10, Отправлен: Октябрь 16, 2010, 04:55:41 »

RCDATA и User-defined.

Ресурсы типа RCDATA или User-defined предназначены для хранения данных произвольного формата. RCDATA является предопределенным типом с идентификатором 10 (см. "Общая информация"). Тип User-defined не имеет своего идентификатора и может иметь любое строковое или целочисленное значение, не занятое стандартными типами ресурсов. На этом различия между этими двумя типами заканчиваются. Данные в ресурсах этих типов располагаются "как есть". Например, если вы загрузили в ресурс типа RCDATA какой-нибудь файл, то данные внутри ресурса будут представлять собой точную копию этого файла на диске. При написании данного материал, я уже неоднократно прибегал к использованию User-defined ресурсов, но сейчас самое время остановиться на этом более подробно.

Несмотря на то, что RCDATA и User-defined практически ничем не отличаются друг от друга, я все же не рекомендую все подряд загружать в RCDATA и делать из него своего рода помойку. Куда логичнее будет создать для каждого типа данных свой User-defined ресурс. Например TrueType шрифты поместить в ресурс с названием типа "TTF", изображения формата PNG в "PNG", текстовые данные в "TEXT" и т.д. Лично я вообще не использую RCDATA и загружаю все необходимые данные по описанной схеме, чего и вам советую. Для справки, в программах, написанных на Delphi, вся информация о диалогах и меню находится как раз в RCDATA, что доставляет массу проблем в случае необходимости их редактирования, например для руссификации. Честно говоря, я долго думал о том, какой бы такой придумать интересный и небольшой пример, чтобы он использовал ресурсы типа RCDATA или User-defined, и в тоже время, это было бы логически оправдано. Ведь если взять и поместить в RCDATA какой-нибудь исполняемый (.exe) файл, то это ничем не будет отличаться от FileInstall(), так как, чтобы его в последствии запустить, этот файл все равно придется записать на диск. Не интересно... Вообщем, решил немного поиграться с графикой, а точнее с картинками. Но просто отобразить картинку в GUI не представляет особых трудностей, тем более, это уже было достаточно подобно описано в разделе "Картинки". Поэтому я решил не просто вывести изображение в GUI, а вывести его с каким-нибудь эффектом. Для этого я взял первый попавшийся .bmp файл и поделил его на много-много "квадратиков" размером 10x10 точек. Затем случайным образом перемешал их и записал в двоичный файл, включая координаты местоположения каждого такого "квадратика" внутри изображения. Теперь, теоретически, если попытаться вывести это изображение, последовательно отображая каждый "квадратик" на своем законном месте, то получиться что-то типа эффекта мозаики. Проверим?

Естественно, полученный мной файл уже не является битмапом, и никакая программа его не распознает. Каким образом я поделил исходный битмап, сейчас не имеет особого значения, да и выходит далеко за рамки данной темы. На данный момент мы имеем некий хитрый файл (Tech.dat), который нужно поместить в ресурсы нашей программы, а затем отобразить его в GUI непосредственно из памяти, минуя запись данных на диск. Вот именно этим мы сейчас и займемся. Итак, для справки, струтура Tech.dat имеет следующий вид:

x1 | y1 | rgb1[100] | x2 | y2 | rgb2[100] | ... | xn | yn | rgbn[100]
Здесь xn и yn - координаты левого верхнего угла каждого "квадратика" относительно левого верхнего угла целого изображения, а rgbn - массив из 100 (10x10) значений цвета (в RGB) для каждой точки, из которых состоит сам "квадратик".

Ну, я думаю вы уже догадались, что для того, чтобы отобразить полностью всю картинку, нужно создать цикл, в котором последовательно будут считываться очередные значения x, y и 100 значений rgb, а затем заполняться квадратная (10x10) область с координатами {x;y} точками c цветами из массива rgb. Конечно мы не будем рисовать каждую точку отдельно, т.к. это займет неприлично много времени, а воспользуемся API функциями, но об этом чуть позже. Сечас давайте вернемся собственно к ресурсам. Как и в предыдущих разделах, для добавления файа Tech.dat в ресурсы с помощью #AutoIt3Wrapper'а используем директиву #AutoIt3Wrapper_Res_File_Add:

Код: AutoIt [Выделить]
#Region
#AutoIt3Wrapper_Res_File_Add=Tech.dat, DATA, TECH
#EndRegion


В случае, если вы выбрали Resource Hacker, это будет выглядеть следующим образом:

Код: AutoIt [Выделить]
#Region
#AutoIt3Wrapper_UseUpx=n
#AutoIt3Wrapper_Run_After=Utilities\ResHacker.exe -add %out%, %out%, Tech.dat, DATA, TECH,
#AutoIt3Wrapper_Run_After=Utilities\Upx.exe %out% --best --no-backup --overlay=copy --compress-exports=1 --compress-resources=0 --strip-relocs=1
#EndRegion



Tech.dat

Здесь "DATA" представляет собой название типа User-defined ресурса, "TECH" - название ресурса, содержащего файл Tech.dat (этот файл должел находиться в той же папке, что и сам скрипт). Так как язык ресурса в данном случае не имеет значения, то я его и не стал указывать. Если вы хотите загрузить файл именно в RCDATA, то просто замените "DATA" на 10. Проверить, загрузился ли Tech.dat, можно, открыв исполняемый файл в Resource Hacker'е:


Если вы откроете файл Tech.dat в каком-нибудь двоичном редакторе, то увидите ту же самую картину, что и на скриншоте, т.е. данные из этого файла были загружены в ресурсы нашей программы один в один. И тут снова встает вопрос о том, как теперь достать эти данные из ресурсов и использовать их в дальнейшем? В AutoIt, да и Windows тоже, нет готовых функций для этого, поэтому нам придется писать собственную функцию для загрузки двоичных данных из ресурсов программы. И я уже написал такую функцию, название которой _LoadResourceData(). Но здесь у меня возник один ворос: в каком именно виде возвращать полученные данные? Просто как двоичные данные в формате AutoIt или структуру? В первом случае возможно будет проще для понимания и последующей обработки, но в любом случае, рано или поздно вы столкнетесь с тем, что придется эти данные переводить в структуру, чтобы можно было воспользоваться API функциями. А от этого никуда не денешься, в Windows вообще все завязано на структурах. Поэтому, недолго думая, я решил, что _LoadResourceData() будет возвращать структуру вида "byte[n]". Подробно о том, что собой представляют структуры и как с ними работать, я расскажу в разделе "Общие принципы работы с ресурсами", а пока лишь скажу, что структуру можно рассматривать как набор переменных различных типов, образующих единое целое, и занимающий непрерывный участок памяти размером, равным сумме размеров всех включенных в структуру переменных. В данном случае, функция _LoadResourceData() выделяет непрерывный участок память размером n байт и возвращает структуру, которая располагается по адресу выделенной памяти. Эту структуру можно рассматривать как массив из n последовательно расположенных байт. Для тех, кто впервые слышит о структурах, это может показаться немного запутанным, но я советую вам обязательно разобраться в них, т.к. в последствии вы еще не раз столкнетесь со структурами. Кстати, в Delphi они называются записями (Records). Итак, вернемся к _LoadResourceData(). Формат вызова для этой функции имеет следующий вид:

_LoadResourceData( $hInstance, $sResType, $sResName [, $iResLanguage] )
Параметры:

$hInstanceУказатель (HANDLE) на модуль, из которого необходимо загрузить двоичные данные.
$sResTypeТип ресурса.
$sResNameНазвание ресурса.
$iResLanguageИдентификатор языка ресурса. (Опционально)

При успешном завершении, функция загрузит данные из ресурса, соответствующего указанному типу, названию и языку, в память и возвратит структуру вида "byte[n]", располагающуюся по адресу этой памяти. Кроме того, флаг @extended будет содержать размер выделенной памяти (размер структуры) в байтах, значение n. В случае ошибки будет возвращен 0. После того, как данные станут не нужны, необходимо освободить занимаемую структурой память, присвоив переменной нулевое значение.

Код: AutoIt [Выделить]
$tData = _LoadResourceData(...)

...

$tData = 0


Для справки, если структура объявлена внутри какой-либо функции как локальная переменная, то ее можно и не обнулять, т.к. при завершенни функции, вся память, занимаемая локальными переменными автоматически освобождается.

Для нашего случая, вызов _LoadResourceData() должен вглядеть так:

Код: AutoIt [Выделить]
$tData = _LoadResourceData($hInstance, "DATA", "TECH")


Если ничего непредвиденного не произойдет, то переменная $tData будет содержать структуру размером, равным размеру файла Tech.dat, размер в байтах будет возвращен посредством флага @extended. Таким образом, мы добились того, чего и хотели, а именно загрузили в память из ресурсов нашей программы файл Tech.dat, который содержит все необходимые данные для отображения картинки. А вот как именно реализовать само отображение в GUI, я пожалуй оставлю без комментариев, позволив вам самим разобраться в нижеследующем коде. Если будет не все понятно, то советую прочитать следующий раздел ("Общие принципы работы с ресурсами"), где я попытаюсь подробно рассказать о работе с ресурсами на уровне API вообще и со структурами в частности. Ну или задавайте вопросы в этой теме.

Код: AutoIt [Выделить]
#Region
#AutoIt3Wrapper_Res_File_Add=Tech.dat, DATA, TECH
#EndRegion

#Include <WinAPIEx.au3>
#Include <WindowsConstants.au3>

$hInstance = _WinAPI_GetModuleHandle(0)
$tData = _LoadResourceData($hInstance, "DATA", "TECH")
If Not @error Then
    $iSize = @extended
Else
    Exit
EndIf

$hForm = GUICreate("", 450, 290, -1, -1, $WS_POPUP, BitOR($WS_EX_LAYERED, $WS_EX_TOPMOST))
GUISetBkColor(0xABABAB)
GUISetState()

_WinAPI_SetLayeredWindowAttributes($hForm, 0xABABAB, 0, $LWA_COLORKEY)

$hDC = _WinAPI_GetDC($hForm)

$pData = DllStructGetPtr($tData)
For $i = 0 To $iSize / 4 / 102 - 1
    $tPart = DllStructCreate("int X;int Y;dword Data[100]", $pData + 102 * 4 * $i)
    $pPart = DllStructGetPtr($tPart, "Data")
    $hBitmap = _WinAPI_CreateBitmap(10, 10, 1, 32, $pPart)
    $X = DllStructGetData($tPart, "X")
    $Y = DllStructGetData($tPart, "Y")
    _WinAPI_DrawBitmap($hDC, $X, $Y, $hBitmap)
    _WinAPI_DeleteObject($hBitmap)
;~  Sleep(1)
Next

_WinAPI_ReleaseDC($hForm, $hDC)

$tData = 0
$tPart = 0

Do
Until GUIGetMsg() = -3

Func _LoadResourceData($hInstance, $sResType, $sResName, $iResLanguage = 0)

    Local $hInfo, $hData, $pData, $iSize

    If $iResLanguage Then
        $hInfo = _WinAPI_FindResourceEx($hInstance, $sResType, $sResName, $iResLanguage)
    Else
        $hInfo = _WinAPI_FindResource($hInstance, $sResType, $sResName)
    EndIf
    $hData = _WinAPI_LoadResource($hInstance, $hInfo)
    $iSize = _WinAPI_SizeOfResource($hInstance, $hInfo)
    $pData = _WinAPI_LockResource($hData)
    If @error Then
        Return SetError(1, 0, "")
    EndIf
    Return SetError(0, $iSize, DllStructCreate("byte[" & $iSize & "]", $pData))
EndFunc   ;==>_LoadResourceData



Если у вас картинка будет проявляться слишком быстро, то снимите комментарий в строке, содержащей Sleep(1), а лучше замените эту функцию на следующую:

Код: AutoIt [Выделить]
Func _Sleep($iDelay)

    Local $Timer = TimerInit()

    Do
    Until TimerDiff($Timer) > $iDelay
EndFunc   ;==>_Sleep


Содержание
« Последнее редактирование: Июнь 17, 2017, 17:58:13 от Garrett »

Оффлайн Yashied [?]

  • AutoIt MVP
  • Глобальный модератор
  • *
  • Сообщений: 5379

  • Автор темы
  • Репутация: 2693
  • Пол: Мужской
    • Награды
  • Версия AutoIt: 3.3.x.x
Re: FAQ по использованию ресурсов в AutoIt
« Ответ #11, Отправлен: Октябрь 16, 2010, 04:56:10 »

Общие принципы работы с ресурсами.

В этом разделе я расскажу о том как работать с ресурсами любого типа на уровне API, т.е. без использования функций, предназначенных для работы только с какими-либо определенными типами ресурсов. В предыдущих разделах я предоставлял готовые функции вида _LoadResource... для загрузки ресурса того или иного типа, а сейчас я постараюсь более подробно рассказать о том, как собственно они работают. Если вас в данный момент это не особо интересует, то можете смело пропустить этот раздел.


Структуры.

Перед тем, как говорить о Windows API, я должен рассказать о таком типе данных, как структура. О том для чего она вообще нужна в AutoIt и как с ней работать. Итак, начну с того, что в AutoIt все переменные являются типом Variant, т.е. такие переменные могут содержать данные любого типа, к тому же, тип данных может динамически изменяться в ходе выполнения программы. С одной стороны, это очень удобно, поскольку вам не приходится заботиться о соответствии типа переменной типу данных, которые присваиваются этой переменной. Да и в большинстве случаев это сильно сокращает количество переменных в вашем коде. Например, вы можете написать так:

Код: AutoIt [Выделить]
Dim $Var[4]
$Var = 0
$Var = "0"


В этом примере, после выполнения первой строки, переменная $Var будет представлять собой одномерный массив из четырех элементов. Во второй строке, тип $Var изменится на целочисленный. В третьей, $Var будет уже строкового типа (т.к. присутствуют кавычки). Как видно, все очень просто, и наверное мало кто задумывается о том, какого типа в данный момент является та или иная переменная. С другой стороны, это требует более внимательного подхода к написанию кода, т.к. при случайном изменении типа переменной, могут возникнуть серьезные ошибки, вплоть до "вылета" программы или потере каких-нибудь данных на диске. Например, если вы напишите так:

Код: AutoIt [Выделить]
Dim $Var[4]
$Var = 0
$Var[0] = 0


то произойдет ошибка во время выполнения программы, т.к. в третьей строке, переменная $Var уже не будет являться массивом, а будет иметь целочисленный тип и содержать значение 0.

Все это конечно не так страшно, просто нужно аккуратнее и внимательнее писать код, но здесь есть и еще один более серьезный недостаток. Не знаю, связано ли это с типом Variant или нет, но адресацией (расположением в памяти) всех переменных занимается непосредственно AutoIt и только он. Пользователю не предоставляется никаких инструментов, позволяющих вделять или освобождать память для переменных. Вы даже не можете узнать адрес по которому находятся данные, связанные с вашей переменной. Например в Delphi для этого служит префикс "^", в PureBasic - "*", а в AutoIt ничего нет. Конечно, если вы будете писать скрипты, используя только нативные функции AutoIt, то это вряд ли вам понадобится, но в случае необходимости использовать функции из Windows API, без этого ну никак не обойтись. И вот именно для этого в AutoIt и были введены структуры.

Итак, что же представляет собой структура и с чем ее едят? Структура - это тип данных, объединяющий несколько переменных одного или разных, но строго фиксированных, типов. Другими словами, структура представляет собой зарезервированную область памяти заданного размера и условно поделенная на отдельные участки. Размер каждого такого участка равен размеру соответствующего иму типа. Таким образом, структура не может состоять из переменных типа Variant. Все переменные, включенные в структуру, должны быть однозначно определенного типа и занимать соответствующий этому типу объем памяти. Общий размер занимаемой структурой памяти есть величина постоянная и равна сумме размеров всех включенных в структуру переменных. Еще нужно сказать, что структура располагается в памяти непрерывно, т.е. все данные, которые содержит структура, следуют в памяти друг за другом, начиная с заданного адреса. Из этого следует, что максимальный размер структуры равен максимальной нефрагментированной области памяти, доступной программе. Например по умолчанию в 32-битной Windows, все приложения могут использовать до 2 ГБ памяти. На деле же будет возможно создать структуру размером не более ~1.5 ГБ, в зависимости от количества памяти в компьютере и количества запущенных в данный момент приложений. В случае перебора, вы получите ошибку "Error allocating memory".

Ну, с тем, что такое структура, мы вроде бы разобрались. Теперь давайте поговорим о том, как с ними работать в AutoIt. Для этого существует всего несколько функций:

DllStructCreate
DllStructGetData
DllStructGetPtr
DllStructGetSize
DllStructSetData

Подробно о них написано в документации по AutoIt. Я расскажу здесь лишь самое необходимое. Создается структура с помощью функции DllStructCreate(). В качестве параметра передается строка, в которой перечисляются условные названия типов данных, из которых собственно и должна состоять структура, разделенные символом ";". Допустимые названия типов для создания структур в AutoIt перечислены в таблице в описании к этой функции (см. выше). Что хорошо, все названия типов полностью соответствуют их названиям в C/C++ и соответственно в MSDN, что безусловно поможет в будущем избежать путаницы, читая описания к API функциям в том же MSDN. Каждый тип данных резервирует строго определенный размер памяти. Вот простой пример создания структуры на AutoIt:

Код: AutoIt [Выделить]
$tStruct = DllStructCreate("int;int")


Здесь создается структура, состоящая из двух последовательно расположенных элементов целочисленного знакового типа INT. Посмотрев в таблицу допустимых типов, вы увидите, что тип INT требует для хранения данных всего 4 байта памяти. Как нетрудно догадаться, размер всей структуры будет равен 4 + 4 = 8 байт. Для удобства работы со структурами, вы можете для каждой переменной в структуре указать уникальное (в пределах этой структуры) имя, например:

Код: AutoIt [Выделить]
$tStruct = DllStructCreate("int X;int Y")


Разница между этим и предыдущим примерами заключается лишь в том, что в первом случае мы можем обращаться к переменным (полям) структуры только по их порядковому номеру (в отличии от массивов, где первый элемент имеет индекс 0, в структурах всегда нумерация начинается с 1), а во втором случае, помимо индекса, мы можем обращаться к полям еще и по их именам (в данном случае "X" и "Y"). Если у вас последовательно расположено несколько переменных одного типа и позволяет логика самой структуры, то можно их записать как "массив". Например предыдущий пример можно было бы записать следующим образом:

Код: AutoIt [Выделить]
$tStruct = DllStructCreate("int[2]")


или

Код: AutoIt [Выделить]
$tStruct = DllStructCreate("int XY[2]")


В обоих случаях будет выделена память размером 8 байт, но только здесь мы должны уже обращаться к полю "XY", используя еще и порядковый номер в "массиве", т.е. номер самой переменной "XY" будет всегда равен 1, т.к. стоит первая в структуре, а индекс, составляющих ее элементов - 1 или 2. Заметьте, что индекс здесь тоже начинается не с 0, как в массивах, а с 1.

ВАЖНО. При создании структуры всегда очень аккуратно перечисляйте типы данных, хорошо представляя, сколько каждый из них требует памяти и в какой последовательности они должны располагаться в структуре. В противном случае, это может стать причиной ряда "необъяснимых" ошибок или привести к краху вашей программы, а то и того хуже...

Как я уже сказал, при создании любой структуры, для нее будет выделен из стека необходимый объем памяти. В данном случае, это конечно ничтожно мало (8 байт), но ведь размер структуры может быть и гораздо больше. И здесь вся ответственность за освобождение используемой структурой памяти ложится непосредственно на вас. Если вы не будите следить за этим и своевременно освобождать неиспользуемую память, то возможно очень скоро увидите на экране надпись типа "Error allocating memory". Поэтому, хорошей идеей будет уничтожить структуру, освобождая тем самым занимаемую ей память, сразу после того, как она (структура) станет не нужна. Никаких функций для этого не требуется, здесь как раз используется свойство переменных типа Variant, т.е. достаточно просто изменить тип переменной, содержащей структуру. Другими словами, присвоить переменной какое-нибудь значение (как правило 0), но можно вообще что угодно, хоть массив или другую структуру для этой переменной создать. В любом случае, структура будет автоматически уничтожена (естественно, все данные, находящиеся в структуре, при этом будут потеряны), и освобожден соответствующий ей объем памяти. Если структура была создана внутри какой-либо функции как локальная переменная, то ее можно и не уничтожать, т.к. при завершении функции, это будет сделано автоматически. Вот простой пример, демонстрирующий как все это работает:

Код: AutoIt [Выделить]
$tStruct = DllStructCreate('byte[1073741824]')
MsgBox(64 + 262144, "", "Создана структура размером 1 ГБ.")
$tStruct = 0
MsgBox(64 + 262144, "", "Структура уничтожена.")


Здесь создается простая структура, состоящая из последовательно расположенных элементов типа BYTE. Размер этой структуры будет равен сумме размеров всех ее элементов (тип BYTE, как нетрудно догадаться, требует 1 байт памяти), т.е. 1 * 1073741824 = 1073741824 байтов или 1 ГБ. Соответственно и объем памяти, необходимый для создания такой структуру будет равен 1 ГБ. Если вы откроете "Диспетчер задач", то увидите, как будет меняться используемый процессом AutoIt.exe объем памяти. Ну, если на вашем компьютере установлено менее 2 ГБ памяти, то скорее всего, вы увидите сообщение типа "Error allocating memory". В этом случае уменьшите размер структуры, например до 512 МБ (536870912 вместо 1073741824).

Все, что мы до сих пор делали, с практической точки зрения ничем не отличается от объявления простых переменных, только более запутанно, и смысл этого на первый взгляд неочевиден. Так оно и было бы, если бы не одно очень полезное свойство структур - их можно объединять. Не добавлять одну к другой, (это физически нельзя сделать, т.к. структура должна занимать непрерывный участок памяти, а две разные структуры будут располагаться в разных областях адресного пространства), а создавать несколько структур по одному и тому же адресу в памяти. Какой в этом смысл? Здесь главное понять, что в памяти компьютера никаких типов данных не существует, и все они располагаются в виде последовательности отдельных байт (минимальной единице информации, к которой непосредственно приводятся все объёмы данных, только не говорите сейчас о битах). А как будет использоваться эта последовательность байт зависит только от нас. Например, если мы создали структуру вида "int", которая занимает 4 байта памяти, а значения для нее лежат в пределах от -2147483648 до 2147483647, то мы можем на ее же месте (по тому же адресу) создать другую структуру вида "short;short" или "short[2]", которая тоже будет занимать 4 байта памяти (тип SHORT требует 2 байта памяти), но только трактоваться эта структура будет по другому, уже ни одно, а два целых знаковых числа, но с диапазонами от –32768 до 32767, что соответствует 16-битному или 2-байтному знаковому числу. Или можем пойти дальше и создать поверх последней структуры, структуру типа "byte;byte;byte;byte" или "byte[4]". Размер такой структуры, как видно, тоже равен 4 байтам, но данные в ней представляют собой 4 последовательных числа с диапазонами от 0 до 255 (8-битное или 1-байтное беззнаковое число). И все эти три структуры будут содержать одни и теже данные (байты), но только по-разному их использовать. Естественно, физически будет создана только первая структура ("int"), а остальные будут использовать уже выделенную под нее память по тому же адресу. И, соответственно, освобождать занимаемую память, нужно только для первой структуры. Вот наглядный пример с комментариями, демонстрирующий все вышесказанное:

Код: AutoIt [Выделить]
; Создаем структуру $tInt вида "int" (4 байта)
$tInt = DllStructCreate("int")

; Записываем данные в структуру
DllStructSetData($tInt, 1, 0x11223344)

; Читаем данные из структуры $tInt и смотрим результат в консоли
$iInt = DllStructGetData($tInt, 1)
ConsoleWrite("| " & Hex($iInt) & " |" & @CR)

;----------------------------------------

; Получаем адрес структуры $tInt
$pInt = DllStructGetPtr($tInt)

; Создаем структуру $tShort вида "short[2]" (2 * 2 = 4 байта) по адресу структуры $tInt
$tShort = DllStructCreate("short[2]", $pInt)

; Читаем данные из структуры $tShort и смотрим результат в консоли
ConsoleWrite("| ")
For $i = 1 To 2
    $iShort = DllStructGetData($tShort, 1, $i)
    ConsoleWrite(Hex($iShort, 4) & " | ")
Next
ConsoleWrite(@CR)

;----------------------------------------

; Получаем адрес структуры $tShort
$pShort = DllStructGetPtr($tShort)

; Создаем структуру $tByte вида "byte[4]" (1 * 4 = 4 байта) по адресу структуры $tShort
$tByte = DllStructCreate("byte[4]", $pShort)

; Читаем данные из структуры $tByte и смотрим результат в консоли
ConsoleWrite("| ")
For $i = 1 To 4
    $iByte = DllStructGetData($tByte, 1, $i)
    ConsoleWrite(Hex($iByte, 2) & " | ")
Next
ConsoleWrite(@CR)

;----------------------------------------

$tInt = 0


Если вы запустите на выполнение этот код, то увидите в консоли (вы же используете SciTE, не правда ли) следующую картину:

| 11223344 |
| 3344 | 1122 |
| 44 | 33 | 22 | 11 |

Здесь хорошо видно, что как мы записали в самом начале кода в структуру $tInt значение 0x11223344, так оно никуда и не делось, т.к. все последующие структуры ($tShort и $tByte) есть суть структуры $tInt, и просто "накладывались" на нее, не затрагивая самих данных. Но если присмотреться внимательнее к полученному результату, то можно обнаружить очень интересную особенность, а именно то, что данные в памяти располагаются задом на перед. Не спрашивайте меня почему так, возможно тот, кто это придумал, был китайцем... Но факт остается фактом, так что примите это за аксиому и запомните, что все данные в пределах одного типа располагаются в памяти, начиная с младшего байта. Такой порядок байт называется little-endian, или "интеловским". Но как ни странно, особо заморачиваться с этим не приходится, т.к. все это происходит на аппаратном уровне. Например в последнем примере, когда мы читаем данные из структуры $tInt, мы ведь не переворачиваем с ног на голову все четыре байта данных, да и вообще ничего не делаем. Просто прочитали число и вывели его в консоль. И результате получили тоже 0x11223344, а не 0x44332211. А все потому, что использовали тот же тип данных, что и при записи в структуру, и за нас это было сделано автоматически. Другое дело, когда мы читаем 4-байтовое число как два 2-байтовых или четыре 1-байтовых числа. В этом случае уже нужно вспоминать о порядке располажения байт в памяти. Во второй строке полученного результата, байты тоже идут в обратной последовательности, но только в пределах 2 байт (тип SHORT). Т.е. при операциях чтения (записи) из памяти данных любого типа, расположение байт в них меняет направление.

Теперь настало время рассказать об оставшихся функциях, предназначенных для работы со структурами, тем более я их уже использовал в вышеприведенном примере. Для записи данных в структуру используется функция DllStructSetData(), для чтения - DllStructGetData(). Когда вы записываете какие-либо данные в структуру, то следите за тем, чтобы эти данные соответствовали типу элемента структуры, в противном случае, результат может быть непредсказуемым. AutoIt не контролирует соответствие этих типов, и в случае ошибки, в структуре может оказаться "каша". Вот основные способы записи данных в структуру, в зависимости от способа ее создания:

Код: AutoIt [Выделить]
$tStruct = DllStructCreate("int;int")

DllStructSetData($tStruct, 1, 400)
DllStructSetData($tStruct, 2, 250)

;----------------------------------------

$tStruct = DllStructCreate("int[2]")

DllStructSetData($tStruct, 1, 400, 1)
DllStructSetData($tStruct, 1, 250, 2)

;----------------------------------------

$tStruct = DllStructCreate("int X;int Y")

DllStructSetData($tStruct, "X", 400)
DllStructSetData($tStruct, "Y", 250)

; или

DllStructSetData($tStruct, 1, 400)
DllStructSetData($tStruct, 2, 250)

;----------------------------------------

$tStruct = DllStructCreate("int XY[2]")

DllStructSetData($tStruct, "XY", 400, 1)
DllStructSetData($tStruct, "XY", 250, 2)

; или

DllStructSetData($tStruct, 1, 400, 1)
DllStructSetData($tStruct, 1, 250, 2)


Чтение данных из структуры происходит аналогичным образом. Если элемент структуры объявлен как "массив", например "byte[n]", "char[n]" или "wchar[n]", то допускается записывать данные в такие элементы следующим образом:

Код: AutoIt [Выделить]
$bData = Binary("0x0123456789ABCDEF")

; Создаем структуру вида "byte[n]", размер котрой (n) равен размеру двоичных данных в переменной $bData
$tData = DllStructCreate("byte[" & BinaryLen($bData) & "]")

; Записываем сразу все n байт двоичных данных из переменной $bData в структуру $tData
DllStructSetData($tData, 1, $bData)

; Читаем сразу все n байт двоичных данных из структуры $tData
$bData = DllStructGetData($tData, 1)

ConsoleWrite($bData & @CR)

;----------------------------------------

$sData = "Общие принципы работы с ресурсами"

; Создаем структуру вида "wchar[n]", размер котрой (n) равен количеству символов в Unicode строке $sData + 1 (+1, т.к. нуль-терминированная строка)
$tData = DllStructCreate("wchar[" & (StringLen($sData) + 1) & "]")

; Записываем всю строку целиком в структуру $tData
DllStructSetData($tData, 1, $sData)

; Читаем всю строку целиком из структуры $tData
$sData = DllStructGetData($tData, 1)

ConsoleWrite($sData & @CR)

;----------------------------------------

$tData = 0


0x0123456789ABCDEF
Общие принципы работы с ресурсами

Обратите внимание на то, что в случае записи в структуру строки, необходимо, чтобы размер структуры был на один символ (2 байта, т.к. тип WCHAR является Unicode символом и требует 2 байта памяти) больше, т.к. все строки в памяти компьютера представляются как нуль-терминированные строки или ASCIZ (строки, последним символом в которых должен быть символ с кодом 0, обозначающий конец строки).

Функция DllStructGetSize() позволяет получить размер (в байтах) созданной ранее структуры. Не подсчитывать же нам всегда размер самостоятельно, а структуры бывают далеко не такие простые, как в этом примере, и могут состоять из 10 и более элементов разного типа. Пользоваться этой функцией очень просто, в качестве единственного параметра передается переменная, содержащая созданную ранее с помощью функции DllStructCreate() структуру. И наконец самая необходимая функция - DllStructGetPtr(), которая возвращает адрес (указатель) созданной ранее структуры в памяти, и именно этот адрес требуется для вызова большинства функций из Windows API. Вызов DllStructGetPtr() аналогичен DllStructGetSize() и не представляет из себя ничего сложного. Еще есть функция IsDllStruct(), которая позволяет определить, является ли в данный момент указанная переменная структурой или нет. Эту функцию полезно использовать для обнаружения ошибки при создании структуры, но обычно этого не делают, т.к. вид структуры известен заранее и никаких ошибок возникать не должно, если структура изначально составлена правильно. Да и всегда можно проверить состояние флага @error сразу после вызова DllStructCreate().

Структуры в AutoIt используются в основном для вызова API функций и в обработчиках WM-сообщений (см. "Курсоры"). Даже названия (префикс) функций для работы со структурами говорят сами за себя. Разработчики как бы намекают нам, что все это добро предназначено именно для вызова функций из DLL. В принципе, так оно и есть. Трудно представить, для чего еще в AutoIt можно приспособить структуры. Да, конечно можно в них хранить какие-нибудь данные, но это крайне не рекомендуется (со слов разработчиков), лучше для этой цели использовать массивы. Хотя, в некоторых случаях структуры могут быть полезны для преобразования данных из одного типа в другой, но эта тема для отдельного разговора. В заключении хочу привести небольшой пример, наглядно демонстрирующий все то, о чем я здесь написал.

Попробуем определить, сколько в данный момент присутствует физической памяти в системе, и сколько из нее доступно (свободно) приложениям. Да, конечно мы можем получить эту информацию, воспользовавшись AutoIt функцией MemGetStats(), и это правильно, но поскольку мы здесь говорим о структурах, то давайте ради закрепления материала используем для этого Windows API. Итак, покопавшись в MSDN, узнаем, что интересующую нас информацию о состоянии памяти в системе можно получить с помощью функции GlobalMemoryStatusEx(). Прочитав очень внимательно описание к этой функции, становится понятно, что перед тем, как вызвать GlobalMemoryStatusEx(), необходимо создать структуру MEMORYSTATUSEX, в которую собствено и будет помещена вся необходимая информация.

typedef struct _MEMORYSTATUSEX {
  DWORD dwLength;
  DWORD dwMemoryLoad;
  DWORDLONG ullTotalPhys;
  DWORDLONG ullAvailPhys;
  DWORDLONG ullTotalPageFile;
  DWORDLONG ullAvailPageFile;
  DWORDLONG ullTotalVirtual;
  DWORDLONG ullAvailVirtual;
  DWORDLONG ullAvailExtendedVirtual;
} MEMORYSTATUSEX, *LPMEMORYSTATUSEX;

Другими словами, мы должны создать требуемую этой функцией пустую структуру и только затем вызвать GlobalMemoryStatusEx(), передав ей в качестве параметра адрес структуры. После чего можно смело читать интересующие нас данные из MEMORYSTATUSEX (см. поля "ullTotalPhys" и "ullAvailPhys"). Почему GlobalMemoryStatusEx() сама не может создать необходимую ей структуру? Здесь ничего страшного нет. Почти все API функции работают по такому принципу, т.е. не выделяют и не освобождают никакой памяти, и всю эту работу перекладывают на вас. И это правильно. И последнее замечание. В AutoIt поддерживаются далеко не все типы данных, которые встречаются в MSDN, но в боьшинстве случаев того, что есть, вполне достаточно. В данном случае, в описании структуры MEMORYSTATUSEX присутствует тип DWORDLONG, которого нет в AutoIt. Но это не проблема, DWORDLONG представляет собой 64-битное или 8-байтное беззнаковое число с диапазоном возможных значений от 0 до 18446744073709551615. Поэтому смело меняем этот тип на тот, который доступен в AutoIt, т.е. на UINT64. В AutoIt нет никакой разницы между этими типами. Ниже представлен код для вызова функции GlobalMemoryStatusEx() с небольшими комментариями.

Код: AutoIt [Выделить]
; Создаем структуру MEMORYSTATUSEX
; http://msdn.microsoft.com/en-us/library/aa366770%28VS.85%29.aspx
$tMem = DllStructCreate( _
        "dword  dwLength;" & _
        "dword  dwMemoryLoad;" & _
        "uint64 ullTotalPhys;" & _
        "uint64 ullAvailPhys;" & _
        "uint64 ullTotalPageFile;" & _
        "uint64 ullAvailPageFile;" & _
        "uint64 ullTotalVirtual;" & _
        "uint64 ullAvailVirtual;" & _
        "uint64 ullAvailExtendedVirtual;")

; До вызова Функции GlobalMemoryStatusEx() необходимо в поле "dwLength" записать размер структуры в байтах

; Получаем размер структуры $tMem
$iSize = DllStructGetSize($tMem)

; Записываем в поле "dwLength" значение из $iSize (размер структуры)
DllStructSetData($tMem, "dwLength", $iSize)

; Получаем адрес структуры $tMem
$pMem = DllStructGetPtr($tMem)

; Вызываем API функцию GlobalMemoryStatusEx(), которая заполнит необходимые поля в структуре $tMem
; http://msdn.microsoft.com/en-us/library/aa366589%28VS.85%29.aspx
DllCall("kernel32.dll", "bool", "GlobalMemoryStatusEx", "ptr", $pMem)

; Читаем значения из полей "ullTotalPhys" и "ullAvailPhys" (количество установленной и доступной памяти соответственно, в байтах)
$iTotalPhys = DllStructGetData($tMem, "ullTotalPhys")
$iAvailPhys = DllStructGetData($tMem, "ullAvailPhys")

; Освобождаем занимаемую структурой $tMem память (можно пропустить, т.к. программа на этом заканчивается)
$tMem = 0

; Выводим результат в консоль
ConsoleWrite("Всего памяти: " & StringFormat("%.2f МБ", $iTotalPhys / 1024 / 1024) & @CR)
ConsoleWrite("Доступно:     " & StringFormat("%.2f МБ", $iAvailPhys / 1024 / 1024) & @CR)


Всего памяти: 2814.54 МБ
Доступно:     2005.82 МБ



API и ресурсы.

В процессе...

Содержание
« Последнее редактирование: Июнь 17, 2017, 18:01:49 от Garrett »

Оффлайн Yashied [?]

  • AutoIt MVP
  • Глобальный модератор
  • *
  • Сообщений: 5379

  • Автор темы
  • Репутация: 2693
  • Пол: Мужской
    • Награды
  • Версия AutoIt: 3.3.x.x
Re: FAQ по использованию ресурсов в AutoIt
« Ответ #12, Отправлен: Октябрь 16, 2010, 04:56:35 »

Ресурсы AutoIt.

В процессе...

Содержание
« Последнее редактирование: Октябрь 17, 2010, 14:27:19 от Yashied »

Оффлайн Yashied [?]

  • AutoIt MVP
  • Глобальный модератор
  • *
  • Сообщений: 5379

  • Автор темы
  • Репутация: 2693
  • Пол: Мужской
    • Награды
  • Версия AutoIt: 3.3.x.x
Re: FAQ по использованию ресурсов в AutoIt
« Ответ #13, Отправлен: Октябрь 17, 2010, 14:27:34 »

Итог.

В процессе...

Содержание

Русское сообщество AutoIt

Re: FAQ по использованию ресурсов в AutoIt
« Ответ #13 Отправлен: Октябрь 17, 2010, 14:27:34 »

 

Похожие темы

  Тема / Автор Ответов Последний ответ
0 Ответов
6663 Просмотров
Последний ответ Август 27, 2009, 00:07:56
от CreatoR
6 Ответов
5594 Просмотров
Последний ответ Сентябрь 27, 2010, 11:06:20
от Pontik
83 Ответов
40715 Просмотров
Последний ответ Февраль 25, 2013, 10:41:42
от Astel064
28 Ответов
82940 Просмотров
Последний ответ Сентябрь 21, 2015, 02:38:28
от CreatoR
1 Ответов
2158 Просмотров
Последний ответ Октябрь 07, 2014, 08:26:01
от gora
16 Ответов
5721 Просмотров
Последний ответ Декабрь 12, 2013, 13:53:21
от Siroga00VII
42 Ответов
12459 Просмотров
Последний ответ Январь 12, 2012, 23:37:06
от Viktor1703
0 Ответов
1777 Просмотров
Последний ответ Май 28, 2012, 22:33:36
от CreatoR
1 Ответов
1541 Просмотров
Последний ответ Январь 19, 2015, 02:30:11
от joiner
6 Ответов
1470 Просмотров
Последний ответ Июль 30, 2015, 03:53:43
от joiner