Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

Операционная система Microsoft Windows 3.1 для программиста

© Александр Фролов, Григорий Фролов
Том 13, М.: Диалог-МИФИ, 1993, 284 стр.

[Назад] [Содеожание] [Дальше]

3.3. Структура DLL-библиотеки

DLL-библиотека состоит из нескольких специфических функций и произвольного набора функций, выполняющих ту работу, для которой разрабатывалась данная библиотека. Как мы уже говорили, DLL-библиотека может иметь (а может и не иметь) сегмент данных и ресурсы.

В заголовке загрузочного модуля DLL-библиотеки описаны экспортируемые точки входа, соответствующие всем или некоторым определенным в ней функциям. Приложения могут вызывать только те функции DLL-библиотеки, которые экспортируются ей.

Функция LibEntry

До тех пор, пока ни одно из приложений не затребовало функцию из DLL-библиотеки, сама библиотека находится в файле на диске. В оперативной памяти ее нет. Однако как только приложение затребует функцию из DLL-библиотеки или загрузит библиотеку в память явным образом, начнется процесс инициализации библиотеки. Этот процесс выполняется только один раз, так как в памяти может находиться только одна копия DLL-библиотеки.

В процессе инициализации после загрузки библиотеки в память Windows вызывает функцию LibEntry , которая должна быть определена в каждой DLL-библиотеке. Можно считать, что функция LibEntry является точкой входа библиотеки, получающей управление при загрузке библиотеки в память.

Задачей функции LibEntry является инициализация локальной области памяти, если она определена для DLL-библиотеки.

Функция LibEntry должна быть дальней функцией, составленной на языке ассемблера, так как она получает параметры через регистры процессора. Перечислим и опишем параметры, передаваемые функции LibEntry при загрузке DLL-библиотеки в память.

Регистры Описание
DS Селектор, указывающий на сегмент данных DLL-библиотеки. Отметим, что хотя сразу после загрузки DLL-библиотека имеет собственный сегмент данных, в нем не определена область локальных данных. Для определения области локальных данных функция LibEntry должна вызвать функцию LocalInit
DI Идентификатор DLL-библиотеки, присвоенный ей операционной системой Windows после загрузки. По своему назначению аналогичен идентификатору приложения hInstance, передаваемому обычному приложению через соответствующий параметр функции WinMain. Идентификатор DLL-библиотеки используется функциями библиотеки при создании таких, например, объектов, как окна (если окно создается функцией, расположенной в DLL-библиотеке, при его создании следует использовать не идентификатор приложения hInstance, вызвавшего функцию, а идентификатор DLL-библиотеки)
CX Требуемый размер локальной области данных, указанный в операторе HEAPSIZE файла определения модуля DLL-библиотеки. При вызове функции инициализации локальной области памяти LocalInit в качестве значения параметра uStartAddr следует использовать нуль, а в качестве значения параметра uEndAddr - содержимое регистра CX
ES:SI Дальний указатель на строку параметров, которая может быть указана при явной загрузке DLL-библиотеки (позже мы рассмотрим различные способы загрузки DLL-библиотеки). Обычно этот параметр не используется

Вам не надо определять функцию LibEntry самостоятельно, так как при создании файла DLL-библиотеки редактор связей, входящий в систему разработки, включит уже имеющийся в стандартной библиотеке модуль. Этот стандартный модуль выполняет всю необходимую работу по инициализации локальной области памяти DLL-библиотеки (с помощью функции LocalInit) и затем вызывает функцию LibMain, которая будет описана в следующем разделе.

Иногда для DLL-библиотеки может потребоваться нестандартная инициализация. Только в этом случае вам придется разработать функцию LibEntry самостоятельно. За основу вы можете взять файл libentry.asm из SDK, который содержит исходный текст упомянутой выше функции.

Функция LibMain

Функция LibMain должна присутствовать в каждой стандартной DLL-библиотеке. Эту функцию вам надо определить самостоятельно, причем вы можете воспользоваться языком программирования С.

По своему назначению функция LibMain напоминает функцию WinMain обычного приложения Windows. Функция WinMain получает управление при запуске приложения, а функция LibMain - при загрузке DLL-библиотеки в память. Так же как и функция WinMain, функция LibMain имеет параметры, которые можно использовать для инициализации библиотеки.

Приведем прототип функции LibMain :

int FAR PASCAL LibMain(HINSTANCE hInstance,
                       WORD wDataSegment,
                       WORD wHeapSize,
                       LPSTR lpszCmdLine);

Параметр hInstance при вызове функции содержит идентификатор DLL-библиотеки. Это ни что иное, как содержимое регистра DI перед вызовом функции LibEntry.

Через параметр wDataSegment передается селектор, соответствующий сегменту данных DLL-библиотеки. Если DLL-библиотека не имеет сегмента данных, этот параметр содержит идентификатор DLL-библиотеки (точнее, идентификатор модуля DLL-библиотеки). Значение параметра wDataSegment соответствует содержимому регистра DS перед вызовом функции LibEntry.

Параметр wHeapSize содержит размер в байтах локальной области данных DLL-библиотеки. Если DLL-библиотека не имеет сегмента данных, в этом параметре находится нулевое значение.

И, наконец, через параметр lpszCmdLine передается указатель на командную строку, которую можно передать DLL-библиотеке при ее явной загрузке в память.

Если инициализация DLL-библиотеки выполнена успешно, функция LibMain должна возвратить ненулевое значение. Если в процессе инициализации произошла ошибка, следует возвратить нуль. В этом случае функция LibEntry также возвратит нуль. Это приведет к тому, что Windows выгрузит библиотеку из памяти.

Разрабатывая процедуру инициализации DLL-библиотеки, учтите, что функция LibMain вызывается только один раз, во время загрузки библиотеки в память. Не следует думать, что для каждого приложения, использующего одну и ту же DLL-библиотеку, будет вызвана функция LibMain. При попытке повторной загрузки уже загруженной ранее DLL-библиотеки будет увеличено содержимое счетчика использования библиотеки, но и только.

Если для каждого приложения в локальной области данных DLL-библиотеки необходимо выделять отдельные структуры данных, вам придется разработать собственную процедуру регистрации приложения.

Например, можно создать специальную функцию регистрации LibRegister, которая возвращает приложению указатель на выделенную для него в локальной области структуру данных. Вызывая функции из DLL-библиотеки, приложение передает им в качестве, например, первого параметра, этот указатель.

Перед завершением работы приложение должно вызвать созданную вами функцию, освобождающую выделенную структуру данных. Она может называться, например, LibUnregister.

Если функция LibMain заказывает блоки памяти из глобальной области данных, следует использовать флаг GMEM_SHARE. Такие блоки памяти будут принадлежать создавшей их DLL-библиотеке. Освобождение заказанных глобальных блоков памяти можно выполнить в функции WEP, получающей управление при выгрузке DLL-библиотеки из памяти.

Приведем пример простейшего варианта функции LibMain:

int FAR PASCAL LibMain(HINSTANCE hInstance,
  WORD wDataSegment, WORD wHeapSize,
  LPSTR lpszCmdLine)
{
  if(wHeapSize != 0)
    UnlockData(0);
  return 1;
}

Эта функция проверяет размер локальной области данных, и если эта область данных существует, она расфиксируется при помощи макрокоманды UnlockData. После этого возвращается признак успешной инициализации.

Функция WEP

DLL-библиотека в любой момент времени может быть выгружена из памяти. В этом случае Windows перед выгрузкой вызывает функцию WEP . Эта функция, как и функция LibMain, вызывается только один раз. Она может быть использована для уничтожения структур данных и освобождения блоков памяти, заказанных при инициализации DLL-библиотеки.

Приведем прототип функции WEP (при использовании системы разработки Borland Turbo C++ for Windows):

int FAR PASCAL WEP(int bSystemExit);

Параметр bSystemExit может принимать значения WEP_FREE_DLL и WEP_SYSTEM_EXIT . Этот параметр указывает причину выгрузки DLL-библиотеки из памяти. В первом случае выгрузка выполняется потому, что либо функциями библиотеки не пользуется ни одно приложение, либо одно из приложений выдало запрос на выгрузку. Если же параметр имеет значение WEP_SYSTEM_EXIT, выгрузка библиотеки происходит из-за завершения работы операционной системы Windows.

Функция WEP должна всегда возвращать значение 1.

Вам не обязательно самостоятельно определять функцию WEP. Так же как и функция LibEntry, функция WEP добавляется в DLL-библиотеку транслятором. Вы, однако, можете определить собственную функцию WEP, если перед выгрузкой DLL-библиотеки требуется освободить заказанные ранее блоки памяти.

Приведем пример простейшей функции WEP:

int FAR PASCAL WEP(int bSystemExit)
{
  return 1;
}

В операционной системе Windows версии 3.0 на использование функции WEP накладывались серьезные ограничения, которые делали ее практически бесполезной. Например, размер стека, используемого для работы этой функции, составлял всего 256 байт, чего совершенно недостаточно для вызова функций программного интерфейса Windows. Есть и другие проблемы, однако мы не будем их затрагивать из-за того, что версия 3.0 уже сильно устарела, а в версии 3.1 все эти ограничения сняты. Поэтому вы можете использовать функцию WEP для любых процедур, в частности, для освобождения памяти, полученной при инициализации в функции LibMain.

Экспортируемые функции

Кроме функций LibMain и WEP в DLL-библиотеке могут быть определены и другие функции (как мы уже говорили, существуют DLL-библиотеки, состоящие из одних ресурсов). Это могут быть экспортируемые и неэкспортируемые функции.

Экспортируемые функции доступны для вызова приложениям Windows. Неэкспортируемые являются локальными для DLL-библиотеки, они доступны только для функций библиотеки.

Самый простой способ сделать функцию экспортируемой в системе разработки Borland Turbo C++ for Windows - указать в ее определении ключевое слово _export. Мы использовали этот способ при определении функции окна. Для экспортируемой функции создается специальный пролог, необходимый для правильной загрузки сегментного регистра DS.

Есть и другой способ - можно перечислить все экспортируемые функции в файле определения модуля при помощи оператора EXPORTS :

EXPORTS
  DrawBitmap  @4
  ShowAll     
  HideAll
  GetMyPool   @8
  FreeMyPool  @9

В приведенном выше примере в разделе EXPORTS перечислены имена нескольких экспортируемых функций. Около некоторых имен после символа "@" указаны порядковые номера соответствующих функций в DLL-библиотеке.

Если вы не укажите порядковые номера экспортируемых функций, при компоновке загрузочного файла DLL-библиотеки редактор связи создаст свою собственную нумерацию, которая может изменяться при внесении изменений в исходные тексты функций и последующей повторной компоновке. Плохо это или хорошо?

Для ответа на этот вопрос следует знать, что ссылка на экспортируемую функцию может выполняться двумя различными способами - по имени функции и по ее порядковому номеру. Если функция вызывается по имени, ее порядковый номер не имеет значения. Однако вызов функции по порядковому номеру выполняется быстрее, поэтому использование порядковых номеров предпочтительнее.

Если при помощи файла определения модуля DLL-библиотеки вы задаете фиксированное распределение порядковых номеров экспортируемых функций, при внесении изменений в исходные тексты DLL-библиотеки это распределение не изменится. В этом случае все приложения, ссылающиеся на функции из этой библиотеки по их порядковым номерам, будут работать правильно. Если же вы не определили порядковые номера функций, у приложений могут возникнуть проблемы с правильной адресацией функции из-за возможного изменения этих номеров.

Чтобы сказанное стало более понятным, рассмотрим подробнее механизм, с помощью которого приложения ссылаются на функции из DLL-библиотек.

Импортирование функций

Когда вы используете статическую компоновку, то включаете в файл проекта приложения соответствующий lib-файл, содержащий нужную вам библиотеку объектных модулей. Такая библиотека содержит исполняемый код модулей, который на этапе статической компоновки включается в exe-файл загрузочного модуля.

Если используется динамическая компоновка, в загрузочный exe-файл приложения записывается не исполнимый код функций, а ссылка на соответствующую DLL-библиотеку и функцию внутри нее. Как мы уже говорили, эта ссылка может быть организована с использованием либо имени функции, либо ее порядкового номера в DLL-библиотеке.

Откуда при компоновке приложения редактор связей узнает имя DLL-библиотеки, имя или порядковый номер экспортируемой функции? Для динамической компоновки функции из DLL-библиотеки можно использовать различные способы.

Библиотека импорта

Для того чтобы редактор связей мог создать ссылку, в файл проекта приложения вы должны включить так называемую библиотеку импорта (import library ), созданную приложением Import Lib , входящим в состав системы разработки Borland Turbo C++ for Windows. Соответствующее средство имеется в SDK, а также в любой другой системе разработки приложений Windows.

Библиотека импорта создается либо на основе dll-файла библиотеки, либо на основе файла определения модуля, используемого для создания DLL-библиотеки.

В любом случае вам надо запустить приложение Import Lib, и из меню "File" выбрать строку "File Select...". При этом на экране появится диалоговая панель "Select File", с помощью которой вы можете выбрать нужный вам dll- или def-файл (рис. 3.6).

Рис. 3.6. Работа с приложением Import Library

После выбора файла приложение Import Lib создаст библиотеку импорта в виде lib-файла, расположенного в том же каталоге, что и исходный dll- или def-файл. Этот файл необходимо включить в проект создаваемого вами приложения, пользующегося функциями DLL-библиотеки.

Следует заметить, что стандартные библиотеки систем разработки приложений Windows содержат как обычные объектные модули, предназначенные для статической компоновки, так и ссылки на различные стандартные DLL-библиотеки, экспортирующие функции программного интерфейса операционной системы Windows.

В состав системы разработки Borland C++ for Windows версии 4.0 и 4.01 входит DLL-библиотека, содержащая функции стандартной библиотеки компилятора. Эта библиотека расположена в файле с именем bc40rtl.dll. В проекте приложения вы можете определить, что для стандартных функций следует использовать динамическую компоновку. В этом случае размер полученного загрузочного модуля заметно сократится, однако для работы приложения будет нужен файл bc40rtl.dll.

Использование оператора IMPORTS

Использование библиотек импорта - удобный, но не единственный способ компоновки функций DLL-библиотеки. Этот способ можно рассматривать как способ неявной компоновки на этапе редактирования загрузочного модуля приложения, так как имена импортируемых функций нигде не перечисляются.

Другой способ основан на использовании в файле определения модуля приложения оператора IMPORTS . С помощью этого оператора можно перечислить импортируемые функции, указав имена функций или их порядковые номера , а также имена файлов соответствующих DLL-библиотек:

IMPORTS
  Msg=dllsrc.4
  dllsrc.TellMe

Во второй строке приведенного выше примера приложение импортирует функцию Msg из DLL-библиотеки dllsrc.dll, причем порядковый номер указанной функции в библиотеке равен 4.

В третьей строке из DLL-библиотеки dllsrc.dll импортируется функция с именем TellMe, причем ее порядковый номер не используется.

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

Динамический импорт функций во время выполнения приложения

В некоторых случаях невозможно выполнить динамическую компоновку на этапе редактирования. Вы можете, например, создать приложение, которое состоит из основного модуля и дополнительных, реализованных в виде DLL-библиотек. Состав этих дополнительных модулей и имена файлов, содержащих DLL-библиотеки, может изменяться, при этом в приложение могут добавляться новые возможности.

Если вы, например, разрабатываете систему распознавания речи, то можете сделать ее в виде основного приложения и набора DLL-библиотек, по одной библиотеке для каждого национального языка. В продажу система может поступить в комплекте с одной или двумя библиотеками, но в дальнейшем пользователь сможет купить дополнительные библиотеки и, просто переписав новые библиотеки на диск, получить возможность работы с другими языками. При этом основной модуль приложения не может "знать" заранее имена файлов дополнительных DLL-библиотек, поэтому динамическая компоновка с использованием библиотеки импорта или оператора IMPORTS файла определения модуля невозможна.

Однако приложение может в любой момент времени загрузить любую DLL-библиотеку, вызвав специально предназначенную для этого функцию программного интерфейса Windows с именем LoadLibrary . Приведем ее прототип:

HINSTANCE WINAPI LoadLibrary(LPCSTR lpszLibFileName);

Параметр функции является указателем на текстовую строку, закрытую двоичным нулем. В эту строку перед вызовом функции следует записать путь к файлу DLL-библиотеки или имя этого файла. Если путь к файлу не указан, при поиске выполняется последовательный просмотр следующих каталогов:

текущий каталога;

каталог, в котором находится операционная система Windows;

системный каталог Windows;

каталог, в котором находится загрузочный файл приложения, загружающего DLL-библиотеку;

каталоги, перечисленные в операторе описания среды PATH, расположенном в файле autoexec.bat;

каталоги, расположенные в сети, если для них определены пути поиска.

Если файл DLL-библиотеки найден, функция LoadLibrary возвращает идентификатор модуля библиотеки. В противном случае возвращается код ошибки, который по своему значению меньше величины HINSTANCE_ERROR, определенной в файле windows.h. Возможны следующие коды ошибок:

Код ошибки Описание
0 Мало памяти, неправильный формат загружаемого файла
2 Файл не найден
3 Путь к файлу не существует
5 Была предпринята попытка динамически загрузить приложение или произошла ошибка при совместном использовании файлов. Также возможно, что была предпринята попытка доступа к файлу в сети пользователем, не имеющим на это достаточных прав
6 Данная библиотека требует отдельный сегмент данных для каждой задачи
8 Мало памяти для запуска приложения
10 Неправильная версия операционной системы Windows
11 Неправильный формат загрузочного файла приложения Windows
12 Данное приложение разработано для операционной системы, отличной от Microsoft Windows
13 Данное приложение разработано для MS-DOS версии 4.0
14 Неизвестный тип исполняемого файла
15 Была предпринята попытка загрузить приложение, разработанное для реального режима работы процессора в среде ранних версий операционной системы Windows
16 Была предпринята попытка загрузить вторую копию исполняемого файла, содержащего сегменты данных, отмеченные как multiple, но не защищенные от записи
19 Попытка загрузки компрессованного выполнимого файла
20 Файл, содержащий DLL-библиотеку, имеет неправильный формат
21 Данное приложение работает только в среде 32-битового расширения Windows

Функция LoadLibrary может быть вызвана разными приложениями для одной и той же DLL-библиотеки несколько раз. В этом случае загрузка DLL-библиотеки выполняется только один раз. Последующие вызовы функции LoadLibrary приводят только к увеличению счетчика использования DLL-библиотеки.

В качестве примера приведем фрагмент исходного текста приложения, загружающего DLL-библиотеку из файла srcdll.dll:

HINSTANCE hLib;
hLib = LoadLibrary("srcdll.dll");
if(hLib >= HINSTANCE_ERROR)
{
  // Работа с DLL-библиотекой
}
FreeLibrary(hLib);

Если DLL-библиотека больше не нужна, ее следует освободить с помощью функции FreeLibrary :

void WINAPI FreeLibrary(HINSTANCE hLibrary);

В качестве параметра этой функции следует передать идентификатор освобождаемой библиотеки.

Если счетчик использования DLL-библиотеки становится равным нулю (что происходит, когда все приложения, работавшие с библиотекой, освободили ее или завершили свою работу), DLL-библиотека выгружается из памяти. При этом вызывается функция WEP, выполняющая все необходимые завершающие действия.

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

Для того чтобы вызвать функцию из библиотеки, зная ее идентификатор, необходимо получить значение дальнего указателя на эту функцию, вызвав функцию GetProcAddress :

FARPROC WINAPI GetProcAddress(HINSTANCE hLibrary, 
  LPCSTR lpszProcName);

Через параметр hLibrary вы должны передать функции идентификатор DLL-библиотеки, полученный ранее от функции LoadLibrary.

Параметр lpszProcName является дальним указателем на строку, содержащую имя функции или ее порядковый номер, преобразованный макрокомандой MAKEINTRESOURCE.

Приведем фрагмент кода, в котором определяются адреса двух функций. В первом случае используется имя функции, а во втором - ее порядковый номер:

FARPROC lpMsg;
FARPROC lpTellMe;
lpMsg = GetProcAddress(hLib, "Msg");
lpTellMe = GetProcAddress(hLib, MAKEINTRESOURCE(8));

Перед тем как передать управление функции по полученному адресу, следует убедиться в том, что этот адрес не равен NULL:

if(lpMsg != (FARPROC)NULL)
{
  (*lpMsg)((LPSTR)"My message");
}

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

typedef int (FAR PASCAL *LPGETZ)(int x, int y);
LPGETZ lpGetZ;
lpGetZ = (LPGETZ)GetProcAddress(hLib, "GetZ");

А что произойдет, если приложение при помощи функции LoadLibrary попытается загрузить DLL-библиотеку, которой нет на диске?

В этом случае операционная система Windows выведет на экран диалоговую панель с сообщением о том, что она не может найти нужную DLL-библиотеку. В некоторых случаях появление такого сообщения нежелательно, так как либо вас не устраивает внешний вид этой диалоговой панели, либо по логике работы вашего приложения описанная ситуация является нормальной.

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

Приведем прототип функции SetErrorMode:

UINT WINAPI SetErrorMode(UINT fuErrorMode);

Эта функция позволяет отключать встроенный в Windows обработчик прерывания MS-DOS INT 24h (критическая ошибка). В качестве параметра этой функции можно указывать комбинацию следующих значений:

Значение Описание
SEM_FAILCRITICALERRORS Операционная система Windows не выводит на экран сообщение обработчика критических ошибок, возвращая приложению соответствующий код ошибки
SEM_NOGPFAULTERRORBOX На экран не выводится сообщение об ошибке защиты памяти. Этот флаг может использоваться только при отладке приложений, если они имеют собственный обработчик такой ошибки
SEM_NOOPENFILEERRORBOX Если Windows не может открыть файл, на экран не выводится диалоговая панель с сообщением об ошибке

Функция SetErrorMode возвращает предыдущий режим обработки ошибки.

Файл определения модуля для DLL-библиотеки

Файл определения модуля для DLL-библиотеки отличается от соответствующего файла обычного приложения Windows. В качестве примера приведем образец такого файла:

LIBRARY     DLLNAME
DESCRIPTION 'DLL-библиотека DLLNAME'
EXETYPE     windows
CODE        moveable discardable
DATA        moveable single
HEAPSIZE    1024
EXPORTS
  DrawBitmap  @4
  ShowAll     
  HideAll
  GetMyPool   @8
  FreeMyPool  @9

В файле определения модуля DLL-библиотеки вместо оператора NAME должен находиться оператор LIBRARY , определяющий имя модуля DLL-библиотеки, под которым она будет известна Windows.

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

Оператор CODE используется для определения атрибутов сегмента кода DLL-библиотеки.

Особенностью оператора DATA является использование параметра SINGLE. Так как DLL-библиотека находится в памяти в единственном экземпляре и может иметь только один сегмент данных, для описания сегмента данных библиотеки требуется параметр SINGLE.

Оператор HEAPSIZE определяет начальное значение локальной области памяти, которая будет выделена для DLL-библиотеки функцией LibEntry на этапе инициализации. Если DLL-библиотека не должна иметь локальную область данных, в качестве параметра следует указать нулевое значение:

HEAPSIZE 0

И, наконец, оператор EXPORTS предназначен для перечисления имен и порядковых номеров функций, экспортируемых DLL-библиотекой.

Анализ DLL-библиотек при помощи утилиты tdump.exe

В комплекте системы разработки Borland Turbo C++ for Windows входит утилита tdump.exe , предназначенная для работы в среде MS-DOS. С помощью этой утилиты вы сможете проанализировать содержимое любой DLL-библиотеки, определив имена экспортируемых функций, их порядковые номера, имена DLL-библиотек и номера функций, импортируемых из этих библиотек и т. д.

В системном каталоге Windows имеется DLL-библиотека toolhelp.dll, предназначенная для создания отладочных средств и работы с внутренними структурами данных Windows. Мы выбрали эту библиотеку для наших исследований в основном из-за ее небольшого размера. Описание самой библиотеки выходит за рамки данного тома "Библиотеки системного программиста".

Итак, скопируйте библиотеку в любой временный каталог и введите в среде MS-DOS (или в среде виртуальной машины MS-DOS) следующую команду:

tdump toolhelp.dll toolhelp.map

В результате в файл toolhelp.map будет записано подробное описание библиотеки toolhelp.dll. Мы приведем полученный листинг с нашими комментариями.

Turbo Dump  Version 3.1 Copyright (c) 1988, 1992 Borland International
                   Display of File TOOLHELP.DLL

В начале файла DLL-библиотеки находится заголовок в формате MS-DOS. Он нас не интересует.

Old Executable Header

DOS File Size                               3730h  ( 14128. )
Load Image Size                              359h  (   857. )
Relocation Table entry count                0000h  (     0. )
Relocation Table address                    0040h  (    64. )
Size of header record      (in paragraphs)  0004h  (     4. )
Minimum Memory Requirement (in paragraphs)  0000h  (     0. )
Maximum Memory Requirement (in paragraphs)  FFFFh  ( 65535. )
File load checksum                          0000h  (     0. )
Overlay Number                              0000h  (     0. )

Initial Stack Segment  (SS:SP)   0000:00B8
Program Entry Point    (CS:IP)   0000:0000

Далее в листинге приводится информация о заголовке загрузочного файла в формате Windows. Это так называемый заголовок нового исполняемого формата.

New Executable header
Operating system                                    Windows
File Load CRC                                       0AABAA86Bh
Program Entry Point   (CS:IP)      0001:016A
Initial Stack Pointer (SS:SP)      0000:0000
Auto Data Segment Index            0002h  (     2. )
Initial Local Heap Size            0200h  (   512. )
Initial Stack Size                 0000h  (     0. )
Segment count                      0002h  (     2. )
Module reference count             0002h  (     2. )
Moveable Entry Point Count         0000h  (     0. )
File alignment unit size           0010h  (    16. )
DOS File Size                      3730h  ( 14128. )
Linker Version                     5.20

Обратите внимание, что в заголовке присутствует информация об адресе точки входа (Program entry Point). Начальное содержимое указателя стека (Initial Stack Pointer) равно нулю, так же как и начальный размер стека (Initial Stack Size). Это понятно, так как DLL-библиотека не имеет собственного стека.

В то же время начальный размер локальной области данных отличен от нуля и равен 512 байт (Initial Local Heap Size). Из заголовка можно также определить, что модуль DLL-библиотеки состоит из двух сегментов (Segment Count).

Далее в листинге приведены различные флаги. В частности, видно, что сегмент данных DGROUP имеет атрибуты single и может использоваться совместно различными приложениями (shared). Данный модуль может работать только в защищенном режиме (Protected mode only) и является ни чем иным, как DLL-библиотекой (Module type - Dynamic link Library(DLL)).

Program Flags
    DGROUP                 : single (shared)
    Global initializaton   : No 
    Protected mode only    : Yes
    Application type       : Uses windowing API
    Self Loading           : No 
    Errors in image        : No 
    Module type            : Dynamic link Library (DLL)
Other EXE Flags
    2.X protected mode     : No 
    2.X proportional font  : No 
    Gangload area          : Yes
Start of Gangload Area                              03E0h
Length of Gangload Area                             3160h
Miminum code swap area size                         0
Expected Windows Version                            3.00

Затем в листинге перечисляются различные таблицы с указанием их смещения и размера:

Segment Table                Offset: 00C0h     Length: 0010h
Resource Table               Offset: 00D0h    Length: 0018h
Resident Names Table         Offset: 00E8h      Length: 0012h
Module Reference Table       Offset: 00FAh    Length: 0004h
Imported Names Table         Offset: 00FEh      Length: 000Dh
Entry Table                  Offset: 010Bh       Length: 0070h
Nonresident Names Table      Offset: 017Bh   Length: 0236h

Это таблица сегментов. В ней описаны два сегмента. Первый сегмент является сегментом кода, второй - сегментом данных.

Segment Table                   offset: 00C0h

    Segment Number: 01h
    Segment Type:   CODE     Alloc Size : 2EEEh
    Sector Offset:  0040h   File length: 2EEEh
    Attributes: Preloaded  Relocations  

    Segment Number: 02h
    Segment Type:   DATA     Alloc Size : 0120h
    Sector Offset:  033Eh   File length: 0120h
    Attributes: Sharable  Preloaded  

Далее приводится информация о ресурсах DLL-библиотеки. Таблица ресурсов описывает единственный ресурс типа Version info, описывающий версию модуля.

Resource Table                  offset: 00D0h

    Sector size: 0010h

    type: Version info
          Identifier: 1             
              offset: 03540h length: 01F0h
          Attributes: Moveable   Shareable       

После таблицы ресурсов описывается таблица резидентных имен. В ней перечисляются имена функций, которые загружаются в память вместе с библиотекой и находятся там до момента выгрузки библиотеки из памяти. В нашем случае в таблице резидентных имен описана единственная функция WEP.

Resident Name Table             offset: 00E8h
    Module Name: 'TOOLHELP'
    Name: WEP                          Entry: 0001

Таблица ссылок на модули содержит имена модулей (DLL-библиотек), на которые ссылаются функции, расположенные в исследуемой библиотеке. Как видно из листинга, функции библиотеки toolhelp.dll ссылаются на модули KERNEL и USER. Эти модули являются основными компонентами операционной системы Windows и расположены, соответственно, в файлах krnl386.exe, krnl286.exe и user.exe.

Module Reference Table          offset: 00FAh
    Module  1: KERNEL                                  
    Module  2: USER                                    


Imported Names Table            offset: 00FEh
    name                               offset
    KERNEL                              0001h
    USER                                0008h

Таблица входов (Entry Table) описывает экспортируемые функции. Для каждой функции приводится ее порядковый номер и смещение.

Entry Table                     offset: 010Bh
  Fixed Segment Records  (  1 Entries)    Segment: 0001h
    Entry    1: Offset: 018Ah   Exported   Single data

  Null Entries: 48

  Fixed Segment Records  ( 34 Entries)   Segment: 0001h
    Entry   50: Offset: 057Bh   Exported   Single data
    Entry   51: Offset: 0318h   Exported   Single data
    Entry   52: Offset: 0399h   Exported   Single data
    Entry   53: Offset: 02A2h   Exported   Single data
    Entry   54: Offset: 0417h   Exported   Single data
    Entry   55: Offset: 04A9h   Exported   Single data
    Entry   56: Offset: 090Eh   Exported   Single data
    Entry   57: Offset: 095Eh   Exported   Single data
    Entry   58: Offset: 09E9h   Exported   Single data
    Entry   59: Offset: 0A90h   Exported   Single data
    Entry   60: Offset: 0AD9h   Exported   Single data
    Entry   61: Offset: 0B15h   Exported   Single data
    Entry   62: Offset: 0B8Ch   Exported   Single data
    Entry   63: Offset: 0CAAh   Exported   Single data
    Entry   64: Offset: 0CEDh   Exported   Single data
    Entry   65: Offset: 0D2Eh   Exported   Single data
    Entry   66: Offset: 0F1Ch   Exported   Single data
    Entry   67: Offset: 0F67h   Exported   Single data
    Entry   68: Offset: 0FCAh   Exported   Single data
    Entry   69: Offset: 28B0h   Exported   Single data
    Entry   70: Offset: 2925h   Exported   Single data
    Entry   71: Offset: 11CEh   Exported   Single data
    Entry   72: Offset: 13F4h   Exported   Single data
    Entry   73: Offset: 1B72h   Exported   Single data
    Entry   74: Offset: 1C29h   Exported   Single data
    Entry   75: Offset: 2060h   Exported   Single data
    Entry   76: Offset: 2111h   Exported   Single data
    Entry   77: Offset: 26EAh   Exported   Single data
    Entry   78: Offset: 29C4h   Exported   Single data
    Entry   79: Offset: 2B6Ch   Exported   Single data
    Entry   80: Offset: 2DAEh   Exported   Single data
    Entry   81: Offset: 0D68h   Exported   Single data
    Entry   82: Offset: 0D97h   Exported   Single data
    Entry   83: Offset: 0DC0h   Exported   Single data

Имена и порядковые номера экспортируемых функций приведены в таблице нерезидентных имен (Non-Resident Name Table):

Non-Resident Name Table         offset: 017Bh
    Module Description: 'TOOLHELP - Debug/Tool Helper library'
    Name: TASKSETCSIP                  Entry:    81
    Name: MEMMANINFO                   Entry:    72
    Name: STACKTRACEFIRST              Entry:    66
    Name: MEMORYWRITE                  Entry:    79
    Name: GLOBALINFO                   Entry:    53
    Name: TASKNEXT                     Entry:    64
    Name: CLASSNEXT                    Entry:    70
    Name: GLOBALENTRYHANDLE            Entry:    54
    Name: GLOBALHANDLETOSEL            Entry:    50
    Name: INTERRUPTREGISTER            Entry:    75
    Name: STACKTRACECSIPFIRST          Entry:    67
    Name: LOCALNEXT                    Entry:    58
    Name: INTERRUPTUNREGISTER          Entry:    76
    Name: MODULENEXT                   Entry:    60
    Name: LOCALINFO                    Entry:    56
    Name: TASKFINDHANDLE               Entry:    65
    Name: TASKSWITCH                   Entry:    83
    Name: MEMORYREAD                   Entry:    78
    Name: NOTIFYREGISTER               Entry:    73
    Name: GLOBALNEXT                   Entry:    52
    Name: TIMERCOUNT                   Entry:    80
    Name: MODULEFINDHANDLE             Entry:    62
    Name: MODULEFIRST                  Entry:    59
    Name: GLOBALENTRYMODULE            Entry:    55
    Name: STACKTRACENEXT               Entry:    68
    Name: GLOBALFIRST                  Entry:    51
    Name: SYSTEMHEAPINFO               Entry:    71
    Name: TERMINATEAPP                 Entry:    77
    Name: TASKFIRST                    Entry:    63
    Name: NOTIFYUNREGISTER             Entry:    74
    Name: TASKGETCSIP                  Entry:    82
    Name: CLASSFIRST                   Entry:    69
    Name: MODULEFINDNAME               Entry:    61
    Name: LOCALFIRST                   Entry:    57

Далее в листинге описываются ссылки на импортируемые модули. Каждая такая ссылка состоит из имени модуля (в нашем случае это KERNEL или USER) и порядкового номера импортируемой функции. Сделав дамп файла krnl386.exe при помощи утилиты tdump.exe, вы сможете определить, что ссылке KERNEL.3 соответствует функция GetVersion, ссылке KERNEL.4 - функция LocalInit, а ссылке KERNEL.5 - функция LocalAlloc.

Segment Relocation Records

    Segment 0001h relocations

    type    offset    target
    BASE    2BBBh     0001h:0000h
    BASE    2E93h     0002h:0000h
    PTR     020Fh      KERNEL.3   
    PTR     0177h      KERNEL.4   
    PTR     27D9h      KERNEL.5   
    PTR     28A5h      KERNEL.7   
    PTR     2761h      KERNEL.137 
    OFFS    2D47h     KERNEL.114 
    BASE    0214h     KERNEL.18  
    PTR     2E66h      USER.13  
    PTR     0E2Bh      KERNEL.150 
    PTR     01EAh      KERNEL.28  
    PTR     27B0h      KERNEL.36  
    PTR     23E5h      KERNEL.170 
    PTR     223Dh      KERNEL.47  
    PTR     23FDh      KERNEL.176 
    PTR     224Fh      KERNEL.50  
    PTR     0B66h      USER.430 
    PTR     28F6h      USER.179 
    PTR     29A5h      KERNEL.72  
    OFFS    2EC1h     KERNEL.178 
    PTR     1D79h      KERNEL.202 
    OFFS    27EFh     0001h:16C5h
    LOBYTE  2ACEh   KERNEL.114  additive
    LOBYTE  2CA4h   KERNEL.114  additive
    Relocations: 25
[Назад] [Содеожание] [Дальше]