Sources.RU Magazine Поиск по журналу
 

TopList

Использование Dll в Delphi

Автор: xZero

Введение

Что такое DLL - знает, как минимум, большинство пользователей PC, тем более программисты, к которым Вы, скорее всего и относитесь, раз читаете эту статью. В этой статье я постараюсь пробежаться по всем общим вопросам, касающимся DLL.

Что конкретно мы рассмотрим:

  1. Как обычно, из области "Hello World", мы создадим свою первую DLL.
  2. Научимся пользоваться функциями этой DLL из своих программ.
  3. Научимся просматривать функции, которые экспортирует определенная DLL.
  4. Может, что нибудь еще....

Процесс создания DLL

Начнем с самого простого - написание своей первой DLL, которая будет содержать всего лишь одну функцию, которая выводит сообщение "Hello World".

Начнем...

  1. Запускаем Delphi (Я использую Delphi 6).
  2. Далее: File -> New ->Other

На закладке 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) функцией из других проектов?

Использование функций DLL

Первый шаг сделан, дело за малым... Как нам использовать эту функцию?

Есть, как минимум, два способа загрузки:

  1. Статический
  2. Динамический

Второй способ предпочтительнее, так во время загрузки мы можем следить за всем происходящим и можем править все ошибки, происходящие при подгружении. Но, сначала, поглядим первый способ:

Создаем новый проект, бросаем на форму одну кнопку и по событию 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.

Просмотр функций определенной DLL

Теперь посмотрим, как можно извлечь все имена функций из файлов 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.

Если вы захотите сами разобраться в коде и у вас что-то не будет получаться, то на нашем форуме вам обязательно помогут, заходите!

Прицепляем наш Viewer ко всем DLL

У нас есть готовая DLL с функцией, есть просмотрщик функций. Осталось добавить некой функциональности для удобства дальнейшей работы. Давайте сделаем это.... В проводнике открываем любую папку. Идем в Сервис -> Свойства папки... Переходим на закладку "Типы файлов". В списке ищем формат DLL. Если такого нет, то жмем кнопку "Создать" и в поле "Расширение" пишем - DLL. Жмем ОК. Находим созданный нами тип - DLL. Выделяем его и жмем "Дополнительно". Далее "Создать", в поле "Действии" пишем то, что будет отображаться в контекстном меню, например DLL Viewer. Через обзор ищем нашу программку.

Все готово!

Теперь при клике правой кнопкой мыши по файлу формата DLL в меню будет наш DLL Viewer. Выбираем его и смотрим все функции!

Это все, спасибо за внимание!



 Design by Шишкин Алексей (Лёха)  ©2004-2008 by sources.ru