Серафим Бочкарев дата публикации 15-07-1999 00:00 Шпаргалка по ресурсам Windows-32 (для Delphi)
Этот текст - попытка сжатого
ответа на большинство заданных в
конференции вопросов по ресурсам
Windows. Возможно, Вы найдете здесь (в
неявном виде) объяснение части
связанных с ресурсами сложностей
в Delphi.
Стандартная
технология:
В тексте `Proj_L.Dpr` есть
следующие примеры:
Внутренний формат
ресурсов и пример DELPHI\DEMOS\RESXPLOR\*.*:
Очень короткое описание
внутреннего формата ресурсов Windows.
Вместо остсутствующих
комментариев в примере viewer`а
ресурсов из стандартной поставки
Delphi. Заимствовано, большей частью,
из Microsoft SDK.
Для компиляции примера надо
создать на диске перечисленные
исходные файлы (все в текстовом
формате). Я не привел примеров для
ресурсов типа BitMap`ов, Icon`ов и
курсоров, поскольку обращения к ним
достаточно тривиальны и не
содержат каких-либо
неоднозначностей, и, во-вторых, они (
декларации ресурсов ) недостаточно
компактно записываются в виде
текста.
Файл `#_Msg.Ini`
Список строк в
текстовом файле
msgHello= Здавствуйте !
msgBye= До свидания ...
Файл `#_Msg.RC`
Скрипт компилятора
ресурсов. В двоичном ресурсе с
именем RC1 записана ASCIIz-строка `QWERTY`.
RC1 RCDATA
{
'51 57 45 52 54 59 00'
}
STRINGTABLE
{
1000, "Здравствуйте ."
1001, "До свидания ..."
}
Файл `Proj_L.Dpr`:
Мы используем Delphi как
линкер, чтобы дописать стандартный
заголовок исполняемых файлов Windows к
файлу `#_Msg.Res`.
Последний делается компилятором
ресурсов из скрипта `#_Msg.RC`.
IDE может ругаться при загрузке
этого проекта из-за отсутствия
секции `uses` - дура.
{$IMAGEBASE $40000000}
{$APPTYPE CONSOLE}
library Proj_L;
{$R #_MSG.RES}
BEGIN
END.
Файл `Make_DLL.Bat`:
Компилируем скрипт `#_Msg.RC`
в файл `#_Msg.Res`;
компилируем и линкуем проект `Proj_L.Dpr`.
Получаем файл `Proj_L.Dll`.
rem -- may be used BRC32 or BRCC32
rem c:\del3\bin\brc32 -r #_msg.rc
c:\del3\bin\brcc32 #_msg.rc
c:\del3\bin\dcc32 /b proj_l.dpr
pause
Файл `Proj.Dpr`
{$APPTYPE GUI}
{$D+,O-,S-,R-,I+,A+,G+}
{$IfOpt D-} {$O+} {$EndIf}
program Proj;
{$IfNDef WIN32}
error: it works only under Win32
{$EndIf}
uses
Windows,
SysUtils,
Classes;
{//////////////////////////////////////////////}
procedure i_MsgBox( const ACap,AStr:String );
{ service routine: simple message-box }
begin
Windows.MessageBox( 0, pChar(AStr), pChar(ACap),
MB_OK or MB_ICONINFORMATION );
end;
{///// TestSList ////}
procedure TestSList;
{ load strings from ini-file via tStringList }
const
cFName = '#_MSG.INI';
var
qSList : tStringList;
begin
qSList := tStringList.Create;
with qSList do try
LoadFromFile( ExtractFilePath(ParamStr(0))+cFName );
i_MsgBox( 'strings collection via VCL:',
Trim(Values['msghello'])+#13+Trim(Values['MSGBYE']) );
finally Free;
end;
end;
{//// TestBuiltInStrRes ////}
RESOURCESTRING
sMsgHello = 'ЯВЕРТЫяверты';
sMsgBye = 'явертыЯВЕРТЫ';
procedure TestBuiltInStrRes;
{ load strings from resources via Delphi`s Linker }
begin
i_MsgBox( 'built-in string resources:', sMsgHello+#13+sMsgBye );
end;
{//////////////////////////////////////////////}
type
tFH_Method = procedure( AFHandle:tHandle );
{ `AFHandle` must be a handle of instance of image (of memory-map)
of a PE-file (EXE or DLL) }
procedure i_Call_FH_Method( AProc:tFH_Method );
{ it is wrapper to load and free a instance of binary
file with resource; also it calls to "AProc()" with
given instance-handle }
const
cLibName = 'PROJ_L.DLL';
var
qFHandle : tHandle;
begin
qFHandle := Windows.LoadLibrary(
pChar(ExtractFilePath(ParamStr(0))+cLibName) );
if qFHandle=0 then
i_MsgBox( 'Error loading library',
Format('Code# %xh',[Windows.GetLastError]) )
else
try AProc( qFHandle );
finally Windows.FreeLibrary( qFHandle );
end;
end;
{//// TestBinRes_WinAPI ////}
procedure TestBinRes_WinAPI( AFHandle:tHandle );
{ loading binary resource via usual windows-API }
var
qResH,
qResInfoH : tHandle;
begin
qResInfoH := Windows.FindResourceEx( AFHandle , RT_RCDATA, 'RC1', 0 );
qResH := Windows.LoadResource( AFHandle, qResInfoH );
try i_MsgBox( 'binary resource (Win API):',
pChar(Windows.LockResource(qResH)) );
finally Windows.FreeResource( qResH );
end;
end;
{//// TestBinRes_VCLStream ////}
procedure TestBinRes_VCLStream( AFHandle:tHandle );
{ loading binary resource via VCL`s stream }
var
qResStream : tResourceStream;
begin
qResStream := tResourceStream.Create( AFHandle, 'RC1', RT_RCDATA );
try i_MsgBox( 'binary resource (VCL stream):',
pChar(qResStream.Memory) );
finally qResStream.Free;
end;
end;
{//// TestStrRes_WinAPI ////}
procedure TestStrRes_WinAPI( AFHandle:tHandle );
{ loading string resource via usual windows-API }
const
cBufSize = 512;
var
qBuf : array[0..1,0..cBufSize-1]of Char;
begin
Windows.LoadStringA( AFHandle, 1000, qBuf[0], cBufSize );
Windows.LoadStringA( AFHandle, 1001, qBuf[1], cBufSize );
i_MsgBox( 'string resources (Win API):',
StrPas(qBuf[0])+#13+StrPas(qBuf[1]) );
end;
BEGIN
TestSList;
TestBuiltInStrRes;
i_Call_FH_Method( TestBinRes_WinAPI );
i_Call_FH_Method( TestBinRes_VCLStream );
i_Call_FH_Method( TestStrRes_WinAPI );
END.
Замечания:
-
Rесурсы частично
вынесены во внешнюю DLL только
для демонстрации, поскольку
большинство вопросов в
конференции подразумевает
именно такое их
использование.
-
Если ресурсы
слинкованы не в отдельную DLL,
а в исполняемый файл проекта,
в параметре AFHandle надо везде
передавать `0` или значение
переменной System.HInstance.
-
Вместо функции
Windows.FindResource() я предпочитаю
FindResourceEx() с лишним явным
параметром - `LanguageId`. Дело в
том, что первая не всегда
находит ресурсы, сделанные
борландовскими
компиляторами - семантика
LanguageId по умолчанию
определена MS не совсем
однозначно.
-
Для однозначности, я
явно указал имя функции
Windows.LoadStringA(). В NT работает еще
функция LoadStringW(), которая
возвращает строки UNICODE. В Win95
LoadStringW() возвращает код
ошибки `not implemented`.
В каталоге DELPHI\DEMOS\RESXPLOR
есть пример работы с ресурсами Windows
на самом `фундаментальном` уровне -
непосредствено с форматом PE COFF (
Portable Executable Common Object File Format ) для Win32.
Данный раздел написан, в основном,
для тех, кто захочет разобраться в
этом стандартном примере Delphi.
Сами по себе ресурсы -
индексированный набор данных с
записями переменной длины. Чтобы
конкретную запись ресурса можно
было найти, у нее есть один из двух
идентификаторов - имя (строка
символов UNICODE) или целое число.
Целыми числами идентифицируются,
например, каталоги стандартных
типов ресурсов и строки в таблицах.
Большинство записей ресурсов
стандартных типов
идентифицируются именами.
Практически, в именах ресурсов
разумно использовать только
подмножетсво стандартных символов
ASCII (коды от 0 до 255). Описание
стандартных типов ресурсов Windows
можно посмотреть в on-line help`е любой IDE
C или Delphi. Любопытно, что способ
идентификации ресурса ( целое число
или ссылка на имя ) специфицирован,
скорее, не на уровне стандарта, а на
уровне принятых соглашений. Для
поиска ресурса мы, в общем случае,
задаем три параметра:
Тип - один из
стандартных кодов типа
ресурса. В вызовах API это может
быть либо адресом строки,
содержащей одно из стандартных
имен, либо - одна из констант
RT_xxx из DELPHI\SOURCE\RTL\WIN\WINDOWS.PAS.
Идентификатор. В
зависимости от типа ресурса,
это может быть целое число или
имя.
Язык ресурса.
Кодируется целым числом.
Формат ресурсов PE COFF
ориентирован чтобы:
- максимально быстро находить
нужный ресурс по указаным трем
параметрам,
- расположить ресурсы достаточно
компактно,
- переносить скомпилированные
ресурсы между процессорами с
разными правилами адресации.
Далее используется термин
RVA (relative virtual address), я его поясню. Все
адреса в защищенных многозадачных
системах (не только на x286..586) обычно
делаются `виртуальными`: То есть,
пользовательское приложение не
должно иметь шанс узнать что-либо о
физических адресах - иначе оно
теоретически может разрушить любую
защиту операционной системы. В Windows
строгой защиты в этом смысле нет, но
есть еще одна причина
`виртуальности` адресов -
динамическая загрузка/выгрузка
данных из ОЗУ на диск для
организации виртуальной памяти.
Процессор аппаратно, `на лету`,
транслирует виртуальные адреся в
физические по таблицам, созданным
ядром операционной системы.
Теперь о слове `relative`. Операционной
системе, по большому счету, без
разницы, какой именно виртуальный
адрес дать первому байту образа
исполняемого файла в ОЗУ. А линкеру
и самой программе, в ряде случаев,
удобнее работать с конкретным
значением. Оно называется `ImageBase`;
линкер записывает его в заголовке
PE-файла. По техническим причинам,
оно не может быть произвольным для
Windows-программ. В Delphi есть директива
`{$ImageBase ...}`. Так вот, RVA
объекта - это его смещение
относительно значения `ImageBase`.
Обычный адрес объекта (он, кстати,
тоже виртуальный) есть сумма
значений глобальной переменной
`ImageBase` и `RVA` данного объекта.
В тексте использована ассемблерная
мнемоника: `DD` и `DW` (Define Double и Define Word),
что означает, соответственно, 32- и
16-разрядное слово. Символ `|`
означает `или`, `либо`.
Я делаю сокращенное изложение
фрагмента документации PE COFF. Я
полагаю, этого более-менее
достаточно, чтобы разобраться,
при желании, с текстом примера
Delphi. Файл PE.TXT ( author Micheal J. O'Leary )
взят из документации Microsoft C. Он же
входит в MS Software Developers Kit (SDK) и в
комплект поставки большинства
компиляторов C для Win32. Если Вам
интересно положение корневого
каталога ресурсов в заголовке PE
COFF или более подробный формат
заголовка - можно смотреть
исходные тексты проекта проекта
RSEXPLOR или, разумеется, сам
первоисточник - PE.TXT
Ресурсы индексированы как
многоуровневое двоичное дерево.
Технологически возможно 2**31
уровней, но в Windows стандартно
используются только три: первый - TYPE
(тип), далее - NAME (имя), далее - LANGUAGE
(язык). Ресурсы должны быть
отсортированы по определенным
правилам - для ускорения поиска.
Типичное расположение ресурсов в
файле: сначала лежит `RESOURCE DIRECTORY`
(каталог/каталоги ресурсов), затем -
`RESOURCE DATA` (собственно данные
ресурсов).
Каталог ресурсов довольно похож, по
структуре, на каталоги дисков. Он
содержит записи (`DIR ENTRIES` - см. далее),
которые указывают либо на ресурсы,
либо на другие каталоги (точнее -
подкаталоги) ресурсов. В отличие от
дисков, сами данные не разносятся
по кластерам, а наоборот - их
стараются плотнее прижать друг к
другу, поскольку никто не
собирается вставлять туда
дополнительные данные после сборки
(линковки) исполняемого файла.
Каталог ресурсов начинается с
заголовка (четыре 32-битных слова):
DD RESOURCE FLAGS
DD TIME/DATE STAMP
DW MAJOR VERSION, DW MINOR VERSION
DW # NAME ENTRY, DW # ID ENTRY
декларация в RXTypes.Pas:
IMAGE_RESOURCE_DIRECTORY = packed record
Characteristics : DWORD;
TimeDateStamp : DWORD;
MajorVersion : WORD;
MinorVersion : WORD;
NumberOfNamedEntries : WORD;
NumberOfIdEntries : WORD;
end;
Здесь важны два поля: `# NAME ENTRY` -
число точек входа, имеющих имена, и
`# ID ENTRY` - число точек входа, имеющих
вместо имен целочисленные
идентификаторы.
За заголовком следует массив из
записей `RESOURCE DIR ENTRIES` (точек входа
каталога). Там лежат `# NAME ENTRY`+ `# ID ENTRY`
записей типа `DIR ENTRY`. Формат записи
`DIR ENTRY` - два 32-битных слова:
DD NAME RVA | INTEGER ID
DD DATA ENTRY RVA | SUBDIR RVA
декларация в RXTypes.Pas:
IMAGE_RESOURCE_DIRECTORY_ENTRY = packed record
Name: DWORD; // Or ID: Word (Union)
OffsetToData: DWORD;
end;
Первое поле содержит либо `NAME RVA` -
адрес строки (UNICODE) с именем, либо -
`INTEGER ID` - целочисленный
идентификатор. `INTEGER ID` может быть,
например, одним из стандартных
кодов типа ресурса или заданным
пользователем кодом строки в
таблице строк.
Самый старший бит второго поля (31-й
бит) называется `Escape-флагом`. Если он
установлен в `1`, считается что
данная `DIR ENTRY` - ссылка на другой
подкаталог ресурсов. Если сброшен в
`0` - данная запись ссылка на данные
ресурса. Понятно, при вычислении
адреса этот бит всегда должен
считаться `0`.
Строка, на которую указывает `NAME RVA`,
очень похожа на паскалевскую
short-string, только вместо байтов она
состоит из 16-битные слов. Самое
первое слово - длина строки, за ним
лежат 16-битные символы UNICODE.
Физически линкер кладет эти строки
переменной длиины между каталогами
и собственно данными ресурсов.
Понятно, что `SUBDIR RVA` указывает на
совершенно аналогичную таблицу
подкаталога.
`DATA ENTRY RVA` указывает на запись `RESOURCE
DATA ENTRY` такого вида:
DD DATA RVA
DD SIZE
DD CODEPAGE
DD RESERVED
декларация в RXTypes.Pas:
IMAGE_RESOURCE_DATA_ENTRY = packed record
OffsetToData : DWORD;
Size : DWORD;
CodePage : DWORD;
Reserved : DWORD;
end;
`DATA RVA` - адрес бинарных данных, `SIZE`
- их размер. `CODEPAGE` (кодовая страницa)
обычно имеет снысл только для
строковых ресурсов. Оговаривается,
что в Win32 это должна быть одна из
стандартных страниц UNICODE. Сами
бинарные данные могут жить либо
прямо за полем `RESERVED`, либо где-то в
другом месте - смотря куда линкер их
положит.
Далее я привожу целиком фрагмент
файла PE.TXT. Это - конкретный пример
размещения ресурсов с подробным
дампом памяти.
The following is an example for an app.
which wants to use the following data as resources:
TypeId# NameId# Language ID Resource Data
00000001 00000001 0 00010001
00000001 00000001 1 10010001
00000001 00000002 0 00010002
00000001 00000003 0 00010003
00000002 00000001 0 00020001
00000002 00000002 0 00020002
00000002 00000003 0 00020003
00000002 00000004 0 00020004
00000009 00000001 0 00090001
00000009 00000009 0 00090009
00000009 00000009 1 10090009
00000009 00000009 2 20090009
Then the Resource Directory in the Portable format looks like:
Offset Data
0000: 00000000 00000000 00000000 00030000 (3 entries in this directory)
0010: 00000001 80000028 (TypeId #1, Subdirectory at offset 0x28)
0018: 00000002 80000050 (TypeId #2, Subdirectory at offset 0x50)
0020: 00000009 80000080 (TypeId #9, Subdirectory at offset 0x80)
0028: 00000000 00000000 00000000 00030000 (3 entries in this directory)
0038: 00000001 800000A0 (NameId #1, Subdirectory at offset 0xA0)
0040: 00000002 00000108 (NameId #2, data desc at offset 0x108)
0048: 00000003 00000118 (NameId #3, data desc at offset 0x118)
0050: 00000000 00000000 00000000 00040000 (4 entries in this directory)
0060: 00000001 00000128 (NameId #1, data desc at offset 0x128)
0068: 00000002 00000138 (NameId #2, data desc at offset 0x138)
0070: 00000003 00000148 (NameId #3, data desc at offset 0x148)
0078: 00000004 00000158 (NameId #4, data desc at offset 0x158)
0080: 00000000 00000000 00000000 00020000 (2 entries in this directory)
0090: 00000001 00000168 (NameId #1, data desc at offset 0x168)
0098: 00000009 800000C0 (NameId #9, Subdirectory at offset 0xC0)
00A0: 00000000 00000000 00000000 00020000 (2 entries in this directory)
00B0: 00000000 000000E8 (Language ID 0, data desc at offset 0xE8
00B8: 00000001 000000F8 (Language ID 1, data desc at offset 0xF8
00C0: 00000000 00000000 00000000 00030000 (3 entries in this directory)
00D0: 00000001 00000178 (Language ID 0, data desc at offset 0x178
00D8: 00000001 00000188 (Language ID 1, data desc at offset 0x188
00E0: 00000001 00000198 (Language ID 2, data desc at offset 0x198
00E8: 000001A8 (At offset 0x1A8, for TypeId #1, NameId #1, Language id #0
00000004 (4 bytes of data)
00000000 (codepage)
00000000 (reserved)
00F8: 000001AC (At offset 0x1AC, for TypeId #1, NameId #1, Language id #1
00000004 (4 bytes of data)
00000000 (codepage)
00000000 (reserved)
0108: 000001B0 (At offset 0x1B0, for TypeId #1, NameId #2,
00000004 (4 bytes of data)
00000000 (codepage)
00000000 (reserved)
0118: 000001B4 (At offset 0x1B4, for TypeId #1, NameId #3,
00000004 (4 bytes of data)
00000000 (codepage)
00000000 (reserved)
0128: 000001B8 (At offset 0x1B8, for TypeId #2, NameId #1,
00000004 (4 bytes of data)
00000000 (codepage)
00000000 (reserved)
0138: 000001BC (At offset 0x1BC, for TypeId #2, NameId #2,
00000004 (4 bytes of data)
00000000 (codepage)
00000000 (reserved)
0148: 000001C0 (At offset 0x1C0, for TypeId #2, NameId #3,
00000004 (4 bytes of data)
00000000 (codepage)
00000000 (reserved)
0158: 000001C4 (At offset 0x1C4, for TypeId #2, NameId #4,
00000004 (4 bytes of data)
00000000 (codepage)
00000000 (reserved)
0168: 000001C8 (At offset 0x1C8, for TypeId #9, NameId #1,
00000004 (4 bytes of data)
00000000 (codepage)
00000000 (reserved)
0178: 000001CC (At offset 0x1CC, for TypeId #9, NameId #9, Language id #0
00000004 (4 bytes of data)
00000000 (codepage)
00000000 (reserved)
0188: 000001D0 (At offset 0x1D0, for TypeId #9, NameId #9, Language id #1
00000004 (4 bytes of data)
00000000 (codepage)
00000000 (reserved)
0198: 000001D4 (At offset 0x1D4, for TypeId #9, NameId #9, Language id #2
00000004 (4 bytes of data)
00000000 (codepage)
00000000 (reserved)
And the data for the resources will look like:
01A8: 00010001
01AC: 10010001
01B0: 00010002
01B4: 00010003
01B8: 00020001
01BC: 00020002
01C0: 00020003
01C4: 00020004
01C8: 00090001
01CC: 00090009
01D0: 10090009
01D4: 20090009
Напомню, что весь этот текст
имеет чисто утилитарный смысл -
пояснение конкретного
стандартного примера Delphi.
Полагаю, произвольное изложение
на русском языке не затрагивает
авторские права Microsoft на описание
COFF. Вы можете использовать этот
текст по своему усмотрению.
[TComponent] [TStringList] [TResourceStream] [Запись/чтение ресурсов]
Обсуждение материала [ 20-08-2006 16:20 ] 2 сообщения |