Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

OC Windows & OC Linux. Лабораторные работы по курсу «Операционные системы»

.pdf
Скачиваний:
0
Добавлен:
06.09.2026
Размер:
2 Мб
Скачать
Функция 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).
Когда вы запускаете несколько копий одного приложения, Win­dows загружает в оперативную память только одну копию кода и ресурсов. Для каждой копии приложения создается отдельный сег­мент данных, стек и очередь сообщений.
Задачу (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