Использование 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. Это также необязательная директива.
Пример 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.
Это гораздо более сложный, но и более
элегантный метод. Он лишен недостатка первого метода. Единственное, что
неприятно - объем кода, необходимого для осуществления этого приема, причем сложность
в том, что функция, импортируемая из DLL достуна лишь тогда, когда эта DLL
загружена и находится в памяти... С примером можно ознакомиться ниже, а пока -
краткое описание используемых этим методом функций WinAPI:
LoadLibrary(LibFileName: PChar) - загрузка указанной библиотеки
LibFileName в память. При успешном завершении функция возвращает дескриптор (THandle)
DLL в памяти.
GetProcAddress(Module: THandle; ProcName: PChar) -
считывает адpес экспоpтиpованной библиотечной функции. При успешном завершении
функция возвращает дескриптор (TFarProc) функции в загруженной DLL.
FreeLibrary(LibModule: THandle) - делает недействительным
LibModule и освобождает связанную с ним память. Следует заметить, что после
вызова этой процедуры функции данной библиотеки больше недоступны.
Пример 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.
Пример 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
Теперь рассмотрим,
как можно извлечь все имена функций из файлов PE формата, к которым и относится
DLL. Структуру PE формата мы тут рассматривать не будем, следовательно, и текст
программы будет без пояснений.
Для этого необходимо
создать новый проект и добавить на форму ListBox в котором будут отображаться
имена функций.
Пример 4.
Просмотр содержимого DLL
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants,
Classes, Graphics,
Controls,
Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
lb: TListBox;
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
cmdline
: String;
ImageBase : DWord;
DosHeader : PImageDosHeader;
PeHeader
: PImageNtHeaders;
PExport
: PImageExportDirectory;
pname
: PDWord;
name
: PChar;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure
TForm1.FormCreate(Sender: TObject);
procedure
FatalOsError;
begin
ShowMessage(SysErrorMessage(GetLastError(
)));
Abort;
end;
Var i: Integer;
begin
try
if (ParamCount( ) <
1) then
Abort
else
cmdline := ParamStr(1);
ImageBase := LoadLibrary(PChar(cmdline));
if (ImageBase = 0) then FatalOsError;
try
DosHeader := PImageDosHeader(ImageBase);
if (DosHeader^.e_magic
<> IMAGE_DOS_SIGNATURE) then
FatalOsError;
PEHeader :=
PImageNtHeaders(DWord(ImageBase) +
DWord(DosHeader^._lfanew));
if (PEHeader^.Signature
<> IMAGE_NT_SIGNATURE) then
FatalOsError;
PExport :=
PImageExportDirectory(ImageBase +
DWord(PEHeader^.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY
_EXPORT].VirtualAddress));
pname
:= PDWord(ImageBase +
DWord(PExport^.AddressOfNames));
For i := 0 to
PExport^.NumberOfNames - 1 do begin
name :=
PChar(PDWord(DWord(ImageBase) +
PDword(pname)^));
lb.Items.Add(name);
inc(pname);
end;
finally
FreeLibrary(ImageBase);
end;
except
Application.ShowMainForm := False;
Application.Terminate;
end;
end;
end.
Теперь
у нас есть готовая DLL с функцией и есть просмотрщик функций. Осталось
добавить некой функциональности для удобства дальнейшей работы. В проводнике
открываем любую папку. Идем в Сервис -> Свойства папки... Переходим на
закладку "Типы файлов". В списке ищем формат DLL. Если такого нет, то
нажимаем кнопку "Создать" и в поле "Расширение" пишем -
DLL. Нажимаем ОК. Находим созданный нами тип - DLL. Выделяем его и нажимаем
"Дополнительно". Далее "Создать", в поле
"Действия" пишем то, что будет отображаться в контекстном меню,
например DLL Viewer. Через обзор ищем нашу программку.
Теперь
при клике правой кнопкой мыши по файлу формата DLL в меню будет наш DLL Viewer.
Выбираем его и просматриваем все функции!
Автор: MakNik
