DLL і Дельфи h2>
Думаю,
багато хто знає, що таке DLL (dynamic link library - runtime). У
бібліотек є чимало переваг, достатньо вагомих, що б їх використовувати. У
цій статті ми навчимося створювати і використовувати динамічні бібліотеки в
своїх проектах. p>
Навіщо
вони потрібні p>
А
навіщо ці самі бібліотеки мені потрібні? - Запитаєте ви. Ну я не знаю, може вони
вам взагалі не потрібні. А може і життєво необхідні. Перерахую можливості і
переваги бібліотек: p>
Універсальність.
Будь-який програміст, знаючи опису і назви функцій, що знаходяться в бібліотеці,
може використовувати їх. p>
Зручність
налагодження. Ви можете розмістити кілька важливих функцій в бібліотеці і оголосити
їх у програмі. При цьому ви будете працювати з бібліотекою, отлажівая ці
функцій, не чіпаючи основну програму. p>
Сховища
ресурсів .. У DLL можна зберігати ресурси, такі як малюнки, форми, іконки меню,
і т.д. p>
Погляд
на майбутнє. За допомогою бібліотек можна легко створювати плагіни, що розширюють
стандартні можливості програми. Тобто можна не випускати різні версії
програми, а випускати плагіни або модифіковані (наприклад з виправленими
помилками) версії бібліотек. p>
Спільне
використання. Якщо бібліотека завантажена, то її можуть використовувати й інші
додатки. p>
Економія
ресурсів. Бібліотеку можна завантажити і вивантажити тоді, коли це дійсно
необхідно. Наприклад, програма в потрібний момент завантажила DLL, викликала функцію,
зробила роботу і вивантажило бібліотеку до наступного разу. У наявності економія
пам'яті p>
Вобщем
DLL - звір корисний і дуже навіть готовий допомогти. P>
Структура
динамічної бібліотеки p>
Що
б створити бібліотеку в Delphi6 виберіть File -> New -> Other і в
вікні, що з'явилося виберіть DLL Wizard. Дельфі згенерує шаблон для
бібліотеки: p>
library Project; p>
(Important note about DLL memory
management: ShareMem must be the p>
first unit in your library "s
USES clause AND your project "s (select p>
Project-View Source) USES clause if
your DLL exports any procedures or p>
functions that pass strings as
parameters or function results. This p>
applies to all strings passed to and
from your DLL - even those that p>
are nested in records and classes.
ShareMem is the interface unit to p>
the BORLNDMM.DLL shared memory
manager, which must be deployed along p>
with your DLL. To avoid using BORLNDMM.DLL,
pass string information p>
using PChar or ShortString
parameters. ) p>
uses p>
SysUtils, p>
Classes; p>
($ R *. res) p>
begin p>
end. p>
В
коментарі вказується на необхідність вставити посилання на модуль ShareMem,
якщо бібліотека експортує довгі рядки в параметрах звернення до
підпрограм або як результат функцій. Це посилання має бути перша як в
пропозиції uses бібліотеки, так і в uses файлу проекту програми, яка
використовує цю бібліотеку. Якщо підпрограми бібліотеки експортують рядка
ShortString або PChar, посилаються на ShareMem не обов'язково. Що б не
виникало непорозумінь у своїх бібліотеках я рекомендую замість типу String
користуватися PChar, а за необхідності конвертуйте типи функціями PChar
(конветірует з String в PChar) і StrPas (конвертує з PChar в String). p>
Структура
бібліотеки схожа на структуру звичайного модуля. Тепер створіть бібліотеку з
таким текстом: p>
library Project2; p>
uses p>
SysUtils, p>
Classes; p>
function MyFunc (num1, num2, Errcode
: Integer; Operation: PChar): Integer; stdcall; p>
begin p>
try p>
if Operation = "plus" then p>
Result: = num1 + num2; p>
if Operation = "minus" then p>
Result: = num1-num2; p>
if Operation = "multiply"
then p>
Result: = num1 * num2; p>
if Operation = "div" then p>
Result: = num1 div num2; p>
if Operation = "mod" then p>
Result: = num1 mod num2; p>
except Result: = Errcode; p>
end; p>
end; p>
exports p>
MyFunc INDEX 1 NAME
"MathFunc"; p>
begin p>
end. p>
Збережіть
це все куди небудь і скомпіліруйте (Ctrl + F9) p>
Це
буде демонстраційна бібліотека, на якій я буду показувати різні
прийоми роботи з DLL. Але для початку давайте розглянемо текст цієї бібліотеки. P>
function
MyFunc (num1, num2, Errcode: Integer; Operation: PChar): Integer; - це
звичайна функція, що повертає ціле число. Грунтуючись на параметрі Operation
функція вирішує, яку операцію зробити над операндами num1 і num2. У разі
помилки вона повертає переданий їй параметр Errcode. Тобто в програмі можна
буде проаналізувати, чи виникла помилка під час виконання функції. p>
stdcall
вказує на те, що функція буде викликатися "класичним способом,
тобто програми, написані на інших мовах теж зможуть користуватися
бібліотекою. Можна використовувати - "register", призначеним тільки
для використання програмами, написаними в середовищі Дельфі, але тоді програми,
написані не в Дельфи не зможуть звертатися до цієї функції. p>
exports p>
MyFunc INDEX 1 NAME
"MathFunc"; p>
Розділ
Exports допомагає компілятору і компонувальника створити спеціальний заголовок
DLL-модуля, в якому перераховуються імена підпрограм і адреси їхніх точок входу.
У DLL може бути кілька списків Exports, але що перераховуються до них підпрограми
повинні бути описані десь вище по тексту бібліотеки. Крім імені підпрограми
в заголовок DLL міститься також її порядковий p>
номер
(INDEX), точніше, присвоєний їй цілочисельний індекс. Це дозволяє викликає
програмі посилатися не на ім'я, а на індекс підпрограми і тим самим зменшити
витрати часу на встановлення з нею зв'язку. Індекс присвоюється підпрограмі
по порядку її появи в списках Exports: перший підпрограма в першому списку
отримує індекс 0, наступна 1 і т. д. p>
Програміст
може змінити умовчуємо індексацію і явно вказати індекс підпрограми,
додавши за її ім'ям у списку Exports слово index і ціле число в діапазоні від
0 до 32767. Крім індексу можна вказати також і довільне (NAME) ім'я
функції. p>
Сподіваюся,
я зрозуміло пояснив;) Вобщем наша демонстраційна бібліотека готова. Тепер
давайте навчимося користуватися бібліотечними функціями p>
Використання
бібліотечних функцій p>
Використовувати
функції з бібліотеки можна двома способами: p>
1.
Прив'язка бібліотеки до програми (статична завантаження) p>
Недоліки: p>
--
немає ефекту економії ресурсів (бібліотека завантажується при запуску програми і
вивантажується при завершенні програми) p>
--
за відсутності хоча б однієї з необхідних бібліотек в папку з програмою,
або в папці $ windir $/system програма не запускається і видає повідомлення про
помилку p>
--
за відсутності хоча б однієї з необхідних функцій в бібліотеці під час запуску
програма видає повідомлення про помилку і не запускається p>
Переваги: p>
--
легкість використання p>
У
цього способу багато недоліків. Але все ж таки він буде корисний починаючим
програмістам. Для використання функцій або процедур з бібліотеки таким
способом потрібно всього лише в розділі implementation вказати ім'я функції або
процедури приблизно так: p>
// якщо функція p>
function FunctionName (Par1:
Par1Type; Par2: Par2Type; ParN: ParNType): ReturnType; stdcall; external
"MyDLL.dll" name "FunctionName" index FunctionIndex; p>
// якщо процедура p>
procedure ProcedureName (Par1:
Par1Type; Par2: Par2Type; ...); stdcall; external "MyDLL.dll" name
"ProcedureName" index ProcIndex; p>
Розглянемо оголошення функції. p>
function FunctionName (Par1:
Par1Type; Par2: Par2Type; ParN: ParNType): ReturnType; - Це власне оголошення функції p>
external
"MyDLL.dll" ця директива вказує на ім'я бібліотеки, з якої
буде викликана функція (у нашому випадку це MyDLL.dll) p>
name
"FunctionName" необьязательная директива, яка вказує на ім'я
функції в бібліотеці; використовується для підвищення швидкості доступу до функцій
(ім'я визначається всередині бібліотеки) p>
index
FunctionIndex теж необьязательная директива, яка використовується для прискорення
доступу до функцій; вказує на індекс функції (індекс оголошує в самій
бібліотеці). p>
Розглядати
оголошення процедури не має сенсу, тому що процедурв викликається точно так само
(за винятком того, що у процедура нічого не повертає). От і все! Тепер
можна користуватися обьявлений функції в межах модуля, в якому вона була
об'явлена. p>
Розглянемо
приклад на основі нашої демонстраційної бібліотеки, яку ми скомпілював
вище. p>
Створити
новий проект Project1 і на його форму помістіть чотири поля Edit. Дайте їм
такі імена: Num1Edit, Num2Edit, OpEdit, ResultEdit. Так само помістіть одну
кнопку, ім'я якої значення не має. У розділі implementation оголосити функцію: p>
implementation p>
function MyFunc (num1, num2, Errcode
: Integer; Operation: PChar): Integer; stdcall; external
"Project2.dll" name "MathFunc" index 1; p>
А
обробник єдиною кнопки приведіть до приблизно такого виду: p>
procedure
TForm1.DoItButtonClick (Sender: TObject); p>
const p>
Errcode
: Integer = 978987;// код помилки - може бути абсолютно будь-яким. P>
var p>
Num1, Num2, Result_: Integer;// для перевірки чисел p>
Operation
: String;// операція, для передачі параметра функції p>
begin p>
try
//перш ніж передати числа p>
Num1
: = StrToInt (Num1Edit.Text);// функції перевіримо їх p>
Num2: = StrToInt (Num2Edit.Text); p>
except p>
Num1Edit.Text: = "0"; p>
Num2Edit.Text: = "0"; p>
ResultEdit.Text: = "Введіть цілі ЧІСЛA"; p>
EXIT; p>
end; p>
Operation
: = OpEdit.Text;// також перевіримо, введена чи правильна команда. P>
if (Operation "plus") and (Operation "minus") and (Operation "multiply") p>
and (Operation "div") and (Operation "mod")
then p>
begin p>
ResultEdit.Text
: = "Введіть коректну команду"; p>
Exit; p>
end; p>
Result_
: = MyFunc (Num1, Num2, Errcode, PChar (Operation));// використання бібліотечної
функції p>
if
Result_ = Errcode then// якщо функція повернула код помилки то p>
begin
//то повідомляємо про це. p>
ResultEdit.Text
: = "ПОМИЛКА"; p>
EXIT; p>
end p>
else
//а якщо результат відмінний від коду помилки p>
ResultEdit.Text: =
IntToStr (Result_);// то виводимо його p>
end; p>
Зверніть
увагу, що ми використовуємо функцію з бібліотеки так само, як і коли вона була
б написана в модулі. Ще раз повторюю, що при прив'язці бібліотеки до програми
функцію можна використовувати тільки в тих модулях, в яких вона була об'явлена.
Ось вам міні калькулятор, який працює на (хотів було сказати на
батарейках) DLL. p>
2.
Динамічне завантаження p>
Недоліки: p>
--
громіздкість і складність коду p>
--
функції бібліотеки доступні тільки тоді, коли бібліотека завантажена в пам'ять p>
Переваги: p>
--
начисто позбавлений всіх недоліків першого способу + деякі інші переваги
перед першим способом p>
Цей
спосіб досить складний, особливо для новачків. Але переваг перед першим
способом у нього набагато більше. Для роботи з динамічно завантажуваними бібліотеками
просто необхідно знати три WinAPI функції: LoadLibrary, GetProcAddress І
FreeLibrary. P>
LoadLibrary (LibFileName:
PChar) - завантажує бібліотеку LibFileName в пам'ять. Якщо бібліотека завантажена
вдало, то функція повертає дескриптор (THandle) DLL в пам'яті. p>
GetProcAddress (Module: THandle;
ProcName: PChar) - знаходить точку входу у функцію ProcName. Увага! Тут потрібно вказати
NAME функції, а не її назва. Якщо функція знайдено, то функція GetProcAddress
повертає дескриптор (TFarProc) функції в завантаженої DLL. p>
FreeLibrary (LibModule: THandle) - вивантажує бібліотеку LibModule. При цьому вся зайнята цієї
бібліотекою пам'ять звільняється. Слід зауважити, що після виклику цієї
процедури функції даної бібліотеки більше і було звернення до них викличе
виняток. p>
Для
того, що б динамічно завантажити функцію з бібліотеки, то необхідно її
оголосити у роздiлi var: p>
MyFunc: function (num1, num2, Errcode
: Integer; Operation: PChar): Integer; stdcall; p>
Також
потрібно оголосити змінну типу THandle. "На пальцях" не обьяснить,
тому давайте розглянемо приклад динамічної завантаження DLL на основі нашої
демонстраційної бібліотеки. p>
Відкрийте
попередній проект з демонстрацією статичної завантаження. У розділі var оголосити
пару нових змінних: p>
LibHandle: THandle; p>
MyFunc: function (num1, num2, Errcode
: Integer; Operation: PChar): Integer; stdcall; p>
Оброблювач
кнопки приведіть до такого виду: p>
procedure
TForm1.DoItButtonClick (Sender: TObject); p>
const p>
Errcode
: Integer = 978987;// код помилки - може бути абсолютно будь-яким. P>
var p>
Num1, Num2, Result_: Integer;// для перевірки чисел p>
Operation
: String;// операція, для передачі параметра функції p>
begin p>
try
//перш ніж передати числа p>
Num1
: = StrToInt (Num1Edit.Text);// функції перевіримо їх p>
Num2: = StrToInt (Num2Edit.Text); p>
except p>
Num1Edit.Text: = "0"; p>
Num2Edit.Text: = "0"; p>
ResultEdit.Text: = "Введіть ЧІСЛA"; p>
EXIT; p>
end; p>
Operation
: = OpEdit.Text;// також перевіримо, введена чи правильна команда. P>
if
(Operation "plus") and (Operation "minus") and (Operation "multiply") p>
and (Operation "div") and (Operation "mod")
then p>
begin p>
ResultEdit.Text
: = "Введіть коректну команду"; p>
Exit; p>
end; p>
// до
цього моменту код залишився без змін. p>
@ MyFunc
: = Nil;// очищаємо адреса функції p>
LibHandle
: = LoadLibrary ( "Project2.dll ");// намагаємося завантажити бібліотеку p>
if
LibHandle> = 32 then p>
begin
//якщо все пройшло успішно то p>
@ MyFunc
: = GetProcAddress (LibHandle, "MathFunc ");// намагаємося знайти адресу
функції p>
if
@ MyFunc nil then// якщо адреса знайдений (функція існує в бібліотеці) p>
Result_
: = MyFunc (Num1, Num2, Errcode, PChar (Operation));// використання бібліотечної
функції p>
if
Result_ = Errcode then// якщо функція повернула код помилки то p>
begin
//то повідомляємо про це. p>
ResultEdit.Text
: = "ПОМИЛКА"; p>
EXIT; p>
end p>
else
//а якщо результат відмінний від коду помилки p>
ResultEdit.Text: =
IntToStr (Result_);// то виводимо його) p>
end; p>
end; p>
Висновок p>
В
цій статті ми торкнулися лише основних аспектів програмування із застосуванням
динамічно-підключаються бібліотек. Але ж в DLL можна зберігати всякі картинки
і навіть форми! За допомогою них зручно створювати всякі плагіни. Але це вже зовсім
інша історія p>
Список літератури h2>
Для
підготовки даної роботи були використані матеріали з сайту http://www.soch.imperium.by
p>