В связи с бурным развитием технологий программирования, все больше людей сталкиваются с проблемой наращивания возможностей своих программ. Данная статья посвящена именно этому вопросу, а именно - программирование DLL в Borland Delphi. Кроме того, так как мы затронем вопросы по использованию библиотек DLL, то попутно коснемся импортирования функций из чужих DLL (в том числе и системных, т.е. WinAPI).
Итак, зачем же нужны библиотеки 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, а затем, получив ее дескриптор, - загружать сам ресурс соотвествующей функцией (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) и добавляет нужный пункт в меню, затем, если пользователь выбрал этот пункт - вызывает вторую функцию, которой передает входное изображение (либо имя файла, содержащего это изображение), а эта функция, в свою очередь, обрабатывает изображение и возвращает его в новом виде (или имя файла с новым изображением). Вот и вся сущность плагина... :-)