Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:OC Windows & OC Linux. Лабораторные работы по курсу «Операционные системы»
.pdf
Функция GetClassWord возвращает значение указанного слова
из структуры класса окна или нулевое значение при ошибке.
Функция SetClassLong аналогична функции SetClassWord, но
работает с двойными словами:
LONG WINAPI. SetClassLong(HWND hwnd, int offset, LONG nVal);
Для параметра offset дополнительно можно указать значение
GCL_WNDPROC, при этом функция заменит адрес функции окна.
1.7. Модели памяти
Для приложений Windows вы можете выбрать одну из четырех
моделей
два сегмента – сегмент кода и автоматический сегмент данных. Перед тем как передать управление приложению, Windows записывает
адрес сегмента кода в регистр CS, адрес автоматического сегмента
данных – в регистры DS и SS. Таким образом, в этой модели памяти
для стека
тот же сегмент.
мещаемым и удаляемым. Соответствующие атрибуты указываются в
файле определения модуля при помощи операторов CODE и DATA:
загружаются из соответствующего файла приложения, так что программисту
после того, как он был удален операционной системой.
дель памяти medium, в которой создается один сегмент данных и
несколько сегментов кода. Однако вызов дальней функции (а в модели памяти medium все функции вызываются как дальние) выполняется дольше, чем
механизма перемещения сегментов.
пользуя при описании функций и данных ключевые слова FAR и
NEAR. Заметим, что даже если ваше приложение было подготовлено в модели памяти small, оно на самом деле пользуется смешанной
памяти: small, medium, compact или large.
Для модели small при загрузке приложения в память создается
и автоматического сегмента данных используется один и
Сегмент кода, так же как и сегмент данных, может быть пере-
CODE preload moveable discardable
DATA preload moveable multiple
Удаляемые сегменты кода при необходимости автоматически
не надо самостоятельно восстанавливать сегмент кода
Для сложных приложений Windows удобно использовать мо-
в MS-DOS. Это связано с наличием в Windows
Чаще, однако, работают со смешанными моделями памяти, ис-
51

моделью памяти, так как все функции программного интерфейса
Windows определены как дальние.
Вы можете также использовать модели памяти compact (один
сегмент кода и несколько сегментов данных) и large (несколько сегментов кода и несколько сегментов данных). Но для этих моделей
есть одно существенное ограничение – можно запускать только одну
копию приложения, созданного
памяти. Если запустить несколько приложений, созданных, например, в модели памяти large, для каждой копии приложения будет
создан свой автоматический сегмент, но все остальные сегменты
кода и данных будут существовать в единственном экземпляре и
адресоваться всеми копиями приложения. Иными словами, все копии приложения будут иметь общие
ключая автоматический сегмент).
Если ваше приложение создано в модели памяти medium, имеет
смысл сгруппировать различные функции в несколько сегментов и
для каждого сегмента определить свои атрибуты. Например, функции инициализации приложения следует расположить в сегменте с
атрибутами PRELOAD и DISCARDABLE. В этом случае эти функции будут загружены
впоследствии будут удалены. Функции, обрабатывающие сообщения, должны быть загружены в память при инициализации приложения и находиться там постоянно, поэтому для них подойдет атрибут PRELOAD. Те функции, которые требуются эпизодически, можно загружать при необходимости и удалять после использования,
поэтому для них следует
DISCARDABLE.
Для назначения атрибутов сегментам приложения файл определения модуля должен содержать оператор SEGMENTS:
CODE preload moveable discardable
DATA preload moveable multiple
SEGMENTS
CODESEG1 moveable discardable
CODESEG2 preload
CODESEG3 loadoncall discardable
в памяти в процессе запуска приложения и
с использованием таких моделей
сегменты кода и данных (ис-
указать атрибуты LOADONCALL и
52

ЛАБОРАТОРНАЯ РАБОТА № 4
БИБЛИОТЕКИ ДИНАМИЧЕСКОЙ КОМПОНОВКИ
Введение
В Windows имеется много важнейших механизмов и систем,
например рассмотренная в лабораторной работе № 3 система управления памятью, интерфейс графических устройств GDI, система динамического обмена данными DDE, система управления шрифтами,
интерфейсы для мультимедиа, система динамической вставки и
привязки объектов OLE, и так далее, и так почти
до бесконечности.
Но вся операционная система Windows и все ее драйверы (кроме виртуальных), а также другие расширения в некотором смысле
есть ни что иное, как набор библиотек динамической компоновки.
Редкое крупное приложение Windows обходится без собственных
библиотек динамической компоновки, и ни одно приложение не
может обойтись без вызова функций, расположенных в таких
биб-
лиотеках. В частности, все функции программного интерфейса Win-
dows находятся именно в библиотеках динамической компоновки
DLL (Dynamic-Link Libraries).
Цель работы: ознакомить студентов с важнейшим механизмом,
лежащим в основе операционной системы Windows – механизмом
библиотек динамической компоновки DLL.
О
ПИСАНИЕ РАБОТЫ
1.1. Статическая и динамическая компоновки
Когда создавался загрузочный файл программы для MS-DOS,
мы готовили исходный текст приложения на своем любимом языке
программирования (Си, Паскаль, Модула-2 и т.д.), затем транслировали его для получения объектного модуля. После этого в дело
включался редактор связей, который компоновал объектные модули,
полученные в результате трансляции
исходных текстов и модули из
библиотек объектных модулей, в один ехе-файл. В процессе запуска
файл программы загружался полностью в оперативную память и ему
передавалось управление.
Таким образом, редактор связей записывал в файл программы
все модули, необходимые для работы. Для обращения к внешним по
отношению к программам модулям операционной системы MS-DOS
53

и модулям BIOS использовался механизм программных прерываний.
В любой момент времени в оперативной и постоянной памяти компьютера находился весь код, необходимый для работы как запущенной программы, так и самой операционной системы MS-DOS.
Если же программа была сложной и не помещалась целиком в
640 Кбайт памяти, на помощь приходили оверлеи, позволяющие загружать в оперативную память только те сегменты программы, которые требовались для решения текущей задачи.
В среде мультизадачной операционной системы, такой, как
Windows, OS/2 или UNIX, статическая компоновка неэффективна,
так как приводит к неэкономному использованию очень дефицитного ресурса – оперативной памяти. Представьте себе, что в системе
одновременно работают 5 приложений, и все они вызывают
функции, как sprintf, memcpy, strcmp и т.д. Если приложения были
собраны с использованием статической компоновки, в памяти будут
находится одновременно 5 копий функции sprintf, 5 копий функции
memcpy и т.д. А ведь программы могут использовать и более сложные функции, например предназначенные для работы с диалоговыми панелями или масштабируемыми шрифтами.
Очевидно, использование оперативной
эффективнее, если бы в памяти находилось только по одной копии
функций, а все работающие параллельно программы могли бы их
вызывать.
Практически в любой многозадачной операционной системе для
любого компьютера используется именно такой способ обращения к
функциям, нужным одновременно большому количеству работающих параллельно программ.
При использовании динамической
нескольких (или нескольких десятков) функций объединяется в отдельные файлы, загружаемые в оперативную память в единственном
экземпляре. Программы, работающие параллельно, вызывают функции, загруженные в память из файлов библиотек динамической компоновки, а не из файлов программ.
Таким образом, используя механизм динамической компоновки,
в загрузочном файле программы
функции, которые являются специфическими для данной программы. Те же функции, которые нужны всем (или многим) программам,
работающим параллельно, можно вынести в отдельные файлы –
библиотеки динамической компоновки – и хранить в памяти в един-
можно расположить только те
54
памяти было бы намного
компоновки загрузочный код
такие

ственном экземпляре. Эти файлы можно загружать в память только
при необходимости, например когда какая-нибудь программа захочет вызвать функцию, код которой расположен в библиотеке.
В операционной системе Windows файлы библиотек динамической компоновки (DLL-библиотеки) имеют расширение имени
dll, хотя можно использовать любое другое, например ехе. В первых версиях Windows DLL-библиотеки располагались
расширением имени ехе. Возможно поэтому файлы krnl286.exe,
krnl386. exe, gdi.exe и user.exe имеют расширение имени ехе, а не
dll, несмотря на то, что перечисленные выше файлы, составляющие
ядро операционной системы Windows, есть ни что иное, как DLLбиблиотеки.
1.2. DLL-библиотеки в операционной системе Windows
Формат DLL-библиотеки почти идентичен формату загрузочного модуля приложения Windows, однако вы не
DLL-библиотеку на выполнение, как обычное приложение. И это
понятно, так как назначение DLL-библиотек другое – они служат
хранилищем функций, вызываемых приложениями во время работы.
Функции, расположенные в DLL-библиотеках, могут вызывать функции, которые находятся в других библиотеках.
Для того чтобы вам были понятны отличия между приложением
и DLL-библиотекой, уточним
приложения (instance) и модуль (module).
Когда вы запускаете несколько копий одного приложения, Windows загружает в оперативную память только одну копию кода и
ресурсов. Для каждой копии приложения создается отдельный сегмент данных, стек и очередь сообщений.
Задачу (task) можно рассматривать как совокупность виртуального процессора и ресурсов, таких, как
глобальная память, очередь сообщений и т.д. В операционной системе Windows имеется база данных запущенных задач, в которой
хранится вся относящаяся к задаче информация, например идентификатор приложения, идентификатор модуля приложения и т.д.
Копия приложения (instance) является контекстом, в котором
выполняется модуль приложения. Идентификатор копии приложения,
WinMain, является идентификатором сегмента данных DGROUP,
используемого при выполнении программного модуля.
который вы получаете через параметр hInstance функции
такие понятия, как задача (task), копия
стек, регистры процессора,
55
можете «запустить»
в файлах с

Модуль приложения (module) состоит из исполнимого кода и
ресурсов. Внутри Windows есть база данных модулей, в которой
хранится такая информация, как ссылки на экспортируемые функции, расположение сегментов кода и сегментов, содержащих ресурсы. Так как модуль приложения не изменяется (сегменты кода и ресурсов не изменяются), все запущенные копии одного приложения
могут
им пользоваться.
DLL-библиотека также является модулем. Она находится в па-
мяти в единственном экземпляре, содержит сегменты кода и ресурсы, а также один сегмент данных. Можно сказать, что для DLL-библиотеки создается одна копия (instance), состоящая только из сегмента данных, и один модуль, состоящий из кода и ресурсов.
DLL-библиотека,
очереди сообщения.
Функции, расположенные в модуле DLL-библиотеки, выполняются в контексте вызвавшей их задачи. При этом они пользуются
стеком копии приложения, так как собственного стека в DLL-библиотеке не предусмотрено.
DLL-библиотека состоит из нескольких специфических функций и произвольного
которой разрабатывалась данная библиотека. Как мы уже говорили,
DLL-библиотека может иметь (а может и не иметь) сегмент данных
и ресурсы.
В заголовке загрузочного модуля DLL-библиотеки описаны
экспортируемые точки входа, соответствующие всем или некоторым
определенным в ней функциям. Приложения могут вызывать только
те функции DLL-библиотеки,
1.3.1. Функция LibEntry
До тех пор, пока ни одно из приложений не затребовало функцию из DLL-библиотеки, сама библиотека находится в файле на
диске. В оперативной памяти ее нет. Однако как только приложение
затребует функцию из DLL-библиотеки или загрузит библиотеку в
память явным образом, начнется процесс инициализации библиотеки
. Этот процесс выполняется только один раз, так как в памяти мо-
жет находиться только одна копия DLL-библиотеки.
в отличие от приложения, не имеет стека и
1.3. Структура DLL-библиотеки
набора функций, выполняющих ту работу, для
которые экспортируются ей.
56

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

ES:SI
Дальний указатель на строку параметров, которая может быть указана при явной загрузке DLL-библиотеки
(позже мы рассмотрим различные способы загрузки
DLL-библиотеки). Обычно этот параметр не использу-
ется
Вам не надо определять функцию LibEntry самостоятельно, так
как при создании файла DLL-библиотеки редактор связей, входящий
в систему разработки, включит уже имеющийся в стандартной библиотеке модуль. Этот стандартный модуль выполняет всю необходимую работу по инициализации локальной области памяти DLLбиблиотеки (с помощью функции LocalInit) и затем вызывает функцию LibMain, которая будет описана
в следующем разделе.
1.3.2. Функция 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-библиотеки).
58

Значение параметра wDataSegment соответствует содержимому регистра DS перед вызовом функции LibEntry.
Параметр wHeapSize содержит размер в байтах локальной области данных DLL-библиотеки. Если DLL-библиотека не имеет сегмента данных, в этом параметре находится нулевое значение.
И, наконец, через параметр lpszCmdLine передается указатель
на командную строку, которую можно передать DLL-библиотеке
при ее явной загрузке в память
.
Если инициализация DLL-библиотеки выполнена успешно,
функция LibMain должна возвратить ненулевое значение. Если в
процессе инициализации произошла ошибка, следует возвратить
нуль. В этом случае функция LibEntry также возвратит нуль. Это
приведет к тому, что Windows выгрузит библиотеку из памяти.
Разрабатывая процедуру инициализации DLL-библиотеки, учтите, что функция LibMain вызывается только один раз, во время
загрузки
библиотеки в память. Не следует думать, что для каждого
приложения, использующего одну и ту же DLL-библиотеку, будет
вызвана функция LibMain. При попытке повторной загрузки уже
загруженной ранее DLL-библиотеки будет увеличено содержимое
счетчика использования библиотеки, но и только.
Если для каждого приложения в локальной области данных
DLL-библиотеки необходимо выделять отдельные структуры данных, вам придется разработать собственную процедуру регистрации
приложения.
1.3.3. Функция 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.
59

Этот параметр указывает причину выгрузки DLL-библиотеки из
памяти. В первом случае выгрузка выполняется потому, что либо
функциями библиотеки не пользуется ни одно приложение, либо
одно из приложений выдало запрос на выгрузку. Если же параметр
имеет значение WEP_SYSTEM_EXIT, выгрузка библиотеки происходит из-за завершения работы операционной системы Windows.
Функция WEP должна всегда возвращать значение
1. Вам не
обязательно самостоятельно определять функцию WEP. Так же как
и функция LibEntry, функция WEP добавляется в DLL-библиотеку
транслятором. Вы, однако, можете определить собственную функцию WEP, если перед выгрузкой DLL-библиотеки требуется освободить заказанные ранее блоки памяти.
1.3.4. Экспортируемые функции
Кроме функций LibMain и WEP в DLL-библиотеке могут быть
определены и другие функции (как мы
уже говорили, существуют
DLL-библиотеки, состоящие из одних ресурсов). Это могут быть
экспортируемые и неэкспортируемые функции.
Экспортируемые функции доступны для вызова приложениям
Windows. Неэкспортируемые являются локальными для DLL-библи-
отеки, они доступны только для функций библиотеки.
Самый простой способ сделать функцию экспортируемой в системе разработки Borland Turbo C++ for Windows – указать в ее определении ключевое слово
_export. Мы использовали этот способ при
определении функции окна. Для экспортируемой функции создается
специальный пролог, необходимый для правильной загрузки сегментного регистра DS.
Есть и другой способ – можно перечислить все экспортируе-
мые функции в файле определения модуля при помощи оператора
EXPORTS:
EXPORTS
DrawBitmap @4
ShowAll
HideAll
GetMyPool @8
FreeMyPool @9
60
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
