Rambler's Top100
"Knowledge itself is power"
F.Bacon
Поиск | Карта сайта | Помощь | О проекте | ТТХ  
 Свитки
  
 

Фильтр по датам

 
 К н и г и
 
Книжная полка
 
 
Библиотека
 
  
  
 


Поиск
 
Поиск по КС
Поиск в статьях
Яndex© + Google©
Поиск книг

 
  
Тематический каталог
Все манускрипты

 
  
Карта VCL
ОШИБКИ
Сообщения системы

 
Форумы
 
Круглый стол
Новые вопросы

 
  
Базарная площадь
Городская площадь

 
   
С Л С

 
Летопись
 
Королевские Хроники
Рыцарский Зал
Глас народа!

 
  
ТТХ
Конкурсы
Королевская клюква

 
Разделы
 
Hello, World!
Лицей

Квинтана

 
  
Сокровищница
Подземелье Магов
Подводные камни
Свитки

 
  
Школа ОБЕРОНА

 
  
Арсенальная башня
Фолианты
Полигон

 
  
Книга Песка
Дальние земли

 
  
АРХИВЫ

 
 

Сейчас на сайте присутствуют:
 
  
 
Во Флориде и в Королевстве сейчас  06:32[Войти] | [Зарегистрироваться]

Шпаргалка по ресурсам Windows-32 (для Delphi)

Серафим Бочкарев
дата публикации 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`.

Внутренний формат ресурсов Windows.

В каталоге 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-разрядное слово. Символ `|` означает `или`, `либо`.

Описание формата ресурсов в MS PE COFF.

Я делаю сокращенное изложение фрагмента документации 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).

Далее я привожу целиком фрагмент файла 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. Вы можете использовать этот текст по своему усмотрению.
 
для "Королевства Delphi"
Серафим Бочкарев
( `Шестикрылый` - сам знаю )
sbochkarev@hotmail.com



Смотрите также материалы по темам:
[TComponent] [TStringList] [TResourceStream] [Запись/чтение ресурсов]

 Обсуждение материала [ 20-08-2006 16:20 ] 2 сообщения
  
Время на сайте: GMT минус 5 часов

Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter.
Функция может не работать в некоторых версиях броузеров.

Web hosting for this web site provided by DotNetPark (ASP.NET, SharePoint, MS SQL hosting)  
Software for IIS, Hyper-V, MS SQL. Tools for Windows server administrators. Server migration utilities  

 
© При использовании любых материалов «Королевства Delphi» необходимо указывать источник информации. Перепечатка авторских статей возможна только при согласии всех авторов и администрации сайта.
Все используемые на сайте торговые марки являются собственностью их производителей.

Яндекс цитирования