Ресурсы в программах на WIN32API.

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

Ресурсы - двоичные данные, которые можно добавлять в исполняемый файл программы. Помимо стандартных ресурсов можно определять собственные - заказные ресурсы. Каждый ресурс идентифицируется либо строкой имени, либо числом от 1 до 65535. Во втором случае число необходимо пропустить через макрос MAKEINTRESOURCE, конвентирующий целое число в тип ресурса, совместимый с функциями управления ресурсами. Таким образом некоторые объекты можно создать заранее и затем загружать их из исполняемого файла или создавать их динамически во время выполнения программы. К стандартным ресурсам относятся: таблица акселераторов, изображение, курсор, метафайл, шрифт, иконка, меню, окно диалога, таблица строк, информация о версии программы.

Размещение ресурсов в .EXE файле имеет два основных преимущества:

- Ресурс загружается более быстро, чем если бы он грузился с диска.
- Нет необходимости переживать, что пользователь случайно удалит файл ресурса и программа будет отображаться не так, как бы хотелось автору.

Для начала нам нужно создать файл ресурса. По умолчанию расширение для этих файлов - .RES. Файл ресурса может быть создан с помощью Image Editor, который входит в поставку с Delphi. Подробнее читайте файл справки данной программы. Но мы пойдем другим путем...

Ресурс Bitmap.

Чтобы действительно использовать ресурс, Вы должны сделать несколько вызовов Windows API. BMP-изображения, курсоры и иконки загруженные в файлы RES могут быть извлечены использованием функций LoadBitmap, LoadCursor и LoadIcon соответственно.

Создайте текстовый файл, например, testres.rc и поместите в него следующее:

 RIS1 BITMAP "Pic1.bmp"

В этом примере:

RIS1 - Название ресурса.
BITMAP - Тип ресурса, или вернее папка, в которой он будет помещен.
Pic1.bmp - Файл изображения, или путь с именем файла.

После сохранения данного текста необходимо скомпилировать ресурс, используя программу Borland Resource Compiler (консольное приложение brcc32.exe), которая входит в поставку с Delphi.

 brcc32.exe testres.rc

Будет создан полноценный файл ресурса с расширением .RES. Далее остается подключить его в программу директивой компилятора - {$R testres.res}.

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

//определяем константу Static
const
 ID_STATIC = 5

//определяем переменную
var
 hBMP: hBitmap;

//где-то в программе загружаем изображение из ресурсов...
hBMP := LoadImage(hInstance, 'RIS1', IMAGE_BITMAP, 0, 0, LR_DEFAULTCOLOR);
//...и помещаем изображение в Static
SendDlgItemMessage(hWin, ID_STATIC, STM_SETIMAGE, IMAGE_BITMAP, hBMP);

При этом орган управления Static должен содержать стиль SS_BITMAP.

Ресурс Icon.

Что же касается иконок, то работа с ними очень похожа на работу с BMP-изображениями. Отличие заключается в папке, в которую компилируются иконки:

 ZNACHEK1 ICON "Pic1.ico"

В этом примере:

ZNACHEK1 - Название ресурса.
ICON - Тип ресурса.
Pic1.ico - Файл иконки.

Наверное, нет смысла напоминать о том, что нужно скомпилировать ресурс, подключить к программе и…

//определяем константу Static
const
 ID_STATIC = 5

//определяем переменную
var
 hZnachek: hIco;

//где-то в программе загружаем значек из ресурсов...
hZnachek := LoadIcon(hInstance, 'ZNACHEK');
//...и помещаем его в Static
SendDlgItemMessage(hWin, ID_STATIC, STM_SETIMAGE, IMAGE_ICON, hZnachek);

При этом орган управления Static должен содержать стиль SS_ICON.

Ресурс String.

Довольно интересна работа со строковыми ресурсами. На первый взгляд здесь все просто: загружаешь готовый текст и наслаждаешся резуальтатом. Но есть и подводные камни. Например, что делать, если нужно вставить в готовый текст какие-то определенные данные, например числа или подставить другой текст? И с этим можно легко справится. В файле ресурса пишем:

STRINGTABLE
LANGUAGE LANG_RUSSIAN, 0x1
{
6, "Здесь будет %s текст."
7, "Я родился %d числа, %d года."
8, "А этот текст\nпостроен с переносами\nи \"кавычками\"!"
}
STRINGTABLE - Секция строковых ресурсов.
LANGUAGE - Здесь указывается язык строковых ресурсов. В данном случае русский.
6, 7, 8 - Идентификаторы, по которым вызываються определенные строки.

Директива %s вставляет текст, а %d - числа. Директива \n перемещает текст на следующую строку, а \" позволяем поместить кавычки в ресурсный файл. Как это работает, будет рассказано немного позже.

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

function LoadStr(ID: DWORD): String;
var
 buffer: array[0..1023] of Char;
begin
 LoadString(hInstance, ID, buffer, SizeOf(buffer));
 result := String(buffer);
end;

Далее в программе…

//определяем константу органа управления Static
const
 ID_STATIC = 5

//определяем переменную
var
 Str: String;

//результатом данного примера будет вывод в компонент Static
//6 строкого ресурса с добавлением слова "мой":
//"Здесь будет мой текст."
Str := 'мой';
SendMessage(GetDlgItem(hWin, ID_STATIC), WM_SETTEXT, 0, Integer(PChar(Format(LoadStr(6), [Str]))));

//результатом данного примера будет вывод в компонент Static
//7 строкого ресурса с добавлением чисел 8 и 1975:
//"Я родился 8 числа, 1975 года."
SendMessage(GetDlgItem(hWin, ID_STATIC), WM_SETTEXT, 0, Integer(PChar(Format(LoadStr(7), [8, 1975]))));

//результатом данного примера будет вывод в компонент Static
//8 строкого ресурса с переносом и текстом в кавычках:
//"А этот текст
//построен с переносами
//и "кавычками"!"
SendMessage(GetDlgItem(hWin, ID_STATIC), WM_SETTEXT, 0, Integer(PChar(LoadStr(8)));

Лицензия из ресурсов.

При установке программы всегда приходится соглашатся с надоедливой лицензией. Следующий пример покажет, как можно загрузить текст из ресурсов в орган управления Edit:

 3 BINRES "License.txt"
3 - Название ресурса.
BINRES - Имя секции, в которой будет расположен ресурс. Необязательно называть секцию именно так. Вы можете назвать ее как угодно.
License.txt - Файл с текстом лицензионного соглашения.

Для загрузки текста в Edit была написана следующая функция:

function ResToString(Instance: hInst; ResName, ResType: PChar): string;
var
 ResSize, HG, HI: Cardinal;
 pc: PChar;
begin
 Result := '';
 HI := FindResource(Instance, ResName, ResType);
  if HI <> 0 then begin
   HG := LoadResource(Instance, HI);
    if HG <> 0 then
     try
      ResSize := SizeOfResource(Instance, HI);
      pc := LockResource(HG);
      SetString(result, pc, ResSize);
   except;
  end;
 end;
end;

Очень важно присвоить органу управления Edit нужные стили. Иначе текст не будет отображен так как нужно. Необходимые стили: ES_MULTILINE, WS_VSCROLL, WS_HSCROLL.

//определяем константу органа управления Edit
const
 ID_EDIT = 32;

//Загружаем текст из ресурсов в Edit
SetDlgItemText(hWin, ID_EDIT, @ResToString(0, MAKEINTRESOURCE(3), 'BINRES')[1]);

Ресурс WAV.

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

 ZVUK WAVE "phone.wav"
ZVUK - Имя ресурса.
WAVE - Секция ресурса.
phone.wav - Название файла.

Для простого проигрывания WAV-ресурсов была написана следующая процедура:

//Вопроизведение WAVE-файла из ресурса
procedure PlayWaveFromResource(ResName, SectionName: String);
var
 FindHandle, ResHandle: THandle;
 ResPtr: Pointer;
begin
 FindHandle := FindResource(HInstance, PChar(ResName), PChar(SectionName));
  if FindHandle <> 0 then begin
   ResHandle := LoadResource(HInstance, FindHandle);
    if ResHandle <> 0 then begin ResPtr := LockResource(ResHandle);
    if ResPtr <> nil then sndPlaySound(PChar(ResPtr), SND_ASYNC or SND_MEMORY);
   UnlockResource(ResHandle);
  end;
  FreeResource(FindHandle);
 end;
end;

Теперь осталось подключить модуль MMSystem в uses программы и проиграть звук:

 PlayWaveFromResource('ZVUK', 'WAVE');

Для остановки воспроизведения звука можно воспользоватся командой:

 sndPlaySound(nil, SND_ASYNC or SND_MEMORY);

Ресурс Menu.

Ни один текстовый редактор не обходится без меню. А в tray-области меню становится просто необходимым:

300 MENU
LANGUAGE LANG_RUSSIAN, 0x0
{
 POPUP "Context"
 {
   MENUITEM "Отобразить сообщение 1", 200
   MENUITEM "Отобразить сообщение 2", 201
   MENUITEM SEPARATOR
   MENUITEM "Закрыть программу", 202
 }
}
300 - Идентификатор меню.
LANGUAGE - Язык меню.
POPUP - Тип меню: всплывающее.
MENUITEM - Пункты меню.
SEPARATOR - Горизонтальная разделяющая полоса.
200, 201, 202 - Идентификаторы пунктов меню.
//определяем переменную меню
var
 Menu: HMenu;

//Загружаем меню
Menu := LoadMenu(hInstance, MAKEINTRESOURCE(300));
//Наводим курсор
GetCursorPos(CPos);
//Устанавливаем положение
TrackPopupMenu(GetSubMenu(Menu, 0), TPM_LeftAlign or TPM_TopAlign, CPos.X, CPos.Y, 10, hWin, nil);
//Уничтожаем
DestroyMenu(Menu);

Сохранения ресурса на диск.

Eсли вам нужно будет извлечь какой-нибудь ресурс из программы для последующей работы, то сделать это можно так:

function ResourceToFile(lpName, lpType, FileName: PChar): BOOL;
var
 HResInfo: HWND;
 HGlobal: HWND;
 FMemory: Pointer;
 FSize, FHandle, nl: Integer;
begin
 HResInfo := FindResource(HInstance, lpName, lpType);
 HGlobal := LoadResource(HInstance, HResInfo);
 FMemory := LockResource(HGlobal);
 FSize := SizeOfResource(HInstance, HResInfo);
 FHandle := Integer(CreateFile(FileName, GENERIC_READ or GENERIC_WRITE,
                     0, nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0));
 Result := WriteFile(HWND(FHandle), FMemory^, FSize, Integer(nl), nil);
 CloseHandle(HWND(FHandle));
end;

Вот так выглядит метод применения данной функции (извлечение значка):

 ResourceToFile('ICON', 'C:\main.ico')

Хочется верить, что после публикации данного материала не будут больше возникать вопросы по поводу использования ресурсов в программах, написаных на WIN32API в DELPHI. Но кто знает, что уже завтра придумает человечество...

Автор - Владимир Дригалкин aka LENIN INC
Сайт - http://delphisources.ho.ua/