Перейти на главную   
  helloworld.ru - документация и книги по программированию  
helloworld.ru - документация и книги по программированию
    главная     хостинг    
Поиск по сайту:  
Смотрите также
Языки программирования
C#
MS Visual C++
Borland C++
C++ Builder
Visual Basic
Quick Basic
Turbo Pascal
Delphi
JavaScript
Java
PHP
Perl
Assembler
AutoLisp
Fortran
Python
1C

Интернет-технологии
HTML
VRML
HTTP
CGI
FTP
Proxy
DNS
протоколы TCP/IP
Apache

Web-дизайн
HTML
Дизайн
VRML
PhotoShop
Cookie
CGI
SSI
CSS
ASP
PHP
Perl

Программирование игр
DirectDraw
DirectSound
Direct3D
OpenGL
3D-графика
Графика под DOS

Алгоритмы
Численные методы
Обработка данных

Сис. программирование
Драйверы

Базы данных
MySQL
SQL

Другое

Хостинг


Друзья
demaker.ru
Реклама

Лучший хостинг. Аренда серверов




helloworld.ru

Глава 11. Динамически компонуемые библиотеки

             Динамически компонуемые  библиотеки (DLL) позволяют несколь-
        ким прикладным программа Windows или DOS защищенного режима  сов-
        местно использовать код и ресурсы. В Borland Pascal вы можете как
        использовать существующие DLL,  так и написать  свои  собственные
        DLL, которые можно применять в других программах.

Что такое DLL?

DLL - это выполняемый модуль, содержащий программный код или ресурсы, используемые другими прикладными программами или DLL. Концептуально динамически компонуемая библиотека аналогичная мо- дулю - они обеспечивают для программ процедуры и функции. Однако между DLL и модулями имеются существенные различия. В частности, модули компонуются статически, а DLL - динамически. Когда программа использует процедуру или функцию из модуля, копия кода этой процедуры или функции статически компонуется с выполняемым файлом программы. Если две программы выполняются од- новременно и используют одну и ту же процедуру и функцию модуля, то в системе будет присутствовать две копии этой подпрограммы. Эффективнее было бы использовать одну копию. Такую возможность предоставляет DLL. В отличие от модуля DLL не компонуется с использующей DLL программой. Вместо этого код и ресурсы DLL находятся в отдельном выполняемом файле с расширением .DLL. Этот файл должен присутс- твовать при выполнении программы-клиента. Вызываемые программой процедуры и функции динамически компонуются со своими точками входа в используемой программе DLL. Другое отличие модулей от DLL состоит в том, что модули мо- гут экспортировать типы, константы, данные и объекты, а DLL - только процедуры и функции. Чтобы ее можно было использовать в программе Borland Pascal, DLL не обязательно должна быть написана на Borland Pascal. Кроме того, программы, написанные на других языках, могут использовать DLL, написанные на Borland Pascal. DLL, таким образом, идеально подходит при программных проектах, реализуемых на нескольких язы- ках.

Использование DLL

Чтобы модуль мог использовать процедуру или функцию в DLL, он должен импортировать процедуру или функцию с помощью описания external. Например, в следующем описании из DLL и именем KERNEL (ядро Windows) импортируется функция с именем GlobalAlloc: function GlobalAlloc(Glags: Word; Bytes: Longint): THandle; far; external 'KERNEL' index 15; В импортируемой процедуре или функции директива external за- нимает место описательной и операторной части, которые нужно было бы включить в противном случае. В импортируемых процедурах и функциях должна использоваться дальняя модель вызова, выбранная ключевым словом far или директивой компилятора {$F+}; во всем ос- тальном их поведение не отличается от обычных процедур и функций. Borland Pascal импортирует процедуры и функции тремя спосо- бами: - по имени; - по новому имени; - по порядковому номеру. Формат директив external для каждого из трех методов показан в приведенном ниже примере. Когда оператор index или name не указан, процедура или функ- ция экспортируются по имени. Это имя совпадает с идентификатором процедуры или функции. В данном примере процедура ImportByName импортируется из библиотеки 'TESTLIB' по имени 'IMPORTBYNAME': procedure ImportByName; external 'TESTLIB'; Когда задан оператор name, процедура или функция импортиру- ется под именем, отличным от имени идентификатора. В следующем примере процедура ImportByName импортируется из библиотеки 'TESTLIB' по имени 'REALNAME': procedure ImportByName; external 'TESTLIB'name 'REALNAME' Наконец, при наличии оператор index процедура или функция импортируется по порядковому значению. Такой вид импорта уменьша- ет время загрузки модуля, так как отпадает необходимость поиска имени в таблице имен DLL. В следующем примере процедура ImportByOrd импортируется из библиотеки 'TESTLIB': procedure ImportByOrd; external 'TESTLIB' index 5; Имя DLL задается после ключевого слова external, а новое имя, заданное в операторе name, не обязано представлять собой строковые литералы. Допускается любое строковое выражение-конс- танта. Аналогично, порядковый номер, задаваемый в операторе index, может быть любым целочисленным выражением-константой. const TestLib = TestLib; Ordinal = 5; procedure ImportByName; external TestLib; procedure ImportByName; external TestLibname 'REALNAME' procedure ImportByOrd; external TestLib index Ordinal; Хотя DLL может содержать переменные, импортировать их в дру- гие модули невозможно. Любой доступ к переменным DLL должен осу- ществляться через процедурный интерфейс.

Модули импорта

Описания импортируемых процедур и функций могут помещаться непосредственно в программу, которая их импортирует. Однако обыч- но они объединяются в модуль импорта, содержащий описания всех процедур и функций в DLL, а также все типы и константы, необходи- мые для интерфейса с DLL. Примерами таких модулей импорта являют- ся поставляемые с Borland Pascal модули WinTypes, WinProcs и WinAPI. Модули импорта не обязательны для интерфейса с DLL, но они значительно упрощают обслуживание использующих множество DLL проектов. В качестве примера рассмотрим DLL с именем DATETIME.DLL, со- держащую четыре подпрограммы для получения и установки даты и времени с помощью типа записи, содержащей число, месяц, год и за- писи, которая содержит секунду, минуту и час. Вместо спецификации соответствующих описаний процедуры, функции и типа в каждой ис- пользующей DLL программе вы можете построить наряду с DLL модуль импорта. В следующем примере создается файл .TPW (в предположе- нии, что целевой платформой является Windows), но отсутствуют код и данные для использующей его программы. unit DateTime; interface type TTimeRec = record Second: Integer; Minute: Integer; Hour: Integer; end; type TDateRec TDateRec = record Day: Integer; Month: Integer; Year: Integer; end; procedure SetTime(var Time: TTimeRec); procedure GetTime(var Time: TTimeRec); procedure SetDate(var Date: TDateRec); procedure GetDate(var Date: TDateRec); inplementation procedure SetTime; external 'DATETIME' index 1; procedure GetTime; external 'DATETIME' index 2; procedure SetDate; external 'DATETIME' index 3; procedure GetTime; external 'DATETIME' index 4; end. Любая программа, использующая DATETIME.DLL может теперь просто задать в своем операторе uses модуль DateTime. Приведем пример программы Windows: program ShowTime; uses WinCrt, DateTime; var Time: TTimeRec; begin GetTime(Time); with Time do WriteLn('Текущее время: ', Hour, ':', Minute, ':', Second); end. Другим преимуществом использования модуля импорта, такого как DateTime, является то, что при модификации DATETIME.DLL обно- вить требуется только модуль импорта DateTime. Когда вы компилируете использующую DLL программу, компилятор не ищет DLL, так что ее присутствие не требуется. Однако DLL должна присутствовать в системе при выполнении программы. Если вы пишете собственные DLL, они не компилируются автома- тически при компиляции использующей ее программы с помощью коман- ды Compile¦Make. DLL следует компилировать отдельно.

Статический и динамический импорт

Директива external обеспечивает возможность статического им- порта процедур и функций из DLL. Статически импортируемая проце- дура и функция всегда ссылается на одну и ту же точку входа в DLL. Расширения Windows и защищенного режима DOS Borland поддер- живает также динамический импорт, при котором имя DLL и имя или порядковый номер импортируемой процедуры или функции задается во время выполнения. Приведенная ниже программа ShowTime использует динамический импорт для вызова процедуры GetTime в DATETIME.DLL. Обратите внимание на использование переменной процедурного типа для представления адреса процедуры GetTime. program ShowTime; uses WinProcs, WinTypes, WinCrt; type TTimeRec = record Second: Integer; Minute: Integer; Hour: Integer; end; TGetTime = procedure(var Time: TTimeRec); var Time: TTimeRec; Handle: THAndle; GetTime: TGetTime; begin Handle := LoadLibrary('DATETIME.DLL'); if Handle >= 32 then begin @GetTie := GetProcAddress(Handle, 'GETTIME'); if @GetTime <> nil then begin GetTime(Time); with Time do WriteLn('Текущее время: ', Hour, ':', Minute, ':', Second); end; FreeLibrary(Handle); end; end;

Написание DLL

Структура DLL Borland Pascal идентичная структуре программы, но DLL начинается вместо заголовка program с заголовка program. Заголовок library указывает Borland Pascal, что нужно создать вы- полняемый файл с расширением .DLL, а не с расширением .EXE, и вы- полняемый файл помечается как DLL. библиотека ¦ ¦ -------------- ---- ------- L-->¦ заголовок +-->¦ ; +-T------------------¦ блок +-------> ¦ библиотеки ¦ L---- ¦ ----------- ^ L------- L-------------- L-->¦ оператор +-- ¦ uses ¦ L----------- ---------- ---------------- заголовок ---->¦ library +-->¦ идентификатор +-----> процедуры L---------- L---------------- В приведенном ниже примере приведена очень простую DLL с двумя экспортируемыми функциями Min и Max, которые вычисляют наи- меньшее и наибольшее из двух целочисленных значений. library MinMax; function Min(X, Y: Integer): Integer; export; begin if X < Y then Min := X else Min := Y; end; function Max(X, Y: Integer): Integer; export; begin if X > Y then Max := X else Max := Y; end; exports Min index 1, Max index 2; begin end. Обратите внимание на использование для подготовки Min и Max, для экспорта ключевого слова export, и на оператор exports, ис- пользуемый для фактического экспорта двух подпрограмм, указываю- щий, для каждой из них, необязательный порядковый номер. Хотя предыдущий пример этого не показывает, библиотека может состоять из нескольких модулей. В таких случаях исходный файл библиотеки часто сводится к оператору uses, оператору exports и коду инициализации библиотеки. Например: library Eritors; uses EdInit, EdInOut, EdFormat, EdPrint; exports InitEditors index 1, DoneEditors index 2, InsertText index 3, DeleteSelection index 4, FormatSelection index 5, PrintSelection index 6, . . . SetErrorHandler index 53; begin InitLibrary; end.

Директива процедуры export

Если процедуры и функции должны экспортироваться DLL, они должны компилироваться с директивой компилятора export. Директива export принадлежит к тому же семейству процедурных директив, что и near, far, inline и interrupt. Это означает, что директива export, если она присутствует, должна указываться перед первым заданием процедуры или функции - она не может указываться в опре- деляющем описании или в опережающем описании. Директива export делает процедуру или функцию экспортируе- мой. Она принудительно использует для подпрограммы дальний тип вызова и подготавливает ее для экспорта, генерируя для процедуры специальный код входа и выхода. Заметим, однако, что фактический экспорт процедуры или функции не происходит, пока подпрограмма не перечисляется в операторе exports библиотеки.

Оператор exports

Процедура или функция экспортируется DLL, когда она указыва- ется в операторе exports библиотеки. оператор exports ¦ ---------- ----------------- ---- L-->¦ exports +-->¦ список экспорта+----------->¦ ; +-------> L---------- L----------------- L---- ----------------- список экспорта --T->¦ запись экcпорта+-----------> ¦ L----------------- ^ ¦ ---- ¦ L------->¦ ; +---------- L---- оператор exports ¦ ---------------- L--->¦ идентификатор +--T------------------------------------ L---------------- ¦ -------- ------------------ ^ ¦ L-->¦ index +->¦ целая константа +-- ¦ L-------- L------------------ ¦ ---------------------------------------------------------------- L-T-------------------------------------T----------------------> ¦ ------- ---------------------- ^¦ ----------- ^ L>¦ name +-->¦ строковая константа +--L->¦ resident +--- L------- L---------------------- L----------- Оператор exports может встречаться в любом месте описатель- ной части программы или библиотеки и любое число раз. Каждая за- пись в операторе exports задает идентификатор экспортируемой про- цедуры или функции. Однако, эта процедура или функция должна опи- сываться до оператора exports, и ее описание должно содержать ди- рективу export. Перед идентификатором в операторе exports вы мо- жете указать идентификатор модуля с точкой; это называется пол- ностью уточненным идентификатором. Запись экспорта может также включать в себя оператор index, который состоит из ключевого слова index, за которым следует це- лочисленное значение в диапазоне от 1 до 32767. Когда задается оператор index, для экспортируемой процедуры или функции должно использоваться специальное порядковое значение. Если в записи экспорта оператор index отсутствует, то порядковое значение прис- ваивается автоматически. Запись может содержать оператор name, состоящий из ключевого слова name, за которым следует строковая константа. При наличии оператора name экспортируемая процедура или функция должна экс- портироваться с помощью задаваемого строковой константой имени. Если оператор name в записи экспорта отсутствует, то процедура или функция экспортируется по ее идентификатору (символы которого преобразуются в верхний регистр). Наконец, запись экспорта может включать в себя ключевое сло- во resident. При задании ключевого слова resident информация об экспорте остается в памяти, пока DLL загружена. Параметр resident существенно уменьшает время поиска подпрограммы в DLL по имени. Программа может содержать оператор exports, но это встреча- ется редко, так как Windows не позволяет прикладным программам экспортировать функции, используемые другие прикладными програм- мами.

Код инициализации библиотеки

Операторная часть библиотеки состоит из кода инициализации библиотеки. Код инициализации выполняется только один раз при первоначальной загрузке библиотеки. Когда другие прикладные прог- раммы будут использовать уже загруженную библиотеку, код инициа- лизации повторно не выполняется, но увеличивается счетчик исполь- зования DLL. DLL хранится в памяти, пока ее счетчик использования больше нуля. Когда счетчик использования становится нулевым, указывая, что все использующие DLL прикладные программы завершили работу, она удаляется из памяти. При этом выполняется код процедуры выхо- да. Процедуры выхода регистрируются с помощью переменной ExitProc, которая описывается в Главе 22 "Вопросы управления". Код инициализации DLL обычно выполняет такие задачи как ре- гистрация класса окна для содержащихся в DLL оконных процедур и установка начальных значений для глобальных переменных DLL. Уста- новив в нулевое значение переменную ExitCode, код инициализации библиотеки может указать состояние ошибки (ExitCode описывается в модуле System). По умолчанию ExitCode равна 1, что указывает на успешную инициализацию. Если код инициализации устанавливает зна- чение этой переменной в 0, то DLL выгружается из системной памя- ти, и вызывающая прикладная программа уведомляется о неудачной загрузке DLL. Когда выполняется библиотечная процедура выхода, переменная ExitCode не содержит код завершения процесса. Вместо этого ExitCode содержит одно из значений wep_System или wep_Free_DLL, определенных в модуле WinTypes. wep_System указывает на заверше- ние работы Windows, а wep_Free_DLL указывает на то, что выгружена данная DLL. Приведем пример библиотеки с кодом инициализации и процеду- рой выхода: library Test; {$S-} uses WinTypes, WinProcs; var SaveExit: Pointer; procedure LibExit; far; begin if ExitCode = wep_System_Exit then begin . . { выполняется завершение работы системы } . . . end else begin . . . { разгружается DLL } . . . end; ExitProcess : SaveExit; end; begin . . . { выполнить инициализацию DLL } . . . SaveExit := ExitProc; { сохранить старый указатель процедуры выхода } ExitProc := @LibExit; { установка процедуры выхода LibExit } end. В защищенном режиме DOS передаваемое процедуре выхода DLL значение ExitCode всегда равно 0 и соответствует wep_FREE_DLL. После разгрузки DLL экспортируемая функция вызывает процеду- ру WEP (процедура выхода Windows) DLL, если она присутствует. Библиотека Borland Pascal автоматически экспортирует функцию WEP, которая продолжает вызывать записанный в переменной ExitProc ад- рес, пока ExitProc не примет значения nil. Поскольку этот меха- низм процедур выхода соответствует работе с процедурами выхода в программах Borland Pascal, и в программах, и в библиотеках вы мо- жете использовать одну и ту же логику процедур выхода. Поскольку операционная система при завершении DLL переключа- ет внутренний стек, процедуры выхода в DLL должны компилироваться с запрещением проверки стека (в состоянии {$S-}). Кроме того, ес- ли в процедуре выхода DLL происходит ошибка этапа выполнения, операционная система аварийно завершает работу, поэтому вы для предотвращения ошибок этапа выполнения вы должны включить в свой код достаточное количество проверок.

Замечания по программированию библиотек

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

Глобальные переменные в DLL

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

Глобальные переменные и файлы в DLL

Как правило, DLL не является "владельцем" каких-либо откры- ваемых ей файлов или получаемых ей от системы глобальных блоков памяти. Такими объектами владеет (прямо или косвенно) сама прик- ладная программа, вызывающая DLL. Когда прикладная программа завершает работу, любые открытые файлы, владельцем которых она является, автоматически закрывают- ся, а все принадлежащие ей глобальные блоки памяти автоматически освобождаются. Это означает, что описатели данных файлов и блоков памяти, записанные в DLL в глобальных переменных, могут в любое время стать недопустимыми без уведомления DLL. По этой причине DLL не следует полагаться на допустимость описателя файла и гло- бальных описателей памяти, хранящихся между обращениями к DLL в глобальных переменных. Такие описатели следует сделать параметра- ми процедур и функций DLL, и вызывающая прикладная программа должна отвечать за их поддержку. В Windows глобальные блоки памяти, распределенные с атрибу- том gmem_DDEShare (определенные в модуле WinTypes), принадлежат DLL, а не вызывающим прикладным программам. Такие блоки памяти остаются распределенными, пока они явно не освобождаются DLL, или пока DLL не выгружается. Администратор памяти защищенного режима DOS не поддерживает совместно используемых блоков памяти и игнорирует флаг gmem_DDEShare. В защищенном режиме DOS распределяемые DLL блоки памяти всегда принадлежат вызывающей библиотеку DLL программе.

DLL и модуль System

В продолжении существования DLL переменная HInstance содер- жит описатель экземпляра DLL. Переменные FPrevInst и CmdShow в DLL всегда равны 0 (как и переменная PrefixSeg), поскольку DLL не имеет префикса программного сегмента (PSP). В прикладной програм- ме PrefixSeg никогда не равна 0, поэтому проверка PrefixSeg <> 0 возвращает True, если текущем модулем является прикладная прог- рамма, и False, если текущим модулем является DLL. Чтобы обеспечить правильную работу администратора динамичес- ки распределяемой области, содержащегося в модуле System, код за- пуска библиотеки устанавливает переменную HeapAllocFlags в значе- ние gmem_Moveable + gmem_DDEShare. В Windows это приводит к тому, что все блоки памяти, распределенные через процедуры New и GetMem, будут принадлежать DLL, а не вызывающей ее прикладной программе. Примечание: Подробности об администраторе памяти вы можете найти в Главе 21.

Ошибки этапа выполнения в DLL

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

DLL и сегменты стека

В отличие от прикладной программы DLL не имеет своего собс- твенного сегмента стека. Вместо этого она использует сегмент сте- ка вызывающей DLL прикладной программы. Это может создать пробле- мы в подпрограмме DLL, которые полагают, что регистры DS и SS ссылаются на один и тот же сегмент, как это имеет место в модуле прикладной программы Windows. Borland Pascal никогда не генерирует код, подразумевающий равенство DS = SS, и в библиотеке исполняющей системы Borland Pascal таких предположений не делается. Если вы пишете код на языке ассемблера, то не полагайтесь на то, что регистры DS и SS содержат одно и то же значение.

Создание совместно используемых DLL

Borland Pascal поддерживает DLL, которые могут совместно ис- пользоваться в защищенном режиме DOS и в Windows. Совместно ис- пользуемые DLL совместимы на уровне двоичного кода. Это означает, что один и тот же файл .DLL может использоваться в прикладной программе защищенного режима DOS или в прикладной программе Windows. При компиляции совместно используемой DLL в качестве целевой платформы нужно выбирать Windows: * В IDE выберите команду Compile¦Target и в диалоговом окне Target (Целевая платформа) укажите Windows. * При использовании компилятора, работающего в режиме ко- мандной строки, для выбора в качестве целевой платформы Windows используйте переключатель /CW. DLL, скомпилированная для защищенного режима DOS, под Windows использоваться не может, так как библиотека исполняющей системы защищенного режима DOS использует отдельные функциональ- ные вызовы DOS и DPMI, которые следует избегать в Windows. Совместно используемая DLL может взаимодействовать с опера- ционной системой (DOS защищенного режиме или Windows) только че- рез модуль WinAPI. Этот модуль представляет функции, общие для защищенного режима DOS и Windows. Другие интерфейсные модули Windows, такие как WinTypes и WinProcs, описывают большое число подпрограмм API, не поддерживаемых в защищенном режиме DOS. Примечание: О модуле WinAPI рассказывается в Главе 17 "Программирование в защищенном режиме DOS". Важно отметить, что хотя совместно используемая DLL может выполняться одновременно и под Windows, в окне защищенного режима Windows DOS, связь через DLL между двумя операционными средами невозможна. Реально в системе будет присутствовать две копии DLL, каждая из которых защищена от другой и использует полностью изо- лированную область памяти.

[ Назад | Оглавление | Далее ]










helloworld.ru © 2001-2021
Все права защищены