23 декабря 2020

Работа с ресурсами приложения

    Ресурсы приложения Windows – это данные, которые встроены внутрь EXE, DLL, CPL и MUI файлов. Общеизвестным примером такого ресурса является иконка приложения. Windows API предопределяет большое количество типов ресурсов приложений. Кроме них приложения могут определять свои собственные типы ресурсов. Хотя, как я понимаю, они все будут равнозначны RCDATA. Давайте добавим приложению как ресурсы логотип и пользовательское соглашение, а при его старте загрузим их на форму.
Form with resources
    Добавление ресурсов в приложение начинается с создания текстового файла, который их описывает:
License TEXT "license.txt"
123 RCDATA "BlackCat.jpg"
Назовем его "AddRes.rc". "RC" – это стандартное расширение для таких файлов. В файле описаны два ресурса. Первый называется "License", имеет нестандартный тип "TEXT" и ссылается на текстовый файл "license.txt". Второй имеет целочисленный идентификатор "123", предопределенный тип "RCDATA" и ссылается на файл JPEG-формата "BlackCat.jpg".
    Теперь нам нужно скомпилировать наш RC-файл. Для этого у RAD Studio есть три компилятора командной строки:
  1. Borland Resource Compiler (BRCC32.EXE):
    Borland Resource Compiler - BRCC32.EXE
  2. Microsoft SDK Resource Compiler (RC.EXE).
    Microsoft SDK Resource Compiler - RC.EXE
  3. CodeGear Resource Compiler/Binder (CGRC.EXE).
    CodeGear Resource Compiler/Binder - CGRC.EXE
    В реальности CGRC.EXE – это не компилятор. Это программа, которая переводит параметры BRCC32.EXE в параметры RC.EXE и запускает с ними RC.EXE.
Все три варианта создадут нам файл с расширением "RES", который нам нужно включить в проект с помощью директивы "$R":
program GetResource;

uses
  Vcl.Forms,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.res}
{$R AddRes.res}

begin
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.
Но есть более простой и надежный способ – это добавить RC-файл в проект:
program GetResource;

uses
  Vcl.Forms,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.res}
{$R 'AddRes.res' 'AddRes.rc'}

begin
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.
В этом случае нам не нужно вызывать компилятор ресурсов каждый раз при их изменении, а достаточно просто перекомпилировать проект. В параметрах проекта можно выбрать, какой из компиляторов RAD Studio будет использовать для этого (BRCC32.EXE или CGRC.EXE/RC.EXE):
Project Options - Building - Resource Compiler
    Доступ к ресурсам приложения можно получить через функции Windows API. Мы же воспользуемся реализованным в Delphi специальным потоком для чтения ресурсов приложения – TResourceStream. Он позволяет получить доступ к ресурсам по имени (ResName):
constructor TResourceStream.Create(Instance: THandle; const ResName: string;  ResType: PChar);
или по целочисленному идентификатору (ResID):
constructor TResourceStream.CreateFromID(Instance: THandle; ResID: Integer;  ResType: PChar);
Параметры:
  • Instance – это дескриптор приложения или DLL содержащие ресурс.
  • ResType – это строка, определяющая тип ресурса. Предопределенные типы в Delphi описаны в модуле WinApi.Windows.
    Создадим в Delphi новый VCL-проект. Добавим на форму два контрола "imgLogo: TImage" - для загрузки изображения и "mLicense: TMemo" - для загрузки текста. В событие FormCreate добавим загрузку ресурсов на форму:
unit Unit1;

interface

uses
  System.Classes, System.SysUtils, System.Types, Winapi.Windows, Vcl.Forms,
  Vcl.Graphics, Vcl.Controls, Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.Imaging.jpeg;

type
  TForm1 = class(TForm)
    imgLogo: TImage;
    mLicense: TMemo;
    procedure FormCreate(Sender: TObject);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
var
  rs: TResourceStream;
begin
  // загрузка по целочисленному идентификатору
  rs := TResourceStream.CreateFromID(HInstance, 123, RT_RCDATA);
  try
    var jpg: TJPEGImage := TJPEGImage.Create;
    try
      jpg.LoadFromStream(rs);
      imgLogo.Picture.Assign(jpg);
    finally
      jpg.Free
    end;
  finally
    rs.Free
  end;

  // загрузка по имени
  rs := TResourceStream.Create(HInstance, 'License', 'TEXT');
  try
    var ss: TStringStream := TStringStream.Create;
    try
      ss.LoadFromStream(rs);
      mLicense.Text := ss.DataString;
    finally
      ss.Free;
    end;
  finally
    rs.Free
  end;

  // загрузка не существующего ресурса
  rs := TResourceStream.Create(HInstance, 'freedom', RT_ICON);
  try
  finally
    rs.Free
  end;

end.
При запуске приложения мы получим сообщение об ошибке:
resource freedom not found
Оно информирует, нас о том, что ресурс "freedom" не найден. С этим легко можно столкнуться, если загружать ресурсы из другого модуля неправильной версии. Что бы избежать подобной досадной неожиданности можно проверить наличие ресурса с помощью функции FindResource:
if FindResource(hInstance, 'freedom', RT_ICON) <> 0 then
  begin
    rs := TResourceStream.Create(HInstance, 'freedom', RT_ICON);
    try
    finally
      rs.Free
    end;
end;
или заключить загрузку ресурса в try-except (это дополнительно поможет избежать ошибки при не правильном формате ресурса):
try
  rs := TResourceStream.Create(HInstance, 'freedom', RT_ICON);
  try
  finally
    rs.Free
  end;
except
end;
Примечание. К ресурсу с целочисленным идентификатором тоже можно обратиться по имени. Для этого перед его идентификатором нужно поставить символ "#":
if FindResource(hInstance, '#123', RT_RCDATA) <> 0 then
  begin
    rs := TResourceStream.Create(HInstance, '#123', RT_RCDATA);

Комментариев нет:

Отправка комментария