Главная » Delphi » Как работать с ресурсами исполняемого файла

0

Мало кто из начинающих программистов пытается лезть в файл ресурсов приложения (res), который автоматически создастся Delphi. И действительно, опыт показывает, что попытка в него вмешаться из самой Delphi в лучшем случае оканчивается ничем. Это ставит "ламера" в тупик, после чего он надолго забывает о самой возможности управлять ресурсами приложения. И, хотя мы уже успешно редактировали этот файл в главах, посвященных приложениям без формы, все же гораздо удобнее и безопаснее свои ресурсы располагать в отдельном файле, тем более что в данной главе речь пойдет не только о тех ресурсах, которые можно создать через Image Editor.

Ресурсы в Windows универсальны, в них можно даже использовать фрагменты выполняемого кода, Общая методика работы с ресурсами в Windows такая: через редактор ресурсов или вручную по определенным правилам создается текстовый файл описания ресурсов (обычно он имеет расширение гс). Затем с помощью одной из утилит компиляции (см. даме) этот текстовый файл превращают в бинарный файл ресурсов (res). Этот файл, как вы уже знаете, подключают ("прилинковывают") к исполняемому файлу. Все эти действия одинаковы для любой среды программирования, в том числе и ассемблер. Для главного ресурсного файла программы компилятор Delphi делает это все автоматически, и именно поэтому так трепетно относится к вмешательству в него.

Чтобы было понятно, с чем мы тут имеем дело, я здесь изменю своему правилу не повторять то, что всегда можно посмотреть в справке, и приведу полный список всех видов ресурсов, которые поддерживаются Windows:

RT_ACCELERATOR Accelerator table RT_ANICURSOR Animated cursor RT_ANIICON Animated icon RT_BITMAP Bitmap resource

RT_CURSOR Hardware-dependent cursor resource

RT_DIALOG Dialog box

RT_FONT Font resource

RT_FONTDIR Font directory resource

RT_GROUP_CURSOR Hardware-independent cursor resource RT_GROUP_ICON Hardware-independent icon resource RT_ICON Hardware-dependent icon resource RT_MENU Menu resource RT_MESSAGETABLE Message-table entry RT_RCDATA Application-defined resource (raw data) RT_STRING String-table entry RT_VERSION Version resource

У читателей, склонных к тому, чтобы делать все как можно проще, наверняка возник вопрос: а зачем вообще нужен столь сложный механизм с какой-го промежуточной компиляцией, если можно ресурсы просто расположить в отдельных файлах соответствующего формата? Например, строки явно удобнее редактировать, если они расположены в обычном тестовом файле, разве INI-файлы, например, нельзя использовать для той же цели? Конечно, можно. Для того чтобы проиллюстрировать основное назначение ресурсов исполняемого файла, я приведу такой пример. Известно, что HTML-файл не является контейнером (на эту тему см. главу 76), поэтому все, что выходит за рамки обычного текста, должно храниться где-то отдельно. Это, с одной стороны, дает большую гибкость в манипуляции элементами оформления, а с другой— попробуйте скачать какую-нибудь красиво оформленную интернет-страницу к себе на диск, и внимательно исследуйте папку, которая при этом образуется (Opera, например, еще и не делает отдельной папки, а все сваливает в кучу в текущей). Иногда там может быть несколько десятков мелких изображений в разных форматах, представляющих элементы оформления страницы, причем эти элементы обычно с небольшими вариациями повторяются для однотипных страниц. Это и есть то, что в ЕХЕ-файлах Windows хранится в ресурсах — представляете, что было бы, если бы мы были вынуждены всю эту мелочевку— иконки, меню, курсоры, диалоги — таскать за собой даже пусть не в готовой программе, а на стадии проекта? С одними именами файлов вышла бы такая путаница, что… в общем, понятно.

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

В комплект Delphi входит специальный редактор Resource Workshop, который одинаков для всех сред программирования от Borland, его вполне можно использовать, но мы здесь пойдем другим путем. Мы исключим из круга решаемых задач вопросы редактирования ресурсов в имеющихся DLL- или ЕХЕ-файлах— на этот предмет есть по меньшей мере полсотни специализированных программ самой разной степени "фирменности": от наколеночных произведений безымянных умельцев до упомянутого Workshop. Мы ведь создаем свои программы, а не правим чужие, не так ли? Однако иметь одну из таких программ совсем не лишнее — часто хочется сделать иконку или картинку на кнопке по образцу имеющихся, а с помощью таких утилит можно извлечь их из файла чужой программы. Скажу по секрету, что часть иконок в этой книге так и сделана (правда, я никогда не использую чужие иконки "as is", всегда стараюсь придать им оригинальные черты — в результате они, за редкими исключениями, меняются до неузнаваемости). Замечу назло ревнителям копирайта, что другая (большая) часть заимствована из коллекции иконок, приобретенной более десяти лет назад совершенно официально в комплекте с очень примитивным их редактором под Windows 3.0— пусть никто меня не сможет обвинить в плагиате!

Второе, что мы по мере возможности исключим из рассмотрения — использование ресурсов для локализации приложений. Данный вопрос мы опустим по той причине, что на этот предмет есть множество различного уровня пособий, и сама Delphi включает в себя довольно удобный механизм создания локализованных версий (механизм плагинов, типа используемых в Firefox скриптовых расширений, в принципе много удобнее, но зато приходится все эти скрипты-плагины таскать отдельно). Переписывать все, что уже изложено по этому поводу другими, не имеет смысла — данная книга посвящена вопросам, на которые трудно найти ответы. К их числу относится, например, вопрос — а как включить информацию о версии, разработчике, фирме и т. п. в исполняемый файл, если он не имеет формы, и потому соответствующие пункты меню Project недоступны? Но начнем мы с другого — па простом примере покажем, как работать с ресурсами вообще.

Наглядная агитация

Как можно, например, записать строку в ресурсы простейшим способом и использовать потом ее в качестве константы? Для этого в Delphi есть специальное зарезервированное слово: resourcestring. Создадим новый проект, назовем его Resproba (папка GlavallM), поставим на форму компоненты Label (сразу очистим его заголовок и зададим полужирный шрифт 14 кегля) и Timer, добавим к проекту еще один модуль без формы (File | New | Unit) под названием Unitres. Содержание этого модуля будет очень коротким — листинг ПЛ.

| Листинг 11.1. Модуль Unitres

unit Unitres;

interface

re sources tring

Stringl = ‘Пролетарии'; String2 = ‘всех стран'; String3 = ‘соединяйтесь!';

implementation

end.

Теперь добавим в основном модуле переменную под названием ttime типа byte, которая будет отсчитывать время, добавим после implementation строку uses unitres; и напишем следующий обработчик события onTimer:

procedure TForml.TimeriTimer(Sender: TObject); begin

Labell.Caption:=”;

Label1.Font.Color:=clBlack;

case (ttime and 3) of

1: Labell.Caption:=Stringl;

2: Label1.Caption:=String2;

3: begin Labell.Font.Color:=clRed;

Label1.Caption:=String3; end;

end;

ttime:=ttime+l; end;

Запустив приложение, мы увидим посекундно возникающие строки знаменитого коммунистического лозунга, причем последняя строка "соединяйтесь!" будет выделяться красным. Между последовательными демонстрациями будет пауза. Попутно заметим, что здесь проиллюстрирован один из простейших способов организации программного двоичного счетчика— в данном случае до 4. Операция ttime and з обнуляет все биты переменной ttime, кроме двух младших, а они могут принимать как раз четыре состояния. Так можно легко соорудить счетчик на любое число, кратное степени двойки — а вот для промежуточных чисел придется потрудиться. Я предлагаю читателю самостоятельно поразмыслить над вопросом, как можно организовать счетчик до 3, если мы хотим в данном примере исключить паузу.

Как видим, использование ресурсов в данном варианте довольно бессмысленное занятие: с таким же успехом можно просто задать строковые константы в разделе объявлений основного модуля. Хотя и этот способ может для чего-нибудь пригодиться, но уже другие виды ресурсов так присоединять будет затруднительно. Подумаем, что наглядная агитация гораздо лучше действует, если сопровождается изображениями, и попробуем добавить к нашему лозунгу картинки. В папке с проектом размещено несколько старых агитационных плакатов с сайта plakaty.ru, адаптированных под наши нужды и сохраненных в формате BMP под названиями Imagei, Image2 и т. п. Как загрузить эти картинки в ресурсы исполняемого файла?

Для этого сначала создадим файл описания ресурсов— обычный текстовый файл, который будет содержать такие строки:

Imagei BITMAP "Imagel.bmp" Image2 BITMAP "Image2.bmp" Image3 BITMAP "Image3.bmp" Image4 BITMAP "Image4.bmp"

STRINGTABLE {

1, "Наглядная агитация" i

Файл назовем Image.rc. В последнем пункте мы ввели в ресурс еще одну строку дополнительно к имеющимся — для иллюстрации, как это делается в обход довольно неудобного механизма resourcestring в Delphi. Использовать мы ее будем для заставки при запуске. Обратите внимание, что все строковые переменные должны стоять в парных кавычках — так, как это принято в языке С. Признаем, что механизм и здесь не очень совершенен — в полях структуры stringtable нельзя использовать идентификаторы строк, а только их номера (несколько усложнив пример, можно присвоить идентификаторы номерам, но не самим строкам!). Заметим еще две существенные вещи: во- первых, в качестве операторных скобок в структуре strimgtable можно использовать и фигурные скобки в стиле С (как в этом примере), и конструкцию begin.. .end в стиле Pascal, что обусловлено универсальным характером утилиты компиляции, о которой сейчас пойдет речь (она едина для всех сред программирования Borland). Во-вторых, в строку можно вводить произвольные символы, предваряя их номер знаком "обратный слеш". Поэтому если нужно включить сам "обратный слеш" (дисковые имена файлов), то он должен повторяться дважды.

Теперь этот файл надо скомпилировать в ресурс с помощью утилиты Ьгсс32 (Borland Resource Command line Compiler). Для удобства создадим раз и навсегда ВАТ-файл для компиляции ресурсов под названием rescomp.bat со следующим содержимым:

"C:\Program Files\Borland\Delphi7\Bin\brcc32.exe" %1

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

rescomp.bat Image.гс

В результате компиляции получим файл Image.res, который будет включать в себя все четыре картинки и строку. Осталось присоединить его к нашему проекту, загрузить картинки и строку из ресурсов и выводить их в нужное время. Для этого объявим переменную Bmpimg:TbitMap, добавим после isplementation CTpOKy ($r Image.res) И Напишем два обработчика ДЛЯ СОЗДАНИЯ BitMap при открытии и закрытии приложения, а также для загрузки заставки:

prooedure TForml.FormCreate(Sender: TObject);

var Buffer: arraytO..255] of Char; begin

Bmplmg:=TBitMap.Create;

Bmplmg.LoadFromResourceName(hlnstance,1Imagel1) ; {загрузка картинки заставки) LoadString(hlnstance,1,Buffer,255); {загрузка строки номер 1) Labell.Caption:=StrPas(Buffer); (вывод заголовка заставки/ end;

procedure TForml.FormDestroy(Sender: TObject); begin

Bmplmg.Destroy; end;

procedure TForml.FormPaint(Sender: TObject); begin

Canvas.Draw(80,100,Bnqplmg); {вывод картинки заставки} end;

Как видите, если мы не используем механизм Delphi, а формируем строку в ресурсах сами, процедура ее загрузки усложняется, причем следует использовать в качестве промежуточного буфера именно байтовый массив, а не тип Pchar — иначе могут возникать различные сложности (см. главы 14 и 21).

Обратите также внимание на то, что загружаем начальную картинку мы по событию oncreate, а прорисовываем — по событию onPaint. Если мы будем пытаться прорисовать BitMap при создании формы, то он не покажется. Можно и все делать по событию onPaint, но тогда у нас каждый раз при фокусировке формы будет выполняться загрузка изображения и заставочной строки. Теперь осталось переписать нашу процедуру по таймеру (чтобы картинки можно было бы лучше рассмотреть, увеличьте время срабатывания таймера до 3 сек):

procedure TForml.TimerlTimer(Sender: TObject); begin

Labell.Capt ion: = " ; Labell .Font.Color:=clBlac)c; case (ttime and 3) of 1: begin

Labell.Caption:=Stringl;

Canvas.FillRect(Canvas.ClipRect); {очистка формы} Bmplmg.LoadFromResourceName(hlnstance,4mage2′); Forml.Canvas.Draw(80,100, Bmplmg); end; 2: begin

Label1.Caption:=String2;

Canvas.FillRect(Canvas.ClipRect); (очистка формы) Bmplmg.LoadFromResourceName(hlnstance,’Image3′); Forml.Canvas.Draw(80,100,Bmplmg); end; 3: begin

Labell.Font.Color:=cIRed; Labell.Caption:=String3;

Canvas.FillRect(Canvas.ClipRect); {очистка формы} Bmplmg.LoadFromResourceName(hlnstance,’Imaged’); Forml.Canvas.Draw(80,100,Bmplmg); end; end;

ttime:=ttime+l; end;

Если убрать у формы заголовок (Borderstyle = bsNone), компонент Lab-Л 1 расположить ПО центру, И установить его СВОЙСТВО Alignment В taCenter (чтобы оно сработало, надо также свойство Autosize установить в False), у нас получится хотя и не совсем доведенная до ума, но вполне приличная художественная инсталляция в стиле соц-арт. В [3] вы можете прочесть, как в Windows ХР сделать саму форму при этом прозрачной, что значительно увеличит эффект (см. об этом также главу 12). Причем неопытный пользователь даже и не сообразит сразу, как избавиться от окна, которое не имеет ни одной кнопки.

Обратим, кстати, внимание на объем исполняемого файла программы — за счет того, что мы теперь не обязаны таскать все картинки отдельно, файл получился почти 2 Мбайта. Но если вы оставите картинки в отдельных файлах, или загрузите их в DLL, как чаще поступают, то в общем объеме вы ничего не выиграете, а вероятность что-то потерять увеличится. Другое дело, что в полной мере все преимущества пользования ресурсами, как легко заменяемыми модулями приложения, проявляются, если загружать их не в исполняемый файл, а именно в отдельную DLL, тогда можно легко заменять картинки и строки, не перекомпилируя саму программу. Именно так и поступают для многоязычных приложений— использование ресурсных файлов практически единственный удобный способ обеспечить беспроблемное размножение одной программы на разных языках, хотя и в самом исполняемом файле, как мы увидим позже, этих языков может быть задано много. Но мы не будем на всем этом специально останавливаться — читатель, без сомнения, теперь и сам может справиться с подобными задачами.

Источник: Ревнч Ю. В.  Нестандартные приемы программирования на Delphi. — СПб.: БХВ-Петербург, 2005. — 560 е.: ил.

По теме:

  • Комментарии