Delphi-int.ru: портал программистов

Вход Регистрация | Забыли пароль?

События

Сегодня:
Вопросы0    Ответы0    Мини-форумы0


Последние:
Вопрос09.08, 09:39 / #6696
Ответ29.03, 23:32 / #6682
Новости8 июля 2023


Сейчас онлайн:
На сайте — 22
На IRC-канале — 2

Ссылки

Формат файлов RES применительно к BITMAP-ресурсам

Автор: SOA

Написать данную статью автора побудило недостаточное количество информации в рунете и за его пределами для написания своего компилятора ресурсов типа BITMAP. Начнём!

RES-файл представляет собой файл, в котором записаны поверх друг друга заголовки ресурсов и соответственно сами данные ресурсов:

Header  (Заголовок)
DATA    (Данные)
Header  (Заголовок)
DATA    (Даные)

Заголовок из себя представляет некую структуру с полями:

typedef struct {
  DWORD DataSize;
  DWORD HeaderSize;
  DWORD TYPE;
  DWORD NAME;
  DWORD DataVersion;
  WORD  MemoryFlags;
  WORD  LanguageId;
  DWORD Version;
  DWORD Characteristics;
} RESOURCEHEADER;

Но эта структура лишь является эталонной моделью, как в сетях является эталонной моделью модель ISO/OSI. На практике же чаще используется менее ромоздкая конструкция:

typedef struct {
  DWORD DataSize;
  DWORD HeaderSize;
  DWORD TYPE;
  DWORD NAME;
} RESOURCEHEADER;

Разберемся, что значат поля в этой структуре.

DataSize — размер данных ресурса, т.е. данных, копируемых из файла bmp. Он всегда меньше реального размера файла на 14 бит ввиду того, что отбрасываются несколько полей заголовка файла bmp (да-да, у bmp файлов тоже есть заголовок, как впрочем и у всех остальных типов файлов).

HeaderSize — размер заголовка ресурса, т.е. размер, занимаемый всей структурой RESOURCEHEADER в байтах.
Эта структура разделена на две части: базу ResourceHeaderBase, значение которой всегда 32 и, по-видимому, отражает принадлежность ресурса к 32 битному приложению; вторая часть — ResourceHeaderOffset равна разнице между размером заголовка и базой.

TYPE — тип ресурса, в нашем случае это значение равно FFFF0200, где ключевым числом является 2 т.к. именно оно является числовым эквивалентом типа BITMAP.
Значения для других типов можно посмотреть здесь: http://msdn.microsoft.com/en-us/library/ms648009(VS.85).aspx
Записываем, конечно, в шестнадцатеричном виде.

NAME — официально это поле имеет тип DWORD. MS пишет, что на самом деле это поле является строкой с нулевым окончанием. На практике было выяснено, что в поле NAME записывается по одному коду символа в ASCII кодировке с отделением символов друг от друга нулевым битом, после чего по неизвестному автору алгоритму строка добивается нулевыми битами, после чего ставится ASCII код нуля.

Значение поля NAME

Также стоит заметить, что в начале заголовка ставится значение типа DWORD, равное нулю, и в конце заголовка ставится два значения типа DWORD, равные нулю.

Рассмотрим, как на практике пишется файл ресурса по смещениям:

Смещение Поле
4 ResourceHeaderBase
8 FFFF
12 FFFF
32 DataSize
36 ResourceHeaderOffset
40 TYPE
44 NAME

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

Ниже приведена программа, записывающая из-bmp файла ресурс. Однако у этой программы есть одно ограничение ввиду того, что, как я уже писал, я не знаю, каким образом высчитывается количество нулевых битов в поле NAME до ASCII значения нуля, поэтому размер имени ресурса не может превышать двух символов.

unit Unit1;
 
interface
 
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;
 
type
  TForm1 = class(TForm)
    Button1: TButton;
    OpenDialog1: TOpenDialog;
    Edit1: TEdit;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
 
type Header = Record
DataSize:DWORD;
HeaderSize:DWORD;
Base:DWORD;
RcType:DWORD;
NAME:String;
End;
 
const
HearedSizeBase = 4;
HeaderSizeOffset = 36;
DataSizeOffset = 32;
TypeOffset = 40;
NameOffset = 44;
 
var
  Form1: TForm1;
  MyRes,SrcFile: TFileStream;
  Heder:Header;
  NULL: array [0..43] of byte;
 
implementation
 
{$R *.dfm}
 
procedure TForm1.Button1Click(Sender: TObject);
var
i:Integer;
Buf: BYTE;
Count: LongInt;
FFFF:DWORD;
CHR:WORD;
 
begin
for i:=0 to 43 do
NULL[i]:=0;
i:=FileCreate('Resource.res');//Создаем сам файл ресурса
FileClose(i);
 
MyRes:=TFileStream.Create('Resource.res',fmOpenWrite);
If OpenDialog1.Execute=true then
SrcFile:=TFileStream.Create(OpenDialog1.FileName,fmOpenRead);
 
 Heder.NAME:=Edit1.Text;//Имя текущего файла для записи в ресурсы
 Heder.DataSize:=SrcFile.Size-14;//Размер ресурса (-14 это отбрасываемая часть заголовка файла bmp)
 Heder.RcType:=$0002FFFF; //ffff0200 тип ресурса
 
 //Записываем описание ресурса
 MyRes.Write(NULL,SizeOf(NULL));
 MyRes.Seek(8,soFromBeginning);
 FFFF:=$0000FFFF;
 MyRes.Write(FFFF,SizeOf(FFFF));//Не знаю почему но эти биты должны иметь именно такое значение
 MyRes.Write(FFFF,SizeOf(FFFF));
 MyRes.Seek(HearedSizeBase,soFromBeginning);
 Heder.Base:=32;
 MyRes.Write(Heder.Base,SizeOf(Heder.Base));//записываем базу заголовка
 MyRes.Seek(DataSizeOffset,soFromBeginning);
 MyRes.Write(Heder.DataSize,SizeOf(Heder.DataSize));//записываем размер данных
 
 MyRes.Seek(NameOffset-2,soFromBeginning);//Записываем имя ресурса
 For i:=0 to Length(Heder.Name) do
 Begin
  CHR:=Ord(Heder.Name[i]);
  MyRes.Write(CHR,SizeOf(CHR));
 End;
 
 buf:=0;                     //Выравнивание
 if Length(Heder.Name)=1 Then
 For i:=1 to 6 do
 MyRes.Write(buf,SizeOf(buf))
 Else
 Begin
 FFFF:=$00000000;
 For i:=1 to 2 do//расчет
 MyRes.Write(FFFF,SizeOf(FFFF));
 End;
 FFFF:=$00001030;
 MyRes.Write(FFFF,SizeOf(FFFF));
 
 i:=MyRes.Seek(0,soFromCurrent);
 MyRes.Seek(TypeOffset,soFromBeginning);
 MyRes.Write(Heder.RcType,SizeOf(Heder.RcType));//Записываем тип ресурса
 MyRes.Seek(i,soFromBeginning);
 
 FFFF:=$00000000;
 MyRes.Write(FFFF,SizeOf(FFFF));
 MyRes.Write(FFFF,SizeOf(FFFF));
 
 i:=MyRes.Seek(0,soFromCurrent);
 Heder.HeaderSize:=i-32;//Размер описания ресурса
 MyRes.Seek(HeaderSizeOffset,soFromBeginning);
 MyRes.Write(Heder.HeaderSize,SizeOf(Heder.HeaderSize));//записываем разницу между размером и базой заголовка
 MyRes.Seek(i,soFromBeginning);
 
 //пишем сам ресурс
 Count:=14;//Отбрасываем часть заголовка bmp файла
 SrcFile.Seek(14,soFromBeginning);
 While Count<SrcFile.Size do
 Begin
 SrcFile.Read(Buf,1);
 MyRes.Write(Buf,1);
 Count:=Count+1;
 End;
 
 SrcFile.Free;
 MyRes.Free;
 ShowMessage('Готово');
end;
 
end.

В данной статье использованы материалы:

  1. http://msdn.microsoft.com/en-us/library/ms648027(VS.85).aspx
  2. http://msdn.microsoft.com/en-us/library/ms648007(VS.85).aspx
  3. http://msdn.microsoft.com/en-us/library/ms648009(VS.85).aspx

При использовании материалов статьи просьба давать ссылку на ресурс и на автора SOA.

Автор: SOA

Статья добавлена: 28 июля 2010

Следующая статья: Поменять местами элементы в ListView »

Рейтинг статьи: 4.00 Голосов: 1 Ваша оценка:

Зарегистрируйтесь/авторизируйтесь,
чтобы оценивать статьи.

Для вставки ссылки на данную статью на другом сайте используйте следующий HTML-код:

Ссылка для форумов (BBCode):

Быстрая вставка ссылки на статью в сообщениях на сайте:
{{a:55}} (буква a — латинская) — только адрес статьи (URL);
{{статья:55}} — полноценная HTML-ссылка на статью (текст ссылки — название статьи).

Поделитесь ссылкой в социальных сетях:


Комментарии читателей к данной статье

Пока нет комментариев к данной статье. Оставьте свой и он будет первым.

Оставлять комментарии к статьям могут только зарегистрированные пользователи.