Програмування на мові Delphi. Глава 5. Виконувані бібліотеки. Частина 1, Різне, Програмування, статті

попередня стаття серії


Зміст



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

Виконувані бібліотеки


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

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

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

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


Структура бібліотеки


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

library SortLib;

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

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

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 не вимагає ніякої підготовки до роботи, тому її операторний блок порожній.

Експорт підпрограм


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

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

exports
BubleSort;

то це означає, що експортне ім'я процедури буде '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;

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

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


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

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

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


ПРИМІТКА

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


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

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





























Директива


Порядок занесення аргументів у стек


Хто відповідає за очищення стека


Передача аргументів через регістри

register  Зліва направо Підпрограма Так
pascal  Зліва направо Підпрограма Немає
cdecl  Справа наліво Зухвала програма Немає
stdcall  Справа наліво Підпрограма Немає

Таблиця 1. Угоди про виклик підпрограм


ПРИМІТКА

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


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

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

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

Приклад бібліотеки


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

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

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

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

library Project1;

{ Important note about DLL memory management … }

uses
SysUtils,
Classes;

begin
end.


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

unit Unit1;

interface

implementation

end.


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

library SortLib;

{ Important note about DLL memory management … }

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 (Малюнок 2).

Рисунок 2. Вікно налаштування параметрів проекту

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

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

наступна стаття серії


Посилання по темі



Схожі статті:


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

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*