Написать данную статью автора побудило недостаточное
количество информации в рунете и за его пределами для написания своего
компилятора ресурсов типа 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 код нуля.
Также стоит заметить, что в начале заголовка ставится значение
типа 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.
|