Этот текст - попытка сжатого ответа на большинство заданных в конференции вопросов по ресурсам 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.
В каталоге
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.
Идентификатор. В зависимости от типа ресурса, это может быть целое число или имя.
Язык ресурса. Кодируется целым числом.
Далее используется
термин 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-разрядное слово. Символ `|` означает
`или`, `либо`.
Ресурсы индексированы как
многоуровневое двоичное дерево. Технологически возможно 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