Использование и создание DLL в Delphi
Введение
В связи с бурным развитием технологий программирования, все больше людей
сталкиваются с проблемой наращивания возможностей своих программ. Данная
статья посвящена именно этому вопросу, а именно - программирование DLL в
Borland Delphi. Кроме того, так как мы затронем вопросы по использованию
библиотек DLL, то попутно коснемся импортирования функций из чужих DLL (в том
числе и системных, т.е. WinAPI).
Области применения DLL
Итак, зачем же нужны библиотеки DLL и где они используются?.. Перечислим
лишь некоторые из областей их применения:
- Отдельные библиотеки, содержащие полезные для программистов
дополнительные функции. Например, функции для работы со строками, или же -
сложные библиотеки для преобразования изображений.
- Хранилища ресурсов. В DLL можно хранить не только программы и
функции, но и всевозможные ресурсы - иконки, рисунки, строковые массивы,
меню, и т.д.
- Библиотеки поддержки. В качестве примера можно привести
библиотеки таких известных пакетов, как: DirectX, ICQAPI (API
для ICQ), OpenGL и т.д.
- Части программы. Например, в DLL можно хранить окна программы
(формы), и т.п.
- Плагины (Plugins). - Вот где настоящий простор для мыслей
программиста! Плагины - дополнения к программе, расширяющие ее возможности.
Например, в этой статье мы рассмотрим теорию создания плагина для
собственной программы.
- Разделяемый ресурс. DLL (Dynamic Link Library) может быть
использована сразу несколькими программами или процессами (т.н.
sharing - разделяемый ресурс)
Краткое описание функций и приемов для работы с DLL
Итак, какие же приемы и функции необходимо использовать, чтобы работать с
DLL? Разберем два метода импортирования функций из библиотеки:
1 способ. Привязка DLL к программе. Это наиболее
простой и легкий метод для использования функций, импортируемых из DLL. Однако
(и на это следует обратить внимание) этот способ имеет очень весомый
недостаток - если библиотека, которую использует программа, не будет найдена,
то программа просто не запустится, выдавая ошибку и сообщая о том, что ресурс
DLL не найден. А поиск библиотеки будет вестись: в текущем каталоге, в
каталоге программы, в каталоге WINDOWS\SYSTEM, и т.д. Итак, для начала -
общая форма этого
приема:
implementation ... function
FunctionName(Par1: Par1Type; Par2: Par2Type; ...): ReturnType; stdcall;
external 'DLLNAME.DLL' name 'FunctionName' index
FuncIndex; // или (если не функция, а
процедура): procedure ProcedureName(Par1: Par1Type; Par2:
Par2Type; ...); stdcall; external 'DLLNAME.DLL' name
'ProcedureName' index ProcIndex;
Здесь: FunctionName (либо ProcedureName) - имя функции (или
процедуры), которое будет использоваться в Вашей программе; Par1, Par2,
... - имена параметров функции или процедуры; Par1Type, Par2Type,
... - типы параметров функции или процедуры (например,
Integer); ReturnType - тип возвращаемого значения (только для
функции); stdcall - директива, которая должна точно совпадать с
используемой в самой DLL; external 'DLLNAME.DLL' - директива,
указывающая имя внешней DLL, из которой будет импортирована данная функция или
процедура (в данном случае - DLLNAME.DLL); name 'FunctionName'
('ProcedureName') - директива, указывающая точное имя функции в самой DLL.
Это необязательная директива, которая позволяет использовать в программе
функцию, имеющую название, отличное от истинного (которое она имеет в
библиотеке); index FunctionIndex (ProcedureIndex) - директива,
указывающая порядковый номер функции или процедуры в DLL. Это также
необязательная директива.
2 способ. Динамическая загрузка DLL. Это гораздо более
сложный, но и более элегантный метод. Он лишен недостатка первого метода.
Единственное, что неприятно - объем кода, необходимого для осуществления этого
приема, причем сложность в том, что функция, импортируемая из DLL достуна лишь
тогда, когда эта DLL загружена и находится в памяти... С примером можно
ознакомиться ниже, а пока - краткое описание используемых этим методом функций
WinAPI:
LoadLibrary(LibFileName: PChar) - загрузка
указанной библиотеки LibFileName в память. При успешном завершении функция
возвращает дескриптор (THandle) DLL в
памяти. GetProcAddress(Module: THandle; ProcName:
PChar) - считывает адpес экспоpтиpованной библиотечной функции. При
успешном завершении функция возвращает дескриптор (TFarProc) функции в
загруженной DLL. FreeLibrary(LibModule: THandle) - делает
недействительным LibModule и освобождает связанную с ним память. Следует
заметить, что после вызова этой процедуры функции данной библиотеки больше
недоступны.
Практика и примеры
Ну а теперь пора привести пару примеров использования вышеперечисленных
методов и приемов:
Пример 1. Привязка DLL к программе |
{... Здесь идет заголовок файла и
определение формы TForm1 и ее экземпляра
Form1}
implementation
{Определяем внешнюю библиотечную
функцию}
function GetSimpleText(LangRus:
Boolean): PChar; stdcall; external 'MYDLL.DLL';
procedure
Button1Click(Sender: TObject); begin {И используем
ее} ShowMessage(StrPas(GetSimpleText(True))); ShowMessage(StrPas(GetSimpleText(False))); {ShowMessage - показывает диалоговое окно с указанной
надписью; StrPas - преобразует строку PChar в
string} end;
|
Теперь то же самое, но вторым способом - с динамической загрузкой:
Пример 2. Динамическая загрузка DLL |
{... Здесь идет заголовок файла и
определение формы TForm1 и ее экземпляра
Form1}
var
Form1: TForm1; GetSimpleText:
function(LangRus: Boolean):
PChar; LibHandle:
THandle;
procedure Button1Click(Sender:
TObject); begin {"Чистим" адрес
функции от "грязи"} @GetSimpleText :=
nil; {Пытаемся загрузить
библиотеку} LibHandle :=
LoadLibrary('MYDLL.DLL'); {Если все
OK} if LibHandle >= 32 then
begin {...то пытаемся
получить адрес функции в
библиотеке} @GetSimpleText :=
GetProcAddress(LibHandle,'GetSimpleText'); {Если и здесь все
OK} if @GetSimpleText <> nil
then {...то
вызываем эту функцию и показываем
результат} ShowMessage(StrPas(GetSimpleText(True))); end; {И не забываем освободить память и выгрузить
DLL} FreeLibrary(LibHandle); end;
|
ПРИМЕЧАНИЕ: Следует воздерживаться от использования типа
string в библиотечных функциях, т.к. при его использовании существуют проблемы
с "разделением памяти". Подробней об этом можно прочитать (правда, на
английском) в тексте пустого проекта DLL, который создает Delphi (File ->
New -> DLL). Так что лучше используйте PChar, а затем при необходимости
конвертируйте его в string функцией StrPas.
Ну а теперь разберем непосредственно саму библиотеку DLL:
Пример 3. Исходник проекта MYDLL.DPR |
library mydll;
uses SysUtils,
Classes;
{Определяем функцию как
stdcall} function GetSimpleText(LangRus: Boolean):
PChar; stdcall; begin {В зависимости от LangRus возвращаем русскую (True) либо
английскую (False) фразу} if LangRus
then Result := PChar('Здравствуй,
мир!') else Result :=
PChar('Hello, world!'); end;
{Директива exports указывает, какие функции будут
экспортированы этой DLL} exports
GetSimpleText;
begin end.
|
Размещение в DLL ресурсов и форм
В DLL можно размещать не только функции, но и курсоры, рисунки, иконки,
меню, текстовые строки. На этом мы останавливаться не будем. Замечу лишь, что
для загрузки ресурса нужно загрузить DLL, а затем, получив ее дескриптор, -
загружать сам ресурс соотвествующей функцией (LoadIcon, LoadCursor, и т.д.). В
этом разделе мы лишь немного затронем размещение в библиотеках DLL окон
приложения (т.е. форм в Дельфи).
Для этого нужно создать новую DLL и добавить в нее новую форму (File ->
New -> DLL, а затем - File -> New Form). Далее, если форма представляет
собой диалоговое окно (модальную форму (bsDialog)), то добавляем в DLL
следующую функцию (допустим, форма называется Form1, а ее класс - TForm1):
Пример 4. Размещение формы в DLL |
function ShowMyDialog(Msg: PChar): Boolean;
stdcall;
... exports
ShowMyDialog;
function ShowMyDialog(Msg: PChar):
Boolean; begin {Создаем
экземпляр Form1 формы TForm1} Form1 :=
TForm1.Create(Application); {В Label1
выводим Msg} Form1.Label1.Caption :=
StrPas(Msg); {Возвращаем True только
если нажата OK (ModalResult = mrOk)} Result :=
(Form1.ShowModal = mrOk); {Освобождаем
память} Form1.Free; end;
|
Если же нужно разместить в DLL немодальную форму, то необходимо сделать две
функции - открытия и закрытия формы. При этом нужно заставить DLL запомнить
дескриптор этой формы.
Создание плагинов
Здесь мы не будем подробно рассматривать плагины, т.к. уже приведенные выше
примеры помогут Вам легко разобраться в львиной части программирования DLL.
Напомню лишь, что плагин - дополнение к программе, расширяющее ее возможности.
При этом сама программа обязательно должна предусматривать наличие таких
дополнений и позволять им выполнять свое предназначение.
Т.е., например, чтобы создать плагин к графическому редактору, который бы
выполнял преобразование изображений, Вам нужно предусмотреть как минимум две
функции в плагине (и, соответственно, вызвать эти функции в программе) -
функция, которая бы возвращала имя плагина (и/или его тип), чтобы добавить
этот плагин в меню (или в тулбар), плюс главная функция - передачи и приема
изображения. Т.е. сначала программа ищет плагины, потом для каждого найденного
вызывает его опозновательную функцию со строго определенным именем (например,
GetPluginName) и добавляет нужный пункт в меню, затем, если пользователь
выбрал этот пункт - вызывает вторую функцию, которой передает входное
изображение (либо имя файла, содержащего это изображение), а эта функция, в
свою очередь, обрабатывает изображение и возвращает его в новом виде (или имя
файла с новым изображением). Вот и вся сущность плагина... :-)
Эпилог
В этой статье отображены основные стороны использования и создания
библиотек DLL в Borland Delphi. Если у Вас есть вопросы - скидывайте их мне на
E-mail: snick@mailru.com, а еще лучше -
пишите в конференции этого сайта (Delphi. Общие вопросы), чтобы и другие
пользователи смогли увидеть Ваш вопрос и попытаться на него ответить!
Автор: Карих Николай
Источник: www.vlata.com/delphi/
|