Использование Dll в Delphi
Что такое DLL - знает, как минимум, большинство пользователей PC, тем более программисты, к которым Вы, скорее всего и относитесь, раз читаете эту статью. В этой статье я постараюсь пробежаться по всем общим вопросам, касающимся DLL.
Что конкретно мы рассмотрим:
Начнем с самого простого - написание своей первой DLL, которая будет содержать всего лишь одну функцию, которая выводит сообщение "Hello World".
Начнем...
На закладке New дважды щелкаем по объекту DLL Wizard. Откроется новый проект. Сохраните его, например, с именем MyFirstDLL.
Чистый модуль имеет примерно такое содержание:
library MyFirstDLL;
uses
SysUtils,
Classes;
{$R *.res}
begin
end.
Теперь напишем всего лишь одну функцию, которая вызовет ShowMessage() из модуля Dialogs. Следовательно, перед началом оформления процедуры допишем в раздел Uses модуль Dialogs. Что, примерно, должно у вас получится:
library MyFirstDLL;
uses Dialogs;
procedure MyFirstFunc; stdcall;
begin
ShowMessage('Hello World');
end;
exports MyFirstFunc;
begin
end.
Как видите, тут нет ничего очень сложного. Единственное скажу, что можно вызывать функции как по имени, так и по индексу (номеру), для этого надо писать так:
exports MyFirstFunc index 1;
Если что не понятно в этом коде, то все таки постарайтесь разобраться сначала сами. Думаю, что с этим проблем не будет... Но если что, то forum.sources.ru Вам всегда рад! Идем дальше, как же можно теперь пользоваться этой (MyFirstFunc) функцией из других проектов?
Первый шаг сделан, дело за малым... Как нам использовать эту функцию?
Есть, как минимум, два способа загрузки:
Второй способ предпочтительнее, так во время загрузки мы можем следить за всем происходящим и можем править все ошибки, происходящие при подгружении. Но, сначала, поглядим первый способ:
Создаем новый проект, бросаем на форму одну кнопку и по событию OnClick этой кнопки и пишем следующее:
procedure TForm1.Button1Click(Sender: TObject); begin MyProc( ); end;
Но это еще не все! В разделе implementation проекта запишите:
implementation procedure MyProc( ); stdcall; external 'MyFirstDLL.dll' name 'MyFirstFunc';
Готово! Компилируйте проект и жмите на кнопку! Если показалось ваше сообщение, то все ок!
Теперь рассмотрим способ с динамической загрузкой. Для этого метода используют функцию LoadLibrary( ), а в конце, для выгрузки - FreeLibrary( ).
Посмотрите на примере:
procedure TForm1.Button1Click(Sender: TObject);
type
TMyFunc = procedure;
var
DLLInstance : THandle;
MyFunc : TMyFunc;
begin
DLLInstance := LoadLibrary(PChar('MyFirstDLL.dll'));
if (DLLInstance = 0) then begin
MessageDlg('Невозможно загрузить DLL', mtError, [mbOK], 0);
Exit;
end;
try
@MyFunc := GetProcAddress(DLLInstance, 'MyFirstFunc');
if Assigned(@MyFunc) then
MyFunc( )
else
MessageDlg('Не найдена искомая процедура!.', mtError,
[mbOK], 0);
finally
FreeLibrary(DLLInstance);
end;
end;
После успешной загрузки DLL функцией LoadLibrary( ), с помощью GetProcAddress( ) найдем адрес нашей функции, по которому и будем вызывать нашу процедуру из DLL. В конце, обязательно, надо сделать FreeLibrary( ). Это настолько важно, что весь код после успешной загрузки, вполть до FreeLibrary( ) я заключил в блок try finally. Это гарантирует выполнение FreeLibrary, даже если при выполнении действий внутри блока try except, возникнет непредвиденная ошибка в виде исключения (Exception).
Дело в том, что успешные вызовы LoadLibrary и FreeLibrary обязательно должны быть парными. И вот почему. Система, для каждой загружаемой процессом библиотеки, ведет внутри себя счетчик, который увеличивается на 1 при каждом успешном вызове LoadLibrary. Соответственно, при выполнени FreeLibrary, она уменьшает этот счетчик, и если он становится равным нулю, то это означает что данная библиотека более не нужна данному процессу, и ее можно смело удалить из памяти.
Если-же правило парности не соблюдать, то это может привести либо к преждевременной выгрузке (при лишнем FreeLibrary) библиотеки из памяти, либо к ее "застревании" там (при недостатке FreeLibrary).
При соблюдении же этого правила, можно не заботиться о возможной вложенности вызовов LoadLibrary / FreeLibrary.
Теперь посмотрим, как можно извлечь все имена функций из файлов PE формата, к которым и относится DLL. Структуру PE формата мы тут рассматривать не будем, следовательно, и исходник будет без пояснений.
Итак, создайте новый проект, бросьте на форму ListBox, в нём мы будем показывать имена функций.
Вот весь проект:
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. Выбираем его и смотрим все функции!
Это все, спасибо за внимание!