В связи с бурным развитием технологий программирования, все больше людей
сталкиваются с проблемой наращивания возможностей своих программ. Данная
статья посвящена именно этому вопросу, а именно - программирование 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. Общие вопросы), чтобы и другие пользователи
смогли увидеть Ваш вопрос и попытаться на него ответить!
Внимание! Запрещается перепечатка данной
статьи или ее части без согласования с автором. Если вы хотите разместить эту
статью на своем сайте или издать в печатном виде, свяжитесь с автором. Автор статьи:Карих Николай