<
  • Главная
Статьи

Програмування на мові Delphi

  1. Глава 5. Динамічно завантажуються бібліотеки
  2. Опубліковано: 03.12.2005 Виправлено: 10.12.2016 Версія тексту: 1.0
  3. 5.1. Динамічно завантажувані бібліотеки
  4. 5.2. Розробка бібліотеки
  5. 5.2.2. експорт підпрограм
  6. 5.2.3. Угоди про виклик підпрограм
  7. 5.2.4. приклад бібліотеки
  8. 5.3. Використання бібліотеки в програмі
  9. 5.3.1. статичний імпорт
  10. 5.3.2. модуль імпорту
  11. 5.3.3. динамічний імпорт
  12. 5.4. Використання бібліотеки з програми на мові C ++
  13. 5.5. Глобальні змінні і константи
  14. 5.6. Ініціалізація і завершення роботи бібліотеки
  15. 5.7. Виняткові ситуації і помилки виконання підпрограм
  16. 5.8. Загальний менеджер пам'яті
  17. 5.9. Стандартні системні змінні
  18. 5.10. Підсумки

Глава 5. Динамічно завантажуються бібліотеки

автори: А.Н. Вальвачев
К.А. Сурков
Д.А. Сурков
Ю.М. Четирько
Опубліковано: 03.12.2005
Виправлено: 10.12.2016
Версія тексту: 1.0

До сих пір створювані нами програми були монолітними і фактично складалися з одного виконуваного файлу. Це, звичайно, дуже зручно, але не завжди ефективно. Якщо ви створюєте не одну програму, а кілька, і в кожній з них користуєтеся загальним набором підпрограм, то код цих підпрограм включається в кожну вашу програму. В результаті досить великі загальні частини коду починають дублюватися у всіх ваших програмах, невиправдано «роздуваючи» їх розміри. Підтримка програм ускладнюється, адже якщо ви виправили помилку в деякій підпрограмі, то вам доведеться перекомпілювати і переслати споживачеві цілком всі програми, які її використовують. Рішення проблеми напрошується само собою - перейти до модульної організації виконуваних файлів. У середовищі Delphi ця ідея реалізується за допомогою динамічно завантажуваних бібліотек. Техніка роботи з ними розглянута в цьому розділі.

5.1. Динамічно завантажувані бібліотеки

Динамічно завантажується бібліотека (від англ. Dynamically loadable library) - це бібліотека підпрограм, яка завантажується в оперативну пам'ять і підключається до використовує програму під час її роботи (а не під час компіляції і збірки). Файли динамічно завантажуваних бібліотек в середовищі Windows зазвичай мають розширення .dll (від англ. Dynamic-Link Library). Для стислості в цій главі ми будемо використовувати термін динамічна бібліотека, або навіть просто бібліотека, маючи на увазі DLL-бібліотеку.

Кілька різних програм можуть використовувати в роботі загальну динамічно завантажується бібліотеки. При цьому операційна система насправді завантажує в оперативну пам'ять лише одну копію бібліотеки і забезпечує спільний доступ до неї з боку всіх програм. Крім того, такі бібліотеки можуть динамічно завантажуватися і розвантажуватися з оперативної пам'яті по ходу роботи програми, звільняючи ресурси системи для інших завдань.

Одне з найважливіших призначень динамічно завантажуваних бібліотек - це взаємодія підпрограм, написаних на різних мовах програмування. Наприклад, ви можете вільно використовувати в середовищі Delphi динамічно завантажувані бібліотеки, розроблені в інших системах програмування за допомогою мов C і C ++. Справедливо і зворотне твердження - динамічно завантажувані бібліотеки, створені в середовищі Delphi, можна підключати до програм на інших мовах програмування.

5.2. Розробка бібліотеки

5.2.1. структура бібліотеки

За структурою вихідний текст бібліотеки схожий на вихідний текст програми, за винятком того, що текст бібліотеки починається з ключового слова library, а не слова program. наприклад:

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

Якщо в тілі бібліотеки оголошені деякі процедури,

procedure BubleSort (var Arr: array of Integer); procedure QuickSort (var Arr: array of Integer);

то це ще не означає, що вони автоматично стануть доступні для виклику ззовні. Для того щоб це дозволити, потрібно помістити імена процедур в спеціальну секцію exports, наприклад:

exports BubleSort, QuickSort;

Перераховані в секції exports процедури і функції відділяються комою, а в кінці всієї секції ставиться крапка з комою. Секцій exports може бути кілька, і вони можуть розташовуватися в програмі довільним чином.

Нижче наведено приклад вихідного тексту найпростішої динамічно завантажується бібліотеки SortLib. Вона містить єдину процедуру BubleSort, сортують масив цілих чисел методом «бульбашки»:

library SortLib; procedure BubleSort (var Arr: array of Integer); var I, J, T: Integer; begin for I: = Low (Arr) to High (Arr) - 1 do for J: = I + 1 to High (Arr) do if Arr [I]> Arr [J] then begin T: = Arr [I]; Arr [I]: = Arr [J]; Arr [J]: = T; end; end; exports BubleSort; begin end.

Оригінальний текст динамічно завантажується бібліотеки закінчується операційним блоком begin ... end, в який можна вставити будь-які оператори для підготовки бібліотеки до роботи. Ці оператори виконуються під час завантаження бібліотеки основною програмою. Наша найпростіша бібліотека SortLib не вимагає ніякої підготовки до роботи, тому її операційний блок порожній.

5.2.2. експорт підпрограм

Якби ми змогли зазирнути всередину компілює файлу бібліотеки, то виявили б, що кожна експортована підпрограма представлена ​​там унікальним символьним ім'ям. Ці імена зібрані в таблицю і використовуються при пошуку підпрограм - з їх допомогою виконується динамічна прив'язка записаних в програмі команд виклику до адрес відповідних процедур і функцій в бібліотеці. Як експортного імені може виступати будь-яка послідовність символів, причому між великими та малими літерами робиться відмінність.

У стандартному випадку експортне ім'я підпрограми вважається в точності таким, як її ідентифікатор в початковому тексті бібліотеки (з урахуванням великих і малих літер). Наприклад, якщо секція exports має такий вигляд,

то це означає, що експортне ім'я процедури буде 'BubleSort'. При бажанні це ім'я можна зробити ніж програмний імені, доповнивши опис директивою name, наприклад:

exports BubleSort name 'BubleSortIntegers';

У підсумку, експортне ім'я процедури BubleSort буде 'BubleSortIntegers'.

Експортні імена підпрограм повинні бути унікальні в межах бібліотеки, тому їх потрібно завжди вказувати явно для перевантажених (overload) процедур і функцій. Наприклад, якщо є дві перевантажені процедури із загальним ім'ям QuickSort,

procedure QuickSort (var Arr: array of Integer); overload; procedure QuickSort (var Arr: array of Real); overload;

то при експорті цих двох процедур необхідно явно вказати відмінні один від одного експортні імена:

exports QuickSort (var Arr: array of Integer) name 'QuickSortIntegers'; QuickSort (var Arr: array of Real) name 'QuickSortReals';

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

5.2.3. Угоди про виклик підпрограм

У розділі 2 ми вже коротко розповідали про те, що в різних мовах програмування використовуються різні правила виклику підпрограм, і що для сумісності з ними в мові Delphi існують директиви register, stdcall, pascal і cdecl. Застосування цих директив стає особливо актуальним при розробці динамічно завантажуваних бібліотек, які використовуються в програмах, написаних на інших мовах програмування.

Щоб розібратися із застосуванням директив, звернемося до механізму виклику підпрограм. Він заснований на використанні стека.

Стек - це область пам'яті, в яку дані містяться в прямому порядку, а й витягуються в зворотному, по аналогії з наповненням і спустошенням магазини патронів у стрілецької зброї. Черговість роботи з елементами в стеці позначається терміном LIFO (від англ. Last In, First Out - останнім увійшов, першим вийшов).

Існує ще звичайна черговість роботи з елементами, що позначається терміном FIFO (від англ. First In, First Out - першим увійшов, першим вийшов).

Для кожної програми на час роботи створюється свій стек. Через нього передаються параметри підпрограм і в ньому ж зберігаються адреси повернення з цих підпрограм. Саме завдяки стеку підпрограми можуть викликати один одного, або навіть рекурсивно самі себе.

Виклик підпрограми складається з «заштовхування» в стек всіх аргументів і адреси наступної команди (для воврат до неї), а потім передачі управління на початок підпрограми. Після закінчення роботи підпрограми з стека витягується адреса воврат з передачею управління на цю адресу; одночасно з цим з стека виштовхуються аргументи. Відбувається так зване очищення стека. Це загальна схема роботи і у неї бувають різні реалізації. Зокрема, аргументи можуть поміщатися в стек або в прямому порядку (зліва направо, як вони перераховані в описі підпрограми), або в зворотному порядку (справа наліво), або взагалі, не через стек, а через вільні регістри процесора для підвищення швидкості роботи. Крім того, очищення стека може виконувати або викликається підпрограма, або зухвала програма. Вибір конкретного угоди про виклик забезпечують директиви register, pascal, cdecl і stdcall. Їх сенс пояснює таблиця 5.1.

Директива Порядок занесення аргументів в стек Хто відповідає за очищення стека Передача аргументів через регістри register Зліва направо Підпрограма Так pascal Зліва направо Підпрограма Немає cdecl Справа наліво Зухвала програма Ні stdcall Справа наліво Підпрограма Ні Таблиця 5.1. Угоди про виклик підпрограм ПРИМІТКА

Директива register не означає, що всі аргументи обов'язково передаються через регістри процесора. Якщо число аргументів більше числа вільних регістрів, то частина аргументів передається через стек.

Виникає резонне питання: яка угода про виклик слід вибирати для процедур і функцій динамічно завантажуваних бібліотек. Відповідь - угода stdcall:

procedure BubleSort (var Arr: array of Integer); stdcall; procedure QuickSort (var Arr: array of Integer); stdcall;

Саме угода stdcall, спочатку призначене для виклику підпрограм операційної системи, найкраще підходить для взаємодії програм і бібліотек, написаних на різних мовах програмування. Всі програми так чи інакше використовують функції операційної системи, отже вони обов'язково підтримують угоду stdcall.

5.2.4. приклад бібліотеки

Збройні теорією, приступимо до практики - розробимо якусь корисну бібліотеку, а потім підключимо її до своєї програми. На цьому прикладі ми покажемо вам, як оформляється динамічно завантажувана бібліотека, складена з декількох програмних модулів.

Крок 1. Запустіть систему Delphi і виберіть в меню команду File | New | Other .... У діалоговому вікні, яке відкриється на екрані, виберіть значок з підписом DLL Wizard і натисніть кнопку OK (рисунок 5.1):

Малюнок 5
Малюнок 5.1. Вікно вибору нового проекту, в якому виділено пункт DLL Wizard

Середовище Delphi створить новий проект з наступною заготівлею бібліотеки:

library Project1; uses SysUtils, Classes; begin end.

Крок 2. За допомогою команди File | New | Unit створіть в проекті новий програмний модуль. Його заготівля буде виглядати наступним чином:

unit Unit1; interface implementation end.

Крок 3. Збережіть модуль під ім'ям SortUtils.pas, а проект - під ім'ям SortLib.dpr. Перейдемо до головного файлу проекту і видаліть з секції uses модулі SysUtils і Classes (вони зараз не потрібні). Головний програмний модуль повинен стати наступним:

library SortLib; uses SortUtils in 'SortUtils.pas'; begin end.

Крок 4. Наберіть вихідний текст модуля SortUtils:

unit SortUtils; interface procedure BubleSort (var Arr: array of Integer); stdcall; procedure QuickSort (var Arr: array of Integer); stdcall; exports BubleSort name 'BubleSortIntegers', QuickSort name 'QuickSortIntegers'; implementation procedure BubleSort (var Arr: array of Integer); var I, J, T: Integer; begin for I: = Low (Arr) to High (Arr) - 1 do for J: = I + 1 to High (Arr) do if Arr [I]> Arr [J] then begin T: = Arr [I]; Arr [I]: = Arr [J]; Arr [J]: = T; end; end; procedure QuickSortRange (var Arr: array of Integer; Low, High: Integer); var L, H, M: Integer; T: Integer; begin L: = Low; H: = High; M: = (L + H) div 2; repeat while Arr [L] <Arr [M] do L: = L + 1; while Arr [H]> Arr [M] do H: = H - 1; if L <= H then begin T: = Arr [L]; Arr [L]: = Arr [H]; Arr [H]: = T; if M = L then M: = H else if M = H then M: = L; L: = L + 1; H: = H - 1; end; until L> H; if H> Low then QuickSortRange (Arr, Low, H); if L <High then QuickSortRange (Arr, L, High); end; procedure QuickSort (var Arr: array of Integer); begin if Length (Arr)> 1 then QuickSortRange (Arr, Low (Arr), High (Arr)); end; end.

У цьому модулі процедури BubleSort і QuickSort сортують масив чисел двома способами: методом «бульбашки» і методом «швидкої» сортування відповідно. З їх реалізацією ми надаємо вам розібратися самостійно, а нас зараз цікавить правильне оформлення процедур для їх експорту з бібліотеки.

Директива stdcall, використана при оголошенні процедур BubleSort і QuickSort,

procedure BubleSort (var Arr: array of Integer); stdcall; procedure QuickSort (var Arr: array of Integer); stdcall;

дозволяє викликати процедури не тільки з програм на мові Delphi, але і з програм на мовах C / C ++ (далі ми покажемо, як це зробити).

Завдяки присутності в модулі секції exports,

exports BubleSort name 'BubleSortIntegers', QuickSort name 'QuickSortIntegers';

підключення модуля в головному файлі бібліотеки автоматично призводить до експорту процедур.

Крок 5. Збережіть всі файли проекту і виконайте компіляцію. В результаті ви отримаєте на диску в своєму робочому каталозі двійковий файл бібліотеки SortLib.dll. Відповідне розширення призначається файлу автоматично, але якщо ви бажаєте, щоб компілятор призначав інше розширення, скористайтеся командою меню Project | Options ... і у вікні Project Options на вкладці Application впишіть розширення файлу в поле Target file extension (рисунок 5.2).

Малюнок 5
Малюнок 5.2. Вікно настройки параметрів проекту

До речі, за допомогою полів LIB Prefix, LIB Suffix і LIB Version цього вікна ви можете задати правило формування імені файлу, який виходить при зборці бібліотеки. Файл складається за формулою:

<LIB Prefix> + <ім'я проекту> + <LIB Suffix> + '.' + <Target file extention> + [ '.' + <LIB Version>]

5.3. Використання бібліотеки в програмі

Для того щоб в прикладній програмі скористатися процедурами і функціями бібліотеки, необхідно виконати так званий імпорт. Імпорт забезпечить регулярне бібліотеки в оперативну пам'ять і прив'язку записаних в програмі команд виклику до адрес відповідних процедур і функцій бібліотеки. Існують два способи імпортування, що відрізняються за зручністю і гнучкості програмування:

  • статичний імпорт (забезпечується директивою компілятора external);
  • динамічний імпорт (забезпечується функціями LoadLibrary і GetProcAddress).

Статичний імпорт є більш зручним, а динамічний - більш гнучким.

5.3.1. статичний імпорт

При статичному імпорті всі дії по завантаженню та підключення бібліотеки виконуються автоматично операційною системою під час запуску головної програми. Щоб задіяти статичний імпорт, досить просто оголосити в програмі процедури і функції бібліотеки як зовнішні. Це робиться за допомогою директиви external, наприклад:

procedure BubleSortIntegers (var Arr: array of Integer); stdcall; external 'SortLib.dll'; procedure QuickSortIntegers (var Arr: array of Integer); stdcall; external 'SortLib.dll';

Після ключового слова external записується ім'я виконуваного файлу бібліотеки у вигляді константної рядки або константного строкового вираження. Разом з директивою external може використовуватися вже відома вам директива name, яка служить для явної вказівки експортного імені процедури в бібліотеці. З її допомогою оголошення процедур можна переписати по-іншому:

procedure BubleSort (var Arr: array of Integer); stdcall; external 'SortLib.dll' name 'BubleSortIntegers'; procedure QuickSort (var Arr: array of Integer); stdcall; external 'SortLib.dll' name 'QuickSortIntegers';

Помістивши в програму наведені вище оголошення, можна викликати процедури BubleSort і QuickSort, як ніби вони є частиною самої програми. Давайте це перевіримо.

Крок 6. Створіть нову консольную програму. Для цього виберіть в меню команду File | New | Other ... і в діалоговому вікні виділіть значок Console Application. Потім натисніть кнопку OK.

Крок 7. Додайте в програму external -оголошення процедур BubleSort і QuickSort, а також наберіть наведений нижче текст програми. Збережіть проект під ім'ям TestStaticImport.dpr.

program TestStaticImport; procedure BubleSort (var Arr: array of Integer); stdcall; external 'SortLib.dll' name 'BubleSortIntegers'; procedure QuickSort (var Arr: array of Integer); stdcall; external 'SortLib.dll' name 'QuickSortIntegers'; var Arr: array [0..9] of Integer; I: Integer; begin Randomize; for I: = Low (Arr) to High (Arr) do Arr [I]: = Random (100); BubleSort (Arr); for I: = Low (Arr) to High (Arr) do Write (Arr [I], ''); Writeln; for I: = Low (Arr) to High (Arr) do Arr [I]: = Random (100); QuickSort (Arr); for I: = Low (Arr) to High (Arr) do Write (Arr [I], ''); Writeln; Writeln ( 'Press Enter to exit ...'); Readln; end.

Крок 8. Виконайте компіляцію і запустіть програму. Якщо числа друкуються на екрані по зростанню, то сортування працює правильно.

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

5.3.2. модуль імпорту

При розробці динамічно завантажуваних бібліотек потрібно завжди думати про їх зручне використання. Давайте, наприклад, звернемося до останнього прикладу і припустимо, що в бібліотеці не дві процедури, а сотня, і потрібні вони не в одній програмі, а в декількох. В цьому випадку набагато зручніше винести external -оголошення процедур в окремий модуль, що підключається до всіх програм в секції uses. Такий модуль умовно називають модулем імпорту. Крім оголошень зовнішніх підпрограм він зазвичай містить визначення типів даних і констант, якими ці підпрограми оперують.

Модуль імпорту для бібліотеки SortLib буде виглядати так:

unit SortLib; interface procedure BubleSort (var Arr: array of Integer); stdcall; procedure QuickSort (var Arr: array of Integer); stdcall; implementation const DllName = 'SortLib.dll'; procedure BubleSort (var Arr: array of Integer); external DllName name 'BubleSortIntegers'; procedure QuickSort (var Arr: array of Integer); external DllName name 'QuickSortIntegers'; end.

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

5.3.3. динамічний імпорт

Дії по завантаженню та підключення бібліотеки (виконувані при статичному імпорті автоматично) можна виконати самостійно, звернувшись до стандартних функцій операційної системи. Таким чином, імпорт можна зробити динамічно під час роботи програми (а не під час її запуску).

Для динамічного імпорту необхідно завантажити бібліотеку в оперативну пам'ять викликом функції LoadLibrary, а потім витягти з неї адреси підпрограм за допомогою функції GetProcAddress. Отримані адреси потрібно зберегти в процедурних змінних відповідного типу. Після цього виклик підпрограм бібліотеки може виконуватися шляхом звернення до процедурних змінним. Для завершення роботи з бібліотекою необхідно викликати функцію FreeLibrary.

Нижче наведено короткий опис функцій LoadLibrary, FreeLibrary і GetProcAddress.

  • LoadLibrary (LibFileName: PChar): HModule - завантажує в оперативну пам'ять бібліотеку, яка зберігається на диску у файлі з ім'ям LibFileName. При успішному виконанні функція повертає числовий описатель бібліотеки, який повинен використовуватися в подальшому для управління бібліотекою. Якщо при завантаженні бібліотеки прізошла якась помилка, то повертається нульове значення. Якщо аргумент LibFileName містить ім'я файлу без маршруту, то цей файл шукається у наступних каталогах: в каталозі, з якого була запущена головна програма, в поточному каталозі, в системному каталозі операційної системи Windows (його точний маршрут можна дізнатися викликом функції GetSystemDirectory), в каталозі , за яким встановлена операційна система (його точний маршрут можна дізнатися викликом функції GetWindowsDirectory), а також в каталогах, перерахованих у змінній оточення PATH.
  • FreeLibrary (LibModule: HModule): Bool - вивантажує бібліотеку, задану описателем LibModule, з оперативної пам'яті і звільняє займані бібліотекою ресурси системи.
  • GetProcAddress (Module: HModule; ProcName: PChar): Pointer - повертає адресу підпрограми з ім'ям ProcName в бібліотеці з описателем Module. Якщо підпрограма з ім'ям ProcName в бібліотеці не існує, то функція повертає значення nil (порожній покажчик).

Наведена нижче програма TestDynamicImport аналогічна за функціональністю програмі TestStaticImport, але замість статичного імпорту використовує техніку динамічного імпорту:

program TestDynamicImport; uses Windows; type TBubleSortProc = procedure (var Arr: array of Integer); stdcall; TQuickSortProc = procedure (var Arr: array of Integer); stdcall; var BubleSort: TBubleSortProc; QuickSort: TQuickSortProc; LibHandle: HModule; Arr: array [0..9] of Integer; I: Integer; begin LibHandle: = LoadLibrary ( 'SortLib.dll'); if LibHandle <> 0 then begin @BubleSort: = GetProcAddress (LibHandle, 'BubleSortIntegers'); @QuickSort: = GetProcAddress (LibHandle, 'QuickSortIntegers'); if (@BubleSort <> nil) and (@QuickSort <> nil) then begin Randomize; for I: = Low (Arr) to High (Arr) do Arr [I]: = Random (100); BubleSort (Arr); for I: = Low (Arr) to High (Arr) do Write (Arr [I], ''); Writeln; for I: = Low (Arr) to High (Arr) do Arr [I]: = Random (100); QuickSort (Arr); for I: = Low (Arr) to High (Arr) do Write (Arr [I], ''); Writeln; end else Writeln ( 'Помилка відсутності процедури в бібліотеці.'); FreeLibrary (LibHandle); end else Writeln ( 'Помилка завантаження бібліотеки.'); Writeln ( 'Press Enter to exit ...'); Readln; end.

У програмі визначено два процедурних типу даних, які за списком параметрів і правилом виклику (stdcall) відповідають підпрограм сортування BubleSort і QuickSort в бібліотеці:

type TBubleSortProc = procedure (var Arr: array of Integer); stdcall; TQuickSortProc = procedure (var Arr: array of Integer); stdcall;

Ці типи даних потрібні для оголошення процедурних змінних, в яких зберігаються адреси підпрограм:

var BubleSort: TBubleSortProc; QuickSort: TQuickSortProc;

У секції var оголошена також змінна для зберігання целочисленного описателя бібліотеки, що повертається функцією LoadLibrary:

var ... LibHandle: HModule;

Програма починає свою роботу з того, що викликає функцію LoadLibrary, в яку передає ім'я файлу DLL-бібліотеки. Функція повертає описувач бібліотеки, який зберігається в змінної LibHandle.

LibHandle: = LoadLibrary ( 'SortLib.dll'); if LibHandle <> 0 then begin ... end

Якщо значення описателя відмінно від нуля, отже бібліотека була знайдена на диску і успішно завантажена в оперативну пам'ять. Переконавшись в цьому, програма звертається до функції GetProcAddress за адресами підпрограм. Отримані адреси зберігаються у відповідних процедурних змінних:

@BubleSort: = GetProcAddress (LibHandle, 'BubleSortIntegers'); @QuickSort: = GetProcAddress (LibHandle, 'QuickSortIntegers');

Зверніть увагу на використання символу @ перед іменем кожної змінної. Він говорить про те, що виконується не виклик підпрограми, а робота з її адресою.

Якщо ця адреса відрізняється від значення nil, значить підпрограма з вказаним ім'ям була знайдена в бібліотеці і її можна викликати шляхом звернення до процедурної змінної:

if (@BubleSort <> nil) and (@QuickSort <> nil) then begin ... BubleSort (Arr); ... QuickSort (Arr); ... end

Після закінчення сортування програма вивантажує бібліотеку викликом функції FreeLibrary.

Як ви переконалися, динамічний імпорт в порівнянні зі статичним вимагає значно більше зусиль на програмування, але він має ряд переваг:

  • Більш ефективне використання ресурсів оперативної пам'яті з тієї причини, що бібліотеку можна завантажувати і вивантажувати у міру потреби;
  • Динамічний імпорт допомагає в тих випадках, коли деякі процедури і функції можуть бути відсутні в бібліотеці. При статичному імпорті такі ситуації обробляє операційна система, яка видає повідомлення про помилку і припиняє роботу програми. Однак при динамічному імпорті програма сама вирішує, що їй робити, тому вона може відключити частину своїх можливостей і працювати далі.

Динамічний імпорт відмінно підходить для роботи з бібліотеками драйверів пристроїв. Він, наприклад, використовується самим середовищем Delphi для роботи з драйверами баз даних.

5.4. Використання бібліотеки з програми на мові C ++

Створені в середовищі Delphi бібліотеки можна використовувати в інших мовах програмування, наприклад в мові C ++. Мова C ++ набув широкого поширення як мова системного програмування, і в ряді випадків програмістам доводиться вдаватися до нього.

Нижче показано, як виконати імпорт підпрограм BubleSort і QuickSort в мові C ++.

extern "C" __declspec (dllimport) void __stdcall BubleSort (int * Array, int HighIndex); extern "C" __declspec (dllimport) void __stdcall QuickSort (int * Array, int HighIndex);

Аби не заглиблюватися в деталі синтаксису, зауважимо, що в мові C ++ відсутні відкриті масиви в параметрах підпрограм. Проте, програміст може викликати такі підпрограми, грунтуючись на тому, що відкритий масив неявно складається з двох параметрів: покажчика на початок масиву і номера останнього елемента.

5.5. Глобальні змінні і константи

Глобальні змінні і константи, оголошені в бібліотеці, не можуть бути експортовані, тому якщо необхідно забезпечити до них доступ з використовує програми, це потрібно робити за допомогою функцій, які повертають значення.

Незважаючи на те, що бібліотека може одночасно підключатися до декількох програмах, її глобальні змінні не є загальними і не можуть бути використані для обміну даними між програмами. На кожне підключення бібліотеки до програми, операційна система створює нове безліч глобальних змінних, тому бібліотеці здається, що вона працює лише з однією програмою. В результаті програмісти позбавлені від необхідності узгоджувати роботу декількох програм з одного бібліотекою.

5.6. Ініціалізація і завершення роботи бібліотеки

Ініціалізація бібліотеки відбувається при її підключенні до програми і полягає у виконанні секцій initialization у всіх складових бібліотеку модулях, а також в її головному програмному блоці. Завершення роботи бібліотеки відбувається при відключенні бібліотеки від програми; в цей момент в кожному модулі виконується секція finalization. Використовуйте цю можливість тоді, коли бібліотека запрошує і звільняє якісь системні ресурси, наприклад файли або з'єднання з базою даних. Запит ресурсу виконується в секції initialization, а його звільнення - в секції finalization.

Існує ще один спосіб ініціалізації і завершення бібліотеки, заснований на використанні зумовленої змінної DllProc. Мінлива DllProc зберігає адресу процедури, яка автоматично викликається при відключенні бібліотеки від програми, а також при створенні і знищенні паралельних потоків в програмах, що використовують DLL-бібліотеку (потоки обговорюються в главі 14). Нижче наведено приклад використання змінної DllProc:

library MyLib; var SaveDllProc: TDLLProc; procedure LibExit (Reason: Integer); begin if Reason = DLL_PROCESS_DETACH then begin ... end; SaveDllProc (Reason); end; begin ... SaveDllProc: = DllProc; DllProc: = @LibExit; end.

Процедура LibExit отримує один цілочисельний аргумент, який уточнює причину виклику. Можливі значення аргументу:

  • DLL_PROCESS_DETACH - відключення програми;
  • DLL_PROCESS_ATTACH - підключення програми;
  • DLL_THREAD_ATTACH - створення паралельного потоку;
  • DLL_THREAD_DETACH - завершення паралельного потоку.

Зверніть увагу, що установка значення змінної DllProc виконується в головному програмному блоці, причому попереднє значення зберігається для виклику "по ланцюжку".

Ми рекомендуємо вам вдаватися до змінної DllProc лише в тому випадку, якщо бібліотека повинна реагувати на створення і знищення паралельних потоків. У всіх інших випадках краще виконувати ініціалізацію і завершення за допомогою секцій initialization і finalization.

5.7. Виняткові ситуації і помилки виконання підпрограм

Для підтримки виняткових ситуацій середовище Delphi використовує засоби операційної системи Window. Тому, якщо в бібліотеці виникає виняткова ситуація, яка ніяк не обробляється, то вона передається викликає програмі. Програма може обробити цю виняткову ситуацію самим звичайним способом - за допомогою операторів try ... except ... end. Такі правила діють для програм і DLL-бібліотек, створених в середовищі Delphi. Якщо ж програма написана на іншій мові програмування, то вона повинна обробляти виключення в бібліотеці, написаної на мові Delphi як виняток операційної системи з кодом $ 0EEDFACE. Адреса інструкції, що викликала виняток, міститься в першому елементі, а об'єкт, що описує виключення, - в другому елементі масиву ExceptionInformation, який є частиною системної записи про виняткову ситуацію.

Якщо бібліотека не підключає модуль SysUtils, то обробка виняткових ситуацій недоступна. В цьому випадку при виникненні в бібліотеці будь-якої помилки відбувається завершення викликає програми, причому програма просто видаляється з пам'яті і код її завершення не виконується. Це може стати причиною побічних помилок, тому якщо ви вирішите не підключати до бібліотеки модуль SysUtils, подбайте про те, щоб виключення »не вислизали" з підпрограм бібліотеки.

5.8. Загальний менеджер пам'яті

Якщо виділення і звільнення динамічної пам'яті явно або неявно поділені між бібліотекою і програмою, то і в бібліотеці, і в програмі слід обов'язково підключити модуль ShareMem. Його потрібно вказати в секції uses першим, причому як в бібліотеці, так і в використовує її програмі.

Модуль ShareMem є модулем імпорту динамічно завантажується бібліотеки Borlndmm.dll, яка повинна поширюватися разом з вашою програмою. У момент ініціалізації модуль ShareMem виконує підміну стандартного менеджера пам'яті на менеджер пам'яті з бібліотеки Borlndmm.dll. Завдяки цьому бібліотека і програма можуть виділяти і звільняти пам'ять спільно.

Модуль ShareMem слід підключати ще й в тому випадку, якщо між бібліотекою і програмою відбувається передача довгих рядків або динамічних масивів. Оскільки довгі рядки і динамічні масиви розміщуються в динамічній пам'яті і управляються автоматично (шляхом підрахунку кількості посилань), то блоки пам'яті для них, що виділяються програмою, можуть звільнятися бібліотекою (а також навпаки). Використання єдиного менеджера пам'яті з бібліотеки Borlndmm.dll позбавляє програму і бібліотеку від прихованих руйнувань пам'яті.

ПРИМІТКА

Останнє правило не відноситься до відритим масивам-параметрам, які ми використовували в підпрограма BubleSort і QuickSort при створенні бібліотеки SortLib.dll.

5.9. Стандартні системні змінні

Як ви вже знаєте, у мові Delphi існує стандартний модуль System, неявно підключається до кожної програми або бібліотеці. У цьому модулі містяться зумовлені системні підпрограми і змінні. Серед них є змінна IsLibrary з типом Boolean, значення якої дорівнює True для бібліотеки і False для звичайної програми. Перевіривши значення змінної IsLibrary, підпрограма може визначити, чи є вона частиною бібліотеки.

У модулі System оголошена також мінлива CmdLine: PChar, що містить командний рядок, якій була запущена програма. Бібліотеки не можуть запускатися самостійно, тому для них змінна CmdLine завжди містить значення nil.

5.10. Підсумки

Прочитавши главу, ви напевно зітхнули з полегшенням. Життя стало легше: зробив одну унікальну за можливостями бібліотеку і вставляй її в усі програми! Потрібно підключити до Delphi-програми модуль з іншого середовища програмування - будь ласка! І все це робиться за допомогою динамічно завантажуваних бібліотек. Сподіваємося, ви освоїли техніку роботи з ними і подужаєте підключення до своєї программма бібліотек, написаних не тільки на мові Delphi, але і на мовах C і C ++. У наступному розділі ми розглянемо деякі інші взаємини між програмами, включаючи управління об'єктами однієї програми з іншої.

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


Новости
  • Виртуальный хостинг

    Виртуальный хостинг. Возможности сервера распределяются в равной мере между всеми... 
    Читать полностью

  • Редизайн сайта

    Редизайн сайта – это полное либо частичное обновление дизайна существующего сайта.... 
    Читать полностью

  • Консалтинг, услуги контент-менеджера

    Сопровождение любых интернет ресурсов;- Знание HTML и CSS- Поиск и обновление контента;-... 
    Читать полностью

  • Трафик из соцсетей

    Сравнительно дешевый способ по сравнению с поисковым и контекстным видами раскрутки... 
    Читать полностью

  • Поисковая оптимизация

    Поисковая оптимизация (англ. search engine optimization, SEO) — поднятие позиций сайта в результатах... 
    Читать полностью